From ff0952d66acc0ba7533698ce66e0dacefa887de3 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:37:07 +0200 Subject: [PATCH 01/12] [FL-3776] Add the Freedom_2_dolphins animation (#3521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add the Freedom_2_dolphins animation * Dolphin: rebalance animation weights Co-authored-by: あく --- .../L3_Freedom_2_dolphins_128x64/frame_0.png | Bin 0 -> 1059 bytes .../L3_Freedom_2_dolphins_128x64/frame_1.png | Bin 0 -> 1114 bytes .../L3_Freedom_2_dolphins_128x64/frame_10.png | Bin 0 -> 1451 bytes .../L3_Freedom_2_dolphins_128x64/frame_11.png | Bin 0 -> 1300 bytes .../L3_Freedom_2_dolphins_128x64/frame_12.png | Bin 0 -> 1666 bytes .../L3_Freedom_2_dolphins_128x64/frame_13.png | Bin 0 -> 1946 bytes .../L3_Freedom_2_dolphins_128x64/frame_14.png | Bin 0 -> 1929 bytes .../L3_Freedom_2_dolphins_128x64/frame_15.png | Bin 0 -> 1929 bytes .../L3_Freedom_2_dolphins_128x64/frame_16.png | Bin 0 -> 1850 bytes .../L3_Freedom_2_dolphins_128x64/frame_17.png | Bin 0 -> 1872 bytes .../L3_Freedom_2_dolphins_128x64/frame_18.png | Bin 0 -> 1715 bytes .../L3_Freedom_2_dolphins_128x64/frame_19.png | Bin 0 -> 2446 bytes .../L3_Freedom_2_dolphins_128x64/frame_2.png | Bin 0 -> 1592 bytes .../L3_Freedom_2_dolphins_128x64/frame_20.png | Bin 0 -> 2383 bytes .../L3_Freedom_2_dolphins_128x64/frame_21.png | Bin 0 -> 2399 bytes .../L3_Freedom_2_dolphins_128x64/frame_22.png | Bin 0 -> 2396 bytes .../L3_Freedom_2_dolphins_128x64/frame_23.png | Bin 0 -> 2398 bytes .../L3_Freedom_2_dolphins_128x64/frame_24.png | Bin 0 -> 2394 bytes .../L3_Freedom_2_dolphins_128x64/frame_25.png | Bin 0 -> 2418 bytes .../L3_Freedom_2_dolphins_128x64/frame_26.png | Bin 0 -> 2434 bytes .../L3_Freedom_2_dolphins_128x64/frame_27.png | Bin 0 -> 2434 bytes .../L3_Freedom_2_dolphins_128x64/frame_28.png | Bin 0 -> 1835 bytes .../L3_Freedom_2_dolphins_128x64/frame_29.png | Bin 0 -> 1852 bytes .../L3_Freedom_2_dolphins_128x64/frame_3.png | Bin 0 -> 1553 bytes .../L3_Freedom_2_dolphins_128x64/frame_30.png | Bin 0 -> 1829 bytes .../L3_Freedom_2_dolphins_128x64/frame_31.png | Bin 0 -> 1882 bytes .../L3_Freedom_2_dolphins_128x64/frame_32.png | Bin 0 -> 1888 bytes .../L3_Freedom_2_dolphins_128x64/frame_33.png | Bin 0 -> 1878 bytes .../L3_Freedom_2_dolphins_128x64/frame_34.png | Bin 0 -> 2056 bytes .../L3_Freedom_2_dolphins_128x64/frame_35.png | Bin 0 -> 2048 bytes .../L3_Freedom_2_dolphins_128x64/frame_36.png | Bin 0 -> 2042 bytes .../L3_Freedom_2_dolphins_128x64/frame_37.png | Bin 0 -> 2062 bytes .../L3_Freedom_2_dolphins_128x64/frame_38.png | Bin 0 -> 2073 bytes .../L3_Freedom_2_dolphins_128x64/frame_39.png | Bin 0 -> 2000 bytes .../L3_Freedom_2_dolphins_128x64/frame_4.png | Bin 0 -> 1551 bytes .../L3_Freedom_2_dolphins_128x64/frame_40.png | Bin 0 -> 2127 bytes .../L3_Freedom_2_dolphins_128x64/frame_41.png | Bin 0 -> 1602 bytes .../L3_Freedom_2_dolphins_128x64/frame_42.png | Bin 0 -> 1463 bytes .../L3_Freedom_2_dolphins_128x64/frame_43.png | Bin 0 -> 1556 bytes .../L3_Freedom_2_dolphins_128x64/frame_44.png | Bin 0 -> 1850 bytes .../L3_Freedom_2_dolphins_128x64/frame_45.png | Bin 0 -> 2130 bytes .../L3_Freedom_2_dolphins_128x64/frame_46.png | Bin 0 -> 2139 bytes .../L3_Freedom_2_dolphins_128x64/frame_47.png | Bin 0 -> 2155 bytes .../L3_Freedom_2_dolphins_128x64/frame_48.png | Bin 0 -> 2179 bytes .../L3_Freedom_2_dolphins_128x64/frame_49.png | Bin 0 -> 2132 bytes .../L3_Freedom_2_dolphins_128x64/frame_5.png | Bin 0 -> 1490 bytes .../L3_Freedom_2_dolphins_128x64/frame_50.png | Bin 0 -> 2191 bytes .../L3_Freedom_2_dolphins_128x64/frame_51.png | Bin 0 -> 2165 bytes .../L3_Freedom_2_dolphins_128x64/frame_52.png | Bin 0 -> 2205 bytes .../L3_Freedom_2_dolphins_128x64/frame_53.png | Bin 0 -> 2151 bytes .../L3_Freedom_2_dolphins_128x64/frame_54.png | Bin 0 -> 2146 bytes .../L3_Freedom_2_dolphins_128x64/frame_55.png | Bin 0 -> 1952 bytes .../L3_Freedom_2_dolphins_128x64/frame_56.png | Bin 0 -> 1878 bytes .../L3_Freedom_2_dolphins_128x64/frame_6.png | Bin 0 -> 1592 bytes .../L3_Freedom_2_dolphins_128x64/frame_7.png | Bin 0 -> 1537 bytes .../L3_Freedom_2_dolphins_128x64/frame_8.png | Bin 0 -> 1460 bytes .../L3_Freedom_2_dolphins_128x64/frame_9.png | Bin 0 -> 1490 bytes .../L3_Freedom_2_dolphins_128x64/meta.txt | 23 ++++++++++++++++++ assets/dolphin/external/manifest.txt | 17 +++++++++---- 59 files changed, 35 insertions(+), 5 deletions(-) create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_0.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_1.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_10.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_11.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_12.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_13.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_14.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_15.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_16.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_17.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_18.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_19.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_2.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_20.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_21.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_22.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_23.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_24.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_25.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_26.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_27.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_28.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_29.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_3.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_30.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_31.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_32.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_33.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_34.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_35.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_36.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_37.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_38.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_39.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_4.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_40.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_41.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_42.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_43.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_44.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_45.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_46.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_47.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_48.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_49.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_5.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_50.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_51.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_52.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_53.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_54.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_55.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_56.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_6.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_7.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_8.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_9.png create mode 100755 assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_0.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_0.png new file mode 100755 index 0000000000000000000000000000000000000000..7d42cb57db999d20125d8ff2d5061a40c975d5a9 GIT binary patch literal 1059 zcmV+;1l;?HP)i+PaAH2SL*d z0B!()`@W%YT^HcbR#V$V+2?6Y0i{FEzzEbfQFq&Hn{oK(oFhd7ZXBLtc~R?FZFDr}1JH>VfUnKP zF_aKmw$)JM@E*%`GxCj)q4wfoGv#bhr4TWn@A#P3c$+< zYrI8J;GzPyC6WkUQiGjJnu3ST*ddhjsWrW{)!H_DETRBBjIczyV=Tvj9bDhpn&dEo z2t)yRPUursm9(uhftCoU_fj9$wl@W^+a5uL^04&L$`PJi8^Bfs-ASPIJ|Z)1gllBHMNS@c(GB74S&Cz*2)Imr_e zLavYQeB$SSo=xg%=l0m!+qRxuOpn8(VjclbKqCTeREUmw5@;RTywR+sqmjXjp5tVD+G%W*91DA1J8(a$3 zPT+E;8Me0a6r2PXG6E2^oF?7fb(>aY;R$CiKnj3`a_mU^C?crs`mhyJ0KZs-XH7() z+IoRSkU2m_Z(_z}*i;pP6SP(PCOARN2*B`XtzgdTl_E$1EI`T)aGRN=02abk37#U@N&vl^Gdr+a1X~H0 zH-ZAtIzy4CkGO1EJz|~$*c7W=AWs7~<>ME=50-(9027&A6}66twNc<}xc7y$LSCfxdbTap zZw-4RK}LYh;SP#i&H|Dc;DoUJtsw=lIouCUwWI(JVk8Gh0USg|fPW0PGgw1Kh7`bI dw46_U{tZ$_s-d*!*B1Z)002ovPDHLkV1ibi;B^21 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_1.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_1.png new file mode 100755 index 0000000000000000000000000000000000000000..25b4609c37949e1dac85ed4d8246a39d4329b66c GIT binary patch literal 1114 zcmV-g1f~0lP)f6h5tB;dvoIhGf-kM%}pb3Oo_cmc%P z+#Ev*L9(rf8b|b4j+2pZh75I-2%9Nqb6dfcj6Z7rSph^Ev64s;;C{GOi`!%pfhd3| zBUTeFf(92Auq~NH@X{LGsibK{*o+-QIiK3oTU)Db-eXY(5Mjhhv=y^*4A{Z_%C;nj z8APB8AaX*lva6((&IBY8P~WvaR@*KL;BM~-GL(m{4=G2u@@#;$A{b5rweK16w2@2n zsD5$sH*43*@u&i@8jvLltU!Bp%eB`cA~#6r+)jNS)M3ytT*hxb3s~WNDFRdN zI80T5l|BQ`_&M5^8XuPvaghN#X4d(wJ?`gfh4KGF2v)N8i95^wNloO)81W=?4k9Pn zAtB`Y7|tht|If8a-R;~Fdq>;$lZzQ~cvhB2fG1!Pfi)^*XL%C1IHMx}t6z!;T$I3AOSDn6>VRwqRR+GHZD}J(W&e9)q(mEGwnSW720jg3#&K;)(RyumrjvSPXRSwzs=^=2!S0N#*7 zv?ZKdA_CPmijM>rN&p{xgh?gjOV6;pfQshLCMW^8A!XlLL{I_nO#~%?4QROmZsSRM0dViKpFkfCc8MUp0G`fn5!6}0W~BE7{qJ6s z01~iM1SNnZ%!puxFiHT47!kp$Yj~~#=+DIa&%4s@2lW3|V->-wdj(YmBqgdkC;=o! z6Tu2f02i^W#48T-7C{N%23W78&G424dM7wqQs~|Poso`|F(c8TBEU_M-UmpBOBzcA zFtb{pa-b85c3ZhIQYpAc0IN7OWn0=`wI{nMwQXQ(A;O>l!p^CaJ7X78Rp26k%*oid ziiD~PW<~HLb+Q|e7avcd&CAs}x|9QC5DCC{MuJsW%r7T_SwztD4T4ye3$R;&)afq1 z!Le5G3E}KEuYg=FtO0q7%QbNLO9?*U2}{#?xkhPHrF%3*QLMxA9hf}V2g z$BT$Vwwa5hbr`e(EP|_%^2#>gx&@5jeX$}FUgMbA2GR)NDbC%=Oa)Tc!XJXA45dU- g0(cmb^Qpi811bdjh~;$8#{d8T07*qoM6N<$g5bIA4FCWD literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_10.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_10.png new file mode 100755 index 0000000000000000000000000000000000000000..575df3b9f5bd672203836637106fa68fba69af7a GIT binary patch literal 1451 zcmV;c1yuTpP)=Kp_r`fzHNq1;8(v^7MM}uovr872WPssi|XZ+O$ z$9Q37n^L=fHmuMY;6I~9jNkXQ#k|AcE#QCHp}BU8>)!=8#air=sNYtj>BPCw89L+D zgXL?4;QExt(YKj_7gnGNo2@f;|D16&Pr!|%ax5=uAM2Ig&G-Ox-~~{t zb2AJ@1WC6Z)Htfgay<-vcZg8EsL+`*Hn$awMEq6r&k~?giA+?I0L$T4EpD?D2}A-^ zDUmI=ngk7;SHQaLM1q&rASae*Es7mqorSLS$B0$3E#7|9V}S7@bMuD#|F zxvgS!n;ThRX@@~t#p@rz8xueS7x7yR-`?Y%Nsvn5RGMiafhh~{00(@Lc6}tB`RQ#- z>CDAM9PzPXqQ-CSv7FHi<9#6nGgp#+EI#ow(WJK#wU%*GVW}6`(5q!XIG-n2Ia99b;_FVm7)R6+{K-30IsW`?WN^vVEb*hu?bNYLx`p({8D5K+8>09h$n z28k>lfofX?OafO75RIx&fkamPyA0QX19^~jQ< z?^)S^6>kDSAP8K&hxIt2BFEJeR>ua{b=e7k-PsCQ%dw;S%}Ud7|67m%7y?vRv8>=+ zan_z{EisSwzY7VD+5#{vCE6@_(?U;QD@kydNcjR-4t}h}=#4Cukt;}$ej}&~y99mB zSP^}s%3=R}CXOY*F5A#3R+Hf97=Xeqo`Hg7RIX?=J0=L71?&n>I=CZIyd=no6Q-X9 zRARJ}xdSPzB*>~Hk~e~+iP)J0qesA5eT~9Hg8q91Zvvcf4xnMiS->hHRK)~8xdm9U z`kX-{K?cnKKEUqA2goPr>X<-F5mRIU;Q>xCHiy+ln*r?lGcT3AJwRLKtKHrxPA0De z=|~yFV>|Ui$cYnrDtRSXib(CbXRCO%m%hdL0BiJ~Ez^1##v3HM?*p8U*~i5*y^m(_ zh|S0>;Ma{G8GpqQE;e*VR`5Z9Q_{T%s^Wqw67;f0>H`-5P}ShCLUiSimX@own~`nc z_3g&DV#Yj7JHlmd7e{dHg^(!vdIwIQGH(}GY&qO@G7o-Y3&7AODeEw3c4oj;2tD@v zDGy-1yLVC{2_G1>KU;BoUy=f)T|LPPgI3f9dZS0Tfp;a)BQ)}C1){fg`}a>lGzNH$ zTxT9Vh?t4Y+dzypz{h_b>j77njv}f9WvG$y zNd0)*RFP#Q8@T(1S+#O+J{_H?Y~yS1Qsd$#d{yA-_5c9<{~oRhiYQ1bF=@Y+`pqi) z&KOfv2dXGU-4*C@_YYfVvrCveoY!X=h??Y;;5-0Fia%qM`ukB)rhfnc002ovPDHLk FV1k?psWt!r literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_11.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_11.png new file mode 100755 index 0000000000000000000000000000000000000000..2cfc582ef9f3c1dba187a96c4941ba73195647ec GIT binary patch literal 1300 zcmV+v1?&2WP)bKpfHgREeh0XZ% z;PoX!aC7$d(T-Vx7dD_S$QhUctxeQvTWvEA|D1EANWkqQaxX6`kF`c;b3OnqyZ~Zt zu8yII(6g-twU6k%T!o=;28C)xgw2$*xl}Nt_@nMWOMpltJP}C(ybgD2@t8~`=n^2x z2yaA_pn{7E*p^Hrcu503sidihuo*jqaz2&Q>sYOAtM?)jAi@YwNGHZ~57@!&$<~O& z3?vW<5P3sqv8$x*Jrn4Wfch@2;ca`D0H4+)$WR{EHF_FhW^Mpq5|~Ax^gSb1+QDe2c*_5H(rW}-2Jk+K&mkCIB`BW4};9$6uzGX6~l$cCN)<>)7_@VptrW748w>8=xV9HY#L?y9o3SZPC!=($UBe1xq>@ zDg!l(e=4NY--`%bl)%uIXjVwo0XYs%8F+(~(k!B~y11cIfo8S;&8G7nF3kePZ0>JeXc58#{x()%N zpw$N9B!EMJP)ORK9TR~=fDMqe0UomwhX5PVMuS}>NYe_|v|Yaqy%Mp#z7lB&Q2W(w z5B)D-Hwl)cfJeW1(<9NJ_D`_Jz z5kTYY*#?GoL)w#&fe^E$XYW8z_rVe++zGG(7M4?|BLfL4M#<3IOYh;5gk>UdCNyPo z*h*Lho%FVHg{4wh(h2m$>QpkbMl^bF7PZ$ca3O$4F6QQtLY`*s-uxW0lA>1{4iTV~ z`+peG+jzvO+MPN+p&>?91*0Um-gC&p#BxFQsG;eJR=CB!gpkv;5vlv z1ZWq0Ot7&`o2>>uod_OU;Px+s%(N+QlMZyav~!G#z};QUl_Z!+1CJ^IfX}h*_fhO9 z4?XP|!OCbbn+DhyLde0R%?9FiCJC!ZwyTs|weD;hcxV8xleXH)BkNIX?;yddC@_)+ ze((K#OvURkv_|V0Mw)ab0Vr`BWIvA*G%7_MTQixat$j9hRZ;Y zieYAs=)WCBM3YF{6rv81`DvinhNKfQ^N6!IUlby{gBl^ok>VeWO87` literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_12.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_12.png new file mode 100755 index 0000000000000000000000000000000000000000..4aa135cd6ba8c0d708869660226ed8983c6d2dc0 GIT binary patch literal 1666 zcmV-|27UR7P)qEWFLL%2t%G zt~s)8(eGuii?jjQNtJt9) z;onhpcf02GW?Jes${9TdeKo#)7!8D3_uhf)B`L-wj-5B>IMq>Py{mC$=s z&TM#B!hdTGD!;@5tT!P+EkUvmh_0W2YE0QsYp+$=GGV?oKW$hH-qW0Oa}1(<5v3+M}>-+w-TAN)+$gF){+!wNbm5k@ZKq7vFhBRlVma39GqGW=&afSdxdVZEa4Q4zq-U71%6`$tgSJGH;}JSh$^Qpp}> z7{ClpF#|J|?~ZB32;d1+!3aem8Lngi3e-LB`*{)fu@j|T2u2_jLNqIfD?nuOEXrr( z&Hm1K2I5tjer*2M{wINUfK@0OI8x~&a;a@s=VV5my;@lid5av$n=`tqsri`#m?Ba5 zV5BIEQ6#EL`cN7hfmtV-k@N0ztrc&X$>6i=TO)|%@I3O&m?h&&9iZPlwvjBav=)b~ zqhK6`$;RZed3WT-Ucs1pJXmgEo}uJ8)UOIv>FC^xVj&ozq+jh^@E^sTiGv0+5482Mu? z933q^uTPuV6~gK1Mvao@e$OG%QAX2X2fyyj*4b@MgaIB@m}lU$cQ{| z%qrPp#QD8C{w&qSQCPNZ+dM@vut;!({3$r9d@r4$erSE$ubR@%@Y(7O@*dWDJmL^D z;ME}%s8z#^qOXiqA!in>k~T9uR#`sscrWMQRnZI&rTT}^dp^qjBT+fp^`x~oa%&L3 zq-=!MdNKp56e{D60-X$$${_RjUst%rOCd<{G{*Y-@uN@)_iCuG0zF^kIA{J0RszvL zr1Y7&pOtI+O+s@%V>Y=pHnuD~Onu7>Tt&Bn3(j^;sQRhfPf zZzM45*(~=)+`$7gJeHfId7^$9-0c*wY~7Al!up5^ksTlL16B)Ah+P!*Zg~o*qRPCF zZ0@rBtL9Y{sK}F@_Y9s~{p^w1s0g-3z^=?&5%II_y|in+e>V&zy&xm%=j`XReeZ_1 z?*pi5UV!^JZ;orcU6=U`gSXp~^oz0(=Wc z*>*OS{lWQ8AR>S>Ln?Zq6M@wruI~At1SEmqI38t{AI-%ODR` zGFS=C)d+A!?>hsY5kU3+s9M*W|0N8CXH+7J8AD_s z{n#oem{Bykf-!g}gG!VO|HFv=wQ8h%D+D8gs$j@C`s<8};kLbgH}Gt*`_H0p#*p74 zfLisdyg@{#iYN+v6K5lsjA-{VWF7A9&%@E89=Yom%50e0m#sh3OS2KCB8#%8XbfAg z>OiXvD`?XGy8n(mS$UYOpht+}RWLKsM0@n;b37a95iv#iSxZOjcyb|*aAs7E7VV?e zUw>8jzzYf77w|T@@5hAdFR~wQ0ANK730p=#^Cco~G1^@s6 M07*qoM6N<$g45PMl>h($ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_13.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_13.png new file mode 100755 index 0000000000000000000000000000000000000000..aae2827e8596d168ae734a93f96135397a266c5d GIT binary patch literal 1946 zcmV;L2W9w)P)Y zdW-`S~);eccSa;c^Bq}RMUS{ zUo-VrkExzV^4PiQ3}8h*iuA}^z4I*X=D#O9AF2h z*nyqKcgKuk2JjTBpu

4&USe4A?!E&v_a5HHp$x0ucy?5bdhr3@kGF6yqcEroSVe zVezWWb4dOee@UPopbcsb>@<2uE!F2$naHWLN3DxR-l9PAWJX7}G`~v#GNhG05Gl%{ zi$qmPpH;>>u&bl#lsjdem1xo21h5f(D(*puxs-7YZe+Qo&s<-UV{jB#0P*dkVl{2>-@wU(Z7 zqI(Dx<5%YTw&-SX>dl+5_yCId2<*mX8ND^8qaFPp4UO>50Om~)y*k1zC-R~(Yh;a% z{=GVWce`83fGaqz>$*HcA)rjKLjD-68s95t)^pbU`nhUJ%j|ha3AOH>-XL$WI^r!3 zu>;R$mZ44VNR>Ws@M260i zLnc77Yqe0#CZ~5(-t&&$;;P6HKIDj5qNwFX*BH50Mq(W%l09Xul!A8#U^iMm`-IwW zIil3ndfpn3&a1HGcxypxKy7PV7hnlz&{g{J}J+ejLtU2}4Tma*oLOjBZlS{JtAjR$++f z%6v?#h`JR`Y?s==ZdBN|9^a7`SK;Z;z87HUV1ote1piGSYGZaqRn#Y2K#_NM`O-Qt zEPU1Y%y?!$OcHoUd!*6X7JU*s=hvi`c|LNMmwdo$kM3}Y>>QQOpaWS0bRb(Y*mo^I zelsYGh>T@rJ*9>tME&(Ax+eryNB8#v&>(b8-pg)ArVC*8$jH#TWaqK?5|Xuw6#}Ts z`};S8B0&{0SbfPYslO4#q&c1bGo_%Ba&amPL z`o6Uv4Cl&5I?Ub!IDn@_l*;CJEl)nvkqN4$qQe+D9nmPlJ$7oz z@D>v}LDl${oE2mv@zcQ`E;;d0K1m(Ka4nze?LC-jIWhI zL@?WNSRKU<%Zu#AOHXW07*qoM6N<$f;-N=*Z=?k literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_14.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_14.png new file mode 100755 index 0000000000000000000000000000000000000000..a66867a32660267596ccc34eb548c63b12d9079d GIT binary patch literal 1929 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 P00000NkvXXu0mjfPmjjh literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_16.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_16.png new file mode 100755 index 0000000000000000000000000000000000000000..2632103d6cb7b296f9858f9084fb7f1ea1991e59 GIT binary patch literal 1850 zcmV-A2gUe_P)`6pHRCt`-T}>i^k8w+dOS-#`vtv z&U+2Hm(mt#2hfW$&$7lpj^i=oYn`<2(ft!L(Y*x9SjR|~fGQZ{Po>djR*j#H&}#bk z&m|0Z4SVo+wj)974EeMkar;>VC^~|Ps zCjE~wOyie0fQ}|hQB9Ea3tHDZz&cYl)#__iw@jKJ!!H{etsEe#JJECYz6*0hs_8$f zubKL*=Tz^b^;p?-2CyO@MSA3|-hCIY>w14xN(mEv6P+1A%eL;Nj%d8~KGs;;0ayjK zQ&du0H?r&QNcWKro#{Wr0q7EtP3sk7cVz&*c4b{Q?XRG^ckFoYeNr5t)5sonIKU20 zu>(7e@6H*;4B#nLL5HC%9e&9H7_fUR$9Wm|HHp$x0ucy?5bdhr3@kGFEyhRWO@EJg zhQ+HgpT zbdjhk>9fjM2X=Kdo$JoE&PufGWbj$}+6f{(yomf}?9%b$96)X!+DKGa#)wnaDiB9O zx*@%E-R<1iUvMUg7pe^?GME}?jjMt+y4Cku`u!qyI_Wtd$ZMC7F=~V?>O{1RXl#bx ztl$?N?bHu6W7M62*Iz~l5f(D(*jw9Ux?N!2YZn*lUiTgJV~opU!xphZ&WNRpWc*%o=CO*W;=sZDr59!h$#5d^CmFhx<@F7R6M3P)Dde#k8>d@YI;GF^3jn)e70$cknN0ho+ zvbItEM4iv3fskZ0r4)7BIq!L!6$Vl}KmPw!BC4 zP<^-7vF5Tm?F3WTqq#dW?jI7EjcinVBktedo1R;{GhTCc3vP`?krmVq72 zKRb8MkDNxzVU68|Yze4>2?R3(WESnhY9x(OJLIuy>Q3X&^*txZWG_~RERzvl6$9^z z8odG6b$xtEF!JYyNW^)H)2ywy~#`cJFA9kaDY=^Hkb@LT*O~@zP#2VMW*Om(0poP1Scm zlG^T+-w`xs24E49vFuuR*Tmh}88qx(MLJMN!RXAN>1$bJx&T%WIY!F41K(hLITqn-Oj*l?o(MQBO5w^SNl<d z;g(R3+(|*%<4Fcc{*DY#(XXltv!~V^B(EAkXOvcSl+N$1NM>FptLO?*{1xoXG|?Wt zrZE}gTM9A8Uu}URrraU2J|XV2vG^L$;h@;s||W-j|q?;4f28=jr!{Vgl_8;zfZwRzgu5#vYK z?7Y{Idns*^b^t9j@+@oo<2c@Ce65q#-MfFsn(SVJWvpW)OF$J7<4>j8W>$@#jnHcP zx7LZKz_^?Gjl4bcU8}9 zdXJ?4F$Oe#i38|pLW*jFq(2Z{uRzq9vZ+>ItGZ><{1|rG&}iiVUfuDYNAJ5ZH>8^W zz51G|zj{vf-jf$yJDdSf}2rS^|cdtdRP(p$=Idi(>Z|LJhqXnu8iTQtW_|M z!gOPL>AKswvESfK5;s;GSY$+M92!?e)aa=1Mf!asb~@>OK9JY0MaHNRGOrWSYj|Tb z{ALB;=xBHSKr=?&8CdQP`BJi(2p@r#D)>EV&sppaMW5_ z#_`@URE%F)>)WC`g1g?l3B(6j#CwQtT$a(JIUVijZ!|Q+BLl#fBzkp(BPX(=F>7Rv zj{dzm|5HvEyKtQ6d0K{IV3}Zr{6(;8e5;(HacF%#u3FMQ;oaSuWPPlTc*G%gz&eMB zAbM(;G4wa%9wBEM>?CbxdaSa16!BKg|E-2*xJ6F?5M<<|+TV#vYbQyobaL$wf63Sg zcOA(L=%g?*ZxyIBh@1?vi2v&b*LXSzibNV?{c?U6D(OC&>Z?HC=LOCuzk?@%Xd*KD z&f3q8ej_XwMSNCAX6Rqn1wK%dwwO`;j9` zUD0?npWR;#cBUfVb?W$30xIp*RIus{tY)M01!ku0(n? zZOeN$kJWc12hEM@bTvFejmCH5g#>0Jo7G;AJ6K?j$7-`D#~YVHXBVKyS^F*DZ|MR$ zTRNS-(KVC$(OfGnTG=G~4f(MTtiYoJFjuqKl83C3-GR~Xn0y;B5 z^fXc%-`c;dF1O4^dC+GB?8>6mi~c!o{z!S#F(Ty!Igykp<>|4j$QV02rlQ}=zXynx zfgQ|0J9o~H92%jrutIWI&_za3@MK|@fL$B3A}?Ft5wk{|<4Sb4a;tLgD0(7`?$|i5 z71D;ww3;4!t|F7ox;v8Wx@bJi0PjkRUaJ`0z>d^t=_%J(A)6h#L}XfJoc@Lo7G1lX zBXdsR9sKNwG}&^#qik5HmVx6q*4n^stg`LS+KObaV#iT#^n8GU{+mEV0C$!KDZ4`O zLQGs{9g5ET6d^l}_YoE+dO8R2POWx3L?uObIqPax zV0-9wHl3*KL8%R#@W@zJ*3KI4Vf9dp8U8Bc%Qd58#|UNscIIEHIi6A#CwLbtk4C*5 zdj+DlI?waxe+F5olEF%9UTJBr#7fG)BGc*8$ZT=f12sJ3vk#O?pm)|s$c4N1To|5XL4MobYu>Ln0{!@%-rgHIoI3M6^FF~uvBu#;J+wR8zW+T z`7Cms+Rq}@@>cC29hsn7D!RBrw&bjqC~L2*F{Umf-~cQo&HERj zz!8uPjFC@=7my z?*46{XF#Ule6NigH5|$Wt6f55O{r%VnK^8&NUcZXJm;g&hsppb8rd_nNQOH^vcNmh zO)yu%h_=+h$`DB2%YP3?gCea+mXSxtvh{alfQo)qT_Ssm=CJiD0dyS4&o78TilDUn zXdgf%M?NO2=n7H%7VOM4-X7hiF(bxD3Plepsvn@|F-{=IeOf8es5&j$M{PfOMRwJA z`Zs}d0c(@xIHr_Dw=vL%YA4Y58OQkaj3W12X65-a-|6S<+y4Q01PDN{%ErV10000< KMNUMnLSTY037nV! literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_18.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_18.png new file mode 100755 index 0000000000000000000000000000000000000000..b5a85de1df6730ba2978509bd7b4f7445632de5d GIT binary patch literal 1715 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 M07*qoM6N<$f`h@c00000 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_2.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_2.png new file mode 100755 index 0000000000000000000000000000000000000000..ca4bf322ba1a693b44b6ddf283a5576467b04d05 GIT binary patch literal 1592 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 Bx1#_6 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_21.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_21.png new file mode 100755 index 0000000000000000000000000000000000000000..6a40d4148f0134314776a5f1ced7826695ac5bf3 GIT binary patch literal 2399 zcmV-l3840gP)U8KeNH?a zTbK>XuRAKt0*Ap?#ya|{dGkJV1OuaNz%^=4<`3?vd_vvMa|LMSb2g5#a`Jhj;vK={ zp<`9R7<#i4r2@w6V)x7@21W(S6YE)wGS*T6b`Ago!5aWv7N}5{QIJ~iUNI0XU@QPC zfKKw%HSW(C$yvv!UV$Ks1L~Ncvz$@}sA?z^Mr#!avf8PF#%x79)f~Lfd|m+~VskQ28!hvfA1`G0*czJ6SLk^}2RsV2nPa{|+-U{mdr?24@|{ zN&+CGN~NrWcsEGZiBYz&y|VF68AAKJdNWb;=5t2z>_EnK+5DSLj+-fmnZn)Ly>nu+ z;ARZaEkhZ=9Di6WD<);JQ+{L&%;fDg5S5^zD?w8MW3rQcXFpZW9K9KsS+aJMuhwN? zVRo{7&HFnVqmY9}tDq^PIss$*D>=acN>2w|%3suto~sOhw*C|YuzekW7#Nv< zl|L(GCwrzoGy8u7V71nD);r^9V6}di0dSuR+!zmLSGKRqWKNtMdn@~IwU`AdtDjM^ zLL9D9Nn_7DG|BX_`}o-jD$aN9$l}rUN3k%ow>lKh^Ze-n+*(%!4Grxeo~>iSJk0=V ztU3&tAIu*Xi%L(H2ltRqnLU+WwB0=ns}c{LTeB;BJ9@?tJwTj{&(UaR1T(gC z#r@SatCdk422kM3l7Mqj*jlG%cgha#)6p}=7 z6Y9i2(-UL)!UrsQ9jDc4+D*1YXPG_)O64EZKlfNkepe6kCj*NDfr`ruasw^@+>UkEU&OQnRDqk<8x{i|-{i=F|0dRyRCO*$}tulb} zr8_jB}(DJhn#$G?xJ~U!0_`fLk3z^lhaat8#Rs z5#3FhGg(Pt^YLp%vUPFHu^AH@pR*GhgNS=kIX{j_9M%;qQtP~w996Ve3%6UNl>p8iIVq6???H^e!;owBrAW6`FQ{zrW)c4f`r&AL|y1UP>+Si7Q5 zG_snSUp29;?0xI>GjYqY(*IThG6jyo-I{b-R1bilX7lkGOY}~{BM|X$1Vu6^-3p#Au6k$lF{mk9su`u?5|FBXWz`8tSmWn&scDd-wG+{oC@xoo1Jvk z7ci=v5>d^ZagrbXP@mC`suVDB!O~^JE@+%y1n0f|bucr)0YSWHwUyP!Ifg-Z8!e~S zWj#RD&HOnnon)>sMs*nCym|fcOQDMX9HsHJGQniK0cWh_UYT$6J_CXp|CX$lsp_z@ z`1~jVagf!~tW2O{#uAFgGab>ft~kkLx)BJSy?24L)1x@O0D|JKf?Fw7Ck#)aL)XmF z6ClWZtJXSTz_Ru!$&w%EsQ5B4(0?d$rPfx4XLOzQ4$<*dsgmOOqQHQ?%&sU~oaD#3 ztXyLruItMGfrt(W*olvyPsl0@WHunfIQm&Hb5hV2H~E-c#Q^d8RsyQppEZKaiAu8G z1E24TN?5evUuXiSEn|4!rJPdHKf9*?XHb>=tZ_Rjs+ru?7bPbBZvz2Ki!0qjI>s2+ z|LgMaSaljf1`>yU<7ad#TeBn8+rcOHbnNNw(~0rh_y7P@3YFrl;aGXKQD7(?GHajP z$SQE22E*1UkalcMN@qqHoq8hvH^9-z^s%hvxNUBj%iZhw^9A|AoX? zrM6r`|5mqMo&0P~_V)7)$v|0wT8+Q`LI}QsWP$3ua#UUFH$_pVyGFet zexfmS^sBcrHjdZp?G<3WmjU%EhLX`uX4K!3su$?^na$s^Pl;<=_Et2;$N{iwOhuL5 zSFukD3r-Z*wr_opptHXdX9@t^aHU7cPSwi$_$Q?T!5E+Yfz+z*iiw=K{s-Xk#uehx RG8_N^002ovPDHLkV1ghXm$m=^ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_22.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_22.png new file mode 100755 index 0000000000000000000000000000000000000000..67189811d89055dff6660f1f2c4286d65e890f68 GIT binary patch literal 2396 zcmV-i38VIjP)Us8+6VEd=o;ySl946lj`PSz3bj}QI|YD};3ENC6sWRJK@dCder87?ham?r z2RgxHpHV(SB6pn4~B8qziE$@b)HW%EojB1CeK z4EZtJUmoR?Kx>R8Qm#2EDExDjB2A*?tEZ5r1S8O9tm$ z_7zP41(l3hC*xfwRTaZ@L9*2IP8-Vlcljz}`|foF@u(opo1-vhFk-mcyH_ek zIaeV-w+*QTbL>IBtdJD>j@h9Q7}3XTU}lmPopBm-7^0QnJMvgNbNH%cM!{MQK0Bw9 z1?h?6bzh%R8J0O&Y34MhRL5aRKBFB7!1#2Mi`kp`E9$fzXMau#X8Rc#BLrZ*P6=}} zzDk1XyaUh3aqJnh%V~v3K1yn2ODC90hAPHv@6Aun0L+n2uX8d!J44Av=ba4C>uOJB zm(n34M8}?)wxibAJ<|U-0cLw$=e+YgE9jg@y$3_1r z$VsYyW0b-Z_+n)kbUD1UVAVJM9Bt=i&sU4Lf)x7RsNL+720N9T|; zyKSdJ0QRgZ24x4br&IAx9NZ#N==FvG%t1;ar`L7hj>+i4Wh8|FXw3~m4t9ku*_QZ; z)-%O`GJPF?#AhcGu6kWIh$+g>cQe0xUg=Zu&AL~6cao}b=EuCJ)BA6NGeF$T;LUZK z8+0X~eX3r`oXZe^C7*I$Md++btcsU|n3Z3$pUmt}hQ9bS(<>`v z$vVf+)#_)3g5;~V<0gUPbJL3{SP4a+xkqJ(A%|vC{g18hM1YJiv%yzL7Wt^6LN>bo zSA3!`UwHu;+<|XaO{>9n;7svTfn;r{nk^WqHX7~e_UvvED!<&%U-3pcuiLwkBblR+ za1Youx9!`>E zKWDzmo|Pm}d|u2cu27_O)yFF8LT;9!63s;bWJ~_+w6(n7K}4qwBfU^NX6@*eh0XMS zq_i9G*1&mcyLlA^@Up zi&2S|bx=_4RD6e661@FFNZ#00qDqP<$U&MIToh`BPSxoZvM+;C490MyjdYae_z=l$?8_X<#P_*dR3%{8gkt{VnRND!vNTjE)E`BcIAY zGp!TJLo*PPd9vUm#<905$2wDK%~qL?g)M)Hwm7YpK|fuKWBCxQ-(S8rtH{m60jg6SVKCZB#57J zuS2szKrJ96(rkgOoQ*tUGQb%i5^^YeMVuV~bC73;9j5<`E{>z!`JqCvcy4#~D_LU< z{uZF98XQ;O0+6ixn(blF(VA;xb;dzWr2A?W?@W|a1uZL&6t9xNp(v9DQ5meNcK1Aj z^qNV`Oe?7A?nQorZvk-=>t|@7nF;<)D{lo#Zk1fSsj_{gdKsETq;cAF)Kww?GA=8E zlHl8k%G?1gFpH-SnClz}^gUkz4BAC&~(Mx`jfJAkS;nfp1SY6eqn0lo!9 zVTyS)w1QSOWHM(|@>$SV5MU-*RCRXw&lFU55Py4qUDw3~I!;Fhqv~UdL}EkEuAynB z|1&+1y}O7Nqob3X1UJ3?QYaJXv)a`r`k)W^6&jFGDe zY!b{6Lab`m*-0}p)GUOak1_B+6xkWP6m|Jz`EHwV9P{LUMIGH8*w2(J+}R&V{sR#+ z!Rwrx3CwCTAaY^1qjQ`UIy$T-pPIoCeY5}vz8yu~;8&CSE)@6{rW*?hunb_otx3MI}5m|7WG2+&a87kf91v4)0|3i6rbt0Pf+z%(ixr@|C?4Sy5f%# zwtDMgdcHaacLURb=%<2?5+d(xG zWS%QjCDTmBSNo0&XdR7LZ9plDcy$c_231MW)om8`)%oe^-n>7?`18MqS4MXNSoJ7} z$G6CFW<@KyyUA8nvWoy8x{se0P#Lk}h~1U8o1DR^IzLNF-fEB?JN^TP4Vbp$QX}mE O00003aGA literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_23.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_23.png new file mode 100755 index 0000000000000000000000000000000000000000..b770f32088c543b70ba617ffa71b3a1bb7a7fb2b GIT binary patch literal 2398 zcmV-k38D6hP)lAW5=Djln9iw!>J!*}!2W2Xokay*|N@!)Xdyl@V#PflScL;-r zhGh<8+3PAwIgIJW?wL+38C6oA*w1|AP)j9nr2tqGyd*#s1+uJj5Ty23ujmNoFy;W| zz)JAcHOgm*RN2R%UL}F^1M--kT}&wgWIdD-gS9LPT<=s)W4fZAY7O2yo0WVQ1cpcz zB#V?KwrioPoiO|68rcHa24ubqm#nvTE9Sm$sV8%Wf?lOZWe>hn6`LU=OLk_HN`}fl z^p#8i2bGFhC*!M5$|^?b!eqJkSzTtOs_2&-Occs-1Pn1;ix* z+gqJI;=XFzb@7__kEqOU?^a569H!*#kgFV&9qnRQBxkSVpYh4~RuHp-kPl_7%h+g? z%Rsds6=F#c4ORQArTN`b3*Q_NfeKS8Y zeVt#J9#6J8F0MG8K@BR~xuSe^b-glD1p$~nE(p=SDpitUrR|{qonO$;PX#NP(25dh zyy|)J8fD{t=PXybtc5tIFrA}}u5M^khKz1)*Z)}pKPw8ZBmr{*20QE5OgHxYBmzvH z4%I3;utHEl$tss6@JbP~M1SG~7Q7WCU{%N*uN+!gTj_Di7R4vNQ-Nm*oax~9Gx%n_ zR@<>k`Y~VzV3Y~P{p^|RN6tYjlqFAw0Iu3um7`4N7iEt^PPC_5d@c!8yyy&&1zR~% z8K@w?6KIA~@wwyGMS!lX3JNL0pfR~|-D*2l_CA$UP^cp`KI>x{Wj)vJV^zG009nOn ze5<6AV6_dicFg#U5q=n?;JRyA1xj%}eyxz($9grs&w?Kb;q?SLpRWWz^1){@#t$Vl zqnI#ke>PX_1RLeB`_39w=@r>0lVA+}eU9q2qHA3L*Dr+N!fY^9&sY253Uyi(-m5ho zI++A4QL`Bgugv!t_;~+N0~&5%En=nmPZJu-5+Xx@D)c*w{y~K6`U26hQk0Gt3;evN zzY(P1!u9uT(ta1Aqp|?MBQeo#_EnY3`nH03R-8;9mjo{}KsO28!14)li53IG)^`TK zjxG!Zc6`G8UhVyA|8EOm{iTuwxV|%xoFs)t3Nlp?rrs+9szN`k{z?+y{;UX9JH{9& zo-Z?inh7d{U)CnxL23rTq*m+aGX~$4gs8N>ZW1_KPx$$z1vq9|ra9$KQVdKaHUqex z-*FhqbP18!+^v4K4-?xVa%S6gU4On6%&xI2s)OH&BZzYGOrxCy2EcH+xk5$z*hG?!+fO z1uz4Z@~drt$-?*O6s3=oQPDvK0q>Qb%I0q3RfCW6I>EoLYh3^Dsm}oTtTJhzM1(4& zs$i?6ltNl0&j6qHT zaUpX+Rxxb<2~wwoI3wWD#dOF};hVIIlx{G`_i^1A1OXhm0N|Qcr(!T;P~8y4DxiW6 zl^*-AB#0t6I%d^Ypc0G)dQv5rEA%f1b0yijQ$PCni9zi$ueOeurBMNt z$DYS$y8~r)-a-8Q2cO%1#TbJa_i5 zBET3wEnq4U4Z-SlRml>^RX>|X6_04(4y{1zys}w@%^fSWo7|B{wf9G#zx;7%S3%VZ zvKb_;OY!FoIRuidPg;mEX{W2I` zJ$-a&Hv!^(XAr#apYH@ZC02spB^)55jEt)56znT!ofr_Sw3}orNHF)Me;m3>*rPiE zeTU-}-%9X5kqliKYL;=qXQzYGABUn~Twk5^T_(OO@Jg3S)$02sB4ku)smw0+{8=$R zGU!mnY2R=vCNZm+c=Et?kxtGADjA*33KHP+xR0I!q7ZlNV@_kFrgvuCr%45WQpdJ; z>ZoM9Qxg09ate4k0*u!-&)XjJdNMOhMz;j8eIR5=QFW}0h^|Nr;!>|EUq9-HRX zar4^=FouWCNT-J5ovYKQd#d?u0hwWyj91Dq9-Z^*r|47qRD)gBYe(<@04_JN`SGUv Q9{>OV07*qoM6N<$f)^Krl>h($ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_24.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_24.png new file mode 100755 index 0000000000000000000000000000000000000000..4a899faf2fa41977236c1259548e27ee6723c612 GIT binary patch literal 2394 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 Z`~w)ArNa<0?~MQe002ovPDHLkV1jFZk7fV> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_29.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_29.png new file mode 100755 index 0000000000000000000000000000000000000000..f18f8798de868ac7f9153c99fb3d0fbe5b05b157 GIT binary patch literal 1852 zcmV-C2gCS@P)8T@Cx9bNF8xOF1 z+HH(+j31V89Dhf+ZQFBf+qUPN^|eH1e2!WA^crjYQFZ!k(sE05oDH7iY&Uy>l%ZhjxxH?E+A>JjQ!|`~=Xe{B;2*U{=`Ec=WLX<5Tv{f4`** zxHSWysJG_in&`l~(eV<{MB(w?-=|Kg-QY7U0}3c@3E@h zNSEi}RqzhKI~{1fECcBEr2xoY`syo3`mNV+C025AcAV8~%|FLSQx|Z?D@VqA@~)}{ zP-Whg>%AHP`Drnag{2zcj8`5RZXHaA=&ggH8IXu~!bjx}3}_XPjm(E-C=MgUP0h=rt|n;=;J4SK5dIL1M4a zse~6{9s^`)59!SmNBVn~MKWhI=e>Yip?y!U=ZCj{XB@LGHVU4u!pCx0>R3T^cvU&w zw9f=O!-_Bd4WkQ)F4mjtxODG@TJ?tY>ZF|(eWE!QNJf+nR?o$<*E<7*U2c@{=-bQZ z9rWf!GRqR$7b_4lH9!OutY@VIuLj64!9yA%KXY8qO@&f8qBn`IaqX~Nn?;mbDnN(t zF&JBWAo-r%nPA>6TJ<6fIqNySI*t%UxhDrP+8MP3n+_sBBY)<61-qt3Gl{FHO6@E(UZ?%5#EQ`_8;wjPMuEhDwD6?&_TYp|~eL8r8?%b&8P4u{n|gB zju{&)Y}@u%16X-Ax-7KrjB%|SIn6@)+=&eB>piS|)&>?2DcGZ7{k^9BXn)7JoRjFF z!VY9h}WDa+Z0`sGi9J zrNtFw*{GuL`P)EQEJO#4XrI}?I&RG&$59@P5Tz7Lmqdnbyo=2;SOv#%{J&iQ%7qcM zK3Obzft0zRN^Q|qH0lCy+qVC|3&=2lwYT#H%12X?T@41QPd<)aA7n)5(g~Eq&q@c# z1~v%p22?Ox>n2vxfpte?{Ks8@wsi$ofUU@tOmJqAhh#{7EnR^1FC&*3m--o&&@@{? z6ojktBiWzAb{xlH(ZkC^=dvd}s)^)!<6eNxd5P8P8To|}6{~lG#pcUhfc0`&xtVe8 zjJ8eI9;=tnQ1iFeAQCNIfX58kYM;sP=$|{8 zhwR=la;d62-8{0iE!VJxoOU2Pu0NGm!0z#aw;SDzW%=j%bIUGZ>X6@S{o1(*R#A6W z0rv?V8s1~Iu>89KkM>!)dYpNE3{zS7#D$=M%jk^)KVye?mb+BOwplC#Fmfw&`)$=X zdvOtKR02uvMXdVvlR&xH%H?bqu$m6;3@=4hfX>*{nPoivAfI!`VgDcqeXSKULE_1^ zjLkUKLFt(w)V|35tGcy(Pk)sOBtF&!L~=h>C|Rg752|afup=X1ejcdW@9F}qLJ{ff z#H73ILF(6>wX2JbSUnwqa#|PQbq7)oLM6X;1(cUBk&V*Ms@^N>pHsU4mKm(TU=M_O zd6`R+c4g`j7OMf%iEWH=SeMRL_fT*UL}R?SCYB9I_S}Qye~OAb!7@PPXEwN&eiz^= ziY{O3@+xVs2-R!u^s-oFf)#ZiT2AyhG|K-YNE?KVWt`M9DuvrOUi`#L_u;jpB?jQs{Qobz4=En3=zc?)WM5LpIW^h!GyqZJAtjEdo7PAA>RoZsudlaF=ta(K_uhv+J902og^~RDFQeTb!wr_BoVLz z=pvDgZV_xyQ2}z9B!Umg;3t(d8y%dXLonxKclx%~a;sj8D1Z(UnUGFQ<{F@b&y%f5 z4kw5}6hP;OMscg8<=zQ!BEUXNb7XU81@O~)1QW``JqMQ~M8;k#R|MuHQ2IO(l^m%= z51SVze_mdh>k$Q@B`_rlWI$@&Qub0rr1s0=0;$4)cH(&J?`4n~RK~A;7m(q6E&^kA z6jW7!7H0-L@u$@mTOXAZQIP>U#_RlT59f(i7_Yw&0+lTNgp+CQt&7e2=^2~bWQsGo z$pr}^HHSH$_9DmMP&x*&i}L^^S|8ZB0FYDz_}uhNY-PAedQD0$CypzYFC!BU=b z+4mF?e74Vg5a676mNHxtHW?>pSS{Sn4PIs9az=m%S?GMOSwU@%MnI=+Hmngyn;UdT zgVfi-R&e1j8XkA;o9%qqWU=zuW9;9-b#_O$s{;6`h&Tzm7H*2*x&Ww?H1VF#3eQ+) z?v@Cy3jl^xtbaWTnU0wt0+#?PGVv{hTaK6Jui|xE1gts1q3x0X7F`BYltAP6$@OKBEy_HY_5*I9l*c0907k+(#ZqRVICw&@b8T@w!^k zT{(d(0&oPTlyFi4XL01M8eI}W225pOMo6v3*+;Mlk+Q!jW~oHXfcb6U>0pXXN^)X& zUW!bS({JUH4Xk9=L}D%6ji8fY1z>j&0(K2OCmtJ*v)OhxfL%Y7kWJ$L8Yf;_W%LF( zc&-FjGll7UI%g9EbkRMft`HMKEL$= zGQ(6Rv^Bn`1Gt~#>S<~`&s>?HL}0-y0%#TPN;I0Uf$F=P>n(I17AzNld?ct^!Q2A7 zPk^Qqrv09JCc>#3XU7!&Sp!r6aO1dqpjr^xu4D!#G_Wi<9qqja-jYB~OBllQj0M)t znw5|B>0$a381$K#AfX^^YartD(do00000NkvXXu0mjf D0)@Uc literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_30.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_30.png new file mode 100755 index 0000000000000000000000000000000000000000..64cf5858030b039b43e24eb3ed49a26394581828 GIT binary patch literal 1829 zcmV+=2io|FP) zoo=nQqkS=j<9Hw8wr%Iwwr%G#>$5~=ypLJ>aE>+p8r}5&g5x;O*D7q=cD}kU%|U&x z9G5aO;}V)a>2!Cz{V;%aK%U}J$irBPEA`A~Vs;$$qAfI&G^GSw84P~`n1QQkUyify z*1B`NeS3guJb?keJ-`DnRg073mfk*B8&~aY4`u+ZyVjmb+)<{OTKv>oz&^O=UmKNz z<=Ch*EXR8RA?MuHt9Z3oy9LaY16Hr~{2XtE3{c&5Yf*1KlXI>Pf+zFaxdEN$b7_V+PC(Yxf0^|Edel-<=TNf1yi+n zVBFH%n%_If@k;_ATg#cok7 z3y2Qi+q^_YmTQ~yr-z5(Oypm7;*fHEXPdIbZQ3^*=fzGWDu72E6M?T z3y2c&41nw*Fv7hoXPP@E2%_%aqD6Ln1}yDYlmkz%wO5D8^XuSlbQdCD*0a72-?f0rj^lx#tsXG@t@QLm<-fqDx-B^Z%R#z<7s9?KFDEN1CCM&LQUx*T{tKqd%YqggJ6M|4>= zjvJP95nA@3{SgafYpQnmn$uRDm;9N>coGdeg==8?r&5$@BJ?$$q&ma=O*qc=odAlEJoZZTD(DTZ`no zMsJKf+=H*hqDLB(6CqQy!A8Vs2ew1g_#nNb-q`Y zq&->ZXYlIApfWVVGKyVaF)p7;R3FT!Jcjn82CfoapSPb^pNPhyk@&Nm$E>dy$BoIk zPk_gaBmGLp$Xe+e83^*#+RrUO8ZQ;%xMt8w+Gm2`Db8;#cMG_}e(7jg>g8+qYLjK4 z*VRVrN5?fA*iyk9az&l>&P97Z3$|bJp6_U^M&}U&WY&+2Ya5DKLhDgHR5V#$#;-ty z>>Ur%s$r;@t#uXSQh$~u`tt}ChpCmtr(RF6sU)iQdmP927H~E27Qpr=^7AO8;SFHhw!eD|cp9`IkD630yV@8V3>K68 z*;~LJ@a)DUkxY;&3sP6s`RJS0TH6kxJ#qrpeJcnmoNmeOy0yc3QZRV1GT#b>hzS^d z)>%*A0P(5tNaU5oYpnUcs%L<`yoO1G z7X;O%V+0;ExAyaGpw?h98)GwCA{v*Uy@U69iz>{@?%lrZ`djcSi2$VP`T=?or#%6Tmc{ zcmva9$a$80DZAvm0PCHVTiwWYnHYpvi=P55X{>osbFJ-9I=-*(ua`#KXp|)|a(YH? zHQi<%uJtC2{AyH}9!!8f)?b(10wTG0XJ+Q$vA2=9yQ@cN<@kdEq)@pC$5CkJ-IJ@y zzat=!rLw&RQ_3`&U)^~2y=c0Z-2%LFAmwO4wpb0#-l*f&8aC~Wyg64x)4lW-z%oO( zVbtNXO(gazSSwi>o;_8LuMVK)-v!9|as#st>vCshc`~&Ow$@eKW9gdJfr(xE7eU&1 z);K0}Ja(D|L;2aYPXe;r^j`$^pXIw$lre0Sa_<1c2&?svCR%H6J9iZ1(YIV2QPD^+ ztUNre^PZ98?R!QxmZH_zT6_Pi*ogMgK(A?wnxaaKICpeS)ECTTWj1L>`hkyqAIJOe z0oYpr)3IxP#fl)%+c1z%hHOs?W2ql0)MB z7cs1{RgSeHlci?Q4$lFjjq1vvS^?{JhuCpK5xUM2`F-LnlE8~EPuP$Vjt zHj?Gp{p%k^N`qJYvchqnrNXjcwb`*Kr&_oGt^@=SsO; zBO{kU`ee{O`8NRyV4coBmJJFc*ZSmq#2A+Rn*fDCw2$Q0{293(XPM-i0EJjh`;0ux zB;N!m#7r&D$}PQpE|Nn#+b_KZpl-Rf&-!=;(5d|10v4cm*xh*avJ&Ge`}+HDxdl9$ z0Z`UkV{%U9uxeDC#H?KF)wZzAvsXBv5_-ed7#hDDmh)HK0`y_fNh5VjTlBiOCqU+u zd)a=+$=Ap_f0hBP`PO@DpFX=K0cK7)a>!GVb^ff}(%TyUk3vHU-~}+6ub;8H-bk0X z!Mor&zq%Y~y(|Oh`9lei4f^aUNBXVPa3^MJaaPXi)#Ls9DQ`u$fEB0wG}5?I>GkgF z1yE;RRqMST0PWLD7UYwu9$>{O*Cz$(M2^YW>e)2|mi8;k0ecIG4&rbEFpGPqoN4Zu zBw$XwH(tpWEbUj615dBjgCOi94zPlgIpxfznZQL2py!Zm$%t4dQ;RF)mR=3dPx}a+ z>cyoY%-9I+A-$RANPlNpB;%Vt?j_s|?R$Dh`=6cJm=)M4d8!&84VEQXc+ShL?J+R?&Is)1~fM_4fmLZx4j{cO-B&IHKNw^z?A=*`;%%MyAo zRw5v$M&B)y?3x$2;Aima#%N{Q2+Jt; z`3kullc;peNZ|Fe9_>dBTqT&jy!u3KtTs}Am-CqCE981(a_$r0F=M7*(HMDF#zrQB ze6{xP79fq63bh;=w37CjBzQ{jTg%-7uCQMQT9!TedS&Tc&hZ+X%5q?Bj!6)f$}LvQ z!1Ie9zY^&5_t&nCMRXo9K<4=oIoeRf5-2G>R5V%MiN6Ao-w<}~NUMfPWNKZ7TMV@!#G8 zdf;VQilFt$X30yWY@d;ti~LZP5XW)+|F?h)16a>iQn@@d?AmgVGkt|j=)TUb9U0NN z+ytVtRpkKLz$U>HfRa2aE4u6=*-Uvm86ZRFat>mG73DyKWeI7|-9SN?K8s%cnU0a* z%eDIZYlMbTm%h3jXx(~)dQR3!R{-@TOqoH+GE>&1zou^iSC#{7Oxn=r$nUeTSY##$ zqw7^C^md5dS#=eiD2oBC=PTqt3+r@#63o-VQa#)IWC9)SW7i8ajTp-S%(|n__R7rDz}Uw$p0Z45_l(MXYel~i zWWBTN{)Ql~1UfXwK*s~S&l zeXGPQy9IdVK(2$J*wii&{tLR$EAQSv3 zxmZ0F`1q~hZXg@1=v~g28<^P$>MPi1GHaq!?ybw(GrD)wRBL3DtMoU5kURsnIrP|R z77XQQpS=<~k);e_`u_z;Wix@XniOyTonRPYwSMoQ4KYAO-*R!5iZaGLDX60qJJbB3 zYx8Pst-b$LY()FoK(A@LvLn_utkQ*gA&t@Ur`O9|K{L8Oe03b}-v?lC0Zhk^`id2S zLF=y2<@rZrcE6d^&)$Bl`R6q-qWvtg*Se9CVd+?zaLk@iea`H!%5Lj$NbJ%;rtZz$ z*FB8h7?2%NgQVv&cFZQ#)%}fiyVLmbXX3niK3daOoJJ-IqdEG literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_32.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_32.png new file mode 100755 index 0000000000000000000000000000000000000000..417419f8582eb96a70806fa7b111f5709d768291 GIT binary patch literal 1888 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 QIRF3v07*qoM6N<$f;&2|82|tP literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_34.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_34.png new file mode 100755 index 0000000000000000000000000000000000000000..26dfe53911400e4daf2cd14640154546bac8b442 GIT binary patch literal 2056 zcmV+j2>17iP)2?kKk#pls@bWjJY8NljX zk#FZ?l2!G>^)ve5*e+8@2FO$(KF7tBv8oa=Fl>B>8BngWbv9_(B8cSJ_m$*hHd(uv zT$uohPFB)4b6#b9#mSztD)Lys)E3ZTLYA;mg-7g$W2{`40Xo5)q32y!LXP zfUqQpg6sK{$z|I{)_*Abw8B{sBPCaC1E^#r7!{~Wz>3B3UL`Rb6is`mBmv6I#7*h% z_5k1729$EE!Ggeoh2zl=Ocv80{iv$nzON(!%JAZ*^nWG5?jU!PVKyne;37JDc#Ouj z0uNaxoMl?n|Lh98J1H_(biSft3|1zn7!>AYq->mP-#;D?_Z6)h`A_NaL}&dUuhs#| ziQ`!|`1s6@XNkvHAd{TY*lH4>FWwm7|C$VulQGDdz^UM~L>Zs$J)0~x2^BJ9Gk!oH zoS5l%2WvM2WPlD+&Z{~b1J44S(Gkb8_so8#_xTOyv+FN0fPKcCs+^ZeI%En@2X9@4 zC<$=P$NcNMu3Z&p7iWetV|FDWb3!zZC6B5qhNu!VbF1>%d&LgNaU9Pbm7b|OHs)JQ zgit~9oSQ*4=oO${mSOO{r%Q?fDoS~aRp))10a(T0ST--Rt(yqmT*bE8>K|9h0O){7 z(aLy)3ep`6V6W)Jp<@*M${xNF+3L6cIMF%Xy=n#Bq2lA-s-8pzCSm|9gNPEkbTn7%XBZ{MdFAe))H zp(XmQFB!7 z0W$V`{+Mn2s#{YA)&~0-$2_e9E0Q3a%}S|^>`GIZIR(u240hd$o*i^$V)6_*A72O0 zCPBq{)!>f?DLa-ifTs(q8a5_^ZR9%J!z`5D)_>#PiGx75>+>!Zw;)Cs(jts3R=EcLg6v#6e1yGvbo^b6<&Upc1|sO-L~#_#f9 zb{$NXS7QG=#}EEa1G4af7zLkI3B#)9Gul2^`LdE^su<{Z0p6r>c~ zbdAP#d!XuE>x0Kb{7&F_`0cJ1UlB6FuTpU}*=J5MW6sPmvb*NE(o?`4$Qr|vbaf-E zlE6CwsATiH@jJKGvG&I);5hDW`qX~S^vN;+I}K#E7hIQ}CNkUPYG1C5&&U4=VvF5@ zQ6XNXRD<84Yi2)VpsL)vF}B$gjrgS_UxW?=bdw<3cC0>s0i&=!{y%`lUoZxpU+D=t z4d1Gyx)X1w0DSNl3_!-h`xyq9vGuPN4f|QhxcBzo!iR&h$FN@jtH6k+aa;xH7xW#< zzw>7(im{eH5VQ2fu8g+WdcPL@s$K&fJu~;MzYr%I6is5ly=7-mc2>wr mX3HjN*DmI78xK}Ic>M=hEj;&{yMVU<0000u%nRaaM6eKgP z{cPKIY>VhPj_vdL(DgT5E!nl9xBZOg(MCLrwj!TskG&VkXZ}|7_Vn3#IL`{9^Kc!F z^46aYy5~f@9y!tSVM)Uh4E>S?pZU%6E7RM4MqTZCngEdzke~R50F;lH<(?&LDn zWA`?9f?9Fj+YkU9GYj7sEH!y7AC{P`vN2vLpV_)2vxv$6^@{d+Hls5u@NNBOeHQu8 z23bumE1Ok1LuN)h9?pUu?XNiTEeL>4?Z7t+7E1!$UqP7TIDXDXd@xSPd$-KJAhOS% zBdv0kqk8B@-%hb+DP95qzU#)yiai(2sUc zrGld*KpvhSyw8^3Hvu}8f?8M+HeNKc!7%{_C`z86>oF_lOxPXBBz6(p9}M z$SeSvQ@tQqzMhj2vh7&~xhy}6#EQP?AEy6j+y4DB=;{h(j=u{bT;l_noq=z*ALXMj zBJz@p$)y`E6 zQw+Kb0lfRly$s;OUW7ziOL`2%b(VZ%>wz!27C- zA8Xa%55>M2NiuacGNB{ULF`_IyntkUY{IEByt9lCCiT9;solUM*+j5L%w8>jCf9S; z*zZ*pqsZwhRg%E7(Mtj*zl8u@!&F+G1Xf>5FnE6`9QHV>%_|ntmgye_WaaHhViB%?(sq}PpU&ObHkd;JW z=%LC{i~5uR5i|Dr4pJSb1dHwW80Gc~q&IXlAgym<&~q z+2GrDg|d++&J)UT=eP{sZ6o62wHF&G)Wy*~&rJJ?)7YVf1A3Ib$<-?)d#WdE%i zj6KqLXAPQ!LK4f<-9jlE4rdxmcL!cIi?y91$|ruKxM0Rcku1cRRpl> zRRrw9JnF8Z=FD8d5GDOx06Tm&W$?>LZ3mX88uZK$GXYYmQvGO`rSA+-VR{#3y&B1@ zau3NOJ6E)>6;A-FjG1%xadO8DnSF-!NaZ;cbD`>Ueg+;vt~kWo>$Q>Sykd(TTA2Z&vlYL1s8#W2_bJRnB5!@$gue({Masa`b<{Xp znZj}Xsw6aqth^)T$dKKS{e@Fg!lIT$9>^L*d{rpWJ(p^sh zV~R$3Y<=7OH+YJ^m7$OEXIINR3eZg8SJEE_|B32n&+E!_eD~=^O%_j|YSOLt=gJuE z5wkn0zhX?(uDqaUe5<`POR_gqbd`3`nRih%?W?-XysxMxcozZQOd$eXN!_<*=bwYse+aWS^y^)j|pfe*Va<}a_k?r+KKiW8Y^WArCgExVe0n+Lh z80w3Q==`dnR|6I4hWC*7tRFiv=wN-APl|I^B*AO~;Qd(yn5oj}>19>2RrT=nVf(%b zkSV_^3Dn-NCV*P$Pba~Owv$n3s6PFF0eHorvq9oXVguQoK|w#M$!4$CpWQ3kjrM!% z()R))>LfwtbOl0J!v88H7`8>WC~{Zooy}3DFNUm!F-7TU7@dLpE0e&p;p*Ub?fO0i zFuJ1GDoIqG0lM<92>zg}E1#Xi^@x7n1mZR8u^1$~&dx`@5arr+CTGy9N?AqBk1LBu eNea(j7? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_36.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_36.png new file mode 100755 index 0000000000000000000000000000000000000000..7bd425e1d105c77f4efcca2ed6d34050bc3305db GIT binary patch literal 2042 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 DQ>_I$ literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_39.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_39.png new file mode 100755 index 0000000000000000000000000000000000000000..30b6bd67e1c2e55059beddacd20e0c5d07176733 GIT binary patch literal 2000 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 B)AIlT literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_40.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_40.png new file mode 100755 index 0000000000000000000000000000000000000000..d413cf0ede7649a87df21726f796cabe144cc090 GIT binary patch literal 2127 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 AoB#j- literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_42.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_42.png new file mode 100755 index 0000000000000000000000000000000000000000..baf3c7c79d561f7be0ce0f6ea3c06befebe5d16e GIT binary patch literal 1463 zcmV;o1xWgdP)S}W^h z>HZG52|zLZvn9^Yrgl#Rbkjgv8 zN?1tX-9a?u__}8yfM$XnvmCNJez3sfbgT2Zu{FVV8Ubd*8#|mp7)l4PbG7}sm4C^& z0|6`%GrQd#kbQW^ofUN7f&it+&Vv)x1XY~8S;)wOyvGMB>n;Qsfkph9^N#C}$(f1F zv^tV`sR>{`pH@N}@KAj*tV$iZ%% z|0Vx_`Z9*((ZjepXGQ+XmAo z%lYFtR#_=K9k%?rGl?6VjW!h%v>H`=en*`Jx7S&5(2n@(Is!7%ql`RvCZcom_O*<$ zoB*x0=7}D^1UZ!7S(vfvyUID4<7g_3Kivua=IT6{8*|icy!OA3f2@#>II6vOylham zf&Qd2w(C0X01;)pt0`LWfCkaX?Z}7;DD1GO)n=t6q=6oVd!G?jXOj~WoU<}bjL2OP z54G0frQqzs3kGlTiIP7<-)v&dB*19HWBzfdrr?9c0v`F9Y2o;tcNbh40Dmo{$PTb2 zZAmm*G;O62O`ju`L9RZ7UEKs&?fjF^TZ{}VgFuh$esvuIE1kb%o3w&jb<{l$vu)dD z_p6xzD+?l}*n&JwTPQbL0HZLbquH`EVSX$4#A;Y(CNgvDEkzD?R})3G14znerdU~L z@@Xp$7N|~v>h}RGtc^z2ku%e>-DU@F7I5=iO_0>z2bke}ZMtRczjGB*Rm>eo`F((l z!U|TXkz~%U$<|C%_3sod}U;mS$%o>j9OguPF`8uKUkm{+%4(krnj1VyM=1 zkAtm)P)>&5jj~_(F?|QHIv=Jzuao28j!3U-_5okS2zK8qBjbN$seDk#>wx&+Hv$Rv=3`kXz+k znvKb>wSJd^xvHF&S=3}^ZtIyr+lu3dI`+7f*;228-;p|C1i*;UJ4k{l0$IzQr^Wy) zgTD+?L%T!0eqd@wu}n%)xIQBpx)RbKEvWQOO7ARJD630`&SFL=mT}Q-j{{U^2Df)DV Rzp?-T002ovPDHLkV1jg@$Aka? literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_43.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_43.png new file mode 100755 index 0000000000000000000000000000000000000000..b4cb1179f388fbf1cad5040e10a9dc10728725d4 GIT binary patch literal 1556 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 FV1hu@Gr|A> literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_49.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_49.png new file mode 100755 index 0000000000000000000000000000000000000000..ef68cf86614c97098e4bc877527deead0c8b577a GIT binary patch literal 2132 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_50.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_50.png new file mode 100755 index 0000000000000000000000000000000000000000..a44a566c7b93d66ffd54487a3c8cca7d48ccf05c GIT binary patch literal 2191 zcmV;A2ypj_P)eK@OGHX0y7O6ks7R;8TS5=9aq zq`u7beO=dc9kSKi+<9T6*%UnikSK9 zC`T0+&Szj{O2%-&`xxuGFA3muFh0-7@yQA>1rH0WWWMW1fu+FAa*BB|zmfpEo!u$I zZz=%G*Ub+>U`2O3SOLfCsA9)^YE1N-DnJL!NfAFm0kCir`R>j~5D=g&W~Ll*EX#*I zUp`)J@C4LO@pi%NuE&a@3fEN@Cf^8_OP=UQ5mR+G;)Uz0TwmxcbMgsSR^%#9%#Y=a z#q7v;tXqsFiz~+Nk}IyQI$bHVvKLOSidlhky2II36@Q`tSk#Gp$M+5iaC}E$p6B^C zH}iw_L*FkfB0B4UxUNev|e^y-8!P$DKD499&BXB!) zi0i(l&i+l;2(khI!E!)|ep!*@+N%2};A6aQ{w)4iCm1JKcKvMOur|QLgK2PuTUXdz`x8vuhU^RDk%50k4X;V@};1 z;whrKeqNJex}6@A?Yv^JKcN7)CUz_3Xep1w*pkMsla#TTM;CS zSH;&ML7C@2Iw;751b#B&cFhX)n~*hA04K(5-D++JtT-o| z--$zwAD;oTWKp)Wz`Bak*^8^rQa)o`8G+b3UC^Rm7T>fvS=`4IpaO`!7zs`&$O#2- z)?jP0_bjg}PK@njQ!-*~D*z^mpP33Ez$|E&1T2mn1;_wo&pNSUz7=Q~?~anF20N|* zY`@ADK53^FfaRh{5Z|++qfZAk)>IvmJGF}M+X~ynHhr@B9GE6o2y0aPxX&h5-sprPLrO{wx->l{@8v?<_> zEDQLxy{uOx#6$tI^<5II_q%n>)UrcH2dlqZz=W~NSV49>AAx6yR7GR)#rZMb_s!wkvTMx8p0Dm&t#ev$KAdOT33;Vq7s7HD}qMUGSZb zQvoWg_F6X%77z3D4zRMiIg;)A!Z~U!Rmbt~1UjA0ieJeWW4l^EzOWGP89?P(7IgKTCD9$P&Lq|N z4*2n2$D%Z2Zn&*=ntxs515#%# zcke%akYaHxJGEBylaZ6jeg*y)1*j&YEXDENgfbTI7z5i?fXrv~g9TSguw%{vG6Pwi z0ak4B{{8%SCAah6!SWqb#d2i3Lx|3PzEc+mw65H<_|`c4{|AWUVz9qyYW;W^-8qy+ ziwCsopc@Zuoi$H_?;d#V?*qgD*Zb)7CylM0C9mQ}A1C6<8l-cknu6X{0{i;_Y_dx9 zoi;f?RE3yyYi6oBvb|XuJJU>*K%I%~p9QGkVrRXR2$g?#?W)_SIWTY)2splL69rJ) z%UA(0piE+S%q3O@ES?8WtQp7SI&MeC`4uTDl4ZED^|uuO4G&%?EIV)^!@GVfb9}O* zP7Z1g_`Cku^CJrI6SjyDU-;bd(aj~Q0lO-ho$k?W?H&J^JH{&&04IIA#bi&N2he}F z$U7Zd<+&^W>qQlSCH?6Z<6bed7n|o2VJfrJj%8OT9!4gWQ>34=ZhYCe zj&i(90XqIzTr3}k>#}eEPhcGMZ&HAX;hsKGf@RJ&dJ=q;^A#5=U8d;weSiIX0d6w? zFUI7xLjuR&^gy_?ZN&JpGsATye>SdSrrJ|K*;dT4W8!sca(^Al|L!)z;$VKS>-zrF zz$yvl+$qROQ?i|b*@`VTilL5{~K R10MhY002ovPDHLkV1j_4D?$JO literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_51.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_51.png new file mode 100755 index 0000000000000000000000000000000000000000..ba00473ddd16115e1bfbace6df50d3da336372f8 GIT binary patch literal 2165 zcmV-*2#WWKP)RCt`-T>b9a^fv#GTzu01rj8v zz25b89LIHRvg^8z*Xw2PKjDgo_XckK&g(fwUdyqvpXeQY7VYoyUBRvJoSx2iK%6|j z&q1mC-zHrp5${JyL_e-*T!C?3GV)#CR9+o!{LWbMep&!g5YeVjx~c?Yd@hNRzm9Tb zxOlxHD^fDW1K($?zM>5D8j6i1K(BgsOQ7S zsa~ETwUfN3DF7#CRlb?5YVn{SSD3D|nJ?bo#d@-{$jNWLa(pUgPG(2GF>jc&D1S(D zwYYF^*Xd4~ot?ZmBRzV*lfAVu!d%z&pS6)6=81iu=v& zOR6FP{i-ChWaHWnX-)+?`EmHq4w&C~b%bzHL`jVZESDX>P@yrn# z6@l|Jp53MZ46yTY$+}AcT(VRgSs`R3a$Js>Z0vN4z~R0{0TArCho515e|)eIb|^wd zj(T4uxS|4Cmu6k5_Q;C6JHFoH{QLes(pB);eP`giM1GEA!x?}#h$L**R*L}FMgAj< z?29Em>+*_RM`}}zGb#dw=fo=C-B~*gLEohS_zr(p??>Y$!tacqcjCkS8GAT3t_1nY zjK%dGixO3l!zw;kAd38KU$;J5SKUjkujEfDG+#^qE?}nOXYBRTzct{8<#Lw-RIo++ zW^1cO(;>g3ybkwI0jQZ)0na03#+dlkpk~hi_>RKE@ns!dElxMDogci<*j)F!e6{{- zASX-NAai_F;#7cDQ%P7=fXbc@!wUJT2)gU>ePyaZ-xOfyW@Cd$x)nH1WYu;lfFsa7 ztK#hxCu8Zv7hzTTeN%wWCQklU(pUvJVJZRDB2ZnIT{*eVRE(|&@Ml*%UD8xeJ4Nnd zQh94DK&6w%&r#)l!qoVQI*y-J;#Q#L;AG-YKBq3`kYv;rvO=Mezsr&Ax zQf^PRuG6`l^^P?58|@zzpNdWKavT$X5{Q9D$yPvibU%(26=c+DS+W>wwFt(~t4)+O zOSBH3XI!A~wgPl)mT@+)+q(ZHphTg zJD-VHLEs*%3NS1Gu5&9L%=>eES^iv}NA9BaQUMTb2fXS?7g)t2;xi1LGr%qdRC8Tn z&$?gvs&+Jga8KpqPX@NKX_jy`Gb78oEyL02z|KKS5zKgZn%l4IW0)G3Z~r#1V^KG` z%`1l>eqNC`^-Pf-#aSJ53Nv1_ZsjrAnc1r*mErIGxmoWB9?!Cbqc8Rs?W^9+Iz`3R zCEQ83!j<{1ImU=>MY`Z8<@eX)WyZJgZwC-EWS$T|BUOy{1%XnftAA8oj8{N>VdR_ zzcX`J3ZO#T%(!NJKkr@Fb#)YAr_AlJj6_u&)wPwqE7n!A9pt+rm{x!Y;J3_mHa>S< ze@6_ez|bm%ddJmS>kQxm=}6-)!};4GKl8UcmT`{328zZX=X+NX4F7l?{>#4wD8T6G zQ+D@Wv;MPnst{abbq;{l=5@+9r2tV-cUj~ggB;h@*^c}1)mfxQj4R69ssbo1_#Ang zRseRc4v9OgQdm`-yr#Op>nOZ-)+0%gDy))TiFru@)CQgRJ3ierJ`1dNzp{?Z^0hin z)%{t2%D=NM3h)U-=~4;9qU#iF)hf-g+DsKZZlId)JzZ1xdtM-(U zQL))E$L}lkxtn~Y3_Kp3AG|Nm04~_yG7hSyl2kPuDSZA|DFVjRU57X_JjY@F4u6C;3UZ-i%7ADn?U;*Gs!00000NkvXXu0mjf1RFsb literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_52.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_52.png new file mode 100755 index 0000000000000000000000000000000000000000..8045749acb6d220582409da302be490c77596535 GIT binary patch literal 2205 zcmV;O2x9k%P)p0I-KL3O@N_=+v9e>927$cs=SW!>5C%((U?~!{Ek1PuRoq21_P+%;`?epCTJqN86f)H z(I3wv$hv-^ea9c%J7o&V08R(udo-qkRh@{1Vf!b{fO=J|t3`90pwbh+cfiMDig7W$ z5&()$cG5RXUT1$N$yu^G`dGo#2$(P-18mjd6}#b{s5fPRNikRG`KeVU1rx5~Wc#YT zq5i6al`Zs{F-P3X;$iP^9WSZl2al3-!34zDPSG1u(tJs41r|PBRzU767nUYSYy`$JuVdt}?D#;4M zKu{Go%O}&z#)g|e)cuOeSrIEIcZ>mavQvzXs7}C+#r;_)u^1FhXVL)y>U8R+{7>%y z?-~Qjx!uBwz>0~k2?rW;-uR-Cgn0!UUSS+^)Q7=m}Qg%GsDVkz|PX3htuZ}bS)s+AbG|_MR0}`L6u!=y@Sh{<>K2 zW&kFbFqJ$v*+|1tk75f|@Cc;L-;4p```0^?KUv?&0IXP5_fKfID_Qqv{-Pf#i(J=r zeM;wHaPj=AA1n#gJ_epnfh*=<`xTR@vBB5|zbke)j$=Oqpfi!SkiPKw{khM@eNQrg z>UI{-N_R7G#Z?s_b+Gpt6EHouyqqMmWH7ppPO^EWo}(}zJjp~3W3YD_v#A2%8E1S{ z;I7y!i&HW{RGbMWMitm8JW2#Y4D2ZeP#}mps$#|c8FN$=&-|T&fa$r{m+EXs*u2RQ zJe5@tL_e}PR8nzl;Xa2EqMnMbnQzL^3It3~d{+LY&`v;4$_fb3U!}VmORoKBYuC(n zSCuo?w8uCj)n7hl%QN)a%&kf&j<*uRB1NoOcS|A=n3l+Qk63 zPWH3Y`Rcr)Z>CrI*#$crVJdIGeiaZERKk!o5iGnTyBWaoF|%8p?G!7Xsm{B8D8JL+ z2Vlu@f<@ZS&U8n_du0jFvt!D zaBMJ}?0bxB0s?hD?&-wA0BSQp2MibtvH%vCfyo6yHq#~ypbDtoxxRM5iN2`w7zgH5 z#i2l8GeG6EqE6K2LHkSd^ z{LS>BzN{%6znI=BGb`|+?)!V20iyEEkP2#5;#hH$7SAf*?)PpJFyLf#jf}{OhI)52 zC3_!jZ)E_KX~7v-?!w>W<^DuKOc)@u?}Fg|o-qR3r`|arh<36uRK+-AfGju+X0`LN z@GOujY0STPKKdKKXPsR6tX8LJEN312u3Xo(mjSRNqf9`M72femTke<6mMD}nJ!0ssS9l!y-<9`=lImRU0TipSa&*)1v z_ShK~lMC`{w01{WC0LVh0CesE4lA&PGe_K@oc)=PRhPPPM<23#MyIeUu9f0DWBj_V z{=W^(q%-$B_kzhiV)q>&>)c6aVmuC8R-(qOD*kz%zyEcxdk2X7qXXSSIYBx49RHM_ z&K*Dr1Bx@hW4_{r&1F!h=&JbN3{WX?ld-$-Zt*kec3DQn6}_`UuKKBTMSrKoKTq?| z0??@y$@JX8cA3PvbREZDfGSyGKy@}_6V{||@vrMTzFY($#i8*|kSdtf0###c;xFrd z_e{rz8ncx?7N5GavG-q{;xlCvdFqZ(`M+X!!hoH-i*w<2!PjAcrN0rhYb;E~LAV-Y zuJ74si65rdy#!W8%luHjp?ziN{P$h&*Z)lUJB8WqjxUr+f;&>_cRRMj7@03422cUL zlL-_xcE*>hSIK=@%PwvB9GQZhQFoTkn6B0s>v=Z*Mo^XrC%{$7^t<47x^UuGbw_eR zxXJ+4*hbJUF#tO5(CkE9V68Ghv{${$K;?`fXB6!yKJyQskM97POcL+tWI;@5a`ghJ z{F>NjqhcjK1VI(U76y2S;N0zWUNTHpDL;oRHseiV#SO)o#ME6Ew`g5_G0|M z7@$htr-GsCI409^5Lae@<>Gex<<4|gY^F!qqyIkuCW$G%Kc6BQh?T6|2{QY;il5yT zGKOG!V!Tn`uippgCQBvbX+ip3(X+~IYHU6M0_S|C=g0f2k-=BFn!s&lEhsy zvAbG)^dIjE$bh%J;t!Q5r4BMz)*Z?DWDrc_#B<7i%qPmrDUu^Qk^FS0yb}cX`UL&Q fb6Fkmd0hViH~V@stqmD&00000NkvXXu0mjfn7}&( literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_53.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_53.png new file mode 100755 index 0000000000000000000000000000000000000000..18337cd63c7b7021f0f14c0c2f4b510736127926 GIT binary patch literal 2151 zcmV-t2$=VYP)?5CLUs+GI2iGF|F5VT~3eV-!`5h1kkKeOX z>iwsau7Zf~qadOlS2V7`I4)W8UEEY$9d7)dvEuu*03s(Mqi=eu0Hc2{h>^Yyb7Z*q zyh>KYWGWB*p0SSiwg4&}%nKTLA9 zxX`xibjQq2E2z>Z z1gt~FL4A+Z^>2EPgX%XN3vT?iDFxa0uknonFPoNp=ay zf~t~$dR35_vhi#uX^sUt@p1UC4w&EgbP3_2h=Lj=kmKhH#7PG~;^-8ClCk!U0iGxZ z3_$1kL=m`PaFRJWE_g0pO6Ps|Ztq!9MR+@U&ilUar#jF9%|ub@Dh?HZPKT?tsKeNI zrMDug(<^wR{kVQT+41Lje!a)f5Pm;C;~egLk^vYq+EfW)KWC)U6jAp_cq_#}Z~iy2;W$Xx*v7(*GkV=+r|Jxxl3>*@hn&fDtNmXzy(V& zT9xvnHCLR7!xbLys}_O7y@de~m@?Fzdn~I(fH;(7GqdAE)}>ijM)7mS-5p^$0$)B?8+w?zzOSOc8HR(V`f+V%vgl?Us&bcpkmNm1y_U|%gSn5NyBac8SV(@B0OUJmzH0jQbgz%xJv7(WO=?PrixXB@uS zI{@xcBZ;wP9bGL>H?N%@6+h0KV>ZbEv$KXls=#q@X1Y9*9Sl&hp=_$Jhn?f&zYwJH;+^J=WFHm75&HUmTnjtqB2IW-bL#>@D)g5a4r^tye# zRta)tEY2jY+mzKYgu2JqQw-ol&9T+a=ftaEu?<(W4o9_Pqt5M|cS&Qtk^QLnlyA5X zZEt0ON=J4iN_+(e7e-Z2w+ImHN*rCcX337mQ0C(@1E_sW&)p<4dsK=C@pTwL1#L$Y zbQy#by~3JRx$k?P0USWran%msbvH)FUR59~3@}Uou5&9L%=VQp6~S0k8s|Ju{bvHJ zAYFnhz!i;VwxhjS@ORB@H@jJ9#gB}#6)RQrDsl1pgNq%SR@I#aTmh)s%=XWAVASdA z85PoQ>5c;DA3k~CpRu^Zfvh_+uui)8T+$;w9R_4usJk-e5Z6=1GemVh7Q?qY00OKk z$Ne)GzDzn}DPuDAp67UG1NXUmQPjEWa@)O6tqA*Te+r0zRg|xax)AQr8NHAE&2H^o z;YRt1TR++vc`p4FuoGYwWK4YLexNRuEEW2n^;6w_D!R~5#|kPBU7V;J{cZgofDO-d zDAFH)Mlfc(J}cUY0ixMmo!csrRf*W3+MLc`RZQdgr;~gIYDS*ABV_oqyTPuD@W8d} zY^O(WDzJ^isrN6S1~(1e1B1TXD(`rwj&%)58HQnJ^x>;UjU;^QWt1P zTrg(>^Q4|x{neS>Y0?PGG6S%}mCH{gGt@h4rz-feshMwq&zHVax zmg>HaxG~hVRVn6KiLi8767M%N7r%^2@ckAB_zB^z%SO?xV5hpLI~T2W?@TMnXFaSB z<2UtFz#FAAuDbeEGkl{_=-5mTzONL`4E=X}|1}Jdi3_sZTaj*asNzuj`OcJ$x7p^b z_?a>Qf_byJ8RukY5uncQelCQpcq)w4k&pp62yd0A$JNf{yJk|ed-VcXDGJK~?0h#^ zKPgdgqYK@PU)hCgCP^#fq{7^651B?XfO7inGMki+xUq;fcXTf6MEm^g6qmyP4+BI& zeRDBH3=jpO%T}5HyOPg1Gdwe6={Ur{5W?b=B8|L3x(sj}^S?J>-)CvMo3ycxPsZi+ zD166p{QVa~A|}A~$S4|rT9|&bnchi4S5|4i+qqTzB|CQ`TUn9dJ_Tk*Tw*%N z^@-$Xez^Uu0zipq%I%+#nHjx1cr%#alzfNpI12xD>~YN9KJht>&U~ExF0RURm(1AR zF-A~OFFrXhS5`SYoIs;9&s{V2twk{FC!cfnF`g_}mL!#A^&GOZqBHxyp$JqQchNtd di{c;U?LQ598PTka3!DG|002ovPDHLkV1m)|6oLQ% literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_54.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_54.png new file mode 100755 index 0000000000000000000000000000000000000000..10be0ad16a87bae7c178b3715a76c1e5c210e267 GIT binary patch literal 2146 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 Y1HPr&iQ>d1uK)l507*qoM6N<$g2Z$WjQ{`u literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_55.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_55.png new file mode 100755 index 0000000000000000000000000000000000000000..fd8269bb4d5af2a4b30bbbc4213b4cbe43fc41e4 GIT binary patch literal 1952 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 Qp8x;=07*qoM6N<$f_H(Or~m)} literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_6.png b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/frame_6.png new file mode 100755 index 0000000000000000000000000000000000000000..56f37fbd8943ee285f8451c64269c2045acf4cb7 GIT binary patch literal 1592 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 literal 0 HcmV?d00001 diff --git a/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt new file mode 100755 index 000000000..4eeb91306 --- /dev/null +++ b/assets/dolphin/external/L3_Freedom_2_dolphins_128x64/meta.txt @@ -0,0 +1,23 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 20 +Active frames: 38 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 12 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 1 + +Slot: 0 +X: 52 +Y: 46 +Text: Thanks, man! +AlignH: Left +AlignV: Top +StartFrame: 37 +EndFrame: 40 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index d7348b25c..c0e1741a0 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -104,7 +104,7 @@ Min butthurt: 0 Max butthurt: 8 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Wake_up_128x64 Min butthurt: 0 @@ -139,7 +139,7 @@ Min butthurt: 0 Max butthurt: 8 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 Name: L3_Furippa3_128x64 Min butthurt: 0 @@ -167,18 +167,25 @@ Min butthurt: 8 Max butthurt: 13 Min level: 1 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Coding_in_the_shell_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 Name: L2_Secret_door_128x64 Min butthurt: 0 Max butthurt: 12 Min level: 2 Max level: 3 -Weight: 4 +Weight: 3 + +Name: L3_Freedom_2_dolphins_128x64 +Min butthurt: 0 +Max butthurt: 12 +Min level: 3 +Max level: 3 +Weight: 5 From 8bd984ff81d2c229cf6dd55afd2c2462e1ec8a02 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:44:31 +0200 Subject: [PATCH 02/12] [FL-3151] Update the WiFi devboard developer documentation (#3520) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../Firmware update on Developer Board.md | 204 ++++-------------- .../Get started with the Dev Board.md | 47 ++-- .../Reading logs via the Dev Board.md | 152 ++++++------- 3 files changed, 145 insertions(+), 258 deletions(-) diff --git a/documentation/devboard/Firmware update on Developer Board.md b/documentation/devboard/Firmware update on Developer Board.md index c62c65344..f6a81d97b 100644 --- a/documentation/devboard/Firmware update on Developer Board.md +++ b/documentation/devboard/Firmware update on Developer Board.md @@ -1,77 +1,30 @@ # Firmware update on Developer Board {#dev_board_fw_update} -It's important to regularly update your Developer Board to keep it up to date. This tutorial will guide you through the necessary steps to successfully update the firmware of your Developer Board. +It's important to regularly update your Developer Board to ensure that you have access to the latest features and bug fixes. This tutorial will guide you through the necessary steps to update the firmware of your Developer Board. -This tutorial assumes that you're familiar with the basics of the command line. If you’re unfamiliar with the command line, please refer to the [Windows](https://www.digitalcitizen.life/command-prompt-how-use-basic-commands/) or [MacOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials. +This tutorial assumes that you're familiar with the basics of the command line. If you’re not, please refer to the [Windows](https://www.digitalcitizen.life/command-prompt-how-use-basic-commands/) or [MacOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials. *** -## Downloading the latest firmware +## Installing the micro Flipper Build Tool -The first thing you need to do is to download the latest Developer Board firmware. +Micro Flipper Build Tool (uFBT) is a cross-platform tool that enables basic development tasks for Flipper Zero, such as building and debugging applications, flashing firmware, and creating VS Code development configurations. -To get the latest pre-built firmware, do the following: +Install uFBT on your computer by running the following command in the Terminal: -1. Go to the [Update Server page](https://update.flipperzero.one/builds/blackmagic-firmware). -![The Update Server page hosts different versions of the Developer Board firmware](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/gIXVO9VrE4LK05CmcMSSD_monosnap-miro-2023-07-19-17-36-23.jpg) - - There, you can find the following version of the Developer Board firmware: - - * **Release:** The most stable version of the firmware, which went through rigorous testing. The Release firmware version has the following format: **X.Y.Z/**, where X, Y, and Z are the build numbers. We recommend installing this version of the firmware. - - * **Release-candidate:** The firmware version that hasn't been tested yet and may contain bugs. The Release-candidate firmware version has the following format: **X.Y.Z-rc/**, where X, Y, and Z are the build numbers. - - * **Development:** The firmware version which builds every day and contains the latest features but might be unstable. - -2. Open the folder with the latest Release firmware and download the `blackmagic-firmware-s2-full-X.Y.Z.tgz` file. - -*** - -## Extracting the firmware - -After downloading the firmware archive, extract it into a folder: - -* On Windows, you can use any archive manager for this, for example, [7-Zip](https://www.7-zip.org/). - -* On MacOS and Linux, you can use the `tar` command: - - ```text - tar -xzf blackmagic-firmware-s2-full-X.Y.Z.tgz -C - ``` - -Don't forget to replace `X.Y.Z` with the actual version number and set the destination directory! - -*** - -## Installing the prerequisites for flashing - -Install the tools below if you haven't already. - -### Python - -Download and install [Python3](https://www.python.org/downloads/). Make sure to check the “Add Python to PATH” option during installation. - -### pip - -To install the pip package manager, run the following command in the Terminal: +**For Linux & macOS:** ```text -python3 -m ensurepip --upgrade +python3 -m pip install --upgrade ufbt ``` -If this command fails, please refer to the [official pip documentation](https://pip.pypa.io/en/stable/installation/) for alternative installation methods. - -### esptool - -esptool is a command-line utility for flashing ESP8266 and ESP32 microcontrollers, including the ESP32-S2 in your Developer Board. - -To install esptool, run the following command in the Terminal: +**For Windows:** ```text -pip3 install esptool +py -m pip install --upgrade ufbt ``` -If this command fails, try using **pip** instead of **pip3**. If this didn’t help, please refer to the [official esptool installation manual](https://docs.espressif.com/projects/esptool/en/latest/esp32/installation.html). +If you want to learn more about uFBT, visit [the project's page](https://pypi.org/project/ufbt/). *** @@ -79,38 +32,38 @@ If this command fails, try using **pip** instead of **pip3**. If this didn’t h 1. List all of the serial devices on your computer. - * ***Windows*** + **Windows** - On Windows, go to Device Manager and expand the Ports (COM & LPT) section. + On Windows, go to Device Manager and expand the Ports (COM & LPT) section. - * ***macOS*** + **macOS** - On macOS, you can run the following command in the Terminal: + On macOS, you can run the following command in the Terminal: - ```text - ls /dev/cu.* - ``` + ```text + ls /dev/cu.* + ``` - * ***Linux*** + **Linux** - On Linux, you can run the following command in the Terminal: + On Linux, you can run the following command in the Terminal: - ```text - ls /dev/tty* - ``` + ```text + ls /dev/tty* + ``` View the devices in the list. -2. Connect the Developer Board to your computer using a USB-C cable.\ +2. Connect the Developer Board to your computer using a USB-C cable. ![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/Aq7gfMI-m_5H6sGGjwb4I_monosnap-miro-2023-07-19-19-47-39.jpg) 3. Switch your Developer Board to Bootloader mode: - 3.1. Press and hold the **BOOT** button. - - 3.2. Press the **RESET** button while holding the **BOOT** button. - - 3.3. Release the **BOOT** button. + 3.1. Press and hold the **BOOT** button. + + 3.2. Press the **RESET** button while holding the **BOOT** button. + + 3.3. Release the **BOOT** button.\ ![You can easily switch the Dev Board to Bootloader mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KynP9iT6sJ3mXLaLyI82__image.png) 4. Repeat Step 1 and view the name of your Developer Board that appeared in the list. @@ -125,103 +78,23 @@ If this command fails, try using **pip** instead of **pip3**. If this didn’t h ## Flashing the firmware -### Getting the flash command +To flash the firmware onto your Developer Board, run the following command in the terminal: -1. Run the Terminal and navigate to the folder with the extracted firmware. +```text +python3 -m ufbt devboard_flash +``` -2. Run the following command to read the file with the flash command: +You should see the following message: `WiFi board flashed successfully`. - ```text - cat flash.command - ``` +## If flashing failed - If you see a similar output, you can proceed to the Flashing step: - - ```text - esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32s2 write_flash --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 bootloader.bin 0x10000 blackmagic.bin 0x8000 partition-table.bin - ``` - - Don't use the exact command above for your Developer Board in the next step since it's just an example and may not match your firmware version! - - If you get an error, ensure you’re in the correct directory and extracted the firmware archive correctly. - -*** - -### Flashing - -1. Copy the command you got from the previous step and replace the `(PORT)` part with the name of the serial device you learned earlier. - - For Windows, replace `(PORT)` with the COM port number—for example, `COM3`. - -2. Run the command in the Terminal. - - Your command should look similar to this: - - ```text - esptool.py -p /dev/cu.usbmodem01 -b 460800 --before default_reset --after hard_reset --chip esp32s2 write_flash --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 bootloader.bin 0x10000 blackmagic.bin 0x8000 partition-table.bin - ``` - - If you get an error, ensure that you’ve entered the correct serial device name and that the Developer Board is in Bootloader mode. - -3. Wait till the firmware flashing is over. The flashing process takes about 30 seconds. - - The Terminal output should look similar to this: - - ```text - esptool.py v4.6.1 - Serial port /dev/cu.usbmodem01 - Connecting... - Chip is ESP32-S2 (revision v0.0) - Features: WiFi, No Embedded Flash, No Embedded PSRAM, ADC and temperature sensor - calibration in BLK2 of efuse V2 - Crystal is 40MHz - MAC: 00:11:22:33:44:55 - Uploading stub... - Running stub... - Stub running... - Changing baud rate to 460800 - Changed. - Configuring flash size... - Flash will be erased from 0x00001000 to 0x00004fff... - Flash will be erased from 0x00010000 to 0x000ecfff... - Flash will be erased from 0x00008000 to 0x00008fff... - Compressed 13248 bytes to 9298... - Wrote 13248 bytes (9298 compressed) at 0x00001000 in 0.3 seconds (effective 402.7 kbit/s)... - Hash of data verified. - Compressed 904288 bytes to 562550... - Wrote 904288 bytes (562550 compressed) at 0x00010000 in 6.7 seconds (effective 1076.5 kbit/s)... - Hash of data verified. - Compressed 3072 bytes to 124... - Wrote 3072 bytes (124 compressed) at 0x00008000 in 0.1 seconds (effective 360.8 kbit/s)... - Hash of data verified. - Leaving... - Hard resetting via RTS pin... - ``` - - If the Terminal output has these two lines at the end, your Developer Board has been successfully updated: - - ```text - Leaving... - Hard resetting via RTS pin... - ``` - - If you get this warning, you can safely ignore it: - - ```text - WARNING: ESP32-S2 (revision v0.0) chip was placed into download mode using GPIO0. - esptool.py can not exit the download mode over USB. To run the app, reset the chip manually. - To suppress this note, set --after option to 'no_reset - ``` - -#### If flashing failed - -If you get an error message during the flashing process, such as: +If you get an error message during the flashing process, such as this: ```text A fatal error occurred: Serial data stream stopped: Possible serial noise or corruption. ``` -or +Or this: ```text FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01' @@ -239,8 +112,11 @@ Try doing the following: ## Finishing the installation -After flashing the firmware, you can reboot the Developer Board by pressing the **RESET** button. +After flashing the firmware: +1. Reboot the Developer Board by pressing the **RESET** button. ![Reset the Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/rcQeKARgrVwa51tLoo-qY_monosnap-miro-2023-07-20-18-29-33.jpg) +2. Disconnect and reconnect the USB-C cable. + The Developer Board should appear as a serial device on your computer. Now, you can use it with the Black Magic Debug client of your choice. diff --git a/documentation/devboard/Get started with the Dev Board.md b/documentation/devboard/Get started with the Dev Board.md index eb230663e..04fa9d358 100644 --- a/documentation/devboard/Get started with the Dev Board.md +++ b/documentation/devboard/Get started with the Dev Board.md @@ -2,7 +2,7 @@ The Wi-Fi Developer Board serves as a tool to debug the Flipper Zero firmware. To debug the firmware, the initial step involves compiling the firmware from its source code. This process enables the debugging functionality within the firmware and generates all the necessary files required for debugging purposes. -> **NOTE:** Building and debugging the Flipper Zero firmware is fully supported on MacOS and Linux. Support for Windows is in beta test. +> **NOTE:** Building and debugging the Flipper Zero firmware is fully supported on MacOS and Linux. Support for Windows is in beta test. *** @@ -14,11 +14,11 @@ Update the firmware of your Developer Board before using it. For more informatio ## Installing Git -You’ll need Git installed on your computer to clone the firmware repository. If you don’t have Git, install it by doing the following: +You'll need Git installed on your computer to clone the firmware repository. If you don't have Git, install it by doing the following: * **MacOS** - On MacOS, install the **Xcode Command Line Tools** package, which includes Git as one of the pre-installed command-line utilities, by running in the Terminal the following command: + On MacOS, install the **Xcode Command Line Tools** package, which includes Git as one of the pre-installed command-line utilities, by running in the Terminal the following command: ```text xcode-select --install @@ -26,11 +26,11 @@ You’ll need Git installed on your computer to clone the firmware repository. I * **Linux** - On Linux, you can install Git using your package manager. For example, on Ubuntu, run in the Terminal the following command: + On Linux, you can install Git using your package manager. For example, on Ubuntu, run in the Terminal the following command: - ```text - sudo apt install git - ``` + ```text + sudo apt install git + ``` For other distributions, refer to your package manager documentation. @@ -57,7 +57,9 @@ Then, run the **Flipper Build Tool** (FBT) to build the firmware: The Developer Board can work in the **Wired** mode and two **Wireless** modes: **Wi-Fi access point (AP)** mode and **Wi-Fi client (STA)** mode. The Wired mode is the simplest to set up, but requires a USB Type-C cable. The Wireless modes are more complex to set up, but they allow you to debug your Flipper Zero wirelessly. -> **NOTE:** Use the following credentials when connecting to the Developer Board in **Wi-Fi access point** mode: Name: **blackmagic**, Password: **iamwitcher** + > **NOTE:** Use the following credentials when connecting to the Developer Board in **Wi-Fi access point** mode:\n + Name: **blackmagic**\n + Password: **iamwitcher** ## Wired @@ -69,13 +71,13 @@ To connect the Developer Board in **Wired** mode, do the following: 2. On your computer, open the **Terminal** and run the following: - * **MacOS** + * **MacOS** ```text ls /dev/cu.* ``` - * **Linux** + * **Linux** ```text ls /dev/tty* @@ -87,9 +89,9 @@ To connect the Developer Board in **Wired** mode, do the following: 4. Rerun the command. Two new devices have to appear: this is the Developer Board. -> **NOTE:** If the Developer Board doesn’t appear in the list of devices, try using a different cable, USB port, or computer. -> -> **NOTE:** Flipper Zero logs can only be viewed when the Developer Board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. For more information, visit [Reading logs via the Dev Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/reading-logs). + > **NOTE:** If the Developer Board doesn't appear in the list of devices, try using a different cable, USB port, or computer. + > + > **NOTE:** Flipper Zero logs can only be viewed when the Developer Board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. For more information, visit [Reading logs via the Dev Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/reading-logs). ## Wireless @@ -97,7 +99,7 @@ To connect the Developer Board in **Wired** mode, do the following: ![The Developer Board in Wi-Fi access point mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/tKRTMHAuruiLSEce2a8Ve_monosnap-miro-2023-06-22-16-39-17.jpg) -Out of the box, the Developer Board is configured to work as a **Wi-Fi access point**. This means it will create its own Wi-Fi network to which you can connect. If your Developer Board doesn’t create a Wi-Fi network, it is probably configured to work in **Wi-Fi client** mode. To reset your Developer Board back to **Wi-Fi access point** mode, press and hold the **BOOT** button for 10 seconds, then wait for the module to reboot. +Out of the box, the Developer Board is configured to work as a **Wi-Fi access point**. This means it'll create its own Wi-Fi network to which you can connect. If your Developer Board doesn't create a Wi-Fi network, it is probably configured to work in **Wi-Fi client** mode. To reset your Developer Board back to **Wi-Fi access point** mode, press and hold the **BOOT** button for 10 seconds, then wait for the module to reboot. ![You can reconfigure the Developer Board mode by pressing and holding the BOOT button](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/57eELJsAwMxeZCEA1NMJw_monosnap-miro-2023-06-22-20-33-27.jpg) @@ -110,11 +112,12 @@ To connect the Developer Board in **Wi-Fi access point** mode, do the following: 3. Connect to the network: * Name: **blackmagic** + * Password: **iamwitcher** 4. To configure the Developer Board, open a browser and go to `http://192.168.4.1`. -#### Wi-Fi client (STA) mode +### Wi-Fi client (STA) mode ![The Developer Board in Wi-Fi client mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/xLQpFyYPfUS5Cx0uQhrNd_monosnap-miro-2023-06-23-12-34-36.jpg) @@ -126,15 +129,15 @@ To connect the Developer Board in **Wi-Fi client** mode, you need to configure i 3. In a browser, go to the configuration page on `http://192.168.4.1`. -4. Select the **STA** mode and enter your network’s **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby networks. +4. Select the **STA** mode and enter your network's **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby networks. 5. Save the configuration and reboot the Developer Board. ![In the Wi-Fi tab, you can set the Developer Board mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/klbLVj8lz2bEvm7j4wRaj_monosnap-miro-2023-06-23-13-06-32.jpg) -After rebooting, the Developer Board connects to your Wi-Fi network. You can connect to the device using the mDNS name [blackmagic.local](http://blackmagic.local) or the IP address it got from your router (you’ll have to figure this out yourself, every router is different). +After rebooting, the Developer Board connects to your Wi-Fi network. You can connect to the device using the mDNS name **blackmagic.local** or the IP address it got from your router (you'll have to figure this out yourself, every router is different). -After connecting to your debugger via [blackmagic.local](http://blackmagic.local), you can find its IP address in the **SYS** tab. You can also change the debugger’s mode to **AP** or **STA** there. +After connecting to your debugger via , you can find its IP address in the **SYS** tab. You can also change the debugger's mode to **AP** or **STA** there. ![In the SYS tab, you can view the IP address of your Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/5XbUptlfqzlV0p6hRUqiG_monosnap-miro-2023-06-22-18-11-30.jpg) @@ -145,10 +148,10 @@ After connecting to your debugger via [blackmagic.local](http://blackmagic.local Open the **Terminal** in the **flipperzero-firmware** directory that you cloned earlier and run the following command: ```text -./fbt flash_blackmagic +./fbt flash ``` -This will upload the firmware you’ve just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware using the [GDB](https://www.gnu.org/software/gdb/) debugger. We recommend using **VSCode** with the recommended extensions, and we have pre-made configurations for it. +This will upload the firmware you've just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware using the [GDB](https://www.gnu.org/software/gdb/) debugger. We recommend using **VSCode** with the recommended extensions, and we have pre-made configurations for it. To debug in **VSCode**, do the following: @@ -162,9 +165,9 @@ To debug in **VSCode**, do the following: 4. In VSCode, open the **Run and Debug** tab and select **Attach FW (blackmagic)** from the dropdown menu. -5. If needed, flash your Flipper Zero with the `./fbt flash_blackmagic` command, then click the **Play** button in the debug sidebar to start the debugging session. +5. If needed, flash your Flipper Zero with the `./fbt flash` command, then click the **Play** button in the debug sidebar to start the debugging session. -6. Note that starting a debug session halts the execution of the firmware, so you’ll need to click the **Continue** button on the toolbar at the top of your VSCode window to continue execution. +6. Note that starting a debug session halts the execution of the firmware, so you'll need to click the **Continue** button on the toolbar at the top of your VSCode window to continue execution. ![Click Continue in the toolbar to continue execution of the firmware](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/lp8ygGaZ3DvWD3OSI9yGO_monosnap-miro-2023-06-23-17-58-09.jpg) diff --git a/documentation/devboard/Reading logs via the Dev Board.md b/documentation/devboard/Reading logs via the Dev Board.md index 112e59a19..e9fc0e2ca 100644 --- a/documentation/devboard/Reading logs via the Dev Board.md +++ b/documentation/devboard/Reading logs via the Dev Board.md @@ -4,9 +4,11 @@ The Developer Board allows you to read Flipper Zero logs via UART. Unlike readin > **NOTE:** Flipper Zero logs can only be viewed when the developer board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. +*** + ## Setting the log level -Depending on your needs, you can set the log level by going to Main Menu -> Settings -> Log Level. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). +Depending on your needs, you can set the log level by going to **Main Menu -> Settings -> Log Level**. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). ![You can manually set the preferred log level](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/INzQMw8QUsG9PXi30WFS0_monosnap-miro-2023-07-11-13-29-47.jpg) @@ -20,17 +22,17 @@ Depending on your operating system, you need to install an additional applicatio On MacOS, you need to install the **minicom** communication program by doing the following: -1. [Install Homebrew](https://brew.sh/) by running in the Terminal the following command: +1. [Install Homebrew](https://brew.sh/) by running the following command in the Terminal: - ```text - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - ``` + ```text + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` 2. After installation of Homebrew, run the following command to install minicom: - ```text - brew install minicom - ``` + ```text + brew install minicom + ``` After installation of minicom on your macOS computer, you can connect to the Developer Board to read Flipper Zero logs by doing the following: @@ -38,21 +40,77 @@ After installation of minicom on your macOS computer, you can connect to the Dev 2. On your computer, open the Terminal and run the following command: - ```text - ls /dev/cu.* - ``` + ```text + ls /dev/cu.* + ``` - Note the list of devices. + Note the list of devices. -3. Connect the developer board to your computer using a USB Type-C cable.\ -![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +3. Connect the developer board to your computer using a USB Type-C cable. +![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) 4. Rerun the command. Two new devices have to appear: this is the Developer Board. - ```text - /dev/cu.usbmodemblackmagic1 - /dev/cu.usbmodemblackmagic3 - ``` + ```text + /dev/cu.usbmodemblackmagic1 + ``` + + ```text + /dev/cu.usbmodemblackmagic3 + ``` + + Your Developer Board might have different names. + +5. Run the following command: + + ```text + minicom -D /dev/ -b 230400 + ``` + + Where `` is the name of your device with a bigger number. + + Example: + + ```text + minicom -D /dev/cu.usbmodemblackmagic3 -b 230400 + ``` + +6. View logs of your Flipper Zero in the Terminal. + +7. To quit, close the minicom window or quit via the minicom menu. + +### Linux + +On Linux, you need to install the **minicom** communication program. For example, on Ubuntu, run in the Terminal the following command: + +```text + sudo apt install minicom + ``` + +After installation of minicom on your Linux computer, you can connect to the Developer Board to read Flipper Zero logs by doing the following: + +1. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. + +2. On your computer, open the Terminal and run the following command: + + ```text + ls /dev/tty* + ``` + + Note the list of devices. + +3. Connect the developer board to your computer using a USB Type-C cable. +![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) + +4. Rerun the command. Two new devices have to appear: this is the Developer Board. + + ```text + /dev/ttyACM0 + ``` + + ```text + /dev/ttyACM1 + ``` Your Developer Board might have different names. @@ -72,57 +130,7 @@ After installation of minicom on your macOS computer, you can connect to the Dev 6. View logs of your Flipper Zero in the Terminal. -7. To quit, close the minicom window or quit via the minicom menu. - -### Linux - -On Linux, you need to install the **minicom** communication program. For example, on Ubuntu, run in the Terminal the following command: - -```text -sudo apt install minicom -``` - -After installation of minicom on your Linux computer, you can connect to the Developer Board to read Flipper Zero logs by doing the following: - -1. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. - -2. On your computer, open the Terminal and run the following command: - - ```text - ls /dev/tty* - ``` - - Note the list of devices. - -3. Connect the developer board to your computer using a USB Type-C cable. -![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) - -4. Rerun the command. Two new devices have to appear: this is the Developer Board. - - ```text - /dev/ttyACM0 - /dev/ttyACM1 - ``` - - Your Developer Board might have different names. - -5. Run the following command: - - ```text - minicom -D /dev/ \-b 230400 - ``` - - Where `` is the name of your device with a bigger number. - - Example: - - ```text - minicom -D /dev/ttyACM1 \-b 230400 - ``` - -6. View logs of your Flipper Zero in the Terminal. - - > **NOTE:** If no logs are shown in the Terminal, try running the command from Step 5 with another device name. + **NOTE:** If no logs are shown in the Terminal, try running the command from Step 5 with another device name. 7. To quit, close the minicom window or quit via the minicom menu. @@ -135,17 +143,17 @@ On Windows, do the following: 2. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. 3. Connect the developer board to your computer using a USB Type-C cable. -![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) +![Connect the developer board with a USB-C cable](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) 4. Find the serial port that the developer board is connected to by going to **Device Manager -> Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. -![Go to Device Manager -> Ports (COM & LPT)](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png) +![Find the serial port in your Device Manager](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png) 5. Run the PuTTY application and select **Serial** as the connection type. 6. Enter the port number you found in the previous step into the **Serial line** field. 7. Set the **Speed** parameter to **230400** and click **Open**. -![Set the required parameters](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/ROBSJyfQ_CXiy4GUZcPbs_monosnap-miro-2023-07-12-13-56-47.jpg) +![Set speed to 230400](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/ROBSJyfQ_CXiy4GUZcPbs_monosnap-miro-2023-07-12-13-56-47.jpg) 8. View logs of your Flipper Zero in the PuTTY terminal window. From 169522cbe873fdcb4159d96ff31d828b44bf8e55 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:37:16 +0300 Subject: [PATCH 03/12] [FL-3792] Fix iButton emulation regression (#3519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- lib/one_wire/one_wire_slave.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/one_wire/one_wire_slave.c b/lib/one_wire/one_wire_slave.c index f426582cf..8dfbe4aa5 100644 --- a/lib/one_wire/one_wire_slave.c +++ b/lib/one_wire/one_wire_slave.c @@ -158,6 +158,8 @@ static inline bool onewire_slave_receive_and_process_command(OneWireSlave* bus) static inline bool onewire_slave_bus_start(OneWireSlave* bus) { FURI_CRITICAL_ENTER(); + + furi_hal_gpio_disable_int_callback(bus->gpio_pin); furi_hal_gpio_init(bus->gpio_pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow); while(onewire_slave_receive_and_process_command(bus)) @@ -166,6 +168,8 @@ static inline bool onewire_slave_bus_start(OneWireSlave* bus) { const bool result = (bus->error == OneWireSlaveErrorNone); furi_hal_gpio_init(bus->gpio_pin, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_enable_int_callback(bus->gpio_pin); + FURI_CRITICAL_EXIT(); return result; From 1c033e2afea4d69ad4492d460a2e1ad532d1ca2f Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:44:48 +0200 Subject: [PATCH 04/12] NFC wording fixes (#3512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC wording fixes * Fix filename Co-authored-by: hedger Co-authored-by: あく --- .../mf_ultralight/mf_ultralight.c | 2 +- .../mf_ultralight/mf_ultralight_render.c | 6 +- .../main/nfc/scenes/nfc_scene_config.h | 3 +- .../main/nfc/scenes/nfc_scene_detect.c | 2 +- ...nfc_scene_mf_classic_keys_warn_duplicate.c | 2 +- .../nfc_scene_mf_classic_mfkey_complete.c | 2 +- .../nfc_scene_mf_classic_update_initial.c | 8 ++- ...ene_mf_classic_update_initial_wrong_card.c | 58 +++++++++++++++++++ .../nfc_scene_mf_classic_write_initial.c | 5 +- ...ene_mf_classic_write_initial_wrong_card.c} | 14 ++--- .../nfc_scene_mf_ultralight_unlock_warn.c | 10 ++-- .../main/nfc/scenes/nfc_scene_slix_unlock.c | 2 +- 12 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c rename applications/main/nfc/scenes/{nfc_scene_mf_classic_wrong_card.c => nfc_scene_mf_classic_write_initial_wrong_card.c} (69%) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index eb6911df7..c2aaac5be 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -169,7 +169,7 @@ static void nfc_scene_read_setup_view(NfcApp* instance) { popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); } else { popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 12, 20, &A_Loading_24); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c index 1bc508adc..c4ad67ff8 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -11,7 +11,11 @@ static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, F void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) { bool all_pages = mf_ultralight_is_all_data_read(data); - furi_string_cat_printf(str, "\e#%s pages unlocked!", all_pages ? "All" : "Not all"); + if(all_pages) { + furi_string_cat_printf(str, "\e#All Pages Are Unlocked!"); + } else { + furi_string_cat_printf(str, "\e#Some Pages Are Locked!"); + } MfUltralightConfigPages* config; mf_ultralight_get_config_page(data, &config); diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 035c4949c..8a5e29ff3 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -43,10 +43,11 @@ ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo) ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete) ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial) ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess) +ADD_SCENE(nfc, mf_classic_update_initial_wrong_card, MfClassicUpdateInitialWrongCard) ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial) ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess) ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail) -ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) +ADD_SCENE(nfc, mf_classic_write_initial_wrong_card, MfClassicWriteInitialWrongCard) ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys) ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) diff --git a/applications/main/nfc/scenes/nfc_scene_detect.c b/applications/main/nfc/scenes/nfc_scene_detect.c index 34c552aba..3ef153657 100644 --- a/applications/main/nfc/scenes/nfc_scene_detect.c +++ b/applications/main/nfc/scenes/nfc_scene_detect.c @@ -19,7 +19,7 @@ void nfc_scene_detect_on_enter(void* context) { popup_reset(instance->popup); popup_set_header(instance->popup, "Reading", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c index c3fb92bee..675463ec9 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys_warn_duplicate.c @@ -12,7 +12,7 @@ void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) { // Setup view Popup* popup = instance->popup; popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); - popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop); + popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop); popup_set_text( popup, "Please enter a\n" diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c index eb0aa7c3a..d5033789a 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_mfkey_complete.c @@ -14,7 +14,7 @@ void nfc_scene_mf_classic_mfkey_complete_on_enter(void* context) { NfcApp* instance = context; widget_add_string_element( - instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Complete!"); + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Completed!"); widget_add_string_multiline_element( instance->widget, 64, diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c index 961afdf53..7c76260b4 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial.c @@ -61,7 +61,7 @@ static void nfc_scene_mf_classic_update_initial_setup_view(NfcApp* instance) { if(state == NfcSceneMfClassicUpdateInitialStateCardSearch) { popup_set_text( - instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); + instance->popup, "Use the source\ncard only", 128, 32, AlignRight, AlignCenter); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); } else { popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter); @@ -111,14 +111,16 @@ bool nfc_scene_mf_classic_update_initial_on_event(void* context, SceneManagerEve nfc_scene_mf_classic_update_initial_setup_view(instance); consumed = true; } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicUpdateInitialWrongCard); consumed = true; } else if(event.event == NfcCustomEventWorkerExit) { if(nfc_save_shadow_file(instance)) { scene_manager_next_scene( instance->scene_manager, NfcSceneMfClassicUpdateInitialSuccess); } else { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicUpdateInitialWrongCard); consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c new file mode 100644 index 000000000..c2c36c74f --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_update_initial_wrong_card.c @@ -0,0 +1,58 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_classic_update_initial_wrong_card_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_classic_update_initial_wrong_card_on_enter(void* context) { + NfcApp* instance = context; + Widget* widget = instance->widget; + + notification_message(instance->notifications, &sequence_error); + + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); + widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wrong Card!"); + widget_add_string_multiline_element( + widget, + 4, + 17, + AlignLeft, + AlignTop, + FontSecondary, + "Data management\nis only possible\nwith source card"); + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_mf_classic_update_initial_wrong_card_widget_callback, + instance); + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_classic_update_initial_wrong_card_on_event( + void* context, + SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(instance->scene_manager); + } + } + return consumed; +} + +void nfc_scene_mf_classic_update_initial_wrong_card_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c index da576a276..12e7ba1ec 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial.c @@ -67,7 +67,7 @@ static void nfc_scene_mf_classic_write_initial_setup_view(NfcApp* instance) { if(state == NfcSceneMfClassicWriteInitialStateCardSearch) { popup_set_header(instance->popup, "Writing", 95, 20, AlignCenter, AlignCenter); popup_set_text( - instance->popup, "Apply the initial\ncard only", 95, 38, AlignCenter, AlignCenter); + instance->popup, "Use the source\ncard only", 95, 38, AlignCenter, AlignCenter); popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); } else { popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); @@ -115,7 +115,8 @@ bool nfc_scene_mf_classic_write_initial_on_event(void* context, SceneManagerEven nfc_scene_mf_classic_write_initial_setup_view(instance); consumed = true; } else if(event.event == NfcCustomEventWrongCard) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWrongCard); + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfClassicWriteInitialWrongCard); consumed = true; } else if(event.event == NfcCustomEventPollerSuccess) { scene_manager_next_scene( diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c similarity index 69% rename from applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c rename to applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c index a879985bc..3f92ebfd3 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_wrong_card.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_write_initial_wrong_card.c @@ -1,6 +1,6 @@ #include "../nfc_app_i.h" -void nfc_scene_mf_classic_wrong_card_widget_callback( +void nfc_scene_mf_classic_write_initial_wrong_card_widget_callback( GuiButtonType result, InputType type, void* context) { @@ -10,7 +10,7 @@ void nfc_scene_mf_classic_wrong_card_widget_callback( } } -void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { +void nfc_scene_mf_classic_write_initial_wrong_card_on_enter(void* context) { NfcApp* instance = context; Widget* widget = instance->widget; @@ -18,7 +18,7 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); + widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Use The Source Card!"); widget_add_string_multiline_element( widget, 4, @@ -26,19 +26,19 @@ void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { AlignLeft, AlignTop, FontSecondary, - "Data management\nis only possible\nwith initial card"); + "Go to NFC Magic\napp if you want to\nwrite blanks"); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", - nfc_scene_mf_classic_wrong_card_widget_callback, + nfc_scene_mf_classic_write_initial_wrong_card_widget_callback, instance); // Setup and start worker view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); } -bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) { +bool nfc_scene_mf_classic_write_initial_wrong_card_on_event(void* context, SceneManagerEvent event) { NfcApp* instance = context; bool consumed = false; @@ -50,7 +50,7 @@ bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent e return consumed; } -void nfc_scene_mf_classic_wrong_card_on_exit(void* context) { +void nfc_scene_mf_classic_write_initial_wrong_card_on_exit(void* context) { NfcApp* instance = context; widget_reset(instance->widget); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c index db5fd945f..4df8a6289 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c @@ -22,14 +22,14 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { for(size_t i = 0; i < sizeof(nfc->mf_ul_auth->password.data); i++) { furi_string_cat_printf(password_str, "%02X ", nfc->mf_ul_auth->password.data[i]); } - furi_string_cat_str(password_str, "?\nCaution, a wrong password\ncan block the card!"); + furi_string_cat_str(password_str, "\nWarning: incorrect password\nwill block the card!"); nfc_text_store_set(nfc, furi_string_get_cstr(password_str)); furi_string_free(password_str); - const char* message = (type == MfUltralightAuthTypeReader) ? "Password captured!" : - "Risky function!"; + const char* message = (type == MfUltralightAuthTypeReader) ? "Password Captured!" : + "Risky Action!"; dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); - dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 12, AlignCenter, AlignTop); + dialog_ex_set_text(dialog_ex, nfc->text_store, 64, 10, AlignCenter, AlignTop); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Continue"); @@ -37,7 +37,7 @@ void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) { notification_message(nfc->notifications, &sequence_set_green_255); } } else { - dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop); + dialog_ex_set_header(dialog_ex, "Risky action!", 64, 4, AlignCenter, AlignTop); dialog_ex_set_text( dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop); dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c index b01876e06..ae725ce67 100644 --- a/applications/main/nfc/scenes/nfc_scene_slix_unlock.c +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c @@ -32,7 +32,7 @@ void nfc_scene_slix_unlock_on_enter(void* context) { popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); popup_set_text( - instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolSlix); From 7987917d85281066136d22d65baafc6f8041eb8a Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:54:07 +0300 Subject: [PATCH 05/12] [FL-3496, FL-3523, FL-3767, FL-3790] Infrared fixes and more (#3515) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FL-3496: do not hardcode universal library names in CLI * FL-3523: remove TODO, no changes necessary * FL-3767: remove TODO, no changes necessary * FL-3790: fix laggy TextInput by not adding it into a ViewStack * Improve documentation * Fix logical error in documentation Co-authored-by: あく --- applications/main/infrared/infrared_app.c | 12 +++-- applications/main/infrared/infrared_app_i.h | 12 +++-- applications/main/infrared/infrared_cli.c | 50 ++++++++++++++++--- .../common/infrared_scene_universal_common.c | 3 +- .../scenes/infrared_scene_edit_delete.c | 7 +-- .../scenes/infrared_scene_edit_move.c | 8 ++- .../scenes/infrared_scene_edit_rename.c | 15 ++---- .../scenes/infrared_scene_remote_list.c | 3 -- .../main/infrared/scenes/infrared_scene_rpc.c | 7 +-- applications/services/gui/view_dispatcher.h | 4 +- applications/services/gui/view_holder.h | 6 +++ applications/services/gui/view_stack.h | 4 +- applications/services/rpc/rpc.c | 2 - 13 files changed, 84 insertions(+), 49 deletions(-) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 7ccbb6cea..82b054478 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -189,6 +189,10 @@ static InfraredApp* infrared_alloc(void) { view_dispatcher_add_view( view_dispatcher, InfraredViewMove, infrared_move_view_get_view(infrared->move_view)); + infrared->loading = loading_alloc(); + view_dispatcher_add_view( + view_dispatcher, InfraredViewLoading, loading_get_view(infrared->loading)); + if(app_state->is_debug_enabled) { infrared->debug_view = infrared_debug_view_alloc(); view_dispatcher_add_view( @@ -198,7 +202,6 @@ static InfraredApp* infrared_alloc(void) { } infrared->button_panel = button_panel_alloc(); - infrared->loading = loading_alloc(); infrared->progress = infrared_progress_view_alloc(); return infrared; @@ -240,13 +243,15 @@ static void infrared_free(InfraredApp* infrared) { view_dispatcher_remove_view(view_dispatcher, InfraredViewMove); infrared_move_view_free(infrared->move_view); + view_dispatcher_remove_view(view_dispatcher, InfraredViewLoading); + loading_free(infrared->loading); + if(app_state->is_debug_enabled) { view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView); infrared_debug_view_free(infrared->debug_view); } button_panel_free(infrared->button_panel); - loading_free(infrared->loading); infrared_progress_view_free(infrared->progress); view_dispatcher_free(view_dispatcher); @@ -385,14 +390,13 @@ void infrared_tx_stop(InfraredApp* infrared) { } void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { - view_stack_add_view(infrared->view_stack, loading_get_view(infrared->loading)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewLoading); furi_thread_set_callback(infrared->task_thread, callback); furi_thread_start(infrared->task_thread); } bool infrared_blocking_task_finalize(InfraredApp* infrared) { furi_thread_join(infrared->task_thread); - view_stack_remove_view(infrared->view_stack, loading_get_view(infrared->loading)); return furi_thread_get_return_code(infrared->task_thread); } diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index bccd58608..1c074323a 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -141,6 +141,7 @@ typedef enum { InfraredViewStack, InfraredViewDebugView, InfraredViewMove, + InfraredViewLoading, } InfraredView; /** @@ -213,8 +214,8 @@ void infrared_tx_stop(InfraredApp* infrared); /** * @brief Start a blocking task in a separate thread. * - * If a ViewStack is currently on screen, a busy "Hourglass" animation - * will be shown and no input will be accepted until completion. + * Before starting a blocking task, the current view will be replaced + * with a busy animation. All subsequent user input will be ignored. * * @param[in,out] infrared pointer to the application instance. * @param[in] callback pointer to the function to be run in the thread. @@ -222,10 +223,11 @@ void infrared_tx_stop(InfraredApp* infrared); void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback); /** - * @brief Wait for a blocking task to finish and receive the result. + * @brief Wait for a blocking task to finish and get the result. * - * Upon the completion of a blocking task, the busy animation will be hidden - * and input will be accepted again. + * The busy animation shown during the infrared_blocking_task_start() call + * will NOT be hidden and WILL remain on screen. If another view is needed + * (e.g. to display the results), the caller code MUST set it explicitly. * * @param[in,out] infrared pointer to the application instance. * @return true if the blocking task finished successfully, false otherwise. diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index b498fc600..123fe78d5 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -10,8 +10,10 @@ #include "infrared_signal.h" #include "infrared_brute_force.h" -#define INFRARED_CLI_BUF_SIZE 10 -#define INFRARED_ASSETS_FOLDER "infrared/assets" +#define INFRARED_CLI_BUF_SIZE (10U) +#define INFRARED_CLI_FILE_NAME_SIZE (256U) +#define INFRARED_FILE_EXTENSION ".ir" +#define INFRARED_ASSETS_FOLDER EXT_PATH("infrared/assets") #define INFRARED_BRUTE_FORCE_DUMMY_INDEX 0 DICT_DEF2(dict_signals, FuriString*, FURI_STRING_OPLIST, int, M_DEFAULT_OPLIST) @@ -66,6 +68,37 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv } } +static void infrared_cli_print_universal_remotes(void) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* dir = storage_file_alloc(storage); + + do { + if(!storage_dir_open(dir, INFRARED_ASSETS_FOLDER)) break; + + FileInfo file_info; + char file_name[INFRARED_CLI_FILE_NAME_SIZE]; + + while(storage_dir_read(dir, &file_info, file_name, sizeof(file_name))) { + if(file_info.flags & FSF_DIRECTORY) { + continue; + } + + char* file_ext = strstr(file_name, INFRARED_FILE_EXTENSION); + if((file_ext == NULL) || (strcmp(file_ext, INFRARED_FILE_EXTENSION) != 0)) { + continue; + } + + *file_ext = '\0'; + printf("%s ", file_name); + } + + printf("\r\n"); + } while(false); + + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); +} + static void infrared_cli_print_usage(void) { printf("Usage:\r\n"); printf("\tir rx [raw]\r\n"); @@ -85,8 +118,9 @@ static void infrared_cli_print_usage(void) { printf("\tir decode []\r\n"); printf("\tir universal \r\n"); printf("\tir universal list \r\n"); - // TODO FL-3496: Do not hardcode universal remote names - printf("\tAvailable universal remotes: tv audio ac projector\r\n"); + printf("\tAvailable universal remotes: "); + + infrared_cli_print_universal_remotes(); } static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) { @@ -211,7 +245,6 @@ static bool infrared_cli_decode_raw_signal( size_t i; for(i = 0; i < raw_signal->timings_size; ++i) { - // TODO FL-3523: Any infrared_check_decoder_ready() magic? const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]); if(message) { @@ -365,7 +398,10 @@ static void infrared_cli_list_remote_signals(FuriString* remote_name) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); FuriString* remote_path = furi_string_alloc_printf( - "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); + "%s/%s%s", + INFRARED_ASSETS_FOLDER, + furi_string_get_cstr(remote_name), + INFRARED_FILE_EXTENSION); do { if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(remote_path))) { @@ -413,7 +449,7 @@ static void infrared_cli_brute_force_signals(Cli* cli, FuriString* remote_name, FuriString* signal_name) { InfraredBruteForce* brute_force = infrared_brute_force_alloc(); FuriString* remote_path = furi_string_alloc_printf( - "%s/%s.ir", EXT_PATH(INFRARED_ASSETS_FOLDER), furi_string_get_cstr(remote_name)); + "%s/%s.ir", INFRARED_ASSETS_FOLDER, furi_string_get_cstr(remote_name)); infrared_brute_force_set_db_filename(brute_force, furi_string_get_cstr(remote_path)); infrared_brute_force_add_record( diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 9fc48bd46..9bdcdc4a8 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -46,7 +46,6 @@ void infrared_scene_universal_common_on_enter(void* context) { InfraredApp* infrared = context; view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); // Load universal remote data in background infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback); @@ -98,6 +97,8 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(!task_success) { scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } else { + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); } } consumed = true; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 8dc4ab6f9..90a2633d3 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -91,10 +91,7 @@ void infrared_scene_edit_delete_on_enter(void* context) { dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_delete_dialog_result_callback); dialog_ex_set_context(dialog_ex, context); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx); } bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) { @@ -136,5 +133,5 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) void infrared_scene_edit_delete_on_exit(void* context) { InfraredApp* infrared = context; - view_stack_remove_view(infrared->view_stack, dialog_ex_get_view(infrared->dialog_ex)); + dialog_ex_reset(infrared->dialog_ex); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 500f3d791..fcac1fc15 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -38,10 +38,7 @@ void infrared_scene_edit_move_on_enter(void* context) { infrared_move_view_set_callback( infrared->move_view, infrared_scene_edit_move_button_callback, infrared); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { @@ -62,6 +59,8 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); scene_manager_search_and_switch_to_previous_scene( infrared->scene_manager, InfraredSceneRemoteList); + } else { + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove); } } consumed = true; @@ -72,6 +71,5 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { void infrared_scene_edit_move_on_exit(void* context) { InfraredApp* infrared = context; - view_stack_remove_view(infrared->view_stack, infrared_move_view_get_view(infrared->move_view)); infrared_move_view_reset(infrared->move_view); } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index 2763c2777..a546ad6e5 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -75,10 +75,7 @@ void infrared_scene_edit_rename_on_enter(void* context) { enter_name_length, false); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationHorizontal); - view_stack_add_view(infrared->view_stack, text_input_get_view(infrared->text_input)); - - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewTextInput); } bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { @@ -117,12 +114,10 @@ void infrared_scene_edit_rename_on_exit(void* context) { InfraredApp* infrared = context; TextInput* text_input = infrared->text_input; - view_stack_remove_view(infrared->view_stack, text_input_get_view(text_input)); - - void* validator_context = text_input_get_validator_callback_context(text_input); - text_input_set_validator(text_input, NULL, NULL); - + ValidatorIsFile* validator_context = text_input_get_validator_callback_context(text_input); if(validator_context) { - validator_is_file_free((ValidatorIsFile*)validator_context); + validator_is_file_free(validator_context); } + + text_input_reset(text_input); } diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 744409a7a..9c880ac2f 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -18,9 +18,6 @@ static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); if(file_selected) { - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - // Load the remote in a separate thread infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback); diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index 03a2bff01..4c263a117 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -30,11 +30,8 @@ void infrared_scene_rpc_on_enter(void* context) { popup_set_context(popup, context); popup_set_callback(popup, infrared_popup_closed_callback); - view_stack_add_view(infrared->view_stack, popup_get_view(infrared->popup)); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); - notification_message(infrared->notifications, &sequence_display_backlight_on); } @@ -69,6 +66,7 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); rpc_system_app_confirm(infrared->rpc_ctx, task_success); @@ -135,6 +133,5 @@ void infrared_scene_rpc_on_exit(void* context) { infrared_tx_stop(infrared); } - view_stack_remove_view(infrared->view_stack, popup_get_view(infrared->popup)); popup_reset(infrared->popup); } diff --git a/applications/services/gui/view_dispatcher.h b/applications/services/gui/view_dispatcher.h index 80cd9cbb0..f8567ea1a 100644 --- a/applications/services/gui/view_dispatcher.h +++ b/applications/services/gui/view_dispatcher.h @@ -1,6 +1,8 @@ /** * @file view_dispatcher.h - * GUI: ViewDispatcher API + * @brief GUI: ViewDispatcher API + * + * @warning Views added to a ViewDispatcher MUST NOT be in a ViewStack at the same time. */ #pragma once diff --git a/applications/services/gui/view_holder.h b/applications/services/gui/view_holder.h index e4646af9c..bc313a926 100644 --- a/applications/services/gui/view_holder.h +++ b/applications/services/gui/view_holder.h @@ -1,3 +1,9 @@ +/** + * @file view_holder.h + * @brief GUI: ViewHolder API + * + * @warning View added to a ViewHolder MUST NOT be in a ViewStack at the same time. + */ #pragma once #include diff --git a/applications/services/gui/view_stack.h b/applications/services/gui/view_stack.h index ed17f682f..7387038b0 100644 --- a/applications/services/gui/view_stack.h +++ b/applications/services/gui/view_stack.h @@ -1,11 +1,13 @@ /** * @file view_stack.h - * GUI: ViewStack API + * @brief GUI: ViewStack API * * ViewStack accumulates several Views in one stack. * Draw callbacks are called sequenctially starting from * first added. Input callbacks are called in reverse order. * Consumed input is not passed on underlying layers. + * + * @warning Views added to a ViewStack MUST NOT be in a ViewDispatcher or a ViewHolder at the same time. */ #pragma once diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index b2700c713..054a0286f 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -190,8 +190,6 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { furi_assert(session); furi_assert(istream->bytes_left); - /* TODO FL-3768 this function may be called after - marking the worker for termination */ if(session->terminate) { return false; } From 558f75672f4fe38a4ee1db0c70daa7b8ed9f05c9 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:08:35 +0200 Subject: [PATCH 06/12] [FL-3760] NFC Parsers cosmetic fixes (#3511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC Parsers cosmetic fixes * Clarify the unknown digit indication Co-authored-by: あく --- applications/main/nfc/plugins/supported_cards/myki.c | 2 +- applications/main/nfc/plugins/supported_cards/opal.c | 2 +- applications/main/nfc/plugins/supported_cards/plantain.c | 2 +- applications/main/nfc/plugins/supported_cards/two_cities.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/myki.c b/applications/main/nfc/plugins/supported_cards/myki.c index c4f1625f5..7b541024e 100644 --- a/applications/main/nfc/plugins/supported_cards/myki.c +++ b/applications/main/nfc/plugins/supported_cards/myki.c @@ -73,7 +73,7 @@ static bool myki_parse(const NfcDevice* device, FuriString* parsed_data) { // Stored card number doesn't include check digit card_number += myki_calculate_luhn(card_number); - furi_string_set(parsed_data, "\e#myki\n"); + furi_string_set(parsed_data, "\e#myki\nNo.: "); // Stylise card number according to the physical card char card_string[20]; diff --git a/applications/main/nfc/plugins/supported_cards/opal.c b/applications/main/nfc/plugins/supported_cards/opal.c index 15bec852b..db9a1784c 100644 --- a/applications/main/nfc/plugins/supported_cards/opal.c +++ b/applications/main/nfc/plugins/supported_cards/opal.c @@ -170,7 +170,7 @@ static bool opal_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, - "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", + "\e#Opal: $%s%ld.%02hu\nNo.: 3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", sign, balance_dollars, balance_cents, diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 512228405..03fa6495a 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -192,7 +192,7 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { } furi_string_printf( - parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); + parsed_data, "\e#Plantain\nNo.: %llu?\nBalance:%lu\n", card_number, balance); parsed = true; } while(false); diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index 26f42ba89..402e9d90a 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -158,7 +158,7 @@ static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, - "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", + "\e#Troika+Plantain\nPN: %llu?\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", card_number, balance, troika_number, From ee36c66572d07c32f96efb5074f8a066e6f12ffa Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 20 Mar 2024 20:32:53 +0400 Subject: [PATCH 07/12] fbt: `doxygen` target (#3510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt: doxygen target * github: adjustments for doxygen * github: always generate docs, only upload for dev * doxygen: fixed exclusion path for awesome-css * github: stricter check for push ref * github: bumped action version to fix node.js deprecation * github: added PVS report url to workflow summary * github: pvs: reworked report URL handling * github: added size report to summary * docs: added `doxygen` target * fbt: common naming scheme for COMSTR * Documentation: fix warnings and errors * Doxygen: stricter warning checks * fbt: "doxy" target, opens generated doxygen documentation in browser * github: doxygen: now using edge (1.10) version Co-authored-by: あく --- .github/workflows/build.yml | 5 +- .github/workflows/docs.yml | 8 +- .github/workflows/pvs_studio.yml | 12 +- SConstruct | 18 +- applications/services/gui/view_holder.h | 5 +- documentation/doxygen/Doxyfile-awesome.cfg | 13 +- documentation/doxygen/Doxyfile.cfg | 600 ++++++++----- documentation/fbt.md | 1 + lib/flipper_application/flipper_application.h | 59 +- lib/flipper_format/flipper_format.h | 803 ++++++++++-------- lib/lfrfid/lfrfid_worker.h | 90 +- lib/toolbox/compress.h | 33 +- scripts/fbt/util.py | 10 + scripts/fbt_tools/doxygen.py | 40 + scripts/fbt_tools/pvsstudio.py | 2 +- site_scons/fbt_extra/util.py | 2 +- 16 files changed, 1021 insertions(+), 680 deletions(-) create mode 100644 scripts/fbt_tools/doxygen.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 38b1d7b68..252310af6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -129,7 +129,7 @@ jobs: - name: 'Find previous comment' if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@v3 id: find-comment with: issue-number: ${{ github.event.pull_request.number }} @@ -138,7 +138,7 @@ jobs: - name: 'Create or update comment' if: ${{ !github.event.pull_request.head.repo.fork && matrix.target == env.DEFAULT_TARGET && github.event.pull_request }} - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} @@ -147,6 +147,7 @@ jobs: - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) - [☁️ Web/App updater](https://lab.flipper.net/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) + - [📊 Size report](https://fw-reports.flipp.dev/?branch=${{steps.names.outputs.branch_name}}) edit-mode: replace - name: 'SDK submission to staging catalog' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e6e53ee01..690bcbe19 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,7 @@ on: push: branches: - dev + pull_request: env: TARGETS: f7 @@ -37,12 +38,17 @@ jobs: python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" - name: 'Generate documentation' - uses: mattnotmitt/doxygen-action@v1.9.8 + uses: mattnotmitt/doxygen-action@edge + env: + DOXY_SRC_ROOT: "${{ github.workspace }}" + DOXY_CONFIG_DIR: "${{ github.workspace }}/documentation/doxygen" + DOXY_OUTPUT_DIR: "${{ github.workspace }}/documentation/doxygen/build" with: working-directory: 'documentation/' doxyfile-path: './doxygen/Doxyfile-awesome.cfg' - name: 'Upload documentation' + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }} uses: jakejarvis/s3-sync-action@v0.5.1 env: AWS_S3_BUCKET: "${{ secrets.FW_DOCS_AWS_BUCKET }}" diff --git a/.github/workflows/pvs_studio.yml b/.github/workflows/pvs_studio.yml index 4527e2920..8eb6fea48 100644 --- a/.github/workflows/pvs_studio.yml +++ b/.github/workflows/pvs_studio.yml @@ -48,6 +48,9 @@ jobs: WARNINGS=0 ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT + if [[ $WARNINGS -ne 0 ]]; then + echo "report-url=https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html" >> $GITHUB_OUTPUT + fi - name: 'Upload report' if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }} @@ -62,7 +65,7 @@ jobs: - name: 'Find Previous Comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@v3 id: fc with: issue-number: ${{ github.event.pull_request.number }} @@ -71,18 +74,19 @@ jobs: - name: 'Create or update comment' if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} - uses: peter-evans/create-or-update-comment@v1 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** - - [Report](https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) + - [Report](${{ steps.pvs-warn.outputs.report-url }}) edit-mode: replace - name: 'Raise exception' if: ${{ steps.pvs-warn.outputs.warnings != 0 }} run: | echo "Please fix all PVS warnings before merge" + echo "Report: ${{ steps.pvs-warn.outputs.report-url }}" + echo "[PVS report](${{ steps.pvs-warn.outputs.report-url }})" >> $GITHUB_STEP_SUMMARY exit 1 - diff --git a/SConstruct b/SConstruct index 2bc0128cc..26505fe8c 100644 --- a/SConstruct +++ b/SConstruct @@ -7,7 +7,7 @@ # construction of certain targets behind command-line options. import os -from fbt.util import path_as_posix +from fbt.util import path_as_posix, open_browser_action DefaultEnvironment(tools=[]) @@ -42,6 +42,7 @@ distenv = coreenv.Clone( "openocd", "blackmagic", "jflash", + "doxygen", ], ENV=os.environ, UPDATE_BUNDLE_DIR="dist/${DIST_DIR}/f${TARGET_HW}-update-${DIST_SUFFIX}", @@ -419,3 +420,18 @@ distenv.PhonyTarget( "env", "@echo $( ${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh $)", ) + +doxy_build = distenv.DoxyBuild( + "documentation/doxygen/build/html/index.html", + "documentation/doxygen/Doxyfile-awesome.cfg", + doxy_env_variables={ + "DOXY_SRC_ROOT": Dir(".").abspath, + "DOXY_BUILD_DIR": Dir("documentation/doxygen/build").abspath, + "DOXY_CONFIG_DIR": "documentation/doxygen", + }, +) +distenv.Alias("doxygen", doxy_build) +distenv.AlwaysBuild(doxy_build) + +# Open generated documentation in browser +distenv.PhonyTarget("doxy", open_browser_action, source=doxy_build) diff --git a/applications/services/gui/view_holder.h b/applications/services/gui/view_holder.h index bc313a926..90ce82b37 100644 --- a/applications/services/gui/view_holder.h +++ b/applications/services/gui/view_holder.h @@ -94,8 +94,9 @@ void view_holder_start(ViewHolder* view_holder); void view_holder_stop(ViewHolder* view_holder); /** View Update Handler - * @param view, View Instance - * @param context, ViewHolder instance + * + * @param view View Instance + * @param context ViewHolder instance */ void view_holder_update(View* view, void* context); diff --git a/documentation/doxygen/Doxyfile-awesome.cfg b/documentation/doxygen/Doxyfile-awesome.cfg index e4c4c95cd..b289055dd 100644 --- a/documentation/doxygen/Doxyfile-awesome.cfg +++ b/documentation/doxygen/Doxyfile-awesome.cfg @@ -1,11 +1,10 @@ -@INCLUDE = doxygen/Doxyfile.cfg +@INCLUDE = $(DOXY_CONFIG_DIR)/Doxyfile.cfg GENERATE_TREEVIEW = YES # required! DISABLE_INDEX = NO FULL_SIDEBAR = NO -HTML_EXTRA_STYLESHEET = doxygen/doxygen-awesome-css/doxygen-awesome.css \ - doxygen/doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ - doxygen/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css +HTML_EXTRA_STYLESHEET = $(DOXY_CONFIG_DIR)/doxygen-awesome-css/doxygen-awesome.css \ + $(DOXY_CONFIG_DIR)/doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ + $(DOXY_CONFIG_DIR)/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css HTML_COLORSTYLE = LIGHT # required with Doxygen >= 1.9.5 -HTML_HEADER = doxygen/header.html -HTML_EXTRA_FILES = doxygen/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js - +HTML_HEADER = $(DOXY_CONFIG_DIR)/header.html +HTML_EXTRA_FILES = $(DOXY_CONFIG_DIR)/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js diff --git a/documentation/doxygen/Doxyfile.cfg b/documentation/doxygen/Doxyfile.cfg index 28ac19e02..2596d161f 100644 --- a/documentation/doxygen/Doxyfile.cfg +++ b/documentation/doxygen/Doxyfile.cfg @@ -1,4 +1,4 @@ -# Doxyfile 1.9.2 +# Doxyfile 1.10.0 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -51,30 +61,43 @@ PROJECT_BRIEF = # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = doxygen/logo.png +PROJECT_LOGO = $(DOXY_CONFIG_DIR)/logo.png # With the PROJECT_ICON tag one can specify an icon that is included in the tabs -# when the HTML document is shown. Doxygen will copy the logo to the output directory. +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. -PROJECT_ICON = doxygen/favicon.ico +PROJECT_ICON = $(DOXY_CONFIG_DIR)/favicon.ico # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = doxygen/build +OUTPUT_DIRECTORY = $(DOXY_CONFIG_DIR)/build -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -86,14 +109,14 @@ ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English @@ -167,7 +190,7 @@ FULL_PATH_NAMES = NO # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -176,7 +199,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -346,13 +369,24 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 5 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. -AUTOLINK_SUPPORT = NO +AUTOLINK_SUPPORT = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this @@ -457,7 +491,7 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing @@ -470,6 +504,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 4 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -551,7 +593,8 @@ HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO @@ -582,14 +625,15 @@ INTERNAL_DOCS = NO # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. -# The default value is: system dependent. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = NO @@ -798,7 +842,7 @@ CITE_BIB_FILES = # messages are off. # The default value is: NO. -QUIET = NO +QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES @@ -814,7 +858,7 @@ WARNINGS = YES # will automatically be disabled. # The default value is: YES. -WARN_IF_UNDOCUMENTED = YES +WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in @@ -839,16 +883,31 @@ WARN_IF_INCOMPLETE_DOC = YES # WARN_IF_INCOMPLETE_DOC # The default value is: NO. -WARN_NO_PARAMDOC = NO +WARN_NO_PARAMDOC = YES + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. -WARN_AS_ERROR = NO +WARN_AS_ERROR = FAIL_ON_WARNINGS # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which @@ -856,13 +915,27 @@ WARN_AS_ERROR = NO # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = @@ -876,23 +949,34 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = ../applications \ - ../documentation \ - ../targets \ - ../assets \ - ../lib \ - ../furi \ - ../.vscode \ +INPUT = $(DOXY_SRC_ROOT)/applications \ + $(DOXY_SRC_ROOT)/documentation \ + $(DOXY_SRC_ROOT)/targets \ + $(DOXY_SRC_ROOT)/assets \ + $(DOXY_SRC_ROOT)/lib \ + $(DOXY_SRC_ROOT)/furi \ + $(DOXY_SRC_ROOT)/.vscode # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. @@ -904,12 +988,12 @@ INPUT_ENCODING = UTF-8 # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -937,47 +1021,45 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = \ - ../lib/mlib \ - ../lib/STM32CubeWB \ - ../lib/littlefs \ - ../lib/nanopb \ - ../assets/protobuf \ - ../lib/libusb_stm32 \ - ../lib/FreeRTOS-Kernel \ - ../lib/microtar \ - ../lib/mbedtls \ - ../lib/cxxheaderparser \ - ../lib/ST25RFAL002 \ - ../lib/fatfs \ - ../lib/mlib \ - ../lib/stm32wb_cmsis \ - ../lib/stm32wb_copro \ - ../lib/stm32wb_hal_driver \ - ../lib/stm32wb_hal \ - ../lib/cmsis_core \ - ../targets/f7/fatfs/ \ - ../applications/plugins/dap_link/lib/free-dap \ - ../applications/debug \ - ../applications/main \ - ../applications/settings \ - ../lib/micro-ecc \ - ../lib/ReadMe.md \ - ../lib/callback-connector \ - ../lib/app-scened-template \ - ../applications/ReadMe.md \ - ../targets/ReadMe.md \ - ../web \ - ../assets/protobuf \ - ../lib/libusb_stm32 \ - ../lib/FreeRTOS-Kernel \ - ../lib/microtar \ - ../lib/mbedtls \ - ../lib/cxxheaderparser \ - ../applications/external/dap_link/lib/free-dap \ - ../lib/heatshrink \ - ./doxygen/doxygen-awesome-css - +EXCLUDE = $(DOXY_SRC_ROOT)/lib/mlib \ + $(DOXY_SRC_ROOT)/lib/STM32CubeWB \ + $(DOXY_SRC_ROOT)/lib/littlefs \ + $(DOXY_SRC_ROOT)/lib/nanopb \ + $(DOXY_SRC_ROOT)/assets/protobuf \ + $(DOXY_SRC_ROOT)/lib/libusb_stm32 \ + $(DOXY_SRC_ROOT)/lib/FreeRTOS-Kernel \ + $(DOXY_SRC_ROOT)/lib/microtar \ + $(DOXY_SRC_ROOT)/lib/mbedtls \ + $(DOXY_SRC_ROOT)/lib/cxxheaderparser \ + $(DOXY_SRC_ROOT)/lib/ST25RFAL002 \ + $(DOXY_SRC_ROOT)/lib/fatfs \ + $(DOXY_SRC_ROOT)/lib/mlib \ + $(DOXY_SRC_ROOT)/lib/stm32wb_cmsis \ + $(DOXY_SRC_ROOT)/lib/stm32wb_copro \ + $(DOXY_SRC_ROOT)/lib/stm32wb_hal_driver \ + $(DOXY_SRC_ROOT)/lib/stm32wb_hal \ + $(DOXY_SRC_ROOT)/lib/cmsis_core \ + $(DOXY_SRC_ROOT)/targets/f7/fatfs/ \ + $(DOXY_SRC_ROOT)/applications/plugins/dap_link/lib/free-dap \ + $(DOXY_SRC_ROOT)/applications/debug \ + $(DOXY_SRC_ROOT)/applications/main \ + $(DOXY_SRC_ROOT)/applications/settings \ + $(DOXY_SRC_ROOT)/lib/micro-ecc \ + $(DOXY_SRC_ROOT)/lib/ReadMe.md \ + $(DOXY_SRC_ROOT)/lib/callback-connector \ + $(DOXY_SRC_ROOT)/lib/app-scened-template \ + $(DOXY_SRC_ROOT)/applications/ReadMe.md \ + $(DOXY_SRC_ROOT)/targets/ReadMe.md \ + $(DOXY_SRC_ROOT)/web \ + $(DOXY_SRC_ROOT)/assets/protobuf \ + $(DOXY_SRC_ROOT)/lib/libusb_stm32 \ + $(DOXY_SRC_ROOT)/lib/FreeRTOS-Kernel \ + $(DOXY_SRC_ROOT)/lib/microtar \ + $(DOXY_SRC_ROOT)/lib/mbedtls \ + $(DOXY_SRC_ROOT)/lib/cxxheaderparser \ + $(DOXY_SRC_ROOT)/applications/external/dap_link/lib/free-dap \ + $(DOXY_SRC_ROOT)/lib/heatshrink \ + $(DOXY_CONFIG_DIR)/doxygen-awesome-css # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -999,10 +1081,7 @@ EXCLUDE_PATTERNS = # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = @@ -1047,6 +1126,11 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. @@ -1088,6 +1172,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -1102,7 +1195,8 @@ USE_MDFILE_AS_MAINPAGE = SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO @@ -1185,10 +1279,11 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1267,7 +1362,12 @@ HTML_STYLESHEET = # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1282,6 +1382,19 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see @@ -1312,15 +1425,6 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1340,6 +1444,33 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1376,6 +1507,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1463,6 +1601,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1580,7 +1728,7 @@ GENERATE_TREEVIEW = NO # area (value NO) or if it should extend to the full height of the window (value # YES). Setting this to YES gives a layout similar to # https://docs.readthedocs.io with more room for contents, but less room for the -# project logo, title, and description. If either GENERATOR_TREEVIEW or +# project logo, title, and description. If either GENERATE_TREEVIEW or # DISABLE_INDEX is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1611,6 +1759,13 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for @@ -1631,17 +1786,6 @@ HTML_FORMULA_FORMAT = png FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1703,8 +1847,8 @@ MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example -# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html -# #tex-and-latex-extensions): +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): @@ -1955,9 +2099,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1978,14 +2129,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2151,13 +2294,39 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2232,7 +2401,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -2299,15 +2469,15 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2321,25 +2491,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2348,7 +2502,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2365,49 +2519,77 @@ HAVE_DOT = NO DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2449,8 +2631,8 @@ DOT_UML_DETAILS = NO # The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters # to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. # Minimum value: 0, maximum value: 1000, default value: 17. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2467,7 +2649,9 @@ TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2476,7 +2660,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2516,16 +2703,26 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2562,11 +2759,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2575,10 +2773,10 @@ MSCFILE_DIRS = DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = @@ -2616,18 +2814,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2640,6 +2826,8 @@ DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2653,3 +2841,19 @@ GENERATE_LEGEND = YES # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/documentation/fbt.md b/documentation/fbt.md index a7df5615b..8e083349f 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -79,6 +79,7 @@ To use language servers other than the default VS Code C/C++ language server, us - `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format. - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. Supports `ARGS="..."` to pass extra arguments to black. - `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. +- `doxygen` - generate Doxygen documentation for the firmware. `doxy` target also opens web browser to view the generated documentation. - `cli` - start a Flipper CLI session over USB. ### Firmware targets diff --git a/lib/flipper_application/flipper_application.h b/lib/flipper_application/flipper_application.h index a119cf530..3daca5bc0 100644 --- a/lib/flipper_application/flipper_application.h +++ b/lib/flipper_application/flipper_application.h @@ -33,15 +33,13 @@ typedef enum { FlipperApplicationLoadStatusMissingImports, } FlipperApplicationLoadStatus; -/** - * @brief Get text description of preload status +/** Get text description of preload status * @param status Status code * @return String pointer to description */ const char* flipper_application_preload_status_to_string(FlipperApplicationPreloadStatus status); -/** - * @brief Get text description of load status +/** Get text description of load status * @param status Status code * @return String pointer to description */ @@ -61,8 +59,7 @@ typedef struct { uint8_t* debug_link; } FlipperApplicationState; -/** - * @brief Initialize FlipperApplication object +/** Initialize FlipperApplication object * @param storage Storage instance * @param api_interface ELF API interface to use for pre-loading and symbol resolving * @return Application instance @@ -70,44 +67,44 @@ typedef struct { FlipperApplication* flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface); -/** - * @brief Destroy FlipperApplication object +/** Destroy FlipperApplication object * @param app Application pointer */ void flipper_application_free(FlipperApplication* app); -/** - * @brief Validate elf file and load application metadata - * @param app Application pointer - * @return Preload result code +/** Validate elf file and load application metadata + * + * @param app Application pointer + * @param[in] path The path to fap file + * + * @return Preload result code */ FlipperApplicationPreloadStatus flipper_application_preload(FlipperApplication* app, const char* path); -/** - * @brief Validate elf file and load application manifest - * @param app Application pointer - * @return Preload result code +/** Validate elf file and load application manifest + * + * @param app Application pointer + * @param[in] path The path to fap file + * + * @return Preload result code */ FlipperApplicationPreloadStatus flipper_application_preload_manifest(FlipperApplication* app, const char* path); -/** - * @brief Get pointer to application manifest for preloaded application +/** Get pointer to application manifest for preloaded application * @param app Application pointer * @return Pointer to application manifest */ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app); -/** - * @brief Load sections and process relocations for already pre-loaded application +/** Load sections and process relocations for already pre-loaded application * @param app Application pointer * @return Load result code */ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app); -/** - * @brief Allocate application thread at entry point address, using app name and +/** Allocate application thread at entry point address, using app name and * stack size from metadata. Returned thread isn't started yet. * Can be only called once for application instance. * @param app Applicaiton pointer @@ -116,20 +113,17 @@ FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplicatio */ FuriThread* flipper_application_alloc_thread(FlipperApplication* app, const char* args); -/** - * @brief Check if application is a plugin (not a runnable standalone app) +/** Check if application is a plugin (not a runnable standalone app) * @param app Application pointer * @return true if application is a plugin, false otherwise */ bool flipper_application_is_plugin(FlipperApplication* app); -/** - * @brief Entry point prototype for standalone applications +/** Entry point prototype for standalone applications */ typedef int32_t (*FlipperApplicationEntryPoint)(void*); -/** - * @brief An object that describes a plugin - must be returned by plugin's entry point +/** An object that describes a plugin - must be returned by plugin's entry point */ typedef struct { const char* appid; @@ -137,21 +131,18 @@ typedef struct { const void* entry_point; } FlipperAppPluginDescriptor; -/** - * @brief Entry point prototype for plugins +/** Entry point prototype for plugins */ typedef const FlipperAppPluginDescriptor* (*FlipperApplicationPluginEntryPoint)(void); -/** - * @brief Get plugin descriptor for preloaded plugin +/** Get plugin descriptor for preloaded plugin * @param app Application pointer * @return Pointer to plugin descriptor */ const FlipperAppPluginDescriptor* flipper_application_plugin_get_descriptor(FlipperApplication* app); -/** - * @brief Load name and icon from FAP file. +/** Load name and icon from FAP file. * * @param path Path to FAP file. * @param storage Storage instance. diff --git a/lib/flipper_format/flipper_format.h b/lib/flipper_format/flipper_format.h index c8f5278ed..46f78e255 100644 --- a/lib/flipper_format/flipper_format.h +++ b/lib/flipper_format/flipper_format.h @@ -1,20 +1,20 @@ -/** - * @file flipper_format.h - * Flipper File Format helper library. - * +/** @file flipper_format.h Flipper File Format helper library. + * * Flipper File Format is a fairly simple format for storing data in a file. - * + * * Flipper file structure: - * + * * ~~~~~~~~~~~~~~~~~~~~~ * # Commentary * Field name: field value * ~~~~~~~~~~~~~~~~~~~~~ - * - * Lines starting with the # character are ignored (considered as comments). The separator between the name of the value and the value itself is the string ": ". + * + * Lines starting with the # character are ignored (considered as comments). The + * separator between the name of the value and the value itself is the string + * ": ". * * Currently supported types: - * + * * ~~~~~~~~~~~~~~~~~~~~~ * String: text * Int32: 1 2 -3 4 @@ -22,13 +22,15 @@ * Float: 1.0 1234.654 * Hex: A4 B3 C2 D1 12 FF * ~~~~~~~~~~~~~~~~~~~~~ - * + * * End of line is LF when writing, but CR is supported when reading. - * - * The library is designed in such a way that comments and field values are completely ignored when searching for keys, that is, they do not consume memory. - * - * File example: - * + * + * The library is designed in such a way that comments and field values are + * completely ignored when searching for keys, that is, they do not consume + * memory. + * + * File example: + * * ~~~~~~~~~~~~~~~~~~~~~ * Filetype: Flipper Test File * Version: 1 @@ -37,59 +39,49 @@ * UINT: 1234 * Hex: 00 01 FF A3 * ~~~~~~~~~~~~~~~~~~~~~ - * + * * Writing: - * + * * ~~~~~~~~~~~~~~~~~~~~~ * FlipperFormat* format = flipper_format_file_alloc(storage); - * - * do { - * const uint32_t version = 1; - * const char* string_value = "String value"; - * const uint32_t uint32_value = 1234; - * const uint16_t array_size = 4; - * const uint8_t* array[array_size] = {0x00, 0x01, 0xFF, 0xA3}; - * - * if(!flipper_format_file_open_new(format, EXT_PATH("flipper_format_test"))) break; - * if(!flipper_format_write_header_cstr(format, "Flipper Test File", version)) break; - * if(!flipper_format_write_comment_cstr(format, "Just test file")) break; - * if(!flipper_format_write_string_cstr(format, "String", string_value)) break; - * if(!flipper_format_write_uint32(format, "UINT", &uint32_value, 1)) break; - * if(!flipper_format_write_hex(format, "Hex Array", array, array_size)) break; - * - * // signal that the file was written successfully - * } while(0); - * + * + * do { const uint32_t version = 1; const char* string_value = "String value"; + * const uint32_t uint32_value = 1234; const uint16_t array_size = 4; const + * uint8_t* array[array_size] = {0x00, 0x01, 0xFF, 0xA3}; + * + * if(!flipper_format_file_open_new(format, EXT_PATH("flipper_format_test"))) + * break; if(!flipper_format_write_header_cstr(format, "Flipper Test File", + * version)) break; if(!flipper_format_write_comment_cstr(format, + * "Just test file")) break; if(!flipper_format_write_string_cstr(format, + * "String", string_value)) break; if(!flipper_format_write_uint32(format, + * "UINT", &uint32_value, 1)) break; if(!flipper_format_write_hex(format, + * "Hex Array", array, array_size)) break; + * + * // signal that the file was written successfully } while(0); + * * flipper_format_free(file); * ~~~~~~~~~~~~~~~~~~~~~ - * + * * Reading: - * + * * ~~~~~~~~~~~~~~~~~~~~~ * FlipperFormat* file = flipper_format_file_alloc(storage); - * - * do { - * uint32_t version = 1; - * FuriString* file_type; - * FuriString* string_value; - * uint32_t uint32_value = 1; - * uint16_t array_size = 4; - * uint8_t* array[array_size] = {0}; - * file_type = furi_string_alloc(); - * string_value = furi_string_alloc(); - * - * if(!flipper_format_file_open_existing(file, EXT_PATH("flipper_format_test"))) break; - * if(!flipper_format_read_header(file, file_type, &version)) break; - * if(!flipper_format_read_string(file, "String", string_value)) break; - * if(!flipper_format_read_uint32(file, "UINT", &uint32_value, 1)) break; - * if(!flipper_format_read_hex(file, "Hex Array", array, array_size)) break; - * - * // signal that the file was read successfully - * } while(0); - * + * + * do { uint32_t version = 1; FuriString* file_type; FuriString* string_value; + * uint32_t uint32_value = 1; uint16_t array_size = 4; uint8_t* + * array[array_size] = {0}; file_type = furi_string_alloc(); string_value = + * furi_string_alloc(); + * + * if(!flipper_format_file_open_existing(file, EXT_PATH("flipper_format_test"))) + * break; if(!flipper_format_read_header(file, file_type, &version)) break; + * if(!flipper_format_read_string(file, "String", string_value)) break; + * if(!flipper_format_read_uint32(file, "UINT", &uint32_value, 1)) break; + * if(!flipper_format_read_hex(file, "Hex Array", array, array_size)) break; + * + * // signal that the file was read successfully } while(0); + * * flipper_format_free(file); * ~~~~~~~~~~~~~~~~~~~~~ - * */ #pragma once @@ -102,215 +94,240 @@ extern "C" { typedef struct FlipperFormat FlipperFormat; -/** - * Allocate FlipperFormat as string. - * @return FlipperFormat* pointer to a FlipperFormat instance +/** Allocate FlipperFormat as string. + * + * @return FlipperFormat* pointer to a FlipperFormat instance */ FlipperFormat* flipper_format_string_alloc(void); -/** - * Allocate FlipperFormat as file. - * @return FlipperFormat* pointer to a FlipperFormat instance +/** Allocate FlipperFormat as file. + * + * @param storage The storage + * + * @return FlipperFormat* pointer to a FlipperFormat instance */ FlipperFormat* flipper_format_file_alloc(Storage* storage); -/** - * Allocate FlipperFormat as file, buffered mode. - * @return FlipperFormat* pointer to a FlipperFormat instance +/** Allocate FlipperFormat as file, buffered mode. + * + * @param storage The storage + * + * @return FlipperFormat* pointer to a FlipperFormat instance */ FlipperFormat* flipper_format_buffered_file_alloc(Storage* storage); -/** - * Open existing file. - * Use only if FlipperFormat allocated as a file. - * @param flipper_format Pointer to a FlipperFormat instance - * @param path File path - * @return True on success +/** Open existing file. Use only if FlipperFormat allocated as a file. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * + * @return True on success */ bool flipper_format_file_open_existing(FlipperFormat* flipper_format, const char* path); -/** - * Open existing file, buffered mode. - * Use only if FlipperFormat allocated as a buffered file. - * @param flipper_format Pointer to a FlipperFormat instance - * @param path File path - * @return True on success +/** Open existing file, buffered mode. Use only if FlipperFormat allocated as a + * buffered file. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * + * @return True on success */ bool flipper_format_buffered_file_open_existing(FlipperFormat* flipper_format, const char* path); -/** - * Open existing file for writing and add values to the end of file. - * Use only if FlipperFormat allocated as a file. - * @param flipper_format Pointer to a FlipperFormat instance - * @param path File path - * @return True on success +/** Open existing file for writing and add values to the end of file. Use only if + * FlipperFormat allocated as a file. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * + * @return True on success */ bool flipper_format_file_open_append(FlipperFormat* flipper_format, const char* path); -/** - * Open file. Creates a new file, or deletes the contents of the file if it already exists. - * Use only if FlipperFormat allocated as a file. - * @param flipper_format Pointer to a FlipperFormat instance - * @param path File path - * @return True on success +/** Open file. Creates a new file, or deletes the contents of the file if it + * already exists. Use only if FlipperFormat allocated as a file. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * + * @return True on success */ bool flipper_format_file_open_always(FlipperFormat* flipper_format, const char* path); -/** - * Open file. Creates a new file, or deletes the contents of the file if it already exists, buffered mode. - * Use only if FlipperFormat allocated as a buffered file. - * @param flipper_format Pointer to a FlipperFormat instance - * @param path File path - * @return True on success +/** Open file. Creates a new file, or deletes the contents of the file if it + * already exists, buffered mode. Use only if FlipperFormat allocated as a + * buffered file. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * + * @return True on success */ bool flipper_format_buffered_file_open_always(FlipperFormat* flipper_format, const char* path); -/** - * Open file. Creates a new file, fails if file already exists. - * Use only if FlipperFormat allocated as a file. - * @param flipper_format Pointer to a FlipperFormat instance - * @param path File path - * @return True on success +/** Open file. Creates a new file, fails if file already exists. Use only if + * FlipperFormat allocated as a file. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param path File path + * + * @return True on success */ bool flipper_format_file_open_new(FlipperFormat* flipper_format, const char* path); -/** - * Closes the file, use only if FlipperFormat allocated as a file. - * @param flipper_format - * @return true - * @return false +/** Closes the file, use only if FlipperFormat allocated as a file. + * + * @param flipper_format The flipper format + * + * @return true + * @return false */ bool flipper_format_file_close(FlipperFormat* flipper_format); -/** - * Closes the file, use only if FlipperFormat allocated as a buffered file. - * @param flipper_format - * @return true - * @return false +/** Closes the file, use only if FlipperFormat allocated as a buffered file. + * + * @param flipper_format The flipper format + * + * @return true + * @return false */ bool flipper_format_buffered_file_close(FlipperFormat* flipper_format); -/** - * Free FlipperFormat. - * @param flipper_format Pointer to a FlipperFormat instance +/** Free FlipperFormat. + * + * @param flipper_format Pointer to a FlipperFormat instance */ void flipper_format_free(FlipperFormat* flipper_format); -/** - * Set FlipperFormat mode. - * @param flipper_format Pointer to a FlipperFormat instance - * @param strict_mode True obligates not to skip valid fields. False by default. +/** Set FlipperFormat mode. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param strict_mode True obligates not to skip valid fields. False by + * default. */ void flipper_format_set_strict_mode(FlipperFormat* flipper_format, bool strict_mode); -/** - * Rewind the RW pointer. - * @param flipper_format Pointer to a FlipperFormat instance - * @return True on success +/** Rewind the RW pointer. + * + * @param flipper_format Pointer to a FlipperFormat instance + * + * @return True on success */ bool flipper_format_rewind(FlipperFormat* flipper_format); -/** - * Move the RW pointer at the end. Can be useful if you want to add some data after reading. - * @param flipper_format Pointer to a FlipperFormat instance - * @return True on success +/** Move the RW pointer at the end. Can be useful if you want to add some data + * after reading. + * + * @param flipper_format Pointer to a FlipperFormat instance + * + * @return True on success */ bool flipper_format_seek_to_end(FlipperFormat* flipper_format); -/** - * Check if the key exists. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @return true key exists - * @return false key is not exists +/** Check if the key exists. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * + * @return true key exists + * @return false key is not exists */ bool flipper_format_key_exist(FlipperFormat* flipper_format, const char* key); -/** - * Read the header (file type and version). - * @param flipper_format Pointer to a FlipperFormat instance - * @param filetype File type string - * @param version Version Value - * @return True on success +/** Read the header (file type and version). + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param filetype File type string + * @param version Version Value + * + * @return True on success */ bool flipper_format_read_header( FlipperFormat* flipper_format, FuriString* filetype, uint32_t* version); -/** - * Write the header (file type and version). - * @param flipper_format Pointer to a FlipperFormat instance - * @param filetype File type string - * @param version Version Value - * @return True on success +/** Write the header (file type and version). + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param filetype File type string + * @param version Version Value + * + * @return True on success */ bool flipper_format_write_header( FlipperFormat* flipper_format, FuriString* filetype, const uint32_t version); -/** - * Write the header (file type and version). Plain C string version. - * @param flipper_format Pointer to a FlipperFormat instance - * @param filetype File type string - * @param version Version Value - * @return True on success +/** Write the header (file type and version). Plain C string version. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param filetype File type string + * @param version Version Value + * + * @return True on success */ bool flipper_format_write_header_cstr( FlipperFormat* flipper_format, const char* filetype, const uint32_t version); -/** - * Get the count of values by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key - * @param count - * @return bool +/** Get the count of values by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key The key + * @param count The count + * + * @return bool */ bool flipper_format_get_value_count( FlipperFormat* flipper_format, const char* key, uint32_t* count); -/** - * Read a string by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Read a string by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * + * @return True on success */ bool flipper_format_read_string(FlipperFormat* flipper_format, const char* key, FuriString* data); -/** - * Write key and string - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Write key and string + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * + * @return True on success */ bool flipper_format_write_string(FlipperFormat* flipper_format, const char* key, FuriString* data); -/** - * Write key and string. Plain C string version. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Write key and string. Plain C string version. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * + * @return True on success */ bool flipper_format_write_string_cstr( FlipperFormat* flipper_format, const char* key, const char* data); -/** - * Read array of uint64 in hex format by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Read array of uint64 in hex format by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_read_hex_uint64( FlipperFormat* flipper_format, @@ -318,13 +335,14 @@ bool flipper_format_read_hex_uint64( uint64_t* data, const uint16_t data_size); -/** - * Write key and array of uint64 in hex format - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Write key and array of uint64 in hex format + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_write_hex_uint64( FlipperFormat* flipper_format, @@ -332,13 +350,14 @@ bool flipper_format_write_hex_uint64( const uint64_t* data, const uint16_t data_size); -/** - * Read array of uint32 by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Read array of uint32 by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_read_uint32( FlipperFormat* flipper_format, @@ -346,13 +365,14 @@ bool flipper_format_read_uint32( uint32_t* data, const uint16_t data_size); -/** - * Write key and array of uint32 - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Write key and array of uint32 + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_write_uint32( FlipperFormat* flipper_format, @@ -360,13 +380,14 @@ bool flipper_format_write_uint32( const uint32_t* data, const uint16_t data_size); -/** - * Read array of int32 by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Read array of int32 by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_read_int32( FlipperFormat* flipper_format, @@ -374,13 +395,14 @@ bool flipper_format_read_int32( int32_t* data, const uint16_t data_size); -/** - * Write key and array of int32 - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Write key and array of int32 + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_write_int32( FlipperFormat* flipper_format, @@ -388,13 +410,14 @@ bool flipper_format_write_int32( const int32_t* data, const uint16_t data_size); -/** - * Read array of bool by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Read array of bool by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_read_bool( FlipperFormat* flipper_format, @@ -402,13 +425,14 @@ bool flipper_format_read_bool( bool* data, const uint16_t data_size); -/** - * Write key and array of bool - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Write key and array of bool + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_write_bool( FlipperFormat* flipper_format, @@ -416,13 +440,14 @@ bool flipper_format_write_bool( const bool* data, const uint16_t data_size); -/** - * Read array of float by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Read array of float by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_read_float( FlipperFormat* flipper_format, @@ -430,13 +455,14 @@ bool flipper_format_read_float( float* data, const uint16_t data_size); -/** - * Write key and array of float - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Write key and array of float + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_write_float( FlipperFormat* flipper_format, @@ -444,13 +470,14 @@ bool flipper_format_write_float( const float* data, const uint16_t data_size); -/** - * Read array of hex-formatted bytes by key - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Read array of hex-formatted bytes by key + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_read_hex( FlipperFormat* flipper_format, @@ -458,13 +485,14 @@ bool flipper_format_read_hex( uint8_t* data, const uint16_t data_size); -/** - * Write key and array of hex-formatted bytes - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @param data_size Values count - * @return True on success +/** Write key and array of hex-formatted bytes + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param data_size Values count + * + * @return True on success */ bool flipper_format_write_hex( FlipperFormat* flipper_format, @@ -472,57 +500,68 @@ bool flipper_format_write_hex( const uint8_t* data, const uint16_t data_size); -/** - * Write comment - * @param flipper_format Pointer to a FlipperFormat instance - * @param data Comment text - * @return True on success +/** Write comment + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param data Comment text + * + * @return True on success */ bool flipper_format_write_comment(FlipperFormat* flipper_format, FuriString* data); -/** - * Write comment. Plain C string version. - * @param flipper_format Pointer to a FlipperFormat instance - * @param data Comment text - * @return True on success +/** Write comment. Plain C string version. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param data Comment text + * + * @return True on success */ bool flipper_format_write_comment_cstr(FlipperFormat* flipper_format, const char* data); -/** - * Removes the first matching key and its value. Sets the RW pointer to a position of deleted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @return True on success +/** Removes the first matching key and its value. Sets the RW pointer to a + * position of deleted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * + * @return True on success */ bool flipper_format_delete_key(FlipperFormat* flipper_format, const char* key); -/** - * Updates the value of the first matching key to a string value. Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a string value. Sets the RW + * pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * + * @return True on success */ bool flipper_format_update_string(FlipperFormat* flipper_format, const char* key, FuriString* data); -/** - * Updates the value of the first matching key to a string value. Plain C version. Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a string value. Plain C + * version. Sets the RW pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * + * @return True on success */ bool flipper_format_update_string_cstr( FlipperFormat* flipper_format, const char* key, const char* data); -/** - * Updates the value of the first matching key to a uint32 array value. Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a uint32 array value. Sets the + * RW pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_update_uint32( FlipperFormat* flipper_format, @@ -530,12 +569,15 @@ bool flipper_format_update_uint32( const uint32_t* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to a int32 array value. Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a int32 array value. Sets the + * RW pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_update_int32( FlipperFormat* flipper_format, @@ -543,12 +585,15 @@ bool flipper_format_update_int32( const int32_t* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to a bool array value. Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a bool array value. Sets the + * RW pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_update_bool( FlipperFormat* flipper_format, @@ -556,12 +601,15 @@ bool flipper_format_update_bool( const bool* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to a float array value. Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a float array value. Sets the + * RW pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_update_float( FlipperFormat* flipper_format, @@ -569,12 +617,15 @@ bool flipper_format_update_float( const float* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to an array of hex-formatted bytes. Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to an array of hex-formatted + * bytes. Sets the RW pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_update_hex( FlipperFormat* flipper_format, @@ -582,40 +633,46 @@ bool flipper_format_update_hex( const uint8_t* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to a string value, or adds the key and value if the key did not exist. - * Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a string value, or adds the + * key and value if the key did not exist. Sets the RW pointer to a position at + * the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * + * @return True on success */ bool flipper_format_insert_or_update_string( FlipperFormat* flipper_format, const char* key, FuriString* data); -/** - * Updates the value of the first matching key to a string value, or adds the key and value if the key did not exist. - * Plain C version. - * Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a string value, or adds the + * key and value if the key did not exist. Plain C version. Sets the RW pointer + * to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * + * @return True on success */ bool flipper_format_insert_or_update_string_cstr( FlipperFormat* flipper_format, const char* key, const char* data); -/** - * Updates the value of the first matching key to a uint32 array value, or adds the key and value if the key did not exist. - * Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a uint32 array value, or adds + * the key and value if the key did not exist. Sets the RW pointer to a position + * at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_insert_or_update_uint32( FlipperFormat* flipper_format, @@ -623,13 +680,16 @@ bool flipper_format_insert_or_update_uint32( const uint32_t* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to a int32 array value, or adds the key and value if the key did not exist. - * Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a int32 array value, or adds + * the key and value if the key did not exist. Sets the RW pointer to a position + * at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_insert_or_update_int32( FlipperFormat* flipper_format, @@ -637,13 +697,16 @@ bool flipper_format_insert_or_update_int32( const int32_t* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to a bool array value, or adds the key and value if the key did not exist. - * Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a bool array value, or adds + * the key and value if the key did not exist. Sets the RW pointer to a position + * at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_insert_or_update_bool( FlipperFormat* flipper_format, @@ -651,13 +714,16 @@ bool flipper_format_insert_or_update_bool( const bool* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to a float array value, or adds the key and value if the key did not exist. - * Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to a float array value, or adds + * the key and value if the key did not exist. Sets the RW pointer to a position + * at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_insert_or_update_float( FlipperFormat* flipper_format, @@ -665,13 +731,16 @@ bool flipper_format_insert_or_update_float( const float* data, const uint16_t data_size); -/** - * Updates the value of the first matching key to an array of hex-formatted bytes, or adds the key and value if the key did not exist. - *Sets the RW pointer to a position at the end of inserted data. - * @param flipper_format Pointer to a FlipperFormat instance - * @param key Key - * @param data Value - * @return True on success +/** Updates the value of the first matching key to an array of hex-formatted + * bytes, or adds the key and value if the key did not exist. Sets the RW + * pointer to a position at the end of inserted data. + * + * @param flipper_format Pointer to a FlipperFormat instance + * @param key Key + * @param data Value + * @param[in] data_size The data size + * + * @return True on success */ bool flipper_format_insert_or_update_hex( FlipperFormat* flipper_format, diff --git a/lib/lfrfid/lfrfid_worker.h b/lib/lfrfid/lfrfid_worker.h index 22135097e..3bee1d146 100644 --- a/lib/lfrfid/lfrfid_worker.h +++ b/lib/lfrfid/lfrfid_worker.h @@ -1,5 +1,4 @@ -/** - * @file lfrfid_worker.h +/** @file lfrfid_worker.h * * LFRFID worker */ @@ -54,37 +53,35 @@ typedef void (*LFRFIDWorkerEmulateRawCallback)(LFRFIDWorkerEmulateRawResult resu typedef struct LFRFIDWorker LFRFIDWorker; -/** - * Allocate LF-RFID worker +/** Allocate LF-RFID worker * @return LFRFIDWorker* */ LFRFIDWorker* lfrfid_worker_alloc(ProtocolDict* dict); -/** - * Free LF-RFID worker - * @param worker +/** Free LF-RFID worker + * + * @param worker The worker */ void lfrfid_worker_free(LFRFIDWorker* worker); -/** - * Start LF-RFID worker thread - * @param worker +/** Start LF-RFID worker thread + * + * @param worker The worker */ void lfrfid_worker_start_thread(LFRFIDWorker* worker); -/** - * Stop LF-RFID worker thread - * @param worker +/** Stop LF-RFID worker thread + * + * @param worker The worker */ void lfrfid_worker_stop_thread(LFRFIDWorker* worker); -/** - * @brief Start read mode - * - * @param worker - * @param type - * @param callback - * @param context +/** Start read mode + * + * @param worker The worker + * @param type The type + * @param callback The callback + * @param context The context */ void lfrfid_worker_read_start( LFRFIDWorker* worker, @@ -92,13 +89,12 @@ void lfrfid_worker_read_start( LFRFIDWorkerReadCallback callback, void* context); -/** - * @brief Start write mode - * - * @param worker - * @param protocol - * @param callback - * @param context +/** Start write mode + * + * @param worker The worker + * @param protocol The protocol + * @param callback The callback + * @param context The context */ void lfrfid_worker_write_start( LFRFIDWorker* worker, @@ -106,20 +102,20 @@ void lfrfid_worker_write_start( LFRFIDWorkerWriteCallback callback, void* context); -/** - * Start emulate mode - * @param worker +/** Start emulate mode + * + * @param worker The worker + * @param[in] protocol The protocol */ void lfrfid_worker_emulate_start(LFRFIDWorker* worker, LFRFIDProtocol protocol); -/** - * @brief Start raw read mode - * - * @param worker - * @param filename - * @param type - * @param callback - * @param context +/** Start raw read mode + * + * @param worker The worker + * @param filename The filename + * @param type The type + * @param callback The callback + * @param context The context */ void lfrfid_worker_read_raw_start( LFRFIDWorker* worker, @@ -128,12 +124,12 @@ void lfrfid_worker_read_raw_start( LFRFIDWorkerReadRawCallback callback, void* context); -/** - * Emulate raw read mode - * @param worker - * @param filename - * @param callback - * @param context +/** Emulate raw read mode + * + * @param worker The worker + * @param filename The filename + * @param callback The callback + * @param context The context */ void lfrfid_worker_emulate_raw_start( LFRFIDWorker* worker, @@ -141,9 +137,9 @@ void lfrfid_worker_emulate_raw_start( LFRFIDWorkerEmulateRawCallback callback, void* context); -/** - * Stop all modes - * @param worker +/** Stop all modes + * + * @param worker The worker */ void lfrfid_worker_stop(LFRFIDWorker* worker); diff --git a/lib/toolbox/compress.h b/lib/toolbox/compress.h index b61bdb022..f844802ec 100644 --- a/lib/toolbox/compress.h +++ b/lib/toolbox/compress.h @@ -43,27 +43,29 @@ typedef struct Compress Compress; /** Allocate encoder and decoder * - * @param compress_buff_size size of decoder and encoder buffer to allocate + * @param compress_buff_size size of decoder and encoder buffer to + * allocate * - * @return Compress instance + * @return Compress instance */ Compress* compress_alloc(uint16_t compress_buff_size); /** Free encoder and decoder * - * @param compress Compress instance + * @param compress Compress instance */ void compress_free(Compress* compress); /** Encode data * - * @param compress Compress instance - * @param data_in pointer to input data - * @param data_in_size size of input data - * @param data_out maximum size of output data - * @param data_res_size pointer to result output data size + * @param compress Compress instance + * @param data_in pointer to input data + * @param data_in_size size of input data + * @param data_out maximum size of output data + * @param[in] data_out_size The data out size + * @param data_res_size pointer to result output data size * - * @return true on success + * @return true on success */ bool compress_encode( Compress* compress, @@ -75,13 +77,14 @@ bool compress_encode( /** Decode data * - * @param compress Compress instance - * @param data_in pointer to input data - * @param data_in_size size of input data - * @param data_out maximum size of output data - * @param data_res_size pointer to result output data size + * @param compress Compress instance + * @param data_in pointer to input data + * @param data_in_size size of input data + * @param data_out maximum size of output data + * @param[in] data_out_size The data out size + * @param data_res_size pointer to result output data size * - * @return true on success + * @return true on success */ bool compress_decode( Compress* compress, diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index a6a631303..42fc296f3 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -1,5 +1,8 @@ import os import re +import subprocess +import sys +import webbrowser import SCons from SCons.Errors import StopError @@ -83,3 +86,10 @@ def path_as_posix(path): if SCons.Platform.platform_default() == "win32": return path.replace(os.path.sep, os.path.altsep) return path + + +def open_browser_action(target, source, env): + if sys.platform == "darwin": + subprocess.run(["open", source[0].abspath]) + else: + webbrowser.open(source[0].abspath) diff --git a/scripts/fbt_tools/doxygen.py b/scripts/fbt_tools/doxygen.py new file mode 100644 index 000000000..319cb9e5d --- /dev/null +++ b/scripts/fbt_tools/doxygen.py @@ -0,0 +1,40 @@ +from SCons.Script import Action, Builder + + +def exists(env): + return True + + +def DoxyBuild(env, target, source, doxy_env_variables=None): + if doxy_env_variables: + doxy_env = env.Clone() + doxy_env.Append(ENV=doxy_env_variables) + else: + doxy_env = env + + return doxy_env._DoxyBuilder(target, source) + + +def generate(env): + if not env["VERBOSE"]: + env.SetDefault( + DOXYGENCOMSTR="\tDOXY\t${TARGET}", + ) + + env.SetDefault( + DOXYGEN="doxygen", + ) + + env.AddMethod(DoxyBuild) + env.Append( + BUILDERS={ + "_DoxyBuilder": Builder( + action=[ + Action( + [["$DOXYGEN", "$SOURCE"]], + "$DOXYGENCOMSTR", + ), + ], + ) + } + ) diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index ecf9d4b06..d74aae768 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -32,7 +32,7 @@ def atexist_handler(): for bf in GetBuildFailures(): for node in Flatten(bf.node): - if node.exists and node.name.endswith(".html"): + if node.exists and "pvs" in node.name and node.name.endswith(".html"): # macOS if sys.platform == "darwin": subprocess.run(["open", node.abspath]) diff --git a/site_scons/fbt_extra/util.py b/site_scons/fbt_extra/util.py index c670c01d4..e99d62c91 100644 --- a/site_scons/fbt_extra/util.py +++ b/site_scons/fbt_extra/util.py @@ -1,5 +1,5 @@ -from fbt.util import link_dir from ansi.color import fg +from fbt.util import link_dir def link_elf_dir_as_latest(env, elf_node): From 7d50c4a742b97cb1af351c23a703786f9e018d3b Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 21 Mar 2024 00:35:48 +0300 Subject: [PATCH 08/12] it-IT-mac layout (#3401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../resources/badusb/assets/layouts/it-IT-mac.kl | Bin 0 -> 256 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 applications/main/bad_usb/resources/badusb/assets/layouts/it-IT-mac.kl diff --git a/applications/main/bad_usb/resources/badusb/assets/layouts/it-IT-mac.kl b/applications/main/bad_usb/resources/badusb/assets/layouts/it-IT-mac.kl new file mode 100644 index 0000000000000000000000000000000000000000..6c10e4266bf294e3a419582ec091473d59eded9b GIT binary patch literal 256 zcmaKnw++Go0KhCO)P&wbCv;IGQOAf|`2PmP$bvU}GyJk+&-OR{wF48UUbDox3y-W^ zo0@Xx$%CaECQO-;FlWJ%6>A>ey|MA;(MuMXGjGA7CCi3JR;*gHZo{T6+ji{QGq&%* yp(Dqhvo~pW@+k!5$Wx$5i83J(6{^&z)1XO Date: Wed, 20 Mar 2024 17:45:16 -0400 Subject: [PATCH 09/12] Adding F13-F24 function key support to BadUSB (#3468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding F13-F24 function key support to BadUSB * Adding F13-F24 function key support to JS version of BadUSB Co-authored-by: hedger Co-authored-by: あく --- .../main/bad_usb/helpers/ducky_script_keycodes.c | 12 ++++++++++++ applications/system/js_app/modules/js_badusb.c | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c index da2fc22f7..56b5144d4 100644 --- a/applications/main/bad_usb/helpers/ducky_script_keycodes.c +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -64,6 +64,18 @@ static const DuckyKey ducky_keys[] = { {"F10", HID_KEYBOARD_F10}, {"F11", HID_KEYBOARD_F11}, {"F12", HID_KEYBOARD_F12}, + {"F13", HID_KEYBOARD_F13}, + {"F14", HID_KEYBOARD_F14}, + {"F15", HID_KEYBOARD_F15}, + {"F16", HID_KEYBOARD_F16}, + {"F17", HID_KEYBOARD_F17}, + {"F18", HID_KEYBOARD_F18}, + {"F19", HID_KEYBOARD_F19}, + {"F20", HID_KEYBOARD_F20}, + {"F21", HID_KEYBOARD_F21}, + {"F22", HID_KEYBOARD_F22}, + {"F23", HID_KEYBOARD_F23}, + {"F24", HID_KEYBOARD_F24}, }; uint16_t ducky_get_keycode_by_name(const char* param) { diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c index 6b19faea2..199773c39 100644 --- a/applications/system/js_app/modules/js_badusb.c +++ b/applications/system/js_app/modules/js_badusb.c @@ -52,6 +52,18 @@ static const struct { {"F10", HID_KEYBOARD_F10}, {"F11", HID_KEYBOARD_F11}, {"F12", HID_KEYBOARD_F12}, + {"F13", HID_KEYBOARD_F13}, + {"F14", HID_KEYBOARD_F14}, + {"F15", HID_KEYBOARD_F15}, + {"F16", HID_KEYBOARD_F16}, + {"F17", HID_KEYBOARD_F17}, + {"F18", HID_KEYBOARD_F18}, + {"F19", HID_KEYBOARD_F19}, + {"F20", HID_KEYBOARD_F20}, + {"F21", HID_KEYBOARD_F21}, + {"F22", HID_KEYBOARD_F22}, + {"F23", HID_KEYBOARD_F23}, + {"F24", HID_KEYBOARD_F24}, }; static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) { From 1bef579b821059c33e75e014bc33717041d3ee62 Mon Sep 17 00:00:00 2001 From: Jaroslav Nesterov Date: Thu, 21 Mar 2024 02:18:02 +0400 Subject: [PATCH 10/12] Add support for DEFAULT_STRING_DELAY in Bad USB App (#3476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for `DEFAULT_STRING_DELAY` in bad_usb * Format Sources Co-authored-by: あく --- applications/main/bad_usb/helpers/ducky_script.c | 6 +++++- .../main/bad_usb/helpers/ducky_script_commands.c | 16 +++++++++++++++- .../main/bad_usb/helpers/ducky_script_i.h | 1 + documentation/file_formats/BadUsbScriptFormat.md | 10 ++++++---- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 1b0ea71d5..5a32ae433 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -432,6 +432,7 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.line_cur = 0; bad_usb->defdelay = 0; bad_usb->stringdelay = 0; + bad_usb->defstringdelay = 0; bad_usb->repeat_cnt = 0; bad_usb->key_hold_nb = 0; bad_usb->file_end = false; @@ -455,6 +456,7 @@ static int32_t bad_usb_worker(void* context) { bad_usb->st.line_cur = 0; bad_usb->defdelay = 0; bad_usb->stringdelay = 0; + bad_usb->defstringdelay = 0; bad_usb->repeat_cnt = 0; bad_usb->file_end = false; storage_file_seek(script_file, 0, true); @@ -582,9 +584,11 @@ static int32_t bad_usb_worker(void* context) { continue; } } else if(worker_state == BadUsbStateStringDelay) { // State: print string with delays + uint32_t delay = (bad_usb->stringdelay == 0) ? bad_usb->defstringdelay : + bad_usb->stringdelay; uint32_t flags = bad_usb_flags_get( WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtDisconnect, - bad_usb->stringdelay); + delay); if(!(flags & FuriFlagError)) { if(flags & WorkerEvtEnd) { diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index a5bc7c8cf..fdf963b40 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -46,6 +46,17 @@ static int32_t ducky_fnc_strdelay(BadUsbScript* bad_usb, const char* line, int32 return 0; } +static int32_t ducky_fnc_defstrdelay(BadUsbScript* bad_usb, const char* line, int32_t param) { + UNUSED(param); + + line = &line[ducky_get_command_len(line) + 1]; + bool state = ducky_get_number(line, &bad_usb->defstringdelay); + if(!state) { + return ducky_error(bad_usb, "Invalid number %s", line); + } + return 0; +} + static int32_t ducky_fnc_string(BadUsbScript* bad_usb, const char* line, int32_t param) { line = &line[ducky_get_command_len(line) + 1]; furi_string_set_str(bad_usb->string_print, line); @@ -53,7 +64,8 @@ static int32_t ducky_fnc_string(BadUsbScript* bad_usb, const char* line, int32_t furi_string_cat(bad_usb->string_print, "\n"); } - if(bad_usb->stringdelay == 0) { // stringdelay not set - run command immediately + if(bad_usb->stringdelay == 0 && + bad_usb->defstringdelay == 0) { // stringdelay not set - run command immediately bool state = ducky_string(bad_usb, furi_string_get_cstr(bad_usb->string_print)); if(!state) { return ducky_error(bad_usb, "Invalid string %s", line); @@ -161,6 +173,8 @@ static const DuckyCmd ducky_commands[] = { {"DEFAULTDELAY", ducky_fnc_defdelay, -1}, {"STRINGDELAY", ducky_fnc_strdelay, -1}, {"STRING_DELAY", ducky_fnc_strdelay, -1}, + {"DEFAULT_STRING_DELAY", ducky_fnc_defstrdelay, -1}, + {"DEFAULTSTRINGDELAY", ducky_fnc_defstrdelay, -1}, {"REPEAT", ducky_fnc_repeat, -1}, {"SYSRQ", ducky_fnc_sysrq, -1}, {"ALTCHAR", ducky_fnc_altchar, -1}, diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index 84c7ef9de..9c1025b00 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -30,6 +30,7 @@ struct BadUsbScript { uint32_t defdelay; uint32_t stringdelay; + uint32_t defstringdelay; uint16_t layout[128]; FuriString* line; diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 3bda30617..cc919da09 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -97,10 +97,12 @@ Will wait indefinitely for a button to be pressed ## String delay Delay between keypresses. -| Command | Parameters | Notes | -| ------------ | ----------------- | --------------------------------------------- | -| STRING_DELAY | Delay value in ms | Applied once to next appearing STRING command | -| STRINGDELAY | Delay value in ms | Same as STRING_DELAY | +| Command | Parameters | Notes | +| -------------------- | ----------------- | --------------------------------------------- | +| STRING_DELAY | Delay value in ms | Applied once to next appearing STRING command | +| STRINGDELAY | Delay value in ms | Same as STRING_DELAY | +| DEFAULT_STRING_DELAY | Delay value in ms | Apply to every appearing STRING command | +| DEFAULTSTRINGDELAY | Delay value in ms | Same as DEFAULT_STRING_DELAY | ### Repeat From 0c465f7eb32ec34fea5b04bd4ecc4e2b60a8ec95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=85=BD=E5=85=BD?= Date: Wed, 20 Mar 2024 18:24:33 -0400 Subject: [PATCH 11/12] Added new IR commands (#3480) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- .../infrared/resources/infrared/assets/tv.ir | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index fcbbf28dc..1159f2f04 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1800,3 +1800,31 @@ type: parsed protocol: NECext address: 01 72 00 00 command: 44 BB 00 00 +# +# Koro Box +# +name: Power +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 18 00 00 00 +# +# Toshiba Amazon TV +# +name: Vol_up +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0E 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 0F 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 01 00 00 00 +command: 17 00 00 00 \ No newline at end of file From 4039ccbcca715a7d0339d882312dcbaccda15d6b Mon Sep 17 00:00:00 2001 From: Silent Date: Wed, 20 Mar 2024 23:36:38 +0100 Subject: [PATCH 12/12] fbt/ufbt: Ensure POSIX paths are passed to GDB on all platforms (#3360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbt/ufbt: Ensure POSIX paths are passed to GDB on all platforms GDB heavily dislikes forward slashes from Windows paths and strips them internally instead of normalizing them. Account for this by passing POSIX paths explicitly. * fbt: different approach for posix path handling * fbt, ufbt: further fixes for path handling * fbt: explicit path stringification * linter fixes Co-authored-by: hedger Co-authored-by: hedger Co-authored-by: あく --- SConstruct | 2 -- scripts/fbt/util.py | 22 +++++++++++++++++++ scripts/fbt_tools/fbt_debugopts.py | 18 +++++++-------- scripts/fbt_tools/fbt_sdk.py | 6 ++--- scripts/ufbt/SConstruct | 35 +++++++++++++++--------------- site_scons/environ.scons | 3 ++- 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/SConstruct b/SConstruct index 26505fe8c..b48b1c684 100644 --- a/SConstruct +++ b/SConstruct @@ -230,7 +230,6 @@ firmware_debug = distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", - FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), ) distenv.Depends(firmware_debug, firmware_flash) @@ -240,7 +239,6 @@ distenv.PhonyTarget( source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", GDBREMOTE="${BLACKMAGIC_ADDR}", - FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), ) # Debug alien elf diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index 42fc296f3..19fdb3ae6 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -3,6 +3,7 @@ import re import subprocess import sys import webbrowser +from pathlib import Path, PurePosixPath import SCons from SCons.Errors import StopError @@ -82,6 +83,27 @@ def resolve_real_dir_node(node): raise StopError(f"Can't find absolute path for {node.name} ({node})") +class PosixPathWrapper: + def __init__(self, pathobj): + self.pathobj = pathobj + + @staticmethod + def fixup_separators(path): + if SCons.Platform.platform_default() == "win32": + return path.replace(os.path.sep, os.path.altsep) + return path + + @staticmethod + def fix_path(path): + return str(PurePosixPath(Path(path).as_posix())) + + def __call__(self, target, source, env, for_signature): + if for_signature: + return self.pathobj + + return self.fix_path(env.subst(self.pathobj)) + + def path_as_posix(path): if SCons.Platform.platform_default() == "win32": return path.replace(os.path.sep, os.path.altsep) diff --git a/scripts/fbt_tools/fbt_debugopts.py b/scripts/fbt_tools/fbt_debugopts.py index 392465a51..77b63e203 100644 --- a/scripts/fbt_tools/fbt_debugopts.py +++ b/scripts/fbt_tools/fbt_debugopts.py @@ -18,7 +18,7 @@ def GetDevices(env): def generate(env, **kw): env.AddMethod(GetDevices) env.SetDefault( - FBT_DEBUG_DIR="${FBT_SCRIPT_DIR}/debug", + FBT_DEBUG_DIR="${POSIXPATH('$FBT_SCRIPT_DIR')}/debug", ) if (adapter_serial := env.subst("$SWD_TRANSPORT_SERIAL")) != "auto": @@ -36,11 +36,11 @@ def generate(env, **kw): env.SetDefault( OPENOCD_GDB_PIPE=[ - "|openocd -c 'gdb_port pipe; log_output ${FBT_DEBUG_DIR}/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" + "|openocd -c 'gdb_port pipe; log_output ${POSIXPATH('$FBT_DEBUG_DIR')}/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" ], GDBOPTS_BASE=[ "-ex", - "source ${FBT_DEBUG_DIR}/gdbinit", + "source ${POSIXPATH('$FBT_DEBUG_DIR')}/gdbinit", "-ex", "target extended-remote ${GDBREMOTE}", ], @@ -57,17 +57,17 @@ def generate(env, **kw): ], GDBPYOPTS=[ "-ex", - "source ${FBT_DEBUG_DIR}/FreeRTOS/FreeRTOS.py", + "source ${POSIXPATH('$FBT_DEBUG_DIR')}/FreeRTOS/FreeRTOS.py", "-ex", - "source ${FBT_DEBUG_DIR}/flipperapps.py", + "source ${POSIXPATH('$FBT_DEBUG_DIR')}/flipperapps.py", "-ex", - "source ${FBT_DEBUG_DIR}/flipperversion.py", + "source ${POSIXPATH('$FBT_DEBUG_DIR')}/flipperversion.py", "-ex", - "fap-set-debug-elf-root ${FBT_FAP_DEBUG_ELF_ROOT}", + "fap-set-debug-elf-root ${POSIXPATH('$FBT_FAP_DEBUG_ELF_ROOT')}", "-ex", - "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py", + "source ${POSIXPATH('$FBT_DEBUG_DIR')}/PyCortexMDebug/PyCortexMDebug.py", "-ex", - "svd_load ${SVD_FILE}", + "svd_load ${POSIXPATH('$SVD_FILE')}", "-ex", "compare-sections", "-ex", diff --git a/scripts/fbt_tools/fbt_sdk.py b/scripts/fbt_tools/fbt_sdk.py index 17acc8cf1..a3f7faa57 100644 --- a/scripts/fbt_tools/fbt_sdk.py +++ b/scripts/fbt_tools/fbt_sdk.py @@ -6,7 +6,7 @@ import shutil from fbt.sdk.cache import SdkCache from fbt.sdk.collector import SdkCollector -from fbt.util import path_as_posix +from fbt.util import PosixPathWrapper from SCons.Action import Action from SCons.Builder import Builder from SCons.Errors import UserError @@ -80,7 +80,7 @@ class SdkMeta: vars, target=Entry(self.MAP_FILE_SUBST), ) - return path_as_posix(expanded_vars) + return PosixPathWrapper.fixup_separators(expanded_vars) class SdkTreeBuilder: @@ -149,7 +149,7 @@ class SdkTreeBuilder: meta.save_to(self.target[0].path) def build_sdk_file_path(self, orig_path: str) -> str: - return path_as_posix( + return PosixPathWrapper.fix_path( posixpath.normpath( posixpath.join( self.SDK_DIR_SUBST, diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 8178a83da..11fefd22b 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -47,7 +47,7 @@ from fbt.appmanifest import FlipperApplication, FlipperAppType from fbt.sdk.cache import SdkCache from fbt.util import ( FORWARDED_ENV_VARIABLES, - path_as_posix, + PosixPathWrapper, resolve_real_dir_node, single_quote, tempfile_arg_esc_func, @@ -89,6 +89,7 @@ env = core_env.Clone( ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), ], FBT_FAP_DEBUG_ELF_ROOT=ufbt_build_dir, + POSIXPATH=PosixPathWrapper, TEMPFILE=TempFileMunge, MAXLINELENGTH=2048, PROGSUFFIX=".elf", @@ -128,7 +129,7 @@ dist_env = env.Clone( "-c", "transport select hla_swd", "-f", - "${FBT_DEBUG_DIR}/stm32wbx.cfg", + "${POSIXPATH('$FBT_DEBUG_DIR')}/stm32wbx.cfg", "-c", "stm32wbx.cpu configure -rtos auto", ], @@ -161,7 +162,6 @@ firmware_debug = dist_env.PhonyTarget( source=dist_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", - FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), ) dist_env.PhonyTarget( @@ -170,15 +170,14 @@ dist_env.PhonyTarget( source=dist_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}", GDBREMOTE="${BLACKMAGIC_ADDR}", - FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")), ) # Debug alien elf debug_other_opts = [ "-ex", - "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py", + "source ${POSIXPATH('FBT_DEBUG_DIR')}/PyCortexMDebug/PyCortexMDebug.py", "-ex", - "source ${FBT_DEBUG_DIR}/flipperversion.py", + "source ${POSIXPATH('FBT_DEBUG_DIR')}/flipperversion.py", "-ex", "fw-version", ] @@ -371,10 +370,6 @@ dist_env.PhonyTarget( # Prepare vscode environment -def _path_as_posix(path): - return pathlib.Path(path).as_posix() - - vscode_dist = [] project_template_dir = dist_env["UFBT_SCRIPT_ROOT"].Dir("project_template") for template_file in project_template_dir.Dir(".vscode").glob("*"): @@ -387,20 +382,24 @@ for template_file in project_template_dir.Dir(".vscode").glob("*"): "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@": pathlib.Path( dist_env.WhereIs("arm-none-eabi-gcc") ).parent.as_posix(), - "@UFBT_TOOLCHAIN_GCC@": _path_as_posix( + "@UFBT_TOOLCHAIN_GCC@": PosixPathWrapper.fix_path( dist_env.WhereIs("arm-none-eabi-gcc") ), - "@UFBT_TOOLCHAIN_GDB_PY@": _path_as_posix( + "@UFBT_TOOLCHAIN_GDB_PY@": PosixPathWrapper.fix_path( dist_env.WhereIs("arm-none-eabi-gdb-py3") ), - "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")), - "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath), - "@UFBT_ROOT_DIR@": _path_as_posix(Dir("#").abspath), - "@UFBT_DEBUG_DIR@": _path_as_posix(dist_env["FBT_DEBUG_DIR"].abspath), - "@UFBT_DEBUG_ELF_DIR@": _path_as_posix( + "@UFBT_TOOLCHAIN_OPENOCD@": PosixPathWrapper.fix_path( + dist_env.WhereIs("openocd") + ), + "@UFBT_APP_DIR@": PosixPathWrapper.fix_path(original_app_dir.abspath), + "@UFBT_ROOT_DIR@": PosixPathWrapper.fix_path(Dir("#").abspath), + "@UFBT_DEBUG_DIR@": dist_env.subst("FBT_DEBUG_DIR"), + "@UFBT_DEBUG_ELF_DIR@": PosixPathWrapper.fix_path( dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath ), - "@UFBT_FIRMWARE_ELF@": _path_as_posix(dist_env["FW_ELF"].abspath), + "@UFBT_FIRMWARE_ELF@": PosixPathWrapper.fix_path( + dist_env["FW_ELF"].abspath + ), }, ) ) diff --git a/site_scons/environ.scons b/site_scons/environ.scons index ece8de212..3f2d3450e 100644 --- a/site_scons/environ.scons +++ b/site_scons/environ.scons @@ -3,6 +3,7 @@ import os from fbt.util import ( FORWARDED_ENV_VARIABLES, + PosixPathWrapper, resolve_real_dir_node, single_quote, tempfile_arg_esc_func, @@ -26,7 +27,6 @@ for env_value_name in variables_to_forward: if environ_value := os.environ.get(env_value_name, None): forward_os_env[env_value_name] = environ_value - coreenv = VAR_ENV.Clone( tools=[ "fbt_tweaks", @@ -43,6 +43,7 @@ coreenv = VAR_ENV.Clone( "ccache", ], TEMPFILE=TempFileMunge, + POSIXPATH=PosixPathWrapper, MAXLINELENGTH=2048, PROGSUFFIX=".elf", ENV=forward_os_env,