From eec07026e857555474d965eac6492e62ed6869a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Mon, 11 Jul 2022 04:09:16 +0100 Subject: [PATCH] feat: support for frame index --- examples/sdl/src/main.rs | 5 ++++- examples/web/index.ts | 27 +++++++++++++++++++-------- res/roms/20y.gb | Bin 0 -> 65536 bytes src/gb.rs | 14 +++++++++++++- src/ppu.rs | 21 +++++++++++++++++++++ 5 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 res/roms/20y.gb diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs index 06651793..846e1f28 100644 --- a/examples/sdl/src/main.rs +++ b/examples/sdl/src/main.rs @@ -43,7 +43,10 @@ impl Emulator { pub fn load_rom(&mut self, path: &str) { let rom = self.system.load_rom_file(path); - println!("==== Cartridge ====\n{}\n===================", rom); + println!( + "========= Cartridge =========\n{}\n=============================\n", + rom + ); } pub fn run(&mut self) { diff --git a/examples/web/index.ts b/examples/web/index.ts index f2e4f018..b1a01042 100644 --- a/examples/web/index.ts +++ b/examples/web/index.ts @@ -1,4 +1,4 @@ -import { default as wasm, GameBoy, PadKey } from "./lib/boytacean.js"; +import { default as wasm, GameBoy, PadKey, PpuMode } from "./lib/boytacean.js"; import info from "./package.json"; const PIXEL_UNSET_COLOR = 0x1b1a17ff; @@ -41,7 +41,7 @@ const KEYS: Record<string, number> = { }; // @ts-ignore: ts(2580) -const ROM_PATH = require("../../res/roms/firstwhite.gb"); +const ROM_PATH = require("../../res/roms/20y.gb"); // Enumeration that describes the multiple pixel // formats and the associated byte size. @@ -217,6 +217,8 @@ const tick = (currentTime: number) => { let counterTicks = 0; + let lastFrame = -1; + while (true) { // limits the number of ticks to the typical number // of ticks required to do a complete PPU draw @@ -227,11 +229,20 @@ const tick = (currentTime: number) => { // runs the Game Boy clock, this operations should // include the advance of both the CPU and the PPU counterTicks += state.gameBoy.clock(); - } - // updates the canvas object with the new - // visual information coming in - updateCanvas(state.gameBoy.frame_buffer_eager(), PixelFormat.RGB); + // in case the current PPU mode is VBlank and the + // fram is different from the previously rendered + // one then it's time to update the canvas + if ( + state.gameBoy.ppu_mode() == PpuMode.VBlank && + state.gameBoy.ppu_frame() != lastFrame + ) { + // updates the canvas object with the new + // visual information coming in + updateCanvas(state.gameBoy.frame_buffer_eager(), PixelFormat.RGB); + lastFrame = state.gameBoy.ppu_frame(); + } + } // increments the number of frames rendered in the current // section, this value is going to be used to calculate FPS @@ -544,7 +555,7 @@ const registerButtons = () => { /** * Draws the tile at the given index to the proper * vertical offset in the given context and buffer. - * + * * @param index The index of the sprite to be drawn. * @param format The pixel format of the sprite. */ @@ -552,7 +563,7 @@ const registerButtons = () => { index: number, context: CanvasRenderingContext2D, buffer: DataView, - format: PixelFormat = PixelFormat.RGB, + format: PixelFormat = PixelFormat.RGB ) => { const pixels = state.gameBoy.get_tile_buffer(index); const line = Math.floor(index / 16); diff --git a/res/roms/20y.gb b/res/roms/20y.gb new file mode 100644 index 0000000000000000000000000000000000000000..139c684a227553640090a1db0dc765384be07f61 GIT binary patch literal 65536 zcmeHw3t&{m)$rVX=e4hFk|iYUCP0W0A)p{)*euUPu@EQ_t!TpACqY3H1d~POWvzhP zR$E)pzAUyz`~4LKl!ZtlA|h>3@x?_1<nqYny=;<u|8r*U&E8#tsI^t=&du)3nK^T2 z&Y3ea=gwo(KsZt}=}OW7KDw{&zfbe+rr(5|=ZnLnmEQ2y9P$)L3_g;ZRP&-Y1Yp4W zjyU*$&Ks`J%}tpn5%2M>Idi%?$O^8^1POX?xWJi;$S1buB<DKFQ&y+R)t+GVo-(=S zHzrs3$V25R-h*3};=j7rP{zG|bo%tSQb>}1HhF*~lN4fcc$O|B){owrdEn!ZcR#s) z{fsFytw-Ma_@gIRt$*cmU*EzTyu+s5S><I8%j7_Mmy^3Y_de;<3B<dd__yo*K5lz5 zd;x4F+b0pq<=GtazC-4G4Y~7k@2&15?}3Ev50mY`C%z+miEkzE7v*g`wl(pxIC$Gx zez1@5!v!uV=z@YF=vyi1gTB{F1QN6k$szaEmP78Axhm5IpT<4|KfP3OhW&Z-Z>i;n z?iGg@@x#{*coPVhxL-fLW&bXv#)D~1ckbPEJWPT`MNmdn36`$r4C{hYApfc`7aRl? zf(GmAvp~SNjQ8hQav`+{Dk&MfW?ujTZsVe=TE1lOPRAPEuCP8>zT3BnF9{nUvmq#z zdGmZ@AOH!21`PtHB+2Un+ylB@D4%5>MLxSV`GHO3r7O6v-~)C;BZtiT0=$2FH8w;J zc>)x%NOF0@CfO?22;}~G0(XCnpaWkIz5#qA_$Khp;9I~?06!7@B=D2LPXXTwzU_Wc zZIjFDWV-3|=2G3fuQ9pGM;@L5x(S6}>-z&gvR<$U1xHYD1vO2)XWLq6<Q0N*Q@xP7 z37a4&+O{p(ww$*?zQd>x4hz{uYyH0Cq(KqPPVsP25r4R1(4g{N_54sk9U_o`zy$<* zKyU;EZ$QWi2!#QmPeAYoguwx!EFg>t2$cb0Qb3p$5bg>Hfq<|uAgl-oD+9vnfbeuc zcp)IX7!ZO1VN*bOCm`$$2z)@;8xX{Ra6BN8g#x!wurCxG3kB~&A!ngbxKQY`P#j(Y z3L}wxvpDQ!Z;QJOE8~+(c4lX1r|r5uZ$|BHi!0KnWzKMAR;5>Fj&^01%=_aDx%2*5 z;>vuX+Gkr-RmgRl*PDX5ieC+*27#fWgdgrsg7AvMl9EBa`5_me*_#sW?V3xFFp$y^ zR_L<Q-2d0GR^O4%``!oTbZ!D=l-lym&>H;k<GX>8;m{sodr;!?y<r#Vt}7@7y#sxl zAP{x{?7)st0L^9(`i?{29SIZ$4dO>iiC)+7osPSVG5QQdWxgbbNY>TL=vRGTBUkl` z_ju0hmpI5$ZkCP3tAk(86<0;ZJ{T<v8UxN$qxXb%^xF^GG5L1i!rTs=BbjvSAUgM( ziudt+sV^)L$>$O{$wvg8<jWF_5)3LP$=6LtkbDLqQS$W^QY4>Auu8rw1cwA8hg0$u z32BlqQAn43!-P(fFImWxeAfxtlFur1k$mHX9LZ-FawQ<&P4Z0?x=TK%kSFEXZ*h<S z=jWmY<x4E79}R+_qion%gENZKYbKhNUPCogf}vnJ@3#^YM|-3bd!&#W6ud#Gsv`*F z3@UO+9P%#54>7o#!_J@-@}A2fxA^#S-)DS@iUC96;@*!I@k3m$p3nte^%^h3)-}{f zE4&}3xSKjsw6Hf=BrVL(gwiC=Yx13gQ8-0P_9h@<7&*O)y7inVEzeI#o`*y55UPqp zsFqomcliZKg#-cOf}mnu&AWDkYD$Iq<|1HZdru4IpmokS6$#Fud&KrTEb~ev**ge$ zCQxY@+_@lBco34|%Ym_WPH0c2s@q^WPj&Z$%2$O`VW3Y9N)~T2j96+fs0tHl5om#6 zkOYe)Buj!_5?qqtmV_)x@Ji5vd$;ZnbCR?s|3~ja)upge0^cZw6VOkP!YSydNMQ&1 z4k?_5ewq~SgnlO}oQ;0A6wX0EM+$dGzdMj+J?aTCFPKIsM#c%SF*s4_PW9C8IGIE4 zagZmhc9Xqbd5mC5*ylIeRZAi-Xxfy%xjt9tOwO7QN_5&6g5gZ$ht>;ZtH5m)bXx_( zRv5vTE0gpp9KjV$n`u`y4aRMspZ`1m612v0_^g1>O86}DCG%UrU*@ag8-26*Cf{BB z8{+at9lu;$@ve?vA+BuH^DD(=Mf@_!>jjI6U$%W>QkZP!*M_;x{3~JIW`2E`H1IEn zxdwh+Sl7UB*gnIyeM^eCwnTh+wD?N7xUOQm$*O?s%f$^8e5FKOH(FG|?XOtFdZcMU znnt9lN16ttY5dwW#g+Ur@xewDzmk6tsx2<#SB6bkj2VluU@;~v#*D>SzGg8FTQ~t} zCL+xwq?v#;6Om@p*QVLUl0)9%NWf?38$dhI0M#X*|5vL^uJ6lSb%El<hCzIT_f5=M zUp3m$Eqil%Qp2JIab=0PVud6R(7h}3kBTcw#FYUBdQ4odfgVT9<tpaSG3_xG^B3Zy znzUbH+M{aP6BJX;_as8?<^766D(0`nNAqS^jSwHJstxc@iI3&o4Zx#SwJZ4Fh%2gU zxAowEn>Snh1w=kkHA;LE5}-!E1mtbKOY-g(e-4q4SJkcz@T(xU9VxJ|1oG6bK&07# zFbWWB7Y6v%kmc8a_A4ylX&}dP04nAHve7w!>@&3NCxHT{LfKEWm#vTuEaZO&h5QO= z{TiA0y|}1CY%I^@17Z_=FBIQ^@B76-iTKQlyg6WdE~eoCgvG@rP>&@dl>Iw34a1SN zrQ-c1;_uZo47aD{ERh%IER`4Y59G~3ichV8N<I|;VU&xjFuV%G6-fLy2>u4a`^&}E z2&_infeNJlTZDhBBtDJ6(=>7P5b=S=KvmxDwfrOHd2^sKD(;1Z#~QsQ_pU@>Aq7UK zZw7{IoaqhooZ_M)eo?u&W!>g=$vC8J71zxWH&lr)Zy2B-<~F@rs4sCREO>Q*VR*x* zDZ^^`P3`H0G<^U|FNAcR71B3vtywp~R5PS}-pGo3SMVEW%v=9Kck8^z_~3>QmRK7` zTIa9C;H_3MIKxLCuiE}cYqGfUXN5Wg4bOjN^Oe?yKWI`@G^z8}ZyrxmH>!CJVi58s z)4cb-ym@v<sVQpeN=)6lKQB0MNmb2jg<N&ln%4$!)qA(*1;tHEstQVKN9WyfOKthB zwH50&AF$@#c}s0c-ppHTM^DD&ny0(IRNY{((P$dewdSSjZVT2I8hvuL`%L=G8!}(2 z7B|ljHx==lHf(;y+9BORjDVIdko}2<>=#mX*x-FP=pgdF_|o?S{%k$ZA_|kOXbyFQ zxm8L4bLcrRhcYA^gnmg4BR3dEQ>bA?wuU*t74wZS3A--|8)(KHn2W&_VdyTJ5lJLU zZioY0%x%OM+THlI)6r&+F==4d2D3HFb|r!y%~k{2v#J$K1T&hg7K~HvSt2B%*_w=T zs%1+A8-jMUfWgMKqm4_1RHWy^IIwn|Xzdaq1L?Uj4(whR+Py^RjP$ZF4lLkww1A1= z!Bj6209)9LwlER8A_xX?H#CTI!61gX9!M`A<1WFk0*t#9>GeeLGRc=G^uoB_NUspB z@jjBTQ0R+smm|s*7}rno^%eSK+?7ag0LEQ~p@A58HPS1>xIvPyzu?2TVx;Fsl);kE zFATxBp-68S#toNzLxd3+SAt+EeqDo9uEn^K7`hJQekA!u2%|7=G}0@>H-j;fuS^(= zaX&_S<1lW#<QpqYz_@bEc0I=3fG-L^!MGcd-b9SMN%Bn)ZpOF@q*sXvlO$iIFd5^f zAib#=H%;<Q7N%p|45W7p#@#CUrV6)V-0cY7fnRq@zUjhDjH^O=v+ynDF3C4bxEteU zBfU8ocaP+|TbPS+)kv=f<K{`eIl{ddHy=qYz_|M)-&~;<<LW?aXe8G2i}+>ya()HB zvfaw`%>@Io8Vtl5{td7!wsc@}f`*quNAcsDi6KKVJ=={*{g*W+!47;i#-c=H@U`uO z)6qO!i{{}gXvVEaGwxNi5no0daUI%n8_<@cs1XwtGOfo<TB?v~17@PAV8gCO^HWO| z4B3~l*p8?I{wr7&upZl~0{%Lz$_A_jr3&V;LY3OHw60(yD^$^_ZLcd>qHEEJd<Bij z^=L%CistCcXi2U^OL7BRlCN!VNed?<Y6_xS5!HsM$%vYQs8&R^DX0;ukZC<;(o%&? z8!!{43dOF)VzpGE*q5=`j;K;wmr{jVC{(GzOQ}LFC{=0}Q>sw5QdeplQ>svQdtDor zq~lw|?({G9*6?b%_!@nQSYIM;7%i$GyhLpM2B^UN_>CIhkEJ*KBUQ0^UMoklhy!bg zyKmh9?p|S&3=xG8Q3w%*+`VA^oZhl)fB1E|2;Pfdmq(lCC6?TfesAXJn#KVzH_U@? zPO51vg*gGkMhIUIa{-1EAbbnV0T@n!@BkS5=Qa9a@JD<X4X06jHw|~9_*pcZP4T@D zE``Az@w?G*cZ#1+!<SI}o-}+J#V>?#84TWte+3Qqqxb`8_$rEDM8kt9z8}KZ!{Chg z!)SOo#V@7dYbgG8H2foqUk2ey7<>_b91V}B_}A0$4HSPO4c|oZD<OOf46cYjm4>HL z{99=FR*HWI4c|%eXF+%t44#NThlcN=_%$><kK!+&;rk@THt31A0Z#E7`BJwBrual3 zF5%Py<bY{D#rG;egJ2+p70RtqYuVIbAk1JG9oA8Up%C8Rfm(z{e@YQd1xi03D0V}` zfex*(kiC_e4ud+ym=1e)f>{9z$&pnDjxI5Dxa!b42v;50{spe#;R;^#{zcAH194HS zQs2HVLAO58K!KG6&C-K@tM3h17G4NzzOZ0~1Anhfb>NR|9PHr-n@ATz7ph>{v?Q-i zJUWO!+UTmP{dt9Wq=-LKHGlEX4-^OZAq^jQIuFeb?;Y(}y(t=O9W)p+Vbz&G%pZXj z&Z8#s1w=ct^I-G9E)K=j(V?)cVx-MQTS#Ut<ii$Xt;3eZE~Dlgw6(h{w2B8?rM|sE z44Je+-d35XCf;sM{nXmU^1=@RR?e><w-uJ-bnc$oN?_5QX(e!#jwu@m9V>yWZ(GB9 zP$|?e{Z{u^tNqH(G3ta`wuv)jZrM#&k~VQf$Cj*M*)hNoWx1+-6|3T6t5{lN0rnU6 zfkG?GSYW38T^3%F!JJ$!`$S=d40gy${8}Un%Vgi9!WP*#RA`jJT4|Df!-Y3wpW%W_ zt9`nt%D4PoJuYFvLKZAxL6|LJeaDtSZi3Z=_9c*s0K_bT+;pKOkcnSr3FM}zC6I}l zC6K(E|3ga$%c}DHwRty>TTu}RRAv6+mPUWc{6#A(7A^#!(LY*TRF$`BLbsc{O=sgu z=I@>9gRjq=0WZ_A4Fhk!iz?DLEpWQ-bGmAJJ3ClTX<uoWo{cLF4W(J<cb#RjG#<L= zdRW&S0VZn+n5?B>vKm0YP_do`6?6BN)_CnOW^eIb11Q%5%1HhVSdz|Q!|PLBI_MA4 z!z<f4K9S=eivy*oJ8z)Gu~1aiF^62|sQgvbxXBOUhq@)Tf9t*1VLuj!!16s#G;ER? zfH?}B^BcK}Le7Pw6LfRfCp=OQzHjUXuf^wo)vE{Uxc6L`;PO4S&Z~#cNQVM^kJYoe z>KPa?*n8|42&KUIr7rO6&wmL<8SFk!t~6HN+Ku~h3H(PSAj=YUq;lov3m3rY^ag{` zWR7515)6t<Oj2Sk$!e6rkfKCdBT)u}jUw8$Q3itpBb^3Asw*u$!`&$}v$MgFg>l({ z*Tv(_$?e*$d!E721LF*a{7VWh?Ri<R-i3YoUVepv#r5ld<$$XOUR^ZESL`>?xWSNj z$k1WKN0gLa1Ho$%ZzKe+`_ZV;Wn;$v7=q(4Zaf4uZUV-Ua!rikdKyJ;_(>%4#)&MB z+;Ec`d9xY?DHWBI42H>5rYcBZ$<ym?w&G&UqSt#ovf>CSE>^=G`GmB#YU43~Fc=D{ zaR@UP42oiFs|umdae6(LW;S~~#l>}Xm<Mz7d??h~DoG5E<Ol?UL9^Lri(tz#&x>LN zTb7X;VlbS#I;4ge3`de!xmYq{nyFR=zy-na;B3@kexwlLomK`bMBOU6i~L*qFxVzf zr_T%}U4^af@C=zfEJBnIz$3}3=tL#|0R>-$rz@~5*C~%k9rA-5wYFj%>ojhajL&c3 z!<>nhG+nkSFsnrbHpmCznbDqpm3)%M@CrWG6Q5ZM%=nxxiwZnTu7xK8tMycpWvnCX z<?P!ACFzwXSx%N4VjfJ#2OXwsU7=h^K@mz1!h^o*isj-c7~@qsDo!jt%+soQh)f_R z@)XHMYF6rFy&L3L<q`5v=;u{{kfvH^^pPKxKMZ4f?1OaG2YJ#ym<M@7ANf=1VFgsW z+VZrtF<zyMKJufbjd4g*eUP3;b?Q);D5EkjzfJXM6$9ZL+ZDA|>v(8)twXJ6P6FMQ z;CPb?>*aa$(QDCxw<n`ute;j_RQ+bXk@d_cXs>%)-mB!Sk?)35rk|&uYRRg;sy)-B z=&RZ@S-s|gB$#^=>V!{&2F5nPUa|vZf^}lOkM%?xLvSqNz~R_}V+f8VIHn+s@+~e# z4s15WME)2BQAAA4iFm4G#j-q@gTX8h%faYs@fkK!Cj{{sHd4pY+h)Uf)iHXq?D4Qz z6-FONe=UrowF-wqNDW7OQDh;NVzf_{4i)dbN8%)|Rgdw!Cs4<dZ6d=f4%$R>NZ-my zGDp?iEUS-N0o9M9%jh8uiQ_$(#OT2^q6C<yLTb7>q{gWD9-fo5`7ypYXb!dN@sYR) zUYET#IN4!3N8~^q?+F(3p2}5)I|_FUXsTHfGGpy)@2|bzx4Gzbd|0)yg^E4JHjmPt zDl=@3Yi(@_9o)O8;nn5yJ=iiZ1l6e+YPrZ^t3G5FZCGZYPTy({*~F}USZ8xc#VQUW z%zH#z$joYv@P!_fek83ph=>tbw6TmHUX>Q|s}jQ&L)?%V>%?k>@PIeer)Fu*(vZcY z_i!23%<L-%RL$BmLv|xyb@HmZ(Hs9%@jH1;eLJ5RfI;J2=%jdX@7oQ(SzMf4*J)$j zzPcuPNwZmAUS(^xRoSZ4(s&Ph1~e7!nEi0|Qheq=SRBM?o49<I`mp+;%us3(?l_4_ zP~U13i-UCmm9J<Y`HQqYrXdBCgQ|H%W|TZU%X!Xn&eCEzX+LQ{;W%qQkRx|)v$a)k z99r?a`Y{zx*N+LwA1rwoo~6q(Qy%~Ik^e(!6w4kuhoH)X45Hi%wR{cP`?>8!sabrA z|K~oGLGK+Et5#L8u^Od-^2heZ8WabUPhuVM(YMOPdOT}i*1iEvvz8-fuvkq)A3CJu z;XyDl{7W3uGbS}{j@YnPUob6LjD3dnB!&x-@I+!6%)aXa9{!;BpjRxEtk~}*on*Lq z^~?U_oR<zNI52*&|F4_hBQ`RE`>SpOS5NdHqg444`LQ}qPLz$ZyeCRrO8;kL4fdBn zown6cCfM%SckoeL4e_ymwd$j>v278nIJn>eCKD!Q53g@!{4s4{T7ufA#-bmn3l`(3 zrj3&gFhUUXIrBN&anw~p0wfsp@_WhVz+PUokKnAFL2u>KbdPY^Iv<xP58wDq#qf>D zcM|GHl-mgU$$B4at6(wG#1>T_HVQ<(>2O9RfKo7ruvaXsWwH9!g|%!HXefI6W;TNG z9`sos08s|iaT>Wp{@7T<aBCOVF2r&XPkltYXg^LrAq!NWWb4)$=Opo-gYIH8m|nyt z50HE8k&4@pFA!86<!>YOij6dcjHtv=jw)ZsCq5C*umwS~z!P$#>Y!RjY`3~Vaj;ci zvxN0oRR^Q{e!JjksYI?_sB*%5maF}{eO`bVzZ`%^+t?gvbsc>uFR@?Ea&!B9#WYaj zaP&eMN4Q`dsAD>&tGr=Rth~B_S}e{JAv2o`)i_jcHrlEBj=5NgERFREEEQ_3<q4Wu z4$3^VlsZhIBHCccigNfSb6h%n`pn@S@^VP7sIRX;`+G;R%9rZ1RzNP)o)Ih#fnKN& z3k2%4F!n(eX8Nf@GGXnn_JUwB>(|(4SwFzuraYXaYfT2*C|N9Q@c%48hZxe^Ri0mK z|HzwPcD>hYzl$_`W_bPtpC0g8Ypb@g%uIebY9L(HfE4#-ZSp;G7fV;$i1*;UsO|Br zY#iwrrcxIGSer#)i9-y8TlJz%9jV!P6lp(=wgNwG1|>fySwrHi;0t~Vdw3XEm<t#F zE0|BpR4~TZkwQqr&$ICH5<A&wtI)hX%d*<{kVAZ!WLe+CR*ZzO_Bb=*6Ft{r3O=ZP zYM983lL*X^R2VEl8pOV84n_1<!#_=LGfCzaOPi(Df+LM+=SdJo)Q+4EZW$#KBhf)( zA{kGffzNZ?C0x0_$_!k4HWt^_$uf>;>Wqq`n%WytTe05-i&eXb)z2JaHDa>|>Y>Uz zvnwLHz$A#da2#TmiU&q~_Jr&qd#gpVMAzIK1sb8=X{3OpahK{VY#TjT-wN}_V!1<y zeX@NSM!~3))v=t_KGGgcgON9^9kwj1ISdK#U@ybbHPQ}@545`36XBZ8tL=7$q0JOd z3MZVj@AdNDpnn`$Lr#-gC}T$<ltspK&p``p)VG+=+M0@+6ka5#xd_8fP^tHLITwbd zTzPyuA2>sx?L=K`v)C-24V?|OwDK|>6QzTIkoI2wSyHT6S7?}Xpr6Q#$~*d0iKyO& z=qMCvFHDh1$jmVC8|`fLakTXC8cl>T6Koe<tD)5p^t1Of18u8B@)}n`dXpPTKXNtk zKxj$bd%f$+-|KxiqdE1xOAciOWt=qdSxCRv0FMRmsh2C{wepR?9-9mIFiz!`)^Mvt zA8IYjKLxE;Um#)Qjxa6z{f%$-`>gXU+so#VC8YKYDu08bQ99@e*c&RyMpABh&NVjy z$DOe8#nHPieXe}O`qdR>^{XrV8?jzau(upA?2ndTgL;+I8ew(RIU2)~d`><q?FgNf zn_E_$EZzsN%yhW|>xS>bLF#zE#{85%3+$WG<WFQM)I8|N2Qiq#-U%y%w3lLNPSU-` zHeDUmvV>SUSj7eTbMofmKU56axUkN0wnDyC&Y;p-BfDe^)CqNGpS+{hBNp?;dso4? z+$d*(nNb4?t-uD(Up6qgJ)jzO_`Z&gz0HdvAr|6-KIQ@+9TW#oJzUOvj?kAaoF6q0 zMWXf9!?&JvHuOszXsjR`^flBnd;Ip&wW=%`F}s`(($q_O$=qsdt!t2%&?%GEr<koR zsH=Nz@CNRH+Ki{4HQ?hq19h+}ZZt8+nJo#4iAl*RR-4`7gt*9hc2+i9#x}t^wy6hQ zz&2l|E@8vkwW)padN5nFHkF!8+%?yZgw^Uqx?DYO{Dg8K$z7j$!%uFUc+<@lm6?-Z zg?SprP0yTh%dNNFe#f0NujZ;|-F5eDlW9(7<~`ip>Y91?&R@XYS6dfYXfoZOnVI<j z_u!(%OO`I<xQBlF@XsJ`<|E5jtbFvb$AA8dUq12VuYUd1Zy@fst5!e#%<q2xhd=)5 z**}vt&;13<`0MlaFTD7-{{x=?-``(a`?AUO$~tcShF4$P7~BMLn;Tx=(%AII);BqB z+gsb;-ti}nduQiA{`s$6@4ol`ZYn4G^}&Z9ef-HDe($IIB5|MXKXCB#L&D)BM~|tI z%|twY;^Zll>GT=au90ulajk1<JsmnMp5zbm4Z%l(B>~iuHr*a_I&r>XS3$GAV)aIU z9gRH;DeuvbMD8IiWEW|Mkw75j<aw@^`>5->;rBcgHWtG>u}%I{g<k%1dHHjoVbsCP zAiNW!dmylgBoQ<Dna*k4pwBdpUsGQ;d|iE6#^=wCOA~tSWO{m<RIHY`iN6b(U)9Yw zGz~av_waxBR|R&Fdmd`G@9`oQ)@3}I^V2Z&D-7QReJc%yI&^jL;8$H8tW03Z@Wq^H zyB_rUmW&gy{xD=?<*GDc;FdAZ7wiH8vQADoGv@hEvqrqUqViej%&S3c&qEmlL3h^> zKS5X^IP&A$4gos@(}#mj+ab<CE`bh@Wvazvi4V!2?b!_D|EKaPIV`tH549RX`7ph& zRt8u$PGE0kE};0T*A<rdVL=Qw13b3go?=gH=u{{q@?sE{#R9?CW6;$R)UIUrgAZd6 zXVhEFE}Pdgrg&prh1^>%fkADk+@oa-6$+INZ68@8fyaTb@lc;}U|HM)`EjI0y4ZKi z!AHwuJXuO60?(KZFqfuJFip-b8Sv0OE2{jo3CBJ6&^;4>_iVZ8+>kbNXtbFAxlPQ3 z+4xz~M%IC_50j1liq*Y#uAyPf(qcGkd49}u<P5wahs<j&`?5aq{<X}*58P502pj7G z1D`oReYSj8!RHyL6Q8;1bmE%v*RLKFEM5JgpC|8-r^zM`#82g4M>&J9fUxW5LQvix z7UgqNt6Z`A`7t*=GjL07plU@J>JTDp{m+p-(2f<9{$cpuNJA3WrVAU-Se_gAiMPHi zL^k-3kPx|_Zn;2nm@}X~JjF_vqobHv?SIsVv5XF7+DI7I=$NLB<6y(CGs%jI;<5>j zcTsV453fhQO&e)fcJj1w9NpII7ZvsW2SCxj(~jeGIz3KhIEP`ot4)oqo0SdIm&fB} z&g1FQB|9stb7m)ZMtYjd>9E_ZDM^V57PHA{H0X6YXf4Jo1c0i+$f&Y;QJoVzXTh8Z zRNIMqx@2d!6HRqGfM~Kp)u7i$h{DtxT~(ZHm5t*Lh(c96XJS>;TtL(wO%$j?O;wv` zq2lgW+yjbxP;rYCw^(sY6t`4y%M|yJ;(n^QhZXlT#XX|9<%;{Z@Bat<Uc1Gh<GwBb z+rIzLaR~_~J=YdKd-iO&jnkVFoQ@PTckX2KvF4NKICF}lQ-(8vJA3TV!9&N+atY3i zE}heoxYLLCf4cwhX)Y<PvnM+}nL8!y+q+LV#U-a_$EL^TkEI`5{(pC<@a+9G#vP1t z$6_1^D$2!<&j~Tk8RI&|xGpj7zso<&ve4$(2C||9GPF6ik*w%|3~i2WC@VT3Lz`n8 z%Zd)j(B`NEvZ4d$rN`!vr5{uNf0_SQkboq$Q6sahtp$K^OG_9cVUxA3rKRoMxp2$b zGiT3*Ag)bnYikX+&~;*XFQ-N^-99@9S<XV9wpO&q+R$b_edg>L_=C5owwBhGHUkda zoDR->7&#rx6($bO&ghLsuntYegv121#iYkc$Yj(L9dl;DN&u4uMkfwu79Gq8iSTE! zSoB5%;C&M=!C*;rxE$7`1eeQo=ImLR6F&d^^Mh%w47<~5w>eXxZi!GIOM(RsL2-KS z)bZnDc2=f4!{y3w!}OTxc4uaNhOU!6&1O$Yv87lc9$sGXpbG~JHfCaTS`8Lf2rYt^ za2|b?x=NQ3MVcLx{3h6k8tM&_q=SVyJeDKLIs$FnCLt&hXlRIQgD-dqm)rCbtUwW1 z`5<r_4CZBQ6wD-HyJORGMr>b$0dxTK&@apjt34d-EaB=O7Qsq5kG@J>rOSvS&5lVT z<$v29IdSa7nb4unj~qF10#5U^NUbd`p_VhpSUkiX`TWSCV`mN=I%G<;B&JvmdXp(J z(Q0+T7~ybO9Vr$T4{;{F$zVy*84RYBgcOHMuQQudQc_dh8K5i~t_(*4jZcL*v(Btf zaKNdnf4lnM00C2?i5P&V#N@<8lYz8I1|peEgtVM(37tM03bll!mKI`=jvhUJ>iALd z#HmxKPM<n;LOgotuz2+F@#9C2!qj+hKg=434jw*q=%{$u;mF9!aHM5)2IH}F=ZrK* zVoI7LB_qS(NQA-+u%eJ?N=Zy~q@{cV)&Dn1bk6SVhB`XZtV!^3KpiuZ&B+$9XDvw< ztHTX($cQ63IWZ~0oRDaS@r%HRzSzS1APk+*A0&WLPt6Gl*rqy2AgyP@Cz|1NruEF3 zR`IZS<jCPeN1BhEIMW7kVOUf;gDYL%W#`Q&oucaJ>5<pt(w^S@{2na&QuPNARKI5w zdOMKd%1$LZqtQx`q>*!`5|Wx=)f<V+VkEX?bwSk3r5JU#P6UjwR2NC*GJpbNB*XOJ zA~w)du2W}>vq2Li8+A_3g(MPy9Dx%yTxx>NN)ian4G9J#5CuFZ;Bl$#SWvFT=5zr{ ziVZ1U*rnK@3Q%N%i)3(E062rqiYo^CB$8kyz(z8N2c*$0i|Eo^P_WgQZgc{k&IYx1 zLJgn-c1&@>IGdadpxKBiB+cqdNG8N;%lP6-&{AQUA{|8Mve?qh7NnKJLAg33jHWKm zpfJQ)fnSbf7>&fjrMirU&I)BJ9*|H<C*pD@Ks}9AE&!Uibel5)VRWgTAXf@<Olz8u zV(hqTAYL1>*z88q$(F{l5~DTI)(O~6aCMwVK_jVFo70|VHGeS{{LYuEPpW?bz0%tZ zAO+yuY;qG_r(U!iwGg$?3-~E%snA!|ObQaz&yZ|0!sz#f?#tHCW#OzzsjOOGM9|Jz zso<?Hi__+2n2E5mOiWIjCBq2H#vGSyOiE24Mxze&L2=kW^xbkvs@@37$(+fVV6+-i zsc1}GN&@PsIn8P}CP1Sl*%H*e9r<6Dw~q40<9_fGsPj#kG-*;@9eja<vzN61!DrH> zDd59dNKZ}StjfyDS(P<~#U4Ux7tEN9In?yJy2{F$8bAP7SBGaeBXDIU%U?ZX(xkg4 z&FfR_;W*5xmWJgeM>))oCqvPhb+ro!nKEg{<e5_!)XbcU<q|>*QogXf*^}yK*WGz1 zk_ATylyld8w^mJ?F$HT0^n9~v%A`qv=|lRg=8OcUGj3);j=JJVx?0|XqDp_A509&w z^(c?JI&FmsQHX;#ds1;16`nHb%3Pl-%VJT|@0?V3XWi`CP@gG-t7(eQRh>I!FvG{3 zT_{<kZ`RvPU6PC3HH9+p7jbTI(U6-i<YPPwvD7=jMeqi=2MnMX^b6XrX!;1NLcbRH zsjUU53z&E&s|n>j0$i;RVRV@8_t!44LYSo&fjnv#i~_8gBWjB<9aF5yS;<*Y0@5D^ z@kj-I1*Y^^{@S9Mm<UeE5AcKubLY<OZ#DS<AD!|#!T|Wb5?ng~fg|7|Ffv96Wv^Wx zn9AboYN6eHmHq`l8}Z`z|Ns485s2Uar}yFG_y6Pf|Ks=n+5K6(a38<_55Io>pT7T3 zG`n=T`3D<yaDoMP)!+@>l?GQbGu-Y@cq<bYb6_hFw(W4^F0vu0Y~gun93}z|><@Ov zeYZ|-Solg$OLM{Q8*IP9Rw3@d;l5sEyO7}Gb8AaDd@gkM>={^CI(g#6ak07i*s-Iq zU?K>I4#8^d!2<{O@8AE~XZ!Z;!_A7l=;(=|$QO;nM8Ns%Gr&G@;NZc}KR*PAOAa4_ z<0Z$MML1Ui2TM-F!gMGEi`p%%ZLnDVwd$|g?(De!;(x9FO8c2`?>5qYusWHF?U#Ph z_KVm(uzW!GY+;>VkL$az`ff6tEl4&o33i3yWE1YTf?l9nu&v?t`?>U36P{^;Te<z< zHZEMoE%px?I($UwH6yPZHG0h0aTCgKxN+jmm6NATn{n%Hcg&o1_nf&k_b#ZdU2t#B z+&Op8nt8`<x6YV0Wpd@s6K}kse8RY~V@8j<ZsawkBZdzh;x8UlboEsOuIzWk<$Vi# z_qy!Tf=lvy<aO`XH8;l#JICtAF>dnWhBRy*Q#DIWKt(f~aA#UsuGZmJGN<L4*u91R zvdUS36{}wezQc>0Bd5>cF_Z3Exbo>2H|^YeT<6T~J7nzSyYGMWnZIrR$EPRssa-E0 z`r|3HA9(C{|JU%(eJ2gBZdVK&H+9Z~kN^Jvz5cJyP8rj>_ZvQb+C7VY{)fNA&kCJ3 zrRVh@F=6`L#lQIDOO5XyIAhM}ab-#QjOr!7{L|W|_YR)5xbp{;UVlr?(kGsM`HlBK z4<&TE<f?0KxOLvLC;$A)*4>BBC1w^3y!I!z-TTn5)~tK;10kH$`O>RL-gx``pZ@x} z_1iu?+>)Htv*@~scPx1LslRM^>!TyBDcP3|`q52y-uJWL{PorCA0KVAcIo9Cb@R;H zM}GVKYj1yYOtN`;7mu!}s$0IQe&dck%`yoDc`*b_nBP@Ds<FO#pkY#>I~g{BzpMT? z&v7)xKa9P^m1xv)-&OxVeg7Zyz7jNG@psi{@2<tg=wHbFJNN%dz`wdI|M6?Tt3KS> zELQne&p|Tyo4Nn5p8xr_eD&PXx8<v&fn9N`JyfVTzv9Xir>>XHQk*Kc6^dJ}xEB-` zRNOm?Q!Td0S#MT@Z>#*g{DPjB_3m?dzy1RTUJXAWf=94Nl)%r1Tzl=vk=HRda^$tw zUQ=3Hg0{$Dzi-gh0|)f)cX^-Qy?Pep=XJ}?@nm=I<W6^`;t8|%Q)_5yD8gkY;pq7~ zu&}K*JhGP2sdH8rZ*I4|{7Wu{^7~%VAB`qfKK<E}*z&Kz@`tJAUkT+G((=1^#qu-V z>1nk5WOf#h9mQ)eAC4|4=lj5rv)O6+nOJ_;b1i5EBO=riHZa*qRk#%bYlTYK+^XJi zf><>j^C;m+2v*ue(h3{<aCSQA=`bA*1K@!OIDNfwq52ea#bjdupH-_O^aBAK;Ms`T z++Gr{<f&9!^>81c!4FSRxgNsF=GNAO<g7jVIhiUJ9;|E%E(w@n$2~}RxP!r-j_R+7 zjCyK1y%fgEmAeoRr#|g>5b)NlsWmw?famqF|BK^O{QiGLEtnkDh6C}?pgE+*C~}6I zP3%^s3Zws@zW*O{SPfrxzQ842z-ia;)o@Czvqn4iTN=u@I{bkT$4V5}esC^Tad1Ql zT^e)g=rX9ItJU;e9bEIkcXfDK$G3HSU1#s=IXOAGxm~+<>(;G%_q@Cw@V<6Q0sN9u zPjy`0sknbA?w^YLm*RFQ?p?*br?~eOw_9-^DDFeWeWbXL759na_9zbTbAFrm|K+-3 zkIk%yyAR)vk9U9D@9C)>0{s8h{eR`PPyV)iyuHf$JG-5Xw;Tg?8bQ$Cc0jEE%>Dmk z#7X~-If?U!c>n(xLC%>J=($c1eH~6<^oHR^8VbTy*+3n8ux0S(EI!J$Q=Z-bX7^LS zZ}<P%z{{*r^?p3wy#1lv|0iJdnJfutmfCS?k1p)Nxjl8>!1n=|LGacGtm!IODPScx zIVr&cXLa$oXiGSB_Vg)OwS^_y!-qaU2y3;U!8&bZjTS<%E(?pYc-ar{`oT)<DL7gP zCyAwRyZrxk{w^k8t-bq$T>m9@yVqM-==YbE!Ksv4BnIX<ha)GaPoKeq$BcnfCwIld zI-S#*o7=bVkRfBoPM&=C-LbG<pPJgW>*bdZ9s1)RPnj}%b}Vc#xLn=3U2(;*VdKV4 zojPYuENnEUrFHM#uix<D<Ht{%cF#Sru*sC3o|o6Z{|GqsFn#*mxv{X>oRQI^$CX!> zl$4jxm{DCF3!gY)wc@9_+2O#?5AFW{2`hV=9qhqh0r(-^|HpTd6X5WbqZu5o=lqcF z|6go;Ja2x0bsoH**gpTs9nXLJ&Y%C3%kD7$HRJquCWLD*c>ls=hWQtlW$67#+-!}# zbQyK)lHHF?O0<}b1|5OB7vXbf&zw35yQVPz3UmU7ol=~DmBlt?{#7sAL;2!~leB!i zt^?bzu>X246oT#7)2B|Jgo&JPUji}Qe`VXRIH#-ouXL+br5!1Pmcq7QajJ*ySD5V& z3Aq1yl<vPChkO07|9S@YU-42ATra|vCuM1h-aTde&$!Ll!Tu$Cqs1*|y4Q>iqj6tp z|5>|#ORto&El(qEdD1;k*z}|e0r0wyuk-Xxp1!@G|GFM|b5~#JDI#XUthitR3kpCF zt}VguR6+sDb|hRXqI;2GV#5|VT&#j-fPL`rxpUAKuo({5r=UTgNnlSL9rn@_#}%h8 zpK5(IjUv)4cvS-&5CU>Q6i7n>EwJ_7ikEzFJ)N%mDO<ij#QXm!eCFDPflH-uYng7S zCsGAa+MjyZq|l!2M;Ks{SLs5u57A}=fip+NjzVW5M~S1sg@zV72x9-k^AFJGaQXo| zUo)7|v=<)$Gn(1atjFQm2G)n6N6{_}CUk3?%;|6hNsQdvhTVBQw1Ex+><9+le^zf* z<LL`^fjGiHs}lSE;oSdM#$S*gj=yQ?ZdjMkhMzCMA03bSt#SCF7C6@cKQn%HQPH45 zgM8p%Yf5nl4H{HbboJE(2VON`z?J>`;aP<~cvzt)J*<Fd6+B(wqypYWkD5Os?@!Ig z@bw8={nNdBKKb~=4|c!*-mZWB^B+6k+41)Fx3;~xb?X~Vjg4Ejyx!2zuzB+)aO&Pl zFo@9R&6^t<UWcDz!QW!p`X>D;7Ug$X-rxPfhw$4g@Y5^%58xXZn}FyGSi=N=!%}|( z?{6T38$S0B9X$4$>#iF;%3F5b;2VaI^-h>}_v~A%W_tVdD)i=0xuh@b3V8?LG41x* zw|a+9n>=l%H-FlllWx6Z;3OL7pE7gG)LzqXy~G#b^XhZ#vNbU`H^tkM6Kt}&X$bzI zSvTQdP4Mqi8k>Z-nuLEe3A>wwVAEDWsn7M9bA4QHeQwX*)x8?GHEtUP|8K#<JAs<3 z*IVsy2Ed(q{iF6k%Ned3aFXiztX>Clt9t|CUc1}TasaI>()DrG?iX@S_~9SY3tO7D z!b;ypPsOS|?9Hb>=cA_Fg0=a*x7B&O7A!vCxvZr203WEX$}6cA_BKJRsb1LE<hJ=_ z^enyjg2lN7k5uP0HWjShd~9&P&F>9%ywQ|dl~;Pew$__^-?l3c*cyYQO(lsXV)p~b zUf{deuFW4?azH!-oYWk!WjnhSZ0o(vjh`hmY7PW>$9-8&(}=9h2S<lLZrWDjwI%YY zS<d9dEaw?u+&wZeIm?-qv$tt6pPK0GweJxc+ppV*-p}SI+;>0>IIFt5f33<1>b0%W zosk2SM&#QaZ#HFS_1X(LGa)1I%6SWG{f8m_Ha{}N@pgNyU7A`i!D@dTRqc!ptDPQI z?a^3mcYC!vX{+s4tKG@nLA7afLdDc-kAiByqg2~XtKBK*A6V_b4(+%3v7wG#SnX?3 z*lDcV@1U@k(0Z?@_4Z~)3+E^(2$0y;*znh(&=A?9GhfLcynmjvD(|{_KC^%?Ea3YT z@O=yT%M17`3iy5neE$Oe$^w2s0e@8iKd^wmx_~b#;0G1(@E=TyL9H@{kDB&^P`dn6 z+nC$(p*bu0toyp`(zIu}*Oka;Gr@qaE#|w_^PS!OB8{3&r4vcY#I~D|pXzu|5kfW< z0%ptE4RY|_+|PTX<3p6fk5CHfObR@c0u@6wYralQ>vELl<8z_Kuv56RqUG~1P#)S_ zfc~~d=rILrg9ZKaP1}0E7IbsfJ?mfcX7%iO#m(uXGuL|US(&}pdW}WcX?s^!xQ|@c zH@&G~dKL5`cbBc(O7e{dB&R#YS)1+5_HoYaOaOb*#2pt->?+Cmqq@ASBDh#?$(h~T zSuY%D0wQ5b#CkyL@?nH-K}nZ}`<yjv^R3wHmNfEtBdVY(hlG!twstw_GdgP<MJARk zXYG>4plQVDta@iwBK&LbM5p`tngiw~+pf&{q^WOx^;Qs)u&1dWvqCV3Z-Q|sw^{*v zQy75Uo_+hv6k$_SZZ*ZO7aE!rG{rj;zyKRxzU$vT$#006yPez0b=6htnsiS6_4;4y zKhR$&KHV_N@POg(hW!SI@oM7~<1*u4jBgu{8k0;tOhZgJn(j6&GyT@|H`D8;cTERP zXTh9ynRCpSnXfVrGmkdkV4iHg-8|bo-~532A@d6JFU`L(KV$xr`7h=d%`chPnO`$+ zGH)@zVcuqb+q~2KFY~+R-R2L?ADj1FFdptae&KV&@N0*U8QwZ9VYq8}*Wvw!4;g+# z)C289O&)qMkQ!uYk1+=(vT7ge;I2E{(Z|!OXr@z}4&UG4M{dwwM5`WtsfM(*;M*8F z_|b%xHn1;@)RKf30JBlg5%>|T<M5&eXHuJwp9;0YJx&XnZfKh&T8ugpKGiH7{0t7V zes)l3J{2Z<bCMO`3{#zUYm!+{LgJx)pL__rZXbTK?~oW$_X-^jJBNQ?)!hwFtD_f# zAC1B$WGekXDiI1(hg}f?AT)XG<gwZaJm>ONV80d_!m%*>mn{*byhxCSVqt9JD3_d- zPd7P~FOux?M#EV}T3FXt3&}FHcvM6zOn|-?17U53i<PJR2wi)YSXlj^2`a+HU~OG6 z59|u(Xyp-mCB2(A5yGcp;O{%whuitIDm(OJ<$Ez$oA>(}j&4EZK^vp;q{gYRiYFiY z_^tJ8o?Kczef+S?a~-7l=*QdE{~6(F!-nSOa62STdSAPKqqJ@P`ZZ51t)4r6*s!5? z!)vni(Yhy>F0HPfKCZ8wxL!RbA3OTix+mt2yFAB19Nk)vY+JwX&xk*5{IH>Y3#IMb zwyi<>01qA7)!zK!h7bR|=85X1bH`5`dc_bZ@69LIV14F}pD=x#UH_VVblns18(mAP ztLKjELtJme3$IEaerYiEZ_lh;sq%*YAG833?}~w=kRk2C2j|S03Qk4ax%ovk_`UE4 z4=JgLYEvx$;n?xwJlK&-&%>+P;CD&Y;P=8W8&fv+$K!qsKd?ICdi-(KiTLxXH&+nl z7azVCZTRn2vXu0Jo4cwjg@A(srFNVeCKQ%}iA4y;qnlV*XxFBfLP{T&M88KBmeTZ6 zQb_ZURr0GS7?1f&0T1by+FAb6LWVJ_a3D*!kBut7JwL=_{;_~Z^Lt~<@6YmkN3nFJ z{9y|HQix~e_owN|*$<O{)~^5C{8v_jo{_<-@=(80fl#9>!Qj!h|8#$}{!jNu>;F`L zwEj;8e69Xf`lI!~(jTq=69HeV{{TnnKjzo!|J3&KS*0fc{uk;0^^jky|3E)V{~`Yu z>i?)1{!81-XZ^o4hJTcvmVZK``Nw{$>KWr8n*W+I#{Zl$*8gXgG5)7RJof*YWla9l z%UJHIG~Pif%UJ&h*o);?D(Mp;Kk{D*@!0<-0$nVBLK*A-V`;nt;8I0SBSdZc$MRq4 z2MvP!s3)q6jDLMdZ2M1)Y5xf^?T_@e?GN&^Gx;(8$Huh(gm(F}(n~MW{-a{r|2BUq zt^c*C=i+gdg?1l)U1N1A83W^g%?pKvIRx~7R%sy}A7>R7jsbY)3#|QSuEuoWSCfwL zbjXk6@N~#OhE!rY8((J?+7XV<5BeC%kL}~1gXKg1IWG)k`2%BF`2mEdYVr?+@<D$C zO#e#*1A#svw<+b{_CkNazusTEVie220^y08{QX(}70UR(g7uSV{-J#$^@IGSn*7K= z$X}D5Ln<qy`3EUS^IthLN`9bCk^I2_gfhne*fPdH=q2SJ^p^5pS;q2DEStF+`3JqG z{Da<8{wvEE|5M8t|IlwJ|1-fy{t?FZV{i_cQ&vg&uK~Z5&huZ)e{B0j)gS5;+y1nD zHSIqkMt)K91^%JkquPHgwy&oBwfT{MkpCC9KhTeA|H^jxmuh-26sx#7Wi@`(|Cwc# zer(^{%2@k=yr}-)RyGsTIgsN7nhtWO?FVv&bl|@XXAEqYQIHP$hUvgRv=h~TXg8XF zf|4J`ADSQ98PanIv^$nhMwLzUqrP8<?F;;m!uCb|Q}SO2`H_E&NBsvnwEZWPK@W%W zuP+<xr~0n6Ka@|~f2yYbsQ<tpZGWl{Kqr=e7>8;7U|gpBFHrK=DD_{Uq}S5)9I`;+ zzqX9=e?O)JU7BtmL+)4dFIMu?bl`6><_G>CR`_46@c%IK2k9%2Kj8miO@8E`rUQM* zPx-%J;eWBhKhUS`59L$-)$(hheB>YMPxT(tQSJ+r_J@8!+ZW_Q`A0p#_EhsPQ0lMB z2l24|YZd+%DE!lOs2@!S{%e%_&r$M6(Fgt)DE!wb{6l`qKj2aRwdEuK&@Qz7X*$rK zL;1)4oeTPnaA_sl?=@wp;K(2JSg?O;0md2pe#Kv`_%%Vdi26VM9YT<wIYGb!`Pc9a zo{2E%|D2fgnNjIbu97|-@sWQ_2mUG%#{Q#C2l|ytdJRp7aSGCrzgYld`(ybC-$v6p zGD}IHj_E-EHcSWlwKSdO$M!{fIQ~zEbcEIVS3-V-r(-(g$NHiD|HI}V<b2Wghmqtf zwLgr2ihRFZ`@={Gdiv$te{9)TYyVNS{$IcS|JV4RP^Q&?auKIJ6@sW-48A6vZKTAc ze;H7_P=wnlS{VO*fELEzxX{9QCPxe7zu(cqknGgLc*IT%<Mk;m4CTAEFwpCyg`xaR zEe!NKYhfUlrG@c-|7c-I@2!P%;9q0Jz}>Yl;OA*!{Gkmkd}+_idTC+&2O3)V3OKi- zg@N7xEu0!7uT)!Fl>7j`N{bKt57ff&LmxgZT<jk_L<<iEr-et9j40K@_-~H1@Q+4~ zF4Mwez-i%lI4*&>1mY5iOW=P)0$;{Hh~va15SKt)0&xk%B@mau|Cj_;lH%jU`v8Ie zfl0_oLSTzZCOIW4%sq=R{AdtOhyPamtR3K7J<&6KJx%Ybq@N`ENm_h>XUD+5M7Uc! zJ{BpH?(OA~HxSO-rj}1$Mz}`=j;ar2o`dP|kCD~-!@uE>N&b>{nDSQ;;SZ2ya%nrh zeh0#u`oz+Y!zkZKd&TAPza@{aIGo8c^f`@d=Dl_eNRqVxhV|y?C_@Y!i+>R~n%G5< z<H;A5fPwyx=5{nod*}iIIE8<KlnY0jW0JHm{4%fshU^QvnEG81@9QS~r|Ey)di}TZ zjgL8-b+K|`)#G9)Un%`Tn84c)?{<)L5t-aU>^B$nRr&wsO8#z8GCA<9QM<F4qCoh6 zKJ7^?@Fn_x6a~Bwqa_gw!@s17a#52Bq{P&Z#HL4O{u)3m{jWjddz}S-bu_B}vBUf4 zQK?`nU%dZE6NFvKXcEzIOkxN7o6+QA@uO*fbtsnquTJ6no#XDwk#%e^@;h*tuAQ*~ zB>-vwKh(r#0@^+=L|{q+)0Fi8HQ3nY7ld<U6EjmxFXb=(6_-F<0&xk%B@mZDTmo?k z#3c}yKwJWG3B)B3mq1(saS6mF5SKt)0&xk%B@mZDTmo?k#3c}yKwJWG3B)B3mq1(s zaS6mF5SKt)0&xk%B@mZDTmo?k#3c}yKwJWG3B)B3mq1(saS6mF5SKt)0&xk%B@mZD cTmo?k#3c}yKwJWG3B)B3mq1(s-(v~<e;Q~XlmGw# literal 0 HcmV?d00001 diff --git a/src/gb.rs b/src/gb.rs index 954ecc37..7ab861d6 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -3,7 +3,7 @@ use crate::{ data::{BootRom, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT}, mmu::Mmu, pad::{Pad, PadKey}, - ppu::{Ppu, Tile, FRAME_BUFFER_SIZE}, + ppu::{Ppu, PpuMode, Tile, FRAME_BUFFER_SIZE}, rom::Cartridge, timer::Timer, util::read_file, @@ -50,6 +50,18 @@ impl GameBoy { self.cpu.pc() } + pub fn ppu_ly(&mut self) -> u8 { + self.ppu().ly() + } + + pub fn ppu_mode(&mut self) -> PpuMode { + self.ppu().mode() + } + + pub fn ppu_frame(&mut self) -> u16 { + self.ppu().frame_index() + } + pub fn clock(&mut self) -> u8 { let cycles = self.cpu_clock(); self.ppu_clock(cycles); diff --git a/src/ppu.rs b/src/ppu.rs index d39e0d49..1b051ed1 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -242,6 +242,11 @@ pub struct Ppu { /// first one, preventing actions. first_frame: bool, + /// Almost unique identifier of the frame that can be used to debug + /// and uniquely identify the frame that is currently ind drawing, + /// the identifier wraps on the u16 edges. + frame_index: u16, + stat_hblank: bool, stat_vblank: bool, stat_oam: bool, @@ -256,6 +261,7 @@ pub struct Ppu { int_stat: bool, } +#[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Clone, Copy, PartialEq)] pub enum PpuMode { HBlank = 0, @@ -304,6 +310,7 @@ impl Ppu { window_map: false, switch_lcd: false, first_frame: false, + frame_index: 0, stat_hblank: false, stat_vblank: false, stat_oam: false, @@ -338,6 +345,7 @@ impl Ppu { self.window_map = false; self.switch_lcd = false; self.first_frame = false; + self.frame_index = 0; self.stat_hblank = false; self.stat_vblank = false; self.stat_oam = false; @@ -404,6 +412,7 @@ impl Ppu { self.mode = PpuMode::OamRead; self.ly = 0; self.first_frame = false; + self.frame_index = self.frame_index.wrapping_add(1); } self.mode_clock -= 456; @@ -556,6 +565,18 @@ impl Ppu { self.palette_obj_1 } + pub fn ly(&self) -> u8 { + self.ly + } + + pub fn mode(&self) -> PpuMode { + self.mode + } + + pub fn frame_index(&self) -> u16 { + self.frame_index + } + pub fn int_vblank(&self) -> bool { self.int_vblank } -- GitLab