From b7d2fe769c59b086436217c0a1e2b39a23b87f34 Mon Sep 17 00:00:00 2001 From: Nikita Vostokov Date: Tue, 11 Jul 2023 17:26:18 +0300 Subject: [PATCH 1/8] Decode only supported Oregon 3 sensor (#2829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: wosk Co-authored-by: あく --- applications/external/weather_station/protocols/oregon3.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/external/weather_station/protocols/oregon3.c b/applications/external/weather_station/protocols/oregon3.c index a211c5ad3..bd35c2fd5 100644 --- a/applications/external/weather_station/protocols/oregon3.c +++ b/applications/external/weather_station/protocols/oregon3.c @@ -116,9 +116,11 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) { switch(sensor_id) { case ID_THGR221: - default: // nibbles: temp + hum + '0' return (4 + 2 + 1) * 4; + default: + FURI_LOG_D(TAG, "Unsupported sensor id 0x%x", sensor_id); + return 0; } } @@ -198,10 +200,8 @@ void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t durati oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data)); if(!instance->var_bits) { - // sensor is not supported, stop decoding, but showing the decoded fixed part + // sensor is not supported, stop decoding instance->decoder.parser_step = Oregon3DecoderStepReset; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); } else { instance->decoder.parser_step = Oregon3DecoderStepVarData; } From a319a6fdf230560101ab3e4814e3a2c7f5a5d1ad Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Tue, 11 Jul 2023 19:36:15 +0400 Subject: [PATCH 2/8] Update toolchain to v23 (#2824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- scripts/toolchain/fbtenv.cmd | 2 +- scripts/toolchain/fbtenv.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 4ae04e2a2..51708b8c4 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=22" +set "FLIPPER_TOOLCHAIN_VERSION=23" if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index e5548f488..85d139040 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,7 +4,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"22"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"23"}"; if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then FBT_TOOLCHAIN_PATH_WAS_SET=0; From b1e13d44b8b4c9d0423cfe52de2566a6177e6527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 12 Jul 2023 12:28:51 +0400 Subject: [PATCH 3/8] Dolphin: add new animation (#2865) --- .../external/L1_My_dude_128x64/frame_0.png | Bin 0 -> 1615 bytes .../external/L1_My_dude_128x64/frame_1.png | Bin 0 -> 1637 bytes .../external/L1_My_dude_128x64/frame_10.png | Bin 0 -> 1044 bytes .../external/L1_My_dude_128x64/frame_11.png | Bin 0 -> 990 bytes .../external/L1_My_dude_128x64/frame_12.png | Bin 0 -> 1100 bytes .../external/L1_My_dude_128x64/frame_13.png | Bin 0 -> 1494 bytes .../external/L1_My_dude_128x64/frame_14.png | Bin 0 -> 1460 bytes .../external/L1_My_dude_128x64/frame_15.png | Bin 0 -> 1440 bytes .../external/L1_My_dude_128x64/frame_16.png | Bin 0 -> 1210 bytes .../external/L1_My_dude_128x64/frame_17.png | Bin 0 -> 1399 bytes .../external/L1_My_dude_128x64/frame_18.png | Bin 0 -> 1454 bytes .../external/L1_My_dude_128x64/frame_19.png | Bin 0 -> 1648 bytes .../external/L1_My_dude_128x64/frame_2.png | Bin 0 -> 1629 bytes .../external/L1_My_dude_128x64/frame_20.png | Bin 0 -> 1433 bytes .../external/L1_My_dude_128x64/frame_21.png | Bin 0 -> 1032 bytes .../external/L1_My_dude_128x64/frame_22.png | Bin 0 -> 1054 bytes .../external/L1_My_dude_128x64/frame_23.png | Bin 0 -> 1050 bytes .../external/L1_My_dude_128x64/frame_24.png | Bin 0 -> 939 bytes .../external/L1_My_dude_128x64/frame_25.png | Bin 0 -> 1447 bytes .../external/L1_My_dude_128x64/frame_26.png | Bin 0 -> 1509 bytes .../external/L1_My_dude_128x64/frame_27.png | Bin 0 -> 1504 bytes .../external/L1_My_dude_128x64/frame_28.png | Bin 0 -> 1529 bytes .../external/L1_My_dude_128x64/frame_29.png | Bin 0 -> 1625 bytes .../external/L1_My_dude_128x64/frame_3.png | Bin 0 -> 1599 bytes .../external/L1_My_dude_128x64/frame_30.png | Bin 0 -> 1575 bytes .../external/L1_My_dude_128x64/frame_31.png | Bin 0 -> 1609 bytes .../external/L1_My_dude_128x64/frame_32.png | Bin 0 -> 1635 bytes .../external/L1_My_dude_128x64/frame_33.png | Bin 0 -> 1668 bytes .../external/L1_My_dude_128x64/frame_34.png | Bin 0 -> 1588 bytes .../external/L1_My_dude_128x64/frame_35.png | Bin 0 -> 1551 bytes .../external/L1_My_dude_128x64/frame_36.png | Bin 0 -> 1656 bytes .../external/L1_My_dude_128x64/frame_37.png | Bin 0 -> 1545 bytes .../external/L1_My_dude_128x64/frame_38.png | Bin 0 -> 1650 bytes .../external/L1_My_dude_128x64/frame_39.png | Bin 0 -> 1028 bytes .../external/L1_My_dude_128x64/frame_4.png | Bin 0 -> 1623 bytes .../external/L1_My_dude_128x64/frame_40.png | Bin 0 -> 1225 bytes .../external/L1_My_dude_128x64/frame_41.png | Bin 0 -> 1256 bytes .../external/L1_My_dude_128x64/frame_42.png | Bin 0 -> 1055 bytes .../external/L1_My_dude_128x64/frame_43.png | Bin 0 -> 831 bytes .../external/L1_My_dude_128x64/frame_44.png | Bin 0 -> 623 bytes .../external/L1_My_dude_128x64/frame_45.png | Bin 0 -> 556 bytes .../external/L1_My_dude_128x64/frame_46.png | Bin 0 -> 928 bytes .../external/L1_My_dude_128x64/frame_47.png | Bin 0 -> 1206 bytes .../external/L1_My_dude_128x64/frame_48.png | Bin 0 -> 1019 bytes .../external/L1_My_dude_128x64/frame_5.png | Bin 0 -> 1648 bytes .../external/L1_My_dude_128x64/frame_6.png | Bin 0 -> 1570 bytes .../external/L1_My_dude_128x64/frame_7.png | Bin 0 -> 1063 bytes .../external/L1_My_dude_128x64/frame_8.png | Bin 0 -> 1024 bytes .../external/L1_My_dude_128x64/frame_9.png | Bin 0 -> 1078 bytes .../external/L1_My_dude_128x64/meta.txt | 32 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 7 ++++ 51 files changed, 39 insertions(+) create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_0.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_1.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_10.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_11.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_12.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_13.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_14.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_15.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_16.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_17.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_18.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_19.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_2.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_20.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_21.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_22.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_23.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_24.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_25.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_26.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_27.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_28.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_29.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_3.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_30.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_31.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_32.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_33.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_34.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_35.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_36.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_37.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_38.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_39.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_4.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_40.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_41.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_42.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_43.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_44.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_45.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_46.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_47.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_48.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_5.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_6.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_7.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_8.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/frame_9.png create mode 100644 assets/dolphin/external/L1_My_dude_128x64/meta.txt diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_0.png b/assets/dolphin/external/L1_My_dude_128x64/frame_0.png new file mode 100644 index 0000000000000000000000000000000000000000..bf07d03d6e633a1cd9725d181b4970b96d216058 GIT binary patch literal 1615 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 M07*qoM6N<$f?iSKcK`qY literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_12.png b/assets/dolphin/external/L1_My_dude_128x64/frame_12.png new file mode 100644 index 0000000000000000000000000000000000000000..19fc985ac866535e616f20dacc7956deb9bf1f9c GIT binary patch literal 1100 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_14.png b/assets/dolphin/external/L1_My_dude_128x64/frame_14.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3a84fff837373f3e503913b6095071d044c33f GIT binary patch literal 1460 zcmV;l1xxygP)AxL1WuIkKPX1RU|H3IM(9i+z9&O$8Zz9^wF+NT%vb zZB|9*Q&fP8a_ODi4sdt!p8_fBsd}Z9a(O%DIywy>qtpAm}OE zuNClo%NW&lF99AE=|2^2skHPywQ% zIMWgCw9Kp^zm(u5AX3WzvLaCA_fj(#7H0|10ixG)>P1Fo z&rOOJcaZ-P`P9#_PJd?Y=VfU)gN%vSeD$(ow}WtNK6O|I%b795HYo=|+@P}^f$Kv{ z?k*>=Bz)0_XG>24vmF8UG1JeElgZ3O9t}Pmz&ikKmRd^A(!)7}G@jmWr4v{qKeDg) z{TBSDc+xe5teR{No@K)`9xeH!IdzZY$6ky<%ddM;I4w)hbONjNM~|i3yR=@%Z9OPR z1Lb~Y>9l=**6%Guulplzq+knM0=(-wHPBNOAu{@A$h~&zo_wm2#^B6gDCzIZ#0f{i5lg|$9Kk~$4M*JlVPYP!1Y~R-cVxsPM7-lD`lVe! zS~6NDb^&le-smr7yj9dXf>b{Ov+o-9X_wv0PpV66?Y{zy2AS=h8hN!kg4AvXo{65d z;q6mYp4lZp^2%A85@sDiYA+#z{24o$=L5CYa^bH_T#0|qI5OqV2k@Y|oI$&t(k7Fz z?h5j$Jv_HOO3SB)*Y^?-y(UE=;%vZ^y=NwUGd8&A9{%1*AT}l?9IN-RQD*`xb`d+I zU&|LRFH9z`)QwJ^ad%v)MiZ|-{Pyr;_ zxC^N5p5-01&hC|dynXBKB$BPXQKjl6kTP~B^a;`IZf0ypC4HWJf#XNIzOWI^_1(}j z|IzrPoO(o}MNzb+Rsp!{kB>nkkGW&?Q;3RC={j;u*1omZ*OMck!|Xdnl0WKy6zS2| zA0LCF*T|D0$+agU;e8BE&R})0+3~(}0C zGfANryU1^?{r-)h)8(H-1ZnVHcPL9FsnE7mpZpql0^t?~!iiVEwkD6e6i_15>I9|e zmzCE^q(fj5ESWW>T$oTPQsDh8>|Fcb0aCy&fSa_P&M_iR+m>pBor{vs*?wonmnrkA z9DsXCO%!A&PG0+*l5kG2A{a8(ezyYPmy^hsCM9yxMr-nnD)e^R#5XcA9wnUV5zi^W zZ1h`SIKmDGt9@#cx#*{y#M&peeN+xGnxF{GPTI)M2wLT$NQLb+lc>dRvdOY6a)53R zPDX^CSy;&&O(Gmus$3_O?B58=Kx^{fyS3Vp>TzWx`L%u?OP@flru+w0?-z}=lz5x~ O0000zkvSgZur`#u4^_xmY;$ErOv@jajJ z4KR;yqW~7`j0oHz>z zC3-U|h2L6|0%QmfVi|aYFY1NgTA~NYvf@fbcv6_*gb%HSFV?+on0OSKuKbfdf+T!; z0+8;tR09D&KsXE8NgcQ0r&#d}G~sja@fheEp}Z7ak=aX3vEm8tUyAu$y#%+BeU+aA zD%^KPcv!6R+u zct$Z}`7@C2?SK37HteyYYDUj~`Q$1k@XSm38eQMlF~N!E$4tC+3LEscZg z)j0u$wkAzB(mlucO!N0x9EUqGytP_SFynP9){|NY0*n?ZWv_QD`cyrwTorQge1NSr z`LAwApK<-3jesI>^WQ}gvXvkb_bq)E+5MV7D+FX~ady%ZLDt?lI=I;5NmR42&1g55 z%taQ*tpR%n%UQn?SA>E{ogyN`O?tQ>)tXzYvBuFR=!1|ug$WBX;=Z*$pvjJg>gf<* zbzL5MfQU6z7qVvPM}g#6{tu&fo|1t)+&5!c5mgs$MV@6faU@;##9wVG(c>>O5w7h3E8?QO%76Q z$K$zvXg5o&Ut#%`u5+6oMpoL9gGS1X(e?FT^+t(U zuAhS5`63g?+$)4{;UA6tqt@yHr0dgH!ZeL}s`bN$qaMtf%&nMX_Ii7xod)hHLpYE6~EyI}QtG&i4N@ff3AAgld(itt-IyX*>PTTrnEZLVyg z|7!RB{50?h?mv5NngUQ`xQZXMLiOA)dVsqF)>x^ecQJrU5AgFOp>sI0*`z&O>E5lV z3xLgT>9K<^VuYTF45_S|hg~Esk@{U2P0_50xI^R0p7o zn8}PWqD+h=V44EaKJ65AwIXoB$9-j|0B#|p&5shLj6^D`T#_*9+N9IZRo=j&lw}@3 zT2R);ql%!#_J~)I+&`sT?GaW8f8Ue;ZJY+0qVO<6LCAC1reeC|!S)C|$2Ga(c*iidap z5-oZLq#EFBn$I=a!cI6X3N!$pkBdH5MZl2k zJTHWKF-OVa&DUGE`tw7nwH6KHsI`8a>#?%F1;YGNuHO71A4whN`QOs1JG@{}oF&Hz zo{*EauHALOdU|qMO{e(bTC$jLQ6D@HYt&$Et+mm3_Z~+pubt8TkSDxs;KBIbO z<9~)6Kgsj|J_B$MabkFxt`URc3&mayO>^Sdsq1JHK=-|Nyi}wWv%SN5a#=QkXae*G zIKAUnMA$K<=ZK7%4!h&u0-sKV>ZDzAa2;6i%yf-$R33OuGM-NbRWH6~q4N$tr^Y=e z!m8=mY_8exbCHcu3SA%O6g~lVN#LS(Oy|s&m_Axl+YA&B4kdx>x1PKjT>hItZpU+` zk3`ny;l#Dx?b9Jy?xK1EaAde5wB~y>!zirt zt-PBxy1P#Rt1+e(D|>kBXFdTW$*E#@$ZJK+8WuaY$LR?G^E^0-&d1#^9lLNX?GD7(CHXb_Y1UIsn%2(z7I2~oxSfxm zm)t9vB%{e=@j8nzALo5m$CYGE@4ODEG0O$KZ}v>T-rD?&p%s%lYIdSzT?^B(Uk^@h zk<5}bi;BbD#tyMP%f>3!05Rz-t|W45cQCZKi*CH^SC zg&C(e5w*!5>ZEoNPsAo!Gyz-}H^!(bFk04m(tNC&*}n&fLTONwWHmC>&DA5-t=9VE z{$FUNs-|Z#?QHVQKyRb67@C#Kjczfs~)$5}HsN!d0jP8f$EsORJOVdy2p!EaB YADMSfJkRd}+5i9m07*qoM6N<$f@&T_hX4Qo literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_17.png b/assets/dolphin/external/L1_My_dude_128x64/frame_17.png new file mode 100644 index 0000000000000000000000000000000000000000..fd910ce5a61c659c7caf0486f2eef29eed874edf GIT binary patch literal 1399 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 IM6N<$f>4IGLjV8( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_19.png b/assets/dolphin/external/L1_My_dude_128x64/frame_19.png new file mode 100644 index 0000000000000000000000000000000000000000..d602a01d5b14b4134e49fdd08ca5441ee263c2db GIT binary patch literal 1648 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 N002ovPDHLkV1kz(xQ_q; literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_25.png b/assets/dolphin/external/L1_My_dude_128x64/frame_25.png new file mode 100644 index 0000000000000000000000000000000000000000..832c2bde9f9cda0876517a95f8d7b6f9a7350b11 GIT binary patch literal 1447 zcmV;Y1z7rtP)>{VeT7VyGKIc+FL&vU5>7JE$Zr}}0-i_LMv$kN z$~>1vA!1I8+?|+GYijN|&Vi#?e!SMe-sH|5`bhu=j4+7wc zuOQWx@61H_DP)l5aA%iy5reg5q-Qzlg?Dd0x;#C_;TD=7ZtwPPcAYN~q6!kJD)uFI zA%nIXY8B$*Mv(PGOLl;jkaj7v5xuy6c8!;xcfYE+-rDgEuY2HWRU6@2@$(${d|p@xaN$^2PK{fM+mUm(ywQKA~W9h z%*_>1^f+T!`mZ6PB;51b_joJIc?G;t?^zXmtO7J+MhrVj@^L?zjU;LmN~&E~0#@qK z{CZv2&qk2dN-v9`(y&ny?BG;uKAbP46zK8mI|X3YOD+9-1ypY-KOOznyQwdzQ#oI8 zE&kTyrXPMpz6ID3n*6T>j~Y;?YW>j2cPl_Os(%xZCcis@wPh|Bv{Ddr{tYRBcW+q( zb~>)?tI2>7T~=92umYhZCwf}|Ljg3V;D<8KdS&%ju}}rap{3mkLWlu zgwD!-)IQk)E-Lq2`_%?4$pt;G%7wHaEURXj0<(SEp; z+nW0TGo6poh#(PS6r_CYL3C9TRo=*XZ)@%Zat4^`kSJ({uT}p~&4U|QLbOw85lF9BM4Z$Q_x{l`AFcsB#-rC)fHn?G{;SAfMJ^q0b^l!oppigB zdu|mXWaYod$XY*Add)mvod!N?fL^1oLIMo0{P#H7>Ze`j#ua(KngqWTpqB`k1ha`y zA^$UEmyT-!0WG z=Xstq0i_foB9hCOQa;AKHTYa>&DG#^S-&&ZUV&rAqLqme=_K;=(o`6P$ zXLu6LJO;fgwQ6xDzcQhtCBsqjUe9nYAIj) z1Z*WDUd@l*yl*gugGd*KYZ#@c%#PtyjLq`z_ggL;%ik z*2flF!)CQ`zr6`$@pbfW?@+JNgqmek$lJNz?di_OK=fY+2io2p+2_w{6?;u8gx*~` zeO@=TRfwC7pw^3)yTJNRaCFVP@##UPZR?r_vk|mAo~GnXz>T2R*e!0dOj#Y7=Ch;8ri1y=)C>>p-L_@7pS`+iWqN6S zuFmL8dRhw&`VFN_b}=Nnl7x!gXr}<&!0vRXWlx>-*-S%ppjID zuCWqUn_e>pPoKPtddLqyBHsf10S9kcqw`zWrBc0hr03V;s}%vV(ewJ&gY{x5QVjOg z6a4w!b!{H$p-YCwo;7-(ix#FWkCtmj^jUtcVV=(gHakTi#$nBEdB4U>BxQ_XG(Gf0>~I8V zdH&2QnCE3Vrp=vQtZG{;k0$Sa>psuGGsC?a0nXp2T9#uGGm4CGjuwAb9pe>&En`Ow zFTz(OBJq2W2w=Xt7sTBN8l zLur=tS{^Kq(d+db?gY+OK4+X0FUMy;SHj|-pKDPCna|H#wHLn+8Ap%Dos`cZy)256 z;tyFna!+(V(Wg@ReMqE6k5B~I1xWpZ7#mtgMCVI|W_b8;1VDvyCurVJG{%SK3|^5I zna}G5SzBm)Wgg)OpyjD2A%ku&B3eabYrZGb86Gc>(7S*Zp-+Iule}h~^RcK5kdTkA zgDN#L|3DUd3basfg@3D{(&pB&Qu>=g^4U@OMm#$*-~1wICwL0=JVuW5XxLhxBRb#W zVOb3ziA1W`s}0BVOfRLil&-;wCKfVeGZN;zYJdnV!?^-z_(yYT^qI3TD*~7`Ko5aL z{crbXp*Q@?nqU@R-r7kM=kuz^N~o{3tnLIKj~D{BS3~B zXd!qNEN|!N5zw_K(Exh0=?WL z%Oji=^C?UOkiN|Eph{?XF4DZbL^w^!8oC6a?C)W=;4E{aUlm0^OFNMmY9R<0J$lOL zJ^`Bvk3#X0yMLOJHM{_LVbF?~hEoN0DjBd&UjY1HYoZCB1zfxT#ri3JC4m`Lv{?ef|9_B~ubilGNjASc0A6_3 z#}-_{X10*EvHl0^m)7u#ZttuD;{=#RD$9!*@AhOCE_3{^s0pfndU4ZtYWv5S|r0)bh&#$iZWqwcLePj7<6WH19t-$O=b|&zc zY9Xk1?de!e{EG1{y2E3#?*cqYAJ@*ZHy>TN6bqwRO@=IZb@#4XxHpmMJ#QhBhXA{5 z{UU`d?cI%M-21+Nb^%)U7=3-pO?sqSNBiLto~MPX2!VUv&$27{zJCX(9<`9{60oPF zYuzeUTYu$P5ijsv2$UdX;(N8Hvgq|Y1YqV!NBVmQWS5jzZ2h9|UMv+{ z>4*zxZ+yZXPlL5mRxxfvMupW|ikFUE50H&W)~i7x>n;SQ7A`3|Ypysnwq%d$7n9(H zPw)h}96(!u2K?fpO$yiWE8?Mbt7%QhqH~N|_z19k@f_MbK1gui_bs{onc=|_3#HnC zRgv8Kxc;h;?fpDsN_sTq0Q7nVw4_KPM>K(=D{6jDEu94pAc_210_EXS$|)5%%{ zL}=;!#U(-9c~t^9BgSU+m+c|EGUeL$Vtw!YCcu?tv{0QMHw zCdpm*_EXW`lYdk~+Bvm(SoCVH1I)%uL1I`v28WS3dp(eLAM38CSCa>LDtg&JCq&7r zs?poI(6+tZis69*b0LvKM@zwJHc)%w?t7k;e^9+%# zUWS+D5<;374tao8F0lgE9RRlr(%MVUHE0UL_bI%IDgL8<410jt8Eyr*_|I7(78*X% z`&|FhR@&l0vhppuK738}0NUc71gra(tk8Sp#-VbxSo|ocXR(9<_KTnsterv%XVgOQ zb?Xs7&l!`}mdcazM4Bi$on+O^1KN9P$ z&tVy7%VuOfagaEkzs(5dHn;O(}VB4L)xYQ0000n literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_28.png b/assets/dolphin/external/L1_My_dude_128x64/frame_28.png new file mode 100644 index 0000000000000000000000000000000000000000..94e05f94d9d191742662fcbf308a2eafd80f54df GIT binary patch literal 1529 zcmVoO27YwfGe>VrP3^|=~UF70==pIvy(=oBY5qI`fKMWfR3p0};W&ar2p z+yGWs^=>1m@cvUUg#je=F3@geQ=B44B}xror5Efnj3;?Nz(NMNJMOGx3uOjiEzs!V zY2Lnz1)k#l0KW_n!rhf@;i~B5$qU4RjB`p&VieOEpvpLQg#k%^sW^34{Dy!8&={WL z{Qw09@MQXb^4t(5IRFQz#Y&poMY%NgKV=61P?!W#tX`DXTDdZ`*pFl3{oOHS0H-Vj z^+P6o3UmCB;Y)yoJ^_kMtY0q{leuzHaqvhlBqG-aNuWY4grXQJ~k4S-j^ zEbLymsGQP%UVCo$oIE+WVMpJic=3*S2X|0@1!f*+_ad?zk-oi*^ow|5N74iTi}E>6 z@%|B5&P(SuR0#4GDI!26GADyPexBz2bbNq$YbF{%#S!bOMYZCfhC9Z7?I>J&FM}@w zpx^ZO8bQUjan?AQWa$)R0EjDFo1Zq9F9V33$c2X-4^)T>OOBibO1R6`4QEc1DlnQSTRx30iqHVGt!*_o9;L|Dq1`u&sy2t@koTB9!ChF+eq{_qblI+md5-@^?y@p-z9% z8|zA6?imoF^;Nwvk=jF5?VuMk4Z=Z)hjEvnRv@+D*G^}_7)QAUi&CDsAxK+^rJc)Kgc_IQ3( zNSIw~0kFU6_^1SOfwZE~>^yq_;t3SN{WdQoJ!~jzJEL72Y4>C1| z=sIYLLZzcXxqqKq~|?`k)YNAGB|0+5M8jS5=V?6s(kDJk-VWt z@d)*cpe$&1x!VXRGW0*H+}>O&%e)r-XV$;9_MHQafLifrGLwNdmX!T_iCT}6y?#7+ ztl(X>Fs_Y_&tB3?Bajwk6(ZhJMdzcrfhIrK!{~UL5n%tN48S|b3Cx6N;r#ch%BQGO fJ~bx(hyDKnt&=3EFH=)P00000NkvXXu0mjf(FWoE literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_29.png b/assets/dolphin/external/L1_My_dude_128x64/frame_29.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce384b165cb3b83bcffcc872254ad52ea846971 GIT binary patch literal 1625 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_30.png b/assets/dolphin/external/L1_My_dude_128x64/frame_30.png new file mode 100644 index 0000000000000000000000000000000000000000..8d42b8b482bade89f498e5ed1cae3a82bf89ee26 GIT binary patch literal 1575 zcmV+?2H5$DP)ZS7q4@pXh&b}8Xmfz6By2Q`mvWB6 z$=rd{R(}+ChtR2ri18ub_x*F;-?J z*?^iuOBV2L;G7Tsz8Y15aESw)5~Quboa;$7i42xh0xOiXYa6JRjkk{pYai8Y^FIi5 z02(io|9dk4?%fJ!NISqc)4#+xku5;d)h->jFaY+4 z?A4OIXkQf-K+Hawxu+c9lt>sK9aoR1qUNvFNj%`TrO5a~4seR$F{&J)qvqeiCY%h< z{9O*<5xior)&i;uNA0NjcfvbovjbF8SQWI`BjN}ZMqAC0nSV{09Dw_0jt8BE8tOaM zx7RS)BkWE`;Qngo#WP>D9OPI!SWS7Bv0-IN$FWrhTD#8*B*3{mWAH}9b#Do~148RK zt|*6pzsaG$5;`)S8WS( zobUU7J|5Yr?-6yL(lK?zViPPp7D$^#-o)ePfKB7zO`Q42v_x)CV{?_ibP z=K&Tu-T#`BaL)l)gnQ|edREOSkWL{ix1~9%onR1S$iZ49N*BN?TC|w!oW`YJsn`_q!QD$_`Ej zr5s9`*8Q|<-kr>4i7!M3z)V0nM@8APG9u0@RH|EW?J7~pJW~Eo+4;JzpF$9K)dX9B zi#6Kw4sgLAE}?exwObC9g|Clfe90+1;NGE}L91R_Xmhs9|mXhC3SO<+YVh37=s^@a_pK!LjNTzN2knIA6V)yBcaE>I@yBLa^hy=^P$ zAIgOH54Y0seKiAUbZ(ivrX;HZGnDNylEiY=u-f&;9>LvL(-xPGdD60qj3i>fiaxbi zC)<}4@RBjWykbwm&#Rq)lc)6)UA0!_b~so^&}Z$FWq{cTH2F7L1!}sm>Plk)&sKmxT-KIifNwDR1UD~k_2Mhno_mjbKF1&hb{g>NryQ`H0e%ZO z14}_4Gz3bH`IIIp7BcPN<(dK;axng-*Ox2|?&)!cm zz!c(L{gBdjLfcy@XU8iBKDS;q`IbfI1?AA*akDW%bUmQu+qQPh|cNX=F_f93$n7)VLx#NHGBC z2;M>Masmv>DIiN#(Yoh~$HVEIa$4$lh;{}z)d{54PxYTQV&;m+gP8?BgYEGLJ92bh Ze*l(B5h$^b7i0hc002ovPDHLkV1m%s`g;HX literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_31.png b/assets/dolphin/external/L1_My_dude_128x64/frame_31.png new file mode 100644 index 0000000000000000000000000000000000000000..ac926d7be3120cd6322739b62a89f67b326563c0 GIT binary patch literal 1609 zcmV-P2DbT$P)dSg`=RNpK1QE70NrC`5^|+t?gFVfT+Q#-mc$$_1FjSiRs?WTfQmAWta)ehRX? zpSxEgR~I?T2p}y$cBgW?eYF?p9!rvq$x)@puLlTXb?nbpR2)i@z;{f3yf|%+RmpLC z6;QQs+=O>j33I&bx{f1;7r!CE04e|Au1m7u5z6yCUxhRQ-hBsnG3N_g!Cs?yD**1< z3No~K0IXOgYj?Q&DuPS0c5R{(0(6xqp7B?BBFg3O_JG;(Qv^8W?Hn`){GEjq`=eu3 zbO5C{kzRxdaEjqEx`;4kQe?x`M?8v5ljM)H+dnzd@w_Tin6h59BUJ+WA}5r)v6b~-#ZpJrxyai zn<_m4^@gxBrea9fSI7QL-OfSV_1yV~wdHAjcnHl+&q4W8C zj@w$6%&>48q(n2TZv`1FGO9L6Bnf7}3G}*>li<-Y+UNJm=B`(fEQ&iT;8W3A213JE zS3@`?O81WOj{ugK9VH2+L|2CGMR%K&z(K77qL2(?t;H?RMEeb^8b6s9Zxq&-V&AF&s!sO^#DekPgvXOm&r7daPfbu^ zC|C(8l)qE~(qeiRw!3^S<$$Kkn#J&@y-yUJ{(@9VQ~g z^So@M?D`b~sDyz^AWmDvidHqm>R4?>WRBS5KzqWj>5W^c)Xx7TNbY|Zya_3l8{ucT zbI{%<4^RzQ4_AQm0;dwV+G{~rDlfMOkg5ZZVz>!{n0uLJTN0T zrr@JjG3`88+EJ6xKlM3#*)t{DboX zoPBS}_>|k2gLHuk4_w0I(Su9=q&Zd8+T8UOIf}Nquj~5%RWP^X@$V|dHFv&i2YGeq zLbdy!M{L)x*TK?`?{NQ=i}ECemIcz}K<^S=@MuyzMlZo|f(y^V-`8I~__|oi4XPB4 zz*9F%wZF$06?3-|H#3fB4Q}hs?}}TIiFPDTE5_{(S$GVKUB7)0Ld6K`d^NZvkGr=D z(wr*Dp7Czc&;sXUsp`Ic5=JqFI^P9L_G|@L+Hpqr^ccE&2a;SVH^SF65ANy%tTO%t zc3=3sS5C+Mp8UfKZGE|Q;qJBCRqX?;G(MgL{96S)SI^vc*8i5oo$LdgX#6T@**|5l zNIxPA&P7X*u9tcMZLpsL%`;d%hqkFap#cA3a$AnKb`u`>UBK=Bs=kBwH@*f97u5%b z$B6OGIe>Lyr^8wSP*sq0Klhw;o-!UhFA;;g&Xsy49w3w9o&qZVQ=S1E#<~f#lB;|D zq;X9iK$_e;!Rr309W&2+j?_3(PF0auOY;fm08_{(>~;(;oKcy#cM;+9nw)u_(kWYY zdG%&<0L$Xx>nA{qoAVwiVZ!%B7FBY?oSyhxgbZm95RFwZt33fx_ir_#w6nGASid<2 z)IGY6X%CPA+*PN+>i#oE*5u6V@W|`n1I&RUApqYC7kL%9aVeJ%|6hH? zI!n4r;PQxV9snz5nM4OT0^{F1jE_A4Ha6af(jS~hW?p{)K=@bv8-Shm00000NkvXX Hu0mjfz{LRy literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_32.png b/assets/dolphin/external/L1_My_dude_128x64/frame_32.png new file mode 100644 index 0000000000000000000000000000000000000000..35070eb6b4a23c5e6493389e66a563f9171ae144 GIT binary patch literal 1635 zcmV-p2AuhcP)<$Zr!=JTD$MedqQ_3dNi%@o2Cw&94$|7YAy8wfGLjSn_|)*{!OR@3Z%0F- zV;TH4l;;3hf=fx?j%2X&yRIvBVS1l~qgOT0|4ASKDgPLse=4R8;5UT-5q-Wp+R~kG zroojYfmFaufp>)aod(V&gU1=bs*F|Rzha@rz295^BN0Q&ak#Op`A!+YDW`KZi_Vfq z>ph^nq!UdJUb!>o8!~`X7M`NY5IRjQ5kxU7lhrfuMh2Gwcoe&0ua@MbwAvjV?y?XKCDb5-mxt?7T$dg+OftjZF`WxYWX21-55%u_}M_Nj!kzhypC_!ul{dRbaK{5g)8_&#zMxN-Q)uVr82^AI8lRwNLLUU)Q zFSDJ?L9345p4|Zn5UZYf?vaB-#z9X+My=wZ2R7A)2loN0tZ??&6Fo(X_8V35;l5=ttI5X>$b z+L^0@w2o#3;6APrur=NgtR~8g0%`t}Ri;^VyvC10iUO_(V0r$EIvEL>9)pyUzj02K z)@<0XdLIR{p08g8OCC?}vm$^M2EBWD1$OM!Rs1-<_kI664(39kbe}0y$5>Y6WmUv3f=`$;WyZQjD7Qcfl1IKYMor?E8`3FkR>P1L;(dZ%9$4QG% zCo%r50@n*bfmS7m$9sV!?%efN1$Y+!5LF zBg6B@1*U)A#lJ4{djQP}Kg|dv?~f{<&fj}Kj%DbQZjX`0hjRdHVxI!E0Zs`Okc^-?0hNqc4#-2Tr+2I^I)6IdlzW(3gqb9|u)P$}F>py8kMm_F{ga(uL$ zDkJ;X<`d2VI+1Vib_y%hM#3lJ=~@Kf@~qheF(n5;lt-VR0Bhgu?`R1VeJ7$+EeuO? z(y@p!Oh*9C2sHHXPBDaN_(vB}^K9!mm9O&{WsmA{Is(jK^s3WMNC5lU0M1M{J7#R4`-`6_5;=-tSm^8Ug5H hQ%BVP#(B)l>klc^gV)~j;w%6F002ovPDHLkV1mQPe z&+|NO0ebJPwbpR>-uuTjZ4COXH0BAQEL7IW8mncB&imdeNL0Y*d49cfdV>*JtT51406?MumB=4_~ z{Uu9|1p2NU7V^%nVH9%YgFYM zS-fTk_*Qroqj{ag64mj4jEQr4*j;S|S=T(^gjm5MnK5_t-)TjK#z3!CJE8YGM1K-H zU~Sy&{fHbKA|;hX|4|ut7*ho9KENu$GbM@kz9%P%cu{rZr=m?<1fOObjdYZPNJfNS_v(be^de5W>#dzRm#bBDbb)Mx}Wvp9Y!#dGvK;kV<78@ zf6q=ni|9u0WCu@=paPK`h|}v&2*3{s+)*0%B&xlwGKORJzZ=MuRG+j<`KBk_@!K$) z?METySllzwF(pXtcuv1>ivg-ZH=<$=$yjeQV9n^Hq&5nxbv=&M2wwQnLdyZTbFX07 z{gRp^Rq&RY-sEp3jaPL9HRi_H&oTfqffgR3f?3x+Uf}HwXzz1O;lWL8l7Xa0R}L_v z{D^?9@rEE7M+J2v`&B6VWcaZ{A|78JPsj7sNfU+#i6SF%NXehJrixnoBrNCdz5n+w zVVcL|c@+a#bRLc2E3j(p-Bp&R*PqY9n#Zr8e~zLeNug&0t#K&3WEB$Glb!tlOmOKn zdfr=Wk5!_q>R67-}$5Yk)`j;@yQn>4@U@146^^c<#;_!LTT$Q~8Zoj2DrTci# zV(?HOV3+a-kiPJGbet8>NAeHvcvde$+81p+&(Z7y>{LE3g8p^^*BV*7w<_^?d=#F< zojd+i0TJcjoD5+Sq-FoNtRqrsX;llizG()KCi~Z5F@uLWDmw+u)`D86{iejQjK`$%bQj*OXy^U!B0QK+j|n(3*Ea znwO(|`uFZ5%B;z%LCPbJF#rnYS;PuR28`z?l#dL6%0@@jesmo(>-q!Mx7!J;WBAzs O0000 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 B=oSC~ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_36.png b/assets/dolphin/external/L1_My_dude_128x64/frame_36.png new file mode 100644 index 0000000000000000000000000000000000000000..d2cd0c970c61dea42452df834901ad2a0501a55c GIT binary patch literal 1656 zcmV-;28a2HP)uPiWtMMVeVe+b?pOUj<{4EYp z83`w{KsNfNyhrP^8q1QviEpIIiEiB`+1NR%^eA_Ll?L9)0;|`4EWfJHS6YGtQKey} zunVU#SJj*>b$|}+aE{sE7~?p{@S=1H`1Z-*ASIz(yCe%J4`Eh8TKlCl z4oxOTR7QmpvJMa#Aa$_wTfjL(=6%@aK~+L}U5o={Jc>KmS%J#kGHnjLS$l;n2~=`u zk=s@UU`JR1YPXDQ^PqaqzZ2fTBORbJ;j)x1S>tdrqCY6fj(s)zWgLA-KM7kkkPJ&i zbkoMgMSNt#C#qvae)&(rC>rk=%xapHCTu11oq+qgdmvo9Wxr;1y;53q_YvPoB0^mC3k+pM}p`r^jaejF@q&PeyT_pR^3?NO;+i098DVscmIwK(7KLV6p;*7an|lZNAzQv8TnPZO`Yzpb(xz;@1csv0lYhy8cS4V z{;LvB&-mvv%l|JRVkB!n)^d;QqdNLEc~zx!M(-C?WEHf?vmkpsJ7sv}S7H91;CY@> zO24l6-z!)uw0pjUABdJIFtPdube5*vJgg{(r4kqZ&7KBY?s1^Xj}C{Zl-U{a-3F(a z5bq0ccy$=YQu=lD6?IZin-@FS(9++0=EZI4N*g%c+oT?7gFRt6g6r~1NP8~Ren0;i zBqda*7`qDHhdvZ$!dJ29k#QzG?w=(vSmxpuTfT;15%-DC8w4H zz~^4rBREg7Dyw;56(Z#wM~$4bkeg>UM)y^ccn*Qe% z+x6!-Sc>=x`kx)bq;N9Bt^^AvtWg~z??su^+XoIj%C5I3@++x}tK&VHxOD=h+8_2C z$tNPqVWf`t0#`(wU0+h9cNw2ja)M$6jwR;(%E_vDj>C$*32Wqk#DDuq809IMfmk5z8&Dspmjr6y@P1S;YyX-IuUsit!fhIZ zzcGNNRPaoE1Rjr{&vr+zrbBhV+T40%4xn8tjZevn^us+HF#=~ik3L$ztCmo! z@m&m1sW=z$Rxh_cr)B@yrJxENMMw4WFu=Xq_z+MoPF_|bQkr?cPiV`X#rZj=#Lj&z z1HgfB1NRz4YtQBd99vnxs}qo?fzmozJ@FBF7Iv_9DafcK*y{{w2H+-HbBGSsn2z9C9^ktZR%iAYQHpG>9qTvyfVxNLGtB^= z38OmRxqNuQ1 zd7kG0<2a5XiPf%WWmV*$aU91OWBk>F>a!z{=>~9T;zz{bMX-fVbdHvwsX=09euAof+C}-^BuNnf+t@XMhx*>SPO$ z0C=NeJiKA{kFkIOP{!H43_RJ%7QVX`cp|QV)}2Pq*(<0Nlm#9Oyu>*8aQNAd6H0WCp!rt=9CI!3bI~ROHZORu;Q;VT zy*uC;u@eX$y>?66Qh(XD2)l;@fE%OI@$7Y(|Cai$G6HJNx7zMnO56b8gZHp{AQ9c@ zy-4zOm&`wcuC8VP@I9}E-3E*5nJwO+Gk!z`(E1!L3pD_qDRg;K(9S1S=*0 z?aTW{L45^%21gpDnar&07+HE9D+OAG?q|L1K?Z_X+=}Y`jXszu2CW1-(VU&Qt;!3$ zg4cELliiy>VcUnCv9s21{#QkJ2WKXM{{mLO+gI1(MBkkDBjs%kA1ZJLB&B8;87*^0 zFd&LS2wzbdAE|SxlT3j{O2p_NI?KNF%Nl28q2|(WM-j|QjMYL=GODv*8k4l#RmLi{i7YYE zzP#roStq@YA(^eeZ7WBl~%M@tm=`OE}ROY*d5XY{|TZ!|PN zX@33jL5Rcv()F3jckXB5(IrB@2g*WdXiMA=2B3;c3?N;f3DlNP+RjSn{P{?K+1i0; zJHBN@!(#R?Jz1Fmdg#vfZ2QdfUXoP>l4-qM?pF?QC+F{kR11ir8yHA?Yc&sb7w|^s zTlB9vMJk}I0Lso*!K=goV>~1Koehr}I?RDW*7+d@xbU5T$=*$o0`|?*)O>uAsa literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_38.png b/assets/dolphin/external/L1_My_dude_128x64/frame_38.png new file mode 100644 index 0000000000000000000000000000000000000000..70e56b168bf780745f329c7eabd85a4f48c5ae39 GIT binary patch literal 1650 zcmV-&295cNP)c;^d8YrnSe7xyUx8+fKgNH`1x3R% zt=owcj6aKV2Us!T-A-_d16H7g0XSqGW4Ey-c%mQLb;}Q9&~ZHDK9z#3q&t}OYI;4< z8OQkPkhqS^n{b;_2l!Xujs6|r7-)=7#{iTq0h$74l_SyU>gC~Emodx&RQ~@NAc+&B zpMxb^2?Lz0fGTvcSSLI(x9=eT|qT9Uxbfkd|NZge|1cU+$R7QT_lpl?d*{%uXoe z01?HmSgR#DDjdOzrc;bRt7o?ZSVz4(VC5cFjBw(qAKisyS-|B0(l2u&=u#S%MUpG( z2&WjCL-q1zIso;43#%39k&U||&HOBFr(;z7#EygN01;~09Wh=xxvd<1zk4niTEtUX zGdqEY>j>9J$6;Wrt-^EVAMvQ*??5~fZ9J;_w&yDTD^@HZxc zbUCP4#@O4yiiv{|+^ft}0??;+M{(Qg6E z9AUx_2cTJH6}oQ-Em<{3IVt)jXm-(-0t=7#$kTh{w@P8(Au2?od8Ir=|9x&Y$x z1pG|#cnujj3_1aodCU%QP9VJpXctzJBZ#{I?V{y$Cx{sBl#@OVSl3)47(L?VY$qNB zRifKzI{H7w8R*-i>oM0(oRZ}oUE$4Bz2Dv!hUW5k$5>L1>7 z8Sm){Y6RLP-R$>J|2_zkwh!Dp4W3AyfwV2MAZ5_>dG}Jlp;9I~@0;HINhSS%cJNt5 zo!~U1a?qSK(-+neSH51aanqztD(U|x;I}1$TDFhdWE>xt*g0;?Qz{U-sq4n7iz?jz zYH-^=8u>qxK#c(^ARQxDj3I41cSkeGkp0fu`*TS(cMrhX7uP?ukyOr3iV92nsy0^U zfTZ&5f--9c)*c?(aPPf#eI#RBA6G#0UJkPiA~A&r8RKW=k?JHnN&R~4c}vFCKJEaf zHaZ5>K^xk3hCQm-4fo+?6))pxc97P1+#c7{@|HY9`q&bD7Sy=d@LXFjq~uSVQ`8}{ z&d3>0d+#Z|?ccu&mLi@$S22J^=UED^LuGZWd=Uefy?KM3L02(G>wQj#aNt%5eEh_lb?#)^2& zE}r*VfA1UsH`3OcPFPBeCS_1+R7l%<#-fo`<&>O}aJ?rN@V-%YJEi_-r5b&(mV%PX?!|){I?2puCsFo{c-(BiC>hFrJRk9!Enn#_^!tCxX~93TrERj0vA|1-2}^#ku|8U4P}_}4{pfLTYtPO#b$D(HU&?P7VV wjk%QpsIo1ibb*@(m1C#Uu{^tte_hx318OBM5yM-Wwg3PC07*qoM6N<$g62{mX#fBK literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_39.png b/assets/dolphin/external/L1_My_dude_128x64/frame_39.png new file mode 100644 index 0000000000000000000000000000000000000000..450b4d4f63dbc666d19228fd3223d0ca0c621835 GIT binary patch literal 1028 zcmV+f1pE7mP){-C}4AW%O z6H`hl#u!pcVK<^e+CRn^LI{w4An=3ObTzr9um}!K%1->CHlOJNl=Rlg)~@G)oYy+Q zEGNioXoSS?kpze)

9$$O&zcY9YR~w&q?BeL2bjN@6GxES+45_>u$E0!_+J{6U}m z8bSQud&y5JndW#PWhefildzJx>i*jNwQmYJ0yK;SOLC11GlQr@!RMp< zuoI!*2F?@#b&+23PZWWh=Kvfv1m&NzIC@Avnf#Peh$-RzDtJ@`R^qvP)C5q9mGvR} z41W$Fp%hl)^SXIx)~2|sK6szKI)IjF$t@(8sLi9-J@jGL;dz_`(4?g(-NR43CGvXc z`e*e|BERVXt=Lfn-KUicB~g>NW<8AvrSKpI2}K8>jVNJBX&ig)E1yFCT52@KT4=?c z@6pydv;Lhp{2U-RsfIJ`ixVP|HgZ~}H9gq*iuK(-2WW{-I>l53^giwB=j))!=Q+7@ z08~?2PLM~8@~@6opzB>pevFPk1&oS-i=kHp=sI)WtJZ&{16W0nh0qfo>RgGBb$}ib zSnfsFwF|d70B!yX5ok}dMg(Y5Ug-dofED7hL~UM-&wm1F<|X~HlHP!t2qApUKq65&zJ^>{Wc*xHqR=*9*!C&*>^F~k`J#PqS z=J_TJXa!ybql+Xi!Z3J9;^K9HW3e?h?2ju>JJ2MdaMhsubpKWG>agbH0b8;2vA1@; z?(YKfCaVxlW>1|OQby~W-M>fwJ;1Kd$u%4aFXI2-Gu_|02jLgb=wvR!#eh%ZA`o39 zF;~$p^6-vitZ7;faB;JS@ yDz9ztS}*C(#=>ZSglUmpyZ?%1?}@Knm&PybUz7epZzaC~0000{{JuMJW)BWFJmx-?CD5Vwh3T^Zv(0J zIM4GW1IKX?5s}v9IF8SKuiX!ODaAzL<(WB#xwUgvqz zd7g9}$LAwcyLD~Qj4sENeSX*ja0#eL@75SU4sjn|M0!NG2)4fWT=^L7ej8gp`(X{f zGC-E-^$~k1ADuu3*Q>X-M;t->E|svjhh==CE06GYFxL3mIDZ{-efXz417w7I2G!bm z_p)tK{WND-l_5rWN_6fLtxI0M18Yws{l1HMX2cEN7#1q*9XJlo$fwy~Ve}82FE7H++ zd=b3+qxIFRl_&>@u2Px9?T*qNwlT5J2J@oODlL_z6m9p#0kp}^zFNwm7+uK7 zIb~`_N*|v?M@G$}D(zc(ppHEf5YgI6IhMZe7{q=Qig;0^ zSk~xhsZ?KLCxPH+~}zpD(*lAEy^k-`C1VAKgvZX(-OFS>SSlEDty=OqWp zZoe|9nlN_+^e}0cn!kcGW%yi}0e0Hza5P8o&Lnu-RXSMY>TRJ zhw-0^a2tpK-Sf^y4=Ei*hB&X@^IL5>-pE$SI!BI>IUpI`l4(bwE7LBQzjMl$cv}xN zgLn(;m68<)Mbh{?oH9!6SdL5w&@h|z_c)AY{5v9028+xJcj{x|Ya1xxEh^uNX`f!D z(PLfBS)vzOv#wG%si(F!JCusce+coEvQn(OZ>#Rw0!E zkO^Ck;Eme@#DP#J8!^_5F}HPqPp2U5%J!}To!hpkUuF@j8TyC;S35vz8`$7wgI)rZ zaRjfF+<6Yb} VUEO9qCnW#?002ovPDHLkV1gCk1=0Wj literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_40.png b/assets/dolphin/external/L1_My_dude_128x64/frame_40.png new file mode 100644 index 0000000000000000000000000000000000000000..369200345db3985d5b105b40ed3ba37e3a0ea311 GIT binary patch literal 1225 zcmV;)1UCDLP)WJa;M*fY6M zDWwomx%fMgGLX1<5L1x2cnm26iHlbOE*?V4K;q(6fQyHaGLX1<72x6_qzoi3UIn;# z2q^=Ji&p_I9zx1M;^I|+i-(Xhkhpji;Nl^q3?vQ&{dSz@a}-mM*jQx#o}W9-_h6pR zlURw>wDT+xDfn*36eJcGzn+u3pI`f}_>~HP6uEc(*I)_~8x)|P*REyYdjg-oZ|Hd* z%8b>J@$?fENwcnb{Lk{sgs%)F(B#(QdB)4RJ=6RlO?Ns^BlyZd0!=QGF3#f~?K(F$$n1A2Dkzq5_uBUbD95 zA@jdN0eBL$9gHGR-b~@5%%fEGy+kD5)lCrgO`vA2B1QV=>uBQ;AEWbE7Drk-1*oKW zGAh=Clb;D<##X8Yq5v!M8`T6;&j6AXM^1hu=;$U`X_7ZlyfW~0EdQJbgr)Sq3A7gF zNr()#wO-G9<7Tdp+(uU39<2O()>jpvI&C?t?^}yU1$9aRp6CDD7iznUZ`G&r8Bzc% z2$%EVAd%vEcPT$Fi|F-@@>{6{)zD`A_bI>{%$@Ek>g+su4oT36@7`Fe;db5@G<@+% zyNfSxCoLYW>!?*JFQxo5-VgLExJs`DjR8*-W*g_(B|K! z0FjGYL83*^ypt_%E#>R^=qW;U(YfDO>*TCcD=4JsS~3trcc7f(HH4dN2CKPuHUak= z@@rr(2yf9_Nn2rcJx`-OPydS|elePnHdHwCa3kM3x1aHD43e$er~IVg`TP4MPF zS^iqVG5AB_z_L8!y#C;ZFfpuo`*G4>~WSS^Ty0NPKAC zXX^J5QSp+DQ~<9cw62}BUS70uClo;XC88e%kSBL4KyQb0elqbD>w#Kpr^!REp{;G@ zg^!OY{;UE>zeE)K3@|Eth!R)@UhG`Vg|UOjQQ{H`v( z^w<19Kx7j{72jHK))2*M<%btf&PR*jDOQA{O25#jfZp|Ns1;_`Rx8{t|2!TlKdY5* zK*ac*nIl8I5lF3sXz#TIo(@9ewd*(?FIqo^uP>NvL3@+)t;HYB!`m}}v{JBVfY$h* n_4fbE6}k6CS(bQ^dT90sAA)%(Wh!_*00000NkvXXu0mjfk>5o{ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_41.png b/assets/dolphin/external/L1_My_dude_128x64/frame_41.png new file mode 100644 index 0000000000000000000000000000000000000000..e0f882268275af59257326019b77b30c145070ac GIT binary patch literal 1256 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 JPDHLkV1iSbhV}pe literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_44.png b/assets/dolphin/external/L1_My_dude_128x64/frame_44.png new file mode 100644 index 0000000000000000000000000000000000000000..f425bcc179405dedfcbf095ce131b75707a636a7 GIT binary patch literal 623 zcmV-#0+9WQP)Hos!R)?YJ15L?jey!PUnW3d2QCjBTVZ%cj}7jHkcMWFgO>x-qa_c<7c zrrmkzL6SWeZc)<$ADB(l^IXO@gnbR96lhmg{`}KNnz9z4klwz4@^2SG{M{bRYfbZY9C-C?eZ? z08<2WutT!*$IDl8>F+Qm(~%xmRsIh!J7u>rGVe1Vb~B$ULNL8q%3tpNVpU*LEk6r) zW}XL4(eJ{&pJm*fOIiTAVkjkjZ~jxolH2d9A||KyVl3rjtzYe{s%B<4KE?n5002ov JPDHLkV1h`J6Q2M8 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_45.png b/assets/dolphin/external/L1_My_dude_128x64/frame_45.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ea1a7e78c0b590c493451bc16d27dfbd421928 GIT binary patch literal 556 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!3HERU8}DLQk(@Ik;M!Q+`=Ht$S`Y;1Oo%( zJx>?Mkcv5PXGZrm8wjwp-}!&;-uwtJfn#Pj-kl3l?)6d-s*)=yo9v;vw8P`WOdfW@ z01s}#fMW#$jD-vuNfsRq77QYWGA<4>3|$g@N(y`ou0S;c>oUFo?wu76 zg|E)ay31R7w|a7vg$Sp<6~mVVK5zDh?6&#L>=g>d4;X+}Sun_)3^l*K-1VbDYct0W z2KI(|U^l!-(!8>(ZsKyj3BkHTTrvsve}ei%E=zq{_E#C`xQrJJA0)x*Ds-}UKKQrm zQB<(4ifH=N=e66W1l`)E8{~EI@Xd>>k2h|WD4c)sZ}nZxGPSFR_XMS%4tRIXd{@1G zyPCDJQr8|~z0?2qr9U$M?)Ycc<|v*i`pt1HBA1+w-00nQ;o-UdIlmA0?bvZR#614) z^!1;8#baga_}U*O7HU4*Q_0t^^zfu}U4>AY`Q|CwU!T0Fy!*{=!;ed;V(CXuSw$`0 zt-rLl>~2KG`IUUupj%ioWi#iK@kh`xU5^^EHL^RJYD@<);T3K0RYV)<>vqZ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_46.png b/assets/dolphin/external/L1_My_dude_128x64/frame_46.png new file mode 100644 index 0000000000000000000000000000000000000000..3113ff2e623dbc8f684d790420b396e3e1fd811c GIT binary patch literal 928 zcmV;R17G}!P)}cXvuQxv{iDYrc2Fmz3-sv83^Kk}L(s zc{R{tAn-^9&@$DGkSl=}04;`_3SfadNuCjJ#{_6GTvPz7WbBMP z8oMSyi{YXISS4d;+?kL}&>P2t7zn(+=I(<`%RIA2XqiW}VKoJh!)g!{17^ur#_q8nk~s zPGCO&A3;>SS@CJVmJveqDLwEc8su1!Ie|m;n^b^N?1_)&_Y_QMaYOv503kjQq3;4p zaQigqZvf~=EiX74-El0OCB;hrSouyWz-;XPf>p*S`{)&S_mzIE0!VGOzm@X?zw@mx zI(kxu$r@^Co>S<8L?8^42NDxx@4M2xq6-oe;-h&y4U-2F10-GWcqFLmLP%iW9IGLTJ9^)ozbq8^7tsZY zKp3WK#|5cCbJ0KhXflCtGvG-Cez3%``+Ask;7{>O7_ar67yzPur^Oeri<$h7|7w4UhF^2sml2^GJDY4u zt+kd?>cPK>B|{Mh|6{u!wr_@}4;}|NcpTv14lEgpICvc3;3g~?ia2;2;NT`K8H&h; z_!hZ8tRZCIXmX#1JBA>*zx_pILwp(|5!c|3JDSXlPze0oH)8Ra;N49FtWKP;La1>BDmjs(>qAm(L6zhN&gx66l01a z5)1(p2R;{B=V)J#I?7YUwazEa>$Puc2guem4l4(M`KXiVu`kX);{Zl|=MZ&O{1#gA zcZ#qTQxuWF0bs73{YCG8{X8hwbrnw>#cDo_`pTPxPreqB{li|Y`jX(wTXZZIhrRWj zXx8Gxu;ydbKQ{;?9#-^#&zm~{H`ShbxR~7!4n))moZa7wUpEK9ldZ`k6%(J784;B8=iPR1E%&q@wUcR}x zPm_f3+)zY|4{O=M&sA%!8Q{=l~I- zdp3{r?;ZkNSL2~Q7tKFaSFP_?T@Z)oe)zuC&4Ukb-lcf^6flZ*odj`2zRg&j{*}IG z9U+Pr3cLRcpfHo&_lB<4O4hMk{88TUp}-Dc)m*grjksP7)d@Kkgb|l~S=`9xC1Oa{^>U5YP_=nGpf)e8{Sgi@>j;!EvmqH4 zZ}Gz&AiMY+`O=du8HyF*+q$QT|40YO5U>_c(Q$b2_-YTdV$80iP(#j+h!}E>J{<3j z$69}W4*;8)-E4)7c0IBQtxuwbUCrjf>mYiBl`V7mv^RSq`UHXihxElf^zgU9a-~-CH;5 z>ZX#D_f6ur$bKkbowrW`J)2s#83tQkP5x}+xBjrb;}Z_Ai^f*Z#{syLjvre(!#CQ0 z;{wL6lP!Jx>n#Ajm|65uXvLT8!R?nFZ?zXR0GNmsC*bnz913mphH1B$vwFbz2SXzB Uk+^RXRR91007*qoM6N<$g2xy?&Hw-a literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_48.png b/assets/dolphin/external/L1_My_dude_128x64/frame_48.png new file mode 100644 index 0000000000000000000000000000000000000000..2734e2fcd57ef6b102da9c2a977264694246b63e GIT binary patch literal 1019 zcmVWDt>!^@==SY6v+uI=P?5h$0A%&%c7vK{+G^7KhfbovmUKNs2OGzjjAg382i^-IqbvR!$)z5w{8p+}aJUitM9m7ED2;&qY5^${xQ96Rp!zUljtpe~oNABIa4=K9YB%wMZ zf@Q23^h6Y4L5s@3wE}od@r53ikZaxX81u`S081vcR)#pH*_yNzLD)LrR~!g4wU*%c z@g|_SR>)5>pU5}?B9b$Lk2e9^1b7iWL`?v`J`*4~c!}L+>!r^|#s8=Ypb->gwZnUb z@feOcL?gJutH#&vM;Ad&Pc*;&UzFGDRlZ*P0$;DPhIPLcW@tU~UJpC8&Jt7Q#p;*38*u{_Ldao!k5luxh?n(OCB3fP03H zV5QTwex&(BOhlBq^R1PCRSZ3>n(xv1qp;??`W@iY%$Lt|Qh4z_UZckA{Z`|!ynlpU zC%{Vb(dO%pX_+AR9sxR@C|>@PzzpJBMbI*I1ik(4kSEsk9nW1Rz(^6Sz!C>KpQHIA z3BJAqTi43D8X5A?<5Ux1r;b^y=+(#+e=8qvzL1PGUy9>ocoo$Q&+OxA{!Sm{zO4UU z3no($>?j6H@$`_XiF!pV@4Nkrpok!55OEG0p26f9e;7pc^IO2L7lX~+T?|&#CXz;m zysR|mTivInx0He3d#~=J7`4!G zd*zLYa{uq(OvQkXZ)4utoqiR3*5usLWX6migV%FO$et|gLhF~Tu_He(u7Vnf#=aP~ pp1*pZH+vyUZ;cr<@y_}o@ef@Xp~TjZ6$$_V002ovPDHLkV1g9->bC#@ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_5.png b/assets/dolphin/external/L1_My_dude_128x64/frame_5.png new file mode 100644 index 0000000000000000000000000000000000000000..df225594910480d7e00543e706d7286736ca814a GIT binary patch literal 1648 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/frame_7.png b/assets/dolphin/external/L1_My_dude_128x64/frame_7.png new file mode 100644 index 0000000000000000000000000000000000000000..9703809405f7a9cb8e800272d21c3cdb499ede15 GIT binary patch literal 1063 zcmV+?1laqDP)uB2+J9c7@?9a1xv zmeyJ;rL>FRi6sMxi~q2{c3U??(id+BxOh9j#Rx1JNL;)f;9?M#3?wez4sbCDO9m1b zZwE++_=x6Xm}fkRY&d%Df$r;iCg&-l);tkK;9TtSBr;-;+X21|sRGEr%P`+~5*ZN) z^nb4_CFdzdR0o)yTVfCAeB(vWf};EpLs zBtWX5qdS08-tppRLLf{EKIWK$L;}PG5Xr-Ny?F7X5u0ye0-S&JxpdvpA>wB(?ux8R z5{f2Snh)2oMy{SE!w$@VJ)-9Cuif*{AWkGw21FDeW2c(%D^{XLiMhkGM|Qp@n1Vzw z_J~|Rs`%>fNvXA#PsH7*wU+Ws{^h*C=gA*(ao4f6)}l?cHXEAW=YwWb1%bzNctq>7 zgXuefh3VLQIF4(sWZldi;E};(N))ch62!*WTK^ua>oHI1_*HRR>Np$s$YeHU`iaOm zpz)&u^UNP};hMHH{}5aAzXw=_R`S?*TvW60(xG$V=w!je{T|>G%=ah)X}(s);BpT@ z83D~;x2wYt^TxRMB&t)g57+aHn4-lQaG%V&DMTSkW-2pgB>@1S07*2xIJxYlw z0*dRZ1Ga{q0yyM%lU$M7MTs%osI|V#iO*-@Z93kR9XZ#%bpWk)<1Nl;EpBKPP}Utk z#nwLOSHY5E^NNVPah&K@%kvKj;$$=y*Amk^qKp1iC;0xOn+XT)P9`ZH3Qu+fwKFUt z(nKwt;QPnF3B-}1(ycp$waM0c!aSbtm;8U^JVmX;g5M}SbV>iupiCX$gaCcxG5PErrn`)3zYxIjklC-7jMTWSrZw&8*O=qXLN3 z>vgW#1mG)FWMEOpAaot@ zD~=2^xt8Gg^G(R&ULilpd;sMHh)B)~KHh}fCdd~tL(~Mo>oWm@gQxH|n=gKLR{W2e z031O51mo|BLo|z1r8kFW|M5Ikfvp7{T?>*Jkk4 zmzkGeIxH)SBeSw;XK_tCD?1L#9(nyU0Y0(QIrA>2+9dGMN%dsrTQvbZDpJdIagCDY zp_A&d+E4oa?FnFAfxWJ@+K=j$cE5$5UjC|lr`VbRno2{)rQK(=FJDx%W_jtg-j`zK z`Q-^9sW8uu;^3dBmKdEU)h+q#_nHWAFZ1zzJ=O%p^-x_i!!)4h@bj|F@97zR49@m> zXMJW_nLe600AN-KR}(Iy^J!i*`lb`0g85gbkCJRz2rHe~GsnyP-AOS!claGZYQ9y` zX!gN?XNImo(&=1(ruj<@08qK}wUvKW3^Pd0x9I#;X!G6u4)AH_)8{cMxbQVxtH$&F zq;Y88zrv;yK$84y^LfXNOrU$OAUmEY7XL|L1@WUI7#X^P*?xD(6KeX7=Pnaqr3fU@ z#F3rP)qDX3Uf+SWOL8tpg*?o-)CBOHtzJR;HxI*jwT~^{8+r+OG5QzX;-#>${Zf~d0`dw u0I>GOaP<7u`>fdtQF?of*ok-74~l=LoVlqksCsn(0000+eVe-j}>5(obaJkEFX^| zBO^!nlba55fwjXrsR-mCf@Vk!%jb0bRNd!PFKK+?>9+vU0|8L;BF3`l5X~!SQ^SUm8H!-F>&>U9$ z_n!aKCX*&rLT~Y@X~rob*9aI#{EwVJy*@akChecY(lK&9&Uf^BDc*YGQd^F1S!8s~ zuXl-yubsv24ImNB3+eHs`lyL+fMw?!AUB1(YygQSzHI<%5k%6$QUh4#AsrX{?83xY z1aU0!SL+6VzXo_ZnVfM`xcdee#TS6=bGWoW4&j~s2V0+W#y$I&{At$V5m)u^09q3( z;g=rL{&&PHrIe&PS#l!iw9fjiO?mCJk^E-!&F9wQmTZM*j4$uCpNrv0T=C11he}>g9(H+bTaPDgQ5EbmFCoqT;8NZY0*GOJW#W&9D-ux5;|Y?7x9E(WT>1 z@wq1JF^#(M`<#n5@3p#2HUKAtmiSzp8S$m_!{l3v-`W@=_W+|4uPHu9_Kf(NO@Q;$ z=D&$4eLn@XY*l>AbtlmPT4HJW9mU%Qz{IuIM{C`9iZ3C$hDa^HtMP`fryD@3I4|*g zHu zaOQez+g1D$&tl&Q(n>n=M%YRG;?Sn_z6X#fikhUTQJY~m@hQG=aOp{uSXZNd=ye!1 zOlmDl`5V=%_>CaRj`k?dYcohsyhNOuu3b+7vq5_%?@8iki#`-M&)ZW#ZB0$DnF40N zEB>R2pX;ORolZPH&l+<+pAAr&boyR$GdywqXD+aLO?L15uU`T9!^onyY*qZ87^Qg8 w@s>M~2B1XDY=V*>&!$+lS~GczIjRS?fA`L=O_`&tlmGw#07*qoM6N<$f)%(2G5`Po literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L1_My_dude_128x64/meta.txt b/assets/dolphin/external/L1_My_dude_128x64/meta.txt new file mode 100644 index 000000000..8c326cf42 --- /dev/null +++ b/assets/dolphin/external/L1_My_dude_128x64/meta.txt @@ -0,0 +1,32 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 19 +Active frames: 51 +Frames order: 0 1 2 3 4 5 6 0 1 2 7 8 9 10 11 12 7 8 9 13 14 15 14 13 14 15 7 8 9 16 17 18 13 14 19 20 21 22 23 24 21 25 26 27 28 29 30 31 32 33 32 34 35 36 35 34 37 38 39 40 41 42 43 44 45 46 17 47 48 7 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 41 +Y: 43 +Text: My dude +AlignH: Right +AlignV: Top +StartFrame: 50 +EndFrame: 50 + +Slot: 0 +X: 59 +Y: 43 +Text: My dude +AlignH: Left +AlignV: Top +StartFrame: 54 +EndFrame: 54 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 55abe0ce8..4e3dbbf11 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -97,6 +97,13 @@ Min butthurt: 0 Max butthurt: 10 Min level: 1 Max level: 3 +Weight: 3 + +Name: L1_My_dude_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 Weight: 4 Name: L2_Wake_up_128x64 From bf15d3ce7450490ad294145c318772caa0c06990 Mon Sep 17 00:00:00 2001 From: AloneLiberty <111039319+AloneLiberty@users.noreply.github.com> Date: Wed, 12 Jul 2023 09:14:11 +0000 Subject: [PATCH 4/8] NFC: Improved MFC emulation on some readers (#2825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Improved MFC emulation on some readers * NFC: Improved emulation on some readers (part 2): Some Android devices don't like this * NFC: Improved emulation on some readers (part 3): I knew that during the emulation timings are critical, but one log breaks all... * NFC: Improved emulation on some readers (part 4): Add fixes to Detect reader and refactor code * NFC: Improved emulation on some readers (part 5) * NFC: Improved emulation on some readers (part 6): GUI doesn't update without delay * NFC: Improved emulation on some readers (part 7): Reworked emulation flow, some bug fixes and improvements Co-authored-by: あく Co-authored-by: gornekich --- lib/nfc/nfc_worker.c | 11 +- lib/nfc/protocols/mifare_classic.c | 165 ++++++++++++++++++++--------- lib/nfc/protocols/nfca.c | 12 +-- lib/nfc/protocols/nfca.h | 3 + 4 files changed, 127 insertions(+), 64 deletions(-) diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index d2834fa46..145007bd3 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -1022,7 +1022,9 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { furi_hal_nfc_listen_start(nfc_data); while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { - mf_classic_emulator(&emulator, &tx_rx, false); + if(!mf_classic_emulator(&emulator, &tx_rx, false)) { + furi_hal_nfc_listen_start(nfc_data); + } } } if(emulator.data_changed) { @@ -1297,8 +1299,6 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { bool reader_no_data_notified = true; while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { - furi_hal_nfc_stop_cmd(); - furi_delay_ms(5); furi_hal_nfc_listen_start(nfc_data); if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { if(reader_no_data_notified) { @@ -1309,7 +1309,9 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { NfcProtocol protocol = reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); if(protocol == NfcDeviceProtocolMifareClassic) { - mf_classic_emulator(&emulator, &tx_rx, true); + if(!mf_classic_emulator(&emulator, &tx_rx, true)) { + furi_hal_nfc_listen_start(nfc_data); + } } } else { reader_no_data_received_cnt++; @@ -1321,6 +1323,7 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { FURI_LOG_D(TAG, "No data from reader"); continue; } + furi_delay_ms(1); } rfal_platform_spi_release(); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index ebe49a4a0..011747d59 100644 --- a/lib/nfc/protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -851,16 +851,20 @@ bool mf_classic_emulator( bool is_reader_analyzer) { furi_assert(emulator); furi_assert(tx_rx); - bool command_processed = false; - bool is_encrypted = false; uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; MfClassicKey access_key = MfClassicKeyA; + bool need_reset = false; + bool need_nack = false; + bool is_encrypted = false; + uint8_t sector = 0; + // Used for decrement and increment - copy to block on transfer - uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE] = {}; + uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE]; bool transfer_buf_valid = false; - // Read command - while(!command_processed) { //-V654 + // Process commands + while(!need_reset && !need_nack) { //-V654 + memset(plain_data, 0, MF_CLASSIC_MAX_DATA_SIZE); if(!is_encrypted) { crypto1_reset(&emulator->crypto); memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); @@ -868,9 +872,10 @@ bool mf_classic_emulator( if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { FURI_LOG_D( TAG, - "Error in tx rx. Tx :%d bits, Rx: %d bits", + "Error in tx rx. Tx: %d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits); + need_reset = true; break; } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); @@ -879,19 +884,28 @@ bool mf_classic_emulator( // After increment, decrement or restore the only allowed command is transfer uint8_t cmd = plain_data[0]; if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) { + need_nack = true; break; } - if(cmd == 0x50 && plain_data[1] == 0x00) { + if(cmd == NFCA_CMD_HALT && plain_data[1] == 0x00) { FURI_LOG_T(TAG, "Halt received"); - furi_hal_nfc_listen_sleep(); - command_processed = true; + need_reset = true; break; } + + if(cmd == NFCA_CMD_RATS) { + // Mifare Classic doesn't support ATS, NACK it and start listening again + FURI_LOG_T(TAG, "RATS received"); + need_nack = true; + break; + } + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { uint8_t block = plain_data[1]; uint64_t key = 0; uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); + sector = mf_classic_get_sector_by_block(block); MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { @@ -902,7 +916,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyA; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; + need_nack = true; break; } } else { @@ -913,7 +927,7 @@ bool mf_classic_emulator( access_key = MfClassicKeyB; } else { FURI_LOG_D(TAG, "Key not known"); - command_processed = true; + need_nack = true; break; } } @@ -942,15 +956,15 @@ bool mf_classic_emulator( tx_rx->tx_bits = sizeof(nt) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; } + if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { FURI_LOG_E(TAG, "Error in NT exchange"); - command_processed = true; + need_reset = true; break; } if(tx_rx->rx_bits != 64) { - FURI_LOG_W(TAG, "Incorrect nr + ar length: %d", tx_rx->rx_bits); - command_processed = true; + need_reset = true; break; } @@ -960,9 +974,14 @@ bool mf_classic_emulator( crypto1_word(&emulator->crypto, nr, 1); uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); if(cardRr != prng_successor(nonce, 64)) { - FURI_LOG_T(TAG, "Wrong AUTH! %08lX != %08lX", cardRr, prng_successor(nonce, 64)); + FURI_LOG_T( + TAG, + "Wrong AUTH on block %u! %08lX != %08lX", + block, + cardRr, + prng_successor(nonce, 64)); // Don't send NACK, as the tag doesn't send it - command_processed = true; + need_reset = true; break; } @@ -985,11 +1004,25 @@ bool mf_classic_emulator( if(!is_encrypted) { FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd); + need_nack = true; break; } - if(cmd == MF_CLASSIC_READ_BLOCK_CMD) { - uint8_t block = plain_data[1]; + // Mifare Classic commands always have block number after command + uint8_t block = plain_data[1]; + if(mf_classic_get_sector_by_block(block) != sector) { + // Don't allow access to sectors other than authorized + FURI_LOG_T( + TAG, + "Trying to access block %u from not authorized sector (command: %02X)", + block, + cmd); + need_nack = true; + break; + } + + switch(cmd) { + case MF_CLASSIC_READ_BLOCK_CMD: { uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); if(mf_classic_is_sector_trailer(block)) { @@ -1005,17 +1038,14 @@ bool mf_classic_emulator( emulator, block, access_key, MfClassicActionACRead)) { memset(&block_data[6], 0, 4); } - } else if(!mf_classic_is_allowed_access( - emulator, block, access_key, MfClassicActionDataRead)) { - // Send NACK - uint8_t nack = 0x04; - crypto1_encrypt( - &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); - tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - tx_rx->tx_bits = 4; - furi_hal_nfc_tx_rx(tx_rx, 300); + } else if( + !mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead) || + !mf_classic_is_block_read(&emulator->data, block)) { + need_nack = true; break; } + nfca_append_crc16(block_data, 16); crypto1_encrypt( @@ -1027,23 +1057,36 @@ bool mf_classic_emulator( tx_rx->tx_parity); tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; - } else if(cmd == MF_CLASSIC_WRITE_BLOCK_CMD) { - uint8_t block = plain_data[1]; - if(block > mf_classic_get_total_block_num(emulator->data.type)) { - break; - } + break; + } + + case MF_CLASSIC_WRITE_BLOCK_CMD: { // Send ACK uint8_t ack = MF_CLASSIC_ACK_CMD; crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) { + need_reset = true; + break; + } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {}; memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + + if(!mf_classic_is_block_read(&emulator->data, block)) { + // Don't allow writing to the block for which we haven't read data yet + need_nack = true; + break; + } + if(mf_classic_is_sector_trailer(block)) { if(mf_classic_is_allowed_access( emulator, block, access_key, MfClassicActionKeyAWrite)) { @@ -1062,38 +1105,39 @@ bool mf_classic_emulator( emulator, block, access_key, MfClassicActionDataWrite)) { memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); } else { + need_nack = true; break; } } + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); emulator->data_changed = true; } + // Send ACK ack = MF_CLASSIC_ACK_CMD; crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - } else if( - cmd == MF_CLASSIC_DECREMENT_CMD || cmd == MF_CLASSIC_INCREMENT_CMD || - cmd == MF_CLASSIC_RESTORE_CMD) { - uint8_t block = plain_data[1]; + break; + } - if(block > mf_classic_get_total_block_num(emulator->data.type)) { - break; - } + case MF_CLASSIC_DECREMENT_CMD: + case MF_CLASSIC_INCREMENT_CMD: + case MF_CLASSIC_RESTORE_CMD: { + MfClassicAction action = (cmd == MF_CLASSIC_INCREMENT_CMD) ? MfClassicActionDataInc : + MfClassicActionDataDec; - MfClassicAction action = MfClassicActionDataDec; - if(cmd == MF_CLASSIC_INCREMENT_CMD) { - action = MfClassicActionDataInc; - } if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) { + need_nack = true; break; } int32_t prev_value; uint8_t addr; if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) { + need_nack = true; break; } @@ -1103,8 +1147,15 @@ bool mf_classic_emulator( tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; - if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) break; + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) { + need_reset = true; + break; + } crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); int32_t value = *(int32_t*)&plain_data[0]; @@ -1121,9 +1172,12 @@ bool mf_classic_emulator( transfer_buf_valid = true; // Commands do not ACK tx_rx->tx_bits = 0; - } else if(cmd == MF_CLASSIC_TRANSFER_CMD) { - uint8_t block = plain_data[1]; + break; + } + + case MF_CLASSIC_TRANSFER_CMD: { if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) { + need_nack = true; break; } if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != @@ -1137,13 +1191,17 @@ bool mf_classic_emulator( crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; - } else { + break; + } + + default: FURI_LOG_T(TAG, "Unknown command: %02X", cmd); + need_nack = true; break; } } - if(!command_processed) { + if(need_nack && !need_reset) { // Send NACK uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD : MF_CLASSIC_NACK_BUF_INVALID_CMD; @@ -1155,15 +1213,16 @@ bool mf_classic_emulator( tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; tx_rx->tx_bits = 4; furi_hal_nfc_tx_rx(tx_rx, 300); + need_reset = true; } - return true; + return !need_reset; } void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { furi_assert(tx_rx); - uint8_t plain_data[4] = {0x50, 0x00, 0x00, 0x00}; + uint8_t plain_data[4] = {NFCA_CMD_HALT, 0x00, 0x00, 0x00}; nfca_append_crc16(plain_data, 2); if(crypto) { diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c index c401f8cc5..ab4f3f23c 100644 --- a/lib/nfc/protocols/nfca.c +++ b/lib/nfc/protocols/nfca.c @@ -3,8 +3,6 @@ #include #include -#define NFCA_CMD_RATS (0xE0U) - #define NFCA_CRC_INIT (0x6363) #define NFCA_F_SIG (13560000.0) @@ -22,7 +20,7 @@ typedef struct { static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; -static uint8_t nfca_sleep_req[] = {0x50, 0x00}; +static uint8_t nfca_halt_req[] = {NFCA_CMD_HALT, 0x00}; uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { uint16_t crc = NFCA_CRC_INIT; @@ -50,17 +48,17 @@ bool nfca_emulation_handler( uint16_t buff_rx_len, uint8_t* buff_tx, uint16_t* buff_tx_len) { - bool sleep = false; + bool halt = false; uint8_t rx_bytes = buff_rx_len / 8; - if(rx_bytes == sizeof(nfca_sleep_req) && !memcmp(buff_rx, nfca_sleep_req, rx_bytes)) { - sleep = true; + if(rx_bytes == sizeof(nfca_halt_req) && !memcmp(buff_rx, nfca_halt_req, rx_bytes)) { + halt = true; } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); *buff_tx_len = sizeof(nfca_default_ats) * 8; } - return sleep; + return halt; } static void nfca_add_bit(DigitalSignal* signal, bool bit) { diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h index 498ef2843..e4978a3e0 100644 --- a/lib/nfc/protocols/nfca.h +++ b/lib/nfc/protocols/nfca.h @@ -5,6 +5,9 @@ #include +#define NFCA_CMD_RATS (0xE0U) +#define NFCA_CMD_HALT (0x50U) + typedef struct { DigitalSignal* one; DigitalSignal* zero; From 25ec09c7eb3682473c19047b992c3ad05cd0b7c0 Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:41:46 +0400 Subject: [PATCH 5/8] SubGhz: fix check connect cc1101_ext (#2857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SubGhz: fix check connect cc1101_ext * SubGhz: fix syntax * SubGhz: enable interface pin pullups * SubGhz: fix syntax * SubGhz: fix CLI check connect CC1101_ext * SubGhz: fix CLI display of the selected device Co-authored-by: あく --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 55 +++++++++--- applications/main/subghz/subghz_cli.c | 34 +++++--- .../targets/f7/furi_hal/furi_hal_spi_config.c | 49 ++++++++++- lib/drivers/cc1101.c | 83 ++++++++++--------- lib/drivers/cc1101.h | 28 +++++-- lib/drivers/cc1101_regs.h | 2 +- 6 files changed, 178 insertions(+), 73 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 896b9bd2f..594a74a01 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -87,6 +87,7 @@ static bool subghz_device_cc1101_ext_check_init() { subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; bool ret = false; + CC1101Status cc1101_status = {0}; furi_hal_spi_acquire(subghz_device_cc1101_ext->spi_bus_handle); FuriHalCortexTimer timer = furi_hal_cortex_timer_get(100 * 1000); @@ -94,16 +95,34 @@ static bool subghz_device_cc1101_ext_check_init() { // Reset furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); - cc1101_write_reg( - subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + furi_hal_gpio_init( + subghz_device_cc1101_ext->spi_bus_handle->miso, + GpioModeInput, + GpioPullUp, + GpioSpeedLow); + cc1101_status = cc1101_reset(subghz_device_cc1101_ext->spi_bus_handle); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } + cc1101_status = cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } // Prepare GD0 for power on self test furi_hal_gpio_init( - subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullNo, GpioSpeedLow); + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullUp, GpioSpeedLow); // GD0 low - cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + cc1101_status = cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != false) { if(furi_hal_cortex_timer_is_expired(timer)) { //timeout @@ -116,10 +135,16 @@ static bool subghz_device_cc1101_ext_check_init() { } // GD0 high - cc1101_write_reg( + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeInput, GpioPullDown, GpioSpeedLow); + cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHW | CC1101_IOCFG_INV); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } while(furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin) != true) { if(furi_hal_cortex_timer_is_expired(timer)) { //timeout @@ -132,17 +157,21 @@ static bool subghz_device_cc1101_ext_check_init() { } // Reset GD0 to floating state - cc1101_write_reg( + cc1101_status = cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - // RF switches - furi_hal_gpio_init(&gpio_rf_sw_0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow); - cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); - // Go to sleep - cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + cc1101_status = cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } ret = true; } while(false); @@ -152,6 +181,8 @@ static bool subghz_device_cc1101_ext_check_init() { FURI_LOG_I(TAG, "Init OK"); } else { FURI_LOG_E(TAG, "Init failed"); + furi_hal_gpio_init( + subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } return ret; } diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index bc7be507e..fe97c8a06 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -28,12 +28,20 @@ #define SUBGHZ_REGION_FILENAME "/int/.region_data" +#define TAG "SubGhz CLI" + static void subghz_cli_radio_device_power_on() { - uint8_t attempts = 0; - while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) { - furi_hal_power_enable_otg(); - //CC1101 power-up time - furi_delay_ms(10); + uint8_t attempts = 5; + while(--attempts > 0) { + if(furi_hal_power_enable_otg()) break; + } + if(attempts == 0) { + if(furi_hal_power_get_usb_voltage() < 4.5f) { + FURI_LOG_E( + "TAG", + "Error power otg enable. BQ2589 check otg fault = %d", + furi_hal_power_check_otg_fault() ? 1 : 0); + } } } @@ -126,9 +134,9 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { furi_hal_subghz_sleep(); } -static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { +static const SubGhzDevice* subghz_cli_command_get_device(uint32_t* device_ind) { const SubGhzDevice* device = NULL; - switch(device_ind) { + switch(*device_ind) { case 1: subghz_cli_radio_device_power_on(); device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME); @@ -138,6 +146,12 @@ static const SubGhzDevice* subghz_cli_command_get_device(uint32_t device_ind) { device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); break; } + //check if the device is connected + if(!subghz_devices_is_connect(device)) { + subghz_cli_radio_device_power_off(); + device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + *device_ind = 0; + } return device; } @@ -175,7 +189,7 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); @@ -295,7 +309,7 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); @@ -688,7 +702,7 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { } } subghz_devices_init(); - const SubGhzDevice* device = subghz_cli_command_get_device(device_ind); + const SubGhzDevice* device = subghz_cli_command_get_device(&device_ind); if(!subghz_devices_is_frequency_valid(device, frequency)) { printf( "Frequency must be in " SUBGHZ_FREQUENCY_RANGE_STR " range, not %lu\r\n", frequency); diff --git a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c index 09ac79d2a..757ac2366 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_spi_config.c +++ b/firmware/targets/f7/furi_hal/furi_hal_spi_config.c @@ -192,6 +192,52 @@ inline static void furi_hal_spi_bus_r_handle_event_callback( } } +inline static void furi_hal_spi_bus_external_handle_event_callback( + FuriHalSpiBusHandle* handle, + FuriHalSpiBusHandleEvent event, + const LL_SPI_InitTypeDef* preset) { + if(event == FuriHalSpiBusHandleEventInit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh); + } else if(event == FuriHalSpiBusHandleEventDeinit) { + furi_hal_gpio_write(handle->cs, true); + furi_hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } else if(event == FuriHalSpiBusHandleEventActivate) { + LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset); + LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER); + LL_SPI_Enable(handle->bus->spi); + + furi_hal_gpio_init_ex( + handle->miso, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->mosi, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + furi_hal_gpio_init_ex( + handle->sck, + GpioModeAltFunctionPushPull, + GpioPullDown, + GpioSpeedVeryHigh, + GpioAltFn5SPI1); + + furi_hal_gpio_write(handle->cs, false); + } else if(event == FuriHalSpiBusHandleEventDeactivate) { + furi_hal_gpio_write(handle->cs, true); + + furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + LL_SPI_Disable(handle->bus->spi); + } +} + inline static void furi_hal_spi_bus_nfc_handle_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event, @@ -291,7 +337,8 @@ FuriHalSpiBusHandle furi_hal_spi_bus_handle_nfc = { static void furi_hal_spi_bus_handle_external_event_callback( FuriHalSpiBusHandle* handle, FuriHalSpiBusHandleEvent event) { - furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m); + furi_hal_spi_bus_external_handle_event_callback( + handle, event, &furi_hal_spi_preset_1edge_low_2m); } FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = { diff --git a/lib/drivers/cc1101.c b/lib/drivers/cc1101.c index d0feb0218..85d915acd 100644 --- a/lib/drivers/cc1101.c +++ b/lib/drivers/cc1101.c @@ -1,14 +1,27 @@ #include "cc1101.h" #include #include +#include + +static bool cc1101_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) { + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CC1101_TIMEOUT * 1000); + + while(furi_hal_gpio_read(handle->miso)) { + if(furi_hal_cortex_timer_is_expired(timer)) { + //timeout + return false; + } + } + if(!furi_hal_spi_bus_trx(handle, tx, rx, size, CC1101_TIMEOUT)) return false; + return true; +} CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { uint8_t tx[1] = {strobe}; CC1101Status rx[1] = {0}; + rx[0].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 1, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 1); assert(rx[0].CHIP_RDYn == 0); return rx[0]; @@ -17,10 +30,10 @@ CC1101Status cc1101_strobe(FuriHalSpiBusHandle* handle, uint8_t strobe) { CC1101Status cc1101_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) { uint8_t tx[2] = {reg, data}; CC1101Status rx[2] = {0}; + rx[0].CHIP_RDYn = 1; + rx[1].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 2); assert((rx[0].CHIP_RDYn | rx[1].CHIP_RDYn) == 0); return rx[1]; @@ -30,10 +43,9 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* assert(sizeof(CC1101Status) == 1); uint8_t tx[2] = {reg | CC1101_READ, 0}; CC1101Status rx[2] = {0}; + rx[0].CHIP_RDYn = 1; - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, 2); assert((rx[0].CHIP_RDYn) == 0); *data = *(uint8_t*)&rx[1]; @@ -58,40 +70,40 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle) { return rssi; } -void cc1101_reset(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SRES); +CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SRES); } CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle) { return cc1101_strobe(handle, CC1101_STROBE_SNOP); } -void cc1101_shutdown(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SPWD); +CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SPWD); } -void cc1101_calibrate(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SCAL); +CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SCAL); } -void cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SIDLE); +CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SIDLE); } -void cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SRX); +CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SRX); } -void cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_STX); +CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_STX); } -void cc1101_flush_rx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SFRX); +CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SFRX); } -void cc1101_flush_tx(FuriHalSpiBusHandle* handle) { - cc1101_strobe(handle, CC1101_STROBE_SFTX); +CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle) { + return cc1101_strobe(handle, CC1101_STROBE_SFTX); } uint32_t cc1101_set_frequency(FuriHalSpiBusHandle* handle, uint32_t value) { @@ -123,12 +135,12 @@ uint32_t cc1101_set_intermediate_frequency(FuriHalSpiBusHandle* handle, uint32_t void cc1101_set_pa_table(FuriHalSpiBusHandle* handle, const uint8_t value[8]) { uint8_t tx[9] = {CC1101_PATABLE | CC1101_BURST}; //-V1009 CC1101Status rx[9] = {0}; + rx[0].CHIP_RDYn = 1; + rx[8].CHIP_RDYn = 1; memcpy(&tx[1], &value[0], 8); - while(furi_hal_gpio_read(handle->miso)) - ; - furi_hal_spi_bus_trx(handle, tx, (uint8_t*)rx, sizeof(rx), CC1101_TIMEOUT); + cc1101_spi_trx(handle, tx, (uint8_t*)rx, sizeof(rx)); assert((rx[0].CHIP_RDYn | rx[8].CHIP_RDYn) == 0); } @@ -139,12 +151,7 @@ uint8_t cc1101_write_fifo(FuriHalSpiBusHandle* handle, const uint8_t* data, uint buff_tx[0] = CC1101_FIFO | CC1101_BURST; memcpy(&buff_tx[1], data, size); - // Start transaction - // Wait IC to become ready - while(furi_hal_gpio_read(handle->miso)) - ; - // Tell IC what we want - furi_hal_spi_bus_trx(handle, buff_tx, (uint8_t*)buff_rx, size + 1, CC1101_TIMEOUT); + cc1101_spi_trx(handle, buff_tx, (uint8_t*)buff_rx, size + 1); return size; } @@ -153,13 +160,7 @@ uint8_t cc1101_read_fifo(FuriHalSpiBusHandle* handle, uint8_t* data, uint8_t* si uint8_t buff_trx[2]; buff_trx[0] = CC1101_FIFO | CC1101_READ | CC1101_BURST; - // Start transaction - // Wait IC to become ready - while(furi_hal_gpio_read(handle->miso)) - ; - - // First byte - packet length - furi_hal_spi_bus_trx(handle, buff_trx, buff_trx, 2, CC1101_TIMEOUT); + cc1101_spi_trx(handle, buff_trx, buff_trx, 2); // Check that the packet is placed in the receive buffer if(buff_trx[1] > 64) { diff --git a/lib/drivers/cc1101.h b/lib/drivers/cc1101.h index af1f15569..d8ee05d52 100644 --- a/lib/drivers/cc1101.h +++ b/lib/drivers/cc1101.h @@ -46,8 +46,10 @@ CC1101Status cc1101_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* /** Reset * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_reset(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_reset(FuriHalSpiBusHandle* handle); /** Get status * @@ -60,8 +62,10 @@ CC1101Status cc1101_get_status(FuriHalSpiBusHandle* handle); /** Enable shutdown mode * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_shutdown(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_shutdown(FuriHalSpiBusHandle* handle); /** Get Partnumber * @@ -90,38 +94,46 @@ uint8_t cc1101_get_rssi(FuriHalSpiBusHandle* handle); /** Calibrate oscillator * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_calibrate(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_calibrate(FuriHalSpiBusHandle* handle); /** Switch to idle * * @param handle - pointer to FuriHalSpiHandle */ -void cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_idle(FuriHalSpiBusHandle* handle); /** Switch to RX * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_rx(FuriHalSpiBusHandle* handle); /** Switch to TX * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_switch_to_tx(FuriHalSpiBusHandle* handle); /** Flush RX FIFO * * @param handle - pointer to FuriHalSpiHandle + * + * @return CC1101Status structure */ -void cc1101_flush_rx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_rx(FuriHalSpiBusHandle* handle); /** Flush TX FIFO * * @param handle - pointer to FuriHalSpiHandle */ -void cc1101_flush_tx(FuriHalSpiBusHandle* handle); +CC1101Status cc1101_flush_tx(FuriHalSpiBusHandle* handle); /** Set Frequency * diff --git a/lib/drivers/cc1101_regs.h b/lib/drivers/cc1101_regs.h index a326dc92c..e0aed6bd9 100644 --- a/lib/drivers/cc1101_regs.h +++ b/lib/drivers/cc1101_regs.h @@ -14,7 +14,7 @@ extern "C" { #define CC1101_IFDIV 0x400 /* IO Bus constants */ -#define CC1101_TIMEOUT 500 +#define CC1101_TIMEOUT 250 /* Bits and pieces */ #define CC1101_READ (1 << 7) /** Read Bit */ From dcb49c540f50b0f7ab501e1662272a21d52b10b3 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 12 Jul 2023 12:49:17 +0300 Subject: [PATCH 6/8] [FL-3422] Loader: exit animation fix (#2860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../services/loader/loader_applications.c | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 1801edef9..7bf189e55 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -38,6 +38,11 @@ typedef struct { FuriString* fap_path; DialogsApp* dialogs; Storage* storage; + Loader* loader; + + Gui* gui; + ViewHolder* view_holder; + Loading* loading; } LoaderApplicationsApp; static LoaderApplicationsApp* loader_applications_app_alloc() { @@ -45,15 +50,30 @@ static LoaderApplicationsApp* loader_applications_app_alloc() { app->fap_path = furi_string_alloc_set(EXT_PATH("apps")); app->dialogs = furi_record_open(RECORD_DIALOGS); app->storage = furi_record_open(RECORD_STORAGE); + app->loader = furi_record_open(RECORD_LOADER); + + app->gui = furi_record_open(RECORD_GUI); + app->view_holder = view_holder_alloc(); + app->loading = loading_alloc(); + + view_holder_attach_to_gui(app->view_holder, app->gui); + view_holder_set_view(app->view_holder, loading_get_view(app->loading)); + return app; } //-V773 -static void loader_applications_app_free(LoaderApplicationsApp* loader_applications_app) { - furi_assert(loader_applications_app); +static void loader_applications_app_free(LoaderApplicationsApp* app) { + furi_assert(app); + + view_holder_free(app->view_holder); + loading_free(app->loading); + furi_record_close(RECORD_GUI); + + furi_record_close(RECORD_LOADER); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_STORAGE); - furi_string_free(loader_applications_app->fap_path); - free(loader_applications_app); + furi_string_free(app->fap_path); + free(app); } static bool loader_applications_item_callback( @@ -96,47 +116,38 @@ static void loader_pubsub_callback(const void* message, void* context) { } } -static void loader_applications_start_app(const char* name) { - // start loading animation - Gui* gui = furi_record_open(RECORD_GUI); - ViewHolder* view_holder = view_holder_alloc(); - Loading* loading = loading_alloc(); - - view_holder_attach_to_gui(view_holder, gui); - view_holder_set_view(view_holder, loading_get_view(loading)); - view_holder_start(view_holder); +static void loader_applications_start_app(LoaderApplicationsApp* app) { + const char* name = furi_string_get_cstr(app->fap_path); // load app FuriThreadId thread_id = furi_thread_get_current_id(); - Loader* loader = furi_record_open(RECORD_LOADER); FuriPubSubSubscription* subscription = - furi_pubsub_subscribe(loader_get_pubsub(loader), loader_pubsub_callback, thread_id); + furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id); - LoaderStatus status = loader_start_with_gui_error(loader, name, NULL); + LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL); if(status == LoaderStatusOk) { furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); } - furi_pubsub_unsubscribe(loader_get_pubsub(loader), subscription); - furi_record_close(RECORD_LOADER); - - // stop loading animation - view_holder_stop(view_holder); - view_holder_free(view_holder); - loading_free(loading); - furi_record_close(RECORD_GUI); + furi_pubsub_unsubscribe(loader_get_pubsub(app->loader), subscription); } static int32_t loader_applications_thread(void* p) { LoaderApplications* loader_applications = p; - LoaderApplicationsApp* loader_applications_app = loader_applications_app_alloc(); + LoaderApplicationsApp* app = loader_applications_app_alloc(); - while(loader_applications_select_app(loader_applications_app)) { - loader_applications_start_app(furi_string_get_cstr(loader_applications_app->fap_path)); + // start loading animation + view_holder_start(app->view_holder); + + while(loader_applications_select_app(app)) { + loader_applications_start_app(app); } - loader_applications_app_free(loader_applications_app); + // stop loading animation + view_holder_stop(app->view_holder); + + loader_applications_app_free(app); if(loader_applications->closed_cb) { loader_applications->closed_cb(loader_applications->context); From a4b48028976613bbefd523e1493cd1a6edc342b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 12 Jul 2023 15:02:52 +0400 Subject: [PATCH 7/8] Revert "[FL-3420] Storage: directory sort (#2850)" (#2868) This reverts commit 136114890f24f6418c3b1672d8e378902ed4db02. --- .../storage/filesystem_api_internal.h | 3 +- .../services/storage/storage_processing.c | 180 +----------------- .../services/storage/storage_sorting.h | 22 --- 3 files changed, 11 insertions(+), 194 deletions(-) delete mode 100644 applications/services/storage/storage_sorting.h diff --git a/applications/services/storage/filesystem_api_internal.h b/applications/services/storage/filesystem_api_internal.h index 52eb6ef13..967d3bb41 100644 --- a/applications/services/storage/filesystem_api_internal.h +++ b/applications/services/storage/filesystem_api_internal.h @@ -19,8 +19,7 @@ struct File { FileType type; FS_Error error_id; /**< Standard API error from FS_Error enum */ int32_t internal_error_id; /**< Internal API error value */ - void* storage; /**< Storage API pointer */ - void* sort_data; /**< Sorted file list for directory */ + void* storage; }; /** File api structure diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index eb745cac4..e6b426961 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -1,5 +1,4 @@ #include "storage_processing.h" -#include "storage_sorting.h" #include #include @@ -101,7 +100,7 @@ static FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** s /******************* File Functions *******************/ -static bool storage_process_file_open( +bool storage_process_file_open( Storage* app, File* file, FuriString* path, @@ -128,7 +127,7 @@ static bool storage_process_file_open( return ret; } -static bool storage_process_file_close(Storage* app, File* file) { +bool storage_process_file_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -261,149 +260,9 @@ static bool storage_process_file_eof(Storage* app, File* file) { return ret; } -/*************** Sorting Dir Functions ***************/ - -static bool storage_process_dir_rewind_internal(StorageData* storage, File* file); -static bool storage_process_dir_read_internal( - StorageData* storage, - File* file, - FileInfo* fileinfo, - char* name, - const uint16_t name_length); - -static int storage_sorted_file_record_compare(const void* sorted_a, const void* sorted_b) { - SortedFileRecord* a = (SortedFileRecord*)sorted_a; - SortedFileRecord* b = (SortedFileRecord*)sorted_b; - - if(a->info.flags & FSF_DIRECTORY && !(b->info.flags & FSF_DIRECTORY)) - return -1; - else if(!(a->info.flags & FSF_DIRECTORY) && b->info.flags & FSF_DIRECTORY) - return 1; - else - return furi_string_cmpi(a->name, b->name); -} - -static bool storage_sorted_dir_read_next( - SortedDir* dir, - FileInfo* fileinfo, - char* name, - const uint16_t name_length) { - bool ret = false; - - if(dir->index < dir->count) { - SortedFileRecord* sorted = &dir->sorted[dir->index]; - if(fileinfo) { - *fileinfo = sorted->info; - } - if(name) { - strncpy(name, furi_string_get_cstr(sorted->name), name_length); - } - dir->index++; - ret = true; - } - - return ret; -} - -static void storage_sorted_dir_rewind(SortedDir* dir) { - dir->index = 0; -} - -static bool storage_sorted_dir_prepare(SortedDir* dir, StorageData* storage, File* file) { - bool ret = true; - dir->count = 0; - dir->index = 0; - FileInfo info; - char name[SORTING_MAX_NAME_LENGTH + 1] = {0}; - - furi_check(!dir->sorted); - - while(storage_process_dir_read_internal(storage, file, &info, name, SORTING_MAX_NAME_LENGTH)) { - if(memmgr_get_free_heap() < SORTING_MIN_FREE_MEMORY) { - ret = false; - break; - } - - if(dir->count == 0) { //-V547 - dir->sorted = malloc(sizeof(SortedFileRecord)); - } else { - // Our realloc actually mallocs a new block and copies the data over, - // so we need to check if we have enough memory for the new block - size_t size = sizeof(SortedFileRecord) * (dir->count + 1); - if(memmgr_heap_get_max_free_block() >= size) { - dir->sorted = - realloc(dir->sorted, sizeof(SortedFileRecord) * (dir->count + 1)); //-V701 - } else { - ret = false; - break; - } - } - - dir->sorted[dir->count].name = furi_string_alloc_set(name); - dir->sorted[dir->count].info = info; - dir->count++; - } - - return ret; -} - -static void storage_sorted_dir_sort(SortedDir* dir) { - qsort(dir->sorted, dir->count, sizeof(SortedFileRecord), storage_sorted_file_record_compare); -} - -static void storage_sorted_dir_clear_data(SortedDir* dir) { - if(dir->sorted != NULL) { - for(size_t i = 0; i < dir->count; i++) { - furi_string_free(dir->sorted[i].name); - } - - free(dir->sorted); - dir->sorted = NULL; - } -} - -static void storage_file_remove_sort_data(File* file) { - if(file->sort_data != NULL) { - storage_sorted_dir_clear_data(file->sort_data); - free(file->sort_data); - file->sort_data = NULL; - } -} - -static void storage_file_add_sort_data(File* file, StorageData* storage) { - file->sort_data = malloc(sizeof(SortedDir)); - if(storage_sorted_dir_prepare(file->sort_data, storage, file)) { - storage_sorted_dir_sort(file->sort_data); - } else { - storage_file_remove_sort_data(file); - storage_process_dir_rewind_internal(storage, file); - } -} - -static bool storage_file_has_sort_data(File* file) { - return file->sort_data != NULL; -} - /******************* Dir Functions *******************/ -static bool storage_process_dir_read_internal( - StorageData* storage, - File* file, - FileInfo* fileinfo, - char* name, - const uint16_t name_length) { - bool ret = false; - FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); - return ret; -} - -static bool storage_process_dir_rewind_internal(StorageData* storage, File* file) { - bool ret = false; - FS_CALL(storage, dir.rewind(storage, file)); - return ret; -} - -static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { +bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { bool ret = false; StorageData* storage; file->error_id = storage_get_data(app, path, &storage); @@ -414,17 +273,13 @@ static bool storage_process_dir_open(Storage* app, File* file, FuriString* path) } else { storage_push_storage_file(file, path, storage); FS_CALL(storage, dir.open(storage, file, cstr_path_without_vfs_prefix(path))); - - if(file->error_id == FSE_OK) { - storage_file_add_sort_data(file, storage); - } } } return ret; } -static bool storage_process_dir_close(Storage* app, File* file) { +bool storage_process_dir_close(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); @@ -432,7 +287,6 @@ static bool storage_process_dir_close(Storage* app, File* file) { file->error_id = FSE_INVALID_PARAMETER; } else { FS_CALL(storage, dir.close(storage, file)); - storage_file_remove_sort_data(file); storage_pop_storage_file(file, storage); StorageEvent event = {.type = StorageEventTypeDirClose}; @@ -442,7 +296,7 @@ static bool storage_process_dir_close(Storage* app, File* file) { return ret; } -static bool storage_process_dir_read( +bool storage_process_dir_read( Storage* app, File* file, FileInfo* fileinfo, @@ -454,34 +308,20 @@ static bool storage_process_dir_read( if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - if(storage_file_has_sort_data(file)) { - ret = storage_sorted_dir_read_next(file->sort_data, fileinfo, name, name_length); - if(ret) { - file->error_id = FSE_OK; - } else { - file->error_id = FSE_NOT_EXIST; - } - } else { - ret = storage_process_dir_read_internal(storage, file, fileinfo, name, name_length); - } + FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); } return ret; } -static bool storage_process_dir_rewind(Storage* app, File* file) { +bool storage_process_dir_rewind(Storage* app, File* file) { bool ret = false; StorageData* storage = get_storage_by_file(file, app->storage); if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { - if(storage_file_has_sort_data(file)) { - storage_sorted_dir_rewind(file->sort_data); - ret = true; - } else { - ret = storage_process_dir_rewind_internal(storage, file); - } + FS_CALL(storage, dir.rewind(storage, file)); } return ret; @@ -621,7 +461,7 @@ static FS_Error storage_process_sd_status(Storage* app) { /******************** Aliases processing *******************/ -static void storage_process_alias( +void storage_process_alias( Storage* app, FuriString* path, FuriThreadId thread_id, @@ -665,7 +505,7 @@ static void storage_process_alias( /****************** API calls processing ******************/ -static void storage_process_message_internal(Storage* app, StorageMessage* message) { +void storage_process_message_internal(Storage* app, StorageMessage* message) { FuriString* path = NULL; switch(message->command) { diff --git a/applications/services/storage/storage_sorting.h b/applications/services/storage/storage_sorting.h deleted file mode 100644 index 9db9d58bf..000000000 --- a/applications/services/storage/storage_sorting.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include - -#define SORTING_MAX_NAME_LENGTH 255 -#define SORTING_MIN_FREE_MEMORY (1024 * 40) - -/** - * @brief Sorted file record, holds file name and info - */ -typedef struct { - FuriString* name; - FileInfo info; -} SortedFileRecord; - -/** - * @brief Sorted directory, holds sorted file records, count and current index - */ -typedef struct { - SortedFileRecord* sorted; - size_t count; - size_t index; -} SortedDir; \ No newline at end of file From 92c0baa46192b29f0dc331f1fc4704a246b43024 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 12 Jul 2023 19:35:11 +0300 Subject: [PATCH 8/8] [FL-3383, FL-3413] Archive and file browser fixes (#2862) * File browser: flickering and reload fixes * The same for archive browser --- .../main/archive/helpers/archive_browser.c | 34 +++++++++++++++- .../main/archive/helpers/archive_browser.h | 1 + .../main/archive/views/archive_browser_view.c | 26 ++++--------- .../services/gui/modules/file_browser.c | 39 ++++++++++++++----- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 70137d694..51457fe81 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -64,8 +64,20 @@ static void if(!is_last) { archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path)); } else { + bool load_again = false; with_view_model( - browser->view, ArchiveBrowserViewModel * model, { model->list_loading = false; }, true); + browser->view, + ArchiveBrowserViewModel * model, + { + model->list_loading = false; + if(archive_is_file_list_load_required(model)) { + load_again = true; + } + }, + true); + if(load_again) { + archive_file_array_load(browser, 0); + } } } @@ -111,6 +123,26 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { return true; } +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; +} + void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 09ffea1f9..5e66a3dbb 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -64,6 +64,7 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { } bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); +bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 3c2f13215..ba147f74c 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -248,24 +248,10 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } -static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { - size_t array_size = files_array_size(model->files); - - if((model->list_loading) || (array_size >= model->item_cnt)) { - return false; +static void file_list_rollover(ArchiveBrowserViewModel* model) { + if(!model->list_loading && files_array_size(model->files) < model->item_cnt) { + files_array_reset(model->files); } - - if((model->array_offset > 0) && - (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { - return true; - } - - if(((model->array_offset + array_size) < model->item_cnt) && - (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { - return true; - } - - return false; } static bool archive_view_input(InputEvent* event, void* context) { @@ -347,12 +333,13 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; + file_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); } @@ -366,10 +353,11 @@ static bool archive_view_input(InputEvent* event, void* context) { if(model->item_idx + scroll_speed >= count) { model->button_held_for_ticks = 0; model->item_idx = 0; + file_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } - if(is_file_list_load_required(model)) { + if(archive_is_file_list_load_required(model)) { model->list_loading = true; browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); } diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index c764a1cf7..91b03ec8a 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -303,6 +303,12 @@ static bool browser_is_list_load_required(FileBrowserModel* model) { return false; } +static void browser_list_rollover(FileBrowserModel* model) { + if(!model->list_loading && items_array_size(model->items) < model->item_cnt) { + items_array_reset(model->items); + } +} + static void browser_update_offset(FileBrowser* browser) { furi_assert(browser); @@ -385,7 +391,7 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) { } } }, - true); + false); BrowserItem_t_clear(&back_item); } @@ -425,14 +431,15 @@ static void (browser->hide_ext) && (item.type == BrowserItemTypeFile)); } + // We shouldn't update screen on each item if custom callback is not set + // Otherwise it will cause screen flickering + bool instant_update = (browser->item_callback != NULL); with_view_model( browser->view, FileBrowserModel * model, - { - items_array_push_back(model->items, item); - // TODO: calculate if element is visible - }, - true); + { items_array_push_back(model->items, item); }, + instant_update); + furi_string_free(item.display_name); furi_string_free(item.path); if(item.custom_icon_data) { @@ -440,7 +447,18 @@ static void } } else { with_view_model( - browser->view, FileBrowserModel * model, { model->list_loading = false; }, true); + browser->view, + FileBrowserModel * model, + { + model->list_loading = false; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + }, + true); } } @@ -604,11 +622,13 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { if(model->item_idx < scroll_speed) { model->button_held_for_ticks = 0; model->item_idx = model->item_cnt - 1; + browser_list_rollover(model); } else { model->item_idx = ((model->item_idx - scroll_speed) + model->item_cnt) % model->item_cnt; } + if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP( @@ -622,13 +642,14 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { model->button_held_for_ticks += 1; } else if(event->key == InputKeyDown) { - int32_t count = model->item_cnt; - if(model->item_idx + scroll_speed >= count) { + if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) { model->button_held_for_ticks = 0; model->item_idx = 0; + browser_list_rollover(model); } else { model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt; } + if(browser_is_list_load_required(model)) { model->list_loading = true; int32_t load_offset = CLAMP(