From 3bc0ac59491c5d283689288258dac8ef36724337 Mon Sep 17 00:00:00 2001 From: Suprapote <111246491+Suprapote@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:04:25 +0200 Subject: [PATCH 01/89] Add sound effects (#108) ## Description Adding sound for make the navigation not so borring. ## Motivation and Context This [issue](https://github.com/Polprzewodnikowy/N64FlashcartMenu/issues/89) ## How Has This Been Tested? ## Screenshots https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/111246491/0f8086f6-16b3-4adb-a925-afbfc9fa6ba9 ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --------- Co-authored-by: Robin Jones --- .gitignore | 1 + Makefile | 16 ++++++++++- README.md | 7 +++++ assets/back.wav | Bin 0 -> 34382 bytes assets/cursorsound.wav | Bin 0 -> 179862 bytes assets/enter.wav | Bin 0 -> 19210 bytes assets/error.wav | Bin 0 -> 198282 bytes assets/settings.wav | Bin 0 -> 19218 bytes src/menu/components/context_menu.c | 5 ++++ src/menu/menu.c | 4 +++ src/menu/settings.c | 6 ++-- src/menu/sound.c | 43 ++++++++++++++++++++++++++++- src/menu/sound.h | 12 +++++++- src/menu/views/browser.c | 7 +++++ src/menu/views/credits.c | 3 +- src/menu/views/error.c | 3 ++ src/menu/views/file_info.c | 2 ++ src/menu/views/flashcart_info.c | 2 ++ src/menu/views/image_viewer.c | 3 ++ src/menu/views/load_disk.c | 3 ++ src/menu/views/load_emulator.c | 2 ++ src/menu/views/load_rom.c | 3 ++ src/menu/views/music_player.c | 2 ++ src/menu/views/rtc.c | 2 ++ src/menu/views/settings_editor.c | 2 ++ src/menu/views/system_info.c | 2 ++ src/menu/views/text_viewer.c | 2 ++ 27 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 assets/back.wav create mode 100644 assets/cursorsound.wav create mode 100644 assets/enter.wav create mode 100644 assets/error.wav create mode 100644 assets/settings.wav diff --git a/.gitignore b/.gitignore index bc2d5ddfc..35d81191c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Ignore generated files in the libdragon FS /filesystem/FiraMonoBold.font64 +/filesystem/*.wav64 # Ignore external development tools /tools/* diff --git a/Makefile b/Makefile index 3b6a1f999..f92267b7e 100644 --- a/Makefile +++ b/Makefile @@ -77,17 +77,27 @@ SRCS = \ FONTS = \ FiraMonoBold.ttf +SOUNDS = \ + cursorsound.wav \ + back.wav \ + enter.wav \ + error.wav \ + settings.wav + OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS)))) MINIZ_OBJS = $(filter $(BUILD_DIR)/libs/miniz/%.o,$(OBJS)) SPNG_OBJS = $(filter $(BUILD_DIR)/libs/libspng/%.o,$(OBJS)) DEPS = $(OBJS:.o=.d) FILESYSTEM = \ - $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64))) + $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64))) \ + $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(SOUNDS:%.wav=%.wav64))) $(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second $(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1 +$(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1 + $(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null)) @@ -95,6 +105,10 @@ $(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf @echo " [FONT] $@" @$(N64_MKFONT) $(MKFONT_FLAGS) -o $(FILESYSTEM_DIR) "$<" +$(FILESYSTEM_DIR)/%.wav64: $(ASSETS_DIR)/%.wav + @echo " [AUDIO] $@" + @$(N64_AUDIOCONV) $(AUDIOCONV_FLAGS) -o $(FILESYSTEM_DIR) "$<" + $(BUILD_DIR)/$(PROJECT_NAME).dfs: $(FILESYSTEM) $(BUILD_DIR)/menu/views/credits.o: .FORCE diff --git a/README.md b/README.md index 288b49418..21ff68bec 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ An open source menu for N64 flashcarts. * Comprehensive ROM information display. * Real Time Clock support. * Music playback (MP3). +* Menu sound effects. ### Video showcase (as of Oct 12 2023) @@ -160,3 +161,9 @@ Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64Fla - [mini.c](https://github.com/univrsal/mini.c) (BSD 2-Clause License) - [minimp3](https://github.com/lieff/minimp3) (CC0 1.0 Universal) - [miniz](https://github.com/richgel999/miniz) (MIT License) + +## Sounds +See [License](https://pixabay.com/en/service/license-summary/) for the following sounds: + - [Cursor sound](https://pixabay.com/en/sound-effects/click-buttons-ui-menu-sounds-effects-button-7-203601/) by Skyscraper_seven (Free to use) + - [Actions (Enter, back) sound](https://pixabay.com/en/sound-effects/menu-button-user-interface-pack-190041/) by Liecio (Free to use) + - [Error sound](https://pixabay.com/en/sound-effects/error-call-to-attention-129258/) by Universfield (Free to use) diff --git a/assets/back.wav b/assets/back.wav new file mode 100644 index 0000000000000000000000000000000000000000..f8d4655e8815dfd6efa52a564ca798ac7ec9cdfd GIT binary patch literal 34382 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

$O~9 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 Dr{M+S literal 0 HcmV?d00001 diff --git a/assets/enter.wav b/assets/enter.wav new file mode 100644 index 0000000000000000000000000000000000000000..ad88ca2324a9751885aa4605dd38d000a7b0c669 GIT binary patch literal 19210 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 F{|B66diVeU literal 0 HcmV?d00001 diff --git a/assets/error.wav b/assets/error.wav new file mode 100644 index 0000000000000000000000000000000000000000..5ced329b11df10bdd5ec2f911d56e1498dfa9baf GIT binary patch literal 198282 zcmZsC1$Yx#_x@Otrta?UEiEltXmNL2WRV3Hhs76nSbSk&(Z!uzbaB^0X-nNDZPFx- z*CJ#8GtAc?-}gNK&XaU9a__n4zUQ3xoVhb6rht z!S%uS$hX3cj33GO?{>6*WXJNjK`ynI~S?R~e)%W0|o7%%Jh7C7Cux5n$SUE2R09Y2rX$Hz&*kC40n zzoqBp7yL)F|33|qFc!WqPUDRJ4>Eal@?+p- zYM)8YHvShK?fT|X!=Z$qcO0&3KgRdVjgY(hzY)ygpj~6%waOi7zlz5?uWjDC+AWI5 zeY`4RA8IU&YJ7wZzRNhaFC4 z`u{APyOP5J1#`jQ(?0+0GIDqFXlTbQccvXDoc_2w+Hu1F{%@UeBjfeV?eSP@M`OD- zXtIaXR6oZTErv-ghm2Z=XN*{dc?WdEE1o{VxJ}4D))hup-b$`)sz)H>WM` z@B4VU7XJGP*E%-_{uqy;cIm$Bjq8cW%6AO&+T~}DYoXnSxV?65aPz@S#`yrgk9NKB zbIZ*KcQx;~IIZ$mwR;TiJl`^po&QEEubFng`@Q}DmgM{Wyu7>yc@*%Ha{aYi?e{y| zN5RR}-UC1D|9clVxBN_TXZhaRwaD87k0^d#zmJrYy&ZjAi|zfi&m*rL{u+LqeDB}! z){a}Qe_o3AQE{Vbmw}%N?k--#|2rD~zVChUS92QS{&;P0?YC=HS z_IO=#z4Ph1pFNEdf+2CH^+Pr|NEP_P_9RQbbK$ICV1WOnBe*le7lA_&wDDqJ^p+< zS81Q&b`9{da5`(>YsYiDmbek{IBb`TlbW+h{tVwAe-|$sr%7(qoM!l#)h;_P%Xe@x|5pvciL`?q|0XU6tTIJ$@yL?;^oCezMkVDCT^DkaA{0Q2|!{5vI#m^#t6}Qi8 zi0hx1o;$*6hsPJEEAuz*&TD{U&Rp-@Jh3d-uNkB@!+-KW+T-Nq<>U3qNylr1(<_HM zUZWglxxRQAxN-Bc@cQF=;g0Znn_R2BzIgQTHo)nmJ)_}g zor_gmf1KRBeQ8L8*ZuIxBO+nhVX*u zE%9DS^Hcur{G{8Z9zSPT_1V==I>2pk^w5*T-VRj`THN2GPgeTTw0T`8b{g4nVuywC z`(vI)HispJYzQp!i}X3}NxLm^Q93TPZ?st{#zaSn1T2?bL_3ntO!L}YT8a%jb^SGj zDpPT=VYD=%j;txE5?7t9@~Pfl9Z}m-cT(n}_@(iJ`lj}@enyjj%iY!l<0+HU>`(Ti zrqHvPv1|{-8?8mpU}JEcxJ(QZNCm5eBGEpPpVcX=Sn)}57wdnlGi;vPjI+IN8)DOD zYr=QHNKS#WsB>u+Ba3#D;}1WmIM?ZD6}njmbdcjsN5kr z zPLg@D?u{R{qnnQzv&paQZG5B1)#kK)i&J;Eah}tChWh&keGHipUKW`a^LyNj1Z9W9 zR8~ zeiW_kucz{RnXzG~!o6{)%A_8ny`nSfJ2Wk7-rI7gHM31=WK6b}2r_``M#nKj*{y6FJ&clB zmYLH{PDZxXp*6l`Uh|Wt$fkXUE{0F~3wohGOB19jSL|unD?MJp z(6T>h2xTdVQ@?Oo(y8bdoeyuS{ldBuj z^s*()xY_)kbYMCo)36o95#bQ)e0wjKp&lcBdIqq;7sKPDcgE!;Dw8d#WnHd!AJJ2r zF{{txeoh0u2M-z=Iy`vfwo&SlppxMqhkY3OYRHqp{|lFF7`gr zYfr|e^cg)z_Xtl5>@M%pJM~18Q^K*B_{gfzzk;Uv_wotz40a21?%*)ic7^zFp#)Dt zmoimk2lMa7%f>8Yt}(~>(0IuBlhMam+_tpMx$RPGXzSG$zm@~dO-;j^?i*|jOZ2yO z4!YjjVC}!U9fsiM?3TW5{~B$~<1L5Chg1Pw&nVe4oF4gM{E1RV)L)&f{h2|eE=g4|0kQvObL`tworX%V z*+he2qA1rY+WLr%#I}?DGKa^GI;Sv~p{~EW{o#JY15&a;#Iyxj~Xw1f#t1)k58e&>wY+{L6$5_09)G(RxlyiOt|>14oo6`Ba$N7Q&VHxeAGQZ=4p?s!Z?Kvtnl1c6Fp3z2XJGx% z1SE+KWy0w|Duzt9q?yN?<{Qtny>At@RyO^nlWRt6E~&lLn^pS8`Hf2DFG{0guOe9S zLB2rlAW@ku8%=kfq7OWdd27v=p{{X_-_Z70cpelVnF_WiqdZg`j_b z`C_>Z==Y`Op8j?7(N=To(6-xc9>$$UvvH-V$+Xl=n>SkQ$v??ZDwCQ!LHo)fz4U#o#GW%i$wE; za|P3gAMuIUH1sEA5xbW;MSr3sq%WCa*@fn-Isv|?LXYKgc=pC6q6CjUq9{W}Wni%u2!6kjZkDtS{fzLYJ!Q1(MPUU9GD z=Sr`t?5dU3$u%`KCu@gG9O`oF4%Cm6ddf7in+=l`nW_Q0<4tv~cIHT`3mb-tW94ea`vV`EK!*_+Isy>6PXY;O6ZT_BN|T5yWTY z3cbT}$atlt#t^OBul8!ZF8@*HU$3q$seWErSe{?%Rx-S3L*c}t(ZyX$0!wwJ&&pPn zcdpP?T&x^cWvn_@-La;yW>PI8Sts$V+fo-?KT5VrF+(*(>u1Pr-qc2$Hj~A46tV<6 zL%bD!vHD{D$o9DX8pnRl9f$=xCDS?8 z`MguK(`Cmlj;|dSIh?T9*qE&P3Af`I`YRJaWtmSKx3?~C-fH+$_d%mqb#C0PP&Leu z71WQYtEt^wlTck(d8cA=`Q);avQZWLDyyo5H37BBlJL5qdP{w|^rUP`gSGrV_&$x& zR}-aoX|8JBVe%r^(dDcViy}G*eXUI5$2RlroE`2t_H=&a65zJc{Rv1M=-t7mk8g%w zcmLo3+dxG?FaM*yjy^lQB0V$RH@aoGTDcgUavXCU?%7w^46te>&Z8TcHRK?=rRu)&fPABDSN&g->>5$^h{{{#{$&qK#uYmil@wgc-<>!A>-yY9IVZ9&eHs4c zL-x+x$h=SaqYB>_buIb5)KnHw(N?*+cBWJ=-=QkfCO3^~U1^$3c469(Q+R^#jFpqk zT)PJj1}6vCuI}AE2YJW(y82fH{2r7MQWLr%ye(pTRAcn4*xPX)32BMf62`};#W}=^ zV?@zjQN1EV!=D8&4jAT}<~7EBtxKU}NBbu>KZvbFbwnoi1u17NR06rg{L<*$wzUOo z-fsxif6)G-Nl~{o7AXHw{3&18@T=?>>Du~Tb>}3{YlV_~bzP+IWWn;Y3W@5aZc?+P zt&e3LeH=M}&ldWKU)ZGEUvpGDhq?9e=;9UaL;Gg=uMZ3iz7-M|wlCZe(Jg9C^qrXe zSVLS(Jey!l1m>A^BYAVj;VD+B`KgA~UMXvm<2vARm64iIVbD~+mtIrd?OfCjrM9(V z2hmXC8tTDbrG{GUO!C%G&6$RBomtaGy|%GR(Nlg$)>HblZla{A=5+PMDxb=x^4DcI zN;j7*Egn(yV`0C7U-OlPp=BObt+nT+iHf7DT&=-S-}2VD!s0_8XKk=a#5v(>t6b|$ z+iUi}ItDqvbLsE))ZN{4j@MmpsgJ#1tpC7(F@a-(`UXda2t%_%Z-h+z zIvlY&EH(J0-+8Y!Zs(m!?D|=M7B0hkAii{@rKfRo%SS_s?ut6CQ6j$rC|NH#So63_ zS<$)t_fkvotRh{(n*6}LN4ZOLVzPBvIiJseI+MBcII2!0!ugiB5`tu}QOQvcKp!z**ul z-%aN}$MdUKH=nb)~7EecL0-wbk)~-6!ix zQLJD;W{dnoPXQL`WVCG!YaRzS*`%4RRy1x_CMYxwk7T=~OY0}sO_xlmU0AcV`e@bD z$`2J-g?ss>GUp2Ws*lyXYGdp2>Ln*0#+mS5Sl**aUjJMQGa7+PS$^|3!OO zeXsF@LfH^5TUB2w8D9IfdU}(sE zLA_LJkon8)6?>F9>Y@4r%_VJ8a|88)-Ht^H9*H_wAGTH72RV&!neDd8V};igpKgAv z|I5HR!ET{v!n#HL8#yw1Qq11y?jWySglo7ibOCosrwcO^J=56bw!J)`G#SX-ck2fb5K22 zm8d$a_R_A_73-~=lAHUrjA2Yi~Ohx=EQizjm zf3TnII28PJj@xXHM$ZY}*L<3Mef$Rm3=PZ(iVsFZibLjv4h~5S>Ko9@Z-LKtuYWyS z+&a4+b5=S|c6e^r#rB?cs`!&=qR>`w2A_e6&}!C)Ib*?%mzziElQq7Lo#b<*e@g;t z{;cd=URiRvXl=o)yqUR+vQK4w{`6<&s84G?FUi{XWnK23oaMQ*zmCsK&krlm7vvOP zEZSH+ydX!I@a zE1YC?)4Iae%fZ%hn?t?*0{dFKHFoxPFKy@A#@M#m9%|Ko-!Sn?(%<{!F%jnmZ*Yc!!Ws9Kg zCu5B%%CduePYHpgjY4LlbFm-s-h{89MQ~m?QDhQbv`P`b6sKBWvc_!&+8ng`0?|Lf zHo_LOo8!>n6yZA0eX{2)?+(7Le%k|FgLVXKLT-l64XF)U6{zvw=V$Hvm$$#y9glSP z9M^>|xbxqRV;!vRAKNamNw6lZK8cPAmkTBl1M#j{XEYu8fnCA;Nxz~DWQBz^-fg*K zIHS!|VaiFst;R~w8nS9bbzAkS8cWUmT7B&VNxfu3U47l`dPDtYsg3NIEUe*G!yvg@ zzFy(2yrN8M{M`71id1b?M?v;7RV&l()p_dQ>8BX%n?|;5H=eMZq>mzV@Ik_Ct5xDL z;-AE$#Y@F2#J`IlitEHq)&s0JTHmv#tdniFf_ANKC)oaF%i0dMyKQG@zuI1DKf>Xe zLxkg>j+EnSr+lX|&QF}Ta-{ueNzBWB<-RyAt3j2o+n;g@f zXs2t=BU}_Nt6a^ltK3@LX1ITG5A@jT@!Es&@bOId?CUwuGs@G-v&iG9$3zc-$9?y) z?nbxuZkXFzSEb7wmuJo=o#R|x+&jC+xd~l0&hMQLI8Jm3w3phRvzcohB9@9S3x5=Z z5vAA>Gy`d6F45yDcQVg>$TY#&q0QV<)%>#QqT#&$cin#NcFi{RKGosIYs#mJS~($) zYnUm!Egc5gTU`CMy5zb~l4%kL$*bD6wf$?|YZW!0Ywpxss5w#dXU+AR$2IC2m)b$K zduuam-6eA*&n514Yw8;6GU}hz`$=y|gJh3o=?%(;UGk0!g<`LAd}Hs%m5SdRJR7tP za`{WeW98Y#1FB8x`I_p2l^B9ntg{n zMekx)@QcJ*!6D%R(Oj!B;z(;7n{1nNwql26&R^X$o}RuN{k;Qz40s!06*wpGULX-P zGU#xSGAJ~7Veqx!(qNB}ZXv@$=7jtbvOQ#F$j*?3AyYznhq#AO!B2y?1a}G61sx4a z4$2N35!e>+bAUJ?!GE4ll&7^D=~Qj6u#s7H5)Khq362n*i3fOR{5)nxhoD=LS8N00 z$|Td%sdeO8%X@RFNoQo*?Am-P{u0D8Xv01 zYn}AB4Fj7CTZXlLFou}J%sOKMWNz6lbPWp4Y?z&p-Z`wgxD{YPD zx#qg&w&tFuRHM~IYNu(hXmQ<7x&mDn{abyq!9j1(IH*$__bLnx%VdtyJ9Yad^J`bs zEUR8rZB<(<*;LaF>z9i%TdjA;JUGNkQ|v9I|bi#N4}{=)bpKca`R z4|ok>FLV$^SUHLpT0gKG?(~nVvu78dpZtdfrG%Qok4ANh{UhF`!+_*dDYH9G?v$T8 zFSRLUdx}TO<&OP3<|i*s#*&XFxh9?O;Lu@jB9$;VAwNDf{!$zkH#;^nCM;%GbbeGy z)GrZ%p$`H#`6YT29)&J%9Ut4hwXP7+P|cjoUZ-Scj$vpFwfg7!9r`|cQg>JvraPl` z)~?r7s#Dd!slGJ&G>%aoQDn(cc|b!i*?8&v`ki%KBsXgJNJiJ|q}vnlSms*lO$&|gZBJW{G%sjcU|6g_pv%;H09RD2 z7pqaIQp{HMR{5*qRLQFOs$i8sdAs41bXVP@T3z+1s?QZam%EkKl#~^h77<0=3y&7G zjFIug1qVp6ng+?|9TNtZj)solHG=(a9xNzYjs z3B65yHujs?FTL-CJ|lY1?X@&xTl%G*Z+cXuS-OXGAJT0>*Na_lbk=vOPxbD2ATc`b zW7LlDULk&gCf`OcwY$N^(J9t`md#eHcLEF64>>_AEfY+)T01tM(T8jAsQM}O4Sz@% z*PpHbTwhzStk18%U%$FOrM|vyMV(vSZxS!b>e`L9W9p_#6B}6hIc2iyn%Y-8S9eoi zYe1WQTS8mo+JcP1rZBUuML-{*}Dmc`aX`em(qk+t;;U4}3lLwd5=Nb#31Hf*r*XWt9~Zt2MQ~>;I8Sy64)<^OBF+-z7LS%rlaRc^9`f(LMQeigV}rU7vL??4e38 z>Gi13?0&8T{upRGc+rrjL&;%*Bhp6Jj~F{#Idtj}(cs?(_8pMiZ(6U}JmV$wi6#wcXl%M%xq^|+)`wipp&TQHi{IN zWP|JJ+P|u2RwBSKHWi5rBMO%mZZG_+(6#thX<)_Ns6LRZ=W|YC?ytFRxqZK0%u6hmSENX; zG(1r~(eG{TV7UR0RZ>J5HeDRd&ad6ac-Q##3fdO>JmPKi&A2s*;ZUPb?tHFmU3XDW zzYNFTRG*xF`v-I!ls&lj(EG!X5kp6A9CdK?oiTrnZ64z=dcw#1Wpq>k-;@ za_Yq-$AsN6ZjskQhXl3wJ@Q`WG0-*G$;sZz#?7j;U>bIbl~Ea%zl_4xbxo{puf|jL zLNT>LEWIyTSd&ntDt}tKr1)gvg~E)YEk!QH1B%5ZOUryJH&j>DqV-~#wBeFsSYxf~ zCr!CFU4P7=Y>IFBx%E)nMdKaQRr7C_pU7B>re4tdnL(@=d4g<5dtqYy9lo6yAYcXg z!ZX5LLWlK7ce4)6Z&Ww3(5!F!p&8egsq2)E@Uyzq~ z_3P=}2RSX-qq09hR;|yvl(jHxNLEBvWR_!Aa#m{AqO2WR1z9!OjbHN$UKgJ&TUpt& zrbTi=nl68>9I5Hp)Yf*8#E}WaPOIIvzdCkyRe7xRsq{|`o*K3!a!O2Gd}-ps*+X=L&JGM1klAlsU$*zbUcE9ZdLHhP zkS6UW=sGfWV^U7MZ_MEcEc9$(x}VTH$NhrK0mrR&e_H=7swBkNK=wFgG5=(&Yncr7 zu|*n7<4HwQLs|U_Nypj)wNZlYM>55y-d z1bxPiW^CwO@}lK0^B<-+#)`I>wuP-#ExlX5G|z0lZYb1-sIMwV$vtKE^+}TDHFZ@B zD%~n7%I=rmFZs7vR^(RnbK#eQJ_WM;&G`fJjd>&T|0wKUa-*zA<<;sy$%gtInUCVf z#mHIw&`9yQ+o7Do78<_x9wdIbve@cbf-h9 zyHnP6{3&@*QdkE|!l(GNabB_ckyzOJpjy9u-u@nSE@vG#*e$UhCt5))LH}gVl0~Lw z@ZAgawVEE9Nt!X5v6^lgPfd~fh`O^nPc=G^t(`lEG!)}NLwlxHYg8?URAwJ&vv;8*m`X|4O(-WhGpsg|Xr6?M(xVZLST+E&?e zx_MsHc*AJ@T-_0EnI=rLTTQ8UsD!F3jcJW)*-rR;@M#4wGCI?)|+F_6c)8Ll8#s( zAq$@v{xK{pY+GnmNJ7Y#;EEvIpizM{1J3*J_0xFI^{{q*YFpD<)6%i!w`L}`L z(RI>Y)Y@xzYsA0`L)Cd|ROe}kX!dONFgBa|m?xWlFce3!XOFGX2i`Mvau z_ut}Q?H>>@E#S9++W|!Z%>itHeV}!qLm(E|98er^BVbuTLO_lG0sln*0>4>)tnY4L zk?%eqJD&~SbzYsl{_?E%2=kcb{)bzRYlDlmi=A_*({m?xXBVdc$1sO*`$)SG+Xx#^ z>tJ!LRj4Rj7$oo`eDGk*A9Y8(*#IV$?nlib*Mi+&F=ZPo+pspj)&VUmny)lf7+ek0 z^w)H#Zlw03CPs5pU8m}-OmCQ3KUv}^(bs;ceO!C4_AF#G2W!_szOx#>ch?@SyCyrgO61R6kvsDeWjbFLQt=DTaoH@_c!WvPE@A zJIjE#oM|gC%{L!27n_|e<1LpgLUKC!mCT@?QmOPmbSLIDGm%xZ8xXf|Pe!EQo{W4T zW((Yfe+vhT%0wICxyV^@N9zjfg*Gm>Cv8h?CRkgnj)-~*Q9(Zb3VVc>q7SgOct3(6 z?h8f<8-#O2m7=azJFRl8gyMeU`Qqc^f5qiuy%@K)v2GC;i*Ji}iD!$uiG|`qt7BF( ztwOBoLbzZ+|spmS8G^6PB7-RbT?)})BI|;@M1Dpc;Mq@a zJP%@mf^Zbn6H+3VC?NhN?h*Il`zi5+_(*(!?K2!NA!>9j>5=(Ko%o^B3F>NNEV_+NSH%+ z7_}$rgo;oUwM9w9jOdVB&>8d=`V(!<%!YQ`m>%pVHkb86rX%-} zCL{@6jQ)elP$w)2SbVju7J@!|&3oOz3z#M0mGJTl<#z4QLPeK3l=_&M3dKf)|{(+uN zucVLA*Xb;}hNfwECW;x%%wzU3kC^k-!`6Ub@gI?(qG@)c_$F*v6ys;0*3V^d=?~^NCf&a$*_$pGr(1h7i4ooF*k9O0 zED}?ow*grZs07)Eq#+V^Hygu#WtK6)OeKASo_H1&}>NzDXgTT``w>|^9g@<*~i z*%>hHNMfYYQfJArOSJV+@AkVjRU=u^Q|Zb_rVx z2#*8}zeo3>BT;+wBiML2@{wKr%^q(s8=0X@G$UYI=?eNQ{gS>#AE&p{3joc7=|S`m zK=)#LKYfXQ3pMdtx{}$whJ+w7zcWvObms6ccKM<4>+HT_rUG&YV0<)0UL>VV0GvT z@FFPsFZd6NJ@4?w>S7>n8C{&)o50q+DB7>j%22ws6b#`a?K!3zDrf4oGu z0gf^BAu*m$-Hyv7M;HS-fQjOoXuFbROyK-hxd69)UK;3cLq>zO~8>&!DI zm#JqMCYT+>u4gZ^6|llD9r+cxiO3Ouh!U64HgpioDvb@pPva6ih?qodC2m6eDJJR& z6@dtFfsX)I8VOPa;etQ`LsSv}0K)qcE<_D@k6-a_xDbB^ktY>vM$bc)u@yN6F+m0^ zz{1&A%nXKLuG8aaoPJC#1q2gR1>o`^_~vnB1{qG;k{U~i<(=gXY(*A{1p!2N0nPsk z9_0miXdx9xjiI(s4=62_Krf?T(2mTH%q^ysNoUux57;&~0$GHdKuQoCO#&~w4fx3y zR148H3Fdt%c;&00&kwM@1D#*P_JC%4Va}l0=irwYf>(AzE0K%HOe6}aWdC9ZvyN;b za~oEIY+x2KGeM6Nm?_LSW)AFcV77waIK})0(cvMJ!_EWKDcNM_3%rTfw{>NGW+ za)cB5tvTU);w2Zc7Skf*1;QwsPD$8EWHOnhYlf?}p;x@oC zOAerpQzmL0{fPDk+o%8yA7UHXcw_-`1F3+x(hcyk5Q2 zcpLOv0N&bwnK1&lU?ljCYAh4GkNplFxf_OIufWR%pr2v3f{;(_Z)|VYgOviWyu|DR zy)JkBhz-O_VmA1Rp5TdX2|fN9zm4y}e*)cl z;U+8xy9|D9Ah3>dK*eNWM|sG8;5H5H0XB^N$czF%v78puHvx;*)JO6+KyN5XTe25M#?h*LyS#X~beT{C0$Zd_5fE~^SOY{RKSIzzlTHDU90i^!KPG*OL zCOfmStRG9T2&-o-35a&`g-|A<)ws zOoKV$aS+FT#&?3ZeTaX-Yk^m^;v{YXZh;XZ&@xWY;HmTRe}H4mh1E|Y`~$Fy7~mG` zP&f1gG6_~QA7&%jOlCA=rnf<6`huE4`BFK6-=Dw>IFYTEYRg;8-*Hp}3Di`i^;vV>WNSbnwKwHPcZmjO>WhrWR5XF@%&IIKT#(W%%>Y#Fv1+llQ1^zOk{V$&cp z24N;N2fYYb>j}I$7nt=}#1koJuY+Hm%BHh%fM;9QniaF|tT!73+KgqB*eEuTbz*VW z0=|M|99UO27>;!S?GIwd0k7D{J_6?Bg!DkRKs8Q;c1L%EZ8<`eIE;P7Oqd76hW_|C zd=@?jwyF3ed@!sXOT>e4U)&wA>w&v~|206YegJ%8HCSV3@NRX$DK?<}U`5#%;NK}o z1-lt={D%3Nv1abkGie`MLS3fT0eg#~oTz3}N#>Ft$cMm+FOVn5Jy08%LiQm$K=iXG z8HjpX;1zm{(c(fT0HfPV{!P}C?$kKy9Mw#XpdZry;1?NYF07pk0MGO<=(j6q_B8qg ztw1%X4Hkm+0M5J?eAr815i~^dM7$@AW*MOQ68;2#i9f|3!`^v7$~GA1a6AkrvA19Y zJ>kjKb@1!%;HA$XOMxLdAu2YLy}|AW#xac@20k!~^#LZW29K2kTPA$6nLOqzlh2ef z3h*5f>}VMO1Mtsbz$m~$O00pXS4mh1Z$YA|!S?23-hk>k!0@jE+pGsR>&ZU&hU&k- zGwY}e)NCr5vZ3l>J-}&l3!r-#*@=uK-AQ<0M|wkMk`9b*BYBC;20xraEuzj-6_g!4 zoZds1(NVyS3YbuccW+oXWE>zb2ceKSbOO2w@c0}pLmSXm@EStMIXtj%tONMlzM%1b zu$PE=Vq(kyjQSqL#vjmNR0s337a0bAp&a~sKUN2vus0(I9yEmZq^ltl+7H&#pNga$ zD1?%dRlx2)lOM?}z(_V(2%JYrnn*t?g_=sO1vx)LJ-GwDmVQFp0`EA-$e1qdPQY|W z=%oc2g+4{yu_f3;hzN18_HhSj`xRb_tANeB5aGb)y8vtM27WmOJhKZy;zi(@*WqIT z*^Lmd7hs`S0lEY209pDjBpNA&j3<^YV|FlIm^%6}$ZSnlP}iuP)I4f5H4GTsKq>>i zd%|~jDwRs0qNrFZ5q^!P=EGH&VWp>$3I)!0imn9Q&H*lgvSZjg@a$nB@&XA4`~HC1 z12aE{eZ@q;$tHlOU5Bp(mOl%h48FJR`f$ag$UDpFM`$(^$<7f-@f;tM)^ajb?Ve;{o#0ksQa_#NsA z=(LcMfSj&$5=gomEKCO|m=Ad$!A=10Y+*+r=YjD|1z95@$7{u=gAcYP1_L(l5~Tzx za20eEbQ1Iv^bw>Bx(GrA-U33v5)@%1SfUkb2x_P#R1lef<{iXn;L&A}Lyf?3{5Fi7 zh5V)`S_-}rM-H+6>C<~J)_=G4?)f^R2?ugGeyw$bRZoM4F3nf z!hZU1$XVOyNXVowF%3*4yBH#n6>#qthzGg_GW%YTHw*A7_`kRb?+G=G3&b<1b9@Ae z?h{vuL&SPwB4EM`GQ2MkUuMHRD1fC-15R879y=XvguJdJQot@_MeHSDyjk=lT28H| ztf&)^@8??9TVgD7^F{Llb0@QhnKZST)Fz|JVzM&_niI`^%!AEi&C|`x%}31tLL70j z41_x#02@kx`0JMF$2&O?zNk*I}#zXWz4=V>gVWT0MEI=7#58{oShML25rW^Aw z-HCn#9_<70AhYG9rN6}t>z%fmC!15uUS`59H%UxiOqr&qQ2lysDm1m4{LG`wznhE9 z{t&10Eg9r9vJ>@=8bBA(bD0)qAL|KzIR$)KcgQ`v0M7{|wn5D+Rj@+vNKhlN1*v-p zM}ou?gkwSW@xqD1dBWMkxxycW8NyIun;=iHS1?{65c~_i*^D0tUi1o(+yG2Ngq}na zkuU5Vh|W)##Y{5OL_emtgTA|hEe6s-v>$w8=oF~9Oo0qz8NHL<1ey0)`ZZlm3!px* zl6e71AB@|A+M{)YbmvHKHXNsOIFqoLB$6Ul~nRs?J_m+nV@03VzQ z6|4r!5lfndG`}z(GA}ZZ1MJ3|Bh2ogXAiT#IRO6KgT_fy3v4=A)vo!r=U@(jYqyy{ zo9!$UEw?RVau#^;Xs9FL^lDl`PXunA2(=?Qn}DoE?g7ISpdM%(+6Qv_dBF0wqNjjE zl%f_i3>%Ei115ACW~K&{LBup*O_04l1-~;3;!7)f6C$4rnu*MVn&1o2vlaUc>^_F6 zq3;8$TMT*eRH#0zf=bo}`W0mTm7v!uh->-uNBRx;wr6nN8>oJj!uokLZ3UH&G|0Jj zF;BnM;C8UJYzL@~$dKN^j@3|i*@G2gt`K>b;d}6l5KnHy=On%t(7pn;rLZjo%bWnL zsW09Okl=>1;LHDo`&K}mu?OY_GQUH20zX2a9xxAaN3z%*;G->&cPxN>x((_Um*^ez z9H?_;(21~xLlwrGcBcuNqFSj&N=GT+(+axuqtoc0=za7}xJOOfFddok%wFat@aewn zVW_esKy~C5y!1B!V%Q7FnEbE-u;TwHu(e9al^mfKJsRT60g(R*7-U4=8 ziob=r(tfDUjQ|el3|UG!yhm^f81ztx=~DC-x(0Z@JK6+Q-CMwob|c%7CBXP6K+Ne4 zoG%F8Bxq$D*iyCx>Vp-aMHM&`XQE~fHX%hR z)ENte>MX2v1HSGC%tDLS0Ec)D9_%)HAAJlSCKD|{zrwLsz$}hH&M_8EgE~b$@asLu zkHBX=;oZisz|(fHGXd49z(3tt8>k^RLEik8`N+Iwz5qk3g6N=vP0187Pnd&Hbqj@9 za}BbsWYBd!#Ovdb{ci)Nv5{H^{(Ui2X}HfyY6G>K`W=|nEr_P?sQ2GuQ8rad6;UPN zAq(JB1fO!?QLU61=5Y~y4=Q}AQ1^QWT(u`V8@TsL$oT(ccd;AUrH~hm1y0@p@)0|h zVx(Xn4aUW0Xv1U@HaZv#gh4Sd)h zayltf$LQc~l+Nrz_89wx)w1pobGjoL@QH(~aYP5BxXtc?`RNH6i=Q--+gt?sL@B%v8chs@>fnrT)z}V%KRg4ffhc_pINeCdL+rq^YQeTXK?UP3+`9*x z3t5K)Rtp;5j7~&jpo(0Nd;p7C2QwLh$l049tv_4E9ED0$EQ0`E%fY9W!M7APBi#zM zdIl=V5WZ;^zAdz#mVs|8g(|)h&Wag-rUO(wr!yNMZ@S3bV?KkP+ZcDKN=^cPdx3q= z8lX-W0rM1#go1w(AWgui--BMxvA;v@WF0%7{SiD?EW|Ve-1`_bzlIqDbyN{k1iouA z-I*5AU%@&SLyqSI86H=m-$DKWwVp7@H)soEFxrwdv)CwzfG^?6P$yIY z-eW4_j}(IM91fUl0xNvXTwyLV7nu7H4~oHeNFmc}1b-oAtyh4HE1|C9iG+HVxhp}H$!FZ5^RT{8b1*a!1a)A?t&aO z18mX?BcZ-u1{V1heS`js9)LV+6dDT8pB?~qeUTjS9Rl_&Fo|M_r7?6CwFdawU2-_o z+D<{0!qw7XehIwtp7|A2DTJ0zmKkvTwMA*MSiDI;$g6upe*FtnkYA9EWCv;mR5x9L zdpxEc;AxhWnFaPT8YxGHLH-;J)k87lO*MED@duF&%sv{P{q+~53E~94f;OTW*xeRl zCUE7BP)FpgpZ+ajUPC?slOhK>V!PR6a_dBbjqHs-n;rX z_V*bSG9+NwnBhZ*jU4=;f1f^I(tk;F?^>C9CV63E?>N6`cvUb&5~%en_pbJ6cGWwH z9fY=i;zZ#@{03s6=aJQ>MQ#2qIfgU3v6?O_XQi$|BK=%fQkz>XuVl-e%J!EIDswBp zTfVjOK+UYWNZDh#P2+U+F6|Tjqo(UE8`_dhTJs9ho<7Ww>{9dz=1)u&>=xb;{bO}g zyb;zz8*EP64z#Pa+iLIOaMK~p@jX0iE_Pb&e9GCu>4SZyt<2hNHBqD^T+kT$keO<| z-?T-yNqtHQ?^;MNNz!T>DnFOMEX^r)D4JBDh81Z(xy{)xzT{_FKKJ~5?UU!HbD7;T z)gNzu-1%|t$3Y(_!KX`R!RMmv-Csw*D$M<*NfoE6=GT6Z-d7IPmNxY@E+hA_JMc9k zkj#FAv(9b2*CyY~0Vji3g#|_4iyjbnB|+68ykmT7Y-dTAi`@pKed;kF{YHkRS6rWg zeRX{zdQZ*R)iWo}t$W|DyE?y3wRG&C{Bwt%@fA^T!cGMp^Lyx(>*nS>&Hk3PgJ?7E zjGU%^F?Vi@YVM(*pgGg{RBk7mS@&rPrYP8Es%$F5jED0>XSlLwFOR@-7?VOcgQ0`O>)dc7+={q;w zYqoCP()Pd@U|wU%CX?ye%%AKFM1p$Z6CpPpgug+j!yA4w`XhCX+-})u-fwzkq}npt z4z|i$7Psta78?Au=T)A{e;YPQ_tfpI&8qgSx>~WayhB-f>4=gw#hFEcMV|`?7Rn1Q z7mO*0D4_Bs`AEU}!sR9X%89CfYDU+UNH+p1PO9T|Hw@`5uiLttpOS9OM&v0LC_u&U z?b4jjxL0~h{pGo$wQY6|8=Bg)ZtNE zM;;t;c=*9#$A{h;a%k}NK`RH&8Ze;0rSIoH8+v!`RhB-xr$dix-Q&Bx?b5B&z~q$) zZ)1qag`rr`WxomD@g5dfT3&6}VlB4v6immqvueu6vd#FQMc7O?*)`iVw>3$cZa3|0 zn%LysL>jIcwi*V)S{sx8nf`bE6nz)Hi=NVb*4@_4)E(FDGkj>i+`8TvVSZ*ANWG)| z*;&Xw^fi`=*AmSFlTctqTGflg#XpH^1yczFwjb?`R4~`*71SuQuO-49XYw~Dwe@Ws z)3UJnbd$yqV^|LFr)TIMY8PwG8kK6GQqpily0PwRZB=#WsxuV{u)sOv8t0p5ka^8)gMhQV8QIg$BMsZl#3DVO?aCY`5J0wnMF>z&YF{ z$#sC+0QZ3&>7I#RZr*j?`96Dn)BI-!y$`(}@jkLHG9;>3)W)c*QAJTmv}1I9bXs(J zbW*f;v@xnC>V4E@SdZ2%${4vnvUj9D;#5R>gfe_%cvSe8uvKA+VP8TQhc<<*3eg3x z4VDG<54s=N5HKV_=>Od3llOG*Q(kUfKYBj$u=5z^e#GsOtI5U3Wt#H=r`L{64nYnh z?U&g7X?x8k&sr{a7JFKGi2Q}PfP^TW1GSUG>}F;QAaC%Ez^rYF<}IRGC+Gt-MfiyS%vMN#V=9FWIV$5ozy|yC=rP9gCSAHR9KtpT?hm z|H}C_J@QSYPt=B}tf&#uiP5uT9AaO^Zi?$4@0y@Zh)#@4icc;`(WF|Xx5^lqxhd;b zc503=&mrHbz)*Oow0q_C8h7VT9zq{G9opR$XkX?cdgWPj+jyQI9sImKId)~U#yoLO|xTC=bK=iSd z3#DG7UZ#Kb1)4Fc#G0j5^(%5qZx$aa+?2m7_gMDF%(C>5w9P4HN&ibMk3SUGDpnS= zJ|-tND1KhTnZ#d7g~@`{ptMfu(=!%i&dC~=-8!c#Cp^!yXmshUirLjYm2R3>xkD7W2bGt=7D%HMpttW31Q)I(oSBUSoTcR*E8RkX6e8lRlSnZ43O zQkx|YO8h7OS*&}^p{U@<_@AeLEcw3t+meWL;cvgXf1U7U@R!Q3nurhIU;Nw|IU(96 z_Euc4gqw*kleeTF&K{KSUVOEzY1N_HQgxtysA-yLlC-bF%_7rkn{6|PkM-KQJavY7`aPt;vq2`ORcuomvF8oYLZX^NG!pnp|o;DRfw)9wDZn znE}bZJH0x)*LSh0=WE~3W|L)}Sr_>`Nk>tt>0d(!owcS&8C&zYDyE{QtU>9r;>5zy zsDw}D&dBMQ?U&UcGccoD`sB1-sR=2KQa-1gOq-OU$l8=0n7gUq-;!<>`s!t>T5UJu zV&PiJ9Qgoq3#+#_)9h;MRt%kPFXuY&ed7H&;r?uVE`bNu6VTvYSgFiQj^mFze@1E#9!_nR@&FZcB zJNYk3sW8AaQy;FWk4Hkos+97BrGFLAF8nM1bZ&fh$E=$f9n#}c52cJlo!vUIdBWKE zU2(5tEo0lo?2Nt{6%h3*CN!~gYOhSY+z$mqOCFV*s=Sq<8W(-J@rH1qBvm%r?4yN; z^=#X7_Ti4PPH$Y!xsCKNcy93)`Hu8EQvZ{GNcy8+Sc1Bz0d#Y_?gxZ&8C%hl;GKYqi7GO6?|t)-*-*Qeu!bF&ktt+N!;c zw_U#damUV1sm>EzBi)*NZ1nu>rSR$GyVCD*{XG9I{^I)QeVh8!cVlMQ$@i0T6WtU0ChUp771ufLS^Sg4ZOIE#1Jm^x7qSNAB=uqgT##fp= zXnMKXo917dH*PkoNqDGxqm9A&K_dcF0!I5+_^tHy^?B)8<{IL(+g@yQz@i=TBuep0e*OFY9|P_*cp11U z=)b^+0fqHF{HFOF_bT)VbKmV+?%dz$iKCsv-*(A1-L3CfI$InwlPY$|l#<=zrNVcH ze9c54O3s%WNSTe>zQHv*R)%(L%2gUUpz??DizAYWt-)_ z6g7&oW^K$rns>6eVj;I2f|mN5+N(t=z7?Fwr5-KRG)o*9{%?Nb~jiN zVD10SZ>F!m&nK@np1~ezZhKriyMQ?A^>tJ@e6ZVS+to&B{n>J-MPGAQvo!fzxafN% zo5Tl1hlNiCZ(*d~GknzksTx<)qjFl=w&I@!E_w5^BQtiVe@!>f=#;S~<9&vEX8+79 znHialvX*Ck$O_HglC8`hlJhnvH1|xdTizkOjt=J6FSuOLsPIZ*qoNx{jf*cA2bSC` zX$ja&N^(MZBWD z*)g*kvvKD4%o|v&uqd$@Zu!o#oz-2d&epH32iwHh%(hk8ZnO)uzhOVzA<1E@qlIHL zdso|0)}t(YSQsn>Rv)eISueF2ZR>8AV|UGdnuC|)d&fETs_U(Bs&QKAoZ~#i<%Nr_ z>nzvPu8FQyt`2VIZbG*>*Q>6}T^qUPxomRra=Gp7=X})3P;YU)2**2)W1NP&9C5kk ze8lN|y#tP092VM7vFl{(V^eMQ&T^NnvSowb0NU4S7jc6WE|D|!3p}sy|dr>o2 z-9_cB)Ys%x$5*|od|C0h{7u;pbh4t7*2V3MW*4p~xRD=_KQr%nt}J&+PEz*R>>pX< zvNCYo@yL9au`Z*1hDAnd`m^-2>ATbaNnf76E&X`U6h^b{R7=PG0tcNoUX>Hpc^GN66H z{{prK90=GSuq)tiKJFYK4M_1n;NRZAu>Pj{UiDA-1^ONK_3_={BlT(OJ;}q%wYFY{ z{b!qS%eQ6@a$i}Fbe_~&x?G~AOZtG$=uoDqKTRT217knKQvEqygjTHy*33c$8=+L! zy3}^BSyX+ZDiSZd9u*7AAD{tuDGe#Sr}QluuP|n#I~@y3@MxbQvUS znM$a1tL<2`u=+jPp;?u&6}>Aymk%pXD4SO%Dmz))wbWShvE+2g`jW*ZV@ihdKDuO0 z$(0gyN$b+W;7;xli3h`$IR*5NjMMh!%Ectp&3A@_mXB^V3#K?F99X zyVST(@ZREgIN(sw)R2~qBbqJ_i*MPdO~-cQ+f}u_*>*%*=eD2PtZ37uO;qcRt?RYE z+^SKl+b#WD?rtG!F(&L)b3yZ7&Hib6r%83=fW~7&cQpDClG$)(gF?R@-XR_dE_>>Y zv2SkUYN;?2$*jcgg0_aq+C8eTHMUi?6;CRbR(Mw2E$>tQsjPe1i_#XQH%mN9wijm< zbuHRcSY6P&;6lDBZ%p3HTzyW@oT+%&-OE0b+cRHZaJ(p_5WpIlG`W#6`vkEB}NqeJo3P=jX!t(xcj~2Tin+28&%+||}ik}El4O(r8dQ9!{s?72*+&`TP?&bE%cFO9V zwKQvO*5RzJS(~yZXLZc7&Wg{xl-Vy+m9Y@b)tanRIh*p97K9en7OyHbmd&WRUn#C` zT(hKhuky9(m%2bB)rs^W25)0WIQFfWIyj)WcqKV1?I5$2`^yhVKZ%=)jtN?rej1MH zC+S9L2W#f3U#Q;I?kDfwEGaG=m7kciEo*$nl(gL`nMwT;zsE0(3&5KwA?kZ%!mq-g zmOlsmIQ+f#+w5<;h|3WRBl<`5iRc@#hL8Qf&Hphr(j%rcZcpOSl-RTdnQ__k^RkMP z%a7Cy*8DQ`7VVY2H2-9M%6^)YncFtc5}$Vd3j_ZPJ`{SW>0e>}TUocg)4p$~=UwV` zAJuby@4xyE?(aJA(V#&?9t^b{-h0HPkv1dO4A%{vGekXT`M}@-l797j*>`K&sc*Yo zt-d$!(ByhZ=b$wIeZIrI9NdeYzc@a#yKQ~n;*R2;^o8iXDNSFhaZ|Og`KR)AnPbV4 z!sNV>IT4vN(rZ$#CJjjNiA#uH9CbI!BDziVnCOg{t?{-=D^k9vi8F(y+X5B`%3pqUu>u}mYMPeUxklFJH-E>X7-m=$ZpBk zDMHMo=6lT-TfDc}jdNU@r$0e_PIEvARzB>cFWANwAhp{NqGq#@ta~* z(R-u%N810g_$mF-;QPdH*CV_lu7>vwmxX`(diCpxuSdUL`WpUK`?V*#-4H$`A~B-T zx8vVCMTruYrRHUM6?83aRb``kqU$8MB*|CM;6E@u z(_VGFMq0JJTvmFxs8c~f?vd>NnbP#|lohOZX{>WhJzQ0*Viv^SiTyjSCgEOkaN6FC z7gG^=%Kc??ikB9g$yH^|$*@X$ zm3%SrRQ$(SQ}o=ZnqQZHuKV%N_hZaP8b_Q6ZyuiV^~l%7U;BLR`?bT@v0o>A-NULU zetjKr>BoY|<}rr2=ZULR0@HtF&dkZn+gLQeynl^SeE|FKF|s}87pSntk#Z}b~5pyR*^1FHJH>N~Gb z!`?AHclYSpUE1wwmw!8lbSm#4Za=B@lCaNBOd*qlUiydm-t=tmmgcn6VWMqs+{Gry zS4mzA^~T})$C@D3jhc2F=R$$1ZHl?e3Ds_X_?h9t65fHmTOiy z8uDwID>6rC`eu4(#%8t19g)AcaA)y}(hcR)Dt)UXYGx=SRh=|zv@dk!dI`)=Z}`_b zVTS0f_@Lx}(#A4x6qlJYlVp26t7o9&t*ISZ`@E)U&7$?5i?$Aq4fdra%Tpxg4U z>$|M&yrI*UjypS?Z~v&>kG3go3R;)8DsEZUA};Jp^Fz%RH?7~KHgsGgtA?!u&iI;n zopfvBob7PLcDhx6^Vafyl5xU+jZbw(bwA~uYF)+lGD%s}vbJRd%X*Y`!#h`1daraw zscq@GlHii_#XiLwi;4?-7hWzf;r#k3uW{bqTtV)XoXG4~Ial)L6f`ROTs*%tqik5k zjY@r0r<#T6w(k(f!!;$^{<=>Z8}(LYMa>4}VK*!OEN@*FRNAbhZ}GySvxPMU6AL2q zC+92k&gYH5EvO{-NA9=W@Z7vyNnV${9l8I@Zk5?0y?^R~WM$&Y1lRbeSX_*wuSC6# z)cv{<`5`(sHalLMXq8ewEi9vJR`;CVd7-%LYKpT;pO$Z}99iv9`(62$I!OCcH_1?F zoFga@b`sxVax-4OU(wzCnbl&uTF0p__uR9+biPIYKLd9JcMUz*q_D}xCT-A>&uv^4 z`WG7VEsfk8Z3}S<*%)kUxU^wq(A1##z)pd88~8Oi5Mc0M3bPtY4yQH?1Yy~026V017&CNK{^1qU}n zzf`YAl~xMZEJD*>b5dQc>ZLlYOsRDQ*Uhaygg;PTg>yx(^5tbWN-IhRm-?3%R-CCC zjiURNDn<1{`MLIEO>%Wkl?orBZWXI=v~j_OD7vJ7$%o?J#hFF(ihS@v+*3HQuwkJ~ zp?zV4!qJ7B3gZfciVhZ;ivBIODS22jq*PFLt!!-hp7Ns7h9xVCLJM;WgNsL%TrJHn z>sE1~GQX;I&3e|Q8sfFr>!DYXm(be_wRku04&KeY z-MppVd0wBr4to91%f;)9=K@bN&vPDKJmTCJpdLT(*3~V|b&0Fg^{9)V%M0hO&aqDY z@X&2n?}lTwLtlpg#~Y5>4t5Sb?dRDYv%PEc!#dNd)Y52SVc}#RY!<3$FYki0(+J6Q z@p`JqYl3j9MoPI-mSU9#BO`#nukH1CPklIm7JZNnw*iGmRyvqP4-Obo3bY5 zWr{=U%+wF5&C+h9wM_q+z9PdV^IPWHtp3@??1wpn@+K5cEg4WASf#I7ts1F*t?sG0 zs%fshq-}>1sV9uA87OKm8UHl-!JWJ!Tp;Qw)`PR>O6$vFWqaja6d8(jW^(gm=57|p zESxMiSqiLXSru8evF>Me&cefdzrs&`7w0uIaT1)2L!x%#67d#Eb6A%BWZz^h<$H;R z_KJ>7SC%UdD9$LJDZVP=6v>KMMTFu8x#+OslwvDy%M=UwnkkCziZzPQW|J)LTQ;!% zpUrLC1iN(m9EZn_|J8d{PgXC?aiPN*`=54BcB5@?+6Zj^uzqOeZuPfiE{<~7&8^M< zG0Ren#|^HV{FSVY>=itW=jc&y!Xvs1!?&?;mnqvYULUUQq4}&Dp;XnJuAWuZwlc7y zUikoG)1PI3m0c`*QI=JvE(sd7{@wnWtDC!!WF5C3Pq{nn4-6$M7~2F zEWa&lCwn99EB%6U>Kp9IEO`5J(IGS`Dd@-7!F?Gl=qG3en>GaQb(o+Be5k&BW(aQ+ zQAyAK&Fz|rg1jqVQJ=4C%fFZ?_~*CwTqW>;1-TIJTR2*UMxtq={i3(9s6BB-UWDr7 zAe`xI;+wF&_rcCt27`0BxCjp6+ZLJSZcI{-M0vyU$vb4edGiR~o~(bei;xPODCdQrR? zB}8YQO9ni!p13Hi5ZVjRpvL=#4oYR*22bc2IUx&GeJE9k?U3R@YZssAXz|MJl~YhiigV?SOMa2lZt2e)Ug!pCdFkG(znp?OmbzUIXX4L=q?+#`<27MoSIS zhIG5e!+9Uef1AtfWhK&A(mgQVn@ZKVe9uP(@s^eMfhn~K9+(k^>t1+YzQRKIwQFI` zwu0YmfiprG*f$Cd;%k2XWwPNOIFwsqh3dKZ>>30jiof{X{ec?k zqTUX^yveY_XfUqiR*@BWMRbDC{7WcD^*c$lS#*QbDZ*pmf1Ki9C;)?4|No)k*eW?L zIVHJ^rsJ*T4VvQXlD(2e=(GbRN|f*$;BcGpCzwH$xq>btPOuENV=3!;75?9LSfN|s z@$E7lf~UR}F6m;Jtbf9M{0AlD8Mu&N;WZbquca^~TcE~Ug@zysJ$qa93O8|TXds%8 zQqcuoc#^oIWV7TOQ~4lid)B)T>)lxzgi5BCXAmxV%&YtdO>U^9TKtx;?}iTh6Uy=S zXqwLwS&V$=k?1H-nmU>?U|PBw&l#HFHW8|SfQ~DUm=d5()*K?Dgu%wIM)B~BwcpDw zUxf$AO7$A`0rh2dxH?B|Nw;jM<}b~8oK_sP!?b%)J-g#b@fM~268%qoOE~iG#=}Mj z=KFfn4EzTCIr~vUe}0!|A}!f(zW6FyOLKHn1K6{xS<&N?Gu(M{oprv)pXVfd(9=vn zp=K{hK}oR$y`T}@-Ex#+KVbGdqrqJb6W3CZ2xDM9yywm^h86QagPu?!{t7EWCOaV~(& z*iSl1Iz#%mbQRi+9lWpNZ|$TO(vQ4qf5~T}zN7d(Y+DD>U9@aERM>+BD%Q9sy5KFw zK;v7(ABH?uMW{cjYpF{^vD^`z$s^4wG!Qb)PuBMjwW~T!bxXBgHB~iAHBvQGwN-V4 zoo%P?!J5BRSE^gH*3Z!7w%4xFhQq#`qPvHpW;DJwE~q@x4K0n^iIUFnZcn0;4J3!X z5SRp`P_XC1FgKlAp9>~U1OMmh?0mov()LYSbyIW#i` zzjgZw?Djj7-RzKFC{FT-=o7>W@eS0idE_Pw;a*U+3EjZn8#*Hc@nHAHNUs#YcB<3ROL^?I~EmYQ*z z2O3B1Zul`PaaNg*PGY#hX_YY)&h#$$;~CsH$@%o7_tQmP|BbnwNy3qSzg8l53uIPt9iq;Wv$~lu`&{{?kU)Q5~Sj77t!Erd+ z2?Dji3N1|=&g>}i+yb0e{)NAL2xk2?6ccy(^Addev;6%Y-U=xw9h!hd?ujZyJyDS4 zi91OSp^R%pR#-1REIlGUB;71sDjkE0&XZM6BZ{3zPd*uqzCaSi>zamAwHogDabEji z^w%;`CO>1nupM3#bz0C;VwejGy2i%GaBCmJv5wO%LGPIXKAwk4T%nBuEw9&%CUQtM zx$5`C;dAPfM2j0Jr{1Zfi5kCnOF+LSMeoxY)!IHVh^uxsx^gdk5(;%Aai0i7zgcA% z0tY{p-8`J=`NmXbYRE1;hHH*Y7$*E5PCD1gX;H#7e(yqIh0q{$WZ$*`w|2x6p`)k= zA2mlqYQYMpu{tMEs|^-5Cgap2YIu+d!{82&6i!7gvJ*dx8?f+Sp@&Pt&&L|2$apl% zPej=wIT@=13eG=ah#x^A^-i3E%Fh{v=Kxsv>m>&zuVI0!BqFI9iY=q0g1sF}6n;)d zJSW*nq+X1s>JQY6p{U5y*ax%3f#O24`=6+=qxk8r!doEER2*F_O{Z}id5@Y)WVnbE zN44&PZh+2S7p^_4U926cZHErfU2Cn?YD!R_z0_RO?9{B)%qCJ#)=bwd((KTjLs^xe zDbrYM8xoJ#YoBOqwV}A#JVbXl90q-!zL8-edd6Zpy8VswP@W!!asC+fN+fLbLiV=Q zRL|7ZG|)7Yn0=P)_0yDM%7VjP$`jC#FJQzyQBMjY1xm0(M?8~eftt4o_p{m`cy4l3y#qn!+ePQucUdAQ;`=i3 zWz>fr_#0ecC7nQ-^FTI_nenAd6Qm!cXQXqatw1@CKt)|7cJRWVqP$)~Oy~~svk|Mn z)h9($QFCc{p8pZy15sF>z=K6YF78V_{@Z9q&I>a{>*o;osq1V`K50Fw#YGb zqe4`W-9O`wH3pU4O;BAVX!VESso)~ogC#h0Oab*S$Cu$EikDbH2~JjKLQnJ=op2o) zBb+9j&iiQLaP&-Vg)ljpp=Go{ZFD1()9dV(!jRHj} zI>5R|;H^w1AV&?YLmX52UT6Q( zuZO32qD@6*U^H|#9z&}?04Al;G!Ctl3spsmuovzvF8EfJiAUmzW|DMfUmum;mHv=M zNi(Hs(jsQqRnlr{rL>UGyk@FBN7_(Y1S0B5p5BG}HWr5cmSC`3($@pQo)aMk$A%I<|bIE)x;5Dp+8zDdd5jTNPYV^ zzAiJxlksyH4%%!0Li>f{d;+gEf%Dg1n95GK6kNe2C7ZM7V!VNSQYO2>9nI}rG(6eh z^UdUd0eHH!({|R5VzswvPZ7O-Y4bU49=i5;L~YeQ$8n@B>bW1dVXVb(X(YZX{isp; zaJGh{L7R-CbG$f4JcyXwUV2U{MqRs$9a|=ImN%CVkdKuAPd-IHO+HROTs{aUYX^B- zc_XG+=JHxur0kJwhisaxfh?bzwv99roKl1Tib%W?h2>0e&>z2)iBjI)q9XoAN5XkqS{}xnmp4@dtU2EZm!VH z(%0zM8C-E<>Q1#XAI<)0VIwMgH}K7PzQRm-x37DRJ^&u(cqzmpdm5Y zqE@n+6ShK@AnPpOE{~C0C|W6o(E%Hy7^vt*taGM!6eT|^|4ZIRE|Nc%{UvjgMM#%` zN#Bu;%EZgXRphE{aB(E~_ZFFQ4PH7UaD(#IZ_>rHb5Cg8G)L4y>d&efDqp&nH(ZkYO_ zWBvRZ5>q zx1wDWOK;-arV<|`FNRZ@8i0!?!SI`(u! zDO$gJsGP$+Wb&*VGh6XYTC8rd7!T3JV#T6#-5mKAV zQa=?HcPso0vy6dYp955jKhcI(;K^kps7Kw?fqHI+U>kK^l%N9tsODstDPYRK$*c>} zfLD-`TyaPuFCoj6<1aUpGm zgXU`Lse|DBL(<*&Bg{grABNkRIl8(c{5D^r+24+9)JT+dW|DX!$T*N~5~p_tNIxGX z-B3{eeH>ZDg5##%rZW0(!;SXF4~Bi@?zX)4TAVT-;-|6*caN@QW_SE4xG{vY`9OD- zEO>F8o@ zg+b_qC!vhJNoQP%e^GOMgXVx$4&knGnSRp=yqcEsYI}njZA7K`7u`myy-GMi*c}Y5 zp;P%09r!IeiVwh4DdaC}(0+Sie=z=hzHS5WYlVw>V(UPlC+Qo$6UOtCyy*L_<0quz z>e2$G{XYB*(y0I4&>jwv%mw5B3(DV4EL+CIQ;r<=pCWN4r%!h1}|cuUCyGTcg%U=^T&Ip4R@<`tx;9bpjk3 zR^i9-fNsO>mzo#Jm%XuqAopp(HeXXr^JiIJK!HNA7{+% zC|S4QH8PTw6^Wy%3RcjM2u4#|LJ#^gO8u|wjye~n5URWhXrvd5=HcitQ8b#r2csrW zrIOo%(z&(JN|?h=*z&uBAAkv(Ws%2J5e5cIam;He5kx>TgKL zNop!iJvwsM3*CL)WgMpN^K?>lO1!*+a6}mmhS>_z{DAX<+z?>sjT_Ad6sl+Fm!0Fg zoJFtv3dcz$TGat`kG`R%o{r0^jK19^`YK&m(J!dQ12`cw@K-#^G$Bo7MUc1jgQNwKb}RMMW6*M=!TJ#5c;$+rpmaxTd9r+Iw&WGu9_QRi{glgv`9x!v*!PDu{ zucSuV1=c>yZa+^HJ4_vBbf?DPPZ(BG4 zbzTNjOheH9Hw5VxljHxz<^VM_7zst!72i4bbc&P>NjME$j@wn45z5%{( z#T59Su141wboWeej?3#;+@o*7VYp!OrHdMdU&KJ10!M(0J4r4|tfY(ZZV4t2Uzf$m zDrFj(M6Qz6$O>fXvS`_7*)!P{**@7C**vh_B-uE6_jQgntz@2b?;lEMOI@UI>2;cj z&F#c7#6F4W4BkbVRFDmc8mpL78-F`X+{V-KJyAo#I`(J1SJ6>EPyh23_4<2u>@~W~ z+lVm>QBbXBMc?9;(T=SDL*PmLd&TJ)%AUz3+HE3tH>X4Yj_HC3PcV}t7ybKk{00-~ zcQmFWcmyY77oPeJH0^`&*U+Nne#rTlhGTL*PpPXZ1%x0nZX-VIXWeg56D4S;)3slr z5o@-o8{!DQM>Ss6L)DF3y+CzB6|eGS-5<~a8lgE(=hzn9w1ixot#w67ejbcJ2oEf0 z!+I*IfA}5uqhoq27|*F+&2(ok&a}5Aoum=y(eBGE<$rK5_FegF`78cBE?>jnL#R@p z$~J(J8q3UOT4^fy`JVI`&!z|2FdS6cjtG7bx2qrY`^)&f1j5bCpSI#3xDF(=1qYo6 z)SV9cG~ErV`*pfC)K3TSJB-ImsW)ih1?b)xRPqN|d?Vh6cMU)Adhx?qb0aPjdB*y9 zaGgLuE(ay9#>Lo?>huGij?+-=$J2!k;Ai(>M%9@4j6_^Yzv3v7ESMN~1+Ojl-w3TI zDB?BGFAxu%mH3FfF-*p5Yd>-2wQd|PuKTr3wBIxn=+CcY7V=azU)5Y?sVY}yf-cjQ z#YzD;xwHjk?omal6zXy6Q|dBi*=IBo?Hv4dyWq~|NZpd79|-1aj4Od3&J`|Hm1e@t z%y-s`WJD5YFoP>GqEwkNoGkYX^{xvj!WAy%ZzZ^{3b zx06>>OU;wDU{~LiPQ)qU8F|x=N#JbeY>$Xp`Q+aQc%ZFi?pMm(X$g*l&YbQy;Gh}C zZq(y?x+w1q*TEj!aT#03UO0&NZVI#aUdHt}dnj?5oM*a6$0Lwl%5f%BE=;B_<8;uB z&d*WuZx+9SCwXNM-PZ;9B5a~g-Oirgj?d0+`b9^?2lx$FfPzBAH6V-;_{wdkV{jUO z@KZQqT*im%1K2(bFSZi8oJyidh&C3Ftx=jr8j&VP{YiaSy-&THnaD=HSM2E}_ z9=?_2Ke4;`A}%bqa8r39=#O{pR(eEb%u`o_ev^8FF4jl z@%{~u_NU~8d$>Qw;dksJY(*ws2?BaX-p>_g3G+d1FNKH6v<>kg+40-;YzBS9zl~b@ zl7)EUSm>XCM}lDAywV=gE@1xG5u_5J4b+Be!-y1}=;nve1J!7fH19N5*wKgSdS20d z!1>IH&cR7u_c;0=J;<)Z40(nf#xUkSM+AL^1;UlMHO|4;tiEIc^Mn$KKQ3Eqr5DJC zX;O{U9mc>cs;VC{GkHJxRw|V&xkzD87c58-s_wwTz&) zdO%)#gL`T=GpiueSkr0tb$fb)NzC47(J9ShI@SKStNb*O&R9IzI}IOf%tHD#H+Na&KEC7t+t%L+&4O+FYqn>!Ja7BiursnUc9q$ehjA~52mZUmF%n4 z_r)hK-4Kk|_Zy>}F6(X_UW7c~KLux41s$0ujPCCO<`*BBk2K_z?!o~olloCEmJ$Uu ze3XZe)MuOr|HH9#1gr`<&QK4)jl+dag<@eb9nEL-QcrT;cT$P1!})n39+*@4-G(uf zY6r$M2Zuz^xev3zFr0hZnL6MQI~2FwdE~^kpo)V`x}P!i&i-BBX~>Q$X0OSp zk8d;mUJ2qD1mf_*{ZwPr5NjkTm0hSsn}WxOz)_fDn#@z`|NCo%rgSQ|BX|t`k6N#> zQAS7LmSHPcy}zNep_!ore~vUP<}>$k2rDF~x$spTcm`c?eRMGzsXN0B58(lv1=C#P zqc^y3C{J4 zFXo!mu(d~l&0;u%gK$qh2U1C4vSBXrV=mHz&P#vratC&iqew?Z`IgwZo7!?E)zKEP z@LPKGfjG<@7De%VIuR#tFn8999VFJgRnrB0PfubCz52PlEylxlEAjL!)2-LI$7jG1 zw}nYGo(bx0YR^pOcrlWXk|W^J7Lr<~DC5MIxHC>-I&+Ka%p3>WJxnWm;SuD6*OmjF zV{`m;YnWm_1{03MS+Eqp$PM@m`Qz^S@VBFuj*jaA{hy%eLEL(qZ0VV7X4a8J4D`X7Xdh_5mmwHGH4j4|?t86x(!Ft68;Rf0RKqMh za0VM1;^>)y3+5KiNs!(MCVfCeT}vl+F(>W>)v|`k!N1g7?G48b_Qb(b<3tdg8?p5` zan&gBhP5$Qc%BGa%>Cc8Djk6uj+Zl8!HZgr_OrPQxD)K+@ zYD}W0U(J+bD5riP{z|jyjPEC2e5RTSV1DvMuVBSbvWs2m7wm*Pq%$^SEf3?aCB_AC zA^9Mc(?5*a@2jD~+Rhw85fSg7vKh8tA;J&9#6*@^zbbZ$|Y zz>HPNsXkBlwJ%fNY|g}1o^D%ie=Ns=@2X)ZJ%({~1DhJ^87#SBK!uwD*YD^}I1x4H zB<=&BS#z3BF*)c4+d^lW2!=JMk9URozcbFfKj>l3;|7UFbT6~0leS1Rq)lY&Wbb8i zc~|)=`BnOYD!COi5f9!K+{2P3e=9#KpDAxF&yyXKwU^~FzxI+or~hd$iDAV@;Ak8J zdLG0cf5be(On3>7Stb=yebZ~>EGAHQsQToFkK8-a629qYI`Z4t8LOzV9mbvWc z!+0as$8&xM=p%;Npe56wQQ(o+?2KMG|7KIk9u;bYL)p!)Ahcw0dn(WZNfVqhFM--K z@c2xkBYuo-RxnPUd+>xx1+}%tCHyfCSCg1ES;8oLO$EPm6>4{Z2|PZ5p4JsSKaE6^KZ%JI zV1s?^@IUke^gZZ&jNs;vrFbB&<9#Llru87pWBMCZ@Rgj^c82lf$!mrzyu*ePWuJnG zT&WHICZp%TxSa^v%;Yy3Lxy@rhe?1tctd9XE$Co+h|EPM(2rIqqFZFkyNPHM0oI&J z94RDIE}=84G(Dit;Ae^=?zaL3oi>cc2{WEpH}&^Uzsu=gx3d?#7DiYVR# z?6VjD=sIt9YkZ)?=z$Fc<<#Bk@D$|ZffxFE9Pz{1O_}7{2)et6$gJIoBdMH^&1B;M zs@2olp|Ooo1>)aE_o5R# zFBg7aJN!SJlAYF5Q{~fR`hzJzG^lMN8KDH$=Tf3$uD}w1$L{Q&#l(!Wcx8TP-SY!x1p;yH4 z)+Q0_e;4M#M%-u@QSnXZl=bJNS~Dj;$FBDqi&m(gVwQx#b0>vOx&6sGJi zP>*;0edE9kqDlvPF#_E8@8BZd2mJjLSO4C04NtPFk>Ja6k&tOqPv)+#x%G4ibEyQ0 z8=ija!2K`b6XZ%Oq$=LCq`#yOaJ-!lmrf*oB$>&LJa^$M+A?)L09p+ZDM3Zs*?TRx zWkbr%AX-5YtMnF5$~Z7f4Bf85FgOyZrI#7|z+_Y!k_<25TAViQVOOuFiroZ8zRaXK z9}HMGu{y@h5I=~EHNP=Rb9yObd7Fw4fcwR!`5(}>*ohMzpk(3i=BBmSd>OeG_@N#Ft9l>=ao z1)Rm+On}|>j(RC;EP%FZ&{gAyZer5r4j-^5UHmoln?K?@>IJe{&xELyZozCKqQ=;Q z-rP}o`32m)83F>|0_RIacG*mYR}KSeAWz{eoZ%YwNf)Z{_3V`Uzi(_ZlepuL-U#NT z4>Lg-h_Hwq|DC@331-OysoM)-C>$ee%_M6zWd8b$UFar^0*Cbk874AcIY@VF2;C7q zK8g=;HeCUtZp=MB(S{4G_9F1WC_{Hcd+?bXQM8(Fc8Wd`fA1W+*>#gyD}yWlrx{sw zIT0t5Co&lHb%IQFpYQho_uR+yc0a(UivfXG{GP}JfD*gYxgP{>8cU>JWLm@PxI%yJ zC&;^)JI0jo9IL1*vT>Pw!}Iu;h};XVfe6%+Lbo`Hr>!$OQlWPkCv;m9w;;I;iUu76hmZ7{ghpo|(+WCCC+)3|e??sk@Dct9_Lm9SfPhJM2>`s0zfPpR2&PWk|5N}crm z!SZv-4##k0kH)>z0W>#7Sf-t`;H@+ zrqAQnpHtj~lZzL5N6y)C>R35*ziCuS=V9N)QcwB9gkJvp2BgoTSa|*AI7&Oyy&X%u zIR!r=7BxdX`i--gA3c`5$EE%!lhV6P@&92G7X~A&?%tv0+?P}gRvZlD?gulivG6Vn zsPg78cdTI_9j0#Y4^r3SGgzu#F2hToh_QX`j(M zSC`V@q zf$!Y$ZXQOS+{y}Fr#tW(chh+0ZD!0bTa$r?fVC$OUzWpdy})EHj*6&=+sU%vw#M_f zNFqxV_YeKzu7-Db^gki9KF8_)BF|_lnaV(>*g;3U0deLFc=S(tQ>FB5rZd$jW~clE z0+E@%a<^MIqlxMKK`M$)+z^n-?Gj7%?U*#YWk-*vXQgI}zd_p{wy9E+j9d9<-Xb+Q z8YP{n`gp01gFSRq`x@4h4bN&ZJ=kbsZ3uUdY``1(8+px^u1sGt^;)LGKba1<00|x^ z0@Za?*0K|-O-;!*dzot`!>F@{#~1)-tRcIyDM+j{Y_Y-2r8{$C{g{cm(EVw`txz*? zV!pxjtPmcs51i9Jtn_s1!5QqhzO20$9V9LP_t$Ut>v?>K5IUg>p@chET*!AFc*fk1 z&NE#`=k^3xAcAf}hA@T+>P2{0bKqZf;Om0;nKnW-)3t1><^paQFoGi7VW2gG*EXDg zy@PJnTfDZTSn2EF(*OOwNw5?Ic>q>HH}YscCvG1oW-4=y3BPAa)8V@H2hY@}ikF#c z!QyX;)63w~cySJ1672^wd-w{LY)=MzO?|xpZ{WeYzTDwA1@7YxrlC)`=_8l>&1{%Z zG~*_X-sHAEybaQiAyzE{j~>B$`xhwG6&BcH=5Tq;HwG7ay`0f6^hz5J@cv6SxUkAXNi==~km$#R6yK?EF3gNH`;d_;l2eQ$em4GH|`Ft+@ zpYL=RKQh(%!N;-mdLsEVmH#J$N!X63IFl2&jXq%A^D5x9i8)V3o^up@;l12xGlHiQ z$TJfQRs6e`RAh7UTMuGVswe7&(}Vd&Es#FZoO&H|&T(aCPfGhrNCt9>I5gI*8eVsMC-2 z-UqVy#jS-F#!x1nOW;ksM-$^jww=#hA{O3$TVC5?e%~^pP6sNdwVc!k+}oG@O8GwpAlM}0P@T*BO?qPg30JcN>w2V>I36B_ zd;JGBLNju3D1WvDS9Rj&_JjGc2;^}Q%=Lj+@R@b~0>3C81wkRZyoC2udbKgXKNrt4 zh~h1V&y}+K>+;2Z_Iv@=xEGPmm)9iWW~($_`vtJ^P)@26^@7Ll9Z>B%V#d}1cwu6l&sYmm>_@B=NSuIb^`O9IVcWxGGBcMds9yA7{vEk z!P#5E)0)JLzA@A1D*oML`s&N5E840LHL4{!ifvI^H(M1dbH4o*xPUO$Qd{wBx z3ij+f^8XBeTcPP2abzal1mq(J>mwDldj3x{Xf4De%N| zFh&%$QUo)o8${*<)RCL0R5z0?FQHM(H_BMW5#-EMpt>YFw)Nodbm!*}faBeck6nLP zYyyD+4vYwffr!at-A6{W6Dq1NEw`VV^V>9F?~P(5R#IDUWIYz(CElMM*#LKbEB=j; z`Ct{=ts+jU!DP#R{e!Q%M%1Wah8F<4VGI@LKKT1zcxtumJ_pW22)VR5f7%l73TOH0{;MIt;e6eA<_4P z;Q+T`_UF#YT75XYhpoiYSyT-(=?t!->vuNGK=V$C^hOJ_*_XimG5ogPT+XGgUXTEW|&A9KP!SC6JKOZ@Ax)n?b zB%FO4>VG?SS6vjS<+K+N=jzZ#CVTZOk>V6x@G;DztYB$?THcBi-IeL>Htu(g zBM&&kTp0-#*-tJ15dGB$>f(>Q-KRUg7G&**67@J}M@KJqB)yXx>`w)C@j@65*|5MC z8~af^N|_>jWFj!13Pw*4Wj(pd0#@>W+))$Agzhu6p0&2=@c@3|$ zA93R?5!(qYIEX565h}*b)Zzc3v)#q3TPga7KezGsW3a04aofQU`Vn>byBvLO-5r9_ z^tyJz5NgYg_{{!k#vOudIDL<(uJ!c%r_#gEqGnmnZgeo@!tdCx|C?DtA1W6IeJ$Lb zJD|FW%)%tPUoc=-X@@e0bozZ`pH^egSZeEWgI^!+@;ifaz!gmW2;SQ?*4v!(a10J* zJ@^3Iz|aO})l*?)Jm=jeleVnL&cp~fQBJAM~h#T|~BFR!FdTKqu>qpqxWkNR);y5}yYksfqDL4yf z;r^Wb&A&UubJ$0}{2(iTmd{+`|D55O?4SyoM#UeCVnh);ve$asoBt z9?fq4JjYE0UwZUFW(FV693Vv!L9Mo%9&S@f zCEbbTsN^if$#k~r^idw1;qS!PeSFvH)NErp3uDRWeK`@$z*Y9lwW^55nV_@x%q{Cw z=X1#fovG^lI1xVF8PbG`dIFu3JD>(J9ggmNkH5&Zb*irWWZZ}J`%W^K`;!w9%>Bd_ zoUJ-F!!GjPRH9Q)&Q@!XN;AHfFM8e@@WT_jGJnC~48Tp{AvgGjGBGmp#m$B3AYbdE^dw;v}zaJFJ5}V7+Us zV>&&=1p5bLGq$jNRV=DV|y!O>#j{ZDH8(}r4=PUW}5%KIA_lF*00=t6ha4_rc zNgmFEcXf&$^;quPk#pPiaV8#~%m;T+C6>}X8xK1?4>Y`$UD}W7x<7YX+rYE1LD^Mz zXQCT_I&n9p754|*{QlURuL$IJ$}XIY>2Obu5{uGwLhAe}bgE>Zv8KD!|g@>`-;z%vEPHJQifAIjz{Siz}MuF8I;EUnm_ca)I0WMlz4p zfH<}>ZE(`(qRl$W#BvhT%TC;-*G$)f{>LEp|6=qoyI?V#)m?!xdx={78k5fF-0YsL ztD;V9f&Oj{tNj^VZ4Li<4%4v|I#5%%N8NzxA>{09-GDKT4&rZ8 zh`a@N-CbvTTi0`b_kW)51m33d`yaS#2+2GoQOJ~`kP3-RB~xXVF)0l~M45-uAY-PK zDG?%nrr&$*zW@Jgzs|kq-h1vjd#`6bpY>UvwVr2VW(~~2jq>8# zM7SX^c)h$NmH1bJ#p|SQDyDYF4<@_o@OSRxd>PJ{c5nL!-KDh+P5%+f>EpW59+!o; zNz>e#rSk4PoYo!ISF&b%-X1veBOQAtpB@ekhN~mJ?fG2sW0A-@7oQx5qx5u7t42_} zD4e*QjBuPM&fJ7o*6$V$q) zbMYZ-G3UxGUQicltV^W4du`@(&zelsrxX1A8+VzT?hZGDsdepXAGhK!2i?JP40I@N z?bmXO$8&lF8(U}BT)nF*Jt~V@x5~S4mF6ybnW|K=BZRQm~ICUc8BYljN;4fgVl z*t$KLN12kIl?U*P-c|s-Ba+M)fu~boUQ}Uv5V~bi7h5k*4#KD_QLJKLkyY5uyZGls zXg@|aIo>f59~q%v<{AETyFBbK%Khs!rXo7AJ{Bpf>yFzhD;c7CPzg&(vERx8+UTz^ z_eX{hF$9G@IY^_8$)xizc zP8Fi;1sB<4)oI@p@CZ~Lf?%FP?DBsGX zbGo-^I(5QKp535kSXWQbFg*Mh7SC&TG_;!RH9fkU+{G-PY`>#^g!$?^2UKaVRWG|= z1nsBG=p%~PUn(#aF|uL2=U3`wQC$UnUF$YzUV=jXFnjdk`J;K@N;$?+Yeuq*l(%9g z&7esy8uScn-1fS6_)YkG$F%*d9hspIV6Hobz2n&!NZ%dmH&hkKXNBR{aJ~bS&z9y0 zE%8HM{g_d_QP#3gcJPrXJ^-6}SWGXg>Uc!#d`m5&iuIJcVa6M@nunl9HmI>$J+Y@M zR!Kc*$K6f!CrI(F%I}wI1s}+CMzKO)*?L$cE=k$a;e@LQ~d;#PBgf7{6LUr|)r$m@{RlzRkzUm-G{eZ1E zfVg{8N@91@S?09$4W;2lL$#H5Nk4DgYx@T~`&>zLqkQQ;_ilUCiu|6Aq1N4xVfVLn zoV|_D_p|=6u03edr~O%nT2Ix;R92aFRaw}}zTNTlo8+%26Sne2!mc~gS_-Lj{id7Z z4G7qjLYALmv`OYTP+oTv#b|>*zY!wTBlNeh=M5~H|I$t3R&5q|j}ii@trCC5?`Bf@7wP=_+!+0=b0GZlPVe3wvl)-yEL4YF>)6a&H&B;8bmztq zR-1IjFzbj5=!(^GM#cN6upvC0jf^MEl#DrL63SnNIi;nv5c*Er}^7D%AD@TR!-k* zQ{LT6zto#)CtHiZ(3;!j*03(uGqO-6abmKwM76ZJtodB7>$<6G%`tOkB!nuG`UIXb zP)>6_cD+_Td!Y4gb?ixWEmdo?ZjV>ofvtiSAKT&bD0*;13?Sp>BfNPIOZH5<>ss@` zCLS$#^b;ajXLXaV#^ViE{Tjj^qNlFdzM(Jubw9sgWSUvcUK(!a)S;8#>b?6Yh=ZtCtJHSS zP*-zfsO53h)|kp*zB9wsEOo^GK6~A_@+|gpHHEzhHL?5oaRt~gOva5HwjMjU*eYFgc10y)|HFFNH9&TqMhGnwU|wd2O!Ry$pN z@Jo-2VrAWHI4AtLme=3vex&xF=kaeCop0ItjLxa7tW$(# z%d%D}_2II5UhC7Jo~4(L_MY!ku{T)lctM5bPPx-y)rNIR=jt_LM}bti_(E!34S(nX z{SN0_z?K8l-0z3jH&BkU)3FQC%tEuSC1&35QP=U1E{w(=O+6km4pksbL31}h&RR-6 zp&`V43D$h2VtSC_C%R_A;s)V%e^LjE>9Tx4%;~z_yQl33NhNU#5ATWHS5H&KX#8oXrm5Ed)uK@TA)g!|w@IhRb+T^AhjfAGab3T= zs!v?iG#voL#h=bp<7(z?W?W>ms`Wd3aDe{Q4m`6t|8HsyU=z%&klyQsS8`edb5NCGNy0xLgxf`QS^k4{uF|%~$$bN4tog`)3kiZ>2?aq^eG*fbFJwm(sB^SQfZZ4ss0!-^QsBKL5nC6(u58kEzeqbaPIX?A#f!|D~WW2~rda`OC z8q#q8F#|tX;1%Dv7w4HPScj^SlFn zMWtt<8dRp9`VZe%gW9Ds6a8qn=(jIsJwYa_)&X5q%T;LJG%`cP$7fUsnv2J^t*X2Y zhLllNzFobblAmvtXXd9dUM2te+Y04h)k!~Lhw(b}pTe{6kW1#^huO{PlDKSrE5~~H z+gt3k1WVr}>i#9ONNr!9qTV}{vs-94w|W2C*jEdf*xJ!lrq@jU=W*F>FMWqE(rjLL z4gImB#?C0cQC~>gc|?wt&6uw;(!JESO376BsM`E@FW|21)ff_$gHBn*@N??Cf9TWN zB$|J&XK^~q_G8~pIuYB5?Cr(sewW7TlUpyIq`_sX>Q@`6$LmYkXnKmz{ql$B^o38h zcHnCmzmLj$*~+h6IB(&kj(9guX{dVhv~H~tl*nm`1~N|%#m97xkNosDzB$C-+ws`A z*L7xh$UO*?e^iBA>s^;&A&a2%O3#0S97n145k+!}BRBp}ODse!DGpo8$~mi2nQO}9 zYs2`uK0EfB8EDQ;=AB>CUUtB{{j~b1(4Ue;Ux9w9S@66OJSvObpZxVtUk_3T_TmRW z^6iy6&fYPv22sDB^o|cx!0tAiv)NZ?2Muhdnev=Ym6mWfR$df?(s@LY^j0nY%R5h8 zKBk{@8-}vT+VUZyN+-5#1$;c110&=`jnPps-n6*FaAUy^fQ>DtvZZ}yk zQOc^yuvuy9X(d*xra$`ubGn;3J=xfN%T9+9u0z7~{WWus-Db4I((;w}E?7s}dal$}{dWg1m-1_fuacl*X) zfAgyCX4V=rER>%ZvmWX&4P<6FQX5Y4tgqqWTcUk$s>37ZNpovV>ab#Q3?{vGFDK~d zd#pCypxb^Pgc<>%o-})EJIZ0HMdcaS(K)kWLfN2J0riyI#fyi%YKRCw3wQlmeEL1< zZY(Sgw}VX6%**{|M`^ZxGO4Z2S1JFUz1($H&9bps-oxA;OKW+LCOePbGDjXTL04g4 zbH5FBJu;z!x)##2{9aLHHmvClYnt);(qd~GYYGp*zu#Qtk2u9T+~zwSOg~veu-50V zbbMxJZS4jb0g!;&H|L?DIzQPxk*oOjv9L-lX=t0QVa6vjTc6eimnjKe=s z|7pT;AHwK!CY9*T@aQ|b*H75gH*jf@{B5#a<0Z_uCv57B|8}9f_kv9?%j@1wa){m5 z7L^o3`p^=-f=*d=E4Id&roxX+eE%wAe;Y5qS8h|=xiob~ovqt{m7+a|Fa2cZ|0^Qj z2!pFZ-ge2Z2`^E(2B;0Sp>f^;pL4j<6Z~*Je|y*JjFGTBl*JK_$#SQUU2*iJY~s^9 z&57N3!LHHe3D1Zdc0*DpTIuCEa8FeRdBz!y`Vw zSyEfM%P3rzbQ>IV7QZ^{A3e4~v!iCuC8}~7<8YE^d}U_53RUlgsONaV6nR_?Jn$4| z@*z(DoIcjp{IwCpsUk-$>bQYd-^{x!(Ftn!ZDl{_)@AXh{A`K5VtjH;(WU%84z%)r zY&Wa_spFmc>7)JtUj1n19M&P5+G@K(`V^}}#Ey995XTs(G>f9P!5Y7GqH{}jTW;=O zt!JVVg{PT#_M92;is<&b3SmEy`U&-=Ca%7whSv5c3 zCKg@~Z(@uVV7*m3j(2c zi#oqi)#7kUYRtSGJ*SoIs3(PfFlIRn&m1PtdXW~{UH$U`Rl@wKuh<*(3p()Nq*v_* zn6p{6e1tBQyVToa*U{&(@6vSJ^y(4^W#6mBx3~15_QdE~VD8~X#hrCAT3-o_t&(`x zQJ(o0?zb5_W)dmyfxpkoaORt-2lTF7D~{JRPo98mFR=Bq=4vO8c4B!8S$k{Go-liR zt7*T=)^Cc;Gt9?n=Hpp}t3`sBI77%^Qz`{Gs zs+!pE!)D?0(9llx$$pz_`O^0utJT@&sC+#KGfRl@>&%OmaQ-0N9WJk{FGtO8cjKMn z;b*Kk-MPM`gXT$!LksBBfaR-G6{^CVYV@4=UPCu~v&)atq$ipe``Ns<3~ISqS`~|2 zLo2A{Y{s(oLF~OUUGh1eJCTmQK#gxBJ^mmS;Idu*GNs8*;msl|y+V^e?e~AC-b~$D zETeisrj^&8*E95Ytgxs68!5g7at7!`a-`h=58BD1NN;9-}0qg7FWK&n!D4C^2?<+n)QRt zpc1-6*YJvN_+&|}o2QN)w@}_HY&|TyKGIRKb?70j+EcU%0cXKb*k^>o2{u7N6fH+M*doR zUad%!mg<)Z+cD~rPRSFj9DDIxu<9$f8fOi;$sptMtsFdudE5kB4%W3g$po)UGQn4A zPg^KXMdbj!WZXaDF{N2CR^+dgai7$^lv55}P=zJGxf*Mx4qKtLJW)j-!aXzEyKk+W zWh@lxP8V-3V&9tR2Zu$EuZ`tcajgji%S6xrl4ti62P=xC*{uOPXY`L#+EP=4ZsysK zQp!Gpkk?>?J>=9s@aKG3TSHv%S(rKrpIt3-AK?)>WR}(W0e(iOhOjAq! zMQtOkby4|rQ(c=TbDE3vnJ;w*b%WSBQg7kUo%mBq${N{KZzy?BlBJvx>(Cqf&kM;K^I6oDpLkIkD!}d3fTysZX|8Z1wsOc3{N+demG8id zuKr$G%*uqD?@)DpU!PPjXZf&qY$Coi^SzmKu1D*t3{Q%SHyOm6U2>z3sE-3(Ln|6l zZCFxM{?tr_82}xZ@P<^bw7!@-(fj^M_pNCpCt+JV@zkr$rqX6!BWTi3MQ1L**rLXN zMHg6E^`0(J;gjLI|?`+q`qcDGrYLr3d=aeE=#52a*1tcO!NWHL($9QBQ=fBGW8q ztjjpcCRt~_B>R5~#~y>%e2m@vU{{Yr*hc}~p6wv@RG+#HFU^WM*ToKUF_B=w|sqY#GYBt#G^&nB4&kYohC|iJcvn zcTdE7s)#VZV;*mc+AVlN89XVwJ;TmHhy7MJZKW3NrC^+sBV_iMyqMKZB7Yf?|1R;S zF2rnS9G->w<7l&Ah>|DF(-N+*o%u4A@9eVrAe7m1SYUM(sJmqox3FJs77R7K1s?m_LdwbntlE^KNEG{H8HYurj1Aiv494YYt(68;$ldpS%Lg_zizf zC)!8WIDoS7C03NyEWX=FKMspt#Ox-^8Rp|2vH!?gS@E}K)=#pHgL3YOC?#>^dJv?S zReK-6v`v`bAFR4Vh51t$F<7K|z^a8CpxjCM_YcNu1#Y*12hD=|li}VZ-ZIq;eb?`1 z`guGbdmay~YiEnRdUp4kTkxm2 z_B&x_VTwjJifcOa;a~eqq@oGlgcCR8gHy1R6V~z8#Q28$?4_>o2P`{!{#QYbML70c zXZeA%n&m7fI?uOx_kx7S{J;)>={>tbO~_%t>w>bnYx&M;`PfR=)894ROOMOt8vcd~ zhds{A0CKC`R!MTQf#%X|pYyZq{5%aKoBz*_lf_zrqtu0;*lI5O43WLHH!6)~1?`f2 z@?~*-nlt{;=*&;7`JOX-15@qhHTBKf;#AoyI`cNM-iNOAbvnclm^mCuyd|c5z}uGL z@n6B~vRA;-Gk)?Udeyh3OgTFmh&F`Z`6i^qsz{?L@+Q-BH6mc$Gx|)9%m&Mhh zmA8W_-5_>1_}E@-t)DQgidcC~SlODs-WxlaF7p0_y{EO$$DJ_tDHuMDD!fGp_ci*+ zE5V8fDPGO2zIoWu!m}1wUVBGZcsdeOUCw`YC)PU6?w4p9X(>M$92xP+t7t`O=mQri zKqn#pb{X_aqdJEb1F@F#;Z4vg{x=t&yp^_EQ(o85tglP+s6ufq=TTnvcZZ`ay`+E< zxgw72f^J_zw}syCBk!~bL;TXnt@r!Yn9?HtJ60s@#BcASOJ1$>VY}!(Pp9)hp4mh{ z?#)p7f?2njR=QHvZ7GX!X_nXcVOmeJj#7mo2tqBt$q#kNn+;&o~ejZ{JV$_t0;H16c5vY66Q zY=4edx5Ywpr5sP@z*J-33nsN>+vw=K}YKgYcpQ{ zMJ=Vbs%14@Cgth*H7+$$G4AYl!&QYp!a_Ex-ygu8&R|bxRWOh8>wVV6Z&4fgN);{? zp?OrF&+xwk$$Gtp-g`DS@XzHu5ad1>5_{A zD0I1(&ALSx-2p1mwb)7(oUA@}T?Izu(2DW9$hM5X%y2!kWeA}|AA!g>dsR=^^CiE} zK(}v%TTT|=cIZpVEb}X?_EE$!sT5-y{#; zBaXZ&`)+`BW+iMK6a#;wr+vqE>pa%8?-siMsY@BvtjfcYR`B69x>@uk9KU>((TcwG z%EqlW6mJa0<4!Fje9mIp$u?;DFYcNJU&{$Yb5l!hF!DvAXGu}2l&?4WZ5GVptoqL$ z)>_ZoW0mOy@A4AF8zFAL3;hj0aG}`tJ#08& z7W@NMa;FrLK{S>}ylOYb&t%*u^rsYN*@sd;u448g*7lYf{#?CMEA1WdtsNV_mb1J| z+34mq74?^-PkmAbutqo0blt|U%5I+1@!BHkd5!(%e}hS{K=eC#^FCug(mJ@x81pVY z#Y1JTchF+a{I45r4mIpuHIn%hvCs4}eC_x`ezlBdHs30Sw`r8G>VN1sE^7!=YGB zUiG^QWltWM>dH6x*i)ia0e*a3eEU<*`tPOe_+1q_UuRzXE@b`=e~Ojm7euhjmt&XG ztBqAQ(d9A@`GDEbFpdOJVriVKayOyBVsOMC1B;i{U=xx}jz z`0Mwucm;o0qEBI&XUqM()cLOTJmSvh{xS#lcBd@o6I&LE=FK5as2+b%Tfd{$tPoF@ z8;f65fzqf9Re(_)MA^|sEb^q?c*rFc%bZEotO6_zow~XB`Yg^h&1W1=m4oei>c;vG zMrT#8yaTE{pc>p>&r~}aNG-^6J@$M;AM6*f{xvI8>$7#nlw)w?Q*7x~QScG5yE^7y zkS2N9teXNkYMSx8;OFx^@;aTY>yti{9&+pkm+sLI6}vnYwGzFo?zD!~_n!2GSv-87 zFn^O+*a9EvOH+O`>Du`c4j(iYc_DEVnb%mT@f#$#QI7YxoPQ1mw_8;qi+Nm?FFnJX zUUh`tJIH8slB3_{Z|R-)Cg(jP+uMciewD3!d|czM%IZ`yohvBcFdjRxuE#dyjIw z+z5Z+`_hC}e1T2uvf?;xk}uYvML#WWj)fhc$o)3Z!M@S|J(Cx8Qy(eMbB>AAGez>I z&NsDP@1j>NR{q?H#pPw~?1_J7;;ENa1$U@m&o&#Mp&s0+=O8WZW-F{-EXvKM)hvhI zM=oW6I?bWaT>p_(X7(bEoFe`|hY$P&8FOJp)e;q{aZ)vIE92^74f;qC#j{k&JTRjO3xIA|9&ZClX13xH18!PQm7;j6bQ*aB+o=x|7h4Rr0 z3+bh@6uXs-hUs7Gl*&h=97nY&AOd_K>X(Cv(;(KRRAq6~j(qoJwfHx!^L)>Wm3eZj zMY64xl&&Al!{4p;KO;J2pzUT7by6t2$7xJ!piOU;i)<<1t3NfwO1HxDXGPp>IR0|l z#S00~tc5XEq~MnKxDCUs0=Mty*-dF257Q@`P!}I`#ItsIem_|Mz9@e7aw!bxO{0^U zLNEYp*aPkEP(yrG@7ecBXK@<2b{2|JM#ouLzJnJpmTeCb3FFQycO=~i1=LeALBeBv zY>9aMBn>Qw?#l%-(>ik0Kl$$KD%q8E6dYl(4_LArKKGE_;p%ua)fx$~W2uZbgAX$8^b1>Lqz+z7$ngg)K)bWJFGHT2uyGou5# zA>NeX%0B$(J<4=fc~K*FZN!6~7M~}3d^D{1R{kC9&@-w8oTFWT&r>Ei(}$pRx|BU;+$i2v z6E;VF7(E=po}1&T(e;&Gu6)Ye--zE$lVd!Fx!nXS_Oa}{cH-@9Z}K|Ox{95o?x$9F zQ5~J(ZwD@A;zOPI)Ox5~7@v!oI9DBPYtl=R!FU!j&+jy{ja)|`5ofOR-$zHd(b>1= zyzjokC`)%@WXZ#rAIw$ z!+!U&Vv$4x`@@y2kX60Ke+KdQuC%aTu4^I>UJpOAi633qU^}K+OO`Q(^7E^F=#-57 zqDOQdoThi|p-^w|w=YHHZxZF}fZwNw!iDJ@)t%)daIlvsKfpNj@KXbEKh_0)1?{_w zhQ)CFg9*R-Bj6C!O;S9=X=uL8Q1IU1V{Q3Jaa`eqGg~bVzMJss*Ra%aIQDzIa~YlX8lNK zGz@=u!Tf1U3#fwS=fWTk;x5Z!!E~9?JA7&ZCjYJX-e*1cS=m%N(IZEaBjnL3lU2@= zN>*?X-&yAzXUPIyQM>42Ry~Ui4G>4hxtfo}l%HYk1v@_#l2zX6sN{RZ{@kjxY1PEe z%GUNlj16Yur%8X?yEyT5tN&uZ^$*x!fh$@mMy}zNUpUJT+2%jK&=PW0G+Oy7PU+~# z21V4m#?Sfj=CVBW9`Us`Ry@!d#k;Unv;xq(g6Pz?|8t{+(9OK14GGmXBC)$*Xt$^ZYu ze$nGp7$Yy>KeD^>bTs92Jn;bk`p#l+I6UEWcg7@OO z(UWsEzMV%~-sXjTuy$YGFkA>8~1`iGwV84JE^mD(^J;4dcJA=b@(j@9qQ3{uls3eyd$&<(0O zN~XR^{&X_YY{$Xj2H4LfBR5a3)LxEoz3$fNVE%@uf8abPQ4mK^1Nynnmaen79SaVK zw9}|kjc}i|m$y+*CSK~HW4w`gQQ!U}4XmPSNss9!KOKz=eNHVnc_|%js}S}aJ zXveO)VdAdmV>AJZHi!#(6omDSVITsmph*(Pf$zjrU0J$7PCC#x!p zAw7aKP1a+)%c`jBF{wtEyQoA=lDU6Df82>lpO!a9u8`5^>BYsnA)Q)U+N zfgR$(DRb=*f7}8`{)<#^v25I@FnY)HSygb-`)x9AE9jgb>jj#pD`277x90K|e>p>o z&m}&WHV5yb1>Y}oZXwEbG9L!>_J!uuVNs=&T&z3p`lT_rUhS)o%Fb5YrkuK2f1GTk zQ8;b2W+9ruop$)DXsuOIyR&Ds-}N~a%)_em`&8DpVsY!#*yj76(R$8X^ZGnI{u`Y7 z8SE{sI_7Ynl|I!sKL1`b{s(;yvFF-F_Wg**KF5AdF`inq#Bx^PYs2sdoB$+ul11?f11+H93o{_8~jO%%nA5&8K^@!)`fib@rUfTaUW)Q3*KK zP`1(@u5~fDdpU-RgX2Z#sEK?mi&-x&?^S6#1X1_UjlLGqCyJA8#K?Sf(Dhdv9LnF3rDN zdHcToW1ml|^S4{KmEL|9TkNJVNlsW>1#X*+_E8>w5clY425)sP^&smi71sM?P=_d6 zvvd)3)gRozn)<3%{@hK!t!EWVYipu9;`)zap6&hIGI_MI*U2-K-LW#+*zF{r-i|IT zumQ^73JWHQW2tmjwP4=~Dmkmwgmg^C&M&OinVOC!K`TtOn57Cg1QIImylPXxZ+npzUDhi!t z{iqVR5;;a-51$$TKe3N25a@1t$pBar_wX!@OTL(_hf7V@YQ)cHz=wl&3dyV6p`4Xf zw_D40gWCKR)#A;R-3eaP$jZdr)(vi@5xlL=+(wpOgkOh7un5fv|}3go!Fivlph+LF5NJ9LA-hYVJmrRLjL*;hNCwN@4FRon``yh2l4OyS*48@pPT zTb|a@nwsz$d|pSu07 z6{WLY#5*Hznv9cnH4o}wka6Gizg7O$L4wI*;bTyu1Rpt;u=yEOjJ|S>R`S&9qQ>p4 zU!JN|jT+s7$~?v_`+>I?p}Y^p2li9rs>+;V4bV1vKvC!Mw0wGA()Ey{GrtOj>v_FB zbFKRM6|P^g$4s#_mFxx5+Kvc)Aox%i9_Ze|Dy!SlI z(kYi+?+30VG{&3p(lan}1rLmMHMQu3S@h5B#j%!Z^AoVm@6-wYwd?JF+HKTw+OgUI*EXL2OoJG+eEvdL`kBn* zEA0Ps%;^KndVs9@9&`L3%$s)_csSSlbm< z)-z&Z)E`zu)H!DMI2$bz)<8TzZYJ zz+CpKiC(qgsyrX?=*6l(v+X4ldl+<4^SR&p{bKqBv#{?4c0Yjk#jb>lamWc6V`Q&w z)kLFepIWcUI#)dsCN?*oB`~BkNyo_{et1~?x=h!+4tJ_7dOjkLdzFPEA6RW3Y{nsX zLfZXe*Fo7q#IHY`?VqZLCup-5WY?+8nDjm;bh&IvB{wa;e?pAg=zS)6$A=+rcK2BM zN?sLR7+J3z-E|L?Tc z`41glXY_YvwV&H<*y$6v(?>ix`nsFy82ykBU$|V1$2^0Feqo=y2y>_C= zMj^ZfcHK`^ZpBiagR8ReKX~R>){MVnwfNiicAVq+YCGfYXWv-i zTqe0!LtDJ54`pr~5C1QEq{jL0rZK;yk9(UeteV-ofF~8jfX0Yc|G3w1V`wo&zOtLH znU!K(%u&kf@Zzw#u%ieauB7N#PJQA|=v`GNQ_UV>RaGY{Sf`(t`gL3^Tta8=L9H&G za)mbX1&7!;Lc4*l$pIS*DuBm|ZJ&#Xg_8bpqr_ zIz3`X(A!lNno@cC8S@#?|8rT_8hO_$>&fTR248ap9e7ANit?ZNLy;26cd(QwnX};`^ zLgVc`B|>-BkJ4=SG>m6uBZC1 zq1kP400*$GP}1B4k*apu=N-ihj(HZp3(e}1zr;_m zu4A7#y-i)_SE#!cE7}Q3g0G$M_gL?9!T6kmAo1Qmi*V5kKS0cFLcu8s8!zeW`4Lu5 zmy18e$`xrYS(2RO8`;wo^^GnfZxvXa>GEN9(q%Hz;VMpXN0~eHCB{82u3Y>Ff*iMF z>{&-@_ae@ktUbSt_dmd^pG)dfE7VYqQk-u}V)j5e@Yizv4A@G2xy)$!^7m$M1~a@G zP54>SW4g-2uPSX>;Ybah-c_H(IM(}=QoGGAnwL^?L+4vnO72N^o4AKx$Npot^8Z3S zJ13n#ErtIds{d(GGqnGI623~QkHSf%B=*)iXar(i$yoEIhm5!-8`q}^RHy$$1k57-9fnfh zvi3YW#%Q{1XVJHk8pCPI%N$JY5e)4*eR99z4e!B^{y2PlNZpXNL%pv~O)k&s`Sjub z%bT|)cN<(~M~an>ulfIXez#lq^S}0vEs@+EbBrwEk4t&^c4s{4v&6Fts?fERYA~a=>;0ec9oSS z=Jnfn;w$Xd2f`1)@kX%YELge%i`l65@{=yZ?;SB07P9kX^W`U%X5ECbNe6ma6iG$nc#O7*;OAb@CzQE4lk{v7Vx^+vq^l+ zhfg)Yxw=tSW94qtmp9^-M{%L_SW!W~QH_Gz!C8-$tFD)|UP_fSrMP~g0Qb!-> zMfdHwYy{)Z3B~QcS<^mw?H$kRFp5Wqg!kMnV&t=nFA(Em&!Fez zsSm1Y-!29g)m>ib(v2R4JuhJtD!}PFn95^%HHNF5Ecd?$?5CGY#qi!l@fxT4x|*F2 zSYdXaR&s^%n9+PHKrN~296I8HFRArT)r+xQ?!TGVeF9Ip8uDF_p%r!%G6wl+S@Ba* z%KYs(?R^+rb7S%hZyL+5R>RnTjY>)5@gTLe3om_1g?thu|Byc}=cnuW=1vu*GbyR$ zmqpcfTi7q*6*+0_k+7Mnl~(jRwp?E&FRyrb++6=9>C5_DY+H!2 z&6B6h^_b)NT$Yc{n2$W>c|O-`7E!u3&~A?CC(q5Yb>)jgbq;){%6dZI>ve2-hg>1F zr<#tcGR(WgiK-apeQ>0quZ?5{kwLVTGe0Z;ir&eYa;Hz6>1Xh6sb`_qkH^f1&{Vom z=sS3AJ6}6vHKXC-H`rS`V^M`ab)_r3Z8ogNx=yFcuKryMPkS!G|94==O7`BK^cm%c z{mt2LoXYtI-QZ{K%yPZGSt{Efw5G>xRIVIp{&h#%uXP-~hSxXd^F?S{|Jq%2javL` z(5F7H&Yf~v?EGA2I0V0{MB)D(GQOe~S|U*a=Fk9JSv{3oH^44+yyblOUD@7L+5Y?N z8`<7}-9~G9_*z-t25fmt(kb`1UgvaJ;q^K}D_ZZ=fr2x^$gStMS5z{~%EMaAf?hPP zQz3Hf5AZ&FO=hvzXze}4=C%;~!Nk%J`TCHYE&BW0_}<3%=KdPHC)YO`)iK3NMyNdf z{TAV{ zD&<DlH^xA4xQ!GY0_Nq^^~5YJyt_}qsMD5TMsiTk3p9TSm^~b zV=YcJ4hwvowPPR9b1-`r<>+2B?=KnQMAu)B;*yqfyP7UB((0+lRn=qlT_tue4MT2} zedl1utF6Dw;K+(0-+)n+qKijOzLJ`J4XyrR`uRb@VrqfmfUU!z_R?Sk$mI@k#fkWSvPb4CF&qiY@T>Y_dizJ3TPgtz}kKzbf@V5d9ovKMQY;;QqTs$=JpIYh$&5 zPfwIxJ!|z+bqpna%6=;7+brD}zGX=DOXAgS&FQRa5gQ?BtitOoW#jW~HvFpQ5 z`qh_E;(hC;#@Gp~w_TSW73Z4B1e;-hPs8MiFytrPIV+!jfW5~+`yDDa#UMpjTEkp= z&TnjXMh9IscpED=@0UkBm8^~!mt6U$vgS>4-M@8|WR*P>pvC2vL!=Ycw^Os{VBJ0a zM;)kKP>eY#?^~-LJk!h=mQ;WGs#f-JbW>yLZl?4!CVd<)8iCg#(Fm`M`##TcHtUSy zl`A*kWKYOr7Rffwq{<_zHb_>S470lDJzVB9zPbUc`Of3ND$&{`FI4dlQ=hzG4xGvFc_6_qkI2efif8xvU98j1#OAx?xU*;& zUHNA%+^IN($l)qdSpKk#I(C6rDhr*X(lS+K9S2Wd)M$i~X*5+>ktO!0(Eu+ytGA_*zhg znL(x$dq6Cq-;Gf5Xzx8s>k2)qLcPkT#r@G^$A^;EJ!GNrpEv&x@XAek(N?Hzz5{n( zWA7f;__WfIUkhH`4qYm;eN8;2r5=-BUNJ$={VDBts~+YQIZyt~#RO`!-pEcs+FruaG{VRJPk+q#;_0uqQzuar1ta>4oja3SR3(jYcC~%cbKs}QELaz7{%tHPsSy%x3;cO589)GIN zuWLH@$RL~gkJd&rx_`U#vKOdfef|G)e%F#RQN{>dVWVH++ftwY9v_`(ZVsnQ#p<=JFL*xC*Zy#_hwFG8YiREJYU2s9LrQUT=0@F+*V_-Mpr3CuLd`|Zq26o0tJ~<< zPFLFxMUT_FG^C=CNCJcua%ZV_nIskiI{^e-5T}p#?ooEpCPFG;~DOwT5wO zkZ_?cUK3{=+PZ9Xrs}F;5>*-D>3wqC4z$p7Or#($!|{b{#=~r@D&Y zM_=PDNq$vRepMUytcJ1Nl2V95l@V__?6n&xw=plq=*Q_HKD9Fv;cX3FSKPIuu!?z# zoqwXUCRQ9qt-UW^(AqhN#$H;7(zSLsiN5H}zUMS23K@aY#^MeP?mk?vz1b5xK8=(& z&iG#!(XWs`di_iDjaGQuE3RNZ=Julu<*%ft$t~uvt4y-AY*f^&*7~GXIXa*8ImNwN z{!TFWcQN5t^CfnpjukA^=|V$zR~w^NPIhz!8`@wbCc*a}Y~G6R*Hts9l&m={q9Ss= z>OfpUE;BoeIedA@(CFUj=<3J7g>@KJoPBdv8*lEegFkU! znJn-=R=D4(0#X-mX(lc`jKOpeRiC!DpsV~g?%?#S<0-#?z-t1f;$AUpAlfh^TH8qf z%}3wnEe%B4%vX-nO+Tj>zA47`wwkjI)v#8w`#~-o@*-A#SZv*Et?drE`!-yCpA}oF zG0Hncf}Z|wX~JlVV2|D4?1p;uqLoEMAk!0Tt|!`hzg8&=YcY5_F{xsI7?bYHXhF7fqmol{qt zt%aOTXeCYPAnh@^NAR$R{H8P>c@eiYH)XypXpp(+06)O1H~39k<653-ag}?>Y?94QhV&i8slsaQ`>~x* z)fgwpSo`t%C)8`2QwU3B%x|89~iMwS@)%ky%Z(NJry z*tRj@@R^}?1#_&4nbr}CKPmon@`!zV+9i?bL9wkee=f|dPZa+Ol273kvD;h& z=T};U$)?J8N^eW-EI3cs#UP49Q@#;v_-?e4=Q>^Dh4^^X&F}S>sGY~IA8|j#FP!ZW zeK*Cu*Q4U_EQoScc2U_m45I-?)w%>e--~{@PWF`7`l7b>RC&{06ko|%w_2UQi|(>D z^^bPrUTv@8PyOY?#hnSKuHA#njx`%3X;D75l366!GKnwCBxmySt2hQLH78(N8OWMxGk$y0#jr4c=q1 zc{q`7(1S8?Kcix2YkJsK3~c2Vt`^}1PWv+4ewjSon?P^Dd%-V^N>mID)5NEP1b=X;t?(&3J zyy3rACF)k}h!nfDJ%%IoPa^Gj5pSwvh9mgf95HY-DKc!0Mbrjh^6 zs^>KZ{8!_+h8Jg|)Tj9QEEVT3sJ|VmY@iRv-6bZoc^^@#4t|qEJvkJxcZ_Q%F|Hij zUvWO0@Z#91VuV@z1RhY6&*taX=aU_V*0SY%*!`|*(G&<8`^mfp8wZ-#1I*fCaCN%K zw1jW{Xx1N>Pi2Kur>X^OZ%+6N-Lu%&dX~hdO;Jcq$R{_Pt$)~ z=JceQ5Od)PXW7y3+ptj!yyXF}kG%lykefuMIFDYfbmqr-3dAYL2^{$_-ns{a-RhIJ z`K$w&?0H>XnZ3H8{GbemUJP^)u7IF6k~hCn8B-K8QDTQ>~w_uXh*R$)9w{^nnkbL9g?=>PpEpTCi!KgiFrEYGd!vjY&qePOOd9 zb)R5r3whwDj#Z-mTDjv_u40i)eiEzn5)&R|x8is}+^uhu9^4PipwWEhRdXbI;p6VI zZ_8r;+nHi#(n}Y;ITelCgSc~RmU!4`HoN^o;8jl@}FLr^89g=-Oo)Uqc*J&~gM$Qy7zc8z}n%^g(cslqV9WvP= zQ(nk_bHd*nCjIJtAjV9nxdk&wXExo!pPP%F1F27M^Q8AhtdH3BL(gKLhj;yMs>c*M z!gTX+hTqL`B_BCHb}Tjui@o{-zmIze^c5A_iV1h~`2yZQjXd@!eBQ&ack}e4^v1OE zv#52HV1@F$`Y!Y2-XvS;z^}XF;jz+T2ur_4AsCBY%oO9|j*mNVtms3}h+*AG7r0f$ z=}uZvIT7}H{O>$&^#dJYrmN}ey_$I6$gRW5MpM1TM#}qd=?hxq%g&OnC$K-jDf5+~nuQ^8YuuxP+^3ma*zvdD5^NC}$BfgHL zQjPG};l2*@c*V1qJ$o6(4q@-u$tZIE`$f76Y*#{DkJSZnW|`!W8F8sBe)nHyU4(s0 z%VqDtW$*C%l8yq-Iqq+eR=#-}#_fh}>t!k{DGy6%F<)X~TWBbM$Q+{&=?D}&njHJ! z;4l9A1=V1B!XW#LgzeRF9uWEO7tKP?{_oMqvxmjFC+P?M%;;Cl=^;2`FF!wtO}D3l zKg{P_8r@c2*FmP<)0n&=b}okphj?NxtEbB*x<*s>Z086h?+QJ-c|7Y`u*xpJcjr%o zS?Mh^ViBxapXBF%C2P))Bz+IRW7psK+jKVV>AxPL3RQw7u`gisrx#+&V&>beEEOw0 zszQNk=E+^YS8x_Lv05hmS%=vs^yJwrFo9A$O8lQB>VGU6{ls@p&@HnivG^A65xrYA zJgR#hQKq=}DIjj#;1xIVoT702X6JB+zsJ2EySa|I+eggj$-a&-;@yn)eLO3dxw^+^ z&*QcI*|0GNc{}eehCdYaC?Lk>Ox9FfZB<1Y*;KH|%b4RSRimBOF0aQdmdkIKc@}pt z_}BhVrF1QK@P2W}$E}!QPEoubEbY(pCPS2us0Fc_b`9(OOo83WrvE9G=V5E?_L|3B zFJrb>71iz&*X}XvZ}z(ks=m9hz3A*4D)v6W5A&Gm2Vh&=yLuU%`;Z-{LDk6#)=yy3 zH^hLE=Kd>uVF1tRg-v$HmpgkrjW_l7`_XJapU3{lAJ564O0Zu`v#medMZBHsoR=ng z<>$Vxa19G77VjDR>8@!SBz+TCj9K@RBb3fgxIzmaQ7wtAdCZJh8M{hseGO9!eYgR2!@$dORh{ zO;2>bP$^?y>F3Pw**y1G*1eo6I^p8J$hT9H;=a`Nv4R#NSX)P{MC+&}rj>`R#W00J zsv!C3;F@#{5X9jl%iIg8p!bvCrh$~>tuF8W`8 zXUxXuDPu30u|ti-%UEn+K{`XH zk9|-6;4?pC0Lw(7iM*=2*EcpNBFdIF7Xpn!$3AZM{FbO+?_#%ooI@R$pIh#CkhiXs zYfPkW^tICPDP5eMWTY=(ixaS&7+6{V3h*YIC-Xzc!?q zb`qHe7@If!*StgnUS@tQ_G|%94=r`6nZI5xbpQv;z-Hy-Q*C+faOV=MRoAO=#12bq z_|W@&{1sg0alGh$BUB1I&ZrCWPxk#8!mlxgUpvD!ko;@M=RAFp`9IxUe1&fM1XQlg zlCi^68a0u>b=aR)r%NrS7D)2XCSqxDl}Y|Ds!6+y-UV4m4%c50j+RU?Gw?N-as9;R zPeOrF=4SZ#iKMo46DC|0W;DQf>qC#&lf5W4EY`Rk;YS-G&wQR7b9Jzs<{9y{4c73W zYp-M0RD;#!VQN0nIrhg3{c$qR@RZndkC|3b&XGaZ@ec$%?i!9k!^oztb7ir1sRrz; zYbG>F>IzSaKYh)Pq5OV0JB;P?vEP2|jQ%~}Ja2D}B4Wma?Eajyc>|Wr!LF95I(}sY zzQlp%yS_>Ad#Lk$9#eW!F3{BV-{nk;vrJxm;u_r~T0T^uPK$CoSuZMNAF$ef3udSH#%lC{!HSwi8Ffe<#}GD~ub+t6amRtKqIoFQ*YWmqnAmo?>^fQ8O1a)*9C*2WZWHC` zWRlN???;WjEqw0}TcSD=9XCI#O6=uNhj8vdg!B9=4Zd}4l4;cyB_DSteO%iJ>fRI) ze+K_}gTKGkC1-w%pXzp!@f12;wVdRgumJ7IOiBpR8q)nQjz zBYhM5=XbRdsHS6a*rjiUye+-Dr>J066>jbx?|K=zsmLkO14H5~|KIuO}wsJKiiG zV>E{u)j01*5|&mbiD<`p&|01|6(f8BJ8WhQ;+_g+V1H3_pom9NUyC`4`&}t&a_oUw z!Q)oUxrDh_8}KCRi7`ZTcu~q?5ub#J4U9M)7Yd^a|))jQ50S%b9mD+)Eo;m8VIL` z@TlQtPJD9cr?JP}Pa^+s$&C5OIb_t8lbr=3*5)_HMOgkenDl_?*b_2Nrx0w!y-%nn zmH+2#2a;m>1c#0Yly5c^q;!^Nul9dP4MGa!_?$V~S+ zqQd;M<6F_=Tk+_7sQ8=T{^>ZK%#oAwnuDU%Z|u05MHjK`yW(fe-C=D18Z3;ypU6LE z`Ml*YX}foi9i8&?|2z5k17=VsQS}9=ALuiNb*8b;+@!v=S&j6cRM8P}mkj8?U1g%L zf1$eBm+A`9$NVz|buDfgYeimFp?OR{axMBuNpT@N=6=rn*lnJy!y^`nx{;?u9C%7Z zk2{rQrcCa@Ww60wdzGI38M>Z_B&&6rm<{FSPjLtMGuZtu?;d-ZEtN&S zB_?z+>Xpp0*emsGKJ&VG*p#OiNf`Y;dCBK0Qqhw&T3785HL^jzhK}7oc?|OW6(!n-jCqUH*B_*_5N^#KOYkHkBS6` z{d~aB@%5nNF#G)tb<)C@xOdNW6o!1h=963H;W^nLM#QcZsB~7;_#2L$fmCsi%j|r- z7=BvK>}~~Nd-1waJo~?$h+~c3ADGEG_K8k`EELnIyq8S+9qQwcu}jrJz0GgSnLcL! zuZ+NV9zXK@Z*@AaNOrG$LlwDal1)`h)W9?;r|5<2MCfUv^>dhVL$fSY_zaj^;QB&V z=p&b_0#B~+?l0AaUsc&|PNgnSxz1+K>vPrwAJ+MHP*whjXQ%Bsc~159l0D|4|E+*m zR!{@Kj~3oiUGQ=BoZiM}oZ8?L9{)Sb#~z<`l6+`OTH zt53Rs-xT$q4~lHPSz;PbUzcP^m*Gih*SFDAs>AIDG=S)EcqUob^0GeL*uQLfvQO0k zb)56M^;7Be{Kv|Nzxeu(EI&_98~Z`Ujyc!=ueLoGp7)>u*Kmb7u$kD~VFg4SjfFf8 zckfh(OfBc$s7f=QiXHoKmDXjLk-bl_@h&|N+pJaI>-kAF`3!0ZWmL>!*X}`TIWze6 zmymrQG*6>;8f_att3wM@SVU8S;kSvSd#9%l89dIY+#dq1B3nqJ!x zI{C-*_$kTbP0vTDX2!iv8p&vj=`uJjBcI38Pva}4M9kxO^L)tNjYr?bqtjpcOZ@u^ z5>KRQbk<2&T~~6fbU$P5?`CUOmn7Zdqpe~cYQ@?szK_>EKF?pi)3g8grOfPJSw;3q zwVpTGe6kM5M2U&wWh?XaI?QYbE-;h7b%Uvu@xKhJHNVP2 z=Thokh9i%uYd6rtTundBt!#gj9+~_Q;|Aa3ZaPsBi``Q0haDX#PW_D3yO_!rDqtr0 z)!jH^H}5}H4)#3@T(P=2x~v}JncZ0KH4I@2JAR1&E~5^7&AZphMb`1P@67PH(^cF9 zC|2QpC$=r;hp}h(VDq7)QHnc?#yYUj3}VlxODz32^grftg6+?lL7^UA3vB|2DvMM# zpi><-Zh(O|$MEBBb-m?Apactzje0J0rRjr{A1LwZoIn(yLR?-OGtAM z#3*JiWK^{|VQ$1enybvj_oz!_6J8R#2H#`8<#cY*j}&;_opx~x4?NA%tN7$tb+PC~ zs>g~I+4(kBy^~#Qs5RDiG}SxPQqTS)It)6&<}Su%u+bT>G90?z*P>&r)6RvjH^4_; z!8lj2_Ft@>4Q}3o8Qm{(v~aXG@~z}0O<15_g4NaiH4wWt?7q)$?s41&BPy~~V0G+w zT!;m)76*^<-k!-R1mgi})LHGmCKvysyjBk78*3jrK(O z<^rR*7Khs*X2$)TGSOwD+o%dO3G9x2%=*HR0ld5$ySKqyYKo;d>AXvU8GHQiDzh*4 zW$uT4v`#X#GG<-YL?PYJ&cOiRb!IQf_@ke(m>mCNqD-&BC+6r_j=K6($nqY|bTKP_ ziTQo6GQ1ByT)do0o|E4wRi=J5l<&8L%e`>9*Ri^|mI&5TDa{}FqBMIY_0Y+O9yBDr{S+#fX^E|Q{_ zatS*><8$JUXQ2i}7xR8d5}$lQ29=p_-S^Hpis4H3J zIoGQ>+@xbI_P~mDm$Bxu1U0KLKQ8D9jXW1S$J*Hw`tP>z=+BJU6z>_m%FUg1oNIb9 zG45}=oUZi-9|+Z`DP<+r$AqeqMfLXbBUY+(LgDCy#jr?)P&<|89I8 zaR%3##du`J5p&B=<8FeL%%|8l?opY}K%@Mo$U7f8MRoTeZR?_(@*0T!-|Fq$x)(Di z+ST8(mtDqW1Dmd3)wmDAd$ilBc=UMhGt}sH=0y#XT;>LG_=1s+4&;Sq*C_c#4;-wu zS$@CV;~u%qJ&6MMpsc=e)-^KIT`}9v^yv1z#(l|#IDmtKJwOQ;&>gJuJr)vs)P2EcVh8)^K#xxJFO!^Y9iNJszm>P` z!3z&LP9$?Q<|^sf@&EYL-~JNS;osSLuWLTYKjX2_e{SQ+8yvAW_=nIctTxHGy}_J=Xq+>h5uWJGdS_s;a)q@F@uHY@Q@F@)5p#^x(j1x#KVvyozcvj zAXEvF;1<@1OuZ({)MuYStd4Bo+0mW$^*pxsg0C+?s%P=C$Hm9i35RUND`TBPbpA&@ zI97J%V$wEjZL1t`>QBS{l9HR@ZH2Nq*4~~5LvblB6Ophyz zYYH7IR^R;Kx&xD!%kyGwNbKkLlDwxoEhX@+IfM(0i^|h|;%{S&sjV2?319CYA zF*5$RQ)qf@Cpcpjo*3MF;~ zOdpH$M3=&QSndK*VkrdsOnvVwY&W>#3YZqF1wQd?iJwEAj^)X&g`?OX@zb-GRY<3mr^Es0W$T3$LySK!hQDZ&L6#VSv#JVj+>6+q3 zdEBO`NFA#Uq8}ogM-E4BaX61o!q~g}YL&sbbIlcT{2~n?j;s78lOvl~=1r8~@)XUu zFZ;uoXBQ(9mLF|S#T~dm;QjN=ftVx9jKLZs|1%`~J+XCqktPpc4s9S5@hbRGZI8H~ zd(HB=s_4hLg|Ek~jz?)nIUW}KjMj7Qt=Ye8qJjlC)1A;P{V zHpHETR>^>3H_*86@liF7s79UhxZru*^(J=o2#tM}@tEVk$B4;&**bP?Y3!`-hMqS& zlk0g$W`1%dsWOGOy3$onQKyJ*pSVj!?B01RZ9R5Eze?9(beo)G$#bmuF9q?x$3->n z3;0j$@EMYL>i;9^PT;j4tNo9gLdG;mgrrDPhGa;D3MnNrM5m%oQbeazk_PHDsgQ(1 z6iKBXnG%w*Btwa0%uE>ynf*WS-(~;LbHDEW?0xUO@BRC&Yh7#juJ2k`@}0NX>6!i3 z(nYPSv{t5lez5mFFq!@`uXJg8d`G(bNV(0^TXXhcnfBarT^C8Np5*Xpw(N@E)1Li6 zJNBbRg`Wwl`(nBL*CzhdSCZCeva=sadg?h_W-&I(X2<|o z*^~LOs*`h-LH(zwijDVFnUW_x8K$e}afyDCkH$oES~aBR^{s}i%(^%S=e0dwGwl-H zkz1#iAIMuUKT-KuxkqW57#Nc`3P z`&6VuVx9&-Sv|?D(^q9xR!{47$G>c{ zF{@`C)-I}FFC_lWt(ut~I1z`HH=NOWnlJu9UgDYE{c`Q>S9X^)Pj?4@s-UlJ5UAY1gNc!CpP_GQH~u(>7c= z2*BHt#O7sWo~m|yB)h(ISMZVKws+YZ4x;yT?iEz%*oj^GgZ@9XD?1>FW(?@TKWAj@qm;*Q0xUw>rfk@oSF=RXjcp!D*Am|GhkRwrt7b;qWhS z1zy`bZ z`^sI48$OvI`otv7k9L=$#!nUrm|H21cxQ5aQ(CMO_eDiyI-Ygn-koM$pT+!BQQ1ZL zWmBCm?Uft*&i(DLxswfjSfU-Zbhb@p`B~|tz4puryH|bn>Wh0{sLrZWbTjtL>)J0H zCuJUF?Y{Gp-e-{_k$)GfU)kULO_7;w=(|OIa+z;rv-it8elhufGVOeSJj<<%IW|tO zUs;W3=_I&ta-99iN6TgJEtmaAm81Lm$YNv9LJ9Dx-rpXuG>P%{?OilOMX4w+r2elN!Ka*Ualzp*~xC6a-X@fL$Z|F z`f9dp*qlA*DNC5UkMlhBao#M_0!bXVccFZYsFTc>PU6E#t=Rpnl&unfTZiXO?>~Ep z=RIZM*7uLaxA#;@>ep%S}3#xxEn0y~hsy9`SyDUq6 zURdpKdz_!PUou(j8>`mo@_8U{IcL$yBFX-R?UGk!T{dmab}IkH4m&2TK0nKHYio{e zrRQP8bn)HkC67m!kI6>-w7vV&y!>%_AlW5G_zB^Qvx>)bJfEDkIkBA6&mNow`rm%{ zV|h|J(Y9&!My=;7dvEjRpO=O%G$D}C^rx3uE|hHL(hK#NzXx=3RF)oW58Rcm-%-@5 zN_&4AqACt|Utsd*OJ#%2g>EtrE2)=cmG|4?Ok+Z+@JLC zn$~Qk-o-??O zSm0kQSNdkV=BTv$j8^ulBFy{x>#lgkVvg5kMYqel>^6Cu|IJ>S33@7U#awYqQzw z_YQB!<8I#1@|=52w(5K3ks@lR5L}iAksqpknyNyhoLlqYWJhuG@q*S!9-HTXcA9Lo za~KXNUOTv*aB$x1z*Y!S$w$g7#DhEJ{kAPGFryHD>-0Un=WpnN<07|zNqR2|o-=K~ zFKxc1D>29A_sPZyfX@G$mBdag1HpYdY0ANIHcy#8^SYTA$CVQwk%c&{s0+{I;PS!m z_4W6seEs+`=bt8B?{RV~a7H?PZeA0I;g5NaKTZDY%2vzss^oE94^>}#?Cy344xze0 z!^V1ww`OlIK5g2SJdOBmksi)@duBTD?4(9BpeDSJ304C;foZJMW5eVjTG*}(Z09Na z(C72TcXGLo^BZm{b~DlEX-Sjqlkv}!&4W@rStk8RGJmj-aOx+My)Gf%X8wE) zq;dW}f3n}dH{H3RYrnX)Juz)KG!*O$S)NaKT`+;Yr@swnJgTVtw07JdTjv}5>+IbV zX*#^pyoC9?4*e+4N-ocDUoV&Luik3CX^JNGI)5>ZKcaQ{<&h(+1yABy9KVw0U2@v-7kHyHD7y-j4(Gl*jg-XLXl1wy);S zF0Is>zdgL+lhgGdK6$DWr+fHC8pekmTjq6m_x7!#H5G?1bT6OlxmUY-k4b;bQ-B!Y z2EQR|y=u}~vQ?X-88mk8CD}98_fuN~JMGtfHG>o1IN6oQlQkJXdx`|$eJkXbRx4gy zx9i)q9r2EK#I8N|$(nq-DC8&U@Y&%Qdik%(uHTey-&EDw*{GMbAI>f#|5^U^d+i%t z9HL3c$~Hyi8+Vm#_)2N{lI_lUi!AOGjw0Yo>}gmjw6np}fR`?fw_!dtX_; zr3-TVY~qLVIiF0L`?LyOa5~Y4Pu~W2g0(&E+^GE&6(1_Y3I~|NGH&;$!^}bsUVh{qn&&Y4y%Rrq7)` z03`aRWOGk@?6D%A1t#rZYKk3R-Acb{LZ)_3vp?RIe>T6YD)ptjfZRlf&<@2$e5Obd zisTyqksPkfznt5yoRtMWD*L>D@zvg4(fjg7n-z1dS?s%PvW0HwpSdX})nHfMQBU?G zp&bjB!z`cHZjeoLHTTU-|QP$nk08QImcC zT<`sX_Q7W9?aNz_d1J-jS}b!(SH)|co?L(3$Fp0R-?u`#3GU0zh?~{rcnLmY(H^jG zXGt!Wj_M*4XTCmZZ=b9`mLBYvZhS8reoPO4|GQNubf}pf1Z`QdJv4vO$UXUP{Fig{ z>2PYyub(9EV~e}KlTFYOhIy?UWwqpqJw8XA{k!Yt`+KqRHQ^gq#bEo}RK2`EgbAYI zG}yI@Bi=Njr{V|w6V4wxpxoz}{>JGca(^z;!Im^XZT)mrHC%tFvzboGcU@d9{>o%~ zRZq-^bHXT3&g%TQI~sMXZ%%swUazD6!xJvLZT54c@B}=QMY=-~udXMSK5Oq&YAv z75%Mxyek{`p6FjKw)hIGl@@g)G(c#aa26XJ1~GmEWLz(ERtEdyjp)pCj7e zXQWNnw?|DBTP~|&0?`in^?kA-M`Q(m6@Ed=<_tevG(2xv;dAOTSSqH?i|TV)uBxqh zUFNtwv-<2jv;VsY;)X0M+~Y^BmtLC>r&-%fo@HI7 zN9SdA5`Vs`;%7r`G2Fj7VT}6puoJIq&7LfeF`?o$6GpOAwp|b5*V;+nNvGj4v)XUs zn{(5X-=!fJw+pZ6c~w^bs;vCQy?Sbzba=n<*|I0ou3ph~&Dlw6e@f!VCG9U1hrBN> z-89*)o_;T#Jk7tEE8gZKF=6iw`M4!^+h5c5OQw#AbIS{HH81LwtIG=i{lq*WrYjXW zJI5EkBBxN%-3n!S9I0ahD}~K z@j0$&O@7<=j_K$2&8F*$$MJbycI(jzQ@ARP6p^yWsu?Orhh?R8=AAUR%~L@(~@{y4E= z{?b=h^t`;sAA0@5uHP&)yomi@QuG&2oyEZV3C8Mfq*|dO@#3lm1u?d|7zXpR(OoO_`Bd1alM>J}+$R z6}{_5z4v?5WZjzwbWO*loo6TEKPOG=ep`>*L$2+_>#G!85r;zTeqLJs>-2qA*Lg&D z1y}u0I`zh)loxfCvo&kL6`oVBaK-`4%Tlg(ZuyYYe|LM(6ke0RR`=4AW#7|Az> z>D}7n&Sd=Wu#G34m@Ca+Jiod|af&m(MZw?54>&+7V8pH^W37oxw=|@=Z1F4HEGB-AtqPng{~@#x}uMl_GE3% zjJqOf{;kh-_&bT~uH-u6jVIIEg?mpYFkp3U*Z$fg$zoF-(9RO8;zk_b`u?mH{&_oz zFaA;9@P|{i;K=;r_mbzqy>ei$98?q~8r(M>7PIoy8)ku)&DPI7p-X@7N=2(@PCM@S z@Raab^fCM^ z|Dx)6L%(auYQr~Nvv=Vc-`e$*-Fu z;5R4j!VkAqx3@J*Zik=^~v(WIm#R7>M>{Xg3-tvp;@>UdJ-_7ZcXn0oHD+L z+s}UH(QJZq0`Hzy=5LdxUYIRCt1Cabh~eiw?HC<%`18)NxTgmkNT1RRyI-{$weby- z>&AV2b02l>Y+K~8V{zK9?eUMNiJxn?f2FACfb{gReC_wr>_hWtGJt*i&L{JWA1SW< zK(gJwC~C8!5m^>4ohs(aMMBH>7Y8$R?(X=Z?gh$ZYRc7<&iyt!W~MW|Zp_DleP9pF z>LVP5=XMgMF0ffWpdwf`KkxsO`}&N&gL`mx@8m?l-}Uk0bei8c^YW_x)(y!X0|jPe zW(&Q(F3tXXpWl*%@5;8^H)ZbnrXJ{VUwd7h9FyhFzWQfBfsg3$XdA@9;D%m1M&m(SLI=xEU9is`X+16 z+mpsUkuKfeed&}jyWz_8<@d=_Mc0`E-w%;8?N+b!f$fJQik|Q}tjr0mfe6*PJ4f}C zSXa)@{7xD_staFlSJ3_g`raY^EPwgSX^ouQcu|)1kFCwceY~LcvT|p&hJ2d-V?3mb zd;N-5?{Cv8VYdy|)&pfvW<`>Xdnk23#Zit<4%Fj-D z+MSc%&Pmi%tevN)9%5B>8RK4MVqecDADN&0Y1ZZBo~LHZf1Zu`emmg6EQ~puAML0A zyLH&Q-D3hPjL>^lyKK{HUqZg9@A9F#EY>lkXOfAP$t*;i{eydFRd)b_0+8;mb;T+iGTOXC5 z{d(xv8JwPvWz%-6GWOx(v|aNIyN5dA1@6;N4VKT(^EJQeFYBKYUAWro+czxxJ?(DptJ`RN68$AC4XPLVXLJ4x~vOtGwh1aOZA;O(rMiDC&B_us(Ez6;!JCz<^O8m z-j`ndt5<#g@2-FBZ`d)-^1OS}pF8^;YmK!KAJ9`h@xM%tPNFppRZRhYD7JlT=+-(# zmoF`XfBsXxm7@tJk`JCeW#e3j5yd->l^@+#0`4k!J1l0q2aO7(9^|pArtNOTfk(8@LMJZ%%HjmFF)}bU9c2k$)G%-%-qdQxWCO(`Wbe%7bl-xk}==)60Y|Po~!u z7wb}WhR%Qb|Ng2r_e|09-($~QpLDNFzC*@Wlv7<$+tU3AQQM+3(ukb|ggbfCX;fc*@J+P?f%-dXg1Z4Blss>lDntn~bz7uBtDWoXRJQ=JmiXSRt&_3Y%nY`?u`$iUlLwO!gX zUv3qTZ7-eI{oR_x&68TW>)vegX&((2_;OZPhuP1ww`WY+sxRX5v|FZP&i6G*`=+E# zqyO5ge@LUxN~3?8H92I8Og^0*$L`sr2yf+dSH0%3{+f6S1C^)4YsJewttadY+f@YR zl@SqN(F)w0)wq9()fR2V)F)R=yVp*4?Z++izPKnK>F<5A72dN9)@%ppqIpF8^E^F`VOeey0A)`)#+^OBN>;)YWjWLW;f?e4oIh)_ zVAjN>-)AJ@#Y13T)^2!ByJCwh`A4!%-^weWl#DOye(&z;7o1}Cb&AbQMlJ+`=jam9pNs2>F3W*uYZ*7(HXzj z6k%_Zq~DNDUUjk#CK5cDPRW~095=Q2S4kG%_o$+YL$Y%F=c~RxAuop%As?HcIwh%_ zlXQ7n&Y$CXv3(daDy{e#(DrSoJQiou{4BLl6USiF|C{c7Ig9q?qSC#m70|~{=T}JP z3+5*tERwrAzbcYEtiQfb(%L0tVymPL>sop0`(851E|Q#a03XYWJeYm)c%V5lPo&#; zfQx6rO(Wi@3g&<32fon#mbK4XjoS9ELAqys7IW)hd z`nAtgLHcO9->%ibcI{!R*%!-Ou+|SL>w|QuCtlH&-Z@3E&&r--7`{FUZ=a9(pZ+c+ z`dgESJ|@pRE41L(?XwHQy8fINT%E1EGJk{1_M7srpLfNoNna}O5)p|D>xu&$)(ib}jn99N`=Jr;lZ6!nwaWpL9pI zQl5lktm_YAf)(SulWQjrbIBx4)du;)5nbECc^k}h{ZwCRg}>V8UrwrDPNHAyyG}k) z9Xzz`0#{vk_{bzgNZ0l^?#UBAy?cLQ>$lcqm({F3oh7i-NcMzc%F}wDnT*dWmOrD9 zXSM6#UuP#>ven)AyGh2T^_lzV-8wst$_75z=zesR+C z(;oHo`CA1!iV1o=j?)+Z+=^V3MwqK~R=e-aG)jL7UIjmNX0OVA?8~#K=WqHPXTanW zeEVZtgP$ce{DBj*rYHB?r}rCY<;$E2BV&;9s;!+fy^f1O2b{5S%4A_N^MBFrozzeK zrt2|B1EJ)LnemJZ{O+@+ql(yZGUC;BAb`@ zamA!f&f?HTyOM>n=qkA)#)rCQI)`l!Z~AN62Eo&5KxZ$QWbdxenIw;;0Y&&xd-l7N zCsKhi-wBFl?uyLmUi)d**c1ainM<>J zH*{5ZmvcN^1w>||zYX%GhNk}a!XmfjyE|+?JwfYs*UsF4XTEEanr>#Dw&v5S?qFeU zot)n?#d8?4(1Z>8O3l-pSuwY+5iD2u!dAt9TlRF;#q0a-VAZ~+)mtySw{9|CZ+Z_t zTs_o#zoqx^88$RT=QaH#KGq6Jf0?fSxoN`!*>=-T%no?6j9d@FBWVnbM^0|nUz(Qj zQa^34^ZQPq+B1J-n$3=(yzk6^z9TEYbv}68K645cP8J*V_Fi!c%v<_wv+UIt*(N7b zy{F&YCI9jH*5mNr*DkrZhgkXU{J$0259h(n0NQbiq4b`Za-*kIbUh;M(JRE4;G{44 z6z;WPGMYDwFjtx?rdc5E)tS3&SNDo!y2iBfxc&N3X@EF>-D!2!?7OOW^aJmJwRw4; zy=<~M@C4I6=c$)V&(Gi5(U>r>n=fU*)$(+eY+R=Ks$!cL6-|zlBIYYEn!R4d$BJnl z=;5S=dyDT)6F1ZC;n;?=PqF4w#g}H$t=}rVvvu36U2lb3+}2S`Adr=&g*eX{_iIt zEx(8J zc~<`M_^iPr!|=~**~}6U2&?bZkgnF%@%z%J9Atsd{LIgnL!IrGJkW|25;G~yvelP z-<1`S^S#wmD<#f0cTCxNis9$&f1msJNWC8q6z`j`bbCL2{gg+U4tYxc5;Nrs?T`<3 z|667Q)=TGJmd-vm89%Mq2m*Rl)QuqQ_56B8!)v{qpL^xRaDg(vCLPn^T&CABe) zXsv7(j{zw%x9dZbw!n)-M0VS*?a+6%yEf}5*X?h=blOP^WI-QD-<+^^VcK_cv49%L zm$E3kWHH`crumW(n7PVSZ%xt{CFhfxXmeCO&j;2w{`pGe zF(g2*@nf+y=7^awcNx>XQ}63SeKudcA9MGdr`P6f73PeD{75TvPx;g>N&WKT=M&4t zzMVDtR5s?Ft)|#QT>^7PUo-|YhNdS>ckVu$uU8lBF@N%9Q=q-$wS^`>ZPE)HgJJUO z=VlTjHJ!dub7^`DoN|& zdPDD`PfHe}Q_#{(T+@8qB{41p9@BJ=Tfm!h9r}4ke*J7f7uXq1? zJAcpot5bI2f~#k1AdKR>xsv%~lfA(gSNRfGvjelW5|2-j)stQMBiTa?a5{Tq`H2ov zHH|+{A59!l*Obrchq$rV)p`F=lxDW|kBZt3DmwXm__ryW&cNIxuc^yt)lh-ur%Lv6 z*@qXj28;GsFgZRgtNx!n-=fwkCrs*cj zO$?c3Vun4x*A|`n%;rzeS)h3)JO6l|7P}n(ko@&jjt;-CC3Bq;+oq?lOD9DZFkTD~ zlfu=FL@a7WI=|*D#(r{I2NjlEy6S6-HgJz2HtINP7-~9~CE34ZFRw2Cvlc4AdhW06 z=gnf%`Eph{4rcQYk^|=R*OG~|F!l=H)!X{v9wrgv@4hX&j!|yvss5_viei=uS2Pvt zd0G1x^?Z5OXw7~CYkcdl-4C>HKHGX8+WY@3IiFv?YHInvio>A=&ntS;O^G9@*9k** zppL&t;%Ao&LK<%>Ub$;}+?V|SnLoX|uW*%hQC^?@{%b3vuTn?;@6+nDr#g}8`v(_U z?weN2YFJq@we!2zX>W=4Uz}}uLDprlKI&vNk880htKl_OG~hg{7-keb)^+Qe!z{i( zUH^BlJecm=6KWQ>PO|>X6m6f?{W>@9`$-nk^0_3c!t>rqp5v5P2*5T))>{_S>1=!R zgp0o+6kxp`YxP(od!xT?jp^%+Cwt+XSP|gPWk!4U-uvYnk1p;xx%D)O@Z#23eaMLc zcNHI)t0Y6vQ@rGK$16_};%aFeEKxm^=Rc<@m$egN5|{M(pL_NC zR!#P#V?^)Ml0|E>4ijEB%mgDEeR>Z;a`u239Jq-;NPb_N zB>Iu|woYSwvTgdfb?D$bvlZ`6!%gx1WDk=F&F3+f{M$+P$SKoTS-z-u#Bjl)SHE@#CK0Q?bcoE_Fc6;#E(q40!Fib76{N~$)D#^x1KlayJ(Vtc#9+CA-b69@DHD= zMqQLGzq$zMA4Tww=8b0$oqxQ~|C2?!vux_>B!6KNm(3oXzU!smEq*9$mZnF<%W0`PQV*Q0HTk?@yAz ztnv~wR-BCPG#vdnH0ilX)a+Ndtj=n=l&orp?o6H~AAfTX5va(B9;lLIFzB6t!`_-M zh(w)>d0ac@pw{;@6MF3IM(E`_*{rpvDv0yNH^`p7ZSn*=<mN?t-Gc@Cv*9JlKWa7Z_gyXYkI5tt|P^~lTUYV zpKlfPpQ)4_Q0#qhzhRmT44TfU?Ksa=rNm5`dE2qiPlugRAp(^XzCGERgJ)*3=>kp# z8RvW~GsU$g`{=#n+sz7wo&9z4A-_pdcmqea&R9^+{rqs=c*p$3+j?wLM7n9ux8+B+ z3&S@b{S#g97y9`F+6mu(>PpYdma$|vWUKD)D(6cZu#fa|Z!t;yV_6W>x}miC4%PgA zo$Sx(fpL9WI{dTr{s%?t-^*(2?sitnK0WrzbI;5u7`+DgXA8DxAIjd{(4Cx@PX0U% z#ricv-3%Y67T|T^VVgCBulmiTjXm*=K7%(O&_2MAJFXpy0damiN`-UdetQBwbN!ZA6M$3t8qwOT&r7Xj=afzli$9q-KPe5OcEzQ{ESZ| z(T}zFFf>F&d!;GP3j1>Qg-+@oY?4Yc{ zkFwHw$yK#~)8pJe|Lr94UnOrRVhpLDn%qy$;`}Il?2vM=uO{tJgxu=p-mLf4F|WVh z)E}tp-_pi`?Vp=|pWXf7stlYPS9Ns1!Jm&SA~PYwRHmQxy6EAYR?2)7o(!`Mql&iZ zJJ1oWGjz%6v1nh-%q^AuE-*dKsd{cdYu3?H{e*LOOa+k@KT#BNcRmo40NY5n)z7j9 z_LNCNv=)2M3|sxs&ZF`4>gRiWHa)@Y!_(h$^6;?W1M=o(JDr*Augu=LW~j$v#W|}a zo!7Sq-`eimDm#ZAYBK$<>8sNLK9xQGQW1&nP(AhZ{`5)mL+?*3Gx}+A|4x>K)ZtJw zXTkDN&=kF#y2~CUL8^Oqjx{-`nF%KbR4GAHSojIt1s-PN{#5F6hyD4qY=-+Q~+XcYn^MG0)qHIIE=JtMwU7a=FQFd^|J?NW%jME{L%K#2Pf2bOb;}>4(_NM85;9wlKW@ZaZ~o; z>hAWkWGb(`bc(Gm>obh5KO~9E`#uiy^+{Y@hgpc1iH+nQ<|yAHwXJuoNb@noL3>|JA2iRmy12p&Jnq>=J1YHDlEXuxp9OvfjH(8$pldsw_Cf|WcdjBlaeyx>xcKb}fV`3LR$5GkJQ+sba z_MZNp9{;7`*OeaiFE(Ji}knd@wVjev?|r2Rm+iHTGV6}#OrgVg*cek_fUng z+ke?^*RAK&?IS1u`;)$M(!;M>3CtUF!)U3g?0CwM7_p<-VCPJ)U_)M!HM_R=RbRe6 z`QDq9Wk1%J#H}*T$BnA2HdYLzDdX zS^?|CI;xoeV)9Jp>;Jy@HK**Z{vO2d8Izq+wR>5T$G93*C9#&7ayv~qsd+1G{9sOx ziA+bd0!Q{78R)@T_iv@)CRXg*D(%tlVWc{RUG))t)bmKsUEr$yE{`0k1Yz*lY_D76fHaezTk&__2g-fO@nY24Zvr3r6cO=_u`+MdwUXT{hh2wktIPDOh**{Ky!lBm)RYKE=W_GZxK_`=2S|`FtMW%k2jGt~W!cFy=I7(`IQp#`H4T6U=C{DIb2y?qY&n z*;Ud_yoR%S;6CtaVjA_f{U(blHr}_V*bVDkw)FGK(z7DChPo)kI8W~mOb=VSIA+Y9 zTBCRtw@gKfo;XKPPxcq`UBh#^YV*T%LwvT~`tdYpRH!!3E}9^({AzV7kHx~$}rWh6ezN7$;|$31Je=>ykQ+ ze^!>rS*6#tD`k9q53EhDw@=qBd)=eB=EG&#rakOj#`>YKn3?A;<*%?KXMgUSJ{^?y zn815pI|B#&v9v~Rp)R5;{>|;WZIa`=x&w8z_fN7G;d}1XQy;-jSs0qM>-04{^N#Gy z8?)-KY|lKuwR>8G$Q@G!5=DS8Hm4cKN;L4k=9As4^{*R>6h(CzWBJVUq;Rs ziI2=(I3rK?$M*ScS-mHlw(#s}Z#rS&wdo42*3qXD{kC@78>cvAt>kD*;wz@Oc*Ry{ zbXwqIEtNHKruicIC-}KjnXXLEXQh+J_jeC1&iZQl>bz||4>R*XpcpLn=hJpvAk%0u zJE4h#n`TylXib;)kGdl)+uu$83^p)VGJy@eFuhzUtA@X1cB^XOo3nIKnKySIx+HK$ zwoZ??Yfo;Uc5IhMVM?jdZqY|pbi?!wU;8C#xmimKWYh6%M5_0uI~a;8UaBo7U+QDL zvWMyxOxJW6h%Y-YJ`=_Awm8i&WgS7{XIz=3TGJOLL$MuX!wd+S?CL$tvt7Nfz3v1* zeKj(1{Y@$r*ls2=i4#QAuEi-vm#35Gwfv!;FdlkB#0jZ8^CuvPd>2r44E-sDw^?L0u{C7C#2iu(=%l_$Q z+pDLkx*i`aHr_E?Xa|d#w@a(=JGM;fZ*2|C#eWmltK zKJH9iXzA7y!*jDx^DRPm-;*tbI(}i&YjNQj?GjmrdfBaM**)14(YCnHePVf;J>yyouYP za^~7VGjy~JTLDv{$9H5e%!ave${I(7M_%@s9;3%o_sAzE4`@>LzO8`i;JV+wk@wrb zCtl#8JH+ji{{F}2E@DeWKcqiKhqX=kZ#V%~o?-{Rz}&n0!Qf4ram;AGLZ>^)zf zzU%IF4XS~UrA@FQ8Jq0oq;|PFqBFNxTc<95GK;K#*(4@49;owMV=TP2N~Op8P&bbVM^XP1club5)tb9+~K>zKd~UGP`fU3scAm|!}r!sBWC;C!;O^Osj(LA@aT zwx+L1=J;k}HM6D7==)NU&^L>DzCHQmL-WtN;7s=2e~OxAN&}0OsIEqjBX^o=hy-l7K5pFoIhnEzbzB^L$dx`vNlK1xmIQln)SZe^!HXsqgh=s z{cF;`jk2fay~0JXjhzIJ^La#(jo!JFCPeU*KF$h(axQ_Xeczlk+H`Qewpb~f^^UJ; zZ#soa?6zPw&{R)#O0{5hO4IBQP7_UybvFHuY3{b^t2)VBvb?4aZkrw6VXCUVFVyoR zSzavrahiyJ0%u}6|3RgP_d7e^cS*W+WB2>-bV}Z>riRN131cZ=oKBH38OR8n^5`E~ znmZ~7hpdYZ$n?_Q^WviX^OC84QYfKrY8L8;Q+%c$=SRIhtDi8D>*7}9+O9xX&STRw zEcp|RQjF%rUG~DPD3j84DDIg)n}_AJO)UIFi!G0u`dajSiapIc(A~LrKf8O@+w47) z)a50sq&+(1OnG}+brQY#Fa)a)&C~1dn&qk{ZhtGy6+3)&iYA=W09{r~`F7GYz0JL; z<-nhC!SV4iSmd=%%@hqmE+EP34hv6Osrruv;G|143UKL6ImU!Gp%NmCkPqo3dDrUTRPs;RGP`g_54g<;VgePJ8fRQDfb_no@}@1XV)?UmDkOSGjYL9c3GjF zxM+WKwyxUA4OdTEqB8-eG%KxO0iYLABj$VvonOVY_X|OOAPO&`&5RG}LS*U9H<>f4A)EGt6NpI&9a+ceg^A`*LobfO^ZtqwnoIsp(cG zkNW6Mpa0z?Lnsre;N{<(ROGfdPcpr!ziB7j-cExp!#bbd&J%Smk!F|%xnx={YQUiu zrwtnpgJ%r~-lQ8tKY&@ICcDm?gcnMp&`Y&A^Ag9*M66Zk%RIZ^x65=^z9eagj7@b> zk$-cN+$QV0Lo)mCyq*3nbBuPHp1btF%p2aVyGw_ipTce3sn6^WNT3cdwVO@z1&}7u zly!w$LR9~iHr<}ST$`uS=j{|qCp4UrJWt5?$yM1HeTv^n&kyP2x6^Bx3S|Bplbyjd zIi&AGmF*EHT{xNDJV6W^%JFA76zr?hU zcz67K-T81ay*etEw2u|mC+A$yF~jK3(|*6I?>g&SM1@V}BsnL};Je8IZ|!kQyW3UR44@D~5tE7$a$8~Pdk?qAb8 z$nwbiuS%c?ZrzTQ)!*C%s;Z&uXxJuh@fzY_0Fq+nYe5ZvSn@K?%c#K zeR;f(ZoQG6!w4?zj?Iix%QGnjTb6c;8TI$+M~ABYt)CPBvetNSqo0Z$|83gsqneHx z3Fkhgk^0Vbs+nOs=7juU+C_)wCtz8sE#J*^=$ksSU8Y)O?#Su=)_J`T>#sg%RqRcu zxO2hP+?-!DSNFSM62~rrjJ)I(Owg41w0^FzrW$}&g4$*Y+DYA-C@ z`gdwju}N%n%L{DCU54D9M;JNp0bNJB)JFn-J^)&3=>-7NNyb?RkyCW9G{PEQbr zkfs`oEK$9EhwK7W)NGaaXRlQd+2S3Nz1|&iUa!aM$#_hcc)?` zL%wQW_qMA&)L_R+l6)CYPd9E%v+Y;;n%WZ$;lWhYVGAbpj_wE_$#?$#4h&d**eLJH^%!Lvog&A`FWZO!8|N$ukTmx3A5)9bOXy}5AM67PZ$92!HYO! zN#)HX=yTgKI)|=pm$65tp+BvwQ`=X?d2uo4ib+ZRACkCsQj@2zla`uiV;-~lk3PPx z@0q#e>_WM}lLwZ`{w~@&>c+)v{(`7rblJUP`s-%0=?b;Rx(nCos+{YwN-GXiG!Y6mq1W2)$zbRiY=RXV zRjWtZHTIRRVo1R?UD@cMRjU#mnuL8pR^s8WF zT!xP0pBc_^1~>^}iLdGWrzE*pWA|E$|_yG0RKfH+<{z?M#w% zDiD^4NYa_0uk3#hcSgccG|Z{zbX_nA^FA=o9!@5(+8ZXzeo@k7xiQC8BE*s^Xh%L} zqam{2$ySSSU<9Mz_51ArnWEo0HS6=6$rfGSJBl&&HQbX{o6}^+nNAHeRyQ<*Ox}dg zV!pp~(%4-5&v8Fqf$6`jo$%7M!>Mb}@A{vUUDO9=k{F~;-FRpTy_TiYUOaSCCS4Ow zFH6RF&U_siU(=o&bHBt0tn?hc_n7f)hK(-475eKWX#%$D-nu=lwFxQ^PB9aFR_#?5 zMd#O1^rsY*|Olr!j#GB8(LP&4vG~->d8E{Fkqh^O(|atwd!1 zN#f$M*`{pQp5)Kx=)JwGE)zB!Hf(LYH~)tLKmPjT{S3dV=TLMbr!(P+Mw6xZjr0an z`|`9vcl;kFO%P#T*w+{*@=VM_^M`fJi)Eb8hOx%JKwco>yo?<3mLz{~4|DqbZIK|v zfYs(voF1YwpcnQT$&bc5BXF_ypB&0#@t)8d7}#_ADRIT{`l55^ka#bh)%fQk1Wa^M z^q9`?1b5T=bzuA7%%w&8oY%0&y)T`EM(8ea-Zeh8DlyCP?7r5q<`ge6237!>ldyFW zxu_1o{qZsGYd!Dj-m$3$ia}3rpL7+fge!8>w92D0Gfp1BXR|WUIrV}^+h1ZC6~m|Z z7ug~ADC)uUcFGOj#gLPFfyhhMiKqU%k0h(g)P|YW3^01hLP*SA>&1QvbJVasxXP~y^H6N^vHkp?2IQI(`;;O zCfa}^tJOh~RXj}Evp3{C*Jr1&VIUhO6qsm!R=PK`H>Ymsj*>&3P^^I`E9OvFbt;EA z&TsI0BXS>hf>qO{z;g_C;W(jl>^3%zwu*r0s4I2Fp5u;8@RG}q&VKp2h!vN?Qw)ox zXkArpU}pFAQPg-x`r>bzO?At3kMg#0#vnbqKf6aG)a=GT{t-IvObSc}nArl;8o46t zxNNH`28S`OmW5g~n`KskN(X#g2>jnCYx;F>c zT}(-1;*UX~#o!Y4zNpIMj|%9m9<>V@{5#GgQ;DO!F8xt~m0|#Xq7SS?G?%JfeCP zYVpw9`|jrDR=TpChr)}frLEdJnMWWKTdaF#)%c}3n!x>FS8;pSr0?PK^cK^|WD~eE z6c<|$Lc;p8#p=b-ep3&flO?(n+vzLB1~jusbfNAI{Trv9%kTBd=@$|C^Gqz-gOk5? zwx~+Ty^{rJy>un2@Y#i?gW5$p7@e7XURUV673beVJ~5$G`pr@_!_po#cbVj!Vk*wO zw)ccEVhNgt@K_O#DY;nFG(faVSJy3b!ePJ_#53Bpy3Z$`!f(|pKNVAB&z_@By<2#T zpTSytPY+S(+smQeI>o9R_HM69{;N&i-PBL@6x=nOcN5-aqGAPk|LxfZo)NMuwihK~ z2$=sO9)h3H_M4vCwPJn#fS;SaozHHt47wAc<17dp3N2UF-GnR;{l^pmG$4RsT+dvGoJ3H|16#3H;d z?U^bq9}h#X{Cz#|NeiIS^vg~ZZ;Crqv4%#AE95M)E?LTm>UaiokumH< z`y;yW8M`U68qyRunp)|viXwSvE6hUi#=2~;?7jb(oX>4{ojPeP9_TpJ?FTSmNR;gg&*x2QgwweC=1K*l%VqLtukNN~L z>E7M{DsKb1RX@ga#mCq?t=ILvcMmM(-P3s(Im{LV)`Up$sW}}hXFGK#zc|Z25a^|{ zGR`6u3$c@Ym7EJtl;*+u@0jF5^La!}PID8n7-52X|L|A`QlJtn=VUQF-%88 zr|a|nb_NtpePo=Xi@7Sgy`fcf%7Cd9X4#&T|5i8ob@DuI%8u}%O;%KcHPM(pk16iF zIV>0F4(1E3$D4r-UpMVpzSUJh^-R)OEE&k2WJm)Q&})MKy;YV)mb!g@eDGeg+MQG( zwvexynR{!R$Xcn&LnxqFkV1Toi?R~uPWlU@IU)Ua3Y`906>Thf=Y=0PY5J_>ermpE zoOz$34*9)s+i}nhJw|#@LVV12JeOPbaL;Ni>WS7Sj19G$rc`H)nb8hRSIE!xMYCHQ& zUkQItdvT;p=rxJOys6QH4TFKmsI$B#TPd;{(_YOnQybvFXQB=^PWP*;A$9l!)Jlco ztR5$1F`R5-{)fXk$QQB4H+drC0Y49m6faEru4zwA4dH!SY^Hcx6?yPd;Yx zq#^P%MRud4rTbfDd2qR~>%Z9ZOGzFpaqm{aDX+Ywc_vOh(GU3Q^mvs#f)m>1UU;r@ zS^0=8M-&JDglWqLdHyrICwy+ZbIfN`ZxL&&(uh7pjBE?-)Qf;$D6diD5>vynRE*VZ zSI$1ZYC;7dcltEeYKM_LUY&CZUedK;{XDyClQGDp#nQKQW&DS8<)G`%+0f1Kqcr%) zB&l13ys?fj6TjBS{d(maeFy4@QS-C3Tz4G~_a(g_&V=k+bq?DIPfbkv^5ncm*S79t z30M);pLh3gs`q>Ik?))8J*@MnV6q^CBZpt1-^$Lwrec+!F-e&ok4|;g!W6)py9PMM zh(0ds|4Wi7A34rifINz(RDN#gT5p}~th_-4GFT?+;TWE}&-~3bd*Ic*`l$zO!+8X2 zCSO>nlPaM&@HrL6QN5G{%hBd+-C#0mBkTz#K3&j@^4rNW>$TM{WGXq!!n%u6&&i)d z2}G-q$PughZ4n{=FI#@FJAi7acH+N4Wn2xdz`Am^cA^Kn%NAfijG13}f)E?X7#s2O zetY@ue}=EdDm!c5iOMt*J_P?7X99}uL>MB1F&#imGJKV?$8`N;WYf_T7piiVqz4_Twx}5>p-anvjB!a z;x}jfsoyx?VXi4Q8MB*Y(Q1dHW#3or7?lXC$v(kmJV!2WwiC@t7PE(jb3J2 z0lhUipL83>rP?;KIC{2lUpd{4kA$?DaScx~Zx9bvBslg7jBO_7#+aO|zljS>tLeGw zofR8vX}!o`_yO8UhN>H~Pg6*s^!{hDL^i6L@I;)RAydf3zj_WW*HI?>!LbyZlj38O zoaLb--?n3QfwKeI!59qAfX@Gl*pjvS#@Oy|nlKJ$?WGcBoxzU$PVU_jUm0n0-RdrUj@vsF1FarNSmy z`6+w#(!P&Z2^$-k5MH$U8AKT>yl8j#tfVeprx!es4E44wyew;;bC;WH)4+~LM)4vR7QXL#oJhbe(R&vzi$pJH5oH={)g0&5XCnT1>ER!^`=9ldn zU9~fZm*{_3xHAN;(ZYR>LxF*di)+W|<7H1F`*zsC>g>wlxo{ap51wP!;w)T|{=omo zSwmx2jt*;kj@`D4*i>9-SLTEe9$5?_w#W6v%f%7EyBK+rehoYU{Qz&si(nEtrxO-qV((~Bp@DK^Iy3**uKsQ*nvIAO{Q;T)o4SlJ(1 z6K8^$buF{9L&kI^mY!d=1I<2IBKg|0^hR|`G-uAs`aRs)T3zYt{lv?=UsLVO%~TP9 z8u@MZ8y1hv!auPtWA4qsGECC_W11oY(!uNLT#)lyg+F#(q6d~x4W7K|Hy^1&!Z#1R z9gfb+%H>u4OuU8FkU0j7skb^hx0s^%x0EBjsqdT04Xb`j%ivmuH zU8KG?=9pkcI9JkK4L!fuTV{5fH-={`V{sm!^B?pklRn(ztR5n~QCr4L6sf>mO-RPV zaE1ncKghIyCn}6?uhC0tBDxL;eH56Z zD#pL)r=Y*%q@U4!jI#-s#qM%e3ryF1CjF*(xMo?>nKh?44(nLQ{SDxBGO&XO6k8)!df_fN~cWi%JHQY;BM)8{ci zL*-0o*9!f8_4t|Gl@-~fy{Iq5HLC}V4r|=XfgI{~yo(z2Sff}iTskF+n`61{(hM7D776$dWu9ed7hQ%S;@a|)RHVdxCEyIJe>+A7x zd^V<(@SkcNgH2;i@K7-5)ERkSktS~mv1YAZk6h%8WDLopuh3D|IJE#Qp23%Q29|6a z3wkixWZzDGgFoS$n=}C-ynV7xqFTP1K9dz6FN5V%*pE$*&SS zUXz8X$cZ`;zD?h^nnO@KTK(blF(U}QNrSEX<5e;nCPdM8MPkG&T z$Ebqg+{o~kX%~&C2Rl(@$HOj`#6}%|)bDXC_<;G+yb(c*2dx(LUwl8hlt$m3s;ioV zP64@}9F6sHk{&EjB?r?1E;o=lXWN{bTvP$Z={Bb(GsyEPJ#}5FFwdH_8Tvmf`%J=4 z7jV|YFVlSI3$TYGM)PZ~Y8T=R$%w?1GDh_!Oj%F$IQbXGhU{OY1+#Yl7;U^h){@xP z8qiv}hmWgFU#&IScM*)4_B_>QPpyhR9Q8;Y?B*Xps4zO=Rk-5jNXqfWJM0in3seQR z#e)qL2sc?)iUT2wl)dW=W0_ouk z0IJtJM}ByW-}fFWKyn;)WoV=G81YHe9>uZ{P~T$>&CL_J%FV^q@QuRwX&;G+AqAv5 z{*kfDz(CChN5;?c{PR!xCf*h;VS2-T2U`rIi7jN|VNJAGgt$R_Vxvi~w@9YgZliMc zo(Tn1L%^nYPN8Y0W^ zbk~d>IU1WiTly}8z~&BJyW>V1h51RzO`r zRA6=h&Kvo!H$`UR8|QC28%Ry~gHxY_i81f(Yx88-nz!ULUZ2+cUD!E`zjT^Db0QuV z4F-*h_-yfRO{_O*1)48vwof64rs762^Mjthf=iq?J><0QH9_6ti9Ik9Id1jBDF=i-{9sMWO!QRtTaecN) ztf_h}50RJB3wsm4{FGk#Rrm9oDH?^}sL0zFSVA*#yIy12CLD>a5xZ}gjd^3T+&np} z$bX=Io{v-o)-{L&vDftf9@TW5t48|R<0c}g37Y~2yH$~g?O-VCJDNFhM`co7@K0Iq z-%nS~*RdPov>ztzAN2fw@*dgWkNb|Aw`or@ZZ$wP9(*Q9jm{4egLgtUMtm=yV(qY= zSMHux?aE%&|N7o__(Ss9L#I@}c(Mer5#0kU;K=hv_X!LP9wY7@IJl`hdU|A|Jc(I- z>Nuyi{x}e7@>pZA9Qn&_U8kAoFjRQT$o13~;2HQL`gvq|PCYe~WVfpDI3}M+=6m$f zlsmohyY^k0ZzdAN!#asg)zHi^#iJ5|@b)4ssDydmEcndKtubp$$0hlGKdmP7WBcD^ ztugu0G>BhyFC?xC0Mo!wl8ZnfWY-WpHP_|R9UhGJbeizWQw#wm8{TH+zWa(S-(Uzp zIib+cncfFm&2&VOt-7WDLKELj+dL^P{zbk+%;TdOD$c_mzJ@m;`%}9&whu=Xe?_Hg za8ICp&I4Gmzh$n?O0Bvq&#Zs(q0A0r=f8?t_)R>NQJLSq$VHEjIk?Um(L1=+6o2Yh zmT{~2U_W9cs!}<()C?1O6RZn1p^Ft`)v39^nXXgc8t>_0#uWQ8I$j|MXZCyN_4}74 zZF4m4=sn@otffwxMU(ZC$=5_&y$3iKJPQsUpFY0A+#flWh(g9??uvC1@yZDBA7S;6 zXa9KtmHS)!n=GJe6s#BGuJ-F`2H)sB7#&DFH*|O)@Zwv!BaB=YVFDX&tw>&WP3HOq z#~IenEixfX%;-E*D2CY31YGEyl`+YV55?Wadd40!Md-yzfY0Ec;Oh@e_7Jk64+581 z6if%?7-B+^F+>a>lP*IIL1-bzxO#eTOaRj*$l?t?iG6TQU&(w_Jy~p4#BbG+bZ3|#aX{SPaKU-GS3%cHshM;@$o zoHw}{?9o*ZCjSgG8hKM!W(wHVik_ z8S#re$(da1WpTvq&`0Qz{u>qX(Geo%hxwX5JkF>y-<*xa>%scf9|ZB8yC_V21rfrm z)Mcd)!M?pV%@Ah{HoyAW;GyY`^c;5}BNd0-)w`%Ks)~$Qd33_Lqj67ah$?;}LcCvn zl=k-M`FbEp`c3$Qde4{%C{kB>GksGfU`#YHdx7S%DXfQD#o)tU(Ek_pikWGHNriEw z*Z;mr69ziXo~V^rQdW9X;fOok5~t|fG~d;$^mv~vrBxyQi zBEXVq5=}SL74nTuZVJfI`R8=+SpK6TCA*{lD#M~*Pp_z{sj!beYT7WmsYg6bdOU>F zIz-PHaQ4qk=iI0Y+s&iOD{~)RbXFG9g$qDO^-bZ~s=dSA#C>qanf@wV6v*eL(@w-Q zfFP;_4b*7tII+KONc#;J&sjOH&s9Tmp&oXYS|dFiwc(|w&z78C#hRw;)?hr;CP@&I zt=cszUpm}8MJ5Avkpo$QWqS?RL*&43@mOj+P7H&t4!#Ay;rwZF5=b5s2e2S0ioF`KaQnF^>AL4lB98iVG~jtL)JeU7zd_3!^*ie6au!-d3+bc@yLy|Smt6E>L0atrl_qjqdy*pNAWI~VY-C<5dE0Qsf%u#Jo6U)oD-VYo4hbfW%tkmUQb;Mr&IK!mO6N@ED?N9v@9z+ zt>|r55vG1M8I<0%L#BM{@VEW0nX1b&70@KPwIb2QE57EqKQ;efx2AkJgK03%nouYp8fwt+p&iR6pIY}6A z-^4Dnre?ZCp|p=ren>q@tjX&_ujsShz!Q7Gy0Ej4PR3?79of^FCI=>WXY-oDbm&u( zKRTJ4`T-|XL(yZUwtP_*Sx-6cD4wM*6?v9EB&-;5jW|Okd*LKv0xHZ@4aa9>#Ib>0~@!3{UBb;Sc z(t;!&mz;l;R-4#ls)UF7f(}g6^UNCZir0_rX;uXWw=Cedt(eKhmrpW=x2c?v@x$$J z`7G};!`R@Dd16`#!51A3Hj>jwe5F3*r$+btU?7QEFrr~fJT(M?=N1X+5oC9CdGV~e ztmHDo8#+~Ig`O*=;qXFOBx^i+a8=$}i~ml(IGs*XclPForjByF{T-9}z+&E>oK3&A zMz~%W6KX1$tgJWg@*=JF)2g9~O72MOOtz6dvCt4kT^+~vJSOX_X7iJ<0=VL=UOBPv zn4y2dq{om(Xn{@;J+E?D^9jZJro4+&^Eh|GV;-fVEUtuH&YB)A60pCgMMF(C)U&9 ziFE+~VnZs^b4dbFUts z`fd4B93se*J}(m=Rl;WYQ~H{Rltmk(S6E%*b(17bOWC-GX)Bv%jbXm(Ppa3xXP&Uf z+Wp3A$^2zWey|=)jdl718$#CV?`oFfZ!w72?v|p2>yrz)W8a%aFK1O#HLv5`boRWo zUU!K3F=zD{eXp#%lOx7701<~S8#*mxVXgI-s6h`t=9m{WYCiIOKAL5~lVW4kR&;5r z`9XH%lJWs|f@d8)EHrw|(KkhNOlH!h>n72!M*HEn&;xVb#DTiAOivt``b%5EWqMaVCbDwgqRZ)Jj1!BU53*|ZLUfI(z4|0$6Kr1F zXJbOdKw(S(hZBh8b@xE4O$FrP?cdqEqM6(VZxX8x6Hu*M1r(?7x^&kxWk`q0gqa3V zvhfi!<$_`l3=Y|ipAxT+&I;@Bm)>EZAha9~GAf*O+-lQRcnHp+^C!p5w$a_J%ULhc zI0Jj;^DcV&hj%u#SDizRVtn*X(YO8q?0@`MRT8oKh#y&1QRKiM;W^L)c@e5PS0SwS?@7pRhEO9H+Vhzoy}p9C78sn9z8m$Ovw@z zLRnNXO<0gKUYUhqWiU8!CRCtt;YnMCX!LVpb*Lg^6hgb@>lhK%4PIj&>GLM(jx1C?4S$imV2&#!Rl54Bp60EH5`9$5Hibi1$CyN|gWXK1fhNIRWUso_ zO?a_>&*>gii_}I(&n6!~DqF0Hc~&a!{2W%}wG-~46Njb8lpR)?ohDIOo>jmE5V`A` zukxu@%2NIB4kmY2gU~G7hbFxjF>ax8r5a% z1t%Rn{bPEP$djJH=sfHw7!Sy}(W`9&=X6CbFvyFHL zQx6uiMxJ%;{OY>JkD^VJy53$?Y0BAVWv+V9Mf6T|H|flb?oq{_VfgE-H35TVqVv2E zgmHA$Zr|AB0ZSLSK;Wf&rXk=>7&W?)}(d=4p>^>o;5Oh7{EHtfybiuP#{K;4*q;h$3x#z$Rv5g=GUd1fX zs|IOdOPqVGHZ^+I^s1>Fi;r}>s7HwKXZZ5+D3u?zA6%|+CJ%P68ilGAet@XURnh}7 zKc23Nzszg!mHrQb#M|Jp2i9UbAyy3C!Uy9OaiZCLRYV9aZCo{pk=m=1s*@G5*yT%D zkz+pj#%ZyxQ27&%vblXwEz?18zF=jdcoXKJiu}u9jEYBvf{7Gq=|#N)9fxAU#^s};8n2t~IXZO3^ANN#4Mq2Y87X6R*=|n^ubE5; z4^!8YPA$HZR~d1ojAK-TpjdiK{Mb(UkyvckoO623Lr!VAe9KCnTug_W!GnQq0RkBS*9FM2ZDYfR1= zjC{V9)p)Ec#>pQuZFP|hPKmygF}d)9G+UP|?zJfi;`A|_N!EH+-}`mnGkILDDwpyz z?8ac#iGoJ&n!luyHYORF-bX{}Drt){RYFvh)RW+E`rzO->PumUZ8zLap zYpd!+a~m^xQY=$sI{N3SMJA^CZ zxx8ya8L>9t=&w)ex`khrF0VT6l;^i@izjm|T($N^vSDl)tr@dTFn{S$jBz96rO6i{n>KzsEd3UE|{% zQ9fN{i@Sr#E_-p6&@C(&GsP}WM`4Sjf0$p?OMoSS5rE%wNuOaR4b}r5C^jwoVD9om z?R9l_SRnaf2dfZ{t~Gg!$aQ2lVg-?c++kpdBmd+JaTD)NOJxwF(^h2^8&zMk%91l< z$4Pvy@!a+kFT;+BIB|={fB_)&bM((NGh{kxrb9EA2pPi;F z_$4DR#Yw!hpSY~=(Qapj>fhn1W}jjJQ%=X33}Q09L?%YLFWJhd3fRpf!;}lqY*?rN zX@v-Qa9vdGvE|iSZ<%xhKFQCHna@1P;I+uAbYb7zwcpy$+}2lgTXk=o3<^oh)_n4ya)z9-~>nKC9A5) ziC}i>nLLx3nSbk=$d>+)?3nL?fk8V&#v%zb+u4*6+p+8{j%goso29g8Rd{q8s8o&_ zepX;0A|t0CUE?w;6`H~NgpLnHi_T#?(`_g(Z>iGELc_|~uEDAvR{EO$HpYTFo)d*# zh1}KjoKe-&b!$@7Lq!3wm%-UJMd&%nd5I)#`j}pUm!;pPmzqpY@-V`|Eca2~Kd?j= z$EocqcJeLnpqeSBq-CZeJfpo0b5oUNQ+a#bF8h_W#Yi?GZcJ+uLtu-mNAf?{PhZK@ z$33g#vmKZ&{Lw%nXDa>T$ba>^sZ{2yj9GB}qiAim?p$Oc`ZV!ZG^9UL);{`^;rQ~) znT`?6#nIWXGjptwRh#KoP&?#-u%zXtZ~&1Zrjw{#4k7#Iv1S++YWgr%RRq|J+*Ri2 zE4Y?;S%n|6D>u|jXP(12g#oL`X;g4XOpDP~E?b2H$XakG_#k^!1)RTtx9P!lXJZzp zT{lj6F_8k21vxP_5`v4JhK)AEGrFO5VErL7dIt26Vf=Xb}%+MW~SFDNMh#!4ta^?m zHtG~3GGVhuKCiDF@-)y8HfBUFqhD~$VbHxGrdF?uyZf?zONoh>+v0r5(SEsL}s~^L(pL?e#)`Mw#G~7P+aUkxZ z6tC#>#Sn02FB_p^CUT~oY6;{i4&lSctL)FPE`w}v_a%JVtiRG9-p~XumpD@mm1uT84ZE$ z|KHpywsYvzK>ugvNsP*_xIoUkU{6`D+;_x^VmVj93eh0DR2E_1ik2~y@gHFws*GOo zy)m(X4;3$u{6#D`=0;dmz2>HKc!yDsb04mn-2KIQ`po$#^6;VQebVbAS5(6onm^`2 ziUe@7)Y-6yM&GQefLiI8I>(NwcfjR`#j=yg(5s@?ff>0TIX0wHEWmQ=6}~k+$(V*d zy2pH^TVra3{E{sk>>!aQ3j>#8N#%{Y^P$xEkK=3sJIK|r?y@DTEgrT5Nfgt7)ssVD zl3v~O@~&2gxM%_QbS7dDHM5vvGae77VB)&@;$xP@n3GRFxQcv(%Fw7g>EnVI%JE5; zXP%j;quSI1S&uvFoDH$kPv+gVpc8W+*#ZoLFnp5M#zFD?A5{WrG0?!csgmvhSp(R)@ zb}xjQrLdy(bIksu!E{2D;96luD!;1kST_2aVLe_S)m&F=W|&OMmD(SC5kG*bF=puC ziJA?C569c;5yKc56+B(a5E@S`RFUOc{l8kzF^AmrZfmr3cL(j{gYZn-kX=3Ii2XO_ z$Bl_lqGJ=$FYZcJ_(vUYR5C8=4!q_;<1cS@u1fAswA81eIyZbQ%R1^ixRxq7gH0vQ zP%FddqeUtQKAKNqmhgx_>_@C?9+t$P=xSL#sDYY`SQrEHk~CR`QI9f&)8|kjebp+9 zdY0u+PEIC!wMp}2ukBiInf8rpENiV2Wg5K>0#$yUiZoeO)O2q0g@lbcZ8QChv>j?Q z7?3FI znQyL^0*|{sdCQ|rM1Z$prjCiXUZ?LeLi$gi?1zk{tuv-N_xM(pX= zlq}iu2N2VvfCFEtCGz<6Iw5(~9B)>tjhI_eY7DqHnm)Il~N14-@J29r54HhWf zz^j1R>RmM5;};W)2!9A0ad2RCFzc&QcNm>r?h)>5y=1LA6yXloMEtp^%8tUK!rxQf zAwy@TIazqMKF6xUNS5QN^I(nZrqR2k>NqoBcvMQA(&Wr`ojv@cY(cifKYHke63;?w zAYUU2QwtFph;I1=aSWtav@~KrwuCRS>&*ODV}Pq*5J3@Df&Q-#Rh3V*PoE@PI3|6w ze}i8sQt`LowV3n#wz{a;T#gNgg|LiDgdP}tMV&qPTEB?ZrF&mrk5h?HpHL4wc2pjm zfg(%P1t%}yhhXz!Cz<}RC8nm(@)eRYR>NxPkh6a5(it`}-(zCWs!86d<}0-y)xdP) z4OW)=GF^wz@&jObr%BN`=RNVTaRceSg`BbnjZ%Wpi)l{0M`UtG=Kj`V;p zIS)v!Vmt=&#ZIba!Xu$IRt{$xGCt<#ipfSbSp+(wQD@POYz@XG?-2#6*{h(DDC}8H zS>;mphzsQVG+eFC)5`fipK3Cr(;cAU`Vxl+81=X@6H9zKP6E{xHCVZ0rvFS0*hDBf zlx`5K1$&b}i$T>woLs>2=%sK%(1@u;&UTlY7R-1=*B8p^*z2%zGlA4a{-&x+AT)G*jrB4KfyDMcbo5sNI8 z*2$Fhw#h$5U^I%=6Q?*E!-T!j`v5KFi%c%$pPU4yw&+yjQ47;`>*{b{%8 z8y~Y~c?|Pru{Q0LQHdTi>B)L%9e9im52Z;}{yu8wFP;^0$_jD5_Y z#9IlD~--V&@X{0o=PQG4r0&iyPWC-nnC@rT|_e2pbMnUIN^&;vSA%fF3~}*Dgcdg!n6q> zItR^D5|@d|*6;fp^t2-S%>6tL1)i0v*qBJf7l?=DrZ6e>q|pzr&&M9s-+?1CgR_Zc z+>Lszm_ij{oVB4Jbae6Y$`~a2-pI)LChV(N#E!RWs)}kYFcSNppOZQIpJvKB{^^o=mf65$_6$G z%YcWbHm1IAN|o9o?NwD*55wM6M^}T?@rj3w#kujs=iIEH!tH$H|10ZWe(lSytBzNd zU4A=GkzkiekV6O(0zre&p@B$5K(YcsGNwTv8nlr7htZ@#6rh0uqywS>0YMTJ$(9p> z<4471S6s#X+^4JyNGI)C$g`4?|Ye&N3DoM5*zdbkRFJei;Ce6w@- zUp{;P+`a1;USg)|NlKKN=IChex}5&UOSYs=Vi!DnhpF9kjm3Zc!qDuFU_X^#y9&Eb z5j)PP?&#?JjT;GjKem7Y1m(H)h^y1~}H?D@d-g`X~IzL;BZLdEyQ0U$6iH?|p0ZHJ7T7f-QSeZ3h z_g@}2`&Y-(e)U+JeQNyOu52uCEOanuZ~37;(Ej@ix}oZ*ovihh$gt&mR3t3gS~u%x z<+s)hx3h-+QI$Pkh`e~!l3p`Z5^i_Lp-0li9^$D%`a$T{pA<#Q&Z;bX)5=m%i-<1 zM(i5ox|-JP=yYhu!CyLW{#)nGP+M26PP#vL@8ccS4co zt{O2+{iJZ0*_o1$)03ziYR2s@AVXEb(QjpZVt?(IrKb>9QiRhSMRQS% zWhfCUGgTs0t1WAJi01HR*$r+R$sb^ZMtvDfZ>>~~!^csf75kBN2kEICQz(Y^-ytY zElPC(m?86$XIeeb3U$nrYTNENcB`O&=*Oy$2ph8Upk18n#Fu?jfAp|9CbO(n;;fj4 zYpssm=C@OCM_kUTr-b*2{``rT%f73snuTJeY*^KnmLF2}6jwA5u@L)YTv1 zj~v^o5%H-V#fnSLRrjjvT3Edh7f=(0xlqA8Rtu<~UiO8hT8r6nZl`YAIUb>|Q~eSq zP)_hWyr&*QeOZ_+MoU$f!Rl^EDVHJZB5XGdnWwDBT*FSfI<9e5m8g4O7p%@^{G2aS z<2L@l>DGQLIWV>uGg>O02a?GIZHH@6he2mLi_>d_O&k zY48!xf)y11l(6ArYn@obIARVEwVFV^PvZg_g+^wCX|YT zOQ~AWOdyT>sV*3+XZgkh@)cDUomt+QhGzu}#+k07697-6XhHbUkR@0~-L92mY$RV( z6@T}RU+<1ux)ma7y>N2#InKpWGCw0nTgiU2TKP&lUFn)=Uw8SD`k&5yDnR=h*6)ft z!>MoGahNx;=&FjXG{#ayin>;Klzsy?U;_v^m0ov8}lY|asiRNKD z8^RaaD4iPn!SGsvki|M5SZX&Jp2%ylzk0Rm+F}tFgW>6{sxQv3Jre2^P_HvHX|DE0 z!i_Kh9txqe3IP=>fRCem7N})?ZoErgGMLEc+~p3Y00a0B_P=)V_zI2(*Sy{JDMj zbPe#N`cBltFuJ!7N!`qUtleMEN%m;=^KJh5r|-307eqfvJLc3efhXQXxi7QZOiBlZ zohpL3PMn?v)&)r+5sPGVxTV#H>E3!z+D8Px7iqCgaRH-jy-9mhVV^&H|J73@CyfEO zwlUr%-kRk))vA2RsO8eEma?Z0oa&-4$j&Iges*q;;#L&W!d+#@Ug2GV5vHWK)P3cA z>+n{^#r&`eURd97(Md;*RbpaRyCkuGs(|_1Iww0uw5pj$y0uDDS4CYBbrM@*4{{~o*HW$N9|p%jP0yuO%1lIll?xe=!(V5 zw|fQOv3CO|0i)u+ynuhx!L1*|2CMc}$)+3fY%2@-vlW|G(o$yHm*zj5Z)2D3KL4N3 z=IuX8r8I6;7IaazLZMhIS8YG~G=9j@{t|o()AUmnxZMg|`2|Z$jj8vfdc2Cf+~nc% zki)39Iz=``PsJk3u<^f|Ye zQ)}R2zk1(Iq&&&bqmOVer_P5x9^y0+MAmE!ToXu6 zfAb@)pxv~%CJ7HtWDc9*JYKCfDPL9psb~utQZ-ec8!#`U(Qtix}FH< z`Dbq|>{K19;~zNl)7Mu=G?o&J;BT-VCgF5;r7Htdv+QO(unF@%K`iTA)h@lu@eI5x?bnuo2VQo*tl0S-@rlWXWm3XA{e-TTeEuLIlK0&DNp zGrLZq&K}o+{jcZuT~)-DY3fm;d*L$JjrWt?*!y1w({*+2Er$c*vs5VDmm=cYCU&CL zT`qEVCF`HNztWRrj?LsW1T{*Yh$X0Ic75+y9FL_z=nrFK*2PS!5F7p03YmBB-|@)J zAW`emWY^Yp*O^?!UUwZUTCs00Y+cUQQGD&j1Lrn6ep_Gh2lp55 zBX`m}1I4ZBqkq6yww9BdpR`_G1$N>T-K*7x+aWI?YG8Py+J^c-RZD!5^+Z8Yz^(>7!e9F7`9{6k zu`<2(W#~TB_Z`rU@<)zQ)RjRY87ot)_KcRFpO+qX3P$(MyFxPJx8w1m%Rgq zVD-Dq%3c&=rzopdMNKb;7@=T!*+s=HrkI1mvq}in#JEU|}?cr`LjJdgF6#2RpndxWP z%hSuRpI=mkKXhj5m+lVA3kL6utg!M^_V}UGA*?E?JBkP3I`Yl5mhx}M{Ds3Bnd17P z=mU5kB$JVN9h?YLR6_WuIvS0Mc1MSog=2E+sj6SDb=A!(Bv(>&)EEbCS>*&))PY_f zaBC0Di*y7kCri@}ss*!an7OVcJ;0y2c=qWV1O9?};XQQ(i^FUV6MdO&;y#?BUQ>`$ z5X<=WUr=bEy{oU(JLFNF@A`{qn?^@W(L3o3xS2xBIBY26l?WqiNDqs|K9zs^QhhlXQWOcUpITo7WpTeIhV#>_L4sS@jE*E zr8`0#h!4r1*j#U+Xf4w*M|F@}C(JA1gqhWO6w_q{G9$QxyEV z{`F7PIYO}&UFuK7ChM7`dG5HXg{2d-YQ22rz1h6XN*?HJs+x7&BA7TF1{iVJo3e}H z>n5ilq}!@C=u)NUVxM~HbTIi_cd%!vgi!0PN&M~|`+Iln+vN&|$T%J?&#{*!@(^r> z@~!ry>MCO@KQsqAL%6Qm?8 zJ0#^H%}J4&&GAV`&ARGge7iMTjFvbg@Vbf~G0*s_&Q&zvLJ6 z-d1@)57xC}OFyUXI;uiD0@HML3sZH%6(gn^qQ*ic!Q$#e>DXz9>@4Ha;aRN#n_tbr z(RzHi(&~sZlX@{})nX4^p~HOVZlZ^*0b=NuG1K&D)XP@{vU0^P^O&#mls%f$dW1yL zSYT=n7A9VF#o8)kuo+Ikl6p{!HhkF(z;$KFBD)OPT#WI?=~~mJyNZWHGoAtYjDP5% zr%1<%Zm_T3%^K0xFPWDR6Kl6GkgAlbh!tH_wzNJB3OAC?3>VE)d>wkvLu8(H6So%A z`52dUnlf4#Ph43rTuoaW(R-60An?{gJ>)+ zYjrAtWJ&0BdwWtRlx!-k0Ji8I_ZSxTB^(Dra-URz5-I=yT;I?ffH)Pocy6%t+o&Q*SS}u2F3*mTT0$eK+ch z|Jl>T^JJ@{7=%(8Z(oL@aT#Q)Bk#;p;f0xpxBl^+tCeNET=$ZwV=QEcyxi(tc}Q9* zpNYZv8^blKRq9(m+}X*3%hvqHN6H4|UUVOpz$1))dv9XWVm@|S7p4eUzbTu-M82<1 zAL7sh^#A_u{Z|))+B2*3c1P|r9HVSc)(2N)jP#3UXIcS0pk4OVY19UE-&qerol!xc zciSli*4ND;+Cm)qIE(AI@}B&zHS~Ne&WoYeALb0@;80wzd(~e|rX!CpvO?%CHkch% z>CKUHC^H8J$v=&ZXhlU!oiHM7EVD7vaEf26^n2fO7M6zN-#yD&wE~g-ZB4+tcSO9w zIEYJ7TXZc(<{{p<+(`y+2Ete>oZc_>6nKX#!Diewl|`))g118_TkF}^mEvAnmwrdK zhB){z-YAAaRJ_vX6s2OIEG;vF?U2pbvk>kp+sv!03S$Zwx!>w~?u%yDLwxVf(j$0& zvzI?vN!;JY$NzLfvmTzuPTq?Z*L5MU!B}x8k-gQ%t+z0iZ0*=YQ=@2oP?{>uTVcY(X~83RMWG%yd_rkj3FZxrCU{N6UtBFKX$OYnT)6$QxAo+b@smEq{~O zQ=(+^^uAVSLvmK`s%VYBJfj+(v!GZ&Y}PM3ZCA z|0wKL!FNx#VvR^EDmH73iTPq~_{3sGoy66+L<+crTm0aqN-sw9A4nj}rY7iRE|W5= z-Gj!TI`7r1$@8)^YfTL;bJNj~BEf26|FgRT?DM(4cqxpei>2jZ3=|?>EFOtv<;&Qo zSFp7k6&v#I7V5%Xs!3U@bCz4PgsM3YrBbw)7p$qbsh@JBu9PA>Vb@|;ww7s^Tk+xi zTzn3*jk_~)W_6ymD9Z=D@+1i2sHS>*uYieA{X+_rgM&Mq70&wRWtko=KP z^35?id+$P>M`uVkbaZ06xMd$)J;(Y7przg%oQrunBz%O$EML#8{J zI~q2$OE$p8R3=+oIafHE0$rXSRZ8$T8e)dId6o^ zZWK?p!xO}R9kQ||W8tT&LwUDH@_f(6Es6)Jc6>tb7Jj4e0k4dsQB=YRY@zji6dAQL z{j6e@U7^G<(acTlyN=49WuH5qPI=neN7?TSO=K6f`J}i`Jz+EYn2~AM^VE4h1O>Z&t3T^#|yKxm^TDT0lX|D?*@>>*quT9AK4^uH=Xy>3iL$^r4P~uNBu!pk&(|=i*n@hE{v=w4&hei< zp1_w`eid-a0gq>4s)7nxs?Ivu;51~`lj57d6%ocnop-wUJ7;^IECb`YRVNf{R-jBOYuA97Yk+U)h3)dk=br1H7#A633bQSqH0Qz}x7WPYk* zx&htv-r9eL=fisJgKy)Cq5(Aq8kSx2>Nqg224$&PcmiceHw=spk>!B6F|XGt{`Nf^ zqbtv=zV5uL_`Y%P#~xKX?&I#9LI_GVz6g7(Y4}O?cbo1%`&Ks!6|%968q%xoP=Kf! zZ0iiWv8RHprL{*=3{;u*-HY|pw)J^Zw!=UpgjhRwZ zC6y=W4NL=d4wzyXrY`JVAd=-xg_l=9`;#nJjl#ur6c{Ddy1k}OZGE3)o!xP_^}Fl;4O zT4cw7RS0y}bnPDXLVhjJ=q8hK4_fM_ltHf3TNTg!Zf1USLJn_K&2zgoiDCIVOGDH! zZ`L;=tOy6v+ww_|vZ7J*Tdff~H}ay!Pu!7z?#|%zcU*ND$pxSfj)Z?Tr|EjooyW+S zy1{yJ4F>o`Fs>Z|OCvMzpCb1uV| z!{CZ`*vz-_m~MJ%+wqv?>3(pfZdft2zF&Bb7u5Uj6O5#rS!`tMeSpgykH0B0Zak9( z%nwR$uW#hi*4U=}#3#f)M9EpJ_+9SpGq4yV`Qh`#51%KB7tJHtya-*-GxbD8g8Cw= zQ*h;7?(1VzLI34a=z-941Y>NkgQ~cup#; zcmuUkV2T=PoOMT)V`Fwa-}#1FUF(fisRR^q?6jR$F?cy^kul~ZJ6KMu!(QH7EvPC> z(LAkMRH_fCii=zp=Me$;Ptgo-k;UpMfB?-fmJmzb%F9zdQ+Dg6ri8MASt?_)`lKE4 zbdA{e+Da(%f;wP_Pbxsl3{ADY3amo;{o_1UF5AOkS(eB^BcLEsl66oR8^>5oFp}-C z7pD^uoVQsmyHAzEH0&GH6&`pf8`A&Ws@A6-=tJhi{l&9Y$EzvgDyq%(Z&4BO@Vco* zbTOSa@!X(`n&KjTx?(+bSR{n`*m=6uzUH_ubJE;3lVk4ePbJvyD(w`((yfe;g;KAr zkWjzDC}Wtdo702X-Y7cYx{8(Vez}u6W<3z}8Vuh^7Jo%voX6g$GC6F3+FxgFR|-k5 zGP}EeBPLh%#tv>Qt)@wTQIXX{llAq(y>*ZA-#VV<0WtzUPgj&Tm;ot|W`$^2gofhK zBefk;i7YCM;Rr0j3~0x!EGNSl*w{?OWgsHNWm&P$Im>VKI$5L1rmDTIimIBf-rMdf z|Lg9xH*KpCw-fV!d+9c8cV<1GvR3_{IuGqAq))N;jos4=(q!uLffkr7&W2gCH3l3b zNIhhyP(WU)cKP;;24IDyDnklEyXjHA)fxJ>x5pRmz7A1)XvuV`Ijy!ZBg_!ork#Mw zLtBNS4%td77IoF$cAIq_2Yb!vM(_h z30dgtR={C@yuAuPZVN?NI{U^{ShabHUE(Pizl_1T!N}rK98eW&8GqM&kbUaQcV#PU z((EK>?|f@$_2}EbNB_OmDE3{cciC0Q{l_?TZ64hZ^cr{qfhcA5nZ$WSQ}H8)VkWXS z-UT;l->eY=uoJYP-PT`Hy{1n6{QFg%(UqoYIC>_kZJHerM|Iyyxat(GH1U&lyAJiM zc5js-51;+T4x`eTP}8WPkVqW^`q1KWTUjo~i08k1H|lQHG+3IBC<0OpS;)F%`&8LW z=r_;mtjWsOL0Dg6wUwV)-JXy9yd5s}l3Qb?*V%qfD#fsbLdm~V(y>6k;Mhc;qLR*} zyp5t;?ToFg%7!ELoj?RW-Rc^i?eVmA9*ob`Z4)Y|fXTb4HEykmkW0a>en03f8#CtK zqh6y%1-9@3H?@Cr&o$`v<(nmCZ*_~t8|!(&&9UFtUh)XnQ7fZ2>-pl>&emT$Z~fi7 z+3TiN57wL5v&m{6dpLD{Z~MCOa#v}y9>oe3`>fd8Vk^p79GfXpVtM#u939%#nO`-n z&ZJ@(4|k*VZk3CMv{!;AqXM$e#cs*B8{ z{*n3umu;KTZm`yl*F38$0(*znrl#^tbrRjWh7$CpNs83v;dHnvRmKJXGYUF&^c06D zW?C{Inh~aa3+2(@4i+9ad5o(gE1|hE#O6{Oy-- z>#xL6tFT(D*)CsbURylwVZ>e!O2gbAGt;fKNml% zucw%or|@o=>imoe2FXfB0J}?5a6e{Y6sU*BqE2h0VIDLWRIX_w)vm)D?-|O388WL@ z(#o=`$5z#bL5;uH;P7KJ)ud9v(p5y%>dx}^XD4t~A{FHDodv&s-mZf2wYy>2c(@)m zeaG#Tv^~kN9!SF0^ckH3^-xr2EeE5tzW;*gRquIR%2zdwt{M&v>>=VWUTkdYS&FfH zOw46Vzdf((Zg#GGI3=n6rK$)XEq}!AXaYX7T1T_PyZD4+VgL2nMkvh+N*P&kXt|I) z4S&g-UQX_iI~_+(PUoRym6cPp%zDa@{%rYRb-L7#IyXCO_x#2s*OnOY}#0?4VrXqS)Q8eG5sBy z^4lt{>#?Zbs>4FwDkA8SkEGnmtg1XyP}~bw zRHEw0Qem9tmoD#l>G8UR9&J8FPzAEOZMqKi%Sy9HF>9Ro7vJTZ>m67mz~wNGqH!xj z#X5B;tGgf{6?O$v8q?7dNy7JcSKb+CpYSWT|~ce zD5Wl_Vx{xIXc=W_)czl8KlPL9eXC0Y<1xEgCicRTczD%+{6Zx)R;j-2KmJG0l3Aq{ z`i=kO@$$@Sob?-GviOxzP2Z(qRr6@<+r})R+VMlXA z3{OLVUG%whIx8ut2++{@nmP1YJqbn<`{Vm&2#?n@1kLpX(6#U&*(ko>u2J?&#r^$l zk0LRG4r5)jz771z7|ZlL_gRfQriGYG^?XntDFvF9vbw~B)f zIaPigNOdrZ?>yWXo6}QZa9=2_lFe)N1mMko{{HG3((TD*MYDbl{RFLZrnFe)T+S+6 zZTArG1K%NwQ8b#Oh`By}Rz{gd&!Tp%`pzBMH(!3kW%h_6Q~`hIjv0c7gY|)>f{E~O zup3{`uZ;!{YZj<=_$^9Q*&2E^|LaeC#}fmU7V zVA6{xLPK(1C4Xb@m>niog;=Ik=S4Y+?tJ#P69+5n?4)W@xm?ei_GMSHu8CB?M!dSp zEB|)3R1>NuwM6}Bv$!4@(FgunbCqh1Su`i@();^&%xY(Nas5bYPin6zj4T+ssD-ns z&#NOZJ+eH7ulL->Sq(xI#Z$ya3hy`1YPtvX1yBwtH#%PAwe;RPH}q=YmNc(AIb;Da zzOxW(WvX3+%!p$t5WAd&4lDLiUczVBXt6&i9nsDv+sV*gC+)qT-L2g3dMqapE{)HQ;rm=tBMxq!`#|gzrR%SK$6e_c&Im3hfrmW+Z zb~v&wqa$#}JrnBlH<;qRbvt1l>UY?^N2WDEdKoFOsV-F^WaIW$VqF=FTqz67io%U{ z4&_l|71S*{#zHht#>P4I4=TqQE&%sY(*^Xj;7=s0z~YDSeMtRStO zm1AZqysh`f=&cYPYh-^uzmBtIKYwsXXH;|)#5z5v&ZD}|ssm!GV$OO|R4UwPab}^T zm1P)zHEGdD%u%a$O*+>QErS$IeaqZ&yPhd7mF@V=pHtN8NUCyK{#3ry%#T_8;52-r z!8gTJ`9i+Rb3NZ9xJ;II4q3n0cSgG}m|`q?=TJQk?<$dRUQGD#DUR0cR_|2PsG1Db zcsXD9zHWWHC#Qt)o>O&HTzBIeu^Z8S&#a=Hf?v(F&#@-Yni`oDjG=yZEb-b_yoHVV zl(P(djnunmCHrl=4=rEKrmVM38K~xy9_G>ZFqF;4*UW;}mZ?8r7uNFW;x%G;hi+&X zZ1&G9X@yarc?!7|TQ1J$?5a`2rk*qAS~KzSJA_1;%em-~yfjgB#~Z-RXh`H|X(v8@jV%BNKL9p7=LDHP^w`5*?w2I_3} zx0rKu^SqW1#0Faj*^U-z5F$UziyYyWbF&TyNB`CjJ*7@8U0ouPwL8T`cAFZBx#R9M zGw5!$p&STXZ`We=f3>$y-fyb~)E?rBu^3FX?pkxUcvjVi1*^@g4Cn#Eld*}iKWodr zd8`b(5c4RvbuUv)AZ_y^hL4Bi^47v+^YAMrs{THxnhwpI>Y}XoO1x9+@eFa$*ucdw z5n6_W)v^49cj6Z>u4jdR4`Ir1V>HcH^T;dWyD%EVp|{kj)R9dscPz0?`447qcc|7A zvAr?SkILfJPM}O478aM8m~Ek5>-c#ppXJxB!de!HpV=u)?Z@xmx&Qy~7Zw+{qam{Y z`h*&R)-aks&OIE5p0Wg9EncObRz-6*W~!U>lC@w9lud~F*4^rOs)uYk9)yHZq7BRt zQ~48q#@p+TmxogMbz-QQ_y5)!!_6>I?3Yu^QC-3J^|LgOO|f!Rb+uSs`=^evzjb_F z#ktiQ^3^(9sK)Td`Zs-WqOzIoC`Au>W>=lzul$<^OTAY2W#8XAURK54%3)V4DIalN zT-QC6bIS^KP;5PG+(Le<+tO!I0@y5%?uhWUW~%y`j8K-1y{#)pMf0=wy4LsbAr>$H z#ZBZLqDb8b7=H>F6jVpa6U;}mx1TbCm~nMJ{_jYAV-)Sa8ozX$)faSRtpB<_9~@(RdqhQ8ToG5zo8EaQ4IIu~sz%QXh+5zlN zy)X7S7i$o8pR#VdaM)QGpRey4jzan{NX-jg6<4ZTsDD()ROJiF9p$=*XfSrz(DP!S z5EWOP-nyD}NL3>ENC(Ns`3P;ve0%SX=X-afg<8#Tt=o6bjyeomUDe8zRszXZSQ)D5 zqN3x8UAP&0j!4GAVI1B=5a3TYl~ES7`BL$(4`8to&rA4pUu%Kt|CuyxBmEeda5e^ zB|9qWuoT;ysUds$9?yL;KiLL$&f6&H@|SXxkg)sW17^hbuv}zojOC}!?8(LAGZ?E^ zw3dVt;~46^69ZYS+6H@Le9*Ajfuie7oHnjGIr`NMj>GtP*0u>pJVcWP&A)Y1mz zZv3jgQ27W2mmZ2?Q3Sje3+f$kwR9QG!5J1iLz!|980>G|G1m0yKZbnfO5o|N8IMG`BFPe6cblr#agQ!FDD zfIVZ{sfN%#uhmyxjdZpUeX%m$Y>vl`%K)=_-Ed_Bdbqr&s=lf$MZ!_^jVG9Oj$1X= z*vW~PS2Y&p^5vdsP2ny}srv9ml^aT8URh<$U;d*Lu39SV_Npw6+3|Wlkl&Uq%a0oQ zGkG|w4cG;5ym|Z?cbhExx1-8ziW{Vk9M#B1yhS)+okU?hgv?Is5&%=WpCuHY` z7=fA3rgho1767LxDu`ydyzX7~)N%SecWYJRe5=9 znpSzOS)Z?XhvvB)i>jaNWvro9 z{w&*ST{XV2jvL73Z+VDk=xU{)wZ|uG=L<48-zh4sL6P-mKSwC5<M+;@EVcbSIt{ex@93MN2gfyDI5UDGO2giTU?4wFY)9HphKkkO88ZZ*}IB zLt)ERF6$`7({vB%GsfQPldA8b9023vIjK43H%%g?i@m92uiZ6!Xpy2WzbsdQ>R!*FSc|ZyVHq0C3MrE~pIi{AbB)%45^9i#~e1&OvK-gTB zLycF?szX`Tx8EvFZuR5i$?RffOm&`m z3+s8Hz?7R{_M&83E0)o$tj7Sez_ip4K6bvU&y3f~xUFCPsh68o&FstCvb=J^Sh7d* zcfVyZb@IdkczAV#@>f@XaUBre1eMthj?;$y_MP%BDy=HqF^A(u4YEF22y63J7KIV% zNq)O0YU;U*g?Lu^ilc%=&@`lGZN3mUP9dTc)#qbnr@UG%RVE5c+;2s5D-uP{s=;An zyiTNw&lPR3DMu#G)JFvm+NUBtl^yHjred^yq+VJ(w$e2)W*846KXdO#l@<4wW2Mhy zIn>*-EEx%Op$0q0&J(7agKp-vc;mb70evR8ZhRlh&R#fk=u1CTwed=|tuordKt!ELHq856e--8udSZg|mlP_=1Qm z(uf>et9pY8Nj~XyuxE*H!Cayl~ah_MpnMp zoH7Fb<^!=gejpoxYV8K4H$aafO|KoIo|UUSiH{T`i^0aYtL>;27d3gpWBsfp=HMmN znN~NuC#tFlymi*oUs!H~706x6U#2hkYio04GVi~v*k^I^8gCJ)WN0|I*y83J_GOQ1 z=2iP*Hd!Dgugut7z`>vrHp7GK5n^5E&H{a>8gIQ@|8<=!I}6Ci+aJ<%^8;Me>%H@i zvDjY@OX;g(Y4*YpRqgun!ar8d3wRDxS?*nSCiALNLFI~b{Nz24I){hwYTsr6Jt_Uk z)uloqD;??*Oi_!mz|&=rB36ANW}I93bLs=U$>w<;%VyoN8hZ!5c6Fw@WSxCi7Ge|D zWjW(0hi3&SD$Xv0;_cO2@-}#mVZs}+*l`%KEL|?9`-org;9?Me$~LSSGEdU0QYat-7FUn!<~1z7 z54xl1X6~`aAHCe17tb#5ng6z5%e*YERn*P1{a-vV10J4PE^X&Q`{H?qaWXbopm&1- zX^bq~`*zR#V6~1PjumaykZ~PXD_VVAL=K;M7L*pRvE!}JPj!>obgQeOJ~R1v>*G^` zW4-CD?u!B`4tA#S9Q=ycnmc^f?17;?$x)g~;}ebyD~`DtIT=c5WCZK4biD41zSRTi z-m-U}%?4(=Ro^<_V2*hpf~gWy?7x0Dd%ndcaJOz4Ul};s+R+A0@F)s;J_w5zy6F#RnaDL16Tx`M%{&zoG8*@&jx`BVrl zUZoFV5ADyW>R^R;T~h7Y!Rk8H^h{xNm|i>A$40!5GpldnPUWzL&n)}alYxa+59Yzv z62M-0hjS}$G~a1+9^<`m8e(9i?P6jV>mo*xm2wn&bdLpf3s8%*C~T~Tm|kd};T`25 zJU4}~T1a(Nxhhs!PrlsMcXht@E|Rgj$-6Mky0lUg!zr_cU$Y1dVQpRVm>M0U-BRR* zbsOp_(-B;bsJf|oU#*zkdyO22>W78LFJQCGn6_ShhOKZp8kehAz}37qeH5?e!)XP^ z2V<;;X1^Q{n-Nt-&ClM^$%j$~ z*tSZ7)#abKZ0h4L{Jz+W{a}}rJ>wOQsQ*JR=pRR@FQW>pF2ib0RnhF&VV8pC;Vi2M zP(_WePy3+n;o0@nLn3^rC|d^0o9uqZkN9^==g03a_I9qU>llk!{GsTUKFZ?Iw-}7C zicL_8ufjoS5EJ%0T)~N|aqudUulI=C@#Z0gnWTc#j8Sy#W0o1hjyfmn0cB^3sj6o= z55K0LjCa7s&12j~jEK2}lXaYzKe5jcK$NGeiQ!PURmqrqswW@j#d$L1N&)v=D28>$ zWqm52X7AKy@q@L)CyEh;8DnR!x=qF%v!Y54&S)(7zN;B@?O}{QB@zn7<>90ZNtbKU zxSVC&aUQWa<1EHZj6>>-_?iAvKU3!$M#H1dlYPX3#UnAP{*z`D-Zy?{B=W#`x6FZ; zmI$Qlv|r_1@2?IBG2RkIt^}!$@~VX-@GB=MJ~{v>Y;aUM^ZK3>&9iQLy6W za@4A3t@!j`_$+(1D$uJxdZSQ&D$|;}q?Q4vpkV9>N9OCi&FIIJ9jUXH;W*~FUB231 zSteb$Jf`Qdn{#EWR#UKpu8>BAhq~%4=9nk4pd3`jX)HyLa!m2stTEHl^3(LEJ9ix! zv#Oa{Mr&-$2Xo6&IO?A;zxX_DkX*yl0)V0h0wH8Ga}VRly9Dt zjbZ`p%)jFX&{JL{g2mg#kn)NAqD(1;#(gZ`+=rjb?D>q`&N-ye`)~X;w03nSujzGO z*{764vWZ!=?pE3Ey8gu5DwMjQ$;aeQW z;vor3Rh7g$t5xAg7@g`w-|QL>jA>&?q76I}&#S}9A>gJYB|C2sO-N5z^` zs{GBS;u_>}->h)l=@fj#*`!=8OYpmKgv2q4axZhoJc#3#8NmX0%zAREyt$51eqA*< z56`pH!2HK(&|cb+h)2URvylepMzxiy)i)ccid zuyN=3j9r|dcNPO7kZS+8?;(t>-iELw4WEbS=P6po!v9kqq0r(ZE64L7hY>Z4tWEp= zSyg7i*VwQ6NKB8Prrpv;@Da+28x5UmB4WZ3p50Fo5Gs2oBZN({YuKa`ziX>G(^wGI zhdou$beD^j{z?;IAaab{3b_QZ31YLO~##2Zqj@*Br&7Q68lEE(gfmxP~Gg-F?E zC78o=8v#0kku}oY#D?-i$7iO6Vj&QwZ=}5kCR9YGpy12aOH%)MC=SBnLiFPmHVMcP|Fgbg@ivD*>rsWC38srS_T^4jukp6%WEZc2?fTm8G7k3YB) z`*s=;cVL{@lhsome)OnVoTge#j`6XzBU2CJ6Y&ofhgRWZC*e0l$7XO&UQ+)~k=|&U zrQw^SE>^{X%cA(jbQ)|s?Ry#%@3HP3Ua+Yn&no?>+9OgbL`Xg-8UC| zR&zP+&O13?F$RM7N%aS%C}ion`S7PQZanJsvoHlz{Mqbtrsf^bo(D8<_;mXm^JK?R z1-4@kr#pv~WA}@MDNJ}CJC9u(J4fy-|0wUTS1F!76%Qk+3y@dvnmRa~mFf__!s2zW zIh!J?*=*F{39NOKpNYz8%<+JZiT{RbJqI`A31+s@gYM!Vf5yv=?6^~^9WN+a&m&?` zVu*U*2Om^5e*YpAWcME4W$)NA;wg@!Y>G!iI@qCevJQAi2cKgstiNoknb|Cm|Llgx zr_^IUqJR1RG=q>20zv}uM`ha?$};WM+m58>uw$+t#Q&i_)XXO4OKL6Kv7Q@ChT(iQ zt~(w=Epjxisqq?^;}}IS^BadM<1E&Qexa4og~# z?B*SO!_d62=(kzsIlhfU#8JP_}yWIGRA0l#5nO%IBPlp6phWsU_ zAv1qE26#74D1I9Sw#3iN!OhhtmYXxhLRN8>Jgc11Ipo2PISh*t-KpPKYGT+~-chz| z?q&U$)8@PLbjD`A8~^d@STBU}d9utjt4l|>QT<(M9Me*YFpc58<)3&Qv~+H~XnUda z15psZnq}MVi*?x^x*3mhILL?vH2yf5oAH;)rZCnSL;d0(v>TlRj*Sn%FSoQs@5k?X zvzz1cxex^gt&&C;q^Pi=>YjQDc8RZ1#65zMdJWvbnMDo09sZ_SiEwZLZaQA}ODV+C z+C3)bR>!cMyUwm@yXEXOUv`K&^Ijfh=I}-qjopgXm_fWb-hgND0iNYQtnS%H$7|CO zI%-yDohP2bvgQqLQ%=fH>=O;8;6K*n7T4ipP@>uI^LTZ&-`!oINJ4Ra2R5Q?upvMNb$f z1J-{I8{&^5!y*O?)9h4U)0gl!(S(2TY5v^Jk;2+oSUe$y&w|B`qHEr32F8iUeu@@6 ztM^D3;LncFn8vr*Kla5Z#5+fnZbe)4Z6rkB^uj4x`BEGsCUJ*$R)t+Sr8*xrkg~~w z@Pc>a_w1Z%L^*Pw)#*)TB)fXJxY)OI$p&MmxKK0I`1Z|Xi^pcGGwEEg;<_BtL}G4i z-i|B115bBb^bYaWCDSbUJ$n{u&43t9XdQD~24XC|zfa=l#X43?#qElgc|*zocI;j( zpT3p;XjbIaRjbU;;${p=^fdxzdWZ;D$_IIQ8Apt6Tmyp(amrs|CBL#J#CNPQ= z{q3Dtex4r_=rhy5D3eqf-k*K_gypFPFlCVvwm0`h)9N?IZj8Zb#z-0u)(pv(lRUcu zz-?zKKBb<+qquT$+j}@BvovNA3LBsJym-=a7^@;YKXRPTqIK%iqdIG|AEV>#t*4P! z#N$L0Imd(PRpL^wtHur6%gfD%6zjz*m=oVKi_;$J?STkJ!g!Zs#5v=+Y|isjotnFF zG1S42KXqqj9$>X*h*vZ=A-})E1OF=yvjfza9gU7CSNC??iBzUPM<{W-tCcJ_RwRHi|^Xf%khBT!G+aI;`rw%vbL?&Seh47bDutO*d!j zVgT7Kp4P&=!z0)&m_s3PR+<&>gZ9_8^dozRHa$A0i2yok4^6!B2+V>Fu) ztQdyDG52@F^4Qm?Rwr-{S(xWKKk*$S;lrYb_~KE2ts;>+5dWqumNl~9)2HT99flBM z$Xqm{?4+@k$MERgilpATBk6Nh4rC^=5cU_{V(gB~@j_w$!36W1v3I`A%XwgS6^UK{ z1!ANfineA)^Mvn;i?M6I8RvvaMTF+Tz2x3AMzokKhjtb_gScqfVP_ZjaqeuB4LS>F z61#E^#-+&ObCwT_qcWiQT};C{i|*aZ+2YvoHKSmB`>d=T6OuuRpUtL_TxX1PYWyGT zIwDpdpPYSpS2>ZHK9BAG4H{9_V9$;&`xRlG z1M8Z(W-x#9H%^o%r~R{v_tI0%TE5KU&Lv)CjLY-PSkb5XXofw1i!ZSpC>6hc@@95o z@s4z3Ob@8bQ}l%DL-tg_B1HL~adAFImVbM{WjVaVu@~XuaD1Ad! ztP4+gcb&$=^xi#>VKeyZI3Z;R2;<7Ly_h$A2N>R zZL@fDuZR$%Ofz=OWvOOl(VLIKG&9$Y6(F#=Z)S8gE@u$eWJMKCH;hwE5DAK`qJRic zm$ca83$y;-VFJ}N6lbOIlvmK<=<|+M?t~#zl<9wJnDTCYQ0akXSFwJ_4SnM$Vg!HW zDS38Yn~GMR%2OP5`jA{uMzR(9&dJ$e$2(u|#H(YMd1)3iJF6`G*x5~HT4#%OYwD@x ztMv_dgie6#JNB3t3^ZyFS}R3^mchTN8Eoz`eS>wPz;9j5c#s)x2Ek>Rpzb#hSVf0z%lGujY^8>y z$Iz%7xLQ>pGlB2qIp!%l_~O~3iTDET;*UHf%u6jcJK>+&S}NDLXnI$yX+4A9HzW%~ zI(A$Jzv0Ui7Fn#nFdo0z*gaOxOH#&hn>Z9Z!?VC4ZqO z@sU=ZSYb+MN^e>hg!A{k(PV!SC2VmN&{`e#+h;wQczrdFu)o5eF*UDXhvFHlnD^ej zS{jC56vBpJ2u}zX-P3sx3u`_UyW+~JYN8v&Dk_H2Mm!ZJA9(tEpkKO7)p)$U>V2L6 zG#Y##Gi`Rn6T}R70)fR9^KytMA5uYp9h9SdJO5_y5Qf#wSpTnbTz7OzNf;9@@*Ghl z-W=yxrH}to#X3(}HoN1*^2Pl%rrfBN!^TtMx)3a7J^q9v6_sFxd4TDsst=8vJzXk3 zfz`yZjWA24%dj5%raiVwp7lMpj-I-x_iWnU7PUT_6&t-+8J2Wvj`>y+)uGb2x z?PlW|ChpVo{G{x4^$gvalxo%cjg5K3@0@Y9xw07O&a2{CtgUaaRSQ#j%Sh5foV}4T zHySUmX7MzfkP*({CL&6DfY{HY%>{mywzIJj4Om-*4+&YtY%-tZKJ4yzRCl0>TL@mA zoBhjHi}viz->qcH!(~>@YB_IlA&fEZ%Rl2MV$ZZ``gGT_@9H_UQ~1()YM%c1x~K&^ zp=RibH}JT2-oTsrQ?uA{U;z9;gyhpagN2!3$D&| zZ+c(x8=o5AWL2XV`^YMxmrsaoly9zQP|`2FeKcaA3_8; z=XYE!rnW4FH)6Q-s_=y`^89cB2W6-DsA#}?b<-L}&n!Ch{CU6*lFxf;qGc7{n~LlTlyX_`0@Xc=!7P{KgJzsFEx&O7xL^5rtfqLCc8w3P zKTO)}$k(%ZIMaw2s}J1`dq_WPrLu^J_xKc+_u1GW-*s%hmu<%-Sc%`&4+1?^&v2n) z*>E*gC2iK6=a183(@j{pF=PXlWtV1R>-cpms-o_3XBg?CshAAs;blzHZ;#sLv2$w#9EErz z`hWj}AG^Qs@~Q<{3(Mrmb#aFCy27`opZb}Y+iq+|MMQu{JU=ZKV@lI1A{Nzn5%2TO zFCi#oW##OuuA?I!o3mEXZ-1}OgIk2fVJ-yV%gt?{!iRlkycWx;yC$7zic!&c^F1u= zNb`%{&-+5LA)Yu@G<`ThHaG5#MQ7({b0VFt<7&)1hH_5X5*&f{YL+4^dy2BGT>Y}E zX;=?o)dNd+ZQr7V*NYxyG_20AU^y=`>zad$u8w_KAROtt3=5B_vsHJio5!m#JHy3i zqX?CuEOo2T;=OhCiZDeP5f0kr4`L6sZ;B|q6@TRLG{2BRym{lL8W?)v2=37X{0!f) zfW>E{kcQAa6l;uucY^j=qv$6N!h~vOd{z#{&+}z=?`q@Oy%lcxKd;vb;Z+c-c~h+l zmQh1PNk@P)w!*F+l2#eC(nIFT+wFg3{h-~E{M8y+KJPK(0b`t}o3nb=qpCU<#o?*% z#=jaG`-=Q{9gj29{pX28xM~p}e)D#A70LTV*k+bmdkc%=Iq{?7e@9j9G8d<{Lt0$B z`izK3(crzT&7N-0ntc2nrsyW`{~x<`K00&f=k@U~3wN$!sO(;>VR8P+OYmQM7|e#^ zX<}87*}v-X>Sr=VuUKYS=H=be4zPtXDZHMYA%}Zx$+taH?joaR-SCFzr|I*+)WISv zjA!xGUY3R3%dKIz5oo^o&EgQn4JwJ(LpzoZZB^2!73(6w3`I})+}>ENjD<1$J*6PE z0PYue%@4etzoh(OE#rt~Ohx;U94B#|o7e6dCp^Ba5N-@RjE0#2y*ja7Q>G^RUvs;JPylG%B*2U#}l?WzM>~DDi;Vn zy|>RxiGXMy|?; zrqX+-fyu0fCgoY4R|J;1illzl6SNMBG)_tuAK;Vv7UGZME0l<@h0biwmdo44tVK`$ zW@m}?6Mn|$#I@#o7Gag)Fzapp7YV&{_V%nvQ2btC6L%{j;K z*h9R|B}K5=-pqRV6+6UVi#X7)9u+z;^jJsMGZMyQwolUzo!n0~psvwr#%79F=9xJV zr;|Tp3B0m1FaE&DA}(uUzj&fZKqrAd{)X@(YO6{5Uu?*0WKH#Bu)R!@MOZX%UVW5J znh%bpY|?L$v}h{7p`tnF*5S0%L79M8Sw909vpsRKa*RF`(#MZWIcc53E1s03Ox#5udCch0FP5=hl()sLkEHw>lX=FN#8X z@m4gbYXygo&wET3h1XM6ruHu?gk-wG@a=Lcx=gsmi_7Qv=^5#4jRmk5_D;9puXVyg zDRTi5ycv@Jie%!A|Q*(?yc7ukCeGi4_`NP zGlb1!8*#pJ9Ud=sg!W;8Xk+}bd=XeYs@{ZumT$ppej-}>X@vGOo|X#Wy*G=R$*`(f z-7DC>FIiK5uWBPoj=$rRA}mzF;;r3EALwe4DZ)7B?xhmPJ>^(5;_87$K;&D~G+azSSjL56#lvMPy<{ifAkIji*sAe=^4D37+HJjE6|U&+1li1U@O| z*M1mmpRO2VY<~o{ZFRnVW3Arz+kPM)KmLx*gro8z+@V>Hb=ZazQ>8u^+ zh2g$q9@Vvt3jb&wdLGN8Sr)E12MQc5DHh;AVysVQXP+LYd_K#2*N<0ya`?hp)EK!^ ziU1qi9l?GHw6AtXaLp5cxlWB4k)B|7hzMm*Y%Xrd8AK>P=9qXQ^yc5JzJA-GcGtOR zAN_XwZ}*!2bFXvNlsXc1=57Zqdx_Ll5#n~%_t2d=FZPq|+0xki{aByX(@VN%WXuMy z;NPhZsSkN@dT=(56To}8Uw&v_wX(&hJBmILj~SC^nK2N&P|XRJvtSXmH4rH@+bu5D zrk?&H=orj&j=bBk^AT}P50*I@SCJ{DG|Eox`Ty=&)iqoA_I6;Q8Ehv=idi)v<0Xp9 zYGR75o`t=kEQ{r!DHW7wBZKw4Jz(@aRObYEm`44_Qo;9etI=Z4N2 z8e{LN62|LrItY$ur}5$-tJ6KT4_;f=#P%YnNDA-Yx_1z1d35T0`8T|3Wg1W5b5JjQ zH5b2i2a0d33S68FCoq%nBkXp+h$*77ZgZnqf;XGtX0%T+mO8mvh>zCOu&kVwDFv9a z?gO?KUtpe}p@7)xsPV))>ay@X<(>oOzkiCY|8KU?+C-?HVXPq3qxk6J;>+v=Kh*SK zKwP68YMyLo>p(He?dv5Q#YAG@j<0#czI;r@ZVUv!F59=8L)CUxgS$F5Slj=xz4m+X zmsj;|9*qCNO1>Lb=ELk$EH3X7pVlje?X|}Rd+<`n3bV~|qrxj$iWl=&W8}a6?Ve|c z{3)08aXkt4nxG-GES=9iHj?kez1vT?T2JiReOAx9vV2{2>kOsP@B|sYPg&n$$IKgi zIX>v7nicnDlVZ5+8FKiRSGu2a(W4$qtFAIpmM9WU59oJFA}jmyiRLbQ$E0J|o8zlr znA35Lc&S%=jTsTIlSyJA{Ml%Ehd6rNF>Yam^5wJ+XfLdt_;&FuWDr!BVhi@KXe4UMBir2(# ze4_XsYC9r67OtBaPj^E4Fu!ayI9z308o8W=J=vS@XVb=tmBkG2rI+WuyZKDtKEW3| zIP3DUuvt_#pUVauwOjMiGlz-Y<8j5GJjQHyR$*u{OP73645HRE?{!|m|9bbZ#5l2c zHZM-j!t#Hae;SK;np&v3pkk#8x46P<_<7u!N0r6I2aV!`O9+u?C@i3m+oO)&vzR$N=zvvofnQg4?mI6L+H&0Tp zFsf#ZPhoLK!*={68}ia{z>$jte4lz-r)`S8ap2$3q%J|R4_g}d?;V|Y=iZq1Sw$V* z7oM>S+vbDCd9Pw8)^U8aoXw=M1iu{*#*nJ-^ZAK)G-BSXcVYFcpC_a;@kHL8`dVF^ zHYUc_^QNLlljqU#evt#3z>|J@yqa@kU`Cj;#?u^a_Z}U3*@t}{qjRe2k)I3;8UbU& zx;&}QK=yWC{_0HU@u7g(3MY28S+>h>d22I-MY8r@o1d}`k9K1>$3nX?^2WAko|>~d z4(|v*oQJu<@3Asxoll42;vPG*H-9YCV^y|hZE@Lm*}lh$*5Y<((rQ;0mYe!rtT9Wx zzfWpx%|i@OF1~syJ3~EvZE=)XeP_wSJSc3*&zc4KGp}mKtTVg#Wj?_}d;oHWouO#f zfBc{QV@&Z}Ruzx4Z5m}O%c>}eEcHBzIgkB~49{ssgehgGK3yba9kH)+(%=eDn3e>>l$IrZgw{Y_o(- zeTfP_r@1)=+h>+B-3#xT{&wDKU@V{4`Pqzt4aMTr!n(jk>+&f*)~ny8AEcNW2^P=l z{3jo3wuag4^>i2ukH_aj?8RHwjv0lWX0Wet z)A@SDb6AiyS-$xTBZis&@42oHV1_=KUwoFOyT$i-w09hKguxGz`rEDe;I~MSwenwz zI1b9rtnPQUR#?L0{S3#7CyqP>?|s82wic5Wo$j9PGc;_v965CU!vbCZ!kSJST~AyVABwZh4_?D@tHh}omgNf2|Kd$q7!sr ziz3tfuOnVn(R+p-FxaR0GB>957JD1N#;Eag-@ALRPhjorv}jkRlq@^t!LTD{f?<)(y6RXtdCNZpzyCaW#l@)n~*ED(_bK_B-<+VNA{m_V|jl@T9*70s* z@6%ut`DMWE0d@JJvKBRqAwa!7T6mCc`qZmV=QNyC#zYGQ4F1_uC6+7 z|JhvwoyJlJ#wu=c61Ilu{GPRA)aC7AsxzBMiA#=y9hz}sKU<4@F%LYX``MzH81jYJ zX*7@^Br+F#0y{TXvbAw(u6S<9VO++@S#7Axa=cg*=r zH%t8X>8?$~)Qj{BSpsb^5BTD=2^)JPvUrH zIInZwow1P@LFT(qlU3Q*v1Xxp@cr{1Ee<+!{Da%SgsO+OvlL z;unyjoN?7*NaX$5BO5qhqnmZJh$HE5NJi`9zu{XR_IO4k;uYSJy^1W}orVMbd}5#B z8NR%#5rqrR`8giX_kOWwpX-zJXe^@2evEDGV*mBLbjT3G<6hsf!yoh0r#aI&rOd5$ z->kc=8Vhza_ltV!HT)BfyGQTfyFJtEAdg#Ima6x?AI=)~b?clVgqfbzpE>UMuzbwG za~v(NbIh>Tm~5Qb*X-cMYz&utp3lg`ont=Uxi&tY2c22BcjMJr+Z^(^5l@MS*X1rE zoFmCs8=D9Hdymd<9S4ri$U^0*K<;A=@H9=XT_GG5%k9y0kZg&eq)Fws{>qjFL`|Tck#JXOcIpP-k z+1=ukcMcR;)=edFzxQYc%uk)$BAoMP1w6~Ge3<9)Qi{Z*5_v3pdaik2*OOQC81G|- z%04`Ae$?3)-JxBm#}iV9%qISrMOcc(7N7Fbd&0eL-s*h9hUT1k$4m1D$K`1F11<>< zc(=^Y^Suw-u&$pzDVuc^j!XUrWw8c03w4)ChZm5dxaRnA2k%;3<3-ICR*+rVr-Uud zfAib@b){8NtnP(5h;OZoh%FSu>aYw0A)+&B96O`9li99{;;PyBf|$EqG5RY7!}Hi% zY{T4)fWPxn_IUSYR^7KTo#U!rS_{lRBrnI@EG?3j40Plu(rM3@VI ztMa&qNfi6mg@WHXUaxKrG*kEl4_jW(7mIa#8U8}pvgaprW?}12)F-hw_KtTv=S(e2^_#UMAYn@PoLL|2jJ8=yzyiE|)j*4&UZb8lX|f$*6CXqrRtT zs8RA}-aby3TEy<1fg^W@i`6Qs(2c!g6=e~ra{Rd5PQN$J$jTWOF2|m}Z23k!Isc{ zUq$r$h=Q6{`hR!xV!YXL?MT zROf`XWO480HVn@m@gNvzw0*y-`19{DhHdL6W@-Dgxyi;|$8LCO=89i=x#+-y;#Dz< zc%TRh{i*|&mE`LlY39ZUL!@R2-}WxB#ZTkuW;U_mJeD0rebyFRLh$CX@qi?}I_7NT z>Of;1;|amot+kep(h)uJBxO7-6ob;ohcafyj<-yQS2XL>0vycDvqIgAduVT#oY zdaAn4iR<&SJH&@8MSee>pR-+V!xC9Uwh>d>$nV^o7t|DCV4!nVrC?KD93RWVY}1Wj z4hx)*FP5k1@9d0$FUkeaGWOnwjp5v5TmBqol(E7ye!zF#^5t|z>Pzg&qpXX8*LPQi zBNhdhhI)>uSr~gZ2aP>G_{=@Ck=^t8IJf`uoERkE!PY%y#Im$A=RIPDxbM7K2fl;> zd1kXYKh0WMv$==;vXWyRQakFh6XWO%dT-C^y@z0Y%Xwp_t~6_&H?Lw5`q=>FK$c?Obu;6}#j`)GV>$n? z1}4rFcS1t1=~XastkxsuY-7oiPkeHZovK{MoV~x#F*C%j=0q8CG2kA)<9)mnwm@26 z&OUE7C)hP^k*&fk{<;~@wqrfi8CNw`%~`aAVJR&}rcugci-C@a7gS*}gZPcn_vm5@ zJOAkYcww=VW!S}7Y&I6V{Y6ixx7^I_8SP7{2Ip_HBg<#?{x(t%Kig=^S-r|ksRwei z!?>_^pXgKhI7DG<-pL1BiNl(^6{~onsHP?vdps<3b>BU& z?AB~xX=m$ZB)VmNpU?=vO?wZ|WtTIFz4QT(q=AhSm5DgI=9KrvGekQc9v_Aiti@*XetW9C+Oy2J?W$z| z4}Hw_DD(A4MW||4RfjSH`9mw8$QB_eA7XXaYo^Aqs#%MXv1(eLM;n8Xk>58Lcwg_u zmy115oDqLgTgH)O7|%_FU~jK8D#js13&CdT`Ly5c`Y2s^e0ghDFFy@qipVJ$-q-t= zeLUGUzg}+2>QE!y3zF0!)jA(q!Bod;J5&StiLrtnWhA0OER&u2j-Md_FJkXm(_fC; zEPZBcf5)O>4Qm(s%}?XzU7d*-8*gDr)-J*qlVpQ&r?{~wE~0x5X3XdG;dUmjv*70(JE^VmGB zS?X-($@$LaH=D4A&kk9e-+QO#y1$0L{KCv<$vyt+Cwk|7b~tjw@bc+9mY%UkL$v1| zb}SuLY`&uidwNgNfR~Q#_ij7(y|%OJ%=Zb-pQW7n|9=MFr?0Fzyz|`WmEOTfwYtNM zbcEU1&tB_&e2(+xhyLman_1n@FTKJiWYOnLWlcT>ADLymZ(keBotsDd^qz~a*G=W< zdM6_XgB(3Pzdv5=xO;sT+pb4)N7cLcSw7`QZPS@{P95Fs;mqH7?eBf@AAb3#?;l_M zmB0HBzW6J@`1c{rKmE#A{`ps4`{I|s^vmYzUw`eN{ga>nnZM>azx4ATe(e|T&R@LS ir(XY=H$U@_{_)?qr~U0;zQ13d`sFWv*%SZdP5vKNfEY;t literal 0 HcmV?d00001 diff --git a/assets/settings.wav b/assets/settings.wav new file mode 100644 index 0000000000000000000000000000000000000000..3e9a80f076598cd14164d58ebeab034b6abb6553 GIT binary patch literal 19218 zcmZ8}1(+K*w6%s!V_ZtIfi?{^%*@Qp%*;t4%*@Pb7#fC#IcdX58*UqDaLF{b$M*0q zRnIT|-})QFjCAk0M@Ls0+1hqZ8#S^t2cUD^P7Mc*oKV0C03hI_!65(^bRhrAzH2h4W)4+h|HG_iZ7$Cz}-7w%4R1%dX;B{02oyC9Dzm^OG zUBjiY4O|x2LeHo+x`O`DHT-A%d+v)>^bR3W|Hmi3j=Z5d3OuxbYoIdNBSuZadI&3u z2HBwC0RX~=qD$Z>pj{e9#lUl@gbJTkXjAGzKSVI1{6fTWo!1h^Kn}x_o zzySK+n6???^=4`lX@|H^*ya0>_4M)*9xM?0%{rD5kZO zOC}T=Sdgg$vdO2%-BZ5UEf5*$~>#2#RX}0x_qb|RD zpr@C|;yK~|0X}|mlb(ehTT<_&CP`~OcU_C@f0;|tZvC71Z=nu;fA;3g?=w1Ol*-(j zwKQk4uWDd@aEV|Gl@FOhhVWbHZp0M-N<9I_GDR&%?62J2QhaGcbKlBcIQR9m%PDP> zj=H{fys){fo6PAZ2fKyJ0G0KXN+{kUx-l#Uhx${z?(Auq(=(c6+{}n&9?j12&J45& zEfiZw^R(y01E!?;j&%?3v)^=#a!StGt^)1{?zx_(Nm)rvlJg~xPb%rj?Rw5PGyh2? z>6_xu!g}CaU;CWl+2gW{=d}0k_C59A3G@mc3GNOK4!VO{;Bhb-ni9>BGYkupY`JfL z>Pq8J@)(n3lFVDIZhoFU%Tdp@%>BUQPr8#_ zFQs_Ol;j#o-@CtYzP0Tz?WD%*KS|$-#Y5KvW&9(3Eq#Z5Rs7fe!T_HIqWpnD_)Xphalb*W+&gzaFUb1~}t8H`JdRniU zt8j6u0qCT*iw})#4|eix$UdEUHe+K(!^~}2Q*(ax6$~y6EsgYz)`TlkUH9$3T7l^vHNxnqT#sc(ZiRXXlY&y< zd|-B{nr2FaxdI;6R z$0IMq%xI5z4S9yD>br^lbRDj+h2pO}8n}liaVhT9mZ|=fzmuCM)pdV#4C8NDZgHdN z09c?ckm-ugWY`J)Erj$+1lUhF| zD~a*^<(y@2VXJ82OiP(pWG*mYtE!Ze+Qw#yH^Y2rWH8e|#n;3;G)Ky|EwpcReCurKY~rluEahzBobCMRY~b4D`q|af)x+7@Ucs8q zjV4!XujBh7=|W%sBJb*);W>Ja$2Z!4H((dI(D_i|aJ_KP@ZGRoEFDXimup>#rc6~c z#rJoX@{~^QneuZ={gmR#%RF0M3mxe{i1bPBlX-#lzU>4*(Vok3-O_n!<*3li^sf+yTVMQ zGC_X5v{Ei!LF^hj9hmIrfar}-BK4~O1IYQ=og9rc(oocx{s!0tEI zv0Sv~MJ3crKSB`s&=cVU}C*uCwRoeN1|Hbl}-Awk>N62ifdZbL~de9vFE$~+$ zCD=2#D7Y~=KiE3x3BCykf%!oyG&Axs7E?Nd7qrd%#Fpi#=-%&Xl~g2YfTyDSwzIDz zuRS+E(3Wl8X63D-xwz>r^Nu`jG*TbM2a4rFg9F8UyK^38pUSS8)7AUgcPVg0SQf4; z#-pF(9;JtN)Obf&=ybNcsk)_st*~8ntagd+51s)@os#Z)PPuEkx;YsBE6XJ|g&Jd6 z)m+lS=)=g5;n^XtFhOt&SA!jbS%DD&#edp=z|RFf1y%?pBYR@ElyAXIdb%lOJ!wDV z^ty(-YrD6(E;xU4WY|l=-aL};&bQ|QFIfv)W^f(oGr$XN4;1r++6Nl?LOFwT=H)c@ zuJEM?UIZtEBH^#ZM$x&k6e&m6)EdSvq9A>V?P~sPZEv6EZ0|nknV7U6j!c6+l)IU; zgS~+5l=(gDrQQKnpQ3b-7RT(-+mYSjpF+EZ)4^u}!{5yR)OXe=`wsbE1da>s)vP<`*b%f0WtCwzgyv|jQ zj`ni41~7M?Al7NWNQI*1!momj1HJua{j>aa0!4yF1SWJN)HJ*~{5V`9ViKoCx5euy zt@QPTiTTcS)Vjj{!MV)6-1E+}#&f|v#+Bo!Yd>mhV0~-uV>-ZIrZ17Zz%0G9npe(< zoffx*_X-(-AN}KepS^#0>-)a(j|rp+rtlcCP`s?tOm9q(bXT^C>6JOznr;i)e&Gk& zosP4xZ$EPua((T(>(m^J?cHr{&9|6$1f%zp3&$o!hJ|RMW{?e*3+@c&7j{6zEkgrC zLqfemTS9%qzDV(yS?;3=U>{xGG{@S~KE)}zuDSnlk8)3QHFXO19sEe!ccNH z4;*ctY;YO%Q~tXA{)Xi?A?~odLdOf5^NJJ608$E6TBo$3P(kkv{J1Lo>C<_k7c;6ti6%r z1$-=WkdA@&=X^Jw;~(38vHfVfV_j)EW$M6AqXvPz+EeLp^mBNb@F;N5pV$Ab|CE1c z;9)Q=)FnJMvP3)=rQ)lkCQ4bYx-pbEPMO(WroEQiwx8`aoIPEfyQVwfD(`YT57-r( z-TK7TnY~06Aui~P)va^8aSF%(oZ@SD<0h$sTB?jFM@$Uw?Mu?y}-S| z(I6Lk6Rr+th>YqsG~y>(VDFjgTiRPwY`bhU-+eKaeeQc=jd!N#J{zkv~)FRbIsVr^kwn|$kDxOru;GfIZBH4!@mfV zgKGon0c&7*AU#+?*c|E}nHtTAPf^nJbs&=z=o4%a(-8A`OFwIAo8PvVZ)yK%Z{e8j z7~^m{O51<6?XXZLk-kYR))y!bEKi`8N^|IYPe_tDqL17N70 zOFbV*G@tc)&+AD91DHFyjAH@TTE!=B*W<^h(i z)=Rc0{9*e5$4!UVaoCa1k;}f;Hp8NFg_$yBV`HsaNd785Gum4GA(9nd9PSYQCQO8% zg|>wzg?fa(3k5=<@KW(ytc%=Q8wBQ4W7%YL1FK=n2V2Z%|HWS0zKb_(eQd9+10m*m zmeyths<{=-lPit5+C{z-`(7L$ek>dd8i9uab8vrfjj$JvBuV1fs2tlPjZ|uBobd+i zBPY=fS;BPM+{Sv{#@Y#(Jt?Q**z9;_zsi@lHMMBmWacB;8N{_`iYkqdmyVT)wikCr zxX9G77@8TfhwchTh3CSc(A02Yu}2KZ0X1Y~kq4MGQ@Ul3^{}lWKZ~Et*Wu%~pCRHh zHXfoLZryC@Z2pOx#W<*eV2k#LTp|8c{3qNk)LOU|JQXBhj{7duHQXfftym~pBQ`1S zl_n|`G@C&YRw@ruf-7aVT7R)|cDrMz<8Q|SM}9|bdm4Y#%36|4G3F|@nV4(r(K3`? zuxFo+t&UC;m%@DXB$OWdNoWNhD}*|s!QuR3!`Mq{tvbZW2V3s}yUoN{%+^D2G-+!a zYx}{r*tXSn#&*kg5h7o2ZEKlpYRz7xLLg0VuAGaHiEfEh3bzTp5uOR@p^c%+;lINZ zBDAB1w@Vkx>y|G(~5|+DTItuQn4Dr1mi)CzzL5 z&)UZEzw+n#q5L~reb{cm zfHST6;-RP#8zxEed^NYe!1x66k`<_8G|k*(2D4FiG3E^TpY^_25TlRRzQ*^Xn@Y&J&r0qgYZY9G06>{9rT!1Wv9K@f9UgR zXJ%XfC$Wz9o*c>+HC;5VG)*>1rgfGSexNfwsYseP&!&7s@~_ETCvAlDPwp$=m3E1I z_yxRMd@76xy@7e5fe`XXbFwnae(w6|M#g(@sql|dJL4F=#{8O(J9YO`&skDNt_qCfD zbO<)f}MlTCi>pN^y?TUy0DkJIm@@5tRf`8YqCnW^uTevfV!$B2G$Pc$udELu;z z667DA*~}{b?qWlA9C?F_SpTqhbv}3Qbw=R0HPwF6{>pK}waTM;a=W+M zXIt8^70DD`lirE1;9dR8oB`RHSp~8mWe0K^_#XL(38TcP(g5ve<1g@nC`Pql@|vbw zt2lCbHl@_bbuD)!cQ{vvRJZ$rr2{oZJ1^ae<&QOwJ&4iqp|Rh^PT>l{!(MY1_}u8D z?<1FWHqbA2T9@hDX1{HNy{IE%Z)IQ1f5k85X-6H`3s1e2lBvU!>$^nj7`778RlONo z7S0_!5B5FRs?v-Z`iBmjr;`H%cORxC(;V% z`Z=vb>cgb2&fb>E^xsCdI#Q8kTKP@>ReBXGEe3^d{-3jlW;FY>^JB{A>Di41HEsqo znf&G?>tpLfYklhh%QQ<->swnt#{~D9y+ zC(tptHCQEBI-vNxzFUDeq59F4Qij?GQ1sv2E$aryBR7}aBxNI93;QA2lk|;Swy(5A zm{R0LW0L--mPb3GESJtjM}+SNN_yF>2cKVjD)PBh=2`E|@EzGi3}p#b+j;1`!?_?Qbn{h}h zp_Gb0iPR9z`0i!j%e9M=Vp)1%F2G=S76UM zqpzkcrgzq-_H)i%u0P;?&Liiy&MZf1=S62PXMg)}Yfsa5MkA|(QrcUoQ}leOV_=K- zPWGFuV_9{xr^6L$PWVM!A}`ac5ogJk)ER0V?PYSAyq0h5dt9#M-&4otI+3ePT6)rB z{u0|A%vAcue~vn$`J*eMpQ1aW3&b^HIT-T=vxj9K$atDH-@hnwQXT{5GKb8EZ1drq zZ?2<=V~O2nFJ?FFgPmEfA?_lsA$HMHnHxv#Hl8X(yj~<8?CHPY&B%F}Q`T#Ezwr+W zY!E(2j>Li5O21*e1r^9NdMP{0{L0qexz$rK#h*Gk%}C`_e6Eky$qWxFs$S_ud`J9g z{BwM0JT=CMZ9=63-W+RI$&C6LrLrdm;^JhrJ*lz{EH!QU`Nn*Pt&5GeHMNc9n>uE> z4tdTdo$xGmR?(Q?QiqgU~>j6AGjTC8ZH;DC^c0l8qdg9f1KYoqaC~}>=~}Dsg5bm0n-sv8B1SVaYrS0mE_T>k<=^6C0s47HJQ!Eb(xNhkI13k zp+2Dpp;qA;;b`clzz2K#r}}#Op9i}}n#Q}Ry+LcbnCZOri2bh9?w;lT&K-AEcTI9$ zbac1>VLflU#muMf5*0vyy^uOXnjEbYHV13^%H#-Hr?Z-5d%SR@jmXk|J%y}6H(_=% z#~3RsvD;1Amec%0r|OxRLZ|IZeV_EkanrnnDyhe$La`sk8<9g1N*p5g6E8&Sgv$u; z{rkLA;A&m_!1nN%crEP@!p%N3-?ZJc%Z~oeR?er6jt+;TxMQB9p5qkXz`EJwWY<$P zQB^OaNU^<<>B3+BkoRfMkeo|72ff4n?SvNMCaHs#3VtIFk##A9YRDAimYIjz7CH8~ znyU^3X*Sj; zUeY0bI<2a>0xG*9^}X|`*& z?GyWwP_+pPEl-gKN!_Jj{ADap^j_E@aYjDoaKrZo<+zLFWE0{)Fc3NF< z?PQC!opqsgh%KAHZgNnHCII7vwOs}Y4j0=hpyBld2+7DM=X9kW0@&|tk3c+uL zkHW!liRg*AAb(P`^uokJY9YJYe9^YU0o>a?Q+GYm$3`U8OCyLj%rsA zD%tW5>2j>AxG8ib5b(awzMEA)`-Zooa5p+i?L;IqD>z`@WZr1*X|8DAVcrba>t@;C zJM(zzBn|OQbJn%Zg8jLkUS56~?HRci$`(!t?}R3yg`p8}owbxe2$J9pca7@t3-JDZ zEAcB$nkrar_PWmVuG#K`?#AwZF5dYPew$IBOQC;)U&n@YT7Rq7RVu+zM+lpRUjBMs zBm0-^qdDXKzEHJzD{TyH39o@r{@+4pK)O60Vc4WH{ zZ`J0~p(r61jbw)Hk+G4*kt&f!VMUl5JR2w#)P>uT4@lO0T zl0RHk=o%>L-{rp&{3?ioyiu5w-iR=@e2KM?>;VRWbf05vKp}k@?X`1RX9)Q1y#bhh0AMIzd;5R85 zwl$6n*9uRgq|2VkuIcI^EvN*7fzClS7!vw~cZ-eU8M3Y}*Kfmn>#1aGdMYcLwpc6LM?3SnX-^(cLHAk5 zD{C(<7q!dyO53K~mgmTA~PQyg>PYdZ%*Te(skhK(Hi4`zFBLlZkD&jTSa~0?ZWlIJbyA& z)%svOoGac)eQGQt`_f~XkBoyY$DUwUaASeFd02<(R6a7_&@5_3y))IP=p;vu<` zlBl7ygZY|W3rFk4)(895P+#$Tm}Sz8Mr0M{7`MXw+_J{{xAm!YlXZf1jCHxSKU_sGX8w`;h51YY zQZ^oH)0A}SX7qH#84d^y;i|_%VQ#2oWM_1!lv{15M+}CzL(CwDQ~T*^Y`Uq6rN6Da z{efeHv%mALUAERTwWNOo5iO(~k`3vmv`3mG`QtR4y%AzvsQlwWPIwsFEn1}AYHm=C zy33s7G}A@14j*sLAI+sKH!TyaAFXSw%Pb#Fuh?~TQSz{nPunQhiC2YV*VeEt{5)g{ zPlDfV)D!(tE$&eMP^;)2jZt7PQGj|z|G{ROOxBOKzV_O1OpV%`^D`{z+y&|h*s7;$ z71c}1M8%^Fm-|SOSRJ^oKR1*L`(quUV|Y}Qm5*vNI89w*Qn|mma;7q-(xw)s>!vm4 zJ(dU7&9)7;xz_#WQrs;1DDhFRto|lFiZzdR5PyqokNgtxN4^o;i?_rp(bDl-QfuWm zwWj{Sm`4n!CNi731Lon@d$yB&irvds;MZAenSWq!P(KjYjJ5hlt)q5DeGmIiZD~eq zgLpPvDpXuxguJ1Ck&Ut3%5=RAF`H89PfROzG&`NW%(mm|ndX~i%OG2OzB(^j&zS>k z7WDxz`dnqA#Kz6Bh0&hTQPDZkAELFR@5S3<@o34|qIe-WTX~@UZe$WEbPcwy$!9KY z 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 = true; } + sound_play_effect(SFX_EXIT); } else if (menu->actions.enter) { if (cm->list[cm->selected].submenu) { cm->submenu = cm->list[cm->selected].submenu; @@ -51,16 +53,19 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) cm->list[cm->selected].action(menu, cm->list[cm->selected].arg); top->hide_pending = true; } + sound_play_effect(SFX_ENTER); } else if (menu->actions.go_up) { cm->selected -= 1; if (cm->selected < 0) { cm->selected = 0; } + sound_play_effect(SFX_CURSOR); } else if (menu->actions.go_down) { cm->selected += 1; if (cm->selected >= cm->count) { cm->selected = (cm->count - 1); } + sound_play_effect(SFX_CURSOR); } return true; diff --git a/src/menu/menu.c b/src/menu/menu.c index beef676d0..b1e05380e 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -115,6 +115,10 @@ static void menu_init (boot_params_t *boot_params) { __boot_tvtype = TV_NTSC; } + if (menu->settings.sound_enabled) { + sound_init_sfx(); + } + display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED); register_VI_handler(frame_counter_handler); diff --git a/src/menu/settings.c b/src/menu/settings.c index 3e046ed07..0b3725317 100644 --- a/src/menu/settings.c +++ b/src/menu/settings.c @@ -13,10 +13,10 @@ static settings_t init = { .show_protected_entries = false, .default_directory = "/", .use_saves_folder = true, + .sound_enabled = true, /* Beta feature flags (should always init to off) */ .bgm_enabled = false, - .sound_enabled = false, .rumble_enabled = false, }; @@ -39,10 +39,10 @@ void settings_load (settings_t *settings) { settings->show_protected_entries = mini_get_bool(ini, "menu", "show_protected_entries", init.show_protected_entries); settings->default_directory = strdup(mini_get_string(ini, "menu", "default_directory", init.default_directory)); settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder); + settings->sound_enabled = mini_get_bool(ini, "menu", "sound_enabled", init.sound_enabled); /* Beta feature flags, they might not be in the file */ settings->bgm_enabled = mini_get_bool(ini, "menu_beta_flag", "bgm_enabled", init.bgm_enabled); - settings->sound_enabled = mini_get_bool(ini, "menu_beta_flag", "sound_enabled", init.sound_enabled); settings->rumble_enabled = mini_get_bool(ini, "menu_beta_flag", "rumble_enabled", init.rumble_enabled); mini_free(ini); @@ -55,10 +55,10 @@ void settings_save (settings_t *settings) { mini_set_bool(ini, "menu", "show_protected_entries", settings->show_protected_entries); mini_set_string(ini, "menu", "default_directory", settings->default_directory); mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder); + mini_set_bool(ini, "menu", "sound_enabled", settings->sound_enabled); /* Beta feature flags, they should not save until production ready! */ // mini_set_bool(ini, "menu_beta_flag", "bgm_enabled", settings->bgm_enabled); - // mini_set_bool(ini, "menu_beta_flag", "sound_enabled", settings->sound_enabled); // mini_set_bool(ini, "menu_beta_flag", "rumble_enabled", settings->rumble_enabled); mini_save(ini, MINI_FLAGS_SKIP_EMPTY_GROUPS); diff --git a/src/menu/sound.c b/src/menu/sound.c index 9e58d0015..bf950d6c5 100644 --- a/src/menu/sound.c +++ b/src/menu/sound.c @@ -3,14 +3,18 @@ #include #include "mp3_player.h" +#include "sound.h" #define DEFAULT_FREQUENCY (44100) #define NUM_BUFFERS (4) -#define NUM_CHANNELS (2) +#define NUM_CHANNELS (3) + +static wav64_t sfx_cursor, sfx_error, sfx_enter, sfx_exit, sfx_setting; static bool sound_initialized = false; +static bool sfx_enabled = false; static void sound_reconfigure (int frequency) { @@ -35,6 +39,43 @@ void sound_init_mp3_playback (void) { sound_reconfigure(mp3player_get_samplerate()); } + +void sound_init_sfx (void) { + mixer_ch_set_vol(SOUND_SFX_CHANNEL, 0.5f, 0.5f); + wav64_open(&sfx_cursor, "rom:/cursorsound.wav64"); + wav64_open(&sfx_exit, "rom:/back.wav64"); + wav64_open(&sfx_setting, "rom:/settings.wav64"); + wav64_open(&sfx_enter, "rom:/enter.wav64"); + wav64_open(&sfx_error, "rom:/error.wav64"); + sfx_enabled = true; +} + + +void sound_play_effect(sound_effect_t sfx) { + if(sfx_enabled) { + switch (sfx) { + case SFX_CURSOR: + wav64_play(&sfx_cursor, SOUND_SFX_CHANNEL); + break; + case SFX_EXIT: + wav64_play(&sfx_exit, SOUND_SFX_CHANNEL); + break; + case SFX_SETTING: + wav64_play(&sfx_setting, SOUND_SFX_CHANNEL); + break; + case SFX_ENTER: + wav64_play(&sfx_enter, SOUND_SFX_CHANNEL); + break; + case SFX_ERROR: + wav64_play(&sfx_error, SOUND_SFX_CHANNEL); + break; + default: + break; + } + } +} + + void sound_deinit (void) { if (sound_initialized) { mixer_close(); diff --git a/src/menu/sound.h b/src/menu/sound.h index 44ecf6bc1..cf3115194 100644 --- a/src/menu/sound.h +++ b/src/menu/sound.h @@ -9,12 +9,22 @@ #define SOUND_MP3_PLAYER_CHANNEL (0) +#define SOUND_SFX_CHANNEL (2) + +typedef enum { + SFX_CURSOR, + SFX_ERROR, + SFX_ENTER, + SFX_EXIT, + SFX_SETTING, +} sound_effect_t; void sound_init_default (void); void sound_init_mp3_playback (void); +void sound_init_sfx (void); +void sound_play_effect(sound_effect_t sfx); void sound_deinit (void); void sound_poll (void); - #endif diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index 75c45a15e..c9a1b7e5f 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -6,6 +6,7 @@ #include "../fonts.h" #include "utils/fs.h" #include "views.h" +#include "../sound.h" static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL }; @@ -296,16 +297,19 @@ static void process (menu_t *menu) { if (menu->browser.selected < 0) { menu->browser.selected = 0; } + sound_play_effect(SFX_CURSOR); } else if (menu->actions.go_down) { menu->browser.selected += scroll_speed; if (menu->browser.selected >= menu->browser.entries) { menu->browser.selected = menu->browser.entries - 1; } + sound_play_effect(SFX_CURSOR); } menu->browser.entry = &menu->browser.list[menu->browser.selected]; } if (menu->actions.enter && menu->browser.entry) { + sound_play_effect(SFX_ENTER); switch (menu->browser.entry->type) { case ENTRY_TYPE_DIR: if (push_directory(menu, menu->browser.entry->name)) { @@ -340,10 +344,13 @@ static void process (menu_t *menu) { menu->browser.valid = false; menu_show_error(menu, "Couldn't open last directory"); } + sound_play_effect(SFX_EXIT); } else if (menu->actions.options && menu->browser.entry) { component_context_menu_show(&entry_context_menu); + sound_play_effect(SFX_SETTING); } else if (menu->actions.settings) { component_context_menu_show(&settings_context_menu); + sound_play_effect(SFX_SETTING); } } diff --git a/src/menu/views/credits.c b/src/menu/views/credits.c index dc1d0665e..fb068e6b0 100644 --- a/src/menu/views/credits.c +++ b/src/menu/views/credits.c @@ -1,5 +1,5 @@ #include "views.h" - +#include "../sound.h" #ifndef MENU_VERSION #define MENU_VERSION "Unknown" @@ -13,6 +13,7 @@ static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/error.c b/src/menu/views/error.c index a5c0b84a3..07eb70d42 100644 --- a/src/menu/views/error.c +++ b/src/menu/views/error.c @@ -1,9 +1,11 @@ #include "views.h" +#include "../sound.h" static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } @@ -48,6 +50,7 @@ void view_error_display (menu_t *menu, surface_t *display) { } void menu_show_error (menu_t *menu, char *error_message) { + sound_play_effect(SFX_ERROR); menu->next_mode = MENU_MODE_ERROR; menu->error_message = error_message; } diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index 211f57914..19ee97d4d 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -1,4 +1,5 @@ #include +#include "../sound.h" #include "utils/fs.h" #include "views.h" @@ -50,6 +51,7 @@ static char *format_file_type (char *name, bool is_directory) { static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 084cc0734..a957eaa9a 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -1,9 +1,11 @@ #include "views.h" +#include "../sound.h" static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/image_viewer.c b/src/menu/views/image_viewer.c index 21c153b31..d58d3743e 100644 --- a/src/menu/views/image_viewer.c +++ b/src/menu/views/image_viewer.c @@ -1,4 +1,5 @@ #include +#include "../sound.h" #include "../png_decoder.h" #include "views.h" @@ -40,6 +41,7 @@ static void process (menu_t *menu) { } else { menu->next_mode = MENU_MODE_BROWSER; } + sound_play_effect(SFX_EXIT); } else if (menu->actions.enter && image) { if (show_message) { show_message = false; @@ -48,6 +50,7 @@ static void process (menu_t *menu) { } else { show_message = true; } + sound_play_effect(SFX_ENTER); } } diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 7216e97d2..983d052a6 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -1,6 +1,7 @@ #include "../cart_load.h" #include "../disk_info.h" #include "boot/boot.h" +#include "../sound.h" #include "views.h" @@ -34,8 +35,10 @@ static void process (menu_t *menu) { } else if (menu->actions.options && menu->load.rom_path) { load_pending = true; load_rom = true; + sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index 60f8d114f..589a6a599 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -1,6 +1,7 @@ #include "../cart_load.h" #include "boot/boot.h" #include "utils/fs.h" +#include "../sound.h" #include "views.h" @@ -36,6 +37,7 @@ static void process (menu_t *menu) { load_pending = true; } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 9695bbc4c..437a43a32 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -1,6 +1,7 @@ #include "../cart_load.h" #include "../rom_info.h" #include "boot/boot.h" +#include "../sound.h" #include "views.h" @@ -198,8 +199,10 @@ static void process (menu_t *menu) { load_pending = true; } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } else if (menu->actions.options) { component_context_menu_show(&options_context_menu); + sound_play_effect(SFX_SETTING); } } diff --git a/src/menu/views/music_player.c b/src/menu/views/music_player.c index 7b3d9751e..4de007563 100644 --- a/src/menu/views/music_player.c +++ b/src/menu/views/music_player.c @@ -42,11 +42,13 @@ static void process (menu_t *menu) { menu_show_error(menu, convert_error_message(err)); } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } else if (menu->actions.enter) { err = mp3player_toggle(); if (err != MP3PLAYER_OK) { menu_show_error(menu, convert_error_message(err)); } + sound_play_effect(SFX_ENTER); } else if (menu->actions.go_left || menu->actions.go_right) { int seconds = menu->actions.go_fast ? SEEK_SECONDS_FAST : SEEK_SECONDS; err = mp3player_seek(menu->actions.go_left ? (-seconds) : seconds); diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index caeb9305b..d810f382d 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -1,4 +1,5 @@ #include +#include "../sound.h" #include "views.h" // FIXME: add implementation! @@ -18,6 +19,7 @@ static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 5e9d18546..badb3696b 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -1,3 +1,4 @@ +#include "../sound.h" #include "views.h" @@ -12,6 +13,7 @@ static const char *format_switch (bool state) { static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/system_info.c b/src/menu/views/system_info.c index 02b78f725..74ed4cbd0 100644 --- a/src/menu/views/system_info.c +++ b/src/menu/views/system_info.c @@ -1,5 +1,6 @@ #include +#include "../sound.h" #include "views.h" @@ -28,6 +29,7 @@ static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } } diff --git a/src/menu/views/text_viewer.c b/src/menu/views/text_viewer.c index 6b688479f..cf8b505fe 100644 --- a/src/menu/views/text_viewer.c +++ b/src/menu/views/text_viewer.c @@ -3,6 +3,7 @@ #include "../components/constants.h" #include "../fonts.h" +#include "../sound.h" #include "utils/utils.h" #include "views.h" @@ -55,6 +56,7 @@ static void perform_vertical_scroll (int lines) { static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; + sound_play_effect(SFX_EXIT); } else if (text) { if (menu->actions.go_up) { perform_vertical_scroll(menu->actions.go_fast ? -10 : -1); From 208527a9ac6bc365760f11022193a211e8d4f7ec Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 5 Jun 2024 21:33:09 +0100 Subject: [PATCH 02/89] Add information text to Flashcart Info (#109) ## Description Warns users that the display is meant to be empty. ## Motivation and Context It is not yet implemented. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/views/flashcart_info.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index a957eaa9a..41c1f4431 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -19,8 +19,17 @@ static void draw (menu_t *menu, surface_t *d) { component_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "FLASHCART INFORMATION\n" + "\n" + "\n" + "This feature is not yet supported.\n\n" ); + // FIXME: Display: + // * cart_type + // * Firmware version + // * supported features (flashcart_features_t) + + component_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" From 7a2833cf34c94040c3904940ed7ac66caab1eabc Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 11 Jun 2024 01:59:30 +0100 Subject: [PATCH 03/89] Improve file information screen (#112) Adds extra emulator ROM extensions. Adds Controller Pak extension used by ares. --- src/menu/views/file_info.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index 19ee97d4d..496c07f47 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -13,8 +13,8 @@ static const char *patch_extensions[] = { "aps", "bps", "ips", "pps", "ups", "xd static const char *archive_extensions[] = { "zip", "rar", "7z", "tar", "gz", NULL }; static const char *image_extensions[] = { "png", "jpg", "gif", NULL }; static const char *music_extensions[] = { "mp3", "wav", "ogg", "wma", "flac", NULL }; -static const char *dexdrive_extensions[] = { "mpk", NULL }; -static const char *emulator_extensions[] = { "emu", NULL }; +static const char *controller_pak_extensions[] = { "mpk", "pak", NULL }; +static const char *emulator_extensions[] = { "nes", "smc", "gb", "gbc", "sms", "gg", NULL }; static struct stat st; @@ -39,10 +39,10 @@ static char *format_file_type (char *name, bool is_directory) { return " Type: Image file\n"; } else if (file_has_extensions(name, music_extensions)) { return " Type: Music file\n"; - } else if (file_has_extensions(name, dexdrive_extensions)) { - return " Type: DexDrive CPak backup file\n"; + } else if (file_has_extensions(name, controller_pak_extensions)) { + return " Type: Controller Pak file\n"; } else if (file_has_extensions(name, emulator_extensions)) { - return " Type: Emulator file\n"; + return " Type: Emulator ROM file\n"; } return " Type: Unknown file\n"; } From 8a3c9ce1a312a78fc3f6b624b6bef3bf2a39c986 Mon Sep 17 00:00:00 2001 From: Christopher Bonhage Date: Tue, 11 Jun 2024 06:13:06 -0400 Subject: [PATCH 04/89] Hide macOS system files in the root of the SD card (#113) ## Description Make the root of the SD card a little cleaner by hiding the crap that macOS puts there. ## Motivation and Context Whenever an SD card touches macOS, a handful of hidden files get created that cannot be touched. N64FlashcartMenu has no need for these files, so they should be hidden. ## How Has This Been Tested? Built and run on SummerCart64 with an NTSC N64 ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [X] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [X] All new and existing tests passed. Signed-off-by: meeq --- src/menu/views/browser.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index c9a1b7e5f..2ff3c8670 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -25,7 +25,14 @@ static const char *hidden_paths[] = { "/ED64", "/ED64P", "/sc64menu.n64", + // Windows garbage "/System Volume Information", + // macOS garbage + "/.fseventsd", + "/.Spotlight-V100", + "/.Trashes", + "/.VolumeIcon.icns", + "/.metadata_never_index", NULL, }; From f8e3ddc58c521dca7e2be957793b662fdb99fba9 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sat, 29 Jun 2024 18:22:05 +0100 Subject: [PATCH 05/89] Fix font ellipsis (#115) ## Description * Update libdragon submodule. * Add optimization. ## Motivation and Context ellipsis was broken. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [x] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- Makefile | 2 +- libdragon | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f92267b7e..ae6e69618 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ FILESYSTEM = \ $(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second -$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-1FF -r 2026-2026 --ellipsis 2026,1 +$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 80-1FF -r 2026-2026 --ellipsis 2026,1 $(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1 diff --git a/libdragon b/libdragon index af650428e..536df10ad 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit af650428e9615f4e08d8e7eae187929a90c15ccc +Subproject commit 536df10ad65614f519bea40482b6e2657b9c4c84 From bd821bf493d90b55f8f7312c5728436a31af62c2 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 7 Jul 2024 18:30:25 +0100 Subject: [PATCH 06/89] Settings context menu (#116) ## Description Adds ability to set the config.ini settings from the menu. ## Motivation and Context Ability to set the settings was missing from the menu. ## How Has This Been Tested? Tested on an SC64. ## Screenshots ![image](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/11439699/6ca39679-d997-4f6c-a670-55d2334814ce) ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- libdragon | 2 +- src/menu/views/rtc.c | 2 +- src/menu/views/settings_editor.c | 146 +++++++++++++++++++++++++++---- 3 files changed, 131 insertions(+), 19 deletions(-) diff --git a/libdragon b/libdragon index 536df10ad..4d4449aac 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 536df10ad65614f519bea40482b6e2657b9c4c84 +Subproject commit 4d4449aac2af2ba8306cd4e61974ac682f1ea400 diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index d810f382d..ae56fd8b2 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -36,7 +36,7 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "\n" "To set the date and time, please use the PC terminal\n" - "application and set via USB.\n\n" + "application and set via USB or a game that uses it.\n\n" "Current date & time: %s\n", menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n" ); diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index badb3696b..4dc2539c3 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -1,4 +1,6 @@ +#include #include "../sound.h" +#include "../settings.h" #include "views.h" @@ -9,9 +11,108 @@ static const char *format_switch (bool state) { } } +static void set_pal60_type (menu_t *menu, void *arg) { + menu->settings.pal60_enabled = (bool) (arg); + settings_save(&menu->settings); +} + +static void set_protected_entries_type (menu_t *menu, void *arg) { + menu->settings.show_protected_entries = (bool) (arg); + settings_save(&menu->settings); + + menu->browser.reload = true; +} + +static void set_use_saves_folder_type (menu_t *menu, void *arg) { + menu->settings.use_saves_folder = (bool) (arg); + settings_save(&menu->settings); +} + +static void set_sound_enabled_type (menu_t *menu, void *arg) { + menu->settings.sound_enabled = (bool) (arg); + settings_save(&menu->settings); +} + +#ifdef BETA_SETTINGS +static void set_bgm_enabled_type (menu_t *menu, void *arg) { + menu->settings.bgm_enabled = (bool) (arg); + settings_save(&menu->settings); +} + +static void set_rumble_enabled_type (menu_t *menu, void *arg) { + menu->settings.rumble_enabled = (bool) (arg); + settings_save(&menu->settings); +} + +// static void set_use_default_settings (menu_t *menu, void *arg) { +// // FIXME: add implementation +// menu->browser.reload = true; +// } +#endif + + +static component_context_menu_t set_pal60_type_context_menu = { .list = { + {.text = "On", .action = set_pal60_type, .arg = (void *) (true) }, + {.text = "Off", .action = set_pal60_type, .arg = (void *) (false) }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + +static component_context_menu_t set_protected_entries_type_context_menu = { .list = { + {.text = "On", .action = set_protected_entries_type, .arg = (void *) (true) }, + {.text = "Off", .action = set_protected_entries_type, .arg = (void *) (false) }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + +static component_context_menu_t set_sound_enabled_type_context_menu = { .list = { + {.text = "On", .action = set_sound_enabled_type, .arg = (void *) (true) }, + {.text = "Off", .action = set_sound_enabled_type, .arg = (void *) (false) }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + +static component_context_menu_t set_use_saves_folder_type_context_menu = { .list = { + {.text = "On", .action = set_use_saves_folder_type, .arg = (void *) (true) }, + {.text = "Off", .action = set_use_saves_folder_type, .arg = (void *) (false) }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + +#ifdef BETA_SETTINGS +static component_context_menu_t set_bgm_enabled_type_context_menu = { .list = { + {.text = "On", .action = set_bgm_enabled_type, .arg = (void *) (true) }, + {.text = "Off", .action = set_bgm_enabled_type, .arg = (void *) (false) }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + +static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = { + {.text = "On", .action = set_rumble_enabled_type, .arg = (void *) (true) }, + {.text = "Off", .action = set_rumble_enabled_type, .arg = (void *) (false) }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; +#endif + +static component_context_menu_t options_context_menu = { .list = { + { .text = "PAL60 Mode", .submenu = &set_pal60_type_context_menu }, + { .text = "Show Hidden Files", .submenu = &set_protected_entries_type_context_menu }, + { .text = "Sound Effects", .submenu = &set_sound_enabled_type_context_menu }, + { .text = "Use Saves Folder", .submenu = &set_use_saves_folder_type_context_menu }, +#ifdef BETA_SETTINGS + { .text = "Background Music", .submenu = &set_bgm_enabled_type_context_menu }, + { .text = "Rumble Feedback", .submenu = &set_rumble_enabled_type_context_menu }, + // { .text = "Restore Defaults", .action = set_use_default_settings }, +#endif + + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + static void process (menu_t *menu) { - if (menu->actions.back) { + if (component_context_menu_process(menu, &options_context_menu)) { + return; + } + + if (menu->actions.enter) { + component_context_menu_show(&options_context_menu); + sound_play_effect(SFX_SETTING); + } else if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); } @@ -26,44 +127,55 @@ static void draw (menu_t *menu, surface_t *d) { component_main_text_draw( ALIGN_CENTER, VALIGN_TOP, - "SETTINGS EDITOR\n" + "MENU SETTINGS EDITOR\n" "\n" ); component_main_text_draw( ALIGN_LEFT, VALIGN_TOP, - "\n" - "\n" - "To change the settings, please adjust them\n" - "directly in the 'menu/config.ini' file.\n\n" - "pal60_enabled: %s\n" - "show_protected_entries: %s\n" - "default_directory: %s\n" - "use_saves_folder: %s\n" - "bgm_enabled: %s\n" - "sound_enabled: %s\n" - "rumble_enabled: %s\n", + "\n\n" + " Default Directory : %s\n\n" + "To change the menu settings, press 'A'.\n\n" + " PAL60 Mode : %s\n" + " Show Hidden Files : %s\n" + " Use Saves folder : %s\n" + "* Sound Effects : %s\n" +#ifdef BETA_SETTINGS + " Background Music : %s\n" + " Rumble Feedback : %s\n" +#endif + "\n\n" + "Note: Certain settings have the following caveats:\n\n" + "* Requires a flashcart reboot.\n", + menu->settings.default_directory, format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.show_protected_entries), - menu->settings.default_directory, format_switch(menu->settings.use_saves_folder), + format_switch(menu->settings.sound_enabled) +#ifdef BETA_SETTINGS + , format_switch(menu->settings.bgm_enabled), - format_switch(menu->settings.sound_enabled), format_switch(menu->settings.rumble_enabled) +#endif ); + component_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, - "\n" + "A: Change\n" "B: Back" ); + component_context_menu_draw(&options_context_menu); + rdpq_detach_show(); } void view_settings_init (menu_t *menu) { - // Nothing to initialize (yet) + + component_context_menu_init(&options_context_menu); + } void view_settings_display (menu_t *menu, surface_t *display) { From a169c0a52fcefee6cf58cc993a85e6b3b1998ff9 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 9 Jul 2024 11:52:25 +0100 Subject: [PATCH 07/89] Add missing button actions (#119) ## Description Allow L or Z to be used in the menu. For instance, could be used to show extra info in the `ROM Info` display. ## Motivation and Context These buttons were not available. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/actions.c | 6 ++++++ src/menu/menu_state.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/menu/actions.c b/src/menu/actions.c index 8bb585870..dcabccffd 100644 --- a/src/menu/actions.c +++ b/src/menu/actions.c @@ -21,6 +21,8 @@ static void actions_clear (menu_t *menu) { menu->actions.back = false; menu->actions.options = false; menu->actions.settings = false; + menu->actions.l_context = false; + menu->actions.z_context = false; } static void actions_update_direction (menu_t *menu) { @@ -91,6 +93,10 @@ static void actions_update_buttons (menu_t *menu) { menu->actions.options = true; } else if (pressed.start) { menu->actions.settings = true; + } else if (pressed.l) { + menu->actions.l_context = true; + } else if (pressed.z) { + menu->actions.z_context = true; } } diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index c2ad221d8..5077da4e9 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -85,6 +85,8 @@ typedef struct { bool back; bool options; bool settings; + bool l_context; + bool z_context; } actions; struct { From deb78d1212bfe8d575ff89b388ee7bbefe70bddf Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 9 Jul 2024 12:52:40 +0100 Subject: [PATCH 08/89] Update libdragon submodule --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 4d4449aac..e2e2dc063 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 4d4449aac2af2ba8306cd4e61974ac682f1ea400 +Subproject commit e2e2dc063add29e9d11f55ae3fbeccc189fb2ad6 From fa4bdbbfb94acff02739d8820e7c706caf621a39 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sat, 13 Jul 2024 22:18:33 +0100 Subject: [PATCH 09/89] Merge L and Z button action (#120) ## Description Merge the L and Z action to be the "same" button press, depending on how the joypad is gripped. ## Motivation and Context Simplifies the menu controls. https://github.com/Polprzewodnikowy/N64FlashcartMenu/pull/119#issuecomment-2218853256 ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/actions.c | 9 +++------ src/menu/menu_state.h | 3 +-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/menu/actions.c b/src/menu/actions.c index dcabccffd..14c87fb63 100644 --- a/src/menu/actions.c +++ b/src/menu/actions.c @@ -21,8 +21,7 @@ static void actions_clear (menu_t *menu) { menu->actions.back = false; menu->actions.options = false; menu->actions.settings = false; - menu->actions.l_context = false; - menu->actions.z_context = false; + menu->actions.lz_context = false; } static void actions_update_direction (menu_t *menu) { @@ -93,10 +92,8 @@ static void actions_update_buttons (menu_t *menu) { menu->actions.options = true; } else if (pressed.start) { menu->actions.settings = true; - } else if (pressed.l) { - menu->actions.l_context = true; - } else if (pressed.z) { - menu->actions.z_context = true; + } else if (pressed.l || pressed.z) { + menu->actions.lz_context = true; } } diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index 5077da4e9..0d6aa4596 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -85,8 +85,7 @@ typedef struct { bool back; bool options; bool settings; - bool l_context; - bool z_context; + bool lz_context; } actions; struct { From 73f848450da8757342b7e4cec08115ebbe7d729b Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 15 Jul 2024 19:39:03 +0100 Subject: [PATCH 10/89] Add extra rom info screen (#121) ## Description Move certain parameters to a seperate screen (messagebox). ## Motivation and Context The ROM info was convoluted Builds on #120 ## How Has This Been Tested? ## Screenshots ![image](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/11439699/7d1626c0-3b8d-4e7d-8c3b-61f23744dc60) ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/views/load_rom.c | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index fd53682e4..5e2f2b70e 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -4,7 +4,7 @@ #include "../sound.h" #include "views.h" - +static bool show_extra_info_message = false; static bool load_pending; static component_boxart_t *boxart; @@ -203,6 +203,13 @@ static void process (menu_t *menu) { } else if (menu->actions.options) { component_context_menu_show(&options_context_menu); sound_play_effect(SFX_SETTING); + } else if (menu->actions.lz_context) { + if (show_extra_info_message) { + show_extra_info_message = false; + } else { + show_extra_info_message = true; + } + sound_play_effect(SFX_SETTING); } } @@ -240,10 +247,7 @@ static void draw (menu_t *menu, surface_t *d) { " Save type: %s\n" " TV type: %s\n" " Expansion PAK: %s\n" - " CIC: %s\n" - " Boot address: 0x%08lX\n" - " SDK version: %.1f%c\n" - " Clock Rate: %.2fMHz\n", + " CIC: %s\n", format_rom_endianness(menu->load.rom_info.endianness), menu->load.rom_info.title, menu->load.rom_info.game_code[0], menu->load.rom_info.game_code[1], menu->load.rom_info.game_code[2], menu->load.rom_info.game_code[3], @@ -254,10 +258,7 @@ static void draw (menu_t *menu, surface_t *d) { format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info)), format_rom_tv_type(rom_info_get_tv_type(&menu->load.rom_info)), format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak), - format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)), - menu->load.rom_info.boot_address, - (menu->load.rom_info.libultra.version / 10.0f), menu->load.rom_info.libultra.revision, - menu->load.rom_info.clock_rate + format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)) ); component_actions_bar_text_draw( @@ -268,12 +269,26 @@ static void draw (menu_t *menu, surface_t *d) { component_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, - "\n" - "R: Options" + "L|Z: Extra Info\n" + "R: Options" ); component_boxart_draw(boxart); + if (show_extra_info_message) { + component_messagebox_draw( + "EXTRA ROM INFO\n" + "\n" + " Boot address: 0x%08lX\n" + " SDK version: %.1f%c\n" + " Clock Rate: %.2fMHz\n\n\n" + "Press L|Z to return.\n", + menu->load.rom_info.boot_address, + (menu->load.rom_info.libultra.version / 10.0f), menu->load.rom_info.libultra.revision, + menu->load.rom_info.clock_rate + ); + } + component_context_menu_draw(&options_context_menu); } From e7c80d27ab8c019785771dc0545fd2c1e88e8065 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 17 Jul 2024 22:52:24 +0100 Subject: [PATCH 11/89] Rom info improvements (#122) ## Description improves upon #121 ## Motivation and Context ## How Has This Been Tested? ## Screenshots ![image](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/11439699/d91185a0-dc39-42d9-aedc-3dbd4b6c965d) ![image](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/11439699/13d18d06-d14c-4e2a-8974-1c62ea8863f4) ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/views/load_rom.c | 49 +++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 5e2f2b70e..ee60b4a73 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -237,34 +237,23 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "\n" "\n" - " Endianness: %s\n" - " Title: %.20s\n" - " Game code: %c%c%c%c\n" - " Media type: %s\n" - " Destination market: %s\n" - " Version: %hhu\n" - " Check code: 0x%016llX\n" - " Save type: %s\n" - " TV type: %s\n" - " Expansion PAK: %s\n" - " CIC: %s\n", - format_rom_endianness(menu->load.rom_info.endianness), - menu->load.rom_info.title, - menu->load.rom_info.game_code[0], menu->load.rom_info.game_code[1], menu->load.rom_info.game_code[2], menu->load.rom_info.game_code[3], - format_rom_media_type(menu->load.rom_info.category_code), - format_rom_destination_market(menu->load.rom_info.destination_code), - menu->load.rom_info.version, - menu->load.rom_info.check_code, + "Description:\n None.\n\n\n\n\n\n\n\n" + "Expansion PAK: %s\n" + "Save type: %s\n" + "TV type: %s\n" + "CIC: %s\n" + "GS/AR Cheats: Off\n" + "Patches: Off\n", + format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak), format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info)), format_rom_tv_type(rom_info_get_tv_type(&menu->load.rom_info)), - format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak), format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)) ); component_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "A: Load and run ROM\n" - "B: Exit" + "B: Back" ); component_actions_bar_text_draw( @@ -279,10 +268,24 @@ static void draw (menu_t *menu, surface_t *d) { component_messagebox_draw( "EXTRA ROM INFO\n" "\n" - " Boot address: 0x%08lX\n" - " SDK version: %.1f%c\n" - " Clock Rate: %.2fMHz\n\n\n" + "Endianness: %s\n" + "Title: %.20s\n" + "Game code: %c%c%c%c\n" + "Media type: %s\n" + "Variant: %s\n" + "Version: %hhu\n" + "Check code: 0x%016llX\n" + "Boot address: 0x%08lX\n" + "SDK version: %.1f%c\n" + "Clock Rate: %.2fMHz\n\n\n" "Press L|Z to return.\n", + format_rom_endianness(menu->load.rom_info.endianness), + menu->load.rom_info.title, + menu->load.rom_info.game_code[0], menu->load.rom_info.game_code[1], menu->load.rom_info.game_code[2], menu->load.rom_info.game_code[3], + format_rom_media_type(menu->load.rom_info.category_code), + format_rom_destination_market(menu->load.rom_info.destination_code), + menu->load.rom_info.version, + menu->load.rom_info.check_code, menu->load.rom_info.boot_address, (menu->load.rom_info.libultra.version / 10.0f), menu->load.rom_info.libultra.revision, menu->load.rom_info.clock_rate From f299bdcac913cf55e25219742bab07051341fc97 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 17 Jul 2024 23:01:22 +0100 Subject: [PATCH 12/89] Rom info improvements (controller pak) (#123) ## Description Show whether a ROM supports (or requires) a controller pak Builds on #122 ## Motivation and Context ## How Has This Been Tested? ## Screenshots ![image](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/11439699/2a83965e-c92a-408f-bb9e-af693b63f738) ![image](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/11439699/e9bb0d12-6ec0-4737-80f8-62bfa75944cb) ![image](https://github.com/Polprzewodnikowy/N64FlashcartMenu/assets/11439699/0897f55f-fb01-4bbe-b6d6-b51aaff6e13c) ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/views/load_rom.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index ee60b4a73..c408b47c7 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -66,16 +66,16 @@ static const char *format_rom_destination_market (rom_destination_type_t market_ } } -static const char *format_rom_save_type (rom_save_type_t save_type) { +static const char *format_rom_save_type (rom_save_type_t save_type, bool supports_cpak) { switch (save_type) { - case SAVE_TYPE_NONE: return "None"; - case SAVE_TYPE_EEPROM_4KBIT: return "EEPROM 4kbit"; - case SAVE_TYPE_EEPROM_16KBIT: return "EEPROM 16kbit"; - case SAVE_TYPE_SRAM_256KBIT: return "SRAM 256kbit"; - case SAVE_TYPE_SRAM_BANKED: return "SRAM 768kbit / 3 banks"; - case SAVE_TYPE_SRAM_1MBIT: return "SRAM 1Mbit"; - case SAVE_TYPE_FLASHRAM_1MBIT: return "FlashRAM 1Mbit"; - case SAVE_TYPE_FLASHRAM_PKST2: return "FlashRAM (Pokemon Stadium 2)"; + case SAVE_TYPE_NONE: return supports_cpak ? "Controller PAK" : "None"; + case SAVE_TYPE_EEPROM_4KBIT: return supports_cpak ? "EEPROM 4kbit | Controller PAK" : "EEPROM 4kbit"; + case SAVE_TYPE_EEPROM_16KBIT: return supports_cpak ? "EEPROM 16kbit | Controller PAK" : "EEPROM 16kbit"; + case SAVE_TYPE_SRAM_256KBIT: return supports_cpak ? "SRAM 256kbit | Controller PAK" : "SRAM 256kbit"; + case SAVE_TYPE_SRAM_BANKED: return supports_cpak ? "SRAM 768kbit / 3 banks | Controller PAK" : "SRAM 768kbit / 3 banks"; + case SAVE_TYPE_SRAM_1MBIT: return supports_cpak ? "SRAM 1Mbit | Controller PAK" : "SRAM 1Mbit"; + case SAVE_TYPE_FLASHRAM_1MBIT: return supports_cpak ? "FlashRAM 1Mbit | Controller PAK" : "FlashRAM 1Mbit"; + case SAVE_TYPE_FLASHRAM_PKST2: return supports_cpak ? "FlashRAM (Pokemon Stadium 2) | Controller PAK" : "FlashRAM (Pokemon Stadium 2)"; default: return "Unknown"; } } @@ -238,16 +238,16 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "\n" "Description:\n None.\n\n\n\n\n\n\n\n" - "Expansion PAK: %s\n" - "Save type: %s\n" - "TV type: %s\n" - "CIC: %s\n" - "GS/AR Cheats: Off\n" - "Patches: Off\n", + "Expansion PAK: %s\n" + "TV type: %s\n" + "CIC: %s\n" + "GS/AR Cheats: Off\n" + "Patches: Off\n" + "Save type: %s\n", format_rom_expansion_pak_info(menu->load.rom_info.features.expansion_pak), - format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info)), format_rom_tv_type(rom_info_get_tv_type(&menu->load.rom_info)), - format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)) + format_cic_type(rom_info_get_cic_type(&menu->load.rom_info)), + format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info), menu->load.rom_info.features.controller_pak) ); component_actions_bar_text_draw( From 1deb7242fc224e97dab6c17cd4b76d9eb6b36a91 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 17 Jul 2024 23:11:06 +0100 Subject: [PATCH 13/89] Fix ability to set SFX audio without console reboot (#118) ## Description Allow realtime setting changes for SFX in menu. Add note that the PAL60 setting still requires a reboot. ## Motivation and Context The sfx setting previously required a flashcart reboot. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/menu.c | 3 ++- src/menu/sound.c | 8 ++++++++ src/menu/sound.h | 2 ++ src/menu/views/settings_editor.c | 11 ++++++----- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/menu/menu.c b/src/menu/menu.c index 2ec045457..479d9cfdb 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -119,8 +119,9 @@ static void menu_init (boot_params_t *boot_params) { __boot_tvtype = TV_NTSC; } + sound_init_sfx(); if (menu->settings.sound_enabled) { - sound_init_sfx(); + sound_use_sfx(true); } display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED); diff --git a/src/menu/sound.c b/src/menu/sound.c index bf950d6c5..98d627484 100644 --- a/src/menu/sound.c +++ b/src/menu/sound.c @@ -50,6 +50,14 @@ void sound_init_sfx (void) { sfx_enabled = true; } +void sound_use_sfx(bool state) { + if (state) { + sfx_enabled = true; + } + else { + sfx_enabled = false; + } +} void sound_play_effect(sound_effect_t sfx) { if(sfx_enabled) { diff --git a/src/menu/sound.h b/src/menu/sound.h index cf3115194..8752a9b97 100644 --- a/src/menu/sound.h +++ b/src/menu/sound.h @@ -7,6 +7,7 @@ #ifndef SOUND_H__ #define SOUND_H__ +#include #define SOUND_MP3_PLAYER_CHANNEL (0) #define SOUND_SFX_CHANNEL (2) @@ -23,6 +24,7 @@ typedef enum { void sound_init_default (void); void sound_init_mp3_playback (void); void sound_init_sfx (void); +void sound_use_sfx(bool); void sound_play_effect(sound_effect_t sfx); void sound_deinit (void); void sound_poll (void); diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 4dc2539c3..22e3ac30e 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -30,6 +30,7 @@ static void set_use_saves_folder_type (menu_t *menu, void *arg) { static void set_sound_enabled_type (menu_t *menu, void *arg) { menu->settings.sound_enabled = (bool) (arg); + sound_use_sfx(menu->settings.sound_enabled); settings_save(&menu->settings); } @@ -134,19 +135,19 @@ static void draw (menu_t *menu, surface_t *d) { component_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n\n" - " Default Directory : %s\n\n" - "To change the menu settings, press 'A'.\n\n" - " PAL60 Mode : %s\n" + " Default Directory : %s\n\n" + "To change the following menu settings, press 'A':\n" + "* PAL60 Mode : %s\n" " Show Hidden Files : %s\n" " Use Saves folder : %s\n" - "* Sound Effects : %s\n" + " Sound Effects : %s\n" #ifdef BETA_SETTINGS " Background Music : %s\n" " Rumble Feedback : %s\n" #endif - "\n\n" "Note: Certain settings have the following caveats:\n\n" "* Requires a flashcart reboot.\n", + "\n", menu->settings.default_directory, format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.show_protected_entries), From 02f4614d06615e5f989298e8a3dc2abb13f99b80 Mon Sep 17 00:00:00 2001 From: Suprapote <111246491+Suprapote@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:30:44 +0200 Subject: [PATCH 14/89] Settings editor display fix (#125) ## Description Remove a newline. ## Motivation and Context ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [x] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/views/settings_editor.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 22e3ac30e..55feffead 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -147,7 +147,6 @@ static void draw (menu_t *menu, surface_t *d) { #endif "Note: Certain settings have the following caveats:\n\n" "* Requires a flashcart reboot.\n", - "\n", menu->settings.default_directory, format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.show_protected_entries), From a8830c01aba8b448c737bb550831836c6b666e9c Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 5 Aug 2024 00:27:43 +0100 Subject: [PATCH 15/89] Improve dev release tag --- .github/workflows/build.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e567d6ae1..224f2063f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,8 @@ jobs: with: name: Rolling release body: Rolling release built from latest commit on `main` branch. - tag_name: rolling-release + tag_name: 'rolling_release' + make_latest: true files: | ./output/N64FlashcartMenu.n64 ./output/menu.bin @@ -90,9 +91,10 @@ jobs: uses: softprops/action-gh-release@v2 if: github.ref == 'refs/heads/develop' with: - name: 'Rolling dev release-V${{ github.run_id }}' - body: Rolling dev prerelease built from latest commit on `develop` branch. - tag_name: prerelease-dev + name: 'Rolling pre-release' + body: Experimental pre-release built from latest commit on `develop` branch. + target_commitish: develop + tag_name: 'pre-release_V${{ github.run_number }}' prerelease: true files: | ./output/N64FlashcartMenu.n64 From 68cab6d40e9c803f897b75ff1575af1b9b88afff Mon Sep 17 00:00:00 2001 From: Suprapote <111246491+Suprapote@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:20:41 +0200 Subject: [PATCH 16/89] 64dd & Japanese covers (#126) ## Description This adds in the 64DD disk load menu to visualize the game box. I also created a European and 64DD boxart packs ## Motivation and Context Improves boxart image support. ## How Has This Been Tested? Tested on a local SC64 ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER ## Summary by CodeRabbit - **New Features** - Enhanced instructions in the README for boxart dimensions and resources. - Improved handling of box art rendering for various formats in the menu. - New functionality for displaying box art during disk loading in the menu interface, including resource management for cleanup. - **Bug Fixes** - Resolved issues with rendering dimensions for specific types of box art, improving visual consistency. - **Documentation** - Updated README to clarify the boxart requirements and provide multiple download links for different regions. --------- Co-authored-by: Robin Jones --- README.md | 14 ++++++++++++-- src/menu/components/boxart.c | 25 +++++++++++++++---------- src/menu/components/constants.h | 20 ++++++++++++++++++++ src/menu/views/load_disk.c | 12 ++++++++++++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1f55ace3c..703804cc9 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,21 @@ An open source menu for N64 flashcarts. These features are subject to change: ### ROM Boxart -To use boxart, you need to place png files of size 158x112 in the folder `/menu/boxart` on the SD card. +To use boxart, place PNG files in the `/menu/boxart` folder on the SD card with the following dimensions: +* Standard covers: 158x112 +* 64DD covers: 129x112 +* Japanese covers: 112x158 + Each file must be named according to the 2 letter ROM ID, or 3 letter ROM ID including media type. i.e. for GoldenEye 2 letters, this would be `GE.png`. i.e. for GoldenEye 3 letters, this would be `NGE.png`. -A known set of PNG files using 2 letter ID's can be downloaded [here](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w). +You can download these boxart packs: + +[American Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w) + +[European Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M) + +[64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM) ### Menu Settings diff --git a/src/menu/components/boxart.c b/src/menu/components/boxart.c index 54121b6e1..f50294eee 100644 --- a/src/menu/components/boxart.c +++ b/src/menu/components/boxart.c @@ -31,7 +31,7 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam sprintf(file_name, "%.3s.png", game_code); path_push(path, file_name); - if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) { + if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { path_free(path); return b; } @@ -40,7 +40,7 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam // TODO: This is bad, we should only check for 3 letter codes sprintf(file_name, "%.2s.png", game_code + 1); path_push(path, file_name); - if (png_decoder_start(path_get(path), BOXART_WIDTH, BOXART_HEIGHT, png_decoder_callback, b) == PNG_OK) { + if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { path_free(path); return b; } @@ -65,15 +65,20 @@ void component_boxart_free (component_boxart_t *b) { } void component_boxart_draw (component_boxart_t *b) { - if (b && b->image && b->image->width == BOXART_WIDTH && b->image->height == BOXART_HEIGHT) { + int box_x = BOXART_X; + int box_y = BOXART_Y; + + if (b && b->image && b->image->width <= BOXART_WIDTH_MAX && b->image->height <= BOXART_HEIGHT_MAX) { rdpq_mode_push(); rdpq_set_mode_copy(false); - rdpq_tex_blit( - b->image, - BOXART_X, - BOXART_Y, - NULL - ); + if (b->image->height == BOXART_HEIGHT_MAX) { + box_x = BOXART_X_JP; + box_y = BOXART_Y_JP; + } else if (b->image->width == BOXART_WIDTH_DD && b->image->height == BOXART_HEIGHT_DD) { + box_x = BOXART_X_DD; + box_y = BOXART_Y_DD; + } + rdpq_tex_blit(b->image, box_x, box_y, NULL); rdpq_mode_pop(); } else { component_box_draw( @@ -84,4 +89,4 @@ void component_boxart_draw (component_boxart_t *b) { BOXART_LOADING_COLOR ); } -} +} \ No newline at end of file diff --git a/src/menu/components/constants.h b/src/menu/components/constants.h index e4d3eb514..c468a3271 100644 --- a/src/menu/components/constants.h +++ b/src/menu/components/constants.h @@ -73,10 +73,30 @@ #define BOXART_WIDTH (158) /** @brief The boxart picture height. */ #define BOXART_HEIGHT (112) + +/** @brief The boxart picture width (64DD). */ +#define BOXART_WIDTH_DD (129) +/** @brief The boxart picture height. */ +#define BOXART_HEIGHT_DD (112) + +/** @brief The boxart picture maximum width. */ +#define BOXART_WIDTH_MAX (158) +/** @brief The boxart picture maximum height. */ +#define BOXART_HEIGHT_MAX (158) + /** @brief The box art position on the X axis. */ #define BOXART_X (VISIBLE_AREA_X1 - BOXART_WIDTH - 24) /** @brief The box art position on the Y axis. */ #define BOXART_Y (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT - 24) +/** @brief The box art position on the X axis for japanese caratules.*/ +#define BOXART_X_JP (VISIBLE_AREA_X1 - BOXART_WIDTH_MAX + 21) +/** @brief The box art position on the Y axis for japanese caratules. */ +#define BOXART_Y_JP (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT_MAX - 24) + +/** @brief The box art position on the X axis for 64DD caratules.*/ +#define BOXART_X_DD (VISIBLE_AREA_X1 - BOXART_WIDTH_DD - 23) +/** @brief The box art position on the Y axis for 64DD caratules. */ +#define BOXART_Y_DD (LAYOUT_ACTIONS_SEPARATOR_Y - BOXART_HEIGHT_DD - 24) /** @brief The scroll bar width. */ #define LIST_SCROLLBAR_WIDTH (12) diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 983d052a6..0165bdc9f 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -7,6 +7,7 @@ static bool load_pending; static bool load_rom; +static component_boxart_t *boxart; static char *convert_error_message (disk_err_t err) { @@ -92,6 +93,8 @@ static void draw (menu_t *menu, surface_t *d) { "R: Load with ROM" ); } + + component_boxart_draw(boxart); } rdpq_detach_show(); @@ -148,6 +151,9 @@ static void load (menu_t *menu) { } } +static void deinit (void) { + component_boxart_free(boxart); +} void view_load_disk_init (menu_t *menu) { if (menu->load.disk_path) { @@ -163,6 +169,8 @@ void view_load_disk_init (menu_t *menu) { if (err != DISK_OK) { menu_show_error(menu, convert_error_message(err)); } + + boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id); } void view_load_disk_display (menu_t *menu, surface_t *display) { @@ -174,4 +182,8 @@ void view_load_disk_display (menu_t *menu, surface_t *display) { load_pending = false; load(menu); } + + if (menu->next_mode != MENU_MODE_LOAD_DISK) { + deinit(); + } } From 3fb4dc439f678e85d3f4dffc09035755f9e89a63 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 6 Aug 2024 13:41:19 +0100 Subject: [PATCH 17/89] Remove individual tag It needs more thought. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 224f2063f..b7da2151a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,7 +94,7 @@ jobs: name: 'Rolling pre-release' body: Experimental pre-release built from latest commit on `develop` branch. target_commitish: develop - tag_name: 'pre-release_V${{ github.run_number }}' + tag_name: 'rolling_pre-release' prerelease: true files: | ./output/N64FlashcartMenu.n64 From dfcc2d0ec4ee39f0b8bbe1e66ea1096b9584590d Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 6 Aug 2024 15:48:00 +0100 Subject: [PATCH 18/89] Update libdragon submodule (#133) ## Description Update libdragon submodule. ## Motivation and Context Keeps it up-to-date. ## How Has This Been Tested? Tested locally on SC64, no regressions witnessed. ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER ## Summary by CodeRabbit - **Chores** - Updated the underlying subproject version for potential enhancements and bug fixes. --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index e2e2dc063..9bae49994 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit e2e2dc063add29e9d11f55ae3fbeccc189fb2ad6 +Subproject commit 9bae49994bf1c796f9939ea1aa7c133813b9053f From a35c6bdec7bf340d6a5638d07eb83c30c4d71c58 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 18 Aug 2024 19:10:33 +0100 Subject: [PATCH 19/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 9bae49994..d25c4a6bb 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 9bae49994bf1c796f9939ea1aa7c133813b9053f +Subproject commit d25c4a6bb22abb93a29049e9d1b4438c93dcb9a3 From 08a42ddb329a66627f2a61a49658d21355d13a6c Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 18 Aug 2024 20:18:30 +0100 Subject: [PATCH 20/89] Update miniz submodule (#136) ## Description Updates the miniz git submodule ## Motivation and Context keep it up-to-date ## How Has This Been Tested? Assuming it is used for PNG conversion. Tested changing backgrounds locally on an SC64, which still works. ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER ## Summary by CodeRabbit - **Chores** - Updated the reference to a newer version of a subproject, potentially incorporating various enhancements and bug fixes. No changes were made to the existing functionality. --- src/libs/miniz | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/miniz b/src/libs/miniz index 16413c213..1ff82be7d 160000 --- a/src/libs/miniz +++ b/src/libs/miniz @@ -1 +1 @@ -Subproject commit 16413c213de38e703d883006193734e8b1178d5d +Subproject commit 1ff82be7d67f5c2f8b5497f538eea247861e0717 From 4e4c109bd7b66881fcf602a7dc04155f2726edfc Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 18 Aug 2024 22:13:22 +0100 Subject: [PATCH 21/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index d25c4a6bb..70ce32232 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit d25c4a6bb22abb93a29049e9d1b4438c93dcb9a3 +Subproject commit 70ce32232a9a89a92be032d19b01ba9d8dd6cf2f From f2fa7d0dbad55f480c6dbb3701e92e26e1fef972 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 30 Aug 2024 20:44:20 +0100 Subject: [PATCH 22/89] Add flashcart features for CIC and Region (#138) ## Description ## Motivation and Context Some flashcarts may be old (which would include old 64drive's), and not use the Ultra CIC, or old one that does not handle auto switching of regions, we should have them as a feature switch. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/flashcart/64drive/64drive.c | 2 ++ src/flashcart/flashcart.h | 2 ++ src/flashcart/sc64/sc64.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/flashcart/64drive/64drive.c b/src/flashcart/64drive/64drive.c index 0fd47bef0..3997bf800 100644 --- a/src/flashcart/64drive/64drive.c +++ b/src/flashcart/64drive/64drive.c @@ -75,6 +75,8 @@ static bool d64_has_feature (flashcart_features_t feature) { case FLASHCART_FEATURE_64DD: return false; case FLASHCART_FEATURE_RTC: return true; case FLASHCART_FEATURE_USB: return true; + case FLASHCART_FEATURE_AUTO_CIC: return true; + case FLASHCART_FEATURE_AUTO_REGION: return true; default: return false; } } diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index b4996f360..ade09eaa8 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -29,6 +29,8 @@ typedef enum { FLASHCART_FEATURE_64DD, FLASHCART_FEATURE_RTC, FLASHCART_FEATURE_USB, + FLASHCART_FEATURE_AUTO_CIC, + FLASHCART_FEATURE_AUTO_REGION, } flashcart_features_t; /** @brief Flashcart save type enumeration */ diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index 7119a2710..bcab63f07 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -254,6 +254,8 @@ static bool sc64_has_feature (flashcart_features_t feature) { case FLASHCART_FEATURE_64DD: return true; case FLASHCART_FEATURE_RTC: return true; case FLASHCART_FEATURE_USB: return true; + case FLASHCART_FEATURE_AUTO_CIC: return true; + case FLASHCART_FEATURE_AUTO_REGION: return true; default: return false; } } From 83ed01a0432c9785e822093655a576fcb519047b Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 30 Aug 2024 21:02:59 +0100 Subject: [PATCH 23/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 70ce32232..e1eb9283e 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 70ce32232a9a89a92be032d19b01ba9d8dd6cf2f +Subproject commit e1eb9283e6feeb37a66797fa538ae7883d73227c From 2116f6e26b4f1a29ea52d68c097f9c159c2e49fe Mon Sep 17 00:00:00 2001 From: Fazana <52551480+FazanaJ@users.noreply.github.com> Date: Mon, 16 Sep 2024 21:48:28 +0100 Subject: [PATCH 24/89] Add support for inputs from controller ports 2-4 (#141) Title sums it up ## Description Adds some extra logic in the controller polling to read from ports 2-4, letting those controllers input on the menu. ## Motivation and Context Port 1 no longer feels alone in the dark, cold world of menu selection. ## How Has This Been Tested? Same method I used for libdragon's error screen which had the same issue. Tested on a system with four controllers inserted. ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: FazanaJ --------- Co-authored-by: Robin Jones --- docs/00_getting_started_sd.md | 2 +- src/menu/actions.c | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/00_getting_started_sd.md b/docs/00_getting_started_sd.md index 4ee6e7636..af937dfe9 100644 --- a/docs/00_getting_started_sd.md +++ b/docs/00_getting_started_sd.md @@ -36,7 +36,7 @@ SD:\ │ │ ├── NDDJ2.n64 │ │ └── NDXJ0.n64 │ │ -│ └── emulators +│ └── emulators\ │ ├── neon64bu.rom │ ├── sodium64.z64 │ ├── gb.v64 diff --git a/src/menu/actions.c b/src/menu/actions.c index 14c87fb63..9c44c23e8 100644 --- a/src/menu/actions.c +++ b/src/menu/actions.c @@ -25,8 +25,17 @@ static void actions_clear (menu_t *menu) { } static void actions_update_direction (menu_t *menu) { - joypad_8way_t held_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_DPAD | JOYPAD_2D_STICK); - joypad_8way_t fast_dir = joypad_get_direction(JOYPAD_PORT_1, JOYPAD_2D_C); + joypad_8way_t held_dir; + joypad_8way_t fast_dir; + + JOYPAD_PORT_FOREACH (i) { + held_dir = joypad_get_direction(i, JOYPAD_2D_DPAD | JOYPAD_2D_STICK); + fast_dir = joypad_get_direction(i, JOYPAD_2D_C); + if (held_dir != JOYPAD_8WAY_NONE || fast_dir != JOYPAD_8WAY_NONE) { + break; + } + } + if (fast_dir != JOYPAD_8WAY_NONE) { held_dir = fast_dir; @@ -82,7 +91,14 @@ static void actions_update_direction (menu_t *menu) { } static void actions_update_buttons (menu_t *menu) { - joypad_buttons_t pressed = joypad_get_buttons_pressed(JOYPAD_PORT_1); + joypad_buttons_t pressed; + + JOYPAD_PORT_FOREACH (i) { + pressed = joypad_get_buttons_pressed(i); + if (pressed.raw) { + break; + } + } if (pressed.a) { menu->actions.enter = true; From 10a8fcab9e2231b9fa2fb9e7bbd48bd4a96fe475 Mon Sep 17 00:00:00 2001 From: Mateusz Faderewski Date: Tue, 17 Sep 2024 00:11:51 +0200 Subject: [PATCH 25/89] libdragon update + reorganized menu init + bug fixes --- Makefile | 1 - libdragon | 2 +- src/menu/actions.c | 7 ++- src/menu/actions.h | 1 + src/menu/components/background.c | 13 ++--- src/menu/menu.c | 96 ++++++++++---------------------- 6 files changed, 42 insertions(+), 78 deletions(-) diff --git a/Makefile b/Makefile index ae6e69618..70255c606 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,6 @@ $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fc $(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 80-1FF -r 2026-2026 --ellipsis 2026,1 $(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1 - $(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null)) $(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf diff --git a/libdragon b/libdragon index e1eb9283e..9dd994151 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit e1eb9283e6feeb37a66797fa538ae7883d73227c +Subproject commit 9dd994151ae3f3709f1f80224e6b654aac8be6b4 diff --git a/src/menu/actions.c b/src/menu/actions.c index 9c44c23e8..003171688 100644 --- a/src/menu/actions.c +++ b/src/menu/actions.c @@ -36,7 +36,6 @@ static void actions_update_direction (menu_t *menu) { } } - if (fast_dir != JOYPAD_8WAY_NONE) { held_dir = fast_dir; menu->actions.go_fast = true; @@ -114,6 +113,12 @@ static void actions_update_buttons (menu_t *menu) { } +void actions_init (void) { + JOYPAD_PORT_FOREACH (port) { + joypad_set_rumble_active(port, false); + } +} + void actions_update (menu_t *menu) { joypad_poll(); diff --git a/src/menu/actions.h b/src/menu/actions.h index f890b55c0..e02e35afd 100644 --- a/src/menu/actions.h +++ b/src/menu/actions.h @@ -11,6 +11,7 @@ #include "menu_state.h" +void actions_init (void); void actions_update (menu_t *menu); diff --git a/src/menu/components/background.c b/src/menu/components/background.c index 451191079..525fb0c8f 100644 --- a/src/menu/components/background.c +++ b/src/menu/components/background.c @@ -98,9 +98,6 @@ static void prepare_background (component_background_t *c) { return; } - uint16_t image_center_x = (c->image->width / 2); - uint16_t image_center_y = (c->image->height / 2); - // Darken the image rdpq_attach(c->image, NULL); rdpq_mode_push(); @@ -108,15 +105,13 @@ static void prepare_background (component_background_t *c) { rdpq_set_prim_color(BACKGROUND_OVERLAY_COLOR); rdpq_mode_combiner(RDPQ_COMBINER_FLAT); rdpq_mode_blender(RDPQ_BLENDER_MULTIPLY); - rdpq_fill_rectangle( - 0 - (DISPLAY_CENTER_X - image_center_x), - 0 - (DISPLAY_CENTER_Y - image_center_y), - DISPLAY_WIDTH - (DISPLAY_CENTER_X - image_center_x), - DISPLAY_HEIGHT - (DISPLAY_CENTER_Y - image_center_y) - ); + rdpq_fill_rectangle(0, 0, c->image->width, c->image->height); rdpq_mode_pop(); rdpq_detach(); + uint16_t image_center_x = (c->image->width / 2); + uint16_t image_center_y = (c->image->height / 2); + // Prepare display list rspq_block_begin(); rdpq_mode_push(); diff --git a/src/menu/menu.c b/src/menu/menu.c index 479d9cfdb..5e2b2168a 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -27,37 +27,27 @@ #define MENU_CACHE_DIRECTORY "cache" #define BACKGROUND_CACHE_FILE "background.data" -#define FRAMERATE_DIVIDER (2) -#define LAG_REPORT (false) +#define INTERLACED (true) +#define FPS_LIMIT (30.0f) static menu_t *menu; -static tv_type_t tv_type; -static volatile int frame_counter = 0; -extern tv_type_t __boot_tvtype; +static void menu_init (boot_params_t *boot_params) { + menu = calloc(1, sizeof(menu_t)); + assert(menu != NULL); -static void frame_counter_handler (void) { - frame_counter += 1; -} + menu->boot_params = boot_params; + + menu->mode = MENU_MODE_NONE; + menu->next_mode = MENU_MODE_STARTUP; -static void frame_counter_reset (void) { -#if LAG_REPORT - static int accumulated = 0; - if (frame_counter > FRAMERATE_DIVIDER) { - accumulated += frame_counter - FRAMERATE_DIVIDER; - debugf( - "LAG: %d additional frame(s) displayed since last draw (accumulated: %d)\n", - frame_counter - FRAMERATE_DIVIDER, - accumulated - ); + menu->flashcart_err = flashcart_init(&menu->storage_prefix); + if (menu->flashcart_err != FLASHCART_OK) { + menu->next_mode = MENU_MODE_FAULT; } -#endif - frame_counter = 0; -} -static void menu_init (boot_params_t *boot_params) { joypad_init(); timer_init(); rtc_init(); @@ -65,22 +55,11 @@ static void menu_init (boot_params_t *boot_params) { rdpq_init(); dfs_init(DFS_DEFAULT_LOCATION); + actions_init(); sound_init_default(); + sound_init_sfx(); - JOYPAD_PORT_FOREACH (port) { - joypad_set_rumble_active(port, false); - } - - menu = calloc(1, sizeof(menu_t)); - assert(menu != NULL); - - menu->mode = MENU_MODE_NONE; - menu->next_mode = MENU_MODE_STARTUP; - - menu->flashcart_err = flashcart_init(&menu->storage_prefix); - if (menu->flashcart_err != FLASHCART_OK) { - menu->next_mode = MENU_MODE_FAULT; - } + hdmi_clear_game_id(); path_t *path = path_init(menu->storage_prefix, MENU_DIRECTORY); @@ -91,6 +70,15 @@ static void menu_init (boot_params_t *boot_params) { settings_load(&menu->settings); path_pop(path); + resolution_t resolution = { + .width = 640, + .height = 480, + .interlaced = INTERLACED ? INTERLACE_HALF : INTERLACE_OFF, + .pal60 = menu->settings.pal60_enabled, + }; + display_init(resolution, DEPTH_16_BPP, 2, GAMMA_NONE, INTERLACED ? FILTERS_DISABLED : FILTERS_RESAMPLE); + display_set_fps_limit(FPS_LIMIT); + path_push(path, MENU_CUSTOM_FONT_FILE); fonts_init(path_get(path)); path_pop(path); @@ -103,40 +91,20 @@ static void menu_init (boot_params_t *boot_params) { path_free(path); - menu->boot_params = boot_params; + sound_use_sfx(menu->settings.sound_enabled); menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory); if (!directory_exists(path_get(menu->browser.directory))) { path_free(menu->browser.directory); menu->browser.directory = path_init(menu->storage_prefix, "/"); } - - hdmi_clear_game_id(); - - tv_type = get_tv_type(); - if ((tv_type == TV_PAL) && menu->settings.pal60_enabled) { - // HACK: Set TV type to NTSC, so PAL console would output 60 Hz signal instead. - __boot_tvtype = TV_NTSC; - } - - sound_init_sfx(); - if (menu->settings.sound_enabled) { - sound_use_sfx(true); - } - - display_init(RESOLUTION_640x480, DEPTH_16_BPP, 2, GAMMA_NONE, FILTERS_DISABLED); - - register_VI_handler(frame_counter_handler); } static void menu_deinit (menu_t *menu) { - unregister_VI_handler(frame_counter_handler); - - // NOTE: Restore previous TV type so boot procedure wouldn't passthrough wrong value. - __boot_tvtype = tv_type; - hdmi_send_game_id(menu->boot_params); + component_background_free(); + path_free(menu->load.disk_path); path_free(menu->load.rom_path); for (int i = 0; i < menu->browser.entries; i++) { @@ -146,9 +114,7 @@ static void menu_deinit (menu_t *menu) { path_free(menu->browser.directory); free(menu); - component_background_free(); - - flashcart_deinit(); + display_close(); sound_deinit(); @@ -158,7 +124,7 @@ static void menu_deinit (menu_t *menu) { timer_close(); joypad_close(); - display_close(); + flashcart_deinit(); } typedef const struct { @@ -200,11 +166,9 @@ void menu_run (boot_params_t *boot_params) { menu_init(boot_params); while (true) { - surface_t *display = (frame_counter >= FRAMERATE_DIVIDER) ? display_try_get() : NULL; + surface_t *display = display_try_get(); if (display != NULL) { - frame_counter_reset(); - actions_update(menu); view_t *view = menu_get_view(menu->mode); From 269a8ae94d45e766a0bbe6cf97dddd77964754dd Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sat, 28 Sep 2024 20:47:53 +0100 Subject: [PATCH 26/89] Optimize boxart image load (#130) ## Description Improves boxart image loading by using directory file paths. It also adds matching by full game ID's (for compatibility), but notes in the readme that it is slow. ## Motivation and Context Loading boxart was slow in certain circumstances. ## How Has This Been Tested? Locally on an SC64. ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER ## Summary by CodeRabbit - **New Features** - Introduced a new image type enumeration for boxart components, enhancing image management. - Updated boxart initialization to allow for specific image views, improving flexibility in image retrieval. - Enhanced instructions for setting up boxart images with clearer directory structures and simplified naming conventions. - **Bug Fixes** - Improved the logic for loading boxart images, enabling better fallback options in case of missing files. --- README.md | 54 +++++++++++++++------ src/menu/components.h | 37 +++++++++++++- src/menu/components/boxart.c | 94 ++++++++++++++++++++++++++++++------ src/menu/views/load_disk.c | 2 +- src/menu/views/load_rom.c | 2 +- 5 files changed, 154 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 703804cc9..26af0067f 100644 --- a/README.md +++ b/README.md @@ -49,22 +49,44 @@ An open source menu for N64 flashcarts. ## Experimental features These features are subject to change: -### ROM Boxart -To use boxart, place PNG files in the `/menu/boxart` folder on the SD card with the following dimensions: -* Standard covers: 158x112 -* 64DD covers: 129x112 -* Japanese covers: 112x158 - -Each file must be named according to the 2 letter ROM ID, or 3 letter ROM ID including media type. -i.e. for GoldenEye 2 letters, this would be `GE.png`. -i.e. for GoldenEye 3 letters, this would be `NGE.png`. -You can download these boxart packs: - -[American Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w) - -[European Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M) - -[64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM) + +### GamePak sprites +To use N64 `GamePak` sprites, place `PNG` files within the `sd:/menu/boxart/` folder. + + +#### Supported sprites +These must be `PNG` files that use the following dimensions: +* Standard N64 GamePak boxart sprites: 158x112 +* Japanese N64 GamePak boxart sprites: 112x158 +* 64DD boxart sprites: 129x112 + +They will be loaded by directories using each character of the full 4 character Game Code (as identified in the menus ROM information). +i.e. for GoldenEye NTSC USA (NGEE), this would be `sd:/menu/boxart/N/G/E/E/boxart_front.png`. +i.e. for GoldenEye PAL (NGEP), this would be `sd:/menu/boxart/N/G/E/P/boxart_front.png`. + +To improve compatibility between regions (as a fallback), you may exclude the region ID (last matched directory) for GamePaks to match with 3 letter IDs instead: +i.e. for GoldenEye, this would be `sd:/menu/boxart/N/G/E/boxart_front.png`. + +**Note1:** Excluding the region ID may show the wrong boxart. +**Note2:** For future support, boxart sprites should also include: `boxart_back.png`, `boxart_top.png`, `boxart_bottom.png`, `boxart_left.png`, `boxart_right.png`. + + +#### Compatibilty mode +If you cannot yet satisfy the correct boxart layout, The menu still has **deprecated** support for filenames containing the Game ID. + +**Note:** This will add a noticeable delay for displaying parts of the menu. + +Each file must be named according to the 2,3 or 4 letter GamePak ID (matched in this order). +i.e. +* for GoldenEye 4 letters, this would be `sd:/menu/boxart/NGEE.png` and/or `sd:/menu/boxart/NGEP.png`. +* for GoldenEye 3 letters, this would be `sd:/menu/boxart/NGE.png`. +* for GoldenEye 2 letters, this would be `sd:/menu/boxart/GE.png`. + + +As a starting point, here are some links to boxart packs: +* [American GamePak Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w) +* [European GamePak Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M) +* [64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM) ### Menu Settings diff --git a/src/menu/components.h b/src/menu/components.h index 4925ee5b2..67fde2d31 100644 --- a/src/menu/components.h +++ b/src/menu/components.h @@ -11,6 +11,41 @@ #include #include "menu_state.h" +/** @brief File image Enumeration. */ +typedef enum { + + /** @brief Boxart image from the front */ + IMAGE_BOXART_FRONT, + + /** @brief Boxart image from the back */ + IMAGE_BOXART_BACK, + + /** @brief Boxart image from the top */ + IMAGE_BOXART_TOP, + + /** @brief Boxart image from the bottom */ + IMAGE_BOXART_BOTTOM, + + /** @brief Boxart image from the left side */ + IMAGE_BOXART_LEFT, + + /** @brief Boxart image from the right side */ + IMAGE_BOXART_RIGHT, + + /** @brief GamePak image from the front */ + IMAGE_GAMEPAK_FRONT, + + /** @brief GamePak image from the back */ + IMAGE_GAMEPAK_BACK, + + /** @brief File image thumbnail */ + IMAGE_THUMBNAIL, + + /** @brief List end marker */ + IMAGE_TYPE_END + +} file_image_type_t; + /** * @addtogroup @@ -64,7 +99,7 @@ typedef struct { surface_t *image; } component_boxart_t; -component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code); +component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view); void component_boxart_free (component_boxart_t *b); void component_boxart_draw (component_boxart_t *b); diff --git a/src/menu/components/boxart.c b/src/menu/components/boxart.c index f50294eee..2535068ca 100644 --- a/src/menu/components/boxart.c +++ b/src/menu/components/boxart.c @@ -17,9 +17,9 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void } -component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code) { +component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view) { component_boxart_t *b; - char file_name[8]; + char boxart_id_path[8]; if ((b = calloc(1, sizeof(component_boxart_t))) == NULL) { return NULL; @@ -29,21 +29,83 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam path_t *path = path_init(storage_prefix, BOXART_DIRECTORY); - sprintf(file_name, "%.3s.png", game_code); - path_push(path, file_name); - if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { - path_free(path); - return b; + sprintf(boxart_id_path, "%c/%c/%c/%c", game_code[0], game_code[1], game_code[2], game_code[3]); + path_push(path, boxart_id_path); + + if (!directory_exists(path_get(path))) { // Allow boxart to not specify the region code. + path_pop(path); + } + + if (directory_exists(path_get(path))) { + switch (current_image_view) { + case IMAGE_GAMEPAK_FRONT: + path_push(path, "gamepak_front.png"); + case IMAGE_GAMEPAK_BACK: + path_push(path, "gamepak_back.png"); + case IMAGE_BOXART_BACK: + path_push(path, "boxart_back.png"); + case IMAGE_BOXART_LEFT: + path_push(path, "boxart_left.png"); + case IMAGE_BOXART_RIGHT: + path_push(path, "boxart_right.png"); + case IMAGE_BOXART_BOTTOM: + path_push(path, "boxart_bottom.png"); + case IMAGE_BOXART_TOP: + path_push(path, "boxart_top.png"); + default: + path_push(path, "boxart_front.png"); + } + + if (file_exists(path_get(path))) { + if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { + path_free(path); + return b; + } + } } - path_pop(path); - - // TODO: This is bad, we should only check for 3 letter codes - sprintf(file_name, "%.2s.png", game_code + 1); - path_push(path, file_name); - if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { - path_free(path); - return b; + else { // compatibility mode + + char file_name[8]; + + // reset the directory path used for boxart. + path = path_init(storage_prefix, BOXART_DIRECTORY); + + sprintf(file_name, "%c%c%c%c.png", game_code[0], game_code[1], game_code[2], game_code[3]); + path_push(path, file_name); + + if (file_exists(path_get(path))) { + if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { + path_free(path); + return b; + } + } + + path_pop(path); + sprintf(file_name, "%c%c%c.png", game_code[0], game_code[1], game_code[2]); + path_push(path, file_name); + + if (file_exists(path_get(path))) { + if (file_exists(path_get(path))) { + if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { + path_free(path); + return b; + } + } + } + else { + path_pop(path); + + sprintf(file_name, "%c%c.png", game_code[1], game_code[2]); + path_push(path, file_name); + if (file_exists(path_get(path))) { + if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { + path_free(path); + return b; + } + } + } } + // TODO: return default image. path_free(path); free(b); @@ -89,4 +151,4 @@ void component_boxart_draw (component_boxart_t *b) { BOXART_LOADING_COLOR ); } -} \ No newline at end of file +} diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 0165bdc9f..39957fcf2 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -170,7 +170,7 @@ void view_load_disk_init (menu_t *menu) { menu_show_error(menu, convert_error_message(err)); } - boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id); + boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id, IMAGE_BOXART_FRONT); } void view_load_disk_display (menu_t *menu, surface_t *display) { diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index c408b47c7..36933b92c 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -355,7 +355,7 @@ void view_load_rom_init (menu_t *menu) { return; } - boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code); + boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code, IMAGE_BOXART_FRONT); component_context_menu_init(&options_context_menu); } From 55e7663baea4b43293b258a93c07c74950129baf Mon Sep 17 00:00:00 2001 From: Suprapote <111246491+Suprapote@users.noreply.github.com> Date: Sat, 28 Sep 2024 21:57:45 +0200 Subject: [PATCH 27/89] New boxart links (#135) ## Description Improve the gamepak boxart links with better sprite matching. ## Motivation and Context Games were not always matched correctly. Works towards a boxart pack for every region of every game. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [x] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --------- Co-authored-by: Robin Jones --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26af0067f..de3d030cb 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,10 @@ i.e. As a starting point, here are some links to boxart packs: -* [American GamePak Boxart](https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w) -* [European GamePak Boxart](https://mega.nz/file/O7AjDbRJ#VnVU10dq8HQvBUQptppI6PAcQMb8-Zembqav8WtAQ_M) -* [64DD Boxart](https://mega.nz/file/O3JzwD7B#BYl1aV-pbrJ-MxWUbM_K0yGVIRbmSoxJJZqQInRzZyM) +* [Japan Boxart](https://mega.nz/file/KyJR0B6B#ERabLautAVPaqJTIdBSv4ghbudNhK7hnEr2ZS1Q6ub0) +* [American Boxart](https://mega.nz/file/rugAFYSQ#JHfgCU2amzNVpC4S6enP3vg--wtAAwsziKa7cej6QCc) +* [European Boxart](https://mega.nz/file/OmIV3aAK#kOWdutK1_41ffN64R6thbU7HEPR_M9qO0YM2mNG6RbQ) +* [64DD Boxart](https://mega.nz/file/ay5wQIxJ#k3PF-VMLrZJxJTr-BOaOKa2TBIK7c2t4zwbdshsQl40) ### Menu Settings From b8fa9e287d515c1159f8385d0fdf29fda3b4a572 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 30 Sep 2024 22:16:40 +0100 Subject: [PATCH 28/89] Update docs for newer firmware (#143) ## Description Update the "supported" SC64 firmware version. ## Motivation and Context Although the latest firmware is not "required", it is best to keep it aligned. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER ## Summary by CodeRabbit - **New Features** - Updated the supported firmware version for "SummerCart64" to 2.20.0+ in error messages. - **Documentation** - Revised developer documentation to reflect the upgrade of the SC64 deployer to v2.20.0, including updated links and compatibility requirements. - Clarified instructions for using the dev container and added a workaround for USB device communication. - Expanded guidance on generating and serving documentation locally. --- .devcontainer/Dockerfile | 2 +- docs/99_developer_guide.md | 6 +++--- src/menu/views/fault.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 116f0cb95..e5a3ad30d 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ FROM debian:bookworm-slim -ARG SC64_DEPLOYER_VERSION=v2.18.0 +ARG SC64_DEPLOYER_VERSION=v2.20.0 RUN apt-get update && \ apt-get upgrade -y && \ apt-get install build-essential doxygen git python3 wget -y && \ diff --git a/docs/99_developer_guide.md b/docs/99_developer_guide.md index ff6ad67c8..ab1481401 100644 --- a/docs/99_developer_guide.md +++ b/docs/99_developer_guide.md @@ -8,11 +8,11 @@ You can use a dev container in VSCode to ease development. ### To deploy: #### SC64 -* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.18.0/sc64-deployer-windows-v2.18.0.zip) +* Download the deployer [here](https://github.com/Polprzewodnikowy/SummerCart64/releases/download/v2.20.0/sc64-deployer-windows-v2.20.0.zip) * Extract and place `sc64deployer.exe` in the `tools/sc64` directory. -Make sure that your firmware is compatible (currently v2.18.0+) -See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.18.0/docs/00_quick_startup_guide.md#firmware-backupupdate) +Make sure that your firmware is compatible (currently v2.20.0+) +See: [here](https://github.com/Polprzewodnikowy/SummerCart64/blob/v2.20.0/docs/00_quick_startup_guide.md#firmware-backupupdate) ##### From the devcontainer It is not currently possible to directly communicate with USB devices. diff --git a/src/menu/views/fault.c b/src/menu/views/fault.c index 7128f666e..6ec767a4e 100644 --- a/src/menu/views/fault.c +++ b/src/menu/views/fault.c @@ -7,7 +7,7 @@ static void draw (menu_t *menu, surface_t *d) { rdpq_clear(RGBA32(0x7F, 0x00, 0x00, 0xFF)); const char *firmware_message = ( - "Supported firmware versions:\n" + "Minimum supported firmware versions:\n" "64drive: 2.05+\n" "EverDrive-64: ???+\n" "SummerCart64: 2.17.0+" From 4d311ffcd89946e1ff41a34b9ff54dcf18d6b955 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 2 Oct 2024 21:11:58 +0100 Subject: [PATCH 29/89] Update 00_getting_started_sd.md --- docs/00_getting_started_sd.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/00_getting_started_sd.md b/docs/00_getting_started_sd.md index af937dfe9..f2f75de8e 100644 --- a/docs/00_getting_started_sd.md +++ b/docs/00_getting_started_sd.md @@ -1,11 +1,19 @@ ## First time setup of SD card -Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart (*FAT32 and EXFAT are fully supported on the SC64*). +Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart +#### SC64 +- FAT32 and EXFAT are fully supported. +- An SD formatted with 128 kiB cluster size is recommended. - Download the latest `sc64menu.n64` (assuming you are using an *sc64*) file from the [releases](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases/) page, then put it in the root directory of your SD card. - Create a folder in the root of your SD card called `menu`. - Place your ROMs on the SD Card, in any folder (**except for `menu`**). +#### Other supported flashcarts +- FAT32 recommended. +- An SD formatted with default cluster size is recommended. + + ### Emulator support Emulators should be added to the `/menu/emulators` directory on the SD card. From f01fb77db347694e3e7ecdb7f31ed99d1178ff9d Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 23 Oct 2024 12:58:21 +0100 Subject: [PATCH 30/89] Minor comment improvements --- docs/99_developer_guide.md | 2 ++ src/menu/actions.h | 4 +++- src/menu/sound.h | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/99_developer_guide.md b/docs/99_developer_guide.md index ab1481401..1734b8d9a 100644 --- a/docs/99_developer_guide.md +++ b/docs/99_developer_guide.md @@ -57,6 +57,8 @@ Generated documentation is located in the `output/docs` folder and auto-publishe Once merged, they can be viewed [here](https://polprzewodnikowy.github.io/N64FlashcartMenu/) ### Test generated docs in the dev-container +Testing the documentation locally allows you to preview changes and ensure everything renders correctly before submitting your changes. + Install Prerequisites: ```bash apt-get install ruby-full build-essential zlib1g-dev diff --git a/src/menu/actions.h b/src/menu/actions.h index e02e35afd..f10a83cec 100644 --- a/src/menu/actions.h +++ b/src/menu/actions.h @@ -10,7 +10,9 @@ #include "menu_state.h" - +/** + * @brief Initialize the actions module + */ void actions_init (void); void actions_update (menu_t *menu); diff --git a/src/menu/sound.h b/src/menu/sound.h index 8752a9b97..e086a362c 100644 --- a/src/menu/sound.h +++ b/src/menu/sound.h @@ -12,6 +12,9 @@ #define SOUND_MP3_PLAYER_CHANNEL (0) #define SOUND_SFX_CHANNEL (2) +/** + * @brief Enumeration of available sound effects for menu interactions. + */ typedef enum { SFX_CURSOR, SFX_ERROR, @@ -23,8 +26,22 @@ typedef enum { void sound_init_default (void); void sound_init_mp3_playback (void); + +/** + * @brief Initialize sound effects system. + */ void sound_init_sfx (void); + +/** + * @brief Enable or disable sound effects. + * @param enable True to enable sound effects, false to disable. + */ void sound_use_sfx(bool); + +/** + * @brief Play a specified sound effect. + * @param sfx The sound effect to play, as defined in sound_effect_t. + */ void sound_play_effect(sound_effect_t sfx); void sound_deinit (void); void sound_poll (void); From 9020445c17ac2b25c8ac691f734f495b717d8ca4 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 23 Oct 2024 13:46:29 +0100 Subject: [PATCH 31/89] Improve boxart Minor fixes for: * menu error SFX * RTC text --- src/menu/components/boxart.c | 25 ++++++++++++++++--------- src/menu/views/error.c | 2 +- src/menu/views/load_disk.c | 4 +++- src/menu/views/load_rom.c | 5 ++++- src/menu/views/rtc.c | 2 +- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/menu/components/boxart.c b/src/menu/components/boxart.c index 2535068ca..d663230a1 100644 --- a/src/menu/components/boxart.c +++ b/src/menu/components/boxart.c @@ -40,20 +40,28 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam switch (current_image_view) { case IMAGE_GAMEPAK_FRONT: path_push(path, "gamepak_front.png"); + break; case IMAGE_GAMEPAK_BACK: path_push(path, "gamepak_back.png"); + break; case IMAGE_BOXART_BACK: path_push(path, "boxart_back.png"); + break; case IMAGE_BOXART_LEFT: path_push(path, "boxart_left.png"); + break; case IMAGE_BOXART_RIGHT: path_push(path, "boxart_right.png"); + break; case IMAGE_BOXART_BOTTOM: path_push(path, "boxart_bottom.png"); + break; case IMAGE_BOXART_TOP: path_push(path, "boxart_top.png"); + break; default: path_push(path, "boxart_front.png"); + break; } if (file_exists(path_get(path))) { @@ -65,12 +73,13 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam } else { // compatibility mode - char file_name[8]; + char file_name[9]; // reset the directory path used for boxart. + path_free(path); path = path_init(storage_prefix, BOXART_DIRECTORY); - sprintf(file_name, "%c%c%c%c.png", game_code[0], game_code[1], game_code[2], game_code[3]); + snprintf(file_name, sizeof(file_name), "%c%c%c%c.png", game_code[0], game_code[1], game_code[2], game_code[3]); path_push(path, file_name); if (file_exists(path_get(path))) { @@ -81,21 +90,19 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam } path_pop(path); - sprintf(file_name, "%c%c%c.png", game_code[0], game_code[1], game_code[2]); + snprintf(file_name, sizeof(file_name), "%c%c%c.png", game_code[0], game_code[1], game_code[2]); path_push(path, file_name); if (file_exists(path_get(path))) { - if (file_exists(path_get(path))) { - if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { - path_free(path); - return b; - } + if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { + path_free(path); + return b; } } else { path_pop(path); - sprintf(file_name, "%c%c.png", game_code[1], game_code[2]); + snprintf(file_name, sizeof(file_name), "%c%c.png", game_code[1], game_code[2]); path_push(path, file_name); if (file_exists(path_get(path))) { if (png_decoder_start(path_get(path), BOXART_WIDTH_MAX, BOXART_HEIGHT_MAX, png_decoder_callback, b) == PNG_OK) { diff --git a/src/menu/views/error.c b/src/menu/views/error.c index 07eb70d42..7cb4f7d29 100644 --- a/src/menu/views/error.c +++ b/src/menu/views/error.c @@ -4,8 +4,8 @@ static void process (menu_t *menu) { if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 39957fcf2..f41407813 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -94,7 +94,9 @@ static void draw (menu_t *menu, surface_t *d) { ); } - component_boxart_draw(boxart); + if (boxart != NULL) { + component_boxart_draw(boxart); + } } rdpq_detach_show(); diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 36933b92c..4fb9d7101 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -262,7 +262,9 @@ static void draw (menu_t *menu, surface_t *d) { "R: Options" ); - component_boxart_draw(boxart); + if (boxart != NULL) { + component_boxart_draw(boxart); + } if (show_extra_info_message) { component_messagebox_draw( @@ -335,6 +337,7 @@ static void load (menu_t *menu) { static void deinit (void) { component_boxart_free(boxart); + boxart = NULL; } diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index ae56fd8b2..da595f6f4 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -36,7 +36,7 @@ static void draw (menu_t *menu, surface_t *d) { "\n" "\n" "To set the date and time, please use the PC terminal\n" - "application and set via USB or a game that uses it.\n\n" + "application and set via USB,\n or a N64 game with RTC support.\n\n" "Current date & time: %s\n", menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n" ); From 4732981f88f8916a92b9e4dcfaf2785c39d75748 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 23 Oct 2024 14:19:59 +0100 Subject: [PATCH 32/89] Correctly init joypay vars Correctly free overrides path --- src/menu/actions.c | 6 +++--- src/menu/rom_info.c | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/menu/actions.c b/src/menu/actions.c index 003171688..4cafbce7c 100644 --- a/src/menu/actions.c +++ b/src/menu/actions.c @@ -25,8 +25,8 @@ static void actions_clear (menu_t *menu) { } static void actions_update_direction (menu_t *menu) { - joypad_8way_t held_dir; - joypad_8way_t fast_dir; + joypad_8way_t held_dir = JOYPAD_8WAY_NONE; + joypad_8way_t fast_dir = JOYPAD_8WAY_NONE; JOYPAD_PORT_FOREACH (i) { held_dir = joypad_get_direction(i, JOYPAD_2D_DPAD | JOYPAD_2D_STICK); @@ -90,7 +90,7 @@ static void actions_update_direction (menu_t *menu) { } static void actions_update_buttons (menu_t *menu) { - joypad_buttons_t pressed; + joypad_buttons_t pressed = {0}; JOYPAD_PORT_FOREACH (i) { pressed = joypad_get_buttons_pressed(i); diff --git a/src/menu/rom_info.c b/src/menu/rom_info.c index e5b750ced..b313950c8 100644 --- a/src/menu/rom_info.c +++ b/src/menu/rom_info.c @@ -835,6 +835,7 @@ static rom_err_t save_override (path_t *path, const char *id, int value, int def mini_t *ini = mini_try_load(path_get(overrides_path)); if (!ini) { + path_free(overrides_path); return ROM_ERR_SAVE_IO; } From a6465787ded3fff24681e76da656e861bec83fa5 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 23 Oct 2024 15:29:46 +0100 Subject: [PATCH 33/89] Improve sound_deinit Close sound effects properly. --- src/menu/sound.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/menu/sound.c b/src/menu/sound.c index 98d627484..c439c4f92 100644 --- a/src/menu/sound.c +++ b/src/menu/sound.c @@ -19,10 +19,7 @@ static bool sfx_enabled = false; static void sound_reconfigure (int frequency) { if ((frequency > 0) && (audio_get_frequency() != frequency)) { - if (sound_initialized) { - mixer_close(); - audio_close(); - } + sound_deinit(); audio_init(frequency, NUM_BUFFERS); mixer_init(NUM_CHANNELS); mp3player_mixer_init(); @@ -86,6 +83,13 @@ void sound_play_effect(sound_effect_t sfx) { void sound_deinit (void) { if (sound_initialized) { + if (sfx_enabled) { + wav64_close(&sfx_cursor); + wav64_close(&sfx_exit); + wav64_close(&sfx_setting); + wav64_close(&sfx_enter); + wav64_close(&sfx_error); + } mixer_close(); audio_close(); sound_initialized = false; From 0116c1466745d52f130251c3501135f528ffbf21 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 23 Oct 2024 16:12:49 +0100 Subject: [PATCH 34/89] Update 99_developer_guide.md Improve submodule docs --- docs/99_developer_guide.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/99_developer_guide.md b/docs/99_developer_guide.md index 1734b8d9a..8aa628795 100644 --- a/docs/99_developer_guide.md +++ b/docs/99_developer_guide.md @@ -45,10 +45,13 @@ For ease of development and debugging, the menu ROM can run in the [Ares emulato * Add the required file to the correct folder on your SD card. -## Update Libdragon submodule -This repo currently uses the `preview` branch as a submodule at a specific commit. +## Update submodules To update to the latest version, use `git submodule update --remote` from the terminal. +### libdragon +This repo currently uses the `preview` branch as a submodule at a specific commit. +* To ensure your local instance is building against it, use `cd ./libdragon && make clobber -j && make libdragon tools -j && make install tools-install -j && cd ..` + ## Generate documentation Run `doxygen` from the dev container terminal. Make sure you fix the warnings before creating a PR! From 329ba270ff9a9698b918c7672fd8dec7d9c7f891 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 23 Oct 2024 20:14:25 +0100 Subject: [PATCH 35/89] Improve flashcart info SC64 only. Other flashcarts may show wrong info (but they are not yet supported anyway). --- src/menu/views/flashcart_info.c | 36 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 41c1f4431..fa1f6eb51 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -2,6 +2,10 @@ #include "../sound.h" +static inline const char *format_boolean_type (bool bool_value) { + return bool_value ? "Supported" : "Unsupported"; +} + static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; @@ -18,24 +22,40 @@ static void draw (menu_t *menu, surface_t *d) { component_main_text_draw( ALIGN_CENTER, VALIGN_TOP, - "FLASHCART INFORMATION\n" + "FLASHCART INFORMATION" "\n" "\n" - "This feature is not yet supported.\n\n" ); - // FIXME: Display: - // * cart_type - // * Firmware version - // * supported features (flashcart_features_t) - - component_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" + "Type:\n" + " %s\n\n" + "Firmware:\n" + " %s\n\n" + "Features:\n" + " Virtual 64DD: %s.\n" + " Real Time Clock: %s.\n" + " USB Debugging: %s.\n" + " CIC Detection: %s.\n" + " Region Detection: %s.\n" + "\n\n", + "SummerCart64", + "V?.?.?", + format_boolean_type(true), + format_boolean_type(true), + format_boolean_type(true), + format_boolean_type(true), + format_boolean_type(true) ); + // FIXME: Display: + // * cart_type + // * Firmware version + // * supported features (flashcart_features_t) + component_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, From 004f182e29e728e92912c5f9d966849c1b493b62 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 23 Oct 2024 20:45:47 +0100 Subject: [PATCH 36/89] Avoid casting void* to bool directly --- src/menu/views/settings_editor.c | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 55feffead..41d964073 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -12,36 +12,36 @@ static const char *format_switch (bool state) { } static void set_pal60_type (menu_t *menu, void *arg) { - menu->settings.pal60_enabled = (bool) (arg); + menu->settings.pal60_enabled = (bool)(uintptr_t)(arg); settings_save(&menu->settings); } static void set_protected_entries_type (menu_t *menu, void *arg) { - menu->settings.show_protected_entries = (bool) (arg); + menu->settings.show_protected_entries = (bool)(uintptr_t)(arg); settings_save(&menu->settings); menu->browser.reload = true; } static void set_use_saves_folder_type (menu_t *menu, void *arg) { - menu->settings.use_saves_folder = (bool) (arg); + menu->settings.use_saves_folder = (bool)(uintptr_t)(arg); settings_save(&menu->settings); } static void set_sound_enabled_type (menu_t *menu, void *arg) { - menu->settings.sound_enabled = (bool) (arg); + menu->settings.sound_enabled = (bool)(uintptr_t)(arg); sound_use_sfx(menu->settings.sound_enabled); settings_save(&menu->settings); } #ifdef BETA_SETTINGS static void set_bgm_enabled_type (menu_t *menu, void *arg) { - menu->settings.bgm_enabled = (bool) (arg); + menu->settings.bgm_enabled = (bool)(uintptr_t)(arg); settings_save(&menu->settings); } static void set_rumble_enabled_type (menu_t *menu, void *arg) { - menu->settings.rumble_enabled = (bool) (arg); + menu->settings.rumble_enabled = (bool)(uintptr_t)(arg); settings_save(&menu->settings); } @@ -53,39 +53,39 @@ static void set_rumble_enabled_type (menu_t *menu, void *arg) { static component_context_menu_t set_pal60_type_context_menu = { .list = { - {.text = "On", .action = set_pal60_type, .arg = (void *) (true) }, + {.text = "On", .action = set_pal60_type, .arg = (void *)(uintptr_t)(true) }, {.text = "Off", .action = set_pal60_type, .arg = (void *) (false) }, COMPONENT_CONTEXT_MENU_LIST_END, }}; static component_context_menu_t set_protected_entries_type_context_menu = { .list = { - {.text = "On", .action = set_protected_entries_type, .arg = (void *) (true) }, - {.text = "Off", .action = set_protected_entries_type, .arg = (void *) (false) }, + {.text = "On", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(true) }, + {.text = "Off", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(false) }, COMPONENT_CONTEXT_MENU_LIST_END, }}; static component_context_menu_t set_sound_enabled_type_context_menu = { .list = { - {.text = "On", .action = set_sound_enabled_type, .arg = (void *) (true) }, - {.text = "Off", .action = set_sound_enabled_type, .arg = (void *) (false) }, + {.text = "On", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(true) }, + {.text = "Off", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(false) }, COMPONENT_CONTEXT_MENU_LIST_END, }}; static component_context_menu_t set_use_saves_folder_type_context_menu = { .list = { - {.text = "On", .action = set_use_saves_folder_type, .arg = (void *) (true) }, - {.text = "Off", .action = set_use_saves_folder_type, .arg = (void *) (false) }, + {.text = "On", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(true) }, + {.text = "Off", .action = set_use_saves_folder_type, .arg = (void *)(uintptr_t)(false) }, COMPONENT_CONTEXT_MENU_LIST_END, }}; #ifdef BETA_SETTINGS static component_context_menu_t set_bgm_enabled_type_context_menu = { .list = { - {.text = "On", .action = set_bgm_enabled_type, .arg = (void *) (true) }, - {.text = "Off", .action = set_bgm_enabled_type, .arg = (void *) (false) }, + {.text = "On", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(true) }, + {.text = "Off", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(false) }, COMPONENT_CONTEXT_MENU_LIST_END, }}; static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = { - {.text = "On", .action = set_rumble_enabled_type, .arg = (void *) (true) }, - {.text = "Off", .action = set_rumble_enabled_type, .arg = (void *) (false) }, + {.text = "On", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(true) }, + {.text = "Off", .action = set_rumble_enabled_type, .arg = (void *)(uintptr_t)(false) }, COMPONENT_CONTEXT_MENU_LIST_END, }}; #endif From a1f2a5d56b7bebdad59f94b9d5d2702e75f06e18 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 13:11:29 +0100 Subject: [PATCH 37/89] Improve sound_poll --- src/menu/sound.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/menu/sound.c b/src/menu/sound.c index c439c4f92..08c5996f3 100644 --- a/src/menu/sound.c +++ b/src/menu/sound.c @@ -97,9 +97,7 @@ void sound_deinit (void) { } void sound_poll (void) { - if (sound_initialized && audio_can_write()) { - short *audio_buffer = audio_write_begin(); - mixer_poll(audio_buffer, audio_get_buffer_length()); - audio_write_end(); + if (sound_initialized) { + mixer_try_play(); } } From 3e37991b73853cee865be609de57ed31b0077ade Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 13:21:49 +0100 Subject: [PATCH 38/89] Improve context menu vars --- src/menu/components.h | 4 +-- src/menu/components/context_menu.c | 40 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/menu/components.h b/src/menu/components.h index 67fde2d31..6d31a3d38 100644 --- a/src/menu/components.h +++ b/src/menu/components.h @@ -73,8 +73,8 @@ void component_background_draw (void); void component_file_list_draw (entry_t *list, int entries, int selected); typedef struct component_context_menu { - int count; - int selected; + int row_count; + int row_selected; bool hide_pending; struct component_context_menu *parent; struct component_context_menu *submenu; diff --git a/src/menu/components/context_menu.c b/src/menu/components/context_menu.c index c4d0364ad..abf1947e7 100644 --- a/src/menu/components/context_menu.c +++ b/src/menu/components/context_menu.c @@ -13,22 +13,22 @@ static component_context_menu_t *get_current_submenu (component_context_menu_t * void component_context_menu_init (component_context_menu_t *cm) { - cm->selected = -1; - cm->count = 0; + cm->row_selected = -1; + cm->row_count = 0; cm->hide_pending = false; cm->parent = NULL; for (int i = 0; (cm->list[i].text) != NULL; i++) { - cm->count += 1; + cm->row_count += 1; } } void component_context_menu_show (component_context_menu_t *cm) { - cm->selected = 0; + cm->row_selected = 0; cm->submenu = NULL; } bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) { - if (!cm || (cm->selected < 0)) { + if (!cm || (cm->row_selected < 0)) { return false; } @@ -44,26 +44,26 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) } sound_play_effect(SFX_EXIT); } else if (menu->actions.enter) { - if (cm->list[cm->selected].submenu) { - cm->submenu = cm->list[cm->selected].submenu; + if (cm->list[cm->row_selected].submenu) { + cm->submenu = cm->list[cm->row_selected].submenu; component_context_menu_init(cm->submenu); - cm->submenu->selected = 0; + cm->submenu->row_selected = 0; cm->submenu->parent = cm; - } else if (cm->list[cm->selected].action) { - cm->list[cm->selected].action(menu, cm->list[cm->selected].arg); + } else if (cm->list[cm->row_selected].action) { + cm->list[cm->row_selected].action(menu, cm->list[cm->row_selected].arg); top->hide_pending = true; } sound_play_effect(SFX_ENTER); } else if (menu->actions.go_up) { - cm->selected -= 1; - if (cm->selected < 0) { - cm->selected = 0; + cm->row_selected -= 1; + if (cm->row_selected < 0) { + cm->row_selected = 0; } sound_play_effect(SFX_CURSOR); } else if (menu->actions.go_down) { - cm->selected += 1; - if (cm->selected >= cm->count) { - cm->selected = (cm->count - 1); + cm->row_selected += 1; + if (cm->row_selected >= cm->row_count) { + cm->row_selected = (cm->row_count - 1); } sound_play_effect(SFX_CURSOR); } @@ -72,7 +72,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) } void component_context_menu_draw (component_context_menu_t *cm) { - if (!cm || (cm->selected < 0)) { + if (!cm || (cm->row_selected < 0)) { return; } @@ -92,7 +92,7 @@ void component_context_menu_draw (component_context_menu_t *cm) { NULL ); - for (int i = 0; i < cm->count; i++) { + for (int i = 0; i < cm->row_count; i++) { const char *text = cm->list[i].text; rdpq_paragraph_builder_span(text, strlen(text)); if (cm->list[i + 1].text != NULL) { @@ -110,7 +110,7 @@ void component_context_menu_draw (component_context_menu_t *cm) { int highlight_x0 = DISPLAY_CENTER_X - (width / 2); int highlight_x1 = DISPLAY_CENTER_X + (width / 2); int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines; - int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->selected) * highlight_height); + int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->row_selected) * highlight_height); component_box_draw( highlight_x0, @@ -126,6 +126,6 @@ void component_context_menu_draw (component_context_menu_t *cm) { if (top->hide_pending) { top->hide_pending = false; - top->selected = -1; + top->row_selected = -1; } } From f5521031e43e0e6bbdfff2a185c12599cec09926 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 13:49:56 +0100 Subject: [PATCH 39/89] Revert change to sound_reconfigure that caused a de reference exception after MP3 playback. --- src/menu/sound.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/menu/sound.c b/src/menu/sound.c index 08c5996f3..ea0884478 100644 --- a/src/menu/sound.c +++ b/src/menu/sound.c @@ -19,7 +19,10 @@ static bool sfx_enabled = false; static void sound_reconfigure (int frequency) { if ((frequency > 0) && (audio_get_frequency() != frequency)) { - sound_deinit(); + if (sound_initialized) { + mixer_close(); + audio_close(); + } audio_init(frequency, NUM_BUFFERS); mixer_init(NUM_CHANNELS); mp3player_mixer_init(); From 819e27784baad3f926bbe40e5f0023890263b6d5 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 14:00:56 +0100 Subject: [PATCH 40/89] Update miniz --- src/libs/miniz | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/miniz b/src/libs/miniz index 1ff82be7d..35528ad76 160000 --- a/src/libs/miniz +++ b/src/libs/miniz @@ -1 +1 @@ -Subproject commit 1ff82be7d67f5c2f8b5497f538eea247861e0717 +Subproject commit 35528ad769143b9ed38a95a22d460b963e39f278 From 6eb6991ca02edd82b1b9ce45af4673db13f61631 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 14:48:21 +0100 Subject: [PATCH 41/89] Detect and display flashcart features correctly --- src/menu/views/flashcart_info.c | 40 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index fa1f6eb51..aa50b74ed 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -1,11 +1,31 @@ #include "views.h" #include "../sound.h" +#include static inline const char *format_boolean_type (bool bool_value) { return bool_value ? "Supported" : "Unsupported"; } +static const char *format_cart_type () { + switch (cart_type) { + case CART_CI: + return "64drive"; + + case CART_EDX: + return "Series X EverDrive-64"; + + case CART_ED: + return "Series V EverDrive-64"; + + case CART_SC: + return "SummerCart64"; + + default: // Probably emulator + return "Emulator?"; + } +} + static void process (menu_t *menu) { if (menu->actions.back) { menu->next_mode = MENU_MODE_BROWSER; @@ -42,21 +62,15 @@ static void draw (menu_t *menu, surface_t *d) { " CIC Detection: %s.\n" " Region Detection: %s.\n" "\n\n", - "SummerCart64", - "V?.?.?", - format_boolean_type(true), - format_boolean_type(true), - format_boolean_type(true), - format_boolean_type(true), - format_boolean_type(true) + format_cart_type(), + "Not Available", + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_64DD)), + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_RTC)), + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_USB)), + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_CIC)), + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_REGION)) ); - // FIXME: Display: - // * cart_type - // * Firmware version - // * supported features (flashcart_features_t) - - component_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" From f5dd0ae3360f45d6df8dec7d3109d34677880d67 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 15:09:12 +0100 Subject: [PATCH 42/89] Improve consistency of sound_play_effect --- src/menu/views/credits.c | 2 +- src/menu/views/file_info.c | 2 +- src/menu/views/flashcart_info.c | 2 +- src/menu/views/load_disk.c | 2 +- src/menu/views/load_emulator.c | 2 +- src/menu/views/load_rom.c | 2 +- src/menu/views/music_player.c | 2 +- src/menu/views/rtc.c | 2 +- src/menu/views/settings_editor.c | 2 +- src/menu/views/system_info.c | 2 +- src/menu/views/text_viewer.c | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/menu/views/credits.c b/src/menu/views/credits.c index fb068e6b0..1889232ad 100644 --- a/src/menu/views/credits.c +++ b/src/menu/views/credits.c @@ -12,8 +12,8 @@ static void process (menu_t *menu) { if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index 496c07f47..1b4609c45 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -50,8 +50,8 @@ static char *format_file_type (char *name, bool is_directory) { static void process (menu_t *menu) { if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index aa50b74ed..01bfe81ff 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -28,8 +28,8 @@ static const char *format_cart_type () { static void process (menu_t *menu) { if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index f41407813..be95010a9 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -38,8 +38,8 @@ static void process (menu_t *menu) { load_rom = true; sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index 589a6a599..9cfaeb487 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -36,8 +36,8 @@ static void process (menu_t *menu) { if (menu->actions.enter) { load_pending = true; } else if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 4fb9d7101..18acd7e62 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -198,8 +198,8 @@ static void process (menu_t *menu) { if (menu->actions.enter) { load_pending = true; } else if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } else if (menu->actions.options) { component_context_menu_show(&options_context_menu); sound_play_effect(SFX_SETTING); diff --git a/src/menu/views/music_player.c b/src/menu/views/music_player.c index 4de007563..c540bfabc 100644 --- a/src/menu/views/music_player.c +++ b/src/menu/views/music_player.c @@ -41,8 +41,8 @@ static void process (menu_t *menu) { if (err != MP3PLAYER_OK) { menu_show_error(menu, convert_error_message(err)); } else if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } else if (menu->actions.enter) { err = mp3player_toggle(); if (err != MP3PLAYER_OK) { diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index da595f6f4..6be481a38 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -18,8 +18,8 @@ static void process (menu_t *menu) { if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 41d964073..41dfa0a98 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -114,8 +114,8 @@ static void process (menu_t *menu) { component_context_menu_show(&options_context_menu); sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/system_info.c b/src/menu/views/system_info.c index 74ed4cbd0..2dea476d9 100644 --- a/src/menu/views/system_info.c +++ b/src/menu/views/system_info.c @@ -28,8 +28,8 @@ static void process (menu_t *menu) { } if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } } diff --git a/src/menu/views/text_viewer.c b/src/menu/views/text_viewer.c index cf8b505fe..0339ba376 100644 --- a/src/menu/views/text_viewer.c +++ b/src/menu/views/text_viewer.c @@ -55,8 +55,8 @@ static void perform_vertical_scroll (int lines) { static void process (menu_t *menu) { if (menu->actions.back) { - menu->next_mode = MENU_MODE_BROWSER; sound_play_effect(SFX_EXIT); + menu->next_mode = MENU_MODE_BROWSER; } else if (text) { if (menu->actions.go_up) { perform_vertical_scroll(menu->actions.go_fast ? -10 : -1); From b07364aa3f7a893bb93d48e66f860ce871ee9862 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 15:28:52 +0100 Subject: [PATCH 43/89] Update flashcart features --- src/flashcart/64drive/64drive.c | 1 + src/flashcart/flashcart.h | 3 +++ src/flashcart/sc64/sc64.c | 2 ++ src/menu/views/flashcart_info.c | 8 +++++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/flashcart/64drive/64drive.c b/src/flashcart/64drive/64drive.c index 363b3bcf3..5b4b5d22b 100644 --- a/src/flashcart/64drive/64drive.c +++ b/src/flashcart/64drive/64drive.c @@ -77,6 +77,7 @@ static bool d64_has_feature (flashcart_features_t feature) { case FLASHCART_FEATURE_USB: return true; case FLASHCART_FEATURE_AUTO_CIC: return true; case FLASHCART_FEATURE_AUTO_REGION: return true; + case FLASHCART_FEATURE_SAVE_WRITEBACK: return true; default: return false; } } diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index ade09eaa8..bd568968f 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -31,6 +31,9 @@ typedef enum { FLASHCART_FEATURE_USB, FLASHCART_FEATURE_AUTO_CIC, FLASHCART_FEATURE_AUTO_REGION, + FLASHCART_FEATURE_BATTERY_HEALTH, + FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU, + FLASHCART_FEATURE_SAVE_WRITEBACK } flashcart_features_t; /** @brief Flashcart save type enumeration */ diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index 927843a33..c0597057f 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -256,6 +256,8 @@ static bool sc64_has_feature (flashcart_features_t feature) { case FLASHCART_FEATURE_USB: return true; case FLASHCART_FEATURE_AUTO_CIC: return true; case FLASHCART_FEATURE_AUTO_REGION: return true; + case FLASHCART_FEATURE_BATTERY_HEALTH: return true; + case FLASHCART_FEATURE_SAVE_WRITEBACK: return true; default: return false; } } diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 01bfe81ff..bf4ddb296 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -61,6 +61,9 @@ static void draw (menu_t *menu, surface_t *d) { " USB Debugging: %s.\n" " CIC Detection: %s.\n" " Region Detection: %s.\n" + " Battery Health: %s.\n" + " Save Writeback: %s.\n" + " Update from menu: %s.\n" "\n\n", format_cart_type(), "Not Available", @@ -68,7 +71,10 @@ static void draw (menu_t *menu, surface_t *d) { format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_RTC)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_USB)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_CIC)), - format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_REGION)) + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_REGION)), + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_BATTERY_HEALTH)), + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_SAVE_WRITEBACK)), + format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU)) ); component_actions_bar_text_draw( From f1238debb1531193a14f37f2a747da17cd215e73 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 16:40:24 +0100 Subject: [PATCH 44/89] Improve fileinfo stat st_mtime will expand to st_mtim.tv_sec using a macro --- src/menu/views/file_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index 1b4609c45..9d45574be 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -84,7 +84,7 @@ static void draw (menu_t *menu, surface_t *d) { S_ISDIR(st.st_mode) ? "Directory" : "File", st.st_mode & S_IWUSR ? "" : "(Read only)", format_file_type(menu->browser.entry->name, S_ISDIR(st.st_mode)), - ctime(&st.st_mtim.tv_sec) + ctime(&st.st_mtime) ); component_actions_bar_text_draw( From dd3ce84a4695826267ba623ecf0b6f464b5428a1 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 17:16:10 +0100 Subject: [PATCH 45/89] Improve flashcart features --- src/flashcart/flashcart.h | 2 +- src/flashcart/sc64/sc64.c | 2 +- src/menu/views/flashcart_info.c | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index bd568968f..fcbcf606a 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -31,7 +31,7 @@ typedef enum { FLASHCART_FEATURE_USB, FLASHCART_FEATURE_AUTO_CIC, FLASHCART_FEATURE_AUTO_REGION, - FLASHCART_FEATURE_BATTERY_HEALTH, + FLASHCART_FEATURE_DIAGNOSTIC_DATA, FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU, FLASHCART_FEATURE_SAVE_WRITEBACK } flashcart_features_t; diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index c0597057f..605ae4191 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -256,7 +256,7 @@ static bool sc64_has_feature (flashcart_features_t feature) { case FLASHCART_FEATURE_USB: return true; case FLASHCART_FEATURE_AUTO_CIC: return true; case FLASHCART_FEATURE_AUTO_REGION: return true; - case FLASHCART_FEATURE_BATTERY_HEALTH: return true; + case FLASHCART_FEATURE_DIAGNOSTIC_DATA: return true; case FLASHCART_FEATURE_SAVE_WRITEBACK: return true; default: return false; } diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index bf4ddb296..635ef2cae 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -59,22 +59,23 @@ static void draw (menu_t *menu, surface_t *d) { " Virtual 64DD: %s.\n" " Real Time Clock: %s.\n" " USB Debugging: %s.\n" - " CIC Detection: %s.\n" + " Automatic CIC: %s.\n" " Region Detection: %s.\n" - " Battery Health: %s.\n" " Save Writeback: %s.\n" " Update from menu: %s.\n" "\n\n", format_cart_type(), - "Not Available", + "Not Available", // TODO get cart firmware version(s). format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_64DD)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_RTC)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_USB)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_CIC)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_AUTO_REGION)), - format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_BATTERY_HEALTH)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_SAVE_WRITEBACK)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_BIOS_UPDATE_FROM_MENU)) + + //TODO: display the battery and temperature information (if available). + //format_diagnostic_data(flashcart_has_feature(FLASHCART_FEATURE_DIAGNOSTIC_DATA)) ); component_actions_bar_text_draw( From 9c0a4b86a2b8dd970b400b846a5592fe2413f6b4 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 19:27:06 +0100 Subject: [PATCH 46/89] Improve settings editor text --- src/menu/views/settings_editor.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 41dfa0a98..64bed3b90 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -145,8 +145,9 @@ static void draw (menu_t *menu, surface_t *d) { " Background Music : %s\n" " Rumble Feedback : %s\n" #endif - "Note: Certain settings have the following caveats:\n\n" - "* Requires a flashcart reboot.\n", + "\n\n" + "Note: Certain settings have the following caveats:\n" + "* Requires rebooting the N64 Console.\n", menu->settings.default_directory, format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.show_protected_entries), From f65b345859f01ef953be5923ec1410af86ff375e Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 19:36:04 +0100 Subject: [PATCH 47/89] Updated doxygen action version use the new tag alias --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7da2151a..7f88be12e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,7 +113,7 @@ jobs: - uses: actions/checkout@v4 - name: Run Doxygen - uses: mattnotmitt/doxygen-action@1.9.5 + uses: mattnotmitt/doxygen-action@v1 with: doxyfile-path: './Doxyfile' From 7b1cf8f2ed5f858287c0a09bffa5b3dbd2d29e97 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 25 Oct 2024 23:00:55 +0100 Subject: [PATCH 48/89] Update README.md Improve Gamepak sprite info --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de3d030cb..6c4474fef 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ These must be `PNG` files that use the following dimensions: * Japanese N64 GamePak boxart sprites: 112x158 * 64DD boxart sprites: 129x112 -They will be loaded by directories using each character of the full 4 character Game Code (as identified in the menus ROM information). +They will be loaded by directories using each character (case-sensitive) of the full 4 character Game Code (as identified in the menu ROM information). i.e. for GoldenEye NTSC USA (NGEE), this would be `sd:/menu/boxart/N/G/E/E/boxart_front.png`. i.e. for GoldenEye PAL (NGEP), this would be `sd:/menu/boxart/N/G/E/P/boxart_front.png`. From 13caeee7a614afa2de792ce41e77fd8c56e9b23f Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 31 Oct 2024 14:36:11 +0000 Subject: [PATCH 49/89] Update Libdragon submodule (#153) ## Description Update submodules libdragon. ## Motivation and Context keeps libs up-to-date. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER ## Summary by CodeRabbit - **Chores** - Updated the subproject commit identifier to a newer version. --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 9dd994151..e93802aec 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 9dd994151ae3f3709f1f80224e6b654aac8be6b4 +Subproject commit e93802aec7e7839710281824ce2e9e4689b3a01d From 6cb3019c76bab79132a5d067b4bb1fce65db2ef5 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 31 Oct 2024 20:23:25 +0000 Subject: [PATCH 50/89] Display Entire Filename on ROM Information Page #101 --- src/menu/components/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/components/common.c b/src/menu/components/common.c index d0fce75f1..29a97ada6 100644 --- a/src/menu/components/common.c +++ b/src/menu/components/common.c @@ -151,7 +151,7 @@ void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *f .height = LAYOUT_ACTIONS_SEPARATOR_Y - OVERSCAN_HEIGHT - (TEXT_MARGIN_VERTICAL * 2), .align = align, .valign = valign, - .wrap = WRAP_ELLIPSES, + .wrap = WRAP_WORD, .line_spacing = TEXT_LINE_SPACING_ADJUST, }, FNT_DEFAULT, From b5f6adc1ea537c7c55b1884cb9bfde6d3b4d52ce Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 31 Oct 2024 21:26:33 +0000 Subject: [PATCH 51/89] Improve load_pending naming Make their actions clear. Works towards the ability to autoload ROMs. --- src/menu/views/load_disk.c | 24 ++++++++++++------------ src/menu/views/load_emulator.c | 12 ++++++------ src/menu/views/load_rom.c | 12 ++++++------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index be95010a9..580401eb4 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -5,8 +5,8 @@ #include "views.h" -static bool load_pending; -static bool load_rom; +static bool load_disk_file_boot_pending; +static bool load_disk_with_rom; static component_boxart_t *boxart; @@ -31,11 +31,11 @@ static char *format_disk_region (disk_region_t region) { static void process (menu_t *menu) { if (menu->actions.enter) { - load_pending = true; - load_rom = false; + load_disk_file_boot_pending = true; + load_disk_with_rom = false; } else if (menu->actions.options && menu->load.rom_path) { - load_pending = true; - load_rom = true; + load_disk_file_boot_pending = true; + load_disk_with_rom = true; sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); @@ -48,7 +48,7 @@ static void draw (menu_t *menu, surface_t *d) { component_background_draw(); - if (load_pending) { + if (load_disk_file_boot_pending) { component_loader_draw(0.0f); } else { component_layout_draw(); @@ -119,7 +119,7 @@ static void draw_progress (float progress) { static void load (menu_t *menu) { cart_load_err_t err; - if (menu->load.rom_path && load_rom) { + if (menu->load.rom_path && load_disk_with_rom) { err = cart_load_n64_rom_and_save(menu, draw_progress); if (err != CART_LOAD_OK) { menu_show_error(menu, cart_load_convert_error_message(err)); @@ -135,7 +135,7 @@ static void load (menu_t *menu) { menu->next_mode = MENU_MODE_BOOT; - if (load_rom) { + if (load_disk_with_rom) { menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM; menu->boot_params->detect_cic_seed = rom_info_get_cic_seed(&menu->load.rom_info, &menu->boot_params->cic_seed); switch (rom_info_get_tv_type(&menu->load.rom_info)) { @@ -163,7 +163,7 @@ void view_load_disk_init (menu_t *menu) { menu->load.disk_path = NULL; } - load_pending = false; + load_disk_file_boot_pending = false; menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); @@ -180,8 +180,8 @@ void view_load_disk_display (menu_t *menu, surface_t *display) { draw(menu, display); - if (load_pending) { - load_pending = false; + if (load_disk_file_boot_pending) { + load_disk_file_boot_pending = false; load(menu); } diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index 9cfaeb487..5b13ba24a 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -11,7 +11,7 @@ static const char *emu_gameboy_rom_extensions[] = { "gb", NULL }; static const char *emu_gameboy_color_rom_extensions[] = { "gbc", NULL }; static const char *emu_sega_8bit_rom_extensions[] = { "sms", "gg", "sg", NULL }; -static bool load_pending; +static bool load_emulator_file_boot_pending; static cart_load_emu_type_t emu_type; static char *format_emulator_name (cart_load_emu_type_t emulator_info) { @@ -34,7 +34,7 @@ static char *format_emulator_name (cart_load_emu_type_t emulator_info) { static void process (menu_t *menu) { if (menu->actions.enter) { - load_pending = true; + load_emulator_file_boot_pending = true; } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; @@ -46,7 +46,7 @@ static void draw (menu_t *menu, surface_t *d) { component_background_draw(); - if (load_pending) { + if (load_emulator_file_boot_pending) { component_loader_draw(0.0f); } else { component_layout_draw(); @@ -107,7 +107,7 @@ static void load (menu_t *menu) { void view_load_emulator_init (menu_t *menu) { - load_pending = false; + load_emulator_file_boot_pending = false; path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); @@ -133,8 +133,8 @@ void view_load_emulator_display (menu_t *menu, surface_t *display) { draw(menu, display); - if (load_pending) { - load_pending = false; + if (load_emulator_file_boot_pending) { + load_emulator_file_boot_pending = false; load(menu); } } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 18acd7e62..ef2d26bda 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -5,7 +5,7 @@ #include "views.h" static bool show_extra_info_message = false; -static bool load_pending; +static bool load_rom_file_boot_pending; static component_boxart_t *boxart; @@ -196,7 +196,7 @@ static void process (menu_t *menu) { } if (menu->actions.enter) { - load_pending = true; + load_rom_file_boot_pending = true; } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; @@ -218,7 +218,7 @@ static void draw (menu_t *menu, surface_t *d) { component_background_draw(); - if (load_pending) { + if (load_rom_file_boot_pending) { component_loader_draw(0.0f); } else { component_layout_draw(); @@ -342,7 +342,7 @@ static void deinit (void) { void view_load_rom_init (menu_t *menu) { - load_pending = false; + load_rom_file_boot_pending = false; if (menu->load.rom_path) { path_free(menu->load.rom_path); @@ -368,8 +368,8 @@ void view_load_rom_display (menu_t *menu, surface_t *display) { draw(menu, display); - if (load_pending) { - load_pending = false; + if (load_rom_file_boot_pending) { + load_rom_file_boot_pending = false; load(menu); } From 4eca91798c7513216d13048083023416617ddd30 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 31 Oct 2024 21:54:14 +0000 Subject: [PATCH 52/89] move boot_pending vars to menu_state works towards ability to autoload ROMs --- src/menu/menu_state.h | 6 ++++++ src/menu/views/load_disk.c | 13 ++++++------- src/menu/views/load_emulator.c | 11 +++++------ src/menu/views/load_rom.c | 12 +++++------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index 0d6aa4596..eaeb83ecc 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -104,6 +104,12 @@ typedef struct { path_t *disk_path; disk_info_t disk_info; } load; + + struct { + bool rom_file; + bool disk_file; + bool emulator_file; + } boot_pending; } menu_t; diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 580401eb4..781ebe270 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -5,7 +5,6 @@ #include "views.h" -static bool load_disk_file_boot_pending; static bool load_disk_with_rom; static component_boxart_t *boxart; @@ -31,10 +30,10 @@ static char *format_disk_region (disk_region_t region) { static void process (menu_t *menu) { if (menu->actions.enter) { - load_disk_file_boot_pending = true; + menu->boot_pending.disk_file = true; load_disk_with_rom = false; } else if (menu->actions.options && menu->load.rom_path) { - load_disk_file_boot_pending = true; + menu->boot_pending.disk_file = true; load_disk_with_rom = true; sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { @@ -48,7 +47,7 @@ static void draw (menu_t *menu, surface_t *d) { component_background_draw(); - if (load_disk_file_boot_pending) { + if (menu->boot_pending.disk_file) { component_loader_draw(0.0f); } else { component_layout_draw(); @@ -163,7 +162,7 @@ void view_load_disk_init (menu_t *menu) { menu->load.disk_path = NULL; } - load_disk_file_boot_pending = false; + menu->boot_pending.disk_file = false; menu->load.disk_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); @@ -180,8 +179,8 @@ void view_load_disk_display (menu_t *menu, surface_t *display) { draw(menu, display); - if (load_disk_file_boot_pending) { - load_disk_file_boot_pending = false; + if (menu->boot_pending.disk_file) { + menu->boot_pending.disk_file = false; load(menu); } diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index 5b13ba24a..3a1317227 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -11,7 +11,6 @@ static const char *emu_gameboy_rom_extensions[] = { "gb", NULL }; static const char *emu_gameboy_color_rom_extensions[] = { "gbc", NULL }; static const char *emu_sega_8bit_rom_extensions[] = { "sms", "gg", "sg", NULL }; -static bool load_emulator_file_boot_pending; static cart_load_emu_type_t emu_type; static char *format_emulator_name (cart_load_emu_type_t emulator_info) { @@ -34,7 +33,7 @@ static char *format_emulator_name (cart_load_emu_type_t emulator_info) { static void process (menu_t *menu) { if (menu->actions.enter) { - load_emulator_file_boot_pending = true; + menu->boot_pending.emulator_file = true; } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; @@ -46,7 +45,7 @@ static void draw (menu_t *menu, surface_t *d) { component_background_draw(); - if (load_emulator_file_boot_pending) { + if (menu->boot_pending.emulator_file) { component_loader_draw(0.0f); } else { component_layout_draw(); @@ -107,7 +106,7 @@ static void load (menu_t *menu) { void view_load_emulator_init (menu_t *menu) { - load_emulator_file_boot_pending = false; + menu->boot_pending.emulator_file = false; path_t *path = path_clone_push(menu->browser.directory, menu->browser.entry->name); @@ -133,8 +132,8 @@ void view_load_emulator_display (menu_t *menu, surface_t *display) { draw(menu, display); - if (load_emulator_file_boot_pending) { - load_emulator_file_boot_pending = false; + if (menu->boot_pending.emulator_file) { + menu->boot_pending.emulator_file = false; load(menu); } } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index ef2d26bda..1e8c4dc13 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -5,10 +5,8 @@ #include "views.h" static bool show_extra_info_message = false; -static bool load_rom_file_boot_pending; static component_boxart_t *boxart; - static char *convert_error_message (rom_err_t err) { switch (err) { case ROM_ERR_LOAD_IO: return "I/O error during loading ROM information and/or options"; @@ -196,7 +194,7 @@ static void process (menu_t *menu) { } if (menu->actions.enter) { - load_rom_file_boot_pending = true; + menu->boot_pending.rom_file = true; } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; @@ -218,7 +216,7 @@ static void draw (menu_t *menu, surface_t *d) { component_background_draw(); - if (load_rom_file_boot_pending) { + if (menu->boot_pending.rom_file) { component_loader_draw(0.0f); } else { component_layout_draw(); @@ -342,7 +340,7 @@ static void deinit (void) { void view_load_rom_init (menu_t *menu) { - load_rom_file_boot_pending = false; + menu->boot_pending.rom_file = false; if (menu->load.rom_path) { path_free(menu->load.rom_path); @@ -368,8 +366,8 @@ void view_load_rom_display (menu_t *menu, surface_t *display) { draw(menu, display); - if (load_rom_file_boot_pending) { - load_rom_file_boot_pending = false; + if (menu->boot_pending.rom_file) { + menu->boot_pending.rom_file = false; load(menu); } From 36647ebdecd2bfbb590740e85970964751eb5dc0 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 5 Nov 2024 09:27:44 +0000 Subject: [PATCH 53/89] N64 Rom autoload (#150) ## Description Allows the ability to autoload a specified ROM instead of the menu, taking into account the ROM database. If enabled, you can currently fallback to the menu by holding joypad start on console reset. ## Motivation and Context This is a better implementation of #140. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER ## Summary by CodeRabbit ## Release Notes - **New Features** - Introduced ROM autoloading functionality, allowing users to enable automatic loading of ROMs with specified paths and filenames. - Added a settings option for managing ROM autoload preferences in the menu. - **User Interface Enhancements** - Updated settings menu to display the state of the "Autoload ROM" setting. - Improved context menus for toggling various settings. - **Bug Fixes** - Enhanced memory management by ensuring proper resource deallocation for settings. - **Documentation** - Added documentation for new functions and settings related to ROM autoloading. --- README.md | 4 ++++ src/menu/settings.c | 12 +++++++++++- src/menu/settings.h | 11 ++++++++++- src/menu/views/load_rom.c | 31 +++++++++++++++++++++++-------- src/menu/views/settings_editor.c | 2 ++ src/menu/views/startup.c | 21 +++++++++++++++++++++ 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6c4474fef..8fae66a3d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ An open source menu for N64 flashcarts. * Real Time Clock support. * Music playback (MP3). * Menu sound effects. +* N64 ROM autoload. ## Documentation @@ -49,6 +50,9 @@ An open source menu for N64 flashcarts. ## Experimental features These features are subject to change: +### N64 ROM autoload +To use the autoload function, while on the `N64 ROM information` display, press the `R` button on your joypad and select the `Set ROM to autoload` option. When you restart the console, it will now only load the selected ROM rather than the menu. +NOTE: to return to the menu, hold joypad `start` button whilst powering on the console. ### GamePak sprites To use N64 `GamePak` sprites, place `PNG` files within the `sd:/menu/boxart/` folder. diff --git a/src/menu/settings.c b/src/menu/settings.c index 0b3725317..5361f0a64 100644 --- a/src/menu/settings.c +++ b/src/menu/settings.c @@ -14,7 +14,10 @@ static settings_t init = { .default_directory = "/", .use_saves_folder = true, .sound_enabled = true, - + .rom_autoload_enabled = false, + .rom_autoload_path = "", + .rom_autoload_filename = "", + /* Beta feature flags (should always init to off) */ .bgm_enabled = false, .rumble_enabled = false, @@ -41,6 +44,10 @@ void settings_load (settings_t *settings) { settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder); settings->sound_enabled = mini_get_bool(ini, "menu", "sound_enabled", init.sound_enabled); + settings->rom_autoload_enabled = mini_get_bool(ini, "menu", "autoload_rom_enabled", init.rom_autoload_enabled); + settings->rom_autoload_path = strdup(mini_get_string(ini, "autoload", "rom_path", init.rom_autoload_path)); + settings->rom_autoload_filename = strdup(mini_get_string(ini, "autoload", "rom_filename", init.rom_autoload_filename)); + /* Beta feature flags, they might not be in the file */ settings->bgm_enabled = mini_get_bool(ini, "menu_beta_flag", "bgm_enabled", init.bgm_enabled); settings->rumble_enabled = mini_get_bool(ini, "menu_beta_flag", "rumble_enabled", init.rumble_enabled); @@ -56,6 +63,9 @@ void settings_save (settings_t *settings) { mini_set_string(ini, "menu", "default_directory", settings->default_directory); mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder); mini_set_bool(ini, "menu", "sound_enabled", settings->sound_enabled); + mini_set_bool(ini, "menu", "autoload_rom_enabled", settings->rom_autoload_enabled); + mini_set_string(ini, "autoload", "rom_path", settings->rom_autoload_path); + mini_set_string(ini, "autoload", "rom_filename", settings->rom_autoload_filename); /* Beta feature flags, they should not save until production ready! */ // mini_set_bool(ini, "menu_beta_flag", "bgm_enabled", settings->bgm_enabled); diff --git a/src/menu/settings.h b/src/menu/settings.h index e9931b059..471879eb3 100644 --- a/src/menu/settings.h +++ b/src/menu/settings.h @@ -30,6 +30,16 @@ typedef struct { /** @brief Enable rumble feedback */ bool rumble_enabled; + + /** @brief Enable the ability to bypass the menu and instantly load a ROM */ + bool rom_autoload_enabled; + + /** @brief A path to the autoloaded ROM */ + char *rom_autoload_path; + + /** @brief A filename of the autoloaded ROM */ + char *rom_autoload_filename; + } settings_t; @@ -40,5 +50,4 @@ void settings_load (settings_t *settings); /** @brief The settings to save */ void settings_save (settings_t *settings); - #endif diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 1e8c4dc13..2a4ce34d0 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -3,6 +3,8 @@ #include "boot/boot.h" #include "../sound.h" #include "views.h" +#include +#include "utils/fs.h" static bool show_extra_info_message = false; static component_boxart_t *boxart; @@ -143,6 +145,17 @@ static void set_tv_type (menu_t *menu, void *arg) { menu->browser.reload = true; } +static void set_autoload_type (menu_t *menu, void *arg) { + free(menu->settings.rom_autoload_path); + menu->settings.rom_autoload_path = strdup(strip_fs_prefix(path_get(menu->browser.directory))); + free(menu->settings.rom_autoload_filename); + menu->settings.rom_autoload_filename = strdup(menu->browser.entry->name); + // FIXME: add a confirmation box here! (press start on reboot) + menu->settings.rom_autoload_enabled = true; + settings_save(&menu->settings); + menu->browser.reload = true; +} + static component_context_menu_t set_cic_type_context_menu = { .list = { {.text = "Automatic", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_AUTOMATIC) }, {.text = "CIC-6101", .action = set_cic_type, .arg = (void *) (ROM_CIC_TYPE_6101) }, @@ -185,6 +198,7 @@ static component_context_menu_t options_context_menu = { .list = { { .text = "Set CIC Type", .submenu = &set_cic_type_context_menu }, { .text = "Set Save Type", .submenu = &set_save_type_context_menu }, { .text = "Set TV Type", .submenu = &set_tv_type_context_menu }, + { .text = "Set ROM to autoload", .action = set_autoload_type }, COMPONENT_CONTEXT_MENU_LIST_END, }}; @@ -340,14 +354,14 @@ static void deinit (void) { void view_load_rom_init (menu_t *menu) { - menu->boot_pending.rom_file = false; + if (!menu->settings.rom_autoload_enabled) { + if (menu->load.rom_path) { + path_free(menu->load.rom_path); + } - if (menu->load.rom_path) { - path_free(menu->load.rom_path); + menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); } - menu->load.rom_path = path_clone_push(menu->browser.directory, menu->browser.entry->name); - rom_err_t err = rom_info_load(menu->load.rom_path, &menu->load.rom_info); if (err != ROM_OK) { path_free(menu->load.rom_path); @@ -356,9 +370,10 @@ void view_load_rom_init (menu_t *menu) { return; } - boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code, IMAGE_BOXART_FRONT); - - component_context_menu_init(&options_context_menu); + if (!menu->settings.rom_autoload_enabled) { + boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code, IMAGE_BOXART_FRONT); + component_context_menu_init(&options_context_menu); + } } void view_load_rom_display (menu_t *menu, surface_t *display) { diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 64bed3b90..068b4675c 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -136,6 +136,7 @@ static void draw (menu_t *menu, surface_t *d) { ALIGN_LEFT, VALIGN_TOP, "\n\n" " Default Directory : %s\n\n" + " Autoload ROM : %s\n" "To change the following menu settings, press 'A':\n" "* PAL60 Mode : %s\n" " Show Hidden Files : %s\n" @@ -149,6 +150,7 @@ static void draw (menu_t *menu, surface_t *d) { "Note: Certain settings have the following caveats:\n" "* Requires rebooting the N64 Console.\n", menu->settings.default_directory, + format_switch(menu->settings.rom_autoload_enabled), format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.show_protected_entries), format_switch(menu->settings.use_saves_folder), diff --git a/src/menu/views/startup.c b/src/menu/views/startup.c index 118d20dcb..c12a90450 100644 --- a/src/menu/views/startup.c +++ b/src/menu/views/startup.c @@ -9,6 +9,27 @@ static void draw (menu_t *menu, surface_t *d) { void view_startup_init (menu_t *menu) { + // FIXME: rather than use a controller button, would it be better to use the cart button? + JOYPAD_PORT_FOREACH (port) { + joypad_poll(); + joypad_buttons_t b_held = joypad_get_buttons_held(port); + + if (menu->settings.rom_autoload_enabled && b_held.start) { + menu->settings.rom_autoload_enabled = false; + menu->settings.rom_autoload_path = ""; + menu->settings.rom_autoload_filename = ""; + settings_save(&menu->settings); + } + } + if (menu->settings.rom_autoload_enabled) { + menu->browser.directory = path_init(menu->storage_prefix, menu->settings.rom_autoload_path); + menu->load.rom_path = path_clone_push(menu->browser.directory, menu->settings.rom_autoload_filename); + menu->boot_pending.rom_file = true; + menu->next_mode = MENU_MODE_LOAD_ROM; + + return; + } + menu->next_mode = MENU_MODE_BROWSER; } From e8c16a7af62a2886117c65fd34397126059b2af5 Mon Sep 17 00:00:00 2001 From: Guillermo Horacio Romero Villa <65469983+E1ite007@users.noreply.github.com> Date: Tue, 5 Nov 2024 04:36:36 -0600 Subject: [PATCH 54/89] Added new boxart following the pre-release folder structure (#152) ## Description Following #130 and with #135 efforts, I created this new database following the newer folder structure. ## Motivation and Context Adding compatibility for the newest `rolling pre-release`. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [x] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: E1ite007 ## Summary by CodeRabbit - **New Features** - Introduced a "GamePak sprites" section with guidelines for using N64 GamePak sprites. - Added a "Compatibility mode" subsection detailing deprecated filename support. - Included a "Sounds" subsection listing sound effects and their licenses. - Provided a link to a boxart pack that adheres to the new structure. - **Documentation** - Enhanced readability with formatting updates and additional examples. --------- Co-authored-by: Robin Jones --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8fae66a3d..c44efae5d 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,9 @@ i.e. for GoldenEye, this would be `sd:/menu/boxart/N/G/E/boxart_front.png`. **Note1:** Excluding the region ID may show the wrong boxart. **Note2:** For future support, boxart sprites should also include: `boxart_back.png`, `boxart_top.png`, `boxart_bottom.png`, `boxart_left.png`, `boxart_right.png`. +As a starting point, here is a link to a boxart pack following the new structure, including `boxart_front.png` and failback images: +* [Link](https://drive.google.com/file/d/1IpCmFqmGgGwKKmlRBxYObfFR9XywaC6n/view?usp=drive_link) + #### Compatibilty mode If you cannot yet satisfy the correct boxart layout, The menu still has **deprecated** support for filenames containing the Game ID. From 2fc4a6a6c12a6151edb77daaed102203317e8c31 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 5 Nov 2024 18:20:35 +0000 Subject: [PATCH 55/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index e93802aec..529501623 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit e93802aec7e7839710281824ce2e9e4689b3a01d +Subproject commit 5295016230d657cd6c7fce5b6ed4a342538e09f5 From fb0a04e048ed78a96c32d59397b1894f9b4aec61 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 5 Nov 2024 21:32:17 +0000 Subject: [PATCH 56/89] header documentation improvements --- src/boot/boot_io.h | 77 ++++++++++++------ src/boot/vr4300_asm.h | 52 +++++++----- src/menu/mp3_player.h | 185 +++++++++++++++++++++++++++++++++++------- 3 files changed, 242 insertions(+), 72 deletions(-) diff --git a/src/boot/boot_io.h b/src/boot/boot_io.h index 6923e4dff..eb9f5cc65 100644 --- a/src/boot/boot_io.h +++ b/src/boot/boot_io.h @@ -7,41 +7,79 @@ #ifndef BOOT_IO_H__ #define BOOT_IO_H__ - #include #include - +/** + * @typedef io8_t + * @brief 8-bit volatile IO type. + */ typedef volatile uint8_t io8_t; -typedef volatile uint32_t io32_t; +/** + * @typedef io32_t + * @brief 32-bit volatile IO type. + */ +typedef volatile uint32_t io32_t; +/** + * @brief Convert an address to its uncached equivalent. + * + * This macro takes an address and converts it to its uncached equivalent + * by setting the appropriate bits. + * + * @param address The address to convert. + * @return The uncached equivalent of the address. + */ #define UNCACHED(address) ((typeof(address)) (((io32_t) (address)) | (0xA0000000UL))) -/** @brief Memory Structure. */ +/** + * @brief Memory Structure. + * + * This structure represents the memory layout for the SP (Signal Processor), + * containing both Data Memory (DMEM) and Instruction Memory (IMEM). + */ typedef struct { - io32_t DMEM[1024]; - io32_t IMEM[1024]; + io32_t DMEM[1024]; /**< Data Memory (DMEM) array of 1024 32-bit words. */ + io32_t IMEM[1024]; /**< Instruction Memory (IMEM) array of 1024 32-bit words. */ } sp_mem_t; +/** + * @brief Base address for SP memory. + */ #define SP_MEM_BASE (0x04000000UL) + +/** + * @brief Pointer to the SP memory structure. + */ #define SP_MEM ((sp_mem_t *) SP_MEM_BASE) -/** @brief SP Registers Structure. */ +/** + * @brief SP Registers Structure. + * + * This structure represents the registers for the SP (Signal Processor). + */ typedef struct { - io32_t PADDR; - io32_t MADDR; - io32_t RD_LEN; - io32_t WR_LEN; - io32_t SR; - io32_t DMA_FULL; - io32_t DMA_BUSY; - io32_t SEMAPHORE; + io32_t PADDR; /**< Physical Address Register. */ + io32_t MADDR; /**< Memory Address Register. */ + io32_t RD_LEN; /**< Read Length Register. */ + io32_t WR_LEN; /**< Write Length Register. */ + io32_t SR; /**< Status Register. */ + io32_t DMA_FULL; /**< DMA Full Register. */ + io32_t DMA_BUSY; /**< DMA Busy Register. */ + io32_t SEMAPHORE; /**< Semaphore Register. */ io32_t __reserved[0xFFF8]; io32_t PC; } sp_regs_t; +/** + * @brief Base address for SP registers. + */ #define SP_BASE (0x04040000UL) + +/** + * @brief Pointer to the SP registers structure. + */ #define SP ((sp_regs_t *) SP_BASE) #define SP_SR_HALT (1 << 0) @@ -85,7 +123,6 @@ typedef struct { #define SP_SR_CLR_SIG7 (1 << 23) #define SP_SR_SET_SIG7 (1 << 24) - /** @brief DPC Registers Structure. */ typedef struct { io32_t START; @@ -123,7 +160,6 @@ typedef struct { #define DPC_SR_CLR_CMD_CTR (1 << 8) #define DPC_SR_CLR_CLOCK_CTR (1 << 9) - /** @brief Video Interface Registers Structure. */ typedef struct { /** @brief The Control Register. */ @@ -198,7 +234,6 @@ typedef struct { #define AI_SR_FIFO_FULL (1 << 31) #define AI_CR_DMA_ON (1 << 0) - /** @brief Peripheral Interface Register Structure. */ typedef struct { /** @brief The Memory Address. */ @@ -233,15 +268,12 @@ typedef struct { #define PI_SR_RESET (1 << 0) #define PI_SR_CLR_INTR (1 << 1) - #define ROM_DDIPL_BASE (0x06000000UL) #define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE) - #define ROM_CART_BASE (0x10000000UL) #define ROM_CART ((io32_t *) ROM_CART_BASE) - static inline uint32_t cpu_io_read (io32_t *address) { io32_t *uncached = UNCACHED(address); uint32_t value = *uncached; @@ -253,5 +285,4 @@ static inline void cpu_io_write (io32_t *address, uint32_t value) { *uncached = value; } - -#endif +#endif /* BOOT_IO_H__ */ diff --git a/src/boot/vr4300_asm.h b/src/boot/vr4300_asm.h index 59dfd77c3..b2736e212 100644 --- a/src/boot/vr4300_asm.h +++ b/src/boot/vr4300_asm.h @@ -3,37 +3,47 @@ #include +/** + * @brief VR4300 Instruction Structure + * + * This structure represents a VR4300 instruction, which can be of different types (R-type, I-type, J-type, etc.). + */ typedef union { - uint32_t raw; + uint32_t raw; /**< Raw 32-bit instruction */ struct { - uint32_t op : 6; - uint32_t rs : 5; - uint32_t rt : 5; - uint32_t imm : 16; - } i_type; + uint32_t op : 6; /**< Opcode field */ + uint32_t rs : 5; /**< Source register */ + uint32_t rt : 5; /**< Target register */ + uint32_t imm : 16; /**< Immediate value */ + } i_type; /**< I-type instruction format */ struct { - uint32_t op : 6; - uint32_t target : 26; - } j_type; + uint32_t op : 6; /**< Opcode field */ + uint32_t target : 26; /**< Target Address field */ + } j_type; /**< J-type instruction format */ struct { - uint32_t op : 6; - uint32_t rs : 5; - uint32_t rt : 5; - uint32_t rd : 5; - uint32_t sa : 5; - uint32_t funct : 6; - } r_type; + uint32_t op : 6; /**< Opcode field */ + uint32_t rs : 5; /**< Source register */ + uint32_t rt : 5; /**< Target register */ + uint32_t rd : 5; /**< Destination register */ + uint32_t sa : 5; /**< Shift amount */ + uint32_t funct : 6; /**< Function field */ + } r_type; /**< Alternate R-type instruction format */ struct { - uint32_t op : 6; - uint32_t co : 1; - uint32_t funct : 25; - } c_type; + uint32_t op : 6; /**< Opcode field */ + uint32_t co : 1; /**< Coprocessor operation bit */ + uint32_t funct : 25; /**< Function field */ + } c_type; /**< C-type instruction format */ } vr4300_instruction_t; +/** + * @brief VR4300 Opcode Enumeration + * + * Enumeration for different opcodes used in VR4300 instructions. + */ typedef enum { OP_SPECIAL, OP_REGIMM, @@ -394,4 +404,4 @@ typedef enum { #define I_SRL(rd, rt, sa) __ASM_R_INST(OP_SPECIAL, 0, rt, rd, sa, FUNCT_SRL) #define I_SW(rt, offset, base) __ASM_I_INST(OP_SW, base, rt, offset) -#endif +#endif /* VR4300_ASM_H__ */ diff --git a/src/menu/mp3_player.h b/src/menu/mp3_player.h index baea06bff..20f6c4854 100644 --- a/src/menu/mp3_player.h +++ b/src/menu/mp3_player.h @@ -7,37 +7,166 @@ #ifndef MP3_PLAYER_H__ #define MP3_PLAYER_H__ - #include - -/** @brief MP3 file error enumeration */ +/** + * @brief MP3 file error enumeration. + * + * Enumeration for different types of errors that can occur in the MP3 player. + */ typedef enum { - MP3PLAYER_OK, - MP3PLAYER_ERR_OUT_OF_MEM, - MP3PLAYER_ERR_IO, - MP3PLAYER_ERR_NO_FILE, - MP3PLAYER_ERR_INVALID_FILE, + MP3PLAYER_OK, /**< No error */ + MP3PLAYER_ERR_OUT_OF_MEM, /**< Out of memory error */ + MP3PLAYER_ERR_IO, /**< Input/Output error */ + MP3PLAYER_ERR_NO_FILE, /**< No file found error */ + MP3PLAYER_ERR_INVALID_FILE, /**< Invalid file error */ } mp3player_err_t; +/** + * @brief Initialize the MP3 player mixer. + * + * This function initializes the mixer for the MP3 player. + */ +void mp3player_mixer_init(void); + +/** + * @brief Initialize the MP3 player. + * + * This function initializes the MP3 player and prepares it for playback. + * + * @return mp3player_err_t Error code indicating the result of the initialization. + */ +mp3player_err_t mp3player_init(void); + +/** + * @brief Deinitialize the MP3 player. + * + * This function deinitializes the MP3 player and releases any resources. + */ +void mp3player_deinit(void); + +/** + * @brief Load an MP3 file. + * + * This function loads an MP3 file from the specified path. + * + * @param path Path to the MP3 file. + * @return mp3player_err_t Error code indicating the result of the load operation. + */ +mp3player_err_t mp3player_load(char *path); + +/** + * @brief Unload the current MP3 file. + * + * This function unloads the currently loaded MP3 file. + */ +void mp3player_unload(void); + +/** + * @brief Process the MP3 player. + * + * This function processes the MP3 player, handling playback and other operations. + * + * @return mp3player_err_t Error code indicating the result of the process operation. + */ +mp3player_err_t mp3player_process(void); + +/** + * @brief Check if the MP3 player is playing. + * + * This function checks if the MP3 player is currently playing. + * + * @return true if the MP3 player is playing, false otherwise. + */ +bool mp3player_is_playing(void); + +/** + * @brief Check if the MP3 player has finished playing. + * + * This function checks if the MP3 player has finished playing the current file. + * + * @return true if the MP3 player has finished playing, false otherwise. + */ +bool mp3player_is_finished(void); + +/** + * @brief Start playback of the MP3 file. + * + * This function starts playback of the currently loaded MP3 file. + * + * @return mp3player_err_t Error code indicating the result of the play operation. + */ +mp3player_err_t mp3player_play(void); + +/** + * @brief Stop playback of the MP3 file. + * + * This function stops playback of the currently loaded MP3 file. + */ +void mp3player_stop(void); + +/** + * @brief Toggle playback of the MP3 file. + * + * This function toggles playback of the currently loaded MP3 file. + * + * @return mp3player_err_t Error code indicating the result of the toggle operation. + */ +mp3player_err_t mp3player_toggle(void); + +/** + * @brief Mute or unmute the MP3 player. + * + * This function mutes or unmutes the MP3 player. + * + * @param mute true to mute, false to unmute. + */ +void mp3player_mute(bool mute); + +/** + * @brief Seek to a specific position in the MP3 file. + * + * This function seeks to a specific position in the currently loaded MP3 file. + * + * @param seconds Number of seconds to seek. + * @return mp3player_err_t Error code indicating the result of the seek operation. + */ +mp3player_err_t mp3player_seek(int seconds); + +/** + * @brief Get the duration of the MP3 file. + * + * This function gets the duration of the currently loaded MP3 file. + * + * @return float Duration of the MP3 file in seconds. + */ +float mp3player_get_duration(void); + +/** + * @brief Get the bitrate of the MP3 file. + * + * This function gets the bitrate of the currently loaded MP3 file. + * + * @return float Bitrate of the MP3 file in kbps. + */ +float mp3player_get_bitrate(void); + +/** + * @brief Get the sample rate of the MP3 file. + * + * This function gets the sample rate of the currently loaded MP3 file. + * + * @return int Sample rate of the MP3 file in Hz. + */ +int mp3player_get_samplerate(void); + +/** + * @brief Get the current playback progress. + * + * This function gets the current playback progress of the MP3 file. + * + * @return float Current playback progress as a percentage (0.0 to 100.0). + */ +float mp3player_get_progress(void); -void mp3player_mixer_init (void); -mp3player_err_t mp3player_init (void); -void mp3player_deinit (void); -mp3player_err_t mp3player_load (char *path); -void mp3player_unload (void); -mp3player_err_t mp3player_process (void); -bool mp3player_is_playing (void); -bool mp3player_is_finished (void); -mp3player_err_t mp3player_play (void); -void mp3player_stop (void); -mp3player_err_t mp3player_toggle (void); -void mp3player_mute (bool mute); -mp3player_err_t mp3player_seek (int seconds); -float mp3player_get_duration (void); -float mp3player_get_bitrate (void); -int mp3player_get_samplerate (void); -float mp3player_get_progress (void); - - -#endif +#endif /* MP3_PLAYER_H__ */ From 1e51108b0d9f2e9d2d242c03093f4dc39cc5406f Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 5 Nov 2024 22:41:19 +0000 Subject: [PATCH 57/89] Further header documentation improvements --- src/menu/fonts.h | 42 ++++++++++++++++++--------- src/menu/png_decoder.h | 65 ++++++++++++++++++++++++++++++++++-------- src/menu/sound.h | 42 +++++++++++++++++++-------- src/menu/usb_comm.h | 26 +++++++++++++---- 4 files changed, 133 insertions(+), 42 deletions(-) diff --git a/src/menu/fonts.h b/src/menu/fonts.h index 9fb000924..97dd54bde 100644 --- a/src/menu/fonts.h +++ b/src/menu/fonts.h @@ -7,23 +7,39 @@ #ifndef FONTS_H__ #define FONTS_H__ -/** @brief Font type enumeration. */ +/** + * @brief Font type enumeration. + * + * This enumeration defines the different types of fonts that can be used + * in the menu system. + */ typedef enum { - FNT_DEFAULT = 1, + FNT_DEFAULT = 1, /**< Default font type */ } menu_font_type_t; -/** @brief Font style enumeration. */ +/** + * @brief Font style enumeration. + * + * This enumeration defines the different styles of fonts that can be used + * in the menu system. + */ typedef enum { - STL_DEFAULT = 0, - STL_GREEN, - STL_BLUE, - STL_YELLOW, - STL_ORANGE, - STL_GRAY, + STL_DEFAULT = 0, /**< Default font style */ + STL_GREEN, /**< Green font style */ + STL_BLUE, /**< Blue font style */ + STL_YELLOW, /**< Yellow font style */ + STL_ORANGE, /**< Orange font style */ + STL_GRAY, /**< Gray font style */ } menu_font_style_t; +/** + * @brief Initialize fonts. + * + * This function initializes the fonts used in the menu system. It can load + * custom fonts from the specified path. + * + * @param custom_font_path Path to the custom font file. + */ +void fonts_init(char *custom_font_path); -void fonts_init (char *custom_font_path); - - -#endif +#endif /* FONTS_H__ */ diff --git a/src/menu/png_decoder.h b/src/menu/png_decoder.h index ea7977f75..a506e1361 100644 --- a/src/menu/png_decoder.h +++ b/src/menu/png_decoder.h @@ -7,27 +7,68 @@ #ifndef PNG_DECODER_H__ #define PNG_DECODER_H__ - #include - -/** @brief PNG decoder errors */ +/** + * @brief PNG decoder errors + * + * Enumeration for different types of errors that can occur in the PNG decoder. + */ typedef enum { - PNG_OK, - PNG_ERR_INT, - PNG_ERR_BUSY, - PNG_ERR_OUT_OF_MEM, - PNG_ERR_NO_FILE, - PNG_ERR_BAD_FILE, + PNG_OK, /**< No error */ + PNG_ERR_INT, /**< Internal error */ + PNG_ERR_BUSY, /**< Decoder is busy */ + PNG_ERR_OUT_OF_MEM, /**< Out of memory error */ + PNG_ERR_NO_FILE, /**< No file found error */ + PNG_ERR_BAD_FILE, /**< Bad file error */ } png_err_t; +/** + * @brief PNG decoder callback type. + * + * This typedef defines the callback function type used by the PNG decoder. + * + * @param err Error code indicating the result of the decoding process. + * @param decoded_image Pointer to the decoded image surface. + * @param callback_data User-defined data passed to the callback function. + */ typedef void png_callback_t (png_err_t err, surface_t *decoded_image, void *callback_data); - +/** + * @brief Start the PNG decoding process. + * + * This function starts the PNG decoding process for the specified file. + * + * @param path Path to the PNG file. + * @param max_width Maximum width of the decoded image. + * @param max_height Maximum height of the decoded image. + * @param callback Callback function to be called when decoding is complete. + * @param callback_data User-defined data to be passed to the callback function. + * @return png_err_t Error code indicating the result of the start operation. + */ png_err_t png_decoder_start (char *path, int max_width, int max_height, png_callback_t *callback, void *callback_data); + +/** + * @brief Abort the PNG decoding process. + * + * This function aborts the ongoing PNG decoding process. + */ void png_decoder_abort (void); + +/** + * @brief Get the progress of the PNG decoding process. + * + * This function returns the current progress of the PNG decoding process as a percentage. + * + * @return float Current progress of the decoding process (0.0 to 100.0). + */ float png_decoder_get_progress (void); -void png_decoder_poll (void); +/** + * @brief Poll the PNG decoder. + * + * This function polls the PNG decoder to handle any ongoing decoding tasks. + */ +void png_decoder_poll (void); -#endif +#endif /* PNG_DECODER_H__ */ diff --git a/src/menu/sound.h b/src/menu/sound.h index e086a362c..97d83d235 100644 --- a/src/menu/sound.h +++ b/src/menu/sound.h @@ -9,28 +9,46 @@ #include -#define SOUND_MP3_PLAYER_CHANNEL (0) -#define SOUND_SFX_CHANNEL (2) +#define SOUND_MP3_PLAYER_CHANNEL (0) /**< Channel for MP3 player sound */ +#define SOUND_SFX_CHANNEL (2) /**< Channel for sound effects */ /** * @brief Enumeration of available sound effects for menu interactions. + * + * This enumeration defines the different sound effects that can be used + * for menu interactions. */ typedef enum { - SFX_CURSOR, - SFX_ERROR, - SFX_ENTER, - SFX_EXIT, - SFX_SETTING, + SFX_CURSOR, /**< Sound effect for cursor movement */ + SFX_ERROR, /**< Sound effect for error */ + SFX_ENTER, /**< Sound effect for entering a menu */ + SFX_EXIT, /**< Sound effect for exiting a menu */ + SFX_SETTING, /**< Sound effect for changing a setting */ } sound_effect_t; +/** + * @brief Initialize the default sound system. + * + * This function initializes the default sound system, setting up + * necessary resources and configurations. + */ +void sound_init_default(void); -void sound_init_default (void); -void sound_init_mp3_playback (void); +/** + * @brief Initialize the MP3 playback system. + * + * This function initializes the MP3 playback system, preparing it + * for playing MP3 files. + */ +void sound_init_mp3_playback(void); /** - * @brief Initialize sound effects system. + * @brief Initialize the sound effects system. + * + * This function initializes the sound effects system, setting up + * necessary resources and configurations for playing sound effects. */ -void sound_init_sfx (void); +void sound_init_sfx(void); /** * @brief Enable or disable sound effects. @@ -46,4 +64,4 @@ void sound_play_effect(sound_effect_t sfx); void sound_deinit (void); void sound_poll (void); -#endif +#endif /* SOUND_H__ */ diff --git a/src/menu/usb_comm.h b/src/menu/usb_comm.h index c7f9a9ed2..0b914643a 100644 --- a/src/menu/usb_comm.h +++ b/src/menu/usb_comm.h @@ -2,20 +2,36 @@ * @file usb_comm.h * @brief USB communication subsystem * @ingroup menu + * + * This file contains the declarations for the USB communication subsystem + * used in the menu system. */ #ifndef USB_COMM_H__ #define USB_COMM_H__ - #include "menu_state.h" - #ifndef NDEBUG -void usb_comm_poll (menu_t *menu); +/** + * @brief Poll the USB communication subsystem. + * + * This function polls the USB communication subsystem to handle any + * incoming or outgoing data. It is only available in debug builds. + * + * @param menu Pointer to the menu structure. + */ +void usb_comm_poll(menu_t *menu); #else +/** + * @brief Poll the USB communication subsystem (no-op in release builds). + * + * This macro is a no-op in release builds, where USB communication polling + * is disabled. + * + * @param menu Pointer to the menu structure. + */ #define usb_comm_poll(menu) #endif - -#endif +#endif /* USB_COMM_H__ */ From 527e27525d2e793c010b323dd37027e2584f9727 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 5 Nov 2024 23:10:29 +0000 Subject: [PATCH 58/89] Further header documentation improvements --- src/flashcart/64drive/README.md | 2 +- src/flashcart/sc64/README.md | 2 +- src/menu/disk_info.h | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/flashcart/64drive/README.md b/src/flashcart/64drive/README.md index 6809afccd..6c6859c0e 100644 --- a/src/flashcart/64drive/README.md +++ b/src/flashcart/64drive/README.md @@ -1,4 +1,4 @@ -## 64drive developer notes +# 64drive developer notes ### Official documentation diff --git a/src/flashcart/sc64/README.md b/src/flashcart/sc64/README.md index 5071466f3..fe3134110 100644 --- a/src/flashcart/sc64/README.md +++ b/src/flashcart/sc64/README.md @@ -1,4 +1,4 @@ -## SummerCart64 developer notes +# SummerCart64 developer notes ### Official documentation diff --git a/src/menu/disk_info.h b/src/menu/disk_info.h index fe5175ec1..656768fff 100644 --- a/src/menu/disk_info.h +++ b/src/menu/disk_info.h @@ -52,7 +52,17 @@ typedef struct { } disk_info_t; +/** + * @brief Loads disk information from the specified path. + * + * This function reads the disk information from the given path and populates + * the provided disk_info structure with the relevant data. + * + * @param path A pointer to a path_t structure that specifies the path to the disk. + * @param disk_info A pointer to a disk_info_t structure where the disk information will be stored. + * @return A disk_err_t value indicating the success or failure of the operation. + */ disk_err_t disk_info_load (path_t *path, disk_info_t *disk_info); -#endif +#endif /* DISK_INFO_H__ */ From 0d2deaa4f4b277f6b7e7d79c956e78ac820497ec Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 5 Nov 2024 23:34:14 +0000 Subject: [PATCH 59/89] Further header documentation improvements --- src/menu/components.h | 284 ++++++++++++++++++++++++++++++++---------- 1 file changed, 218 insertions(+), 66 deletions(-) diff --git a/src/menu/components.h b/src/menu/components.h index 6d31a3d38..f15a6ba52 100644 --- a/src/menu/components.h +++ b/src/menu/components.h @@ -1,109 +1,261 @@ /** * @file components.h - * @brief Menu Components + * @brief Menu Graphical User Interface Components * @ingroup menu */ #ifndef COMPONENTS_H__ #define COMPONENTS_H__ - #include #include "menu_state.h" -/** @brief File image Enumeration. */ +/** + * @addtogroup menu_ui_components + * @{ + */ + +/** + * @brief File image Enumeration. + * + * Enumeration for different types of file images used in the GUI. + */ typedef enum { + IMAGE_BOXART_FRONT, /**< Boxart image from the front */ + IMAGE_BOXART_BACK, /**< Boxart image from the back */ + IMAGE_BOXART_TOP, /**< Boxart image from the top */ + IMAGE_BOXART_BOTTOM, /**< Boxart image from the bottom */ + IMAGE_BOXART_LEFT, /**< Boxart image from the left side */ + IMAGE_BOXART_RIGHT, /**< Boxart image from the right side */ + IMAGE_GAMEPAK_FRONT, /**< GamePak image from the front */ + IMAGE_GAMEPAK_BACK, /**< GamePak image from the back */ + IMAGE_THUMBNAIL, /**< File image thumbnail */ + IMAGE_TYPE_END /**< List end marker */ +} file_image_type_t; - /** @brief Boxart image from the front */ - IMAGE_BOXART_FRONT, +/** + * @brief Draw a box component. + * + * @param x0 Starting x-coordinate. + * @param y0 Starting y-coordinate. + * @param x1 Ending x-coordinate. + * @param y1 Ending y-coordinate. + * @param color Color of the box. + */ +void component_box_draw(int x0, int y0, int x1, int y1, color_t color); - /** @brief Boxart image from the back */ - IMAGE_BOXART_BACK, +/** + * @brief Draw a border component. + * + * @param x0 Starting x-coordinate. + * @param y0 Starting y-coordinate. + * @param x1 Ending x-coordinate. + * @param y1 Ending y-coordinate. + */ +void component_border_draw(int x0, int y0, int x1, int y1); - /** @brief Boxart image from the top */ - IMAGE_BOXART_TOP, +/** + * @brief Draw the layout component. + */ +void component_layout_draw(void); - /** @brief Boxart image from the bottom */ - IMAGE_BOXART_BOTTOM, +/** + * @brief Draw a progress bar component. + * + * @param x0 Starting x-coordinate. + * @param y0 Starting y-coordinate. + * @param x1 Ending x-coordinate. + * @param y1 Ending y-coordinate. + * @param progress Progress value (0.0 to 1.0). + */ +void component_progressbar_draw(int x0, int y0, int x1, int y1, float progress); - /** @brief Boxart image from the left side */ - IMAGE_BOXART_LEFT, +/** + * @brief Draw a seek bar component. + * + * @param progress Progress value (0.0 to 1.0). + */ +void component_seekbar_draw(float progress); - /** @brief Boxart image from the right side */ - IMAGE_BOXART_RIGHT, +/** + * @brief Draw a loader component. + * + * @param position Position value (0.0 to 1.0). + */ +void component_loader_draw(float position); - /** @brief GamePak image from the front */ - IMAGE_GAMEPAK_FRONT, +/** + * @brief Draw a scrollbar component. + * + * @param x Starting x-coordinate. + * @param y Starting y-coordinate. + * @param width Width of the scrollbar. + * @param height Height of the scrollbar. + * @param position Current position. + * @param items Total number of items. + * @param visible_items Number of visible items. + */ +void component_scrollbar_draw(int x, int y, int width, int height, int position, int items, int visible_items); - /** @brief GamePak image from the back */ - IMAGE_GAMEPAK_BACK, +/** + * @brief Draw a list scrollbar component. + * + * @param position Current position. + * @param items Total number of items. + * @param visible_items Number of visible items. + */ +void component_list_scrollbar_draw(int position, int items, int visible_items); - /** @brief File image thumbnail */ - IMAGE_THUMBNAIL, +/** + * @brief Draw a dialog component. + * + * @param width Width of the dialog. + * @param height Height of the dialog. + */ +void component_dialog_draw(int width, int height); - /** @brief List end marker */ - IMAGE_TYPE_END +/** + * @brief Draw a message box component. + * + * @param fmt Format string for the message. + * @param ... Additional arguments for the format string. + */ +void component_messagebox_draw(char *fmt, ...); -} file_image_type_t; +/** + * @brief Draw the main text component. + * + * @param align Horizontal alignment. + * @param valign Vertical alignment. + * @param fmt Format string for the text. + * @param ... Additional arguments for the format string. + */ +void component_main_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); + +/** + * @brief Draw the actions bar text component. + * + * @param align Horizontal alignment. + * @param valign Vertical alignment. + * @param fmt Format string for the text. + * @param ... Additional arguments for the format string. + */ +void component_actions_bar_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); +/** + * @brief Initialize the background component. + * + * @param cache_location Location of the cache. + */ +void component_background_init(char *cache_location); /** - * @addtogroup - * @{ menu_components + * @brief Free the background component resources. */ +void component_background_free(void); -void component_box_draw (int x0, int y0, int x1, int y1, color_t color); -void component_border_draw (int x0, int y0, int x1, int y1); -void component_layout_draw (void); -void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress); -void component_seekbar_draw (float progress); -void component_loader_draw (float position); -void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items); -void component_list_scrollbar_draw (int position, int items, int visible_items); -void component_dialog_draw (int width, int height); -void component_messagebox_draw (char *fmt, ...); -void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); -void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); +/** + * @brief Replace the background image. + * + * @param image New background image. + */ +void component_background_replace_image(surface_t *image); -void component_background_init (char *cache_location); -void component_background_free (void); -void component_background_replace_image (surface_t *image); -void component_background_draw (void); +/** + * @brief Draw the background component. + */ +void component_background_draw(void); -void component_file_list_draw (entry_t *list, int entries, int selected); +/** + * @brief Draw the file list component. + * + * @param list List of entries. + * @param entries Number of entries. + * @param selected Index of the selected entry. + */ +void component_file_list_draw(entry_t *list, int entries, int selected); +/** + * @brief Context menu structure. + */ typedef struct component_context_menu { - int row_count; - int row_selected; - bool hide_pending; - struct component_context_menu *parent; - struct component_context_menu *submenu; + int row_count; /**< Number of rows in the context menu */ + int row_selected; /**< Index of the selected row */ + bool hide_pending; /**< Flag to indicate if hiding is pending */ + struct component_context_menu *parent; /**< Pointer to the parent context menu */ + struct component_context_menu *submenu; /**< Pointer to the submenu */ struct { - const char *text; - void (*action) (menu_t *menu, void *arg); - void *arg; - struct component_context_menu *submenu; - } list[]; + const char *text; /**< Text of the menu item */ + void (*action)(menu_t *menu, void *arg); /**< Action function for the menu item */ + void *arg; /**< Argument for the action function */ + struct component_context_menu *submenu; /**< Pointer to the submenu */ + } list[]; /**< List of menu items */ } component_context_menu_t; -#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL } +#define COMPONENT_CONTEXT_MENU_LIST_END { .text = NULL } /**< End marker for the context menu list */ + +/** + * @brief Initialize the context menu component. + * + * @param cm Pointer to the context menu structure. + */ +void component_context_menu_init(component_context_menu_t *cm); -void component_context_menu_init (component_context_menu_t *cm); -void component_context_menu_show (component_context_menu_t *cm); -bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm); -void component_context_menu_draw (component_context_menu_t *cm); +/** + * @brief Show the context menu component. + * + * @param cm Pointer to the context menu structure. + */ +void component_context_menu_show(component_context_menu_t *cm); -/** @brief Box Art Structure. */ +/** + * @brief Process the context menu component. + * + * @param menu Pointer to the menu structure. + * @param cm Pointer to the context menu structure. + * @return True if the context menu was processed, false otherwise. + */ +bool component_context_menu_process(menu_t *menu, component_context_menu_t *cm); + +/** + * @brief Draw the context menu component. + * + * @param cm Pointer to the context menu structure. + */ +void component_context_menu_draw(component_context_menu_t *cm); + +/** + * @brief Box Art Structure. + */ typedef struct { - bool loading; - surface_t *image; + bool loading; /**< Flag to indicate if the box art is loading */ + surface_t *image; /**< Pointer to the box art image */ } component_boxart_t; -component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view); -void component_boxart_free (component_boxart_t *b); -void component_boxart_draw (component_boxart_t *b); +/** + * @brief Initialize the box art component. + * + * @param storage_prefix Prefix for the storage location. + * @param game_code Game code for the box art. + * @param current_image_view Current image view type. + * @return Pointer to the initialized box art component. + */ +component_boxart_t *component_boxart_init(const char *storage_prefix, char *game_code, file_image_type_t current_image_view); -/** @} */ /* menu_components */ +/** + * @brief Free the box art component resources. + * + * @param b Pointer to the box art component. + */ +void component_boxart_free(component_boxart_t *b); + +/** + * @brief Draw the box art component. + * + * @param b Pointer to the box art component. + */ +void component_boxart_draw(component_boxart_t *b); +/** @} */ /* menu_ui_components */ -#endif +#endif /* COMPONENTS_H__ */ From 17c214537ffa555900af3d72d348d27419612ac4 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 5 Nov 2024 23:40:42 +0000 Subject: [PATCH 60/89] Minor fix for disk load error Don't bother to load boxart after. --- src/menu/views/load_disk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 781ebe270..9ba914d90 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -169,6 +169,7 @@ void view_load_disk_init (menu_t *menu) { disk_err_t err = disk_info_load(menu->load.disk_path, &menu->load.disk_info); if (err != DISK_OK) { menu_show_error(menu, convert_error_message(err)); + return; } boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id, IMAGE_BOXART_FRONT); From e7608dc557d9f46bd0439d2dd03c42550f04cfe4 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 7 Nov 2024 20:16:35 +0000 Subject: [PATCH 61/89] Add ability to adjust RTC (#107) ## Description This PR adds the ability to set the time from the menu. The underlying logic is sound given what is currently available within libdragon, but expected changes are noted (like `settimeofday`) for future improvement and will need to be revisited once available. A future PR will need to add better GUI components for numeric control. ## Motivation and Context #106 ## How Has This Been Tested? locally on a SC64 ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/views/rtc.c | 212 ++++++++++++++++++++++++++++++----- src/menu/views/system_info.c | 2 +- 2 files changed, 184 insertions(+), 30 deletions(-) diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index 6be481a38..c93d7aa23 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -1,26 +1,165 @@ -#include +#include +#include +#include +#include #include "../sound.h" #include "views.h" -// FIXME: add implementation! -// struct { -// uint16_t seconds; -// uint16_t minutes; -// uint16_t hours; -// uint16_t day; -// uint16_t month; -// uint16_t year; -// } adjusted_datetime; +#define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; }) +#define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; }) +#define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max))) -// static void save_adjusted_datetime () { +#define YEAR_MIN 1996 +#define YEAR_MAX 2095 -// } +typedef enum { + RTC_EDIT_YEAR, + RTC_EDIT_MONTH, + RTC_EDIT_DAY, + RTC_EDIT_HOUR, + RTC_EDIT_MIN, + RTC_EDIT_SEC, +} rtc_field_t; + +static const char* const DAYS_OF_WEEK[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + +static struct tm rtc_tm = {0}; +static bool is_editing_mode; +static rtc_field_t editing_field_type; + +int wrap( uint16_t val, uint16_t min, uint16_t max ) { + if( val < min ) return max; + if( val > max ) return min; + return val; +} + +rtc_time_t rtc_time_from_tm( struct tm *time ) { + return(rtc_time_t){ + .year = CLAMP(time->tm_year + 1900, YEAR_MIN, YEAR_MAX), + .month = CLAMP(time->tm_mon, 1, 12), + .day = CLAMP(time->tm_mday, 1, 31), + .hour = CLAMP(time->tm_hour, 0, 23), + .min = CLAMP(time->tm_min, 0, 59), + .sec = CLAMP(time->tm_sec, 0, 59), + .week_day = CLAMP(time->tm_wday, 0, 6), + }; +} + +void adjust_rtc_time( struct tm *t, int incr ) { + switch(editing_field_type) + { + case RTC_EDIT_YEAR: + t->tm_year = wrap( t->tm_year + incr, YEAR_MIN - 1900, YEAR_MAX - 1900 ); + break; + case RTC_EDIT_MONTH: + t->tm_mon = wrap( t->tm_mon + incr, 0, 11 ); + break; + case RTC_EDIT_DAY: + t->tm_mday = wrap( t->tm_mday + incr, 1, 31 ); + break; + case RTC_EDIT_HOUR: + t->tm_hour = wrap( t->tm_hour + incr, 0, 23 ); + break; + case RTC_EDIT_MIN: + t->tm_min = wrap( t->tm_min + incr, 0, 59 ); + break; + case RTC_EDIT_SEC: + t->tm_sec = wrap( t->tm_sec + incr, 0, 59 ); + break; + } + // Recalculate day-of-week and day-of-year + time_t timestamp = mktime( t ); + *t = *gmtime( ×tamp ); +} + +void component_editdatetime_draw ( struct tm t, rtc_field_t selected_field ) { + // FIXME: move this to components.c once improved. + /* Format RTC date/time as strings */ + char full_dt[30]; + char current_selection_chars[30]; + + snprintf( full_dt, sizeof(full_dt), ">%04d|%02d|%02d|%02d|%02d|%02d< %s", + t.tm_year + 1900, + t.tm_mon + 1, + t.tm_mday, + t.tm_hour, + t.tm_min, + t.tm_sec, + DAYS_OF_WEEK[t.tm_wday] + ); + + switch(selected_field) + { + // Note: for what ever reason, hat chars need to be duplicated to display correctly. This will be solved when there is a decent UI for it. + case RTC_EDIT_YEAR: + snprintf( current_selection_chars, sizeof(current_selection_chars), "*^^^^^^^^********************"); + break; + case RTC_EDIT_MONTH: + snprintf( current_selection_chars, sizeof(current_selection_chars), "******^^^^*****************"); + break; + case RTC_EDIT_DAY: + snprintf( current_selection_chars, sizeof(current_selection_chars), "*********^^^^**************"); + break; + case RTC_EDIT_HOUR: + snprintf( current_selection_chars, sizeof(current_selection_chars), "************^^^^***********"); + break; + case RTC_EDIT_MIN: + snprintf( current_selection_chars, sizeof(current_selection_chars), "***************^^^^********"); + break; + case RTC_EDIT_SEC: + snprintf( current_selection_chars, sizeof(current_selection_chars), "******************^^^^*****"); + break; + } + component_messagebox_draw( + "|YYYY|MM|DD|HH|MM|SS| DOW\n%s\n%s\n", full_dt, current_selection_chars); +} static void process (menu_t *menu) { - if (menu->actions.back) { + if (menu->actions.back && !is_editing_mode) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; } + else if (menu->actions.enter && !is_editing_mode) { + rtc_tm = *gmtime(&menu->current_time); + is_editing_mode = true; + } + + if (is_editing_mode) { + if (menu->actions.go_left) { + if ( editing_field_type <= RTC_EDIT_YEAR ) { editing_field_type = RTC_EDIT_SEC; } + else { editing_field_type = editing_field_type - 1; } + } + else if (menu->actions.go_right) { + if ( editing_field_type >= RTC_EDIT_SEC ) { editing_field_type = RTC_EDIT_YEAR; } + else { editing_field_type = editing_field_type + 1; } + } + else if (menu->actions.go_up) { + adjust_rtc_time( &rtc_tm, +1 ); + } + else if (menu->actions.go_down) { + adjust_rtc_time( &rtc_tm, -1 ); + } + else if (menu->actions.options) { // R button = save + if(rtc_is_writable()) { + // FIXME: settimeofday is not available in libdragon yet. + // struct timeval new_time = { .tv_sec = mktime(&rtc_tm) }; + // int res = settimeofday(&new_time, NULL); + + rtc_time_t rtc_time = rtc_time_from_tm(&rtc_tm); + int res = rtc_set(&rtc_time); + if (res != 1) { + menu_show_error(menu, "Failed to set RTC time"); + } + } + else { + menu_show_error(menu, "RTC is not writable"); + } + is_editing_mode = false; + } + else if (menu->actions.back) { // cancel + is_editing_mode = false; + } + } } static void draw (menu_t *menu, surface_t *d) { @@ -30,36 +169,51 @@ static void draw (menu_t *menu, surface_t *d) { component_layout_draw(); - component_main_text_draw( + component_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "ADJUST REAL TIME CLOCK\n" "\n" "\n" - "To set the date and time, please use the PC terminal\n" - "application and set via USB,\n or a N64 game with RTC support.\n\n" - "Current date & time: %s\n", - menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n" - ); - - component_main_text_draw( - ALIGN_LEFT, VALIGN_TOP, - "\n" + "To set the RTC date and time, Press A.\n" + "You can also use the PC terminal application via USB,\n" + "or even an N64 game with RTC support.\n" "\n" + "Current date & time: %s\n" + "\n", + menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown" ); + if (!is_editing_mode) { + component_actions_bar_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "A: Change\n" + "B: Back" + ); + } + else { + component_actions_bar_text_draw( + ALIGN_RIGHT, VALIGN_TOP, + "Up/Down: Adjust Field\n" + "Left/Right: Switch Field" + ); + component_actions_bar_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "R: Save\n" + "B: Back" + ); + } - component_actions_bar_text_draw( - ALIGN_LEFT, VALIGN_TOP, - "\n" // "A: Save\n" - "B: Back" - ); + if (is_editing_mode) { + component_editdatetime_draw(rtc_tm, editing_field_type); + } rdpq_detach_show(); } void view_rtc_init (menu_t *menu) { - // Nothing to initialize (yet) + is_editing_mode = false; + editing_field_type = RTC_EDIT_YEAR; } void view_rtc_display (menu_t *menu, surface_t *display) { diff --git a/src/menu/views/system_info.c b/src/menu/views/system_info.c index 2dea476d9..18cffb0d0 100644 --- a/src/menu/views/system_info.c +++ b/src/menu/views/system_info.c @@ -57,7 +57,7 @@ static void draw (menu_t *menu, surface_t *d) { "Joypad 2 is %sconnected %s\n" "Joypad 3 is %sconnected %s\n" "Joypad 4 is %sconnected %s\n", - menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown\n", + menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown", is_memory_expanded() ? "" : "not ", (joypad[0]) ? "" : "not ", format_accessory(0), (joypad[1]) ? "" : "not ", format_accessory(1), From 5f14f7876d0d7ae4a3be33fbba68a0d37eda10a8 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 7 Nov 2024 22:31:01 +0000 Subject: [PATCH 62/89] Add basic flashcart support for V-Series ED64 limited to loading ROM's. --- Makefile | 1 + README.md | 14 ++- src/flashcart/ed64/ed64_vseries.c | 155 ++++++++++++++++++++++++++++++ src/flashcart/ed64/ed64_vseries.h | 24 +++++ src/flashcart/ed64/ed64_xseries.h | 24 +++++ src/flashcart/flashcart.c | 10 +- 6 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 src/flashcart/ed64/ed64_vseries.c create mode 100644 src/flashcart/ed64/ed64_vseries.h create mode 100644 src/flashcart/ed64/ed64_xseries.h diff --git a/Makefile b/Makefile index 70255c606..eac1f424a 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ SRCS = \ flashcart/64drive/64drive_ll.c \ flashcart/64drive/64drive.c \ flashcart/flashcart_utils.c \ + flashcart/ed64/ed64_vseries.c \ flashcart/flashcart.c \ flashcart/sc64/sc64_ll.c \ flashcart/sc64/sc64.c \ diff --git a/README.md b/README.md index c44efae5d..974577ba4 100644 --- a/README.md +++ b/README.md @@ -114,10 +114,20 @@ If required, you can manually adjust the file on the SD card using your computer * Download the latest `menu.bin` file from the [releases](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases/) page, then put it in the root directory of your SD card. -### ED64 & ED64P +### ED64 - WIP - UNTESTED AND UNSUPPORTED - USE AT OWN RISK Currently not supported, but work is in progress (See [PR's](https://github.com/Polprzewodnikowy/N64FlashcartMenu/pulls)). +NOTE: The menu may be able to load ROM's but not perform saves and may break existing ones.. -The aim is to replace [Altra64](https://github.com/networkfusion/altra64) and [ED64-UnofficialOS](https://github.com/n64-tools/ED64-UnofficialOS-binaries). +#### ED64 (Vseries) +The aim is to reach feature parity with [ED64-UnofficialOS](https://github.com/n64-tools/ED64-UnofficialOS-binaries) / [ED64-OfficialOS](https://krikzz.com/pub/support/everdrive-64/v2x-v3x/os-bin/). +Download the `OS64.v64` ROM from the latest [action run - assets] and place it in the `/ED64` folder. + +#### ED64 (X series) +X Series support is currently awaiting fixes, in the meantime use the official [OS](https://krikzz.com/pub/support/everdrive-64/x-series/OS/) instead. + +#### ED64 (P clone) +Download the `OS64P.v64` ROM from the latest [action run - assets] and place it in the `/ED64P` folder. +The aim is to reach feature parity with [Altra64](https://github.com/networkfusion/altra64) # Open source software and licenses used diff --git a/src/flashcart/ed64/ed64_vseries.c b/src/flashcart/ed64/ed64_vseries.c new file mode 100644 index 000000000..22b6596d7 --- /dev/null +++ b/src/flashcart/ed64/ed64_vseries.c @@ -0,0 +1,155 @@ +#include +#include +#include + +#include +#include + +#include "utils/fs.h" +#include "utils/utils.h" + +#include "../flashcart_utils.h" +#include "ed64_vseries.h" + +typedef enum { + ED64_V1_0 = 110, + ED64_V2_0 = 320, + ED64_V2_5 = 325, + ED64_V3_0 = 330, +} ed64_vseries_device_variant_t; + +/* ED64 save location base address */ +#define SRAM_ADDRESS (0xA8000000) +/* ED64 ROM location base address */ +#define ROM_ADDRESS (0xB0000000) + +static flashcart_err_t ed64_vseries_init (void) { + return FLASHCART_OK; +} + +static flashcart_err_t ed64_vseries_deinit (void) { + return FLASHCART_OK; +} + +static ed64_vseries_device_variant_t get_cart_model() { + ed64_vseries_device_variant_t variant = ED64_V1_0; // FIXME: check cart model from ll for better feature handling. + return variant; +} + +static bool ed64_vseries_has_feature (flashcart_features_t feature) { + bool is_model_v3 = (get_cart_model() == ED64_V3_0); + switch (feature) { + case FLASHCART_FEATURE_RTC: return is_model_v3 ? true : false; + case FLASHCART_FEATURE_USB: return is_model_v3 ? true : false; + case FLASHCART_FEATURE_AUTO_CIC: return is_model_v3 ? true : false; + default: return false; + } +} + +static flashcart_err_t ed64_vseries_load_rom (char *rom_path, flashcart_progress_callback_t *progress) { + FIL fil; + UINT br; + + if (f_open(&fil, strip_fs_prefix(rom_path), FA_READ) != FR_OK) { + return FLASHCART_ERR_LOAD; + } + + fatfs_fix_file_size(&fil); + + size_t rom_size = f_size(&fil); + + if (rom_size > MiB(64)) { + f_close(&fil); + return FLASHCART_ERR_LOAD; + } + + size_t sdram_size = MiB(64); + + size_t chunk_size = KiB(128); + for (int offset = 0; offset < sdram_size; offset += chunk_size) { + size_t block_size = MIN(sdram_size - offset, chunk_size); + if (f_read(&fil, (void *) (ROM_ADDRESS + offset), block_size, &br) != FR_OK) { + f_close(&fil); + return FLASHCART_ERR_LOAD; + } + if (progress) { + progress(f_tell(&fil) / (float) (f_size(&fil))); + } + } + if (f_tell(&fil) != rom_size) { + f_close(&fil); + return FLASHCART_ERR_LOAD; + } + + if (f_close(&fil) != FR_OK) { + return FLASHCART_ERR_LOAD; + } + + return FLASHCART_OK; +} + +static flashcart_err_t ed64_vseries_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset) { + FIL fil; + UINT br; + + if (f_open(&fil, strip_fs_prefix(file_path), FA_READ) != FR_OK) { + return FLASHCART_ERR_LOAD; + } + + fatfs_fix_file_size(&fil); + + size_t file_size = f_size(&fil) - file_offset; + + if (file_size > (MiB(64) - rom_offset)) { + f_close(&fil); + return FLASHCART_ERR_ARGS; + } + + if (f_lseek(&fil, file_offset) != FR_OK) { + f_close(&fil); + return FLASHCART_ERR_LOAD; + } + + if (f_read(&fil, (void *) (ROM_ADDRESS + rom_offset), file_size, &br) != FR_OK) { + f_close(&fil); + return FLASHCART_ERR_LOAD; + } + if (br != file_size) { + f_close(&fil); + return FLASHCART_ERR_LOAD; + } + + if (f_close(&fil) != FR_OK) { + return FLASHCART_ERR_LOAD; + } + + return FLASHCART_OK; +} + +static flashcart_err_t ed64_vseries_load_save (char *save_path) { + // FIXME: the savetype will be none. + return FLASHCART_OK; +} + +static flashcart_err_t ed64_vseries_set_save_type (flashcart_save_type_t save_type) { + // FIXME: the savetype will be none. + return FLASHCART_OK; +} + +static flashcart_t flashcart_ed64_vseries = { + .init = ed64_vseries_init, + .deinit = ed64_vseries_deinit, + .has_feature = ed64_vseries_has_feature, + .load_rom = ed64_vseries_load_rom, + .load_file = ed64_vseries_load_file, + .load_save = ed64_vseries_load_save, + .load_64dd_ipl = NULL, + .load_64dd_disk = NULL, + .set_save_type = ed64_vseries_set_save_type, + .set_save_writeback = NULL, +}; + + +flashcart_t *ed64_vseries_get_flashcart (void) { + return &flashcart_ed64_vseries; +} diff --git a/src/flashcart/ed64/ed64_vseries.h b/src/flashcart/ed64/ed64_vseries.h new file mode 100644 index 000000000..c96b5a0db --- /dev/null +++ b/src/flashcart/ed64/ed64_vseries.h @@ -0,0 +1,24 @@ +/** + * @file ed64_vseries.h + * @brief ED64 Vseries flashcart support + * @ingroup flashcart + */ + +#ifndef FLASHCART_ED64_VSERIES_H__ +#define FLASHCART_ED64_VSERIES_H__ + + +#include "../flashcart.h" + + +/** + * @addtogroup ED64_Vseries + * @{ + */ + +flashcart_t *ed64_vseries_get_flashcart (void); + +/** @} */ /* ED64_Vseries */ + + +#endif diff --git a/src/flashcart/ed64/ed64_xseries.h b/src/flashcart/ed64/ed64_xseries.h new file mode 100644 index 000000000..a6bf497cd --- /dev/null +++ b/src/flashcart/ed64/ed64_xseries.h @@ -0,0 +1,24 @@ +/** + * @file ed64xseries.h + * @brief ED64 Xseries flashcart support + * @ingroup flashcart + */ + +#ifndef FLASHCART_ED64XSERIES_H__ +#define FLASHCART_ED64XSERIES_H__ + + +#include "../flashcart.h" + + +/** + * @addtogroup ED64_Xseries + * @{ + */ + +flashcart_t *ed64xseries_get_flashcart (void); + +/** @} */ /* ED64_Xseries */ + + +#endif diff --git a/src/flashcart/flashcart.c b/src/flashcart/flashcart.c index 0cb55e841..942ebd50f 100644 --- a/src/flashcart/flashcart.c +++ b/src/flashcart/flashcart.c @@ -10,6 +10,7 @@ #include "flashcart.h" #include "flashcart_utils.h" +#include "ed64/ed64_vseries.h" #include "64drive/64drive.h" #include "sc64/sc64.h" @@ -108,10 +109,13 @@ flashcart_err_t flashcart_init (const char **storage_prefix) { flashcart = d64_get_flashcart(); break; - case CART_EDX: // Series X EverDrive-64 - break; + // FIXME: this is commented out awaiting a fix from libcart. + // case CART_EDX: // Series X EverDrive-64 + // flashcart = ed64_xseries_get_flashcart(); + // break; - case CART_ED: // Original EverDrive-64 + case CART_ED: // Series V EverDrive-64 or clone + flashcart = ed64_vseries_get_flashcart(); break; case CART_SC: // SummerCart64 From 866e63095238f3d8fc2d3ab991478476e2fdd3a6 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 7 Nov 2024 23:01:32 +0000 Subject: [PATCH 63/89] Improve RTC adjustment logic Handle events where RTC is unavailable. --- src/menu/views/rtc.c | 63 ++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index c93d7aa23..88196d00d 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -119,7 +119,7 @@ static void process (menu_t *menu) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; } - else if (menu->actions.enter && !is_editing_mode) { + else if (menu->actions.enter && !is_editing_mode && menu->current_time >= 0) { rtc_tm = *gmtime(&menu->current_time); is_editing_mode = true; } @@ -169,26 +169,49 @@ static void draw (menu_t *menu, surface_t *d) { component_layout_draw(); - component_main_text_draw( - ALIGN_CENTER, VALIGN_TOP, - "ADJUST REAL TIME CLOCK\n" - "\n" - "\n" - "To set the RTC date and time, Press A.\n" - "You can also use the PC terminal application via USB,\n" - "or even an N64 game with RTC support.\n" - "\n" - "Current date & time: %s\n" - "\n", - menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown" - ); - if (!is_editing_mode) { - component_actions_bar_text_draw( - ALIGN_LEFT, VALIGN_TOP, - "A: Change\n" - "B: Back" - ); + if( menu->current_time >= 0 ) { + + component_main_text_draw( + ALIGN_CENTER, VALIGN_TOP, + "ADJUST REAL TIME CLOCK\n" + "\n" + "\n" + "To set the RTC date and time, Press A.\n" + "You can also use the PC terminal application via USB,\n" + "or even an N64 game with RTC support.\n" + "\n" + "Current date & time: %s\n" + "\n", + menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown" + ); + + component_actions_bar_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "A: Change\n" + "B: Back" + ); + } + else { + + component_main_text_draw( + ALIGN_CENTER, VALIGN_TOP, + "ADJUST REAL TIME CLOCK\n" + "\n" + "\n" + "This cart does not support a real time clock." + "\n" + "Current date & time: %s\n" + "\n", + menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown" + ); + + component_actions_bar_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "\n" + "B: Back" + ); + } } else { component_actions_bar_text_draw( From bbf6c15b971dd1e927c474d1832a4a8373beacda Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 10 Nov 2024 00:48:55 +0000 Subject: [PATCH 64/89] Add joypad images (#156) ## Description Taken from https://github.com/n64brew/N64brew-GameJam2024-Template/ (MIT) Also improves the assets by adding them into sub-directories. ## Motivation and Context Will allow use in the menu to improve navigation/context. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [x] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- .gitignore | 1 + Makefile | 35 ++++++++++++++++++++++-- assets/{ => fonts}/FiraMonoBold.ttf | Bin assets/images/joypad/joypad_a.png | Bin 0 -> 653 bytes assets/images/joypad/joypad_b.png | Bin 0 -> 649 bytes assets/images/joypad/joypad_c_down.png | Bin 0 -> 666 bytes assets/images/joypad/joypad_c_left.png | Bin 0 -> 707 bytes assets/images/joypad/joypad_c_right.png | Bin 0 -> 715 bytes assets/images/joypad/joypad_c_up.png | Bin 0 -> 711 bytes assets/images/joypad/joypad_d_down.png | Bin 0 -> 419 bytes assets/images/joypad/joypad_d_left.png | Bin 0 -> 423 bytes assets/images/joypad/joypad_d_right.png | Bin 0 -> 419 bytes assets/images/joypad/joypad_d_up.png | Bin 0 -> 440 bytes assets/images/joypad/joypad_l.png | Bin 0 -> 376 bytes assets/images/joypad/joypad_r.png | Bin 0 -> 424 bytes assets/images/joypad/joypad_start.png | Bin 0 -> 672 bytes assets/images/joypad/joypad_z.png | Bin 0 -> 387 bytes assets/{ => sounds}/back.wav | Bin assets/{ => sounds}/cursorsound.wav | Bin assets/{ => sounds}/enter.wav | Bin assets/{ => sounds}/error.wav | Bin assets/{ => sounds}/settings.wav | Bin 22 files changed, 33 insertions(+), 3 deletions(-) rename assets/{ => fonts}/FiraMonoBold.ttf (100%) create mode 100644 assets/images/joypad/joypad_a.png create mode 100644 assets/images/joypad/joypad_b.png create mode 100644 assets/images/joypad/joypad_c_down.png create mode 100644 assets/images/joypad/joypad_c_left.png create mode 100644 assets/images/joypad/joypad_c_right.png create mode 100644 assets/images/joypad/joypad_c_up.png create mode 100644 assets/images/joypad/joypad_d_down.png create mode 100644 assets/images/joypad/joypad_d_left.png create mode 100644 assets/images/joypad/joypad_d_right.png create mode 100644 assets/images/joypad/joypad_d_up.png create mode 100644 assets/images/joypad/joypad_l.png create mode 100644 assets/images/joypad/joypad_r.png create mode 100644 assets/images/joypad/joypad_start.png create mode 100644 assets/images/joypad/joypad_z.png rename assets/{ => sounds}/back.wav (100%) rename assets/{ => sounds}/cursorsound.wav (100%) rename assets/{ => sounds}/enter.wav (100%) rename assets/{ => sounds}/error.wav (100%) rename assets/{ => sounds}/settings.wav (100%) diff --git a/.gitignore b/.gitignore index 9b9f42053..dcc08de71 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ # Ignore generated files in the libdragon FS /filesystem/FiraMonoBold.font64 /filesystem/*.wav64 +/filesystem/*.sprite # Ignore external development tools /tools/* diff --git a/Makefile b/Makefile index eac1f424a..d8cf4edfb 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,30 @@ SOUNDS = \ error.wav \ settings.wav +JOYPAD_IMAGES = \ + joypad_a.png \ + joypad_b.png \ + joypad_c_down.png \ + joypad_c_left.png \ + joypad_c_right.png \ + joypad_c_up.png \ + joypad_d_down.png \ + joypad_d_left.png \ + joypad_d_right.png \ + joypad_d_up.png \ + joypad_l.png \ + joypad_r.png \ + joypad_start.png \ + joypad_z.png +# joypad_j_east.png \ +# joypad_j_north.png \ +# joypad_j_northeast.png \ +# joypad_j_northwest.png \ +# joypad_j_south.png \ +# joypad_j_southeast.png \ +# joypad_j_southwest.png \ +# joypad_j_west.png \ + OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS)))) MINIZ_OBJS = $(filter $(BUILD_DIR)/libs/miniz/%.o,$(OBJS)) SPNG_OBJS = $(filter $(BUILD_DIR)/libs/libspng/%.o,$(OBJS)) @@ -92,7 +116,8 @@ DEPS = $(OBJS:.o=.d) FILESYSTEM = \ $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64))) \ - $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(SOUNDS:%.wav=%.wav64))) + $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(SOUNDS:%.wav=%.wav64))) \ + $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(JOYPAD_IMAGES:%.png=%.sprite))) $(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second @@ -101,14 +126,18 @@ $(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1 $(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null)) -$(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/%.ttf +$(FILESYSTEM_DIR)/%.font64: $(ASSETS_DIR)/fonts/%.ttf @echo " [FONT] $@" @$(N64_MKFONT) $(MKFONT_FLAGS) -o $(FILESYSTEM_DIR) "$<" -$(FILESYSTEM_DIR)/%.wav64: $(ASSETS_DIR)/%.wav +$(FILESYSTEM_DIR)/%.wav64: $(ASSETS_DIR)/sounds/%.wav @echo " [AUDIO] $@" @$(N64_AUDIOCONV) $(AUDIOCONV_FLAGS) -o $(FILESYSTEM_DIR) "$<" +$(FILESYSTEM_DIR)/%.sprite: $(ASSETS_DIR)/images/joypad/%.png + @echo " [SPRITE] $@" + @$(N64_MKSPRITE) $(MKSPRITE_FLAGS) -o $(dir $@) "$<" + $(BUILD_DIR)/$(PROJECT_NAME).dfs: $(FILESYSTEM) $(BUILD_DIR)/menu/views/credits.o: .FORCE diff --git a/assets/FiraMonoBold.ttf b/assets/fonts/FiraMonoBold.ttf similarity index 100% rename from assets/FiraMonoBold.ttf rename to assets/fonts/FiraMonoBold.ttf diff --git a/assets/images/joypad/joypad_a.png b/assets/images/joypad/joypad_a.png new file mode 100644 index 0000000000000000000000000000000000000000..9e6e6493a13daaa26db6f7e330dbd78ae4cc62db GIT binary patch literal 653 zcmV;80&@L{P)@y)rp$;Mubp*{q=+-boVW`By)VZpJ)TN)Gb3Xx6yS8gT z0P4V-#1bKC;-T~A4DNPV!h6W^f&j%`u__k(9fA6#&daapy8H0GPHzZrxeD zPxJ0J0H}(yI+sej4=w|bO6-igpybxd8qGd_UbwwjQMFS(jtBU7_yGd|ww=0AV%d%z ze6M9yqFb{>PYw%=a}aVBEC?swg6JkRVwO}zGwjgFe(hYC2Y%T3QGY30TL&rtu!gr>ukBSu;71dO{tKVf zUc47gZv{BBOf>Iq)2zFYyIFCFZvHp3->kMe?c-OX>8*?{(`c03TA3#7b&Z%MRWm4X n`r2{CKePU?4cXc{814T9ih=Y)U@uCn00000NkvXXu0mjfV)P$* literal 0 HcmV?d00001 diff --git a/assets/images/joypad/joypad_b.png b/assets/images/joypad/joypad_b.png new file mode 100644 index 0000000000000000000000000000000000000000..e5874cfff3bd9b22aeaeca708b1d544099bf19f3 GIT binary patch literal 649 zcmV;40(Sk0P)EmEuLgoa}NqN%VJ43W-R*>ZvCs@ z0f3TSYdu@e-4{!O$B~8_D5!ygj`duVOgXnf_6k*IKFU8^-dwIxPSO7spy#v^e(%F; zdf+ApOHL1!wnV1)@#iAtX}-o&LlWTxMmC9j^%huyBJRgCQ*gn-1g}t%WSP+mIf&77 zx&Q$7kCjk^WjKJbVRfXS)bE$)>|q!gn(9z_W6?|lxyk}SfUq0R)yD(nQR&GKvM)yu z(f#59F~AHuQ!E1s6-4h?bVpq2D1MS%$=-kl2n84?Fcwls-An<5Of_h}X}UkI{ijqP zx)+|^c^$aE&vN%3GwxF=WSp& zc7Ta8ndRcKS+(zEB1dZryElK6hROc4KF(Ors!sg{qn1`%Lf(O%CfE50s~T j`QB^G1AWxG9OeH4Nfh#H*|FL)00000NkvXXu0mjfw>}j^ literal 0 HcmV?d00001 diff --git a/assets/images/joypad/joypad_c_down.png b/assets/images/joypad/joypad_c_down.png new file mode 100644 index 0000000000000000000000000000000000000000..773e13914c0b1b7d50d9ec77a5c91e2195601855 GIT binary patch literal 666 zcmV;L0%iS)P)UMk%I5uh$*b8}; z`1sFMz}&+*0Dz`1#4W2`p^_wJt}d*+yoBzF1;*HoW>FMTep@#<}-H~ZppI0~7ApwBXlhZei z`ayjhqG>ubU7s*F4(j`7o&Gv7HcXLNUrUWpp@U?NL`2R;_#L5Rk*h1zaQ?e28D>p}XNVth_BllS-JnRn)8eBVc_UOphV zLp{=xB#M230LKA9t>(J6V;`1EmAPB@ofob3Ox<94fe9-SWjl6i@U*6lu!1188B9X7>Zvg=J{2`gSc;Vb|H~8St!t3`Npp^n-u?k80 zdo5M%#ZYYM%*rB}=Hwpfk)|aAtzgBbc=K)p*@G;?#70Q0H;if2Xo&=yNvD@Mk%-ZH z!#vN!$2AjQzV0E&mmy0Xk(xXNL4ZI=KgaPr2LSs&vUvO9GkB&1Me@4#qEkH2b1aUZ zu|Fc?>UbPsi9Hcu7^ZFS`2g#>t_uLcpooN~VQT6I;_>)@C8)Vom$e=HkW$(>lq3nd zp2Ebn2`Gx%5uv$s90!y-S+-oR%9D(NdS$yZ%lN*J z)hFs+Dy7Fy`bd;gY;A4g(nu0SB*^FUST~Gs*Y6b0vj9-cS;@6e>!m^=-_<}61dNT1 zH(?;{!PwbE;)G$^+3_-&%)XVgM%#v|RV#~RIuw!~6M;l|p67f3 pZnf$#7JVka}1WO7|-+oX-Ya2NM*&T~FEhj7l(uV>4l z*)U^^5k;0oNg@CcbOY|TJ+JkvHFxcnf3M%4>;qO8NJ34-cJxs_Vi*P}r2}B!_faa9 zP^tWInzd$XW}aIIfYk+(P?NErrkN9CF%7*QhbRgdrbeTVe7+DgYxc;@Jhub@P!q8o z)69vQ7Dvr$;L*w&HcQ(>0h$(vY34*Vsg?mioD46`rp8iN(&;e*0G#(w{G)^|L9`WA2AeL@iQ005LyC^A(9iH67kdhiDT2uXk%c2L>< zf`=<>_*Sk00HlyaB=F+_{}PK3OrAZ1lS%a;o7C-ecz<3(hLiwcbo4Z`*(?}i2exjf z!zHind%o{SjxNr=qR}WanJjc&AF%tr53lXJg41&5wzi6gjr=WuQi@C_i|Ogh$AST% zv{i)bx_1cY9BU7>J=4rFO^c)LwXs>;z{JFbW9z)Hdfmc$etqxK^+r?xfM(53<@1H0 z(WpaKWK2z6{7-Nlg~EDZTXq^adQ3^Rtm`^6jPt{fsaQm{Qr)vHJ3Vkr{d#_v-iR=^ xq{vi}LJ|Q8x}6Sp+rHm0Kh%vC7ZEtrAlS=(I8t7`h_as)l-2)S|gMCmDt^QN4$*?QU`V%2ieTl zK`xh{vkF#!--z$j0B2!st|xUXrD;*A8rt6d1>f@#zj?I=Di%%GNE?gRp>?ls#CJph zh_pr~8^fOGVSeE&=8X+_o>v2CT2xB)q;7{>!V>@>PL0!~9@Fmx!vOGUaSM`Af-IEq zabdHr9Zic$v6$XFJx064WCpz2IkUAt% zm9{qyY-V;Did3q-Rk@7W_bYWCLI|irJ49JJ`>Lw)e@qn3L&$-KWC=Jb9O8GrPzRAN z$(Kbm=G3lY%t>G#pd7B|M?W^xj0G^V~5p#*ocq^SONP-ATb3s5_>hrpIWP8d8U- tLOW#nY)3ffWyV;xT&avc9Aq0o|6k6t4$`cRcX9v#002ovPDHLkV1gs$K0yEg literal 0 HcmV?d00001 diff --git a/assets/images/joypad/joypad_d_down.png b/assets/images/joypad/joypad_d_down.png new file mode 100644 index 0000000000000000000000000000000000000000..4223ec3b6635002821e2a3ac4272e71b738580ea GIT binary patch literal 419 zcmV;U0bKrxP)0{F&LM&1UnxUawyH9eB!O-BW|~V3f&jDG z%z$0jHS7ZraQ4(R@C~a=E<5i^T%I?;G%?!Jw2kDa2#h477@1Vx;`~clizO_0HP>@hz!C_(?Dze!*yMM9FNCx=mjz} zEXy+TZQJ5-IQ&*s^-dy!)oS(TdEV!AI(>QeH$+6V)~B{@x4Yf$>kq0;VK#^u4$uGq N002ovPDHLkV1oC?vg`l= literal 0 HcmV?d00001 diff --git a/assets/images/joypad/joypad_d_left.png b/assets/images/joypad/joypad_d_left.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4676037387c3b74da86950f8a204e6ea581002 GIT binary patch literal 423 zcmV;Y0a*TtP)jV|m&?!rW2~DZHcf*p%V3Ow5CT%lp}-iiZCjkrXEaR% zDJ5>%{I)+3AR^>>j;gA_IfqhecD0^BUDwF63;;&0PT`)OWmyP<09xzC-D;KHx}Y#0c_hw9LEU55OWO`0EV(ZCT_P|Y&IJdMKQj@ z^?Lp6y2&m909LCNeBU4a9d3+yKAlb<#+X@=NtZ-~G)-g6vfh+ZA!c$J!xug1R#QB$ RSQr2R002ovPDHLkV1nh5r?~(C literal 0 HcmV?d00001 diff --git a/assets/images/joypad/joypad_d_right.png b/assets/images/joypad/joypad_d_right.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f3fd939451319905f58b8e579a785144aeea57 GIT binary patch literal 419 zcmV;U0bKrxP)V$dKR#kN@%kncxl8n~tbvF}$x~{R^ZhyK{98naZD2gdgH@X4<2q8d3$g&KZ%?4Ff zjp_yfBI>=F8Tb1gaU5g6-w*EdG(k_KX^>K)C<=sOh|A^j=_m93S2_5|ID^rPQ%$ny)lX(?1Cw7Y%|Sbcsn~I-P4! zK~y-6#Z$XZ#4r%twKs`lDfoy5DRNDm0x6}1#5I&iP|@&fl>7o6d`D7KrOgjmc_^l! zBsmTRiD`FcS39$d-h0>rwn_aL4d_&2Q4~8Ox)nk^20>79&f{Si-f+zWW6YJ5@{Wk^ ziD)mS)M=VtD5at(iU0thX_^lL06;`umgS9<@`;G9gCNL^F*~jGWs)So*z%@~v5=~& zI#_F8vMk$ctDaxbb zzVD%JTbQP4l|$b9d8xoThdj@}rmUne&+|;qx!1a`AH4U!G6E52=iK}1A)Si9Q=JmdoX}wf25I z9w$En42Q#)Uaxlq0LBt z3`(i@EX$IzEZ?lPk7=4-P9~EFX0EmZX0sWRBmohj-EPD8eE@*g8cL}&j^n8@=4|T_ zSF05~&jSGDdA`T0000OaVYn zl$xUW+Sht_kLa{K&u#%5jCoBd51V25Y_(pm`HnLNzA6fIk|c@K^d#1L^N1+F18gwn zMN+q+qXoVr-G(|HAV64TZ)|mBu{4ENY;Sh~5 ztaa10&8LNEG0rhNHHEA*LM*DmIw}G z5S)V%!Q~uTQ6L+QKC{=r^77o=@!I_S!%4pp08TD1lTS(Vg6|QX##(L5~TeYIY%NiiPG?7B=pzxVs^ z;w;-5D^poJj4cI9wWZ@M`kX7^`r2kRI{%~f?mp33dHx>>TMCI3kxJ?S0000Z{Bp?OUt%xgF{)Cmr}}CDdiOroxy{(_G?*|k6{=d>bm}PeB_IylrL3P-AO6& z6SUU1N~vc6_l_f9z&XDTH3LABB;cGEK6hZi7~{ci*FYqK2LHk74WdwCT~|!g1OQ0W z6pXRIC9u|_?|Zau3;(VBIsxY#S(d?C3!?C(yF2K*4tbvMNx}y6JVR>@A;j(oK?5Q} z6h&y72G-i3!NCr;Z8PpHgg{XgM-9H_D1kBNO=}&zZJuWsW8Q-a#&LX7N?pWpd_zR% he$9R7aU36g{u?J6XzyjDz4rhB002ovPDHLkV1n9sofZH9 literal 0 HcmV?d00001 diff --git a/assets/back.wav b/assets/sounds/back.wav similarity index 100% rename from assets/back.wav rename to assets/sounds/back.wav diff --git a/assets/cursorsound.wav b/assets/sounds/cursorsound.wav similarity index 100% rename from assets/cursorsound.wav rename to assets/sounds/cursorsound.wav diff --git a/assets/enter.wav b/assets/sounds/enter.wav similarity index 100% rename from assets/enter.wav rename to assets/sounds/enter.wav diff --git a/assets/error.wav b/assets/sounds/error.wav similarity index 100% rename from assets/error.wav rename to assets/sounds/error.wav diff --git a/assets/settings.wav b/assets/sounds/settings.wav similarity index 100% rename from assets/settings.wav rename to assets/sounds/settings.wav From 0d30a6f3b927059ef6c4e5a485e95db4e63f577d Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 11 Nov 2024 12:11:30 +0000 Subject: [PATCH 65/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 529501623..0c4e38885 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 5295016230d657cd6c7fce5b6ed4a342538e09f5 +Subproject commit 0c4e388851cabab52f421bff5e75e9dc3ab36c72 From 96506edbc7ef135c2cb01eb5ada8ab405490dc08 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 11 Nov 2024 17:29:39 +0000 Subject: [PATCH 66/89] Rename components to ui_components (#160) Renames `components` to `ui_components` Makes the source more clear as to its functionality - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- Makefile | 10 +-- src/menu/menu.c | 4 +- src/menu/{components.h => ui_components.h} | 64 +++++++++---------- .../background.c | 10 +-- .../{components => ui_components}/boxart.c | 10 +-- .../{components => ui_components}/common.c | 56 ++++++++-------- .../{components => ui_components}/constants.h | 0 .../context_menu.c | 16 ++--- .../{components => ui_components}/file_list.c | 10 +-- src/menu/views/browser.c | 28 ++++---- src/menu/views/credits.c | 10 +-- src/menu/views/error.c | 6 +- src/menu/views/fault.c | 2 +- src/menu/views/file_info.c | 10 +-- src/menu/views/flashcart_info.c | 10 +-- src/menu/views/image_viewer.c | 10 +-- src/menu/views/load_disk.c | 24 +++---- src/menu/views/load_emulator.c | 16 ++--- src/menu/views/load_rom.c | 34 +++++----- src/menu/views/music_player.c | 12 ++-- src/menu/views/rtc.c | 18 +++--- src/menu/views/settings_editor.c | 18 +++--- src/menu/views/system_info.c | 10 +-- src/menu/views/text_viewer.c | 12 ++-- src/menu/views/views.h | 2 +- 25 files changed, 198 insertions(+), 204 deletions(-) rename src/menu/{components.h => ui_components.h} (75%) rename src/menu/{components => ui_components}/background.c (95%) rename src/menu/{components => ui_components}/boxart.c (93%) rename src/menu/{components => ui_components}/common.c (70%) rename src/menu/{components => ui_components}/constants.h (100%) rename src/menu/{components => ui_components}/context_menu.c (87%) rename src/menu/{components => ui_components}/file_list.c (95%) diff --git a/Makefile b/Makefile index d8cf4edfb..052b3c44e 100644 --- a/Makefile +++ b/Makefile @@ -41,11 +41,6 @@ SRCS = \ libs/miniz/miniz.c \ menu/actions.c \ menu/cart_load.c \ - menu/components/background.c \ - menu/components/boxart.c \ - menu/components/common.c \ - menu/components/context_menu.c \ - menu/components/file_list.c \ menu/disk_info.c \ menu/fonts.c \ menu/hdmi.c \ @@ -56,6 +51,11 @@ SRCS = \ menu/rom_info.c \ menu/settings.c \ menu/sound.c \ + menu/ui_components/background.c \ + menu/ui_components/boxart.c \ + menu/ui_components/common.c \ + menu/ui_components/context_menu.c \ + menu/ui_components/file_list.c \ menu/usb_comm.c \ menu/views/browser.c \ menu/views/credits.c \ diff --git a/src/menu/menu.c b/src/menu/menu.c index 5e2b2168a..91a51a09f 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -87,7 +87,7 @@ static void menu_init (boot_params_t *boot_params) { directory_create(path_get(path)); path_push(path, BACKGROUND_CACHE_FILE); - component_background_init(path_get(path)); + ui_components_background_init(path_get(path)); path_free(path); @@ -103,7 +103,7 @@ static void menu_init (boot_params_t *boot_params) { static void menu_deinit (menu_t *menu) { hdmi_send_game_id(menu->boot_params); - component_background_free(); + ui_components_background_free(); path_free(menu->load.disk_path); path_free(menu->load.rom_path); diff --git a/src/menu/components.h b/src/menu/ui_components.h similarity index 75% rename from src/menu/components.h rename to src/menu/ui_components.h index f15a6ba52..f0dc6060b 100644 --- a/src/menu/components.h +++ b/src/menu/ui_components.h @@ -1,24 +1,20 @@ /** - * @file components.h + * @file ui_components.h * @brief Menu Graphical User Interface Components * @ingroup menu */ -#ifndef COMPONENTS_H__ -#define COMPONENTS_H__ +#ifndef UI_COMPONENTS_H__ +#define UI_COMPONENTS_H__ #include #include "menu_state.h" -/** - * @addtogroup menu_ui_components - * @{ - */ /** * @brief File image Enumeration. * - * Enumeration for different types of file images used in the GUI. + * Enumeration for different types of file images used in the user interface. */ typedef enum { IMAGE_BOXART_FRONT, /**< Boxart image from the front */ @@ -42,7 +38,7 @@ typedef enum { * @param y1 Ending y-coordinate. * @param color Color of the box. */ -void component_box_draw(int x0, int y0, int x1, int y1, color_t color); +void ui_components_box_draw(int x0, int y0, int x1, int y1, color_t color); /** * @brief Draw a border component. @@ -52,12 +48,12 @@ void component_box_draw(int x0, int y0, int x1, int y1, color_t color); * @param x1 Ending x-coordinate. * @param y1 Ending y-coordinate. */ -void component_border_draw(int x0, int y0, int x1, int y1); +void ui_components_border_draw(int x0, int y0, int x1, int y1); /** * @brief Draw the layout component. */ -void component_layout_draw(void); +void ui_components_layout_draw(void); /** * @brief Draw a progress bar component. @@ -68,21 +64,21 @@ void component_layout_draw(void); * @param y1 Ending y-coordinate. * @param progress Progress value (0.0 to 1.0). */ -void component_progressbar_draw(int x0, int y0, int x1, int y1, float progress); +void ui_components_progressbar_draw(int x0, int y0, int x1, int y1, float progress); /** * @brief Draw a seek bar component. * * @param progress Progress value (0.0 to 1.0). */ -void component_seekbar_draw(float progress); +void ui_components_seekbar_draw(float progress); /** * @brief Draw a loader component. * * @param position Position value (0.0 to 1.0). */ -void component_loader_draw(float position); +void ui_components_loader_draw(float position); /** * @brief Draw a scrollbar component. @@ -95,7 +91,7 @@ void component_loader_draw(float position); * @param items Total number of items. * @param visible_items Number of visible items. */ -void component_scrollbar_draw(int x, int y, int width, int height, int position, int items, int visible_items); +void ui_components_scrollbar_draw(int x, int y, int width, int height, int position, int items, int visible_items); /** * @brief Draw a list scrollbar component. @@ -104,7 +100,7 @@ void component_scrollbar_draw(int x, int y, int width, int height, int position, * @param items Total number of items. * @param visible_items Number of visible items. */ -void component_list_scrollbar_draw(int position, int items, int visible_items); +void ui_components_list_scrollbar_draw(int position, int items, int visible_items); /** * @brief Draw a dialog component. @@ -112,7 +108,7 @@ void component_list_scrollbar_draw(int position, int items, int visible_items); * @param width Width of the dialog. * @param height Height of the dialog. */ -void component_dialog_draw(int width, int height); +void ui_components_dialog_draw(int width, int height); /** * @brief Draw a message box component. @@ -120,7 +116,7 @@ void component_dialog_draw(int width, int height); * @param fmt Format string for the message. * @param ... Additional arguments for the format string. */ -void component_messagebox_draw(char *fmt, ...); +void ui_components_messagebox_draw(char *fmt, ...); /** * @brief Draw the main text component. @@ -130,7 +126,7 @@ void component_messagebox_draw(char *fmt, ...); * @param fmt Format string for the text. * @param ... Additional arguments for the format string. */ -void component_main_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); +void ui_components_main_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); /** * @brief Draw the actions bar text component. @@ -140,31 +136,31 @@ void component_main_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fm * @param fmt Format string for the text. * @param ... Additional arguments for the format string. */ -void component_actions_bar_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); +void ui_components_actions_bar_text_draw(rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...); /** * @brief Initialize the background component. * * @param cache_location Location of the cache. */ -void component_background_init(char *cache_location); +void ui_components_background_init(char *cache_location); /** * @brief Free the background component resources. */ -void component_background_free(void); +void ui_components_background_free(void); /** * @brief Replace the background image. * * @param image New background image. */ -void component_background_replace_image(surface_t *image); +void ui_components_background_replace_image(surface_t *image); /** * @brief Draw the background component. */ -void component_background_draw(void); +void ui_components_background_draw(void); /** * @brief Draw the file list component. @@ -173,7 +169,7 @@ void component_background_draw(void); * @param entries Number of entries. * @param selected Index of the selected entry. */ -void component_file_list_draw(entry_t *list, int entries, int selected); +void ui_components_file_list_draw(entry_t *list, int entries, int selected); /** * @brief Context menu structure. @@ -199,14 +195,14 @@ typedef struct component_context_menu { * * @param cm Pointer to the context menu structure. */ -void component_context_menu_init(component_context_menu_t *cm); +void ui_components_context_menu_init(component_context_menu_t *cm); /** * @brief Show the context menu component. * * @param cm Pointer to the context menu structure. */ -void component_context_menu_show(component_context_menu_t *cm); +void ui_components_context_menu_show(component_context_menu_t *cm); /** * @brief Process the context menu component. @@ -215,14 +211,14 @@ void component_context_menu_show(component_context_menu_t *cm); * @param cm Pointer to the context menu structure. * @return True if the context menu was processed, false otherwise. */ -bool component_context_menu_process(menu_t *menu, component_context_menu_t *cm); +bool ui_components_context_menu_process(menu_t *menu, component_context_menu_t *cm); /** * @brief Draw the context menu component. * * @param cm Pointer to the context menu structure. */ -void component_context_menu_draw(component_context_menu_t *cm); +void ui_components_context_menu_draw(component_context_menu_t *cm); /** * @brief Box Art Structure. @@ -240,22 +236,20 @@ typedef struct { * @param current_image_view Current image view type. * @return Pointer to the initialized box art component. */ -component_boxart_t *component_boxart_init(const char *storage_prefix, char *game_code, file_image_type_t current_image_view); +component_boxart_t *ui_components_boxart_init(const char *storage_prefix, char *game_code, file_image_type_t current_image_view); /** * @brief Free the box art component resources. * * @param b Pointer to the box art component. */ -void component_boxart_free(component_boxart_t *b); +void ui_components_boxart_free(component_boxart_t *b); /** * @brief Draw the box art component. * * @param b Pointer to the box art component. */ -void component_boxart_draw(component_boxart_t *b); - -/** @} */ /* menu_ui_components */ +void ui_components_boxart_draw(component_boxart_t *b); -#endif /* COMPONENTS_H__ */ +#endif /* UI_COMPONENTS_H__ */ diff --git a/src/menu/components/background.c b/src/menu/ui_components/background.c similarity index 95% rename from src/menu/components/background.c rename to src/menu/ui_components/background.c index 525fb0c8f..548bc0f0c 100644 --- a/src/menu/components/background.c +++ b/src/menu/ui_components/background.c @@ -1,7 +1,7 @@ #include #include -#include "../components.h" +#include "../ui_components.h" #include "constants.h" #include "utils/fs.h" @@ -157,7 +157,7 @@ static void display_list_free (void *arg) { } -void component_background_init (char *cache_location) { +void ui_components_background_init (char *cache_location) { if (!background) { background = calloc(1, sizeof(component_background_t)); background->cache_location = strdup(cache_location); @@ -166,7 +166,7 @@ void component_background_init (char *cache_location) { } } -void component_background_free (void) { +void ui_components_background_free (void) { if (background) { if (background->image) { surface_free(background->image); @@ -185,7 +185,7 @@ void component_background_free (void) { } } -void component_background_replace_image (surface_t *image) { +void ui_components_background_replace_image (surface_t *image) { if (!background) { return; } @@ -206,7 +206,7 @@ void component_background_replace_image (surface_t *image) { prepare_background(background); } -void component_background_draw (void) { +void ui_components_background_draw (void) { if (background && background->image_display_list) { rspq_block_run(background->image_display_list); } else { diff --git a/src/menu/components/boxart.c b/src/menu/ui_components/boxart.c similarity index 93% rename from src/menu/components/boxart.c rename to src/menu/ui_components/boxart.c index d663230a1..160705238 100644 --- a/src/menu/components/boxart.c +++ b/src/menu/ui_components/boxart.c @@ -1,6 +1,6 @@ #include -#include "../components.h" +#include "../ui_components.h" #include "../path.h" #include "../png_decoder.h" #include "constants.h" @@ -17,7 +17,7 @@ static void png_decoder_callback (png_err_t err, surface_t *decoded_image, void } -component_boxart_t *component_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view) { +component_boxart_t *ui_components_boxart_init (const char *storage_prefix, char *game_code, file_image_type_t current_image_view) { component_boxart_t *b; char boxart_id_path[8]; @@ -120,7 +120,7 @@ component_boxart_t *component_boxart_init (const char *storage_prefix, char *gam return NULL; } -void component_boxart_free (component_boxart_t *b) { +void ui_components_boxart_free (component_boxart_t *b) { if (b) { if (b->loading) { png_decoder_abort(); @@ -133,7 +133,7 @@ void component_boxart_free (component_boxart_t *b) { } } -void component_boxart_draw (component_boxart_t *b) { +void ui_components_boxart_draw (component_boxart_t *b) { int box_x = BOXART_X; int box_y = BOXART_Y; @@ -150,7 +150,7 @@ void component_boxart_draw (component_boxart_t *b) { rdpq_tex_blit(b->image, box_x, box_y, NULL); rdpq_mode_pop(); } else { - component_box_draw( + ui_components_box_draw( BOXART_X, BOXART_Y, BOXART_X + BOXART_WIDTH, diff --git a/src/menu/components/common.c b/src/menu/ui_components/common.c similarity index 70% rename from src/menu/components/common.c rename to src/menu/ui_components/common.c index 29a97ada6..dec318852 100644 --- a/src/menu/components/common.c +++ b/src/menu/ui_components/common.c @@ -1,11 +1,11 @@ #include -#include "../components.h" +#include "../ui_components.h" #include "../fonts.h" #include "constants.h" -void component_box_draw (int x0, int y0, int x1, int y1, color_t color) { +void ui_components_box_draw (int x0, int y0, int x1, int y1, color_t color) { rdpq_mode_push(); rdpq_set_mode_fill(color); @@ -13,7 +13,7 @@ void component_box_draw (int x0, int y0, int x1, int y1, color_t color) { rdpq_mode_pop(); } -void component_border_draw (int x0, int y0, int x1, int y1) { +void ui_components_border_draw (int x0, int y0, int x1, int y1) { rdpq_mode_push(); rdpq_set_mode_fill(BORDER_COLOR); @@ -25,14 +25,14 @@ void component_border_draw (int x0, int y0, int x1, int y1) { rdpq_mode_pop(); } -void component_layout_draw (void) { - component_border_draw( +void ui_components_layout_draw (void) { + ui_components_border_draw( VISIBLE_AREA_X0, VISIBLE_AREA_Y0, VISIBLE_AREA_X1, VISIBLE_AREA_Y1 ); - component_box_draw( + ui_components_box_draw( VISIBLE_AREA_X0, LAYOUT_ACTIONS_SEPARATOR_Y, VISIBLE_AREA_X1, @@ -41,47 +41,47 @@ void component_layout_draw (void) { ); } -void component_progressbar_draw (int x0, int y0, int x1, int y1, float progress) { +void ui_components_progressbar_draw (int x0, int y0, int x1, int y1, float progress) { float progress_width = progress * (x1 - x0); - component_box_draw(x0, y0, x0 + progress_width, y1, PROGRESSBAR_DONE_COLOR); - component_box_draw(x0 + progress_width, y0, x1, y1, PROGRESSBAR_BG_COLOR); + ui_components_box_draw(x0, y0, x0 + progress_width, y1, PROGRESSBAR_DONE_COLOR); + ui_components_box_draw(x0 + progress_width, y0, x1, y1, PROGRESSBAR_BG_COLOR); } -void component_seekbar_draw (float position) { +void ui_components_seekbar_draw (float position) { int x0 = SEEKBAR_X; int y0 = SEEKBAR_Y; int x1 = SEEKBAR_X + SEEKBAR_WIDTH; int y1 = SEEKBAR_Y + SEEKBAR_HEIGHT; - component_border_draw(x0, y0, x1, y1); - component_progressbar_draw(x0, y0, x1, y1, position); + ui_components_border_draw(x0, y0, x1, y1); + ui_components_progressbar_draw(x0, y0, x1, y1, position); } -void component_loader_draw (float progress) { +void ui_components_loader_draw (float progress) { int x0 = LOADER_X; int y0 = LOADER_Y; int x1 = LOADER_X + LOADER_WIDTH; int y1 = LOADER_Y + LOADER_HEIGHT; - component_border_draw(x0, y0, x1, y1); - component_progressbar_draw(x0, y0, x1, y1, progress); + ui_components_border_draw(x0, y0, x1, y1); + ui_components_progressbar_draw(x0, y0, x1, y1, progress); } -void component_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items) { +void ui_components_scrollbar_draw (int x, int y, int width, int height, int position, int items, int visible_items) { if (items <= 1 || items <= visible_items) { - component_box_draw(x, y, x + width, y + height, SCROLLBAR_INACTIVE_COLOR); + ui_components_box_draw(x, y, x + width, y + height, SCROLLBAR_INACTIVE_COLOR); } else { int scroll_height = (int) ((visible_items / (float) (items)) * height); float scroll_position = ((position / (float) (items - 1)) * (height - scroll_height)); - component_box_draw(x, y, x + width, y + height, SCROLLBAR_BG_COLOR); - component_box_draw(x, y + scroll_position, x + width, y + scroll_position + scroll_height, SCROLLBAR_POSITION_COLOR); + ui_components_box_draw(x, y, x + width, y + height, SCROLLBAR_BG_COLOR); + ui_components_box_draw(x, y + scroll_position, x + width, y + scroll_position + scroll_height, SCROLLBAR_POSITION_COLOR); } } -void component_list_scrollbar_draw (int position, int items, int visible_items) { - component_scrollbar_draw( +void ui_components_list_scrollbar_draw (int position, int items, int visible_items) { + ui_components_scrollbar_draw( LIST_SCROLLBAR_X, LIST_SCROLLBAR_Y, LIST_SCROLLBAR_WIDTH, @@ -92,17 +92,17 @@ void component_list_scrollbar_draw (int position, int items, int visible_items) ); } -void component_dialog_draw (int width, int height) { +void ui_components_dialog_draw (int width, int height) { int x0 = DISPLAY_CENTER_X - (width / 2); int y0 = DISPLAY_CENTER_Y - (height / 2); int x1 = DISPLAY_CENTER_X + (width / 2); int y1 = DISPLAY_CENTER_Y + (height / 2); - component_border_draw(x0, y0, x1, y1); - component_box_draw(x0, y0, x1, y1, DIALOG_BG_COLOR); + ui_components_border_draw(x0, y0, x1, y1); + ui_components_box_draw(x0, y0, x1, y1, DIALOG_BG_COLOR); } -void component_messagebox_draw (char *fmt, ...) { +void ui_components_messagebox_draw (char *fmt, ...) { char buffer[512]; size_t nbytes = sizeof(buffer); @@ -126,7 +126,7 @@ void component_messagebox_draw (char *fmt, ...) { free(formatted); } - component_dialog_draw( + ui_components_dialog_draw( paragraph->bbox.x1 - paragraph->bbox.x0 + MESSAGEBOX_MARGIN, paragraph->bbox.y1 - paragraph->bbox.y0 + MESSAGEBOX_MARGIN ); @@ -136,7 +136,7 @@ void component_messagebox_draw (char *fmt, ...) { rdpq_paragraph_free(paragraph); } -void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) { +void ui_components_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) { char buffer[1024]; size_t nbytes = sizeof(buffer); @@ -166,7 +166,7 @@ void component_main_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *f } } -void component_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) { +void ui_components_actions_bar_text_draw (rdpq_align_t align, rdpq_valign_t valign, char *fmt, ...) { char buffer[256]; size_t nbytes = sizeof(buffer); diff --git a/src/menu/components/constants.h b/src/menu/ui_components/constants.h similarity index 100% rename from src/menu/components/constants.h rename to src/menu/ui_components/constants.h diff --git a/src/menu/components/context_menu.c b/src/menu/ui_components/context_menu.c similarity index 87% rename from src/menu/components/context_menu.c rename to src/menu/ui_components/context_menu.c index abf1947e7..b08085fcf 100644 --- a/src/menu/components/context_menu.c +++ b/src/menu/ui_components/context_menu.c @@ -1,4 +1,4 @@ -#include "../components.h" +#include "../ui_components.h" #include "../fonts.h" #include "../sound.h" #include "constants.h" @@ -12,7 +12,7 @@ static component_context_menu_t *get_current_submenu (component_context_menu_t * } -void component_context_menu_init (component_context_menu_t *cm) { +void ui_components_context_menu_init (component_context_menu_t *cm) { cm->row_selected = -1; cm->row_count = 0; cm->hide_pending = false; @@ -22,12 +22,12 @@ void component_context_menu_init (component_context_menu_t *cm) { } } -void component_context_menu_show (component_context_menu_t *cm) { +void ui_components_context_menu_show (component_context_menu_t *cm) { cm->row_selected = 0; cm->submenu = NULL; } -bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) { +bool ui_components_context_menu_process (menu_t *menu, component_context_menu_t *cm) { if (!cm || (cm->row_selected < 0)) { return false; } @@ -46,7 +46,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) } else if (menu->actions.enter) { if (cm->list[cm->row_selected].submenu) { cm->submenu = cm->list[cm->row_selected].submenu; - component_context_menu_init(cm->submenu); + ui_components_context_menu_init(cm->submenu); cm->submenu->row_selected = 0; cm->submenu->parent = cm; } else if (cm->list[cm->row_selected].action) { @@ -71,7 +71,7 @@ bool component_context_menu_process (menu_t *menu, component_context_menu_t *cm) return true; } -void component_context_menu_draw (component_context_menu_t *cm) { +void ui_components_context_menu_draw (component_context_menu_t *cm) { if (!cm || (cm->row_selected < 0)) { return; } @@ -105,14 +105,14 @@ void component_context_menu_draw (component_context_menu_t *cm) { int width = layout->bbox.x1 - layout->bbox.x0 + MESSAGEBOX_MARGIN; int height = layout->bbox.y1 - layout->bbox.y0 + MESSAGEBOX_MARGIN; - component_dialog_draw(width, height); + ui_components_dialog_draw(width, height); int highlight_x0 = DISPLAY_CENTER_X - (width / 2); int highlight_x1 = DISPLAY_CENTER_X + (width / 2); int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines; int highlight_y = VISIBLE_AREA_Y0 + layout->bbox.y0 + ((cm->row_selected) * highlight_height); - component_box_draw( + ui_components_box_draw( highlight_x0, highlight_y, highlight_x1, diff --git a/src/menu/components/file_list.c b/src/menu/ui_components/file_list.c similarity index 95% rename from src/menu/components/file_list.c rename to src/menu/ui_components/file_list.c index 7a9835ce2..af10e3a09 100644 --- a/src/menu/components/file_list.c +++ b/src/menu/ui_components/file_list.c @@ -1,6 +1,6 @@ #include -#include "../components.h" +#include "../ui_components.h" #include "../fonts.h" #include "constants.h" @@ -25,7 +25,7 @@ static int format_file_size (char *buffer, int64_t size) { } -void component_file_list_draw (entry_t *list, int entries, int selected) { +void ui_components_file_list_draw (entry_t *list, int entries, int selected) { int starting_position = 0; if (entries > LIST_ENTRIES && selected >= (LIST_ENTRIES / 2)) { @@ -35,10 +35,10 @@ void component_file_list_draw (entry_t *list, int entries, int selected) { } } - component_list_scrollbar_draw(selected, entries, LIST_ENTRIES); + ui_components_list_scrollbar_draw(selected, entries, LIST_ENTRIES); if (entries == 0) { - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "^%02X** empty directory **", STL_GRAY @@ -117,7 +117,7 @@ void component_file_list_draw (entry_t *list, int entries, int selected) { int highlight_height = (layout->bbox.y1 - layout->bbox.y0) / layout->nlines; int highlight_y = VISIBLE_AREA_Y0 + TEXT_MARGIN_VERTICAL + TEXT_OFFSET_VERTICAL + ((selected - starting_position) * highlight_height); - component_box_draw( + ui_components_box_draw( FILE_LIST_HIGHLIGHT_X, highlight_y, FILE_LIST_HIGHLIGHT_X + FILE_LIST_HIGHLIGHT_WIDTH, diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index 2ff3c8670..abe86b598 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -288,11 +288,11 @@ static component_context_menu_t settings_context_menu = { }; static void process (menu_t *menu) { - if (component_context_menu_process(menu, &entry_context_menu)) { + if (ui_components_context_menu_process(menu, &entry_context_menu)) { return; } - if (component_context_menu_process(menu, &settings_context_menu)) { + if (ui_components_context_menu_process(menu, &settings_context_menu)) { return; } @@ -353,10 +353,10 @@ static void process (menu_t *menu) { } sound_play_effect(SFX_EXIT); } else if (menu->actions.options && menu->browser.entry) { - component_context_menu_show(&entry_context_menu); + ui_components_context_menu_show(&entry_context_menu); sound_play_effect(SFX_SETTING); } else if (menu->actions.settings) { - component_context_menu_show(&settings_context_menu); + ui_components_context_menu_show(&settings_context_menu); sound_play_effect(SFX_SETTING); } } @@ -365,11 +365,11 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_file_list_draw(menu->browser.list, menu->browser.entries, menu->browser.selected); + ui_components_file_list_draw(menu->browser.list, menu->browser.entries, menu->browser.selected); const char *action = NULL; @@ -385,7 +385,7 @@ static void draw (menu_t *menu, surface_t *d) { } } - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "%s\n" "^%02XB: Back^00", @@ -393,7 +393,7 @@ static void draw (menu_t *menu, surface_t *d) { path_is_root(menu->browser.directory) ? STL_GRAY : STL_DEFAULT ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, "Start: Settings\n" "^%02XR: Options^00", @@ -401,7 +401,7 @@ static void draw (menu_t *menu, surface_t *d) { ); if (menu->current_time >= 0) { - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_CENTER, VALIGN_TOP, "\n" "%s", @@ -409,9 +409,9 @@ static void draw (menu_t *menu, surface_t *d) { ); } - component_context_menu_draw(&entry_context_menu); + ui_components_context_menu_draw(&entry_context_menu); - component_context_menu_draw(&settings_context_menu); + ui_components_context_menu_draw(&settings_context_menu); rdpq_detach_show(); } @@ -419,8 +419,8 @@ static void draw (menu_t *menu, surface_t *d) { void view_browser_init (menu_t *menu) { if (!menu->browser.valid) { - component_context_menu_init(&entry_context_menu); - component_context_menu_init(&settings_context_menu); + ui_components_context_menu_init(&entry_context_menu); + ui_components_context_menu_init(&settings_context_menu); if (load_directory(menu)) { path_free(menu->browser.directory); menu->browser.directory = path_init(menu->storage_prefix, ""); diff --git a/src/menu/views/credits.c b/src/menu/views/credits.c index 1889232ad..3595b30c4 100644 --- a/src/menu/views/credits.c +++ b/src/menu/views/credits.c @@ -20,16 +20,16 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "MENU INFORMATION" ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -54,7 +54,7 @@ static void draw (menu_t *menu, surface_t *d) { BUILD_TIMESTAMP ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "B: Exit" diff --git a/src/menu/views/error.c b/src/menu/views/error.c index 7cb4f7d29..b163f8856 100644 --- a/src/menu/views/error.c +++ b/src/menu/views/error.c @@ -12,12 +12,12 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); if (menu->error_message) { - component_messagebox_draw(menu->error_message); + ui_components_messagebox_draw(menu->error_message); } else { - component_messagebox_draw("Unspecified error"); + ui_components_messagebox_draw("Unspecified error"); } rdpq_detach_show(); diff --git a/src/menu/views/fault.c b/src/menu/views/fault.c index 6ec767a4e..ee2f8c5eb 100644 --- a/src/menu/views/fault.c +++ b/src/menu/views/fault.c @@ -13,7 +13,7 @@ static void draw (menu_t *menu, surface_t *d) { "SummerCart64: 2.17.0+" ); - component_messagebox_draw( + ui_components_messagebox_draw( "UNRECOVERABLE ERROR\n" "\n" "%s\n" diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index 9d45574be..be204e5f1 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -58,11 +58,11 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "ENTRY INFORMATION\n" "\n" @@ -70,7 +70,7 @@ static void draw (menu_t *menu, surface_t *d) { menu->browser.entry->name ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -87,7 +87,7 @@ static void draw (menu_t *menu, surface_t *d) { ctime(&st.st_mtime) ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "B: Exit" diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 635ef2cae..abf99991d 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -36,18 +36,18 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "FLASHCART INFORMATION" "\n" "\n" ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -78,7 +78,7 @@ static void draw (menu_t *menu, surface_t *d) { //format_diagnostic_data(flashcart_has_feature(FLASHCART_FEATURE_DIAGNOSTIC_DATA)) ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "B: Back" diff --git a/src/menu/views/image_viewer.c b/src/menu/views/image_viewer.c index d58d3743e..64af16f6b 100644 --- a/src/menu/views/image_viewer.c +++ b/src/menu/views/image_viewer.c @@ -58,9 +58,9 @@ static void draw (menu_t *menu, surface_t *d) { if (!image) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_loader_draw(png_decoder_get_progress()); + ui_components_loader_draw(png_decoder_get_progress()); } else { rdpq_attach_clear(d, NULL); @@ -73,13 +73,13 @@ static void draw (menu_t *menu, surface_t *d) { rdpq_mode_pop(); if (show_message) { - component_messagebox_draw( + ui_components_messagebox_draw( "Set \"%s\" as background image?\n\n" "A: Yes, B: Back", menu->browser.entry->name ); } else if (image_set_as_background) { - component_messagebox_draw("Preparing background…"); + ui_components_messagebox_draw("Preparing background…"); } } @@ -93,7 +93,7 @@ static void deinit (menu_t *menu) { if (image) { if (image_set_as_background) { - component_background_replace_image(image); + ui_components_background_replace_image(image); } else { surface_free(image); free(image); diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index 9ba914d90..c1e24a792 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -45,14 +45,14 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); if (menu->boot_pending.disk_file) { - component_loader_draw(0.0f); + ui_components_loader_draw(0.0f); } else { - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "64DD disk information\n" "\n" @@ -60,7 +60,7 @@ static void draw (menu_t *menu, surface_t *d) { menu->browser.entry->name ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -80,21 +80,21 @@ static void draw (menu_t *menu, surface_t *d) { menu->load.rom_path ? path_last_get(menu->load.rom_path) : "" ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "A: Load and run 64DD disk\n" "B: Exit" ); if (menu->load.rom_path) { - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, "R: Load with ROM" ); } if (boxart != NULL) { - component_boxart_draw(boxart); + ui_components_boxart_draw(boxart); } } @@ -107,9 +107,9 @@ static void draw_progress (float progress) { if (d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_loader_draw(progress); + ui_components_loader_draw(progress); rdpq_detach_show(); } @@ -153,7 +153,7 @@ static void load (menu_t *menu) { } static void deinit (void) { - component_boxart_free(boxart); + ui_components_boxart_free(boxart); } void view_load_disk_init (menu_t *menu) { @@ -172,7 +172,7 @@ void view_load_disk_init (menu_t *menu) { return; } - boxart = component_boxart_init(menu->storage_prefix, menu->load.disk_info.id, IMAGE_BOXART_FRONT); + boxart = ui_components_boxart_init(menu->storage_prefix, menu->load.disk_info.id, IMAGE_BOXART_FRONT); } void view_load_disk_display (menu_t *menu, surface_t *display) { diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index 3a1317227..ad9231454 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -43,19 +43,19 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); if (menu->boot_pending.emulator_file) { - component_loader_draw(0.0f); + ui_components_loader_draw(0.0f); } else { - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "Load Emulated ROM\n" ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -65,7 +65,7 @@ static void draw (menu_t *menu, surface_t *d) { menu->browser.entry->name ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "A: Load and run Emulated ROM\n" "B: Exit" @@ -81,9 +81,9 @@ static void draw_progress (float progress) { if (d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_loader_draw(progress); + ui_components_loader_draw(progress); rdpq_detach_show(); } diff --git a/src/menu/views/load_rom.c b/src/menu/views/load_rom.c index 2a4ce34d0..fb95003fe 100644 --- a/src/menu/views/load_rom.c +++ b/src/menu/views/load_rom.c @@ -203,7 +203,7 @@ static component_context_menu_t options_context_menu = { .list = { }}; static void process (menu_t *menu) { - if (component_context_menu_process(menu, &options_context_menu)) { + if (ui_components_context_menu_process(menu, &options_context_menu)) { return; } @@ -213,7 +213,7 @@ static void process (menu_t *menu) { sound_play_effect(SFX_EXIT); menu->next_mode = MENU_MODE_BROWSER; } else if (menu->actions.options) { - component_context_menu_show(&options_context_menu); + ui_components_context_menu_show(&options_context_menu); sound_play_effect(SFX_SETTING); } else if (menu->actions.lz_context) { if (show_extra_info_message) { @@ -228,14 +228,14 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); if (menu->boot_pending.rom_file) { - component_loader_draw(0.0f); + ui_components_loader_draw(0.0f); } else { - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "N64 ROM information\n" "\n" @@ -243,7 +243,7 @@ static void draw (menu_t *menu, surface_t *d) { menu->browser.entry->name ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -262,24 +262,24 @@ static void draw (menu_t *menu, surface_t *d) { format_rom_save_type(rom_info_get_save_type(&menu->load.rom_info), menu->load.rom_info.features.controller_pak) ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "A: Load and run ROM\n" "B: Back" ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, "L|Z: Extra Info\n" "R: Options" ); if (boxart != NULL) { - component_boxart_draw(boxart); + ui_components_boxart_draw(boxart); } if (show_extra_info_message) { - component_messagebox_draw( + ui_components_messagebox_draw( "EXTRA ROM INFO\n" "\n" "Endianness: %s\n" @@ -306,7 +306,7 @@ static void draw (menu_t *menu, surface_t *d) { ); } - component_context_menu_draw(&options_context_menu); + ui_components_context_menu_draw(&options_context_menu); } rdpq_detach_show(); @@ -318,9 +318,9 @@ static void draw_progress (float progress) { if (d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_loader_draw(progress); + ui_components_loader_draw(progress); rdpq_detach_show(); } @@ -348,7 +348,7 @@ static void load (menu_t *menu) { } static void deinit (void) { - component_boxart_free(boxart); + ui_components_boxart_free(boxart); boxart = NULL; } @@ -371,8 +371,8 @@ void view_load_rom_init (menu_t *menu) { } if (!menu->settings.rom_autoload_enabled) { - boxart = component_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code, IMAGE_BOXART_FRONT); - component_context_menu_init(&options_context_menu); + boxart = ui_components_boxart_init(menu->storage_prefix, menu->load.rom_info.game_code, IMAGE_BOXART_FRONT); + ui_components_context_menu_init(&options_context_menu); } } diff --git a/src/menu/views/music_player.c b/src/menu/views/music_player.c index c540bfabc..b2dde5975 100644 --- a/src/menu/views/music_player.c +++ b/src/menu/views/music_player.c @@ -61,13 +61,13 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_seekbar_draw(mp3player_get_progress()); + ui_components_seekbar_draw(mp3player_get_progress()); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "MUSIC PLAYER\n" "\n" @@ -83,7 +83,7 @@ static void draw (menu_t *menu, surface_t *d) { mp3player_get_duration() ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -102,7 +102,7 @@ static void draw (menu_t *menu, surface_t *d) { mp3player_get_samplerate() ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "A: %s\n" "B: Exit | Left / Right: Rewind / Fast forward", diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index 88196d00d..f7e93dad0 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -110,7 +110,7 @@ void component_editdatetime_draw ( struct tm t, rtc_field_t selected_field ) { snprintf( current_selection_chars, sizeof(current_selection_chars), "******************^^^^*****"); break; } - component_messagebox_draw( + ui_components_messagebox_draw( "|YYYY|MM|DD|HH|MM|SS| DOW\n%s\n%s\n", full_dt, current_selection_chars); } @@ -165,14 +165,14 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); if (!is_editing_mode) { if( menu->current_time >= 0 ) { - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "ADJUST REAL TIME CLOCK\n" "\n" @@ -186,7 +186,7 @@ static void draw (menu_t *menu, surface_t *d) { menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown" ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "A: Change\n" "B: Back" @@ -194,7 +194,7 @@ static void draw (menu_t *menu, surface_t *d) { } else { - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "ADJUST REAL TIME CLOCK\n" "\n" @@ -206,7 +206,7 @@ static void draw (menu_t *menu, surface_t *d) { menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown" ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "B: Back" @@ -214,12 +214,12 @@ static void draw (menu_t *menu, surface_t *d) { } } else { - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, "Up/Down: Adjust Field\n" "Left/Right: Switch Field" ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "R: Save\n" "B: Back" diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 068b4675c..4b2467d1e 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -106,12 +106,12 @@ static component_context_menu_t options_context_menu = { .list = { static void process (menu_t *menu) { - if (component_context_menu_process(menu, &options_context_menu)) { + if (ui_components_context_menu_process(menu, &options_context_menu)) { return; } if (menu->actions.enter) { - component_context_menu_show(&options_context_menu); + ui_components_context_menu_show(&options_context_menu); sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); @@ -122,17 +122,17 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "MENU SETTINGS EDITOR\n" "\n" ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n\n" " Default Directory : %s\n\n" @@ -163,13 +163,13 @@ static void draw (menu_t *menu, surface_t *d) { ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "A: Change\n" "B: Back" ); - component_context_menu_draw(&options_context_menu); + ui_components_context_menu_draw(&options_context_menu); rdpq_detach_show(); } @@ -177,7 +177,7 @@ static void draw (menu_t *menu, surface_t *d) { void view_settings_init (menu_t *menu) { - component_context_menu_init(&options_context_menu); + ui_components_context_menu_init(&options_context_menu); } diff --git a/src/menu/views/system_info.c b/src/menu/views/system_info.c index 18cffb0d0..653ee0b07 100644 --- a/src/menu/views/system_info.c +++ b/src/menu/views/system_info.c @@ -36,16 +36,16 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_CENTER, VALIGN_TOP, "N64 SYSTEM INFORMATION" ); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "\n" @@ -65,7 +65,7 @@ static void draw (menu_t *menu, surface_t *d) { (joypad[3]) ? "" : "not ", format_accessory(3) ); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "\n" "B: Exit" diff --git a/src/menu/views/text_viewer.c b/src/menu/views/text_viewer.c index 0339ba376..b3cdb5591 100644 --- a/src/menu/views/text_viewer.c +++ b/src/menu/views/text_viewer.c @@ -1,7 +1,7 @@ #include #include -#include "../components/constants.h" +#include "../ui_components/constants.h" #include "../fonts.h" #include "../sound.h" #include "utils/utils.h" @@ -69,19 +69,19 @@ static void process (menu_t *menu) { static void draw (menu_t *menu, surface_t *d) { rdpq_attach(d, NULL); - component_background_draw(); + ui_components_background_draw(); - component_layout_draw(); + ui_components_layout_draw(); - component_main_text_draw( + ui_components_main_text_draw( ALIGN_LEFT, VALIGN_TOP, "%s\n", text->contents + text->offset ); - component_list_scrollbar_draw(text->current_line, text->lines, LIST_ENTRIES); + ui_components_list_scrollbar_draw(text->current_line, text->lines, LIST_ENTRIES); - component_actions_bar_text_draw( + ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, "^%02XUp / Down: Scroll^00\n" "B: Back", diff --git a/src/menu/views/views.h b/src/menu/views/views.h index 8da900dcb..8c475280d 100644 --- a/src/menu/views/views.h +++ b/src/menu/views/views.h @@ -8,7 +8,7 @@ #define VIEWS_H__ -#include "../components.h" +#include "../ui_components.h" #include "../menu_state.h" From 90157f59301ad060ff04c57143ab37b781013825 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 11 Nov 2024 20:15:54 +0000 Subject: [PATCH 67/89] Add menu customization readme --- README.md | 1 + docs/07_menu_customization.md | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 docs/07_menu_customization.md diff --git a/README.md b/README.md index 974577ba4..75b5ecb92 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ An open source menu for N64 flashcarts. ## Documentation * [Getting started guide](./docs/00_getting_started_sd.md) * [Menu controls](./docs/01_menu_controls.md) +* [Menu customizations](./docs/07_menu_customizations.md) * [Developer guide](./docs/99_developer_guide.md) ## Video showcase (as of Oct 12 2023) diff --git a/docs/07_menu_customization.md b/docs/07_menu_customization.md new file mode 100644 index 000000000..99807b216 --- /dev/null +++ b/docs/07_menu_customization.md @@ -0,0 +1,5 @@ +# Menu customization + +## Using a custom font +Add a `font64` file to the root directory called "custom.font64" +This can be build using `libdragon` tools. From e83991a9871eae04c064e198cd16843cf172a2ec Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 22 Nov 2024 19:33:26 +0000 Subject: [PATCH 68/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 0c4e38885..23bba79ab 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 0c4e388851cabab52f421bff5e75e9dc3ab36c72 +Subproject commit 23bba79ab570c4504e8707e34ac935c669e57d32 From 09d9648f6aa6165c3e9f87656a9bbff36c23987d Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 22 Nov 2024 20:08:39 +0000 Subject: [PATCH 69/89] Improve RTC component name --- src/menu/views/rtc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index f7e93dad0..0283f7628 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -72,8 +72,8 @@ void adjust_rtc_time( struct tm *t, int incr ) { *t = *gmtime( ×tamp ); } -void component_editdatetime_draw ( struct tm t, rtc_field_t selected_field ) { - // FIXME: move this to components.c once improved. +void rtc_ui_component_editdatetime_draw ( struct tm t, rtc_field_t selected_field ) { + // FIXME: move this to ui_components.c once improved. /* Format RTC date/time as strings */ char full_dt[30]; char current_selection_chars[30]; @@ -227,7 +227,7 @@ static void draw (menu_t *menu, surface_t *d) { } if (is_editing_mode) { - component_editdatetime_draw(rtc_tm, editing_field_type); + rtc_ui_component_editdatetime_draw(rtc_tm, editing_field_type); } rdpq_detach_show(); From 1eec461d3efe0210019550fb498658f581e82736 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 22 Nov 2024 20:20:57 +0000 Subject: [PATCH 70/89] Fix RTC wrap --- src/menu/views/rtc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index 0283f7628..3df38a6d5 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -5,8 +5,8 @@ #include "../sound.h" #include "views.h" -#define MAX(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a > _b ? _a : _b; }) -#define MIN(a,b) ({ typeof(a) _a = a; typeof(b) _b = b; _a < _b ? _a : _b; }) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) #define CLAMP(x, min, max) (MIN(MAX((x), (min)), (max))) #define YEAR_MIN 1996 @@ -27,7 +27,7 @@ static struct tm rtc_tm = {0}; static bool is_editing_mode; static rtc_field_t editing_field_type; -int wrap( uint16_t val, uint16_t min, uint16_t max ) { +int wrap( int val, uint16_t min, uint16_t max ) { if( val < min ) return max; if( val > max ) return min; return val; From b779af4db3ca098ba1572acc0c70cbdcda07b133 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 24 Nov 2024 16:49:40 +0000 Subject: [PATCH 71/89] Smsplus64 Emulator (#159) ## Description Switch to smsPlus64 (https://github.com/fhoedemakers/smsplus64) from TotalSMS. ## Motivation and Context This emulator, although still WIP (like most others) works with the plugin system. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [x] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- README.md | 2 +- docs/00_getting_started_sd.md | 4 +++- src/menu/cart_load.c | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 75b5ecb92..a3beb078d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ An open source menu for N64 flashcarts. * Fully Open Source. * Loads all known N64 games (including iQue and Aleck64 ROMs (even if they are byteswapped)). * Fully emulates the 64DD and loads 64DD disks (SummerCart64 only). -* Emulator support (NES, SNES, GB, GBC) ROMs. +* Emulator support (NES, SNES, GB, GBC, SMS, GG) ROMs. * N64 ROM box image support. * Background image (PNG) support. * Comprehensive ROM save database (including HomeBrew headers). diff --git a/docs/00_getting_started_sd.md b/docs/00_getting_started_sd.md index f2f75de8e..00f1f59d2 100644 --- a/docs/00_getting_started_sd.md +++ b/docs/00_getting_started_sd.md @@ -22,6 +22,7 @@ Menu currently supports the following emulators and associated ROM file names: - **NES**: [neon64v2](https://github.com/hcs64/neon64v2/releases) by *hcs64* - `neon64bu.rom` - **SNES**: [sodium64](https://github.com/Hydr8gon/sodium64/releases) by *Hydr8gon* - `sodium64.z64` - **Game Boy** / **GB Color**: [gb64](https://lambertjamesd.github.io/gb64/romwrapper/romwrapper.html) by *lambertjamesd* - `gb.v64` / `gbc.v64` ("Download Emulator" button) +- **SMS** / **GG**: [smsPlus64](https://github.com/fhoedemakers/smsplus64/releases) by *fhoedmakers* - `smsPlus64.z64` ### 64DD disk support @@ -48,7 +49,8 @@ SD:\ │ ├── neon64bu.rom │ ├── sodium64.z64 │ ├── gb.v64 -│ └── gbc.v64 +│ ├── gbc.v64 +│ └── smsPlus64.v64 │ ├── (a rom).z64 ├── (a rom).n64 diff --git a/src/menu/cart_load.c b/src/menu/cart_load.c index b57afb6c5..003c40453 100644 --- a/src/menu/cart_load.c +++ b/src/menu/cart_load.c @@ -177,8 +177,8 @@ cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type, save_type = FLASHCART_SAVE_TYPE_FLASHRAM_1MBIT; break; case CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT: - path_push(path, "TotalSMS.z64"); - save_type = FLASHCART_SAVE_TYPE_SRAM_256KBIT; + path_push(path, "smsPlus64.z64"); + save_type = FLASHCART_SAVE_TYPE_NONE; break; } From 058c41b8e0bea7ed65e6f172933ba981b9287800 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sun, 24 Nov 2024 16:54:10 +0000 Subject: [PATCH 72/89] Update 00_getting_started_sd.md Corrected extension type. --- docs/00_getting_started_sd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/00_getting_started_sd.md b/docs/00_getting_started_sd.md index 00f1f59d2..650e4cd99 100644 --- a/docs/00_getting_started_sd.md +++ b/docs/00_getting_started_sd.md @@ -50,7 +50,7 @@ SD:\ │ ├── sodium64.z64 │ ├── gb.v64 │ ├── gbc.v64 -│ └── smsPlus64.v64 +│ └── smsPlus64.z64 │ ├── (a rom).z64 ├── (a rom).n64 From a7669b1057de1d9634aef2e093de3e068fbf46e4 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 4 Dec 2024 12:30:38 +0000 Subject: [PATCH 73/89] Add Fairchild Channel F emulator support (#166) ## Description Adds the required changes for supporting the Fairchild Channel F emulator. ## Motivation and Context Support more emulators. ## How Has This Been Tested? Still required. ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [x] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [x] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- README.md | 2 +- docs/00_getting_started_sd.md | 4 +++- src/menu/cart_load.c | 4 ++++ src/menu/cart_load.h | 2 ++ src/menu/views/browser.c | 2 +- src/menu/views/file_info.c | 2 +- src/menu/views/load_emulator.c | 5 +++++ 7 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a3beb078d..30dfa22bd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ An open source menu for N64 flashcarts. * Fully Open Source. * Loads all known N64 games (including iQue and Aleck64 ROMs (even if they are byteswapped)). * Fully emulates the 64DD and loads 64DD disks (SummerCart64 only). -* Emulator support (NES, SNES, GB, GBC, SMS, GG) ROMs. +* Emulator support (NES, SNES, GB, GBC, SMS, GG, CHF) ROMs. * N64 ROM box image support. * Background image (PNG) support. * Comprehensive ROM save database (including HomeBrew headers). diff --git a/docs/00_getting_started_sd.md b/docs/00_getting_started_sd.md index 650e4cd99..ab6ef78dc 100644 --- a/docs/00_getting_started_sd.md +++ b/docs/00_getting_started_sd.md @@ -23,6 +23,7 @@ Menu currently supports the following emulators and associated ROM file names: - **SNES**: [sodium64](https://github.com/Hydr8gon/sodium64/releases) by *Hydr8gon* - `sodium64.z64` - **Game Boy** / **GB Color**: [gb64](https://lambertjamesd.github.io/gb64/romwrapper/romwrapper.html) by *lambertjamesd* - `gb.v64` / `gbc.v64` ("Download Emulator" button) - **SMS** / **GG**: [smsPlus64](https://github.com/fhoedemakers/smsplus64/releases) by *fhoedmakers* - `smsPlus64.z64` +- **Fairchild Channel F**: [Press-F-Ultra](https://github.com/celerizer/Press-F-Ultra/releases) by *celerizer* - `Press-F.z64` ### 64DD disk support @@ -50,7 +51,8 @@ SD:\ │ ├── sodium64.z64 │ ├── gb.v64 │ ├── gbc.v64 -│ └── smsPlus64.z64 +│ ├── smsPlus64.z64 +│ └── Press-F.z64 │ ├── (a rom).z64 ├── (a rom).n64 diff --git a/src/menu/cart_load.c b/src/menu/cart_load.c index 003c40453..054102edc 100644 --- a/src/menu/cart_load.c +++ b/src/menu/cart_load.c @@ -180,6 +180,10 @@ cart_load_err_t cart_load_emulator (menu_t *menu, cart_load_emu_type_t emu_type, path_push(path, "smsPlus64.z64"); save_type = FLASHCART_SAVE_TYPE_NONE; break; + case CART_LOAD_EMU_TYPE_FAIRCHILD_CHANNELF: + path_push(path, "Press-F.z64"); + save_type = FLASHCART_SAVE_TYPE_NONE; + break; } if (!file_exists(path_get(path))) { diff --git a/src/menu/cart_load.h b/src/menu/cart_load.h index 04c2c6791..ad4d34a1e 100644 --- a/src/menu/cart_load.h +++ b/src/menu/cart_load.h @@ -54,6 +54,8 @@ typedef enum { CART_LOAD_EMU_TYPE_GAMEBOY_COLOR, /** @brief The ROM is designed for a Sega 8Bit system (Game Gear or Master System). */ CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT, + /** @brief The ROM is designed for a Fairchild Channel F system. */ + CART_LOAD_EMU_TYPE_FAIRCHILD_CHANNELF, } cart_load_emu_type_t; diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index abe86b598..53d445f46 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -11,7 +11,7 @@ static const char *rom_extensions[] = { "z64", "n64", "v64", "rom", NULL }; static const char *disk_extensions[] = { "ndd", NULL }; -static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", NULL }; +static const char *emulator_extensions[] = { "nes", "sfc", "smc", "gb", "gbc", "sms", "gg", "sg", "chf", NULL }; // TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts. static const char *save_extensions[] = { "sav", NULL }; static const char *image_extensions[] = { "png", NULL }; diff --git a/src/menu/views/file_info.c b/src/menu/views/file_info.c index be204e5f1..1a979426a 100644 --- a/src/menu/views/file_info.c +++ b/src/menu/views/file_info.c @@ -14,7 +14,7 @@ static const char *archive_extensions[] = { "zip", "rar", "7z", "tar", "gz", NUL static const char *image_extensions[] = { "png", "jpg", "gif", NULL }; static const char *music_extensions[] = { "mp3", "wav", "ogg", "wma", "flac", NULL }; static const char *controller_pak_extensions[] = { "mpk", "pak", NULL }; -static const char *emulator_extensions[] = { "nes", "smc", "gb", "gbc", "sms", "gg", NULL }; +static const char *emulator_extensions[] = { "nes", "smc", "gb", "gbc", "sms", "gg", "chf", NULL }; static struct stat st; diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c index ad9231454..b4e2c1b37 100644 --- a/src/menu/views/load_emulator.c +++ b/src/menu/views/load_emulator.c @@ -10,6 +10,7 @@ static const char *emu_snes_rom_extensions[] = { "sfc", "smc", NULL }; static const char *emu_gameboy_rom_extensions[] = { "gb", NULL }; static const char *emu_gameboy_color_rom_extensions[] = { "gbc", NULL }; static const char *emu_sega_8bit_rom_extensions[] = { "sms", "gg", "sg", NULL }; +static const char *emu_fairchild_channelf_rom_extensions[] = { "chf", NULL }; static cart_load_emu_type_t emu_type; @@ -25,6 +26,8 @@ static char *format_emulator_name (cart_load_emu_type_t emulator_info) { return "Nintendo GAMEBOY Color"; case CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT: return "SEGA 8bit system"; + case CART_LOAD_EMU_TYPE_FAIRCHILD_CHANNELF: + return "Fairchild Channel F"; default: return "Unknown"; } @@ -120,6 +123,8 @@ void view_load_emulator_init (menu_t *menu) { emu_type = CART_LOAD_EMU_TYPE_GAMEBOY_COLOR; } else if (file_has_extensions(path_get(path), emu_sega_8bit_rom_extensions)) { emu_type = CART_LOAD_EMU_TYPE_SEGA_GENERIC_8BIT; + } else if (file_has_extensions(path_get(path), emu_fairchild_channelf_rom_extensions)) { + emu_type = CART_LOAD_EMU_TYPE_FAIRCHILD_CHANNELF; } else { menu_show_error(menu, "Unsupported ROM"); } From 6a2c4828b28c31f8b9973e5b26682c827e1d0bed Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Wed, 4 Dec 2024 13:58:22 +0000 Subject: [PATCH 74/89] Submodule updates (#169) ## Description Update libdragon and miniz to latest. ## Motivation and Context Keeps submodules up-to-date. potential fixes for RDPQ text flicker. ## How Has This Been Tested? SC64 ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [x] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- libdragon | 2 +- src/libs/miniz | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libdragon b/libdragon index 23bba79ab..75db5bc4f 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 23bba79ab570c4504e8707e34ac935c669e57d32 +Subproject commit 75db5bc4fdf6eff753491773f131c532c45656e7 diff --git a/src/libs/miniz b/src/libs/miniz index 35528ad76..0f4cbb8c2 160000 --- a/src/libs/miniz +++ b/src/libs/miniz @@ -1 +1 @@ -Subproject commit 35528ad769143b9ed38a95a22d460b963e39f278 +Subproject commit 0f4cbb8c27a5dc48967e5a7d3b68f8666d8f96d4 From b75d6e63700feda7e6489eed46edb2c31657d366 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Tue, 10 Dec 2024 22:22:09 +0000 Subject: [PATCH 75/89] Improve text Improves text and expectation for flashcart feature. --- src/menu/views/flashcart_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index abf99991d..9dce0fee0 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -62,7 +62,7 @@ static void draw (menu_t *menu, surface_t *d) { " Automatic CIC: %s.\n" " Region Detection: %s.\n" " Save Writeback: %s.\n" - " Update from menu: %s.\n" + " Auto F/W Updates: %s.\n" "\n\n", format_cart_type(), "Not Available", // TODO get cart firmware version(s). From 82b48843a1b38751d6fca717893a0e12dc80aa82 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 23 Dec 2024 18:18:01 +0000 Subject: [PATCH 76/89] Update libdragon submodule (#172) ## Description Updates the libdragon SDK ## Motivation and Context Keeps it up-to-date. ## How Has This Been Tested? ## Screenshots ## Types of changes - [ ] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 75db5bc4f..30503770c 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 75db5bc4fdf6eff753491773f131c532c45656e7 +Subproject commit 30503770c1f40c4290668842d4597f4051191225 From 570113ebac5d9d5eda364861312baffec2218f3e Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 23 Dec 2024 18:34:41 +0000 Subject: [PATCH 77/89] Removed joypad images They are not used (yet) and is blocking a release. --- Makefile | 28 ++---------------------- assets/images/joypad/joypad_a.png | Bin 653 -> 0 bytes assets/images/joypad/joypad_b.png | Bin 649 -> 0 bytes assets/images/joypad/joypad_c_down.png | Bin 666 -> 0 bytes assets/images/joypad/joypad_c_left.png | Bin 707 -> 0 bytes assets/images/joypad/joypad_c_right.png | Bin 715 -> 0 bytes assets/images/joypad/joypad_c_up.png | Bin 711 -> 0 bytes assets/images/joypad/joypad_d_down.png | Bin 419 -> 0 bytes assets/images/joypad/joypad_d_left.png | Bin 423 -> 0 bytes assets/images/joypad/joypad_d_right.png | Bin 419 -> 0 bytes assets/images/joypad/joypad_d_up.png | Bin 440 -> 0 bytes assets/images/joypad/joypad_l.png | Bin 376 -> 0 bytes assets/images/joypad/joypad_r.png | Bin 424 -> 0 bytes assets/images/joypad/joypad_start.png | Bin 672 -> 0 bytes assets/images/joypad/joypad_z.png | Bin 387 -> 0 bytes 15 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 assets/images/joypad/joypad_a.png delete mode 100644 assets/images/joypad/joypad_b.png delete mode 100644 assets/images/joypad/joypad_c_down.png delete mode 100644 assets/images/joypad/joypad_c_left.png delete mode 100644 assets/images/joypad/joypad_c_right.png delete mode 100644 assets/images/joypad/joypad_c_up.png delete mode 100644 assets/images/joypad/joypad_d_down.png delete mode 100644 assets/images/joypad/joypad_d_left.png delete mode 100644 assets/images/joypad/joypad_d_right.png delete mode 100644 assets/images/joypad/joypad_d_up.png delete mode 100644 assets/images/joypad/joypad_l.png delete mode 100644 assets/images/joypad/joypad_r.png delete mode 100644 assets/images/joypad/joypad_start.png delete mode 100644 assets/images/joypad/joypad_z.png diff --git a/Makefile b/Makefile index 052b3c44e..450f87398 100644 --- a/Makefile +++ b/Makefile @@ -85,30 +85,6 @@ SOUNDS = \ error.wav \ settings.wav -JOYPAD_IMAGES = \ - joypad_a.png \ - joypad_b.png \ - joypad_c_down.png \ - joypad_c_left.png \ - joypad_c_right.png \ - joypad_c_up.png \ - joypad_d_down.png \ - joypad_d_left.png \ - joypad_d_right.png \ - joypad_d_up.png \ - joypad_l.png \ - joypad_r.png \ - joypad_start.png \ - joypad_z.png -# joypad_j_east.png \ -# joypad_j_north.png \ -# joypad_j_northeast.png \ -# joypad_j_northwest.png \ -# joypad_j_south.png \ -# joypad_j_southeast.png \ -# joypad_j_southwest.png \ -# joypad_j_west.png \ - OBJS = $(addprefix $(BUILD_DIR)/, $(addsuffix .o,$(basename $(SRCS)))) MINIZ_OBJS = $(filter $(BUILD_DIR)/libs/miniz/%.o,$(OBJS)) SPNG_OBJS = $(filter $(BUILD_DIR)/libs/libspng/%.o,$(OBJS)) @@ -117,7 +93,7 @@ DEPS = $(OBJS:.o=.d) FILESYSTEM = \ $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(FONTS:%.ttf=%.font64))) \ $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(SOUNDS:%.wav=%.wav64))) \ - $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(JOYPAD_IMAGES:%.png=%.sprite))) + $(addprefix $(FILESYSTEM_DIR)/, $(notdir $(IMAGES:%.png=%.sprite))) $(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second @@ -134,7 +110,7 @@ $(FILESYSTEM_DIR)/%.wav64: $(ASSETS_DIR)/sounds/%.wav @echo " [AUDIO] $@" @$(N64_AUDIOCONV) $(AUDIOCONV_FLAGS) -o $(FILESYSTEM_DIR) "$<" -$(FILESYSTEM_DIR)/%.sprite: $(ASSETS_DIR)/images/joypad/%.png +$(FILESYSTEM_DIR)/%.sprite: $(ASSETS_DIR)/images/%.png @echo " [SPRITE] $@" @$(N64_MKSPRITE) $(MKSPRITE_FLAGS) -o $(dir $@) "$<" diff --git a/assets/images/joypad/joypad_a.png b/assets/images/joypad/joypad_a.png deleted file mode 100644 index 9e6e6493a13daaa26db6f7e330dbd78ae4cc62db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 653 zcmV;80&@L{P)@y)rp$;Mubp*{q=+-boVW`By)VZpJ)TN)Gb3Xx6yS8gT z0P4V-#1bKC;-T~A4DNPV!h6W^f&j%`u__k(9fA6#&daapy8H0GPHzZrxeD zPxJ0J0H}(yI+sej4=w|bO6-igpybxd8qGd_UbwwjQMFS(jtBU7_yGd|ww=0AV%d%z ze6M9yqFb{>PYw%=a}aVBEC?swg6JkRVwO}zGwjgFe(hYC2Y%T3QGY30TL&rtu!gr>ukBSu;71dO{tKVf zUc47gZv{BBOf>Iq)2zFYyIFCFZvHp3->kMe?c-OX>8*?{(`c03TA3#7b&Z%MRWm4X n`r2{CKePU?4cXc{814T9ih=Y)U@uCn00000NkvXXu0mjfV)P$* diff --git a/assets/images/joypad/joypad_b.png b/assets/images/joypad/joypad_b.png deleted file mode 100644 index e5874cfff3bd9b22aeaeca708b1d544099bf19f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 649 zcmV;40(Sk0P)EmEuLgoa}NqN%VJ43W-R*>ZvCs@ z0f3TSYdu@e-4{!O$B~8_D5!ygj`duVOgXnf_6k*IKFU8^-dwIxPSO7spy#v^e(%F; zdf+ApOHL1!wnV1)@#iAtX}-o&LlWTxMmC9j^%huyBJRgCQ*gn-1g}t%WSP+mIf&77 zx&Q$7kCjk^WjKJbVRfXS)bE$)>|q!gn(9z_W6?|lxyk}SfUq0R)yD(nQR&GKvM)yu z(f#59F~AHuQ!E1s6-4h?bVpq2D1MS%$=-kl2n84?Fcwls-An<5Of_h}X}UkI{ijqP zx)+|^c^$aE&vN%3GwxF=WSp& zc7Ta8ndRcKS+(zEB1dZryElK6hROc4KF(Ors!sg{qn1`%Lf(O%CfE50s~T j`QB^G1AWxG9OeH4Nfh#H*|FL)00000NkvXXu0mjfw>}j^ diff --git a/assets/images/joypad/joypad_c_down.png b/assets/images/joypad/joypad_c_down.png deleted file mode 100644 index 773e13914c0b1b7d50d9ec77a5c91e2195601855..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 666 zcmV;L0%iS)P)UMk%I5uh$*b8}; z`1sFMz}&+*0Dz`1#4W2`p^_wJt}d*+yoBzF1;*HoW>FMTep@#<}-H~ZppI0~7ApwBXlhZei z`ayjhqG>ubU7s*F4(j`7o&Gv7HcXLNUrUWpp@U?NL`2R;_#L5Rk*h1zaQ?e28D>p}XNVth_BllS-JnRn)8eBVc_UOphV zLp{=xB#M230LKA9t>(J6V;`1EmAPB@ofob3Ox<94fe9-SWjl6i@U*6lu!1188B9X7>Zvg=J{2`gSc;Vb|H~8St!t3`Npp^n-u?k80 zdo5M%#ZYYM%*rB}=Hwpfk)|aAtzgBbc=K)p*@G;?#70Q0H;if2Xo&=yNvD@Mk%-ZH z!#vN!$2AjQzV0E&mmy0Xk(xXNL4ZI=KgaPr2LSs&vUvO9GkB&1Me@4#qEkH2b1aUZ zu|Fc?>UbPsi9Hcu7^ZFS`2g#>t_uLcpooN~VQT6I;_>)@C8)Vom$e=HkW$(>lq3nd zp2Ebn2`Gx%5uv$s90!y-S+-oR%9D(NdS$yZ%lN*J z)hFs+Dy7Fy`bd;gY;A4g(nu0SB*^FUST~Gs*Y6b0vj9-cS;@6e>!m^=-_<}61dNT1 zH(?;{!PwbE;)G$^+3_-&%)XVgM%#v|RV#~RIuw!~6M;l|p67f3 pZnf$#7JVka}1WO7|-+oX-Ya2NM*&T~FEhj7l(uV>4l z*)U^^5k;0oNg@CcbOY|TJ+JkvHFxcnf3M%4>;qO8NJ34-cJxs_Vi*P}r2}B!_faa9 zP^tWInzd$XW}aIIfYk+(P?NErrkN9CF%7*QhbRgdrbeTVe7+DgYxc;@Jhub@P!q8o z)69vQ7Dvr$;L*w&HcQ(>0h$(vY34*Vsg?mioD46`rp8iN(&;e*0G#(w{G)^|L9`WA2AeL@iQ005LyC^A(9iH67kdhiDT2uXk%c2L>< zf`=<>_*Sk00HlyaB=F+_{}PK3OrAZ1lS%a;o7C-ecz<3(hLiwcbo4Z`*(?}i2exjf z!zHind%o{SjxNr=qR}WanJjc&AF%tr53lXJg41&5wzi6gjr=WuQi@C_i|Ogh$AST% zv{i)bx_1cY9BU7>J=4rFO^c)LwXs>;z{JFbW9z)Hdfmc$etqxK^+r?xfM(53<@1H0 z(WpaKWK2z6{7-Nlg~EDZTXq^adQ3^Rtm`^6jPt{fsaQm{Qr)vHJ3Vkr{d#_v-iR=^ xq{vi}LJ|Q8x}6Sp+rHm0Kh%vC7ZEtrAlS=(I8t7`h_as)l-2)S|gMCmDt^QN4$*?QU`V%2ieTl zK`xh{vkF#!--z$j0B2!st|xUXrD;*A8rt6d1>f@#zj?I=Di%%GNE?gRp>?ls#CJph zh_pr~8^fOGVSeE&=8X+_o>v2CT2xB)q;7{>!V>@>PL0!~9@Fmx!vOGUaSM`Af-IEq zabdHr9Zic$v6$XFJx064WCpz2IkUAt% zm9{qyY-V;Did3q-Rk@7W_bYWCLI|irJ49JJ`>Lw)e@qn3L&$-KWC=Jb9O8GrPzRAN z$(Kbm=G3lY%t>G#pd7B|M?W^xj0G^V~5p#*ocq^SONP-ATb3s5_>hrpIWP8d8U- tLOW#nY)3ffWyV;xT&avc9Aq0o|6k6t4$`cRcX9v#002ovPDHLkV1gs$K0yEg diff --git a/assets/images/joypad/joypad_d_down.png b/assets/images/joypad/joypad_d_down.png deleted file mode 100644 index 4223ec3b6635002821e2a3ac4272e71b738580ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrxP)0{F&LM&1UnxUawyH9eB!O-BW|~V3f&jDG z%z$0jHS7ZraQ4(R@C~a=E<5i^T%I?;G%?!Jw2kDa2#h477@1Vx;`~clizO_0HP>@hz!C_(?Dze!*yMM9FNCx=mjz} zEXy+TZQJ5-IQ&*s^-dy!)oS(TdEV!AI(>QeH$+6V)~B{@x4Yf$>kq0;VK#^u4$uGq N002ovPDHLkV1oC?vg`l= diff --git a/assets/images/joypad/joypad_d_left.png b/assets/images/joypad/joypad_d_left.png deleted file mode 100644 index fb4676037387c3b74da86950f8a204e6ea581002..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 423 zcmV;Y0a*TtP)jV|m&?!rW2~DZHcf*p%V3Ow5CT%lp}-iiZCjkrXEaR% zDJ5>%{I)+3AR^>>j;gA_IfqhecD0^BUDwF63;;&0PT`)OWmyP<09xzC-D;KHx}Y#0c_hw9LEU55OWO`0EV(ZCT_P|Y&IJdMKQj@ z^?Lp6y2&m909LCNeBU4a9d3+yKAlb<#+X@=NtZ-~G)-g6vfh+ZA!c$J!xug1R#QB$ RSQr2R002ovPDHLkV1nh5r?~(C diff --git a/assets/images/joypad/joypad_d_right.png b/assets/images/joypad/joypad_d_right.png deleted file mode 100644 index d9f3fd939451319905f58b8e579a785144aeea57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrxP)V$dKR#kN@%kncxl8n~tbvF}$x~{R^ZhyK{98naZD2gdgH@X4<2q8d3$g&KZ%?4Ff zjp_yfBI>=F8Tb1gaU5g6-w*EdG(k_KX^>K)C<=sOh|A^j=_m93S2_5|ID^rPQ%$ny)lX(?1Cw7Y%|Sbcsn~I-P4! zK~y-6#Z$XZ#4r%twKs`lDfoy5DRNDm0x6}1#5I&iP|@&fl>7o6d`D7KrOgjmc_^l! zBsmTRiD`FcS39$d-h0>rwn_aL4d_&2Q4~8Ox)nk^20>79&f{Si-f+zWW6YJ5@{Wk^ ziD)mS)M=VtD5at(iU0thX_^lL06;`umgS9<@`;G9gCNL^F*~jGWs)So*z%@~v5=~& zI#_F8vMk$ctDaxbb zzVD%JTbQP4l|$b9d8xoThdj@}rmUne&+|;qx!1a`AH4U!G6E52=iK}1A)Si9Q=JmdoX}wf25I z9w$En42Q#)Uaxlq0LBt z3`(i@EX$IzEZ?lPk7=4-P9~EFX0EmZX0sWRBmohj-EPD8eE@*g8cL}&j^n8@=4|T_ zSF05~&jSGDdA`T0000OaVYn zl$xUW+Sht_kLa{K&u#%5jCoBd51V25Y_(pm`HnLNzA6fIk|c@K^d#1L^N1+F18gwn zMN+q+qXoVr-G(|HAV64TZ)|mBu{4ENY;Sh~5 ztaa10&8LNEG0rhNHHEA*LM*DmIw}G z5S)V%!Q~uTQ6L+QKC{=r^77o=@!I_S!%4pp08TD1lTS(Vg6|QX##(L5~TeYIY%NiiPG?7B=pzxVs^ z;w;-5D^poJj4cI9wWZ@M`kX7^`r2kRI{%~f?mp33dHx>>TMCI3kxJ?S0000Z{Bp?OUt%xgF{)Cmr}}CDdiOroxy{(_G?*|k6{=d>bm}PeB_IylrL3P-AO6& z6SUU1N~vc6_l_f9z&XDTH3LABB;cGEK6hZi7~{ci*FYqK2LHk74WdwCT~|!g1OQ0W z6pXRIC9u|_?|Zau3;(VBIsxY#S(d?C3!?C(yF2K*4tbvMNx}y6JVR>@A;j(oK?5Q} z6h&y72G-i3!NCr;Z8PpHgg{XgM-9H_D1kBNO=}&zZJuWsW8Q-a#&LX7N?pWpd_zR% he$9R7aU36g{u?J6XzyjDz4rhB002ovPDHLkV1n9sofZH9 From b984b1b66661b794ab08d8662738d1764013d858 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 23 Dec 2024 19:05:18 +0000 Subject: [PATCH 78/89] Remove PAL60 from settings editor Now requires the `BETA_SETTING` flag until it is fixed. --- src/menu/views/settings_editor.c | 35 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 4b2467d1e..aeb1bae08 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -11,11 +11,6 @@ static const char *format_switch (bool state) { } } -static void set_pal60_type (menu_t *menu, void *arg) { - menu->settings.pal60_enabled = (bool)(uintptr_t)(arg); - settings_save(&menu->settings); -} - static void set_protected_entries_type (menu_t *menu, void *arg) { menu->settings.show_protected_entries = (bool)(uintptr_t)(arg); settings_save(&menu->settings); @@ -35,6 +30,11 @@ static void set_sound_enabled_type (menu_t *menu, void *arg) { } #ifdef BETA_SETTINGS +static void set_pal60_type (menu_t *menu, void *arg) { + menu->settings.pal60_enabled = (bool)(uintptr_t)(arg); + settings_save(&menu->settings); +} + static void set_bgm_enabled_type (menu_t *menu, void *arg) { menu->settings.bgm_enabled = (bool)(uintptr_t)(arg); settings_save(&menu->settings); @@ -52,12 +52,6 @@ static void set_rumble_enabled_type (menu_t *menu, void *arg) { #endif -static component_context_menu_t set_pal60_type_context_menu = { .list = { - {.text = "On", .action = set_pal60_type, .arg = (void *)(uintptr_t)(true) }, - {.text = "Off", .action = set_pal60_type, .arg = (void *) (false) }, - COMPONENT_CONTEXT_MENU_LIST_END, -}}; - static component_context_menu_t set_protected_entries_type_context_menu = { .list = { {.text = "On", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(true) }, {.text = "Off", .action = set_protected_entries_type, .arg = (void *)(uintptr_t)(false) }, @@ -77,6 +71,12 @@ static component_context_menu_t set_use_saves_folder_type_context_menu = { .list }}; #ifdef BETA_SETTINGS +static component_context_menu_t set_pal60_type_context_menu = { .list = { + {.text = "On", .action = set_pal60_type, .arg = (void *)(uintptr_t)(true) }, + {.text = "Off", .action = set_pal60_type, .arg = (void *) (false) }, + COMPONENT_CONTEXT_MENU_LIST_END, +}}; + static component_context_menu_t set_bgm_enabled_type_context_menu = { .list = { {.text = "On", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(true) }, {.text = "Off", .action = set_bgm_enabled_type, .arg = (void *)(uintptr_t)(false) }, @@ -91,11 +91,11 @@ static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = #endif static component_context_menu_t options_context_menu = { .list = { - { .text = "PAL60 Mode", .submenu = &set_pal60_type_context_menu }, { .text = "Show Hidden Files", .submenu = &set_protected_entries_type_context_menu }, { .text = "Sound Effects", .submenu = &set_sound_enabled_type_context_menu }, { .text = "Use Saves Folder", .submenu = &set_use_saves_folder_type_context_menu }, #ifdef BETA_SETTINGS + { .text = "PAL60 Mode", .submenu = &set_pal60_type_context_menu }, { .text = "Background Music", .submenu = &set_bgm_enabled_type_context_menu }, { .text = "Rumble Feedback", .submenu = &set_rumble_enabled_type_context_menu }, // { .text = "Restore Defaults", .action = set_use_default_settings }, @@ -136,27 +136,28 @@ static void draw (menu_t *menu, surface_t *d) { ALIGN_LEFT, VALIGN_TOP, "\n\n" " Default Directory : %s\n\n" - " Autoload ROM : %s\n" + " Autoload ROM : %s\n\n" "To change the following menu settings, press 'A':\n" - "* PAL60 Mode : %s\n" " Show Hidden Files : %s\n" " Use Saves folder : %s\n" " Sound Effects : %s\n" #ifdef BETA_SETTINGS + "* PAL60 Mode : %s\n" " Background Music : %s\n" " Rumble Feedback : %s\n" -#endif "\n\n" "Note: Certain settings have the following caveats:\n" - "* Requires rebooting the N64 Console.\n", + "* Requires rebooting the N64 Console.\n" +#endif + , menu->settings.default_directory, format_switch(menu->settings.rom_autoload_enabled), - format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.show_protected_entries), format_switch(menu->settings.use_saves_folder), format_switch(menu->settings.sound_enabled) #ifdef BETA_SETTINGS , + format_switch(menu->settings.pal60_enabled), format_switch(menu->settings.bgm_enabled), format_switch(menu->settings.rumble_enabled) #endif From 312b19e631a794e1520d2bf06a20a3346f9df757 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 23 Dec 2024 22:35:39 +0000 Subject: [PATCH 79/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 30503770c..48f8521dd 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 30503770c1f40c4290668842d4597f4051191225 +Subproject commit 48f8521dda3dd9d4f3b9535d82256cc4e458f8bc From c82f5e063f29b711eb00cbfb9253fca6794bd52f Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 26 Dec 2024 12:26:06 +0000 Subject: [PATCH 80/89] Improve text People were confused. --- src/menu/views/flashcart_info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 9dce0fee0..52799644d 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -65,7 +65,7 @@ static void draw (menu_t *menu, surface_t *d) { " Auto F/W Updates: %s.\n" "\n\n", format_cart_type(), - "Not Available", // TODO get cart firmware version(s). + "Feature coming soon.", // TODO get cart firmware version(s). format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_64DD)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_RTC)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_USB)), From 27ca0a75d5438cbfdb1f833d4aa3c3dc908b71b5 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 26 Dec 2024 16:38:55 +0000 Subject: [PATCH 81/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index 48f8521dd..f921ffcdd 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 48f8521dda3dd9d4f3b9535d82256cc4e458f8bc +Subproject commit f921ffcdd37f65af0841f223f8d19eb5a5215113 From 21871f6e5453dacfa62a91738725a737163a05ef Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Thu, 26 Dec 2024 21:23:52 +0000 Subject: [PATCH 82/89] [develop] Show F/W version in Flashcart info (#174) ## Description Add detection for the flashcart firmware version and show it within the Flashcart Info view. ## Motivation and Context The feature was incomplete. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [ ] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/flashcart/64drive/64drive.c | 26 ++++++++++++++++++++++++++ src/flashcart/ed64/ed64_vseries.c | 1 + src/flashcart/flashcart.c | 4 ++++ src/flashcart/flashcart.h | 10 ++++++++++ src/flashcart/sc64/sc64.c | 9 +++++++++ src/menu/views/flashcart_info.c | 11 +++++++++-- 6 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/flashcart/64drive/64drive.c b/src/flashcart/64drive/64drive.c index 5b4b5d22b..98e28fc1a 100644 --- a/src/flashcart/64drive/64drive.c +++ b/src/flashcart/64drive/64drive.c @@ -82,6 +82,31 @@ static bool d64_has_feature (flashcart_features_t feature) { } } +/** + * @brief Retrieves the firmware version of the 64drive device. + * + * The firmware version is returned as a flashcart_firmware_version_t structure, with each field + * including the major, minor, and revision numbers. + * The major version is set to 1 for 64drive variant A, and 2 for 64drive variant B. + * + * @return A flashcart_firmware_version_t structure containing the firmware version information. + */ +static flashcart_firmware_version_t d64_get_firmware_version (void) { + flashcart_firmware_version_t version_info; + + d64_ll_get_version(&device_variant, &version_info.minor, &version_info.revision); + + if (device_variant == DEVICE_VARIANT_A) { + version_info.major = 1; + } else if (device_variant == DEVICE_VARIANT_B) { + version_info.major = 2; + } else { + version_info.major = 0; + } + + return version_info; +} + static flashcart_err_t d64_load_rom (char *rom_path, flashcart_progress_callback_t *progress) { FIL fil; UINT br; @@ -277,6 +302,7 @@ static flashcart_t flashcart_d64 = { .init = d64_init, .deinit = d64_deinit, .has_feature = d64_has_feature, + .get_firmware_version = d64_get_firmware_version, .load_rom = d64_load_rom, .load_file = d64_load_file, .load_save = d64_load_save, diff --git a/src/flashcart/ed64/ed64_vseries.c b/src/flashcart/ed64/ed64_vseries.c index 22b6596d7..02ec3f761 100644 --- a/src/flashcart/ed64/ed64_vseries.c +++ b/src/flashcart/ed64/ed64_vseries.c @@ -140,6 +140,7 @@ static flashcart_t flashcart_ed64_vseries = { .init = ed64_vseries_init, .deinit = ed64_vseries_deinit, .has_feature = ed64_vseries_has_feature, + .get_firmware_version = NULL, // FIXME: show the returned firmware version info. .load_rom = ed64_vseries_load_rom, .load_file = ed64_vseries_load_file, .load_save = ed64_vseries_load_save, diff --git a/src/flashcart/flashcart.c b/src/flashcart/flashcart.c index 942ebd50f..6b634cace 100644 --- a/src/flashcart/flashcart.c +++ b/src/flashcart/flashcart.c @@ -156,6 +156,10 @@ bool flashcart_has_feature (flashcart_features_t feature) { return flashcart->has_feature(feature); } +flashcart_firmware_version_t flashcart_get_firmware_version (void) { + return flashcart->get_firmware_version(); +} + flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress) { flashcart_err_t err; diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index fcbcf606a..4a191290b 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -57,6 +57,13 @@ typedef struct { uint8_t defect_tracks[16][12]; } flashcart_disk_parameters_t; +/** @brief Flashcart Firmware version Structure. */ +typedef struct { + uint16_t major; + uint16_t minor; + uint32_t revision; +} flashcart_firmware_version_t; + typedef void flashcart_progress_callback_t (float progress); /** @brief Flashcart Structure */ @@ -67,6 +74,8 @@ typedef struct { flashcart_err_t (*deinit) (void); /** @brief The flashcart feature function */ bool (*has_feature) (flashcart_features_t feature); + /** @brief The flashcart firmware version function */ + flashcart_firmware_version_t (*get_firmware_version) (void); /** @brief The flashcart ROM load function */ flashcart_err_t (*load_rom) (char *rom_path, flashcart_progress_callback_t *progress); /** @brief The flashcart file load function */ @@ -88,6 +97,7 @@ char *flashcart_convert_error_message (flashcart_err_t err); flashcart_err_t flashcart_init (const char **storage_prefix); flashcart_err_t flashcart_deinit (void); bool flashcart_has_feature (flashcart_features_t feature); +flashcart_firmware_version_t flashcart_get_firmware_version (void); flashcart_err_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress); flashcart_err_t flashcart_load_file (char *file_path, uint32_t rom_offset, uint32_t file_offset); flashcart_err_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type); diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index 605ae4191..e53c3e278 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -187,6 +187,14 @@ static bool disk_load_sector_table (char *path, uint32_t *sector_table_offset, u return false; } +static flashcart_firmware_version_t sc64_get_firmware_version (void) { + flashcart_firmware_version_t version_info; + + sc64_ll_get_version(&version_info.major, &version_info.minor, &version_info.revision); + + return version_info; +} + static flashcart_err_t sc64_init (void) { uint16_t major; @@ -572,6 +580,7 @@ static flashcart_t flashcart_sc64 = { .init = sc64_init, .deinit = sc64_deinit, .has_feature = sc64_has_feature, + .get_firmware_version = sc64_get_firmware_version, .load_rom = sc64_load_rom, .load_file = sc64_load_file, .load_save = sc64_load_save, diff --git a/src/menu/views/flashcart_info.c b/src/menu/views/flashcart_info.c index 52799644d..64ee2060d 100644 --- a/src/menu/views/flashcart_info.c +++ b/src/menu/views/flashcart_info.c @@ -26,6 +26,13 @@ static const char *format_cart_type () { } } +static const char *format_cart_version () { + flashcart_firmware_version_t version = flashcart_get_firmware_version(); + static char buffer[16]; + sprintf(buffer, "%u.%u.%lu", version.major, version.minor, version.revision); + return buffer; +} + static void process (menu_t *menu) { if (menu->actions.back) { sound_play_effect(SFX_EXIT); @@ -54,7 +61,7 @@ static void draw (menu_t *menu, surface_t *d) { "Type:\n" " %s\n\n" "Firmware:\n" - " %s\n\n" + " Version: %s\n\n" "Features:\n" " Virtual 64DD: %s.\n" " Real Time Clock: %s.\n" @@ -65,7 +72,7 @@ static void draw (menu_t *menu, surface_t *d) { " Auto F/W Updates: %s.\n" "\n\n", format_cart_type(), - "Feature coming soon.", // TODO get cart firmware version(s). + format_cart_version(), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_64DD)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_RTC)), format_boolean_type(flashcart_has_feature(FLASHCART_FEATURE_USB)), From edf3e5aa90e1690a8efc330f70e21ac546024a74 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 27 Dec 2024 00:27:12 +0000 Subject: [PATCH 83/89] Fix text flicker introduces ` --outline 1` as a mkfont flag. makes other flags more verbose. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 450f87398..df9896533 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ FILESYSTEM = \ $(MINIZ_OBJS): N64_CFLAGS+=-DMINIZ_NO_TIME -fcompare-debug-second $(SPNG_OBJS): N64_CFLAGS+=-isystem $(SOURCE_DIR)/libs/miniz -DSPNG_USE_MINIZ -fcompare-debug-second -$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=-c 1 --size 16 -r 20-7F -r 80-1FF -r 2026-2026 --ellipsis 2026,1 +$(FILESYSTEM_DIR)/FiraMonoBold.font64: MKFONT_FLAGS+=--compress 1 --outline 1 --size 16 --range 20-7F --range 80-1FF --range 2026-2026 --ellipsis 2026,1 $(FILESYSTEM_DIR)/%.wav64: AUDIOCONV_FLAGS=--wav-compress 1 $(@info $(shell mkdir -p ./$(FILESYSTEM_DIR) &> /dev/null)) From 67fec690d8550248ec5d1940ea01db67ab027d73 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 27 Dec 2024 00:37:43 +0000 Subject: [PATCH 84/89] Update SC64_DEPLOYER_VERSION --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e5a3ad30d..55ac91455 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ FROM debian:bookworm-slim -ARG SC64_DEPLOYER_VERSION=v2.20.0 +ARG SC64_DEPLOYER_VERSION=v2.20.2 RUN apt-get update && \ apt-get upgrade -y && \ apt-get install build-essential doxygen git python3 wget -y && \ From fb75890e17dc614af3200f9ccbe8f5fe34432995 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Fri, 27 Dec 2024 02:10:47 +0000 Subject: [PATCH 85/89] [develop] Disk info view - load with rom - button context (#175) ## Description Changes the button context from `R` to `L|Z` and moves the `move load_disk_with_rom` to `menu_state` so that it can be used for things like autoload. ## Motivation and Context Aligns the button context with the ROM info menu. Makes it easier to set and re-use expansion ROM's. ## How Has This Been Tested? ## Screenshots ## Types of changes - [x] Improvement (non-breaking change that adds a new feature) - [ ] Bug fix (fixes an issue) - [x] Breaking change (breaking change) - [ ] Documentation Improvement - [ ] Config and build (change in the configuration and build system, has no impact on code or features) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. Signed-off-by: GITHUB_USER --- src/menu/menu_state.h | 1 + src/menu/views/load_disk.c | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index eaeb83ecc..b34b6d1ad 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -103,6 +103,7 @@ typedef struct { rom_info_t rom_info; path_t *disk_path; disk_info_t disk_info; + bool combined_disk_rom; } load; struct { diff --git a/src/menu/views/load_disk.c b/src/menu/views/load_disk.c index c1e24a792..f266021bc 100644 --- a/src/menu/views/load_disk.c +++ b/src/menu/views/load_disk.c @@ -4,8 +4,6 @@ #include "../sound.h" #include "views.h" - -static bool load_disk_with_rom; static component_boxart_t *boxart; @@ -31,10 +29,10 @@ static char *format_disk_region (disk_region_t region) { static void process (menu_t *menu) { if (menu->actions.enter) { menu->boot_pending.disk_file = true; - load_disk_with_rom = false; - } else if (menu->actions.options && menu->load.rom_path) { + menu->load.combined_disk_rom = false; + } else if (menu->actions.lz_context && menu->load.rom_path) { menu->boot_pending.disk_file = true; - load_disk_with_rom = true; + menu->load.combined_disk_rom = true; sound_play_effect(SFX_SETTING); } else if (menu->actions.back) { sound_play_effect(SFX_EXIT); @@ -89,7 +87,7 @@ static void draw (menu_t *menu, surface_t *d) { if (menu->load.rom_path) { ui_components_actions_bar_text_draw( ALIGN_RIGHT, VALIGN_TOP, - "R: Load with ROM" + "L|Z: Load with ROM\n" ); } @@ -118,7 +116,7 @@ static void draw_progress (float progress) { static void load (menu_t *menu) { cart_load_err_t err; - if (menu->load.rom_path && load_disk_with_rom) { + if (menu->load.rom_path && menu->load.combined_disk_rom) { err = cart_load_n64_rom_and_save(menu, draw_progress); if (err != CART_LOAD_OK) { menu_show_error(menu, cart_load_convert_error_message(err)); @@ -134,7 +132,7 @@ static void load (menu_t *menu) { menu->next_mode = MENU_MODE_BOOT; - if (load_disk_with_rom) { + if (menu->load.combined_disk_rom) { menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM; menu->boot_params->detect_cic_seed = rom_info_get_cic_seed(&menu->load.rom_info, &menu->boot_params->cic_seed); switch (rom_info_get_tv_type(&menu->load.rom_info)) { From e14ea9c9467e1222b216bd594773027ceb021bf3 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sat, 28 Dec 2024 16:04:09 +0000 Subject: [PATCH 86/89] Improve documentation --- README.md | 25 +++++++++++++++++-------- docs/00_getting_started_sd.md | 5 ++++- src/menu/views/settings_editor.c | 1 - 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 30dfa22bd..72d4c468f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ An open source menu for N64 flashcarts. ## Documentation * [Getting started guide](./docs/00_getting_started_sd.md) * [Menu controls](./docs/01_menu_controls.md) -* [Menu customizations](./docs/07_menu_customizations.md) +* [Menu customization](./docs/07_menu_customization.md) * [Developer guide](./docs/99_developer_guide.md) ## Video showcase (as of Oct 12 2023) @@ -53,11 +53,11 @@ These features are subject to change: ### N64 ROM autoload To use the autoload function, while on the `N64 ROM information` display, press the `R` button on your joypad and select the `Set ROM to autoload` option. When you restart the console, it will now only load the selected ROM rather than the menu. -NOTE: to return to the menu, hold joypad `start` button whilst powering on the console. +The autoload setting is stored in `config.ini` and persists until changed. This feature may slightly increase boot time as the menu needs to check for the Start button state. +NOTE: To return to the menu, hold the joypad `Start` button while powering on the console. ### GamePak sprites -To use N64 `GamePak` sprites, place `PNG` files within the `sd:/menu/boxart/` folder. - +To use N64 GamePak sprites, place PNG files within the `sd:/menu/boxart/` folder. #### Supported sprites These must be `PNG` files that use the following dimensions: @@ -65,6 +65,10 @@ These must be `PNG` files that use the following dimensions: * Japanese N64 GamePak boxart sprites: 112x158 * 64DD boxart sprites: 129x112 +Supported PNG formats: +* RGB/RGBA color formats +* 8-bit color depth + They will be loaded by directories using each character (case-sensitive) of the full 4 character Game Code (as identified in the menu ROM information). i.e. for GoldenEye NTSC USA (NGEE), this would be `sd:/menu/boxart/N/G/E/E/boxart_front.png`. i.e. for GoldenEye PAL (NGEP), this would be `sd:/menu/boxart/N/G/E/P/boxart_front.png`. @@ -72,8 +76,13 @@ i.e. for GoldenEye PAL (NGEP), this would be `sd:/menu/boxart/N/G/E/P/boxart_fro To improve compatibility between regions (as a fallback), you may exclude the region ID (last matched directory) for GamePaks to match with 3 letter IDs instead: i.e. for GoldenEye, this would be `sd:/menu/boxart/N/G/E/boxart_front.png`. -**Note1:** Excluding the region ID may show the wrong boxart. -**Note2:** For future support, boxart sprites should also include: `boxart_back.png`, `boxart_top.png`, `boxart_bottom.png`, `boxart_left.png`, `boxart_right.png`. +**Warning**: Excluding the region ID may show the wrong boxart. +**Note**: For future support, boxart sprites should also include: +* `boxart_back.png` +* `boxart_top.png` +* `boxart_bottom.png` +* `boxart_left.png` +* `boxart_right.png` As a starting point, here is a link to a boxart pack following the new structure, including `boxart_front.png` and failback images: * [Link](https://drive.google.com/file/d/1IpCmFqmGgGwKKmlRBxYObfFR9XywaC6n/view?usp=drive_link) @@ -117,14 +126,14 @@ If required, you can manually adjust the file on the SD card using your computer ### ED64 - WIP - UNTESTED AND UNSUPPORTED - USE AT OWN RISK Currently not supported, but work is in progress (See [PR's](https://github.com/Polprzewodnikowy/N64FlashcartMenu/pulls)). -NOTE: The menu may be able to load ROM's but not perform saves and may break existing ones.. +**Warning**: The menu may be able to load ROMs but cannot guarantee save functionality. Existing saves may be corrupted. #### ED64 (Vseries) The aim is to reach feature parity with [ED64-UnofficialOS](https://github.com/n64-tools/ED64-UnofficialOS-binaries) / [ED64-OfficialOS](https://krikzz.com/pub/support/everdrive-64/v2x-v3x/os-bin/). Download the `OS64.v64` ROM from the latest [action run - assets] and place it in the `/ED64` folder. #### ED64 (X series) -X Series support is currently awaiting fixes, in the meantime use the official [OS](https://krikzz.com/pub/support/everdrive-64/x-series/OS/) instead. +X Series support is currently awaiting fixes. Please use the official [OS](https://krikzz.com/pub/support/everdrive-64/x-series/OS/) for now. #### ED64 (P clone) Download the `OS64P.v64` ROM from the latest [action run - assets] and place it in the `/ED64P` folder. diff --git a/docs/00_getting_started_sd.md b/docs/00_getting_started_sd.md index ab6ef78dc..1661dacd9 100644 --- a/docs/00_getting_started_sd.md +++ b/docs/00_getting_started_sd.md @@ -1,6 +1,9 @@ ## First time setup of SD card -Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart +### Flashcarts +Using your PC, insert the SD card and ensure it is formatted for compatibility with your flashcart. +**warning** Filenames are expected to be part of the ASCII character set. Unicode characters are not fully supported and may cause a crash screen. + #### SC64 - FAT32 and EXFAT are fully supported. - An SD formatted with 128 kiB cluster size is recommended. diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index aeb1bae08..8e7c2bc9a 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -177,7 +177,6 @@ static void draw (menu_t *menu, surface_t *d) { void view_settings_init (menu_t *menu) { - ui_components_context_menu_init(&options_context_menu); } From 0068699fec7ed10e2caf1a66b85aebfcc06c05ea Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sat, 28 Dec 2024 18:18:13 +0000 Subject: [PATCH 87/89] Default menu sound fx to off Rename functions to better describe them. --- src/menu/menu.c | 2 +- src/menu/settings.c | 6 +++--- src/menu/settings.h | 2 +- src/menu/views/settings_editor.c | 16 ++++++++-------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/menu/menu.c b/src/menu/menu.c index 91a51a09f..a30ff6f08 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -91,7 +91,7 @@ static void menu_init (boot_params_t *boot_params) { path_free(path); - sound_use_sfx(menu->settings.sound_enabled); + sound_use_sfx(menu->settings.soundfx_enabled); menu->browser.directory = path_init(menu->storage_prefix, menu->settings.default_directory); if (!directory_exists(path_get(menu->browser.directory))) { diff --git a/src/menu/settings.c b/src/menu/settings.c index 5361f0a64..703871aab 100644 --- a/src/menu/settings.c +++ b/src/menu/settings.c @@ -13,7 +13,7 @@ static settings_t init = { .show_protected_entries = false, .default_directory = "/", .use_saves_folder = true, - .sound_enabled = true, + .soundfx_enabled = false, .rom_autoload_enabled = false, .rom_autoload_path = "", .rom_autoload_filename = "", @@ -42,7 +42,7 @@ void settings_load (settings_t *settings) { settings->show_protected_entries = mini_get_bool(ini, "menu", "show_protected_entries", init.show_protected_entries); settings->default_directory = strdup(mini_get_string(ini, "menu", "default_directory", init.default_directory)); settings->use_saves_folder = mini_get_bool(ini, "menu", "use_saves_folder", init.use_saves_folder); - settings->sound_enabled = mini_get_bool(ini, "menu", "sound_enabled", init.sound_enabled); + settings->soundfx_enabled = mini_get_bool(ini, "menu", "soundfx_enabled", init.soundfx_enabled); settings->rom_autoload_enabled = mini_get_bool(ini, "menu", "autoload_rom_enabled", init.rom_autoload_enabled); settings->rom_autoload_path = strdup(mini_get_string(ini, "autoload", "rom_path", init.rom_autoload_path)); @@ -62,7 +62,7 @@ void settings_save (settings_t *settings) { mini_set_bool(ini, "menu", "show_protected_entries", settings->show_protected_entries); mini_set_string(ini, "menu", "default_directory", settings->default_directory); mini_set_bool(ini, "menu", "use_saves_folder", settings->use_saves_folder); - mini_set_bool(ini, "menu", "sound_enabled", settings->sound_enabled); + mini_set_bool(ini, "menu", "soundfx_enabled", settings->soundfx_enabled); mini_set_bool(ini, "menu", "autoload_rom_enabled", settings->rom_autoload_enabled); mini_set_string(ini, "autoload", "rom_path", settings->rom_autoload_path); mini_set_string(ini, "autoload", "rom_filename", settings->rom_autoload_filename); diff --git a/src/menu/settings.h b/src/menu/settings.h index 471879eb3..26454412b 100644 --- a/src/menu/settings.h +++ b/src/menu/settings.h @@ -26,7 +26,7 @@ typedef struct { bool bgm_enabled; /** @brief Enable Sounds */ - bool sound_enabled; + bool soundfx_enabled; /** @brief Enable rumble feedback */ bool rumble_enabled; diff --git a/src/menu/views/settings_editor.c b/src/menu/views/settings_editor.c index 8e7c2bc9a..a5c3accaa 100644 --- a/src/menu/views/settings_editor.c +++ b/src/menu/views/settings_editor.c @@ -23,9 +23,9 @@ static void set_use_saves_folder_type (menu_t *menu, void *arg) { settings_save(&menu->settings); } -static void set_sound_enabled_type (menu_t *menu, void *arg) { - menu->settings.sound_enabled = (bool)(uintptr_t)(arg); - sound_use_sfx(menu->settings.sound_enabled); +static void set_soundfx_enabled_type (menu_t *menu, void *arg) { + menu->settings.soundfx_enabled = (bool)(uintptr_t)(arg); + sound_use_sfx(menu->settings.soundfx_enabled); settings_save(&menu->settings); } @@ -58,9 +58,9 @@ static component_context_menu_t set_protected_entries_type_context_menu = { .lis COMPONENT_CONTEXT_MENU_LIST_END, }}; -static component_context_menu_t set_sound_enabled_type_context_menu = { .list = { - {.text = "On", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(true) }, - {.text = "Off", .action = set_sound_enabled_type, .arg = (void *)(uintptr_t)(false) }, +static component_context_menu_t set_soundfx_enabled_type_context_menu = { .list = { + {.text = "On", .action = set_soundfx_enabled_type, .arg = (void *)(uintptr_t)(true) }, + {.text = "Off", .action = set_soundfx_enabled_type, .arg = (void *)(uintptr_t)(false) }, COMPONENT_CONTEXT_MENU_LIST_END, }}; @@ -92,7 +92,7 @@ static component_context_menu_t set_rumble_enabled_type_context_menu = { .list = static component_context_menu_t options_context_menu = { .list = { { .text = "Show Hidden Files", .submenu = &set_protected_entries_type_context_menu }, - { .text = "Sound Effects", .submenu = &set_sound_enabled_type_context_menu }, + { .text = "Sound Effects", .submenu = &set_soundfx_enabled_type_context_menu }, { .text = "Use Saves Folder", .submenu = &set_use_saves_folder_type_context_menu }, #ifdef BETA_SETTINGS { .text = "PAL60 Mode", .submenu = &set_pal60_type_context_menu }, @@ -154,7 +154,7 @@ static void draw (menu_t *menu, surface_t *d) { format_switch(menu->settings.rom_autoload_enabled), format_switch(menu->settings.show_protected_entries), format_switch(menu->settings.use_saves_folder), - format_switch(menu->settings.sound_enabled) + format_switch(menu->settings.soundfx_enabled) #ifdef BETA_SETTINGS , format_switch(menu->settings.pal60_enabled), From 0f0321d691159c2223c065a5a31b53e286da5993 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sat, 28 Dec 2024 22:54:14 +0000 Subject: [PATCH 88/89] Improve menu text --- src/menu/views/browser.c | 10 +++++----- src/menu/views/credits.c | 2 +- src/menu/views/rtc.c | 2 +- src/menu/views/system_info.c | 11 ++++++----- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index 53d445f46..62ff21290 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -278,11 +278,11 @@ static void set_menu_next_mode (menu_t *menu, void *arg) { static component_context_menu_t settings_context_menu = { .list = { - { .text = "Edit settings", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_SETTINGS_EDITOR) }, - { .text = "Show system info", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_SYSTEM_INFO) }, - { .text = "Show credits", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_CREDITS) }, - { .text = "Adjust RTC", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_RTC) }, - { .text = "Show cart info", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_FLASHCART) }, + { .text = "Menu settings", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_SETTINGS_EDITOR) }, + { .text = "Time (RTC) settings", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_RTC) }, + { .text = "Menu information", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_CREDITS) }, + { .text = "Flashcart information", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_FLASHCART) }, + { .text = "N64 information", .action = set_menu_next_mode, .arg = (void *) (MENU_MODE_SYSTEM_INFO) }, COMPONENT_CONTEXT_MENU_LIST_END, } }; diff --git a/src/menu/views/credits.c b/src/menu/views/credits.c index 3595b30c4..e4ddb9c32 100644 --- a/src/menu/views/credits.c +++ b/src/menu/views/credits.c @@ -42,7 +42,7 @@ static void draw (menu_t *menu, surface_t *d) { " Robin Jones / NetworkFusion\n" " Mateusz Faderewski / Polprzewodnikowy\n" "Credits:\n" - " N64Brew / libdragon contributors\n" + " N64Brew / libDragon contributors\n" "\n" "OSS software used:\n" " libdragon (UNLICENSE License)\n" diff --git a/src/menu/views/rtc.c b/src/menu/views/rtc.c index 3df38a6d5..3becf6c46 100644 --- a/src/menu/views/rtc.c +++ b/src/menu/views/rtc.c @@ -188,7 +188,7 @@ static void draw (menu_t *menu, surface_t *d) { ui_components_actions_bar_text_draw( ALIGN_LEFT, VALIGN_TOP, - "A: Change\n" + "A: Adjust time\n" "B: Back" ); } diff --git a/src/menu/views/system_info.c b/src/menu/views/system_info.c index 653ee0b07..9dbe08a3e 100644 --- a/src/menu/views/system_info.c +++ b/src/menu/views/system_info.c @@ -49,20 +49,21 @@ static void draw (menu_t *menu, surface_t *d) { ALIGN_LEFT, VALIGN_TOP, "\n" "\n" - "Current date & time: %s" - "\n" "Expansion PAK is %sinserted\n" "\n" "Joypad 1 is %sconnected %s\n" "Joypad 2 is %sconnected %s\n" "Joypad 3 is %sconnected %s\n" - "Joypad 4 is %sconnected %s\n", - menu->current_time >= 0 ? ctime(&menu->current_time) : "Unknown", + "Joypad 4 is %sconnected %s\n" + "\n" + "\n" + "Physical Disk Drive attached: %s\n", is_memory_expanded() ? "" : "not ", (joypad[0]) ? "" : "not ", format_accessory(0), (joypad[1]) ? "" : "not ", format_accessory(1), (joypad[2]) ? "" : "not ", format_accessory(2), - (joypad[3]) ? "" : "not ", format_accessory(3) + (joypad[3]) ? "" : "not ", format_accessory(3), + "Unknown" // Fixme: Implement disk drive detection ); ui_components_actions_bar_text_draw( From a7895b49246aebe2f4950b4b0101e6247447feb5 Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Mon, 30 Dec 2024 12:16:18 +0000 Subject: [PATCH 89/89] Update libdragon --- libdragon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdragon b/libdragon index f921ffcdd..a9e651fb7 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit f921ffcdd37f65af0841f223f8d19eb5a5215113 +Subproject commit a9e651fb7289b30e76304eddc5f5a383ff3e2ad2