From 5e290d825b40dc6174d0f21940787938271ce487 Mon Sep 17 00:00:00 2001 From: Trevor Merritt Date: Mon, 14 Oct 2024 12:13:33 -0400 Subject: [PATCH] more roms --- gemma/src/chip8/computer.rs | 1 + gemma/src/chip8/instructions.rs | 2 +- gemma/src/chip8/video.rs | 51 ++++++++++++++---- .../src/bin/support/gemma_egui_support.rs | 11 ++-- gemmaimgui/src/bin/gemmaimgui.rs | 2 + gemmaimgui/src/bin/support/emmagui_support.rs | 33 +----------- resources/roms/Chip8 Picture.ch8 | Bin 0 -> 164 bytes .../Chip8 emulator Logo [Garstyciuks].ch8 | Bin 0 -> 288 bytes .../Clock Program [Bill Fisher, 1981].ch8 | Bin 0 -> 280 bytes ...lay Timer Test [Matthew Mikolay, 2010].ch8 | Bin 0 -> 58 bytes .../Division Test [Sergey Naydenov, 2010].ch8 | Bin 0 -> 371 bytes resources/roms/Fishie [Hap, 2005].ch8 | Bin 0 -> 160 bytes .../roms/Framed MK1 [GV Samways, 1980].ch8 | Bin 0 -> 176 bytes .../roms/Framed MK2 [GV Samways, 1980].ch8 | Bin 0 -> 176 bytes ...umping X and O [Harry Kleinberg, 1977].ch8 | Bin 0 -> 82 bytes resources/roms/Keypad Test [Hap, 2006].ch8 | Bin 0 -> 114 bytes resources/roms/Life [GV Samways, 1980].ch8 | Bin 0 -> 256 bytes .../roms/Maze (alt) [David Winter, 199x].ch8 | Bin 0 -> 38 bytes resources/roms/Maze [David Winter, 199x].ch8 | Bin 0 -> 34 bytes .../Minimal game [Revival Studios, 2007].ch8 | Bin 0 -> 85 bytes .../Particle Demo [zeroZshadow, 2008].ch8 | Bin 0 -> 353 bytes ...om Number Test [Matthew Mikolay, 2010].ch8 | Bin 0 -> 34 bytes .../SQRT Test [Sergey Naydenov, 2010].ch8 | Bin 0 -> 386 bytes .../Sierpinski [Sergey Naydenov, 2010].ch8 | Bin 0 -> 521 bytes .../roms/Stars [Sergey Naydenov, 2010].ch8 | Bin 0 -> 968 bytes .../Trip8 Demo (2008) [Revival Studios].ch8 | Bin 0 -> 3203 bytes .../roms/Zero Demo [zeroZshadow, 2007].ch8 | 11 ++++ 27 files changed, 64 insertions(+), 47 deletions(-) create mode 100644 resources/roms/Chip8 Picture.ch8 create mode 100644 resources/roms/Chip8 emulator Logo [Garstyciuks].ch8 create mode 100644 resources/roms/Clock Program [Bill Fisher, 1981].ch8 create mode 100644 resources/roms/Delay Timer Test [Matthew Mikolay, 2010].ch8 create mode 100644 resources/roms/Division Test [Sergey Naydenov, 2010].ch8 create mode 100644 resources/roms/Fishie [Hap, 2005].ch8 create mode 100644 resources/roms/Framed MK1 [GV Samways, 1980].ch8 create mode 100644 resources/roms/Framed MK2 [GV Samways, 1980].ch8 create mode 100644 resources/roms/Jumping X and O [Harry Kleinberg, 1977].ch8 create mode 100644 resources/roms/Keypad Test [Hap, 2006].ch8 create mode 100644 resources/roms/Life [GV Samways, 1980].ch8 create mode 100644 resources/roms/Maze (alt) [David Winter, 199x].ch8 create mode 100644 resources/roms/Maze [David Winter, 199x].ch8 create mode 100644 resources/roms/Minimal game [Revival Studios, 2007].ch8 create mode 100644 resources/roms/Particle Demo [zeroZshadow, 2008].ch8 create mode 100644 resources/roms/Random Number Test [Matthew Mikolay, 2010].ch8 create mode 100644 resources/roms/SQRT Test [Sergey Naydenov, 2010].ch8 create mode 100644 resources/roms/Sierpinski [Sergey Naydenov, 2010].ch8 create mode 100644 resources/roms/Stars [Sergey Naydenov, 2010].ch8 create mode 100644 resources/roms/Trip8 Demo (2008) [Revival Studios].ch8 create mode 100644 resources/roms/Zero Demo [zeroZshadow, 2007].ch8 diff --git a/gemma/src/chip8/computer.rs b/gemma/src/chip8/computer.rs index 40af58c..9e3bb64 100644 --- a/gemma/src/chip8/computer.rs +++ b/gemma/src/chip8/computer.rs @@ -96,6 +96,7 @@ impl Chip8Computer { Chip8CpuStates::WaitingForInstruction => { self.sound_timer.tick(); self.delay_timer.tick(); + self.video_memory.tick(); } Chip8CpuStates::WaitingForKey => { println!("waiting for a key press..."); diff --git a/gemma/src/chip8/instructions.rs b/gemma/src/chip8/instructions.rs index bbeb528..ee8bee7 100644 --- a/gemma/src/chip8/instructions.rs +++ b/gemma/src/chip8/instructions.rs @@ -695,7 +695,7 @@ impl Chip8CpuInstructions { debug!("PPOOSSTT -> CHIP8CPUINSTRUCTION:DRAWVXNIBBLE -> {}", input.video_memory.format_as_string()); - let mut did_change: bool = false; + let mut did_change: bool = input.video_memory.has_frame_changed; if did_change { input.registers.poke(0xf, 1u8); diff --git a/gemma/src/chip8/video.rs b/gemma/src/chip8/video.rs index a24f43e..29a1ba6 100644 --- a/gemma/src/chip8/video.rs +++ b/gemma/src/chip8/video.rs @@ -42,22 +42,22 @@ impl Chip8Video { } pub fn poke(&mut self, address: u16, new_value: bool) -> Self { - trace!("OFFSET: {address} - POKING {new_value}"); + // println!("OFFSET: {address} - POKING {new_value}"); + + // Loop the address let effective_address = if address >= 2048 { address - 2048 } else { address }; - let old_value = self.memory[effective_address as usize]; - let value_to_poke = new_value ^ old_value; - if old_value != value_to_poke { - trace!("**VIDEO** TOGGLING"); - self.has_frame_changed = true; - } else { - trace!("NOT TOGGLING"); - } - self.memory[effective_address as usize] = new_value ^ old_value; + let old_value = self.memory[effective_address as usize]; + let xored_value = new_value ^ old_value; // XOR of the video + let value_changed = old_value != xored_value; // From True to False is a change. + + self.has_frame_changed = if xored_value && value_changed { false } else { true }; + + self.memory[effective_address as usize] = xored_value; self.to_owned() } @@ -369,6 +369,7 @@ mod test { fn verify_change_registered() { let mut v = Chip8Video::default(); v.poke(0x01, true); + v.poke(0x01, true); assert!(v.has_frame_changed); v.start_frame(); @@ -423,4 +424,34 @@ mod test { x = x.reset(); assert_eq!(x.format_as_string(), read_test_result("test_reset_clears_video.asc")); } + + #[test] + fn collision_test() { + // Setup: Set 0xFF to 0x00 with a new frame ready + // Action: Run Poke to the same area + // Test: Verify the 'changed' flag is tripped + let mut x = Chip8Video::default(); + x.poke_byte(0x00, 0xff); + x.tick(); + // set the cell thats already set... + x.poke(0x00, true); + // it becomes unset and theres a frame changed + assert_eq!(false, x.peek(0x00)); + + assert_eq!(true, x.has_frame_changed); + } + + #[test] + fn collision_test2() { + let mut x = Chip8Video::default(); + x.poke_byte(0x00, 0b11110000); + assert_eq!(true, x.has_frame_changed); + x.tick(); + assert_eq!(false, x.has_frame_changed); + // clear the 'has changed' flag + + // now set a no-collision value + x.poke_byte(0x00, 0b00001111); + assert_eq!(false, x.has_frame_changed); + } } diff --git a/gemmaegui/src/bin/support/gemma_egui_support.rs b/gemmaegui/src/bin/support/gemma_egui_support.rs index ed858de..2b281bd 100644 --- a/gemmaegui/src/bin/support/gemma_egui_support.rs +++ b/gemmaegui/src/bin/support/gemma_egui_support.rs @@ -36,7 +36,7 @@ pub struct GemmaEguiSupport {} impl GemmaEguiSupport { pub fn controls_view(mut system: &mut Chip8Computer, state: &mut GemmaEGuiState, ui: &mut Ui) { - ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| { if ui.button("Start").clicked() { println!("Start"); @@ -62,9 +62,12 @@ impl GemmaEguiSupport { } }); - ui.checkbox(&mut state.display_memory, "Display Memory"); - ui.checkbox(&mut state.display_video, "Display Video"); - ui.checkbox(&mut state.display_registers, "Display Registers"); + + ui.with_layout(egui::Layout::left_to_right(Align::TOP), |ui| { + ui.checkbox(&mut state.display_memory, "Display Memory"); + ui.checkbox(&mut state.display_video, "Display Video"); + ui.checkbox(&mut state.display_registers, "Display Registers"); + }); } pub fn registers_view(system: &Chip8Computer, ui: &mut Ui) { diff --git a/gemmaimgui/src/bin/gemmaimgui.rs b/gemmaimgui/src/bin/gemmaimgui.rs index c64ec11..730d0f9 100644 --- a/gemmaimgui/src/bin/gemmaimgui.rs +++ b/gemmaimgui/src/bin/gemmaimgui.rs @@ -8,6 +8,7 @@ use gemma::{ use imgui::*; use sys::{ImColor, ImVec2, ImVector_ImU32}; use rand::random; +use gemma::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction; use gemma::chip8::system_memory::Chip8SystemMemory; use support::{emmagui_support::GemmaImguiSupport, ui_state::ImGuiUiState}; @@ -42,6 +43,7 @@ fn main() { for (key_code, key_reg) in LIN_KEYS { if down_keys[key_code as usize] { system.keypad.push_key(key_reg); + system.state = WaitingForInstruction; } else { // do we need to release it? if system.keypad.pressed(key_reg) { diff --git a/gemmaimgui/src/bin/support/emmagui_support.rs b/gemmaimgui/src/bin/support/emmagui_support.rs index 0f6bc88..745bc5f 100644 --- a/gemmaimgui/src/bin/support/emmagui_support.rs +++ b/gemmaimgui/src/bin/support/emmagui_support.rs @@ -2,7 +2,6 @@ use gemma::constants::CHIP8_KEYBOARD; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; -use std::thread::sleep; use std::time::Duration; use imgui::{Condition, ImColor32, Ui}; use log::debug; @@ -61,7 +60,7 @@ impl GemmaImguiSupport { pub fn video_display(system_to_control: &Chip8Computer, gui_state: &ImGuiUiState, ui: &Ui) { // draw area size let draw_area_size = ui.io().display_size; - println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]); + // println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]); let cell_width = ((draw_area_size[0] as i32 / 64) * 6) / 10; let cell_height = ((draw_area_size[1] as i32 / 32) * 6) / 10; @@ -177,7 +176,6 @@ impl GemmaImguiSupport { let cols = position.1; ui.window("System Memory") .size([400.0, 300.0], Condition::FirstUseEver) - .position([500.0, 300.0], Condition::Always) .build(|| { let mut current_x_hover: i32 = 0; let mut current_y_hover: i32 = 0; @@ -230,33 +228,4 @@ impl GemmaImguiSupport { }); } - pub fn system_memory_render(memory: Chip8SystemMemory, ui: &Ui) { - ui.window("System Memory") - .size([300.0, 100.0], Condition::FirstUseEver) - .build(|| { - ui.text("Rendering System Memory Here"); - let draw_list = ui.get_foreground_draw_list(); - let mut idx = 0; - for row in 0..CHIP8_VIDEO_HEIGHT { - for column in 0..CHIP8_VIDEO_WIDTH { - let x_offset = column * CELL_WIDTH; - let y_offset = row * CELL_WIDTH; - let start_point = [x_offset as f32, y_offset as f32]; - let end_point = [(x_offset + CELL_WIDTH) as f32, - (y_offset + CELL_HEIGHT) as f32 - ]; - let memory_offset = (row * CHIP8_VIDEO_WIDTH) + column; - let target_color = if memory.peek(memory_offset as u16) == 0 { - ImColor32::BLACK - } else { - ImColor32::WHITE - }; - draw_list.add_rect([x_offset as f32, y_offset as f32], - [(x_offset + CELL_WIDTH) as f32, (y_offset + CELL_HEIGHT) as f32], - target_color).build(); - idx += 1; - } - } - }); - } } \ No newline at end of file diff --git a/resources/roms/Chip8 Picture.ch8 b/resources/roms/Chip8 Picture.ch8 new file mode 100644 index 0000000000000000000000000000000000000000..74ab4bf9169a6022ca29ba20d515cf0d34dd1409 GIT binary patch literal 164 zcmZR0u*f5UAyF=g;S$p&p&||=2O%yX-!6%1k=F&ii+V+TAaYSe0(T_xdIL$t^|fc4u*FO=%C&qJ|H|Kgy9d%7m+JK z+Fn6lLt9IW;UCj4u2(>s00;qt5(W@e0#vI72G&p-(>iqHO2GPo_A)?N)(``<-a%<} n_2~SNknn(bhkBs>S46(B`~ljlt)Z`A52Rml{bKruuABh?Dy1yW literal 0 HcmV?d00001 diff --git a/resources/roms/Clock Program [Bill Fisher, 1981].ch8 b/resources/roms/Clock Program [Bill Fisher, 1981].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..ec137bd137128f1400f768753b44eb39ff75a0d7 GIT binary patch literal 280 zcmey!^@-~<*B7p@T;I4D9;7oWovUE{sCm6Log2jZ1Y(JUSf4>GMG)%?h@}f+eFd@1 zL9A~;)?cn%)xV-b3AxsPMU`HZF}iUHWhuS*%Wlgcl)!XDC@76V>4nmVQbt!cp(>>h zLJ6r1N*|PdlrXw*33Vy`5K2g4Q2L?FP|WDeCNxVKs3)00nL(MOh|!5fXqOY0&^l!f zp@bx$h_XN-qq2Zdf+LgAA%s{W14t}`ebIyK?d6ON?|%GRuitNV`^p~(U`V$Bxsl;r ex*o{YKw1gp4!16{remE>y1^NDoj4vItPWP%zYXs0j_C zAlCt11#%HkHP~JekSm466BwEq5*S)QZcbGWMD+(yh6^Zz;uB?IKy(LcFwp=k_F3{9f;jKcQK49#pU0;!Bm22HL& zzGVwvE1$4-gKJxeu=Ll|h6ILpO$-g9_6)+V4GKWrnIDWlrGC)7>{`tB5lnspk__)O Jk~JA1000x{jmQ81 literal 0 HcmV?d00001 diff --git a/resources/roms/Fishie [Hap, 2005].ch8 b/resources/roms/Fishie [Hap, 2005].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..ca1eaf391eaf5595cd4377b948871036fe89d22a GIT binary patch literal 160 zcmZR0ut*_^BjHB@N1}j(f)L+Dj!$xh9F7J;96|yNFd$)L17o7ocJ}txmKGKk3=9ni zpZ~A3VUYj-^FRYLGeg6J4;20MMkXn#mvNbI62 Y!@K_|pd&ybpo8H@M@PpGhCg5c06TLi(f|Me literal 0 HcmV?d00001 diff --git a/resources/roms/Keypad Test [Hap, 2006].ch8 b/resources/roms/Keypad Test [Hap, 2006].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..c60558ee714cf811ff746caafb09072c03f20b83 GIT binary patch literal 114 zcmWgg+(kkkkcXp-o`1&^jg| zCI%{j2MZoFeB^TE7ut};(Iw=?AT;flgiuSPNTbLHt_GF{7LffyE%^)=1be<*5M+4w zi7SC2ks*(v=)V(#P)iQOqUH;NEgvoj7W`3KR>-2XtN@A&L3|(viGwjqA&U>t-9F4h Qzf2f~n1u9zq@EBH0N{~CP5=M^ literal 0 HcmV?d00001 diff --git a/resources/roms/Maze (alt) [David Winter, 199x].ch8 b/resources/roms/Maze (alt) [David Winter, 199x].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..0dca98194909931a4c217eca56340d0b693542d8 GIT binary patch literal 38 tcmYdbNMu-~bcoT2agp2wkpdP22O*XOhC&uY1tAt8nFa?10R@K!0RXgI2xI^N literal 0 HcmV?d00001 diff --git a/resources/roms/Maze [David Winter, 199x].ch8 b/resources/roms/Maze [David Winter, 199x].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..152ae7dab7d5e7634987ea23c45a922db5ac9389 GIT binary patch literal 34 pcmZ1^cZku5ago#okpdP22O)+8hC&uY1tA6@i3SG+0R@K!0RWch2af;% literal 0 HcmV?d00001 diff --git a/resources/roms/Minimal game [Revival Studios, 2007].ch8 b/resources/roms/Minimal game [Revival Studios, 2007].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..b759e918f658e67d15085ab800590c43ffe823de GIT binary patch literal 85 zcmZR0pd_Ls1%wJpQVEP7M3tO`*pd}eBpKc<^1AHC@GgPl!8{?262=53AZ<|cKY;~E kTNM9KU<1+)#f%K^KCl}w2stslvyu2Oq4K=$|IFt$0B?yNNB{r; literal 0 HcmV?d00001 diff --git a/resources/roms/Particle Demo [zeroZshadow, 2008].ch8 b/resources/roms/Particle Demo [zeroZshadow, 2008].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..2df976558b145545accfb5199f978892c78c0f7d GIT binary patch literal 353 zcmZ3in81+8ki>C8^pjkJ3UOo_YdTXFgV2s_h8%`PH-5`~PE|Um6eE-bBCAVk2;mts6*CG= zQJMqR5h~=fnBkY)htwv9D#jG~E~f;AM7ajOM!Cbx#XlI{f$WBECUSQkzA`E3$(!pGZNeofuqCKzq>aaVGDGK{Y9of^-l< z_`*_drRd$#lGK%#oQkj&DRT4^N<|QsXiTa%aA7?2!&Mh$nmyw?gbe%a$ywNWfUM63 zh_%o;hFKGBWHMaXpWSOjh%A?x7cpWzsRk@+M;_@;Qb{Z3sqpTvOy?sI9GPl!VP1*` grHA>+Jjh$`-cX+{=bR?8qd|+E@7K1<8|(+bFV!QG%>V!Z literal 0 HcmV?d00001 diff --git a/resources/roms/Sierpinski [Sergey Naydenov, 2010].ch8 b/resources/roms/Sierpinski [Sergey Naydenov, 2010].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..e02d458a8b72c8462530cc80e58cc218175a68e9 GIT binary patch literal 521 zcmb_YyGjE=6g>lGO%;~iY@5U)U@9Xvb}1xCx-Cp83<+bKh(9p9c?c2I#>#T`2mAsd z#{7VtACPjH1Qcw7cV-O)ZJpvWbIv{YoI76bp!kYWn`f znR+p3;!tI3rrY!uRsk8xVzNufYzqn0>jt5lPzznsVhq!BZG0g6vY4TC=h7$Vxp6k# zBUsJcXf}zAQduO+8M-~zfcN#|TBa$KV*-Xv4(npD6IBo4pUXPZB)=|wwl5GP z4#?wSfPph~1&ix(liqj3!l>X!vN=BUFF%CjNMp#L0sm1}kg8{Wv=X}Z*1U3-wyvlm L-S5^U{^fiFk+H;b literal 0 HcmV?d00001 diff --git a/resources/roms/Stars [Sergey Naydenov, 2010].ch8 b/resources/roms/Stars [Sergey Naydenov, 2010].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..712e83c594a32ab724afe1c99e140b2289c58dbc GIT binary patch literal 968 zcmaiyze~eF6vw{@YQ;f9lLJ901rc=e*OD!Rc5n~^wu4(#;#$SUMYyEaK|!Sd!-1n? z$NGD46c_y;%2m*@`0kRDs#Onid3X1@cklb&%MUNRetAe-7c)s|3&muA4^HSsxlt14uLU%kky-fqAm2w!k{pX?+a>mVXb>|+F@#00|8 zWDLPY(L?@Kd;NpvhPI;NNYl?0&yA|&UnxHNYwz^Ny$Unx+^WK3Hq&RjjMzOQiJ}O7 z->NbX_Ef6mpqG!7KtdP~$Zw=nf1ZBVYx0)rHTGb=_M_4JJN-qkeV6F9AOF?c>FKpM zzw34E?@9*gwO37KX(wb+X1$Pjp-%XbZKtZwOtx2BIoYDhw0c!Nfw9xRGWLb;G2LG( zqz)TXT<=ou^c{27mpflx=pN0^sFKC3+8&%vayy{vWh_yP{nd1;JQqPeY_YTHF1?HP z7TZQ`D4JdXpA_?G%ZF~^qq2h-G4~i>uc0<_WQ8oAk4v;(I~uQ#b5E3x%Y5!V6am`0 zhduBOQKZ|4YUwRhvrnb{k&c<)XhwTxdlrC29}RcL)xR=U=tBea7R`;a+jYwgjHW{) GgT^PW2$7cn literal 0 HcmV?d00001 diff --git a/resources/roms/Trip8 Demo (2008) [Revival Studios].ch8 b/resources/roms/Trip8 Demo (2008) [Revival Studios].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..d88d28fa3f28155bfb1fa52309432768892378cf GIT binary patch literal 3203 zcmZ`)O^7B(8LsZG|LU%;->&}o{+QXDLWbxeNJu6UgUJL8iJPnAXo4}>5EVS=x~?n) zU2@1Fxr?lV5g`Z9!Gi<@g#o>Y9`=x<3=>@>B$HX+n%VhwT-LAXuit*&df$5Md7tWb z;n^=e|K#(JJ^kEQFFyX{SDt%tYwIC&^U~_mxcT1Z`OP*7W-Z9&~DhPPoH)#?DgmHdXL`0KfAZM zi{Du6;{9&-^LrQWV(0*&+FFCq+ZW&dGeXZ@y!g58?Z?0P*b`{`+fP091X{iH&O0w5 zw0iTWTi+~RV0-8rXczrtYu|o{-&*|d|EmLQ{eYpM(*VY!!MQ^JYy#AiH?v_fH$T{lWS z@)DUv9aFFPpbFHiT0cZt#g)^A84DFSvdhp+uD~Kv)XY0M#ftKt6k^uVArZzW0xLlN z#5u=7lw-eO#B)X<$1p5q={V&sl>NA>kr70?0mljHrB+oP<{T+q*lBK6S~iHgq8TU0 z`-Ll6DrFk{lwgH~N#ZHYCB<rLrU3KYFc_M-AV4e1`nLaVF5c9Wk^%4(^4VT0uM7I{8E=RmU0{; z*UOfdq#sEdq*yoZLtts)D>_s>_2E4K6YoZXi#UoD zN5}!ac5|7!B-O~E*UP#~QPN%(D~)s*JTJq_=K4z&2x?DyU1FJVA?J~gkx+5Qf6x0+ zx}lflf?*#L)@iWCN)@KM)TD9Df~cY|xpK1S0_h*{Vde_CN)?E=?*0~R{${M#Z!Rf)o#KZ;cTSv}x zv6OY>Gyr9*T95(DkhMJL-0Na)myIi!o!r8ulA2TfQl0o5IAZlACaotf7j<%3v5K3J z)!H)Q$G&imJ^rmpF#7Gb=e#0vL-uEOi4okKC}(I90_SOtUeR8ZfsT{ zmAcL9W5VdHJRY3Rs`DMl6%^NYok0z#Ep`scwqzdh=v|TX#==LUv%YL3D(}CA{ZE`= z4ayoN&F5Ao$;xG|LX=re(tDnjBwQfNz^yA2o=0ZlNpC6CFu;3()gh%}rMPVSI4Zts zMejzMCyljS&pi zb5lppwVXK>0(VM=F%TX#qui%1T8ZI2D<2z7O<$X22-SnOOr_qi+6vl1@1>EI4*}Gu zY@9b$+d$qXI$rZJ2p>g5V`8>fIeySo%jijtrzVlk3rXscg-{F5Y?w)6Ypnge!EIx0 zH8)iQ{l}BX(Eou^MkDm!3(}>aE7M@tSGqH{(mmFl3s@zhdm6ox1U?Hs)|}`GD$^|Q zzl|*$Ag6^B5M5b&Xry(O3|xiAMoyq+se7Q8Q>kJPXUo>i^+V0f+5UA5gw?&#exO_? z*SeWd9NvP!a@o!r2YDT9f(I@;A-(G)=@ql>o&85vryM`jJPFac(U7P(^gqIfz>UiH za6sCraY_#*^vc4!2#d)ZQ^WfbkJYT2lR2O2!F0hVGd7c)_&^i!)Ubw2#WaQU#iL9O zEaQi+@6!;>(55EIfsV8jURY)-T`8`Vs%1@eX|Ux^1~ITn3{8S^M>mgS7h~vr@8)LE z5U=3#MTT;y5)aC0Ym7BP4{9c6Vh~Aokij#H+fr463LzwrqZkaRq>riV`UoRLXN&^n i@>(b