From 9ecfdc9db3e7acc8f6e80bb0ab788853363f48c6 Mon Sep 17 00:00:00 2001 From: EliseZeroTwo Date: Fri, 12 Jan 2024 17:20:56 -0700 Subject: [PATCH] feat: implement more accurate memory bus conflicts with DMA --- .forgejo/workflows/action.yml | 20 +++++++ .github/workflows/action.yml | 20 +++++++ meowgb-core/src/gameboy.rs | 8 +-- meowgb-core/src/gameboy/dma.rs | 54 +++++++++++++++++- meowgb-core/src/gameboy/ppu.rs | 20 +++---- .../expected_output/add_sp_e_timing.bin | 1 + .../expected_output/call_cc_timing.bin | 1 + .../expected_output/call_cc_timing2.bin | 1 + meowgb-tests/expected_output/call_timing.bin | 1 + meowgb-tests/expected_output/call_timing2.bin | 1 + meowgb/src/main.rs | 2 - meowgb/src/window/overlay.rs | 27 ++++++++- run-test-roms.sh | 50 ++++++++++++++++ .../roms/add_sp_e_timing.gb | Bin 0 -> 32768 bytes .../mooneye-test-suite/roms/call_cc_timing.gb | Bin 0 -> 32768 bytes .../roms/call_cc_timing2.gb | Bin 0 -> 32768 bytes .../mooneye-test-suite/roms/call_timing.gb | Bin 0 -> 32768 bytes .../mooneye-test-suite/roms/call_timing2.gb | Bin 0 -> 32768 bytes tests.md | 5 ++ 19 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 meowgb-tests/expected_output/add_sp_e_timing.bin create mode 100644 meowgb-tests/expected_output/call_cc_timing.bin create mode 100644 meowgb-tests/expected_output/call_cc_timing2.bin create mode 100644 meowgb-tests/expected_output/call_timing.bin create mode 100644 meowgb-tests/expected_output/call_timing2.bin create mode 100644 test-roms/mooneye-test-suite/roms/add_sp_e_timing.gb create mode 100644 test-roms/mooneye-test-suite/roms/call_cc_timing.gb create mode 100644 test-roms/mooneye-test-suite/roms/call_cc_timing2.gb create mode 100644 test-roms/mooneye-test-suite/roms/call_timing.gb create mode 100644 test-roms/mooneye-test-suite/roms/call_timing2.gb diff --git a/.forgejo/workflows/action.yml b/.forgejo/workflows/action.yml index 1e27741..38bc2d6 100644 --- a/.forgejo/workflows/action.yml +++ b/.forgejo/workflows/action.yml @@ -30,6 +30,10 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/blargg/roms/mem_timing.gb test -m 100000000 -s meowgb-tests/expected_output/mem_timing.bin + - name: Run test ROM (mooneye-test-suite add_sp_e_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/add_sp_e_timing.gb test -m 100000000 -s meowgb-tests/expected_output/add_sp_e_timing.bin + - name: Run test ROM (mooneye-test-suite basic) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/basic.gb test -m 100000000 -s meowgb-tests/expected_output/basic.bin @@ -42,6 +46,22 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/boot_regs-dmgABC.gb test -m 100000000 -s meowgb-tests/expected_output/boot_regs-dmgABC.bin + - name: Run test ROM (mooneye-test-suite call_cc_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_cc_timing.gb test -m 100000000 -s meowgb-tests/expected_output/call_cc_timing.bin + + - name: Run test ROM (mooneye-test-suite call_cc_timing2) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_cc_timing2.gb test -m 100000000 -s meowgb-tests/expected_output/call_cc_timing2.bin + + - name: Run test ROM (mooneye-test-suite call_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_timing.gb test -m 100000000 -s meowgb-tests/expected_output/call_timing.bin + + - name: Run test ROM (mooneye-test-suite call_timing2) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_timing2.gb test -m 100000000 -s meowgb-tests/expected_output/call_timing2.bin + - name: Run test ROM (mooneye-test-suite daa) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/daa.gb test -m 100000000 -s meowgb-tests/expected_output/daa.bin diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 1e27741..38bc2d6 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -30,6 +30,10 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/blargg/roms/mem_timing.gb test -m 100000000 -s meowgb-tests/expected_output/mem_timing.bin + - name: Run test ROM (mooneye-test-suite add_sp_e_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/add_sp_e_timing.gb test -m 100000000 -s meowgb-tests/expected_output/add_sp_e_timing.bin + - name: Run test ROM (mooneye-test-suite basic) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/basic.gb test -m 100000000 -s meowgb-tests/expected_output/basic.bin @@ -42,6 +46,22 @@ jobs: if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/boot_regs-dmgABC.gb test -m 100000000 -s meowgb-tests/expected_output/boot_regs-dmgABC.bin + - name: Run test ROM (mooneye-test-suite call_cc_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_cc_timing.gb test -m 100000000 -s meowgb-tests/expected_output/call_cc_timing.bin + + - name: Run test ROM (mooneye-test-suite call_cc_timing2) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_cc_timing2.gb test -m 100000000 -s meowgb-tests/expected_output/call_cc_timing2.bin + + - name: Run test ROM (mooneye-test-suite call_timing) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_timing.gb test -m 100000000 -s meowgb-tests/expected_output/call_timing.bin + + - name: Run test ROM (mooneye-test-suite call_timing2) + if: always() + run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_timing2.gb test -m 100000000 -s meowgb-tests/expected_output/call_timing2.bin + - name: Run test ROM (mooneye-test-suite daa) if: always() run: ./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/daa.gb test -m 100000000 -s meowgb-tests/expected_output/daa.bin diff --git a/meowgb-core/src/gameboy.rs b/meowgb-core/src/gameboy.rs index 541b9f1..096abda 100644 --- a/meowgb-core/src/gameboy.rs +++ b/meowgb-core/src/gameboy.rs @@ -106,7 +106,7 @@ impl Gameboy { pub fn tick(&mut self) -> bool { if self.tick_count == 0 { cpu::tick_cpu(self); - let redraw_requested = self.ppu.tick(&mut self.interrupts); + let redraw_requested = self.ppu.tick(&self.dma, &mut self.interrupts); self.dma.tick_dma(&mut self.ppu, &self.memory, self.cartridge.as_deref()); self.serial.tick(&mut self.interrupts); self.timer.tick(&mut self.interrupts); @@ -114,7 +114,7 @@ impl Gameboy { self.tick_count += 1; redraw_requested } else { - let redraw_requested = self.ppu.tick(&mut self.interrupts); + let redraw_requested = self.ppu.tick(&self.dma, &mut self.interrupts); self.timer.tick(&mut self.interrupts); self.tick_count += 1; self.tick_count %= 4; @@ -328,7 +328,7 @@ impl Gameboy { assert!(!self.registers.mem_op_happened); assert!(self.registers.mem_read_hold.is_none()); self.registers.mem_op_happened = true; - let value = match self.ppu.dma_occuring { + let value = match self.dma.is_conflict(address) { true => match address { 0..=0xFEFF => 0xFF, 0xFF00..=0xFF7F => self.cpu_read_io(address), @@ -366,7 +366,7 @@ impl Gameboy { self.registers.mem_op_happened = true; self.last_write = Some((address, value)); - match self.ppu.dma_occuring { + match self.dma.is_conflict(address) { true => match address { 0..=0xFEFF => {} 0xFF00..=0xFF7F => self.cpu_write_io(address, value), diff --git a/meowgb-core/src/gameboy/dma.rs b/meowgb-core/src/gameboy/dma.rs index 65fc66b..7f3605b 100644 --- a/meowgb-core/src/gameboy/dma.rs +++ b/meowgb-core/src/gameboy/dma.rs @@ -1,16 +1,64 @@ use super::{mapper::Mapper, memory::Memory, ppu::Ppu}; -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DmaMemoryBus { + External, + Video, + Other, +} + +impl DmaMemoryBus { + pub fn from_base(base: u8) -> Self { + match base { + 0..=0x7F + | 0xA0..=0xFD => Self::External, + 0x80..=0x9F => Self::Video, + _ => Self::Other + } + } + + pub fn conflict_in_range(self, address: u16) -> bool { + let base = (address >> 8) as u8; + + if base == 0xFE { + true + } else if base == 0xFF { + false + } else { + match self { + DmaMemoryBus::External => base < 0x7F || (base >= 0xA0 && base <= 0xFD), + DmaMemoryBus::Video => base >= 0x80 && base <= 0x9F, + DmaMemoryBus::Other => false, + } + } + } +} + +#[derive(Debug, Clone, Copy)] pub struct DmaState { original_base: u8, + pub dma_in_progress: bool, pub base: u8, pub remaining_cycles: u8, restarting: Option<(u8, bool)>, } impl DmaState { + pub fn is_conflict(&self, address: u16) -> bool { + self.in_progress().map(|bus| { + bus.conflict_in_range(address) + }).unwrap_or_default() + } + + pub fn in_progress(&self) -> Option { + match self.dma_in_progress { + true => Some(DmaMemoryBus::from_base(self.original_base)), + false => None, + } + } + pub fn new() -> Self { - Self { original_base: 0, base: 0, remaining_cycles: 0, restarting: None } + Self { dma_in_progress: false, original_base: 0, base: 0, remaining_cycles: 0, restarting: None } } pub fn init_request(&mut self, base: u8) { @@ -34,7 +82,7 @@ impl DmaState { None => {} } - ppu.dma_occuring = self.remaining_cycles > 0; + self.dma_in_progress = self.remaining_cycles > 0; if self.remaining_cycles > 0 { let offset = 0xA0 - self.remaining_cycles; diff --git a/meowgb-core/src/gameboy/ppu.rs b/meowgb-core/src/gameboy/ppu.rs index 685dfed..c15dd22 100644 --- a/meowgb-core/src/gameboy/ppu.rs +++ b/meowgb-core/src/gameboy/ppu.rs @@ -1,4 +1,4 @@ -use super::interrupts::Interrupts; +use super::{interrupts::Interrupts, dma::DmaState}; pub const FB_HEIGHT: u32 = 144; pub const FB_WIDTH: u32 = 160; @@ -298,8 +298,6 @@ pub struct Ppu { sprite_buffer: [Option; 10], sprite_count: usize, - pub dma_occuring: bool, - current_draw_state: Option, wy_match: bool, @@ -355,7 +353,6 @@ impl Ppu { dot_target: 0, sprite_buffer: [None; 10], sprite_count: 0, - dma_occuring: false, current_draw_state: None, wy_match: false, first_frame: true, @@ -452,15 +449,14 @@ impl Ppu { ((high & 0b1) << 1) | low & 0b1 } - fn internal_read_oam(&mut self, offset: usize) -> u8 { - match self.dma_occuring && !OVERRIDE_PPU_MEMORY_ACCESS { + fn internal_read_oam(&mut self, dma_state: &DmaState, offset: usize) -> u8 { + match dma_state.in_progress().is_some() && !OVERRIDE_PPU_MEMORY_ACCESS { true => 0xFF, false => self.oam[offset as usize], } } pub fn dma_write_oam(&mut self, offset: u8, value: u8) { - assert!(self.dma_occuring); self.oam[offset as usize] = value; } @@ -569,7 +565,7 @@ impl Ppu { self.registers.ly_lyc = self.registers.ly == self.registers.lyc; } - pub fn tick(&mut self, interrupts: &mut Interrupts) -> bool { + pub fn tick(&mut self, dma_state: &DmaState, interrupts: &mut Interrupts) -> bool { if self.enabled() { self.registers.cycles_since_last_ly_increment += 1; match self.mode() { @@ -587,10 +583,10 @@ impl Ppu { let oam_item_idx: usize = (self.current_dot as usize / 2) * 4; let oam_entry = OAMEntry::parse([ - self.internal_read_oam(oam_item_idx), - self.internal_read_oam(oam_item_idx + 1), - self.internal_read_oam(oam_item_idx + 2), - self.internal_read_oam(oam_item_idx + 3), + self.internal_read_oam(dma_state, oam_item_idx), + self.internal_read_oam(dma_state, oam_item_idx + 1), + self.internal_read_oam(dma_state, oam_item_idx + 2), + self.internal_read_oam(dma_state, oam_item_idx + 3), ]); let sprite_height = self.sprite_height(); diff --git a/meowgb-tests/expected_output/add_sp_e_timing.bin b/meowgb-tests/expected_output/add_sp_e_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/add_sp_e_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/call_cc_timing.bin b/meowgb-tests/expected_output/call_cc_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/call_cc_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/call_cc_timing2.bin b/meowgb-tests/expected_output/call_cc_timing2.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/call_cc_timing2.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/call_timing.bin b/meowgb-tests/expected_output/call_timing.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/call_timing.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb-tests/expected_output/call_timing2.bin b/meowgb-tests/expected_output/call_timing2.bin new file mode 100644 index 0000000..2a02163 --- /dev/null +++ b/meowgb-tests/expected_output/call_timing2.bin @@ -0,0 +1 @@ + " \ No newline at end of file diff --git a/meowgb/src/main.rs b/meowgb/src/main.rs index 4421d64..76ecc35 100644 --- a/meowgb/src/main.rs +++ b/meowgb/src/main.rs @@ -193,8 +193,6 @@ pub fn run_gameboy( gameboy.debugging |= bp_triggered; if bp_triggered || step { - gameboy.debugging = bp_triggered; - let now = time::OffsetDateTime::now_utc(); if let Some(debugging_tbf) = debugging_tbf { diff --git a/meowgb/src/window/overlay.rs b/meowgb/src/window/overlay.rs index 59ecbe7..b57b876 100644 --- a/meowgb/src/window/overlay.rs +++ b/meowgb/src/window/overlay.rs @@ -1,6 +1,6 @@ /// Provides an [egui] based overlay for debugigng the emulator whilst it is /// running -use egui::{ClippedPrimitive, Context, Grid, TexturesDelta}; +use egui::{ClippedPrimitive, Context, Grid, TexturesDelta, RichText, Color32}; use egui_wgpu::renderer::{Renderer, ScreenDescriptor}; use meowgb_core::gameboy::serial::SerialWriter; use pixels::{wgpu, PixelsContext}; @@ -28,6 +28,7 @@ pub struct GuiWindowState { pub wram_window_open: bool, pub oam_window_open: bool, pub hram_window_open: bool, + pub dma_window_open: bool, } impl GuiWindowState { @@ -39,6 +40,7 @@ impl GuiWindowState { self.wram_window_open = false; self.oam_window_open = false; self.hram_window_open = false; + self.dma_window_open = false; } pub fn any_open(&self) -> bool { @@ -49,6 +51,7 @@ impl GuiWindowState { || self.wram_window_open || self.oam_window_open || self.hram_window_open + || self.dma_window_open } } @@ -68,6 +71,7 @@ pub struct Gui { pub is_debugging: bool, pub breakpoints: [[bool; 3]; 0x10000], pub sender: std::sync::mpsc::Sender, + pub dma: meowgb_core::gameboy::dma::DmaState, } impl Framework { @@ -124,6 +128,7 @@ impl Framework { self.gui.oam = gameboy.gameboy.ppu.oam; self.gui.hram = gameboy.gameboy.memory.hram; self.gui.wram = gameboy.gameboy.memory.wram; + self.gui.dma = gameboy.gameboy.dma; // Run the egui frame and create all paint jobs to prepare for rendering. let raw_input = self.egui_state.take_egui_input(window); @@ -189,6 +194,7 @@ impl Gui { wram_window_open: false, oam_window_open: false, hram_window_open: false, + dma_window_open: false, }, state_restore: None, registers: gameboy.gameboy.registers, @@ -203,6 +209,7 @@ impl Gui { wram: gameboy.gameboy.memory.wram, hram: gameboy.gameboy.memory.hram, oam: gameboy.gameboy.ppu.oam, + dma: gameboy.gameboy.dma } } @@ -235,6 +242,10 @@ impl Gui { if ui.button("Toggle OAM Window").clicked() { self.state.oam_window_open = !self.state.oam_window_open; } + + if ui.button("Toggle DMA Window").clicked() { + self.state.dma_window_open = !self.state.dma_window_open; + } }); egui::Window::new("Register State").open(&mut self.state.register_window_open).show( @@ -336,6 +347,20 @@ impl Gui { }); }); + egui::Window::new("DMA").vscroll(true).open(&mut self.state.dma_window_open).show(ctx, |ui| { + if let Some(bus) = self.dma.in_progress() { + ui.heading(RichText::new(format!("Active ({:#?} Bus)", bus)).color(Color32::LIGHT_GREEN)); + } else { + ui.heading(RichText::new("Inactive").color(Color32::LIGHT_RED)); + } + + let offset = (0xA0 - self.dma.remaining_cycles) as u16; + ui.label(format!("Read Address: {:#04X}", ((self.dma.base as u16) << 8) | offset)); + ui.label(format!("Write Address: {:#04X}", 0xFE00 | offset)); + ui.label(format!("Base: {:#04X}", (self.dma.base as u16) << 8)); + ui.label(format!("Remaining Bytes: {:#02X}", self.dma.remaining_cycles)); + }); + egui::Window::new("HRAM").vscroll(true).open(&mut self.state.hram_window_open).show(ctx, |ui| { egui::Grid::new("memory_ov_hram").show(ui, |ui| { ui.label("ROW: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"); diff --git a/run-test-roms.sh b/run-test-roms.sh index 7626947..0424adb 100755 --- a/run-test-roms.sh +++ b/run-test-roms.sh @@ -37,6 +37,16 @@ else echo "Failed: $res" fi +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/add_sp_e_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/add_sp_e_timing.gb test -m 100000000 -s meowgb-tests/expected_output/add_sp_e_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + echo "Running test ROM ./test-roms/mooneye-test-suite/roms/basic.gb" TEST_TOTAL=$((TEST_TOTAL + 1)) @@ -67,6 +77,46 @@ else echo "Failed: $res" fi +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/call_cc_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_cc_timing.gb test -m 100000000 -s meowgb-tests/expected_output/call_cc_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/call_cc_timing2.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_cc_timing2.gb test -m 100000000 -s meowgb-tests/expected_output/call_cc_timing2.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/call_timing.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_timing.gb test -m 100000000 -s meowgb-tests/expected_output/call_timing.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + +echo "Running test ROM ./test-roms/mooneye-test-suite/roms/call_timing2.gb" + +TEST_TOTAL=$((TEST_TOTAL + 1)) + +if res=$(./target/release/meowgb-tests test-roms/mooneye-test-suite/roms/call_timing2.gb test -m 100000000 -s meowgb-tests/expected_output/call_timing2.bin 2>&1 > /dev/null) ; then + TEST_SUCCESS=$((TEST_SUCCESS + 1)) +else + echo "Failed: $res" +fi + echo "Running test ROM ./test-roms/mooneye-test-suite/roms/daa.gb" TEST_TOTAL=$((TEST_TOTAL + 1)) diff --git a/test-roms/mooneye-test-suite/roms/add_sp_e_timing.gb b/test-roms/mooneye-test-suite/roms/add_sp_e_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..995df8d8f8df89f9f8ca9b671fb212c761818953 GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/test-roms/mooneye-test-suite/roms/call_timing2.gb b/test-roms/mooneye-test-suite/roms/call_timing2.gb new file mode 100644 index 0000000000000000000000000000000000000000..ab985df979accd148792a856bb844db77816a43b GIT binary patch literal 32768 zcmeI4Uq~EB7{F)OtJ$crYfQpwx5?gd$i-?fM#y0=+jZTT(-ltAO27)SS~a1qN)&3$ zc{!V=)%?>33k_JBw?JQ{P#Po<2uBi+%a*iIp^%jALwst(mXQ7lt=IW(+}_y`@Fg$J z_u=l%H{YL``OP;6_u!<{&$p078~4pGeSNn#k;1%Ta*7m@O++Z!F*!ww@7^9*zIX43 zi&wAq_Y4&OeEZ(ri=SUz*rUETI5^OIytlf)TN&vc9w8kagbKHZRQszRI>ySh@|eD7 zl*e8Y=MEyOSEwo8b8v;1!yd*3-){eEC=b*;z zCbeRp@;4#YXX)!tF%fGa{q56i#9B)Klz5+ezAFV+j|7ka5?iZ*d6rvIG!1N$~AJ^2_XqkqJG;Ae9V%7RK(DWdYJoGz zXP8!1)z-qeU}LM7B$zwXMKKhD8Dz(Yu_`Lc%b6W&F#G)ieG^nVI{NyucBp||oz;LS zvNH?ctS}1r%d#leP!UDO-(}@i4D5dq6+v+42T`@Pt*!70yPg%$OX6wk6VqBU5BByS z9UkdDHcUXLe_yxIw=;PqP))53!^6GDMve{+{AH3oJi zxczS2hY{!edg8JJ4`&jD-2@*;oSF3`NS(;UwYqU1MV#sN#3ep4>0L$K{+1i}tvhCL zqjlJ{UNx;L(@Q>0LeI-yjov|&dxcPLd+f4OoS#?x!Zu<1ri!h*e3GE*2O@_K_YZ2; z7iO@0)EY34(`Sr6C+HKU&p3Ug&7(GV+~&q??u5-nZEoD=G-;(YQ#=i{#>#1Ny_C_K zR<+Vn3W+)BYaAM%bTHO9?0?cByvCt5*C9D?>vp4U+AV5{t*L+62NIXeg1pO%D+Pf( zpY({%LUI5~Y>|?nw?U<@C2fvg