From 3b804b789a07ea5d6e1d0d15d164d98806ae74c2 Mon Sep 17 00:00:00 2001 From: TiKevin83 Date: Thu, 13 Feb 2020 23:53:47 -0500 Subject: [PATCH 1/6] Update libgambatte platform target for building on windows 10 --- libgambatte/libgambatte.vcxproj | 10 +++++----- output/dll/libgambatte.dll | Bin 171008 -> 176640 bytes 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libgambatte/libgambatte.vcxproj b/libgambatte/libgambatte.vcxproj index 37d3a96a03..2a1e315b6d 100644 --- a/libgambatte/libgambatte.vcxproj +++ b/libgambatte/libgambatte.vcxproj @@ -22,32 +22,32 @@ {5D630682-7BDA-474D-B387-0EB420DDC199} Win32Proj libgambatte - 8.1 + 10.0 DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode DynamicLibrary true - v141 + v142 Unicode DynamicLibrary false - v141 + v142 true Unicode diff --git a/output/dll/libgambatte.dll b/output/dll/libgambatte.dll index e482dab5d7df3febde5246fe253eec2c6ed619d5..e597cd2560715dd535e7386eb4674592374bcfbc 100644 GIT binary patch delta 83358 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

1Q8 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 TpMzHE=sek}_Ph6QFP-w=bpD3) delta 76977 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 Date: Sun, 16 Feb 2020 12:05:56 -0500 Subject: [PATCH 2/6] ~half the changes from Sinamas/GSR 2020 updates --- libgambatte/array.h | 52 ++ libgambatte/libgambatte.vcxproj | 4 + libgambatte/libgambatte.vcxproj.filters | 12 + libgambatte/src/Interrupter.cpp | 103 ++++ libgambatte/src/array.h | 57 ++ libgambatte/src/cpu.cpp | 147 +++--- libgambatte/src/cpu.h | 3 +- libgambatte/src/initstate.cpp | 11 +- libgambatte/src/interrupter.h | 54 ++ libgambatte/src/interruptrequester.cpp | 18 +- libgambatte/src/interruptrequester.h | 8 +- libgambatte/src/mem/cartridge.cpp | 163 +++--- libgambatte/src/mem/cartridge.h | 4 +- libgambatte/src/mem/memptrs.cpp | 162 +++--- libgambatte/src/mem/memptrs.h | 82 +-- libgambatte/src/memory.cpp | 639 +++++++++++++---------- libgambatte/src/memory.h | 22 +- libgambatte/src/savestate.h | 8 +- libgambatte/src/sound.cpp | 68 ++- libgambatte/src/sound.h | 37 +- libgambatte/src/sound/channel1.cpp | 125 ++--- libgambatte/src/sound/channel1.h | 17 +- libgambatte/src/sound/channel2.cpp | 85 ++- libgambatte/src/sound/channel2.h | 14 +- libgambatte/src/sound/channel3.cpp | 148 +++--- libgambatte/src/sound/channel3.h | 23 +- libgambatte/src/sound/channel4.cpp | 142 +++-- libgambatte/src/sound/channel4.h | 17 +- libgambatte/src/sound/duty_unit.cpp | 79 +-- libgambatte/src/sound/duty_unit.h | 3 +- libgambatte/src/sound/envelope_unit.cpp | 31 +- libgambatte/src/sound/length_counter.cpp | 40 +- libgambatte/src/sound/psgdef.h | 31 ++ libgambatte/src/tima.cpp | 66 ++- libgambatte/src/tima.h | 7 +- libgambatte/src/video/lcddef.h | 42 +- 36 files changed, 1527 insertions(+), 997 deletions(-) create mode 100644 libgambatte/array.h create mode 100644 libgambatte/src/Interrupter.cpp create mode 100644 libgambatte/src/array.h create mode 100644 libgambatte/src/interrupter.h create mode 100644 libgambatte/src/sound/psgdef.h diff --git a/libgambatte/array.h b/libgambatte/array.h new file mode 100644 index 0000000000..9b692d8412 --- /dev/null +++ b/libgambatte/array.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * sinamas@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef ARRAY_H +#define ARRAY_H + +#include + +template +class SimpleArray : Uncopyable { +public: + explicit SimpleArray(std::size_t size = 0) : a_(size ? new T[size] : 0) {} + ~SimpleArray() { delete[] defined_ptr(a_); } + void reset(std::size_t size = 0) { delete[] defined_ptr(a_); a_ = size ? new T[size] : 0; } + T* get() const { return a_; } + operator T* () const { return a_; } + +private: + T* a_; +}; + +class Uncopyable { +protected: + Uncopyable() {} +private: + Uncopyable(Uncopyable const&); + Uncopyable& operator=(Uncopyable const&); +}; + +template +inline T* defined_ptr(T* t) { + typedef char type_is_defined[sizeof * t ? 1 : -1]; + (void)sizeof(type_is_defined); + return t; +} + +#endif \ No newline at end of file diff --git a/libgambatte/libgambatte.vcxproj b/libgambatte/libgambatte.vcxproj index 2a1e315b6d..549134284f 100644 --- a/libgambatte/libgambatte.vcxproj +++ b/libgambatte/libgambatte.vcxproj @@ -154,9 +154,11 @@ + + @@ -180,6 +182,7 @@ + @@ -192,6 +195,7 @@ + diff --git a/libgambatte/libgambatte.vcxproj.filters b/libgambatte/libgambatte.vcxproj.filters index b34a8d79f1..93ddd6e40b 100644 --- a/libgambatte/libgambatte.vcxproj.filters +++ b/libgambatte/libgambatte.vcxproj.filters @@ -123,6 +123,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -203,5 +212,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/libgambatte/src/Interrupter.cpp b/libgambatte/src/Interrupter.cpp new file mode 100644 index 0000000000..3ee5af603e --- /dev/null +++ b/libgambatte/src/Interrupter.cpp @@ -0,0 +1,103 @@ +// +// Copyright (C) 2007 by sinamas +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License version 2 for more details. +// +// You should have received a copy of the GNU General Public License +// version 2 along with this program; if not, write to the +// Free Software Foundation, Inc., +// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "interrupter.h" +#include "memory.h" + +namespace gambatte { + + Interrupter::Interrupter(unsigned short& sp, unsigned short& pc, unsigned char& opcode, bool& prefetched) + : sp_(sp) + , pc_(pc) + , opcode_(opcode) + , prefetched_(prefetched) + { + } + + void Interrupter::prefetch(unsigned long cc, Memory& mem) { + if (!prefetched_) { + opcode_ = mem.read(pc_, cc); + pc_ = (pc_ + 1) & 0xFFFF; + prefetched_ = true; + } + } + + unsigned long Interrupter::interrupt(unsigned long cc, Memory& memory) { + // undo prefetch (presumably unconditional on hw). + if (prefetched_) { + pc_ = (pc_ - 1) & 0xFFFF; + prefetched_ = false; + } + cc += 12; + sp_ = (sp_ - 1) & 0xFFFF; + memory.write(sp_, pc_ >> 8, cc); + cc += 4; + + unsigned const pendingIrqs = memory.pendingIrqs(cc); + unsigned const n = pendingIrqs & -pendingIrqs; + unsigned address; + if (n <= 4) { + static unsigned char const lut[] = { 0x00, 0x40, 0x48, 0x48, 0x50 }; + address = lut[n]; + } + else + address = 0x50 + n; + + sp_ = (sp_ - 1) & 0xFFFF; + memory.write(sp_, pc_ & 0xFF, cc); + memory.ackIrq(n, cc); + pc_ = address; + cc += 4; + + if (address == 0x40 && !gsCodes_.empty()) + applyVblankCheats(cc, memory); + + return cc; + } + + static int asHex(char c) { + return c >= 'A' ? c - 'A' + 0xA : c - '0'; + } + + void Interrupter::setGameShark(std::string const& codes) { + std::string code; + gsCodes_.clear(); + + for (std::size_t pos = 0; pos < codes.length(); pos += code.length() + 1) { + code = codes.substr(pos, codes.find(';', pos) - pos); + if (code.length() >= 8) { + GsCode gs; + gs.type = asHex(code[0]) << 4 | asHex(code[1]); + gs.value = (asHex(code[2]) << 4 | asHex(code[3])) & 0xFF; + gs.address = (asHex(code[4]) << 4 + | asHex(code[5]) + | asHex(code[6]) << 12 + | asHex(code[7]) << 8) & 0xFFFF; + gsCodes_.push_back(gs); + } + } + } + + void Interrupter::applyVblankCheats(unsigned long const cc, Memory& memory) { + for (std::size_t i = 0, size = gsCodes_.size(); i < size; ++i) { + if (gsCodes_[i].type == 0x01) + memory.write(gsCodes_[i].address, gsCodes_[i].value, cc); + } + } + +} \ No newline at end of file diff --git a/libgambatte/src/array.h b/libgambatte/src/array.h new file mode 100644 index 0000000000..239af8f2ca --- /dev/null +++ b/libgambatte/src/array.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * sinamas@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ +#ifndef ARRAY_H +#define ARRAY_H + +#include + +template +class SimpleArray : Uncopyable { +public: + explicit SimpleArray(std::size_t size = 0) : a_(size ? new T[size] : 0) {} + ~SimpleArray() { delete[] defined_ptr(a_); } + void reset(std::size_t size = 0) { delete[] defined_ptr(a_); a_ = size ? new T[size] : 0; } + T* get() const { return a_; } + operator T* () const { return a_; } + +private: + T* a_; +}; + +class Uncopyable { +protected: + Uncopyable() {} +private: + Uncopyable(Uncopyable const&); + Uncopyable& operator=(Uncopyable const&); +}; + +template +inline T* defined_ptr(T* t) { + typedef char type_is_defined[sizeof * t ? 1 : -1]; + (void)sizeof(type_is_defined); + return t; +} + +template +inline void defined_delete(T* t) { delete defined_ptr(t); } + +struct defined_deleter { template static void del(T* p) { defined_delete(p); } }; + +#endif \ No newline at end of file diff --git a/libgambatte/src/cpu.cpp b/libgambatte/src/cpu.cpp index d086c9842f..1833b0290b 100644 --- a/libgambatte/src/cpu.cpp +++ b/libgambatte/src/cpu.cpp @@ -23,7 +23,7 @@ namespace gambatte { CPU::CPU() -: mem_(sp, pc) +: mem_(sp, pc, opcode_, prefetched_) , cycleCounter_(0) , pc(0x100) , sp(0xFFFE) @@ -38,7 +38,8 @@ CPU::CPU() , e(0xD8) , h(0x01) , l(0x4D) -, skip_(false) +, opcode_(0) +, prefetched_(false) , numInterruptAddresses() , tracecallback(0) { @@ -103,7 +104,13 @@ void CPU::loadState(SaveState const &state) { cf = cfFromF(state.cpu.f); h = state.cpu.h & 0xFF; l = state.cpu.l & 0xFF; - skip_ = state.cpu.skip; + opcode_ = state.cpu.opcode; + prefetched_ = state.cpu.prefetched; + if (state.cpu.skip) { + opcode_ = mem_.read(pc, cycleCounter_); + prefetched_ = true; + } + } // The main reasons for the use of macros is to more conveniently be able to tweak @@ -111,12 +118,11 @@ void CPU::loadState(SaveState const &state) { // time they were written GCC had a tendency to not be able to keep hot variables // in regs if you took an address/reference in an inline function. -#define bc() ( b << 8 | c ) -#define de() ( d << 8 | e ) -#define hl() ( h << 8 | l ) +#define bc() ( b * 0x100u | c ) +#define de() ( d * 0x100u | e ) +#define hl() ( h * 0x100u | l ) #define READ(dest, addr) do { (dest) = mem_.read(addr, cycleCounter); cycleCounter += 4; } while (0) -#define PEEK(dest, addr) do { (dest) = mem_.read(addr, cycleCounter); } while (0) #define PC_READ(dest) do { (dest) = mem_.read_excb(pc, cycleCounter, false); pc = (pc + 1) & 0xFFFF; cycleCounter += 4; } while (0) #define PC_READ_FIRST(dest) do { (dest) = mem_.read_excb(pc, cycleCounter, true); pc = (pc + 1) & 0xFFFF; cycleCounter += 4; } while (0) #define FF_READ(dest, addr) do { (dest) = mem_.ff_read(addr, cycleCounter); cycleCounter += 4; } while (0) @@ -174,7 +180,7 @@ void CPU::loadState(SaveState const &state) { // Rotate 8-bit register right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF and HCF, Check ZF: #define rr_r(r) do { \ unsigned const oldcf = cf & 0x100; \ - cf = (r) << 8; \ + cf = (r) * 0x100u; \ (r) = zf = ((r) | oldcf) >> 1; \ hf2 = 0; \ } while (0) @@ -190,7 +196,7 @@ void CPU::loadState(SaveState const &state) { // sra r (8 cycles): // Shift 8-bit register right, store old bit0 in CF. bit7=old bit7. Reset SF and HCF, Check ZF: #define sra_r(r) do { \ - cf = (r) << 8; \ + cf = (r) * 0x100u; \ zf = (r) >> 1; \ (r) = zf | ((r) & 0x80); \ hf2 = 0; \ @@ -200,7 +206,7 @@ void CPU::loadState(SaveState const &state) { // Shift 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: #define srl_r(r) do { \ zf = (r); \ - cf = (r) << 8; \ + cf = (r) * 0x100u; \ zf >>= 1; \ (r) = zf; \ hf2 = 0; \ @@ -261,7 +267,7 @@ void CPU::loadState(SaveState const &state) { unsigned const hl = hl(); \ unsigned val; \ READ(val, hl); \ - val &= ~(1 << (n)); \ + val &= ~(1u << (n)); \ WRITE(hl, val); \ } while (0) @@ -461,9 +467,8 @@ void CPU::loadState(SaveState const &state) { // rst n (16 Cycles): // Push present address onto stack, jump to address n (one of 00h,08h,10h,18h,20h,28h,30h,38h): #define rst_n(n) do { \ - cycleCounter += 4; \ - PUSH(pc >> 8, pc & 0xFF); \ - pc = n; \ + push_rr(pc >> 8, pc & 0xFF); \ + pc = (n); \ } while (0) // ret (16 cycles): @@ -474,6 +479,17 @@ void CPU::loadState(SaveState const &state) { PC_MOD(high << 8 | low); \ } while (0) +namespace { + unsigned long freeze(Memory & mem, unsigned long cc) { + mem.freeze(cc); + if (cc < mem.nextEventTime()) { + unsigned long cycles = mem.nextEventTime() - cc; + cc += cycles + (-cycles & 3); + } + return cc; + } +} + void CPU::process(unsigned long const cycles) { mem_.setEndtime(cycleCounter_, cycles); mem_.updateInput(); @@ -524,7 +540,7 @@ void CPU::process(unsigned long const cycles) { result[8] = toF(hf2, cf, zf); result[9] = h; result[10] = l; - result[11] = skip_; + result[11] = prefetched_; PC_READ_FIRST(opcode); result[12] = opcode; result[13] = mem_.debugGetLY(); @@ -534,9 +550,13 @@ void CPU::process(unsigned long const cycles) { PC_READ_FIRST(opcode); } - if (skip_) { - pc = (pc - 1) & 0xFFFF; - skip_ = false; + if (!prefetched_) { + PC_READ(opcode); + } + else { + opcode = opcode_; + cycleCounter += 4; + prefetched_ = false; } switch (opcode) { @@ -607,7 +627,7 @@ void CPU::process(unsigned long const cycles) { // rrca (4 cycles): // Rotate 8-bit register A right, store old bit0 in CF. Reset SF, HCF, ZF: case 0x0F: - cf = a << 8 | a; + cf = a * 0x100u | a; a = cf >> 1 & 0xFF; hf2 = 0; zf = 1; @@ -616,14 +636,13 @@ void CPU::process(unsigned long const cycles) { // stop (4 cycles): // Halt CPU and LCD display until button pressed: case 0x10: - { - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + PC_READ(opcode_); + cycleCounter = mem_.stop(cycleCounter - 4, prefetched_); + if (cycleCounter < mem_.nextEventTime()) { + unsigned long cycles = mem_.nextEventTime() - cycleCounter; + cycleCounter += cycles + (-cycles & 3); } + break; case 0x11: @@ -687,7 +706,7 @@ void CPU::process(unsigned long const cycles) { case 0x1F: { unsigned oldcf = cf & 0x100; - cf = a << 8; + cf = a * 0x100u; a = (a | oldcf) >> 1; } @@ -1023,14 +1042,12 @@ void CPU::process(unsigned long const cycles) { // halt (4 cycles): case 0x76: - if (mem_.ff_read(0x0F, cycleCounter) & mem_.ff_read(0xFF, cycleCounter) & 0x1F) { - if (mem_.ime()) - pc = (pc - 1) & 0xFFFF; - else - skip_ = true; + opcode_ = mem_.read(pc, cycleCounter); + if (mem_.pendingIrqs(cycleCounter)) { + prefetched_ = true; } else { - mem_.halt(cycleCounter); - + prefetched_ = mem_.halt(cycleCounter); + cycleCounter += 4 + 4 * !mem_.isCgb(); if (cycleCounter < mem_.nextEventTime()) { unsigned long cycles = mem_.nextEventTime() - cycleCounter; cycleCounter += cycles + (-cycles & 3); @@ -1705,13 +1722,7 @@ void CPU::process(unsigned long const cycles) { break; case 0xD3: // not specified. should freeze. - mem_.di(); - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + cycleCounter = freeze(mem_, cycleCounter); break; // call nc,nn (24;12 cycles): @@ -1779,13 +1790,7 @@ void CPU::process(unsigned long const cycles) { break; case 0xDB: // not specified. should freeze. - mem_.di(); - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + cycleCounter = freeze(mem_, cycleCounter); break; // call z,nn (24;12 cycles): @@ -1801,13 +1806,7 @@ void CPU::process(unsigned long const cycles) { break; case 0xDD: // not specified. should freeze. - mem_.di(); - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + cycleCounter = freeze(mem_, cycleCounter); break; case 0xDE: @@ -1846,13 +1845,7 @@ void CPU::process(unsigned long const cycles) { case 0xE3: case 0xE4: // not specified. should freeze. - mem_.di(); - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + cycleCounter = freeze(mem_, cycleCounter); break; case 0xE5: @@ -1898,16 +1891,10 @@ void CPU::process(unsigned long const cycles) { break; - case 0xEB: - case 0xEC: + case 0xEB: // not specified. should freeze. + case 0xEC: // not specified. should freeze. case 0xED: // not specified. should freeze. - mem_.di(); - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + cycleCounter = freeze(mem_, cycleCounter); break; case 0xEE: @@ -1957,13 +1944,7 @@ void CPU::process(unsigned long const cycles) { break; case 0xF4: // not specified. should freeze. - mem_.di(); - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + cycleCounter = freeze(mem_, cycleCounter); break; case 0xF5: @@ -2027,15 +2008,9 @@ void CPU::process(unsigned long const cycles) { mem_.ei(cycleCounter); break; - case 0xFC: + case 0xFC: // not specified. should freeze. case 0xFD: // not specified. should freeze - mem_.di(); - cycleCounter = mem_.stop(cycleCounter); - - if (cycleCounter < mem_.nextEventTime()) { - unsigned long cycles = mem_.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } + cycleCounter = freeze(mem_, cycleCounter); break; case 0xFE: { diff --git a/libgambatte/src/cpu.h b/libgambatte/src/cpu.h index 96c1a25b4f..2b75eef7a4 100644 --- a/libgambatte/src/cpu.h +++ b/libgambatte/src/cpu.h @@ -115,7 +115,8 @@ private: unsigned short sp; unsigned hf1, hf2, zf, cf; unsigned char a, b, c, d, e, /*f,*/ h, l; - bool skip_; + unsigned char opcode_; + bool prefetched_; int *interruptAddresses; int numInterruptAddresses; diff --git a/libgambatte/src/initstate.cpp b/libgambatte/src/initstate.cpp index 72e26c6f85..d71543e0ad 100644 --- a/libgambatte/src/initstate.cpp +++ b/libgambatte/src/initstate.cpp @@ -1178,6 +1178,8 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.cpu.f = 0; state.cpu.h = 0; state.cpu.l = 0; + state.cpu.opcode = 0x00; + state.cpu.prefetched = false; state.cpu.skip = false; state.mem.biosMode = true; state.mem.cgbSwitching = false; @@ -1199,20 +1201,22 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.mem.ioamhram.ptr[0x140] = 0; state.mem.ioamhram.ptr[0x144] = 0x00; + // DIV, TIMA, and the PSG frame sequencer are clocked by bits of the + // cycle counter less divLastUpdate (equivalent to a counter that is + // reset on DIV write). state.mem.divLastUpdate = 0; - state.mem.timaBasetime = 0; state.mem.timaLastUpdate = 0; state.mem.tmatime = disabled_time; state.mem.nextSerialtime = disabled_time; state.mem.lastOamDmaUpdate = disabled_time; state.mem.unhaltTime = disabled_time; state.mem.minIntTime = 0; - state.mem.halttime = 0; state.mem.rombank = 1; state.mem.dmaSource = 0; state.mem.dmaDestination = 0; state.mem.rambank = 0; state.mem.oamDmaPos = 0xFE; + state.mem.haltHdmaState = 0; state.mem.IME = false; state.mem.halted = false; state.mem.enableRam = false; @@ -1269,11 +1273,12 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM // spu.cycleCounter >> 12 & 7 represents the frame sequencer position. state.spu.cycleCounter = state.cpu.cycleCounter >> 1; + state.spu.lastUpdate = 0; state.spu.ch1.sweep.counter = SoundUnit::counter_disabled; state.spu.ch1.sweep.shadow = 0; state.spu.ch1.sweep.nr0 = 0; - state.spu.ch1.sweep.negging = false; + state.spu.ch1.sweep.neg = false; state.spu.ch1.duty.nextPosUpdate = SoundUnit::counter_disabled; state.spu.ch1.duty.pos = 0; state.spu.ch1.duty.high = false; diff --git a/libgambatte/src/interrupter.h b/libgambatte/src/interrupter.h new file mode 100644 index 0000000000..5daa808835 --- /dev/null +++ b/libgambatte/src/interrupter.h @@ -0,0 +1,54 @@ +// +// Copyright (C) 2007 by sinamas +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License version 2 for more details. +// +// You should have received a copy of the GNU General Public License +// version 2 along with this program; if not, write to the +// Free Software Foundation, Inc., +// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifndef INTERRUPTER_H +#define INTERRUPTER_H + +#include +#include + +namespace gambatte { + + struct GsCode { + unsigned short address; + unsigned char value; + unsigned char type; + }; + + class Memory; + + class Interrupter { + public: + Interrupter(unsigned short& sp, unsigned short& pc, unsigned char& opcode, bool& prefetched); + void prefetch(unsigned long cc, Memory& mem); + unsigned long interrupt(unsigned long cycleCounter, Memory& memory); + void setGameShark(std::string const& codes); + + private: + unsigned short& sp_; + unsigned short& pc_; + unsigned char& opcode_; + bool& prefetched_; + std::vector gsCodes_; + + void applyVblankCheats(unsigned long cc, Memory& mem); + }; + +} + +#endif diff --git a/libgambatte/src/interruptrequester.cpp b/libgambatte/src/interruptrequester.cpp index af37ed584e..eaa0fce20f 100644 --- a/libgambatte/src/interruptrequester.cpp +++ b/libgambatte/src/interruptrequester.cpp @@ -83,9 +83,14 @@ void InterruptRequester::flagIrq(unsigned bit) { eventTimes_.setValue(minIntTime_); } -void InterruptRequester::ackIrq(unsigned bit) { - ifreg_ ^= bit; - di(); +void InterruptRequester::flagIrq(unsigned bit, unsigned long cc) { + unsigned const prevPending = pendingIrqs(); + ifreg_ |= bit; + + if (!prevPending && pendingIrqs() && intFlags_.imeOrHalted()) { + minIntTime_ = std::max(minIntTime_, cc); + eventTimes_.setValue(minIntTime_); + } } void InterruptRequester::setIereg(unsigned iereg) { @@ -108,6 +113,13 @@ void InterruptRequester::setIfreg(unsigned ifreg) { } } +void InterruptRequester::setMinIntTime(unsigned long cc) { + minIntTime_ = cc; + + if (eventTimes_.value(intevent_interrupts) < minIntTime_) + eventTimes_.setValue(minIntTime_); +} + SYNCFUNC(InterruptRequester) { SSS(eventTimes_); diff --git a/libgambatte/src/interruptrequester.h b/libgambatte/src/interruptrequester.h index cb54265078..1b8aa3230f 100644 --- a/libgambatte/src/interruptrequester.h +++ b/libgambatte/src/interruptrequester.h @@ -52,9 +52,11 @@ public: void halt(); void unhalt(); void flagIrq(unsigned bit); - void ackIrq(unsigned bit); + void flagIrq(unsigned bit, unsigned long cc); + void ackIrq(unsigned bit) { ifreg_ &= ~bit; } void setIereg(unsigned iereg); void setIfreg(unsigned ifreg); + void setMinIntTime(unsigned long cc); IntEventId minEventId() const { return static_cast(eventTimes_.min()); } unsigned long minEventTime() const { return eventTimes_.minValue(); } @@ -71,9 +73,9 @@ private: bool halted() const { return flags_ & flag_halted; } bool imeOrHalted() const { return flags_; } void setIme() { flags_ |= flag_ime; } - void unsetIme() { flags_ &= ~flag_ime; } + void unsetIme() { flags_ &= ~(1u * flag_ime); } void setHalted() { flags_ |= flag_halted; } - void unsetHalted() { flags_ &= ~flag_halted; } + void unsetHalted() { flags_ &= ~(1u * flag_halted); } void set(bool ime, bool halted) { flags_ = halted * flag_halted + ime * flag_ime; } private: diff --git a/libgambatte/src/mem/cartridge.cpp b/libgambatte/src/mem/cartridge.cpp index 0d6539af68..1cdd7cc968 100644 --- a/libgambatte/src/mem/cartridge.cpp +++ b/libgambatte/src/mem/cartridge.cpp @@ -23,18 +23,18 @@ #include #include -namespace gambatte { +using namespace gambatte; namespace { -static unsigned toMulti64Rombank(unsigned rombank) { +unsigned toMulti64Rombank(unsigned rombank) { return (rombank >> 1 & 0x30) | (rombank & 0xF); } class DefaultMbc : public Mbc { public: virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { - return (addr< 0x4000) == (bank == 0); + return (addr < rombank_size()) == (bank == 0); } virtual void SyncState(NewState *ns, bool isReader) @@ -50,8 +50,12 @@ public: { } + virtual unsigned char curRomBank() const { + return 1; + } + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) { - if (p < 0x2000) { + if (p < rambank_size()) { enableRam_ = (data & 0xF) == 0xA; memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0); } @@ -73,12 +77,12 @@ public: } }; -static inline unsigned rambanks(MemPtrs const &memptrs) { - return std::size_t(memptrs.rambankdataend() - memptrs.rambankdata()) / 0x2000; +inline unsigned rambanks(MemPtrs const &memptrs) { + return (memptrs.rambankdataend() - memptrs.rambankdata()) / rambank_size(); } -static inline unsigned rombanks(MemPtrs const &memptrs) { - return std::size_t(memptrs.romdataend() - memptrs.romdata() ) / 0x4000; +inline unsigned rombanks(MemPtrs const &memptrs) { + return (memptrs.romdataend() - memptrs.romdata()) / rombank_size(); } class Mbc1 : public DefaultMbc { @@ -92,6 +96,10 @@ public: { } + virtual unsigned char curRomBank() const { + return rombank_; + } + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) { switch (p >> 13 & 3) { case 0: @@ -113,8 +121,7 @@ public: break; case 3: - // Pretty sure this should take effect immediately, but I have a policy not to change old behavior - // unless I have something (eg. a verified test or a game) that justifies it. + // Should this take effect immediately rather? rambankMode_ = data & 1; break; } @@ -143,7 +150,7 @@ private: rambank_ & (rambanks(memptrs_) - 1)); } - void setRombank() const { memptrs_.setRombank(adjustedRombank(rombank_) & (rombanks(memptrs_) - 1)); } + void setRombank() const { memptrs_.setRombank(rombank_ & (rombanks(memptrs_) - 1)); } public: @@ -166,6 +173,10 @@ public: { } + virtual unsigned char curRomBank() const { + return rombank_; + } + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) { switch (p >> 13 & 3) { case 0: @@ -238,6 +249,10 @@ public: { } + virtual unsigned char curRomBank() const { + return rombank_; + } + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) { switch (p & 0x6100) { case 0x0000: @@ -282,6 +297,10 @@ public: { } + virtual unsigned char curRomBank() const { + return rombank_; + } + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const cc) { switch (p >> 13 & 3) { case 0: @@ -459,14 +478,12 @@ private: unsigned char rambank_; bool enableRam_; - static unsigned adjustedRombank(unsigned bank) { return bank; } - void setRambank() const { memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, rambank_ & (rambanks(memptrs_) - 1)); } - void setRombank() const { memptrs_.setRombank(adjustedRombank(rombank_) & (rombanks(memptrs_) - 1)); } + void setRombank() const { memptrs_.setRombank(rombank_ & (rombanks(memptrs_) - 1)); } public: virtual void SyncState(NewState *ns, bool isReader) @@ -477,12 +494,76 @@ public: } }; -static bool hasRtc(unsigned headerByte0x147) { +std::string stripExtension(std::string const& str) { + std::string::size_type const lastDot = str.find_last_of('.'); + std::string::size_type const lastSlash = str.find_last_of('/'); + + if (lastDot != std::string::npos && (lastSlash == std::string::npos || lastSlash < lastDot)) + return str.substr(0, lastDot); + + return str; +} + +std::string stripDir(std::string const& str) { + std::string::size_type const lastSlash = str.find_last_of('/'); + if (lastSlash != std::string::npos) + return str.substr(lastSlash + 1); + + return str; +} + +void enforce8bit(unsigned char* data, std::size_t size) { + if (static_cast(0x100)) + while (size--) + *data++ &= 0xFF; +} + +unsigned pow2ceil(unsigned n) { + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + ++n; + + return n; +} + +bool presumedMulti64Mbc1(unsigned char const header[], unsigned rombanks) { + return header[0x147] == 1 && header[0x149] == 0 && rombanks == 64; +} + +bool hasBattery(unsigned char headerByte0x147) { + switch (headerByte0x147) { + case 0x03: + case 0x06: + case 0x09: + case 0x0F: + case 0x10: + case 0x13: + case 0x1B: + case 0x1E: + case 0xFE: // huc3 + case 0xFF: + return true; + } + + return false; +} + +bool hasRtc(unsigned headerByte0x147) { switch (headerByte0x147) { case 0x0F: - case 0x10: return true; - default: return false; + case 0x10: + case 0xFE: // huc3 + return true; } + + return false; +} + +int asHex(char c) { + return c >= 'A' ? c - 'A' + 0xA : c - '0'; } } @@ -503,23 +584,6 @@ void Cartridge::loadState(SaveState const &state) { mbc_->loadState(state.mem); } -static void enforce8bit(unsigned char *data, std::size_t size) { - if (static_cast(0x100)) - while (size--) - *data++ &= 0xFF; -} - -static unsigned pow2ceil(unsigned n) { - --n; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - ++n; - - return n; -} - static bool isMbc2(unsigned char h147) { return h147 == 5 || h147 == 6; } static unsigned numRambanksFromH14x(unsigned char h147, unsigned char h149) { @@ -532,10 +596,6 @@ static unsigned numRambanksFromH14x(unsigned char h147, unsigned char h149) { return 4; } -static bool presumedMulti64Mbc1(unsigned char const header[], unsigned rombanks) { - return header[0x147] == 1 && header[0x149] == 0 && rombanks == 64; -} - LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool const forceDmg, bool const multicartCompat) { enum Cartridgetype { type_plain, type_mbc1, @@ -610,17 +670,17 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool cgb = !forceDmg; } std::size_t const filesize = romfilelength; - rombanks = std::max(pow2ceil(filesize / 0x4000), 2u); + rombanks = std::max(pow2ceil(filesize / rombank_size()), 2u); mbc_.reset(); memptrs_.reset(rombanks, rambanks, cgb ? 8 : 2); rtc_.set(false, 0); - std::memcpy(memptrs_.romdata(), romfiledata, (filesize / 0x4000) * 0x4000ul); - std::memset(memptrs_.romdata() + filesize / 0x4000 * 0x4000ul, + std::memcpy(memptrs_.romdata(), romfiledata, (filesize / rombank_size() * rombank_size())); + std::memset(memptrs_.romdata() + filesize / rombank_size() * rombank_size(), 0xFF, - (rombanks - filesize / 0x4000) * 0x4000ul); - enforce8bit(memptrs_.romdata(), rombanks * 0x4000ul); + (rombanks - filesize / rombank_size()) * rombank_size()); + enforce8bit(memptrs_.romdata(), rombanks * rombank_size()); switch (type) { case type_plain: mbc_.reset(new Mbc0(memptrs_)); break; @@ -642,21 +702,6 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool return LOADRES_OK; } -static bool hasBattery(unsigned char headerByte0x147) { - switch (headerByte0x147) { - case 0x03: - case 0x06: - case 0x09: - case 0x0F: - case 0x10: - case 0x13: - case 0x1B: - case 0x1E: - case 0xFF: return true; - default: return false; - } -} - void Cartridge::loadSavedata(char const *data, unsigned long const cc) { if (hasBattery(memptrs_.romdata()[0x147])) { int length = memptrs_.rambankdataend() - memptrs_.rambankdata(); @@ -747,5 +792,3 @@ SYNCFUNC(Cartridge) SSS(rtc_); TSS(mbc_); } - -} diff --git a/libgambatte/src/mem/cartridge.h b/libgambatte/src/mem/cartridge.h index e07baa4aa8..2260c31349 100644 --- a/libgambatte/src/mem/cartridge.h +++ b/libgambatte/src/mem/cartridge.h @@ -34,6 +34,7 @@ namespace gambatte { class Mbc { public: virtual ~Mbc() {} + virtual unsigned char curRomBank() const = 0; virtual void romWrite(unsigned P, unsigned data, unsigned long cycleCounter) = 0; virtual void loadState(SaveState::Mem const &ss) = 0; virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0; @@ -62,10 +63,11 @@ public: unsigned char * wsrambankptr() const { return memptrs_.wsrambankptr(); } unsigned char * vrambankptr() const { return memptrs_.vrambankptr(); } OamDmaSrc oamDmaSrc() const { return memptrs_.oamDmaSrc(); } + bool isInOamDmaConflictArea(unsigned p) const { return memptrs_.isInOamDmaConflictArea(p); } void setVrambank(unsigned bank) { memptrs_.setVrambank(bank); } void setWrambank(unsigned bank) { memptrs_.setWrambank(bank); } void setOamDmaSrc(OamDmaSrc oamDmaSrc) { memptrs_.setOamDmaSrc(oamDmaSrc); } - unsigned curRomBank() const { return memptrs_.curRomBank(); } + unsigned char curRomBank() const { return mbc_->curRomBank(); } void mbcWrite(unsigned addr, unsigned data, unsigned long const cc) { mbc_->romWrite(addr, data, cc); } bool isCgb() const { return gambatte::isCgb(memptrs_); } void resetCc(unsigned long const oldCc, unsigned long const newCc) { time_.resetCc(oldCc, newCc); } diff --git a/libgambatte/src/mem/memptrs.cpp b/libgambatte/src/mem/memptrs.cpp index 65b5125bbf..82fd17a9bb 100644 --- a/libgambatte/src/mem/memptrs.cpp +++ b/libgambatte/src/mem/memptrs.cpp @@ -20,7 +20,56 @@ #include #include -namespace gambatte { +using namespace gambatte; + +namespace { + + template struct OamDmaConflictMap; + template struct OamDmaConflictMap { enum { r = 0xFCFF }; }; + template struct OamDmaConflictMap { enum { r = 0xFCFF }; }; + template struct OamDmaConflictMap { enum { r = 0x0300 }; }; + template struct OamDmaConflictMap { enum { r = cgb ? 0xF000 : 0xFCFF }; }; + template struct OamDmaConflictMap { enum { r = cgb ? 0xFCFF : 0x0000 }; }; + + template + bool isInOamDmaConflictArea(OamDmaSrc src, unsigned p) + { + static unsigned short const m[] = { + OamDmaConflictMap::r, + OamDmaConflictMap::r, + OamDmaConflictMap::r, + OamDmaConflictMap::r, + OamDmaConflictMap::r, + 0 }; + return p < mm_oam_begin && (m[src] >> (p >> 12) & 1); + } + + template + void disconnectOamDmaAreas(unsigned char const* (&rmem)[0x10], unsigned char* (&wmem)[0x10]) + { + if (OamDmaConflictMap::r & 0x00FF) + std::fill_n(rmem, 8, static_cast(0)); + if (OamDmaConflictMap::r & 0x0C00) + rmem[0xB] = rmem[0xA] = wmem[0xB] = wmem[0xA] = 0; + if (OamDmaConflictMap::r & 0x7000) + rmem[0xE] = rmem[0xD] = rmem[0xC] = wmem[0xE] = wmem[0xD] = wmem[0xC] = 0; + } + + template + void disconnectOamDmaAreas(unsigned char const* (&rmem)[0x10], unsigned char* (&wmem)[0x10], + OamDmaSrc src) + { + switch (src) { + case oam_dma_src_rom: disconnectOamDmaAreas(rmem, wmem); break; + case oam_dma_src_sram: disconnectOamDmaAreas(rmem, wmem); break; + case oam_dma_src_vram: disconnectOamDmaAreas(rmem, wmem); break; + case oam_dma_src_wram: disconnectOamDmaAreas(rmem, wmem); break; + case oam_dma_src_invalid: disconnectOamDmaAreas(rmem, wmem); break; + case oam_dma_src_off: break; + } + } + +} // unnamed namespace. MemPtrs::MemPtrs() : rmem_() @@ -30,7 +79,6 @@ MemPtrs::MemPtrs() , vrambankptr_(0) , rsrambankptr_(0) , wsrambankptr_(0) -, memchunk_(0) , rambankdata_(0) , wramdataend_(0) , oamDmaSrc_(oam_dma_src_off) @@ -39,32 +87,27 @@ MemPtrs::MemPtrs() { } -MemPtrs::~MemPtrs() { - delete []memchunk_; -} - void MemPtrs::reset(unsigned const rombanks, unsigned const rambanks, unsigned const wrambanks) { - delete []memchunk_; - memchunk_len = - 0x4000 - + rombanks * 0x4000ul - + 0x4000 - + rambanks * 0x2000ul - + wrambanks * 0x1000ul - + 0x4000; - memchunk_ = new unsigned char[memchunk_len]; + int const num_disabled_ram_areas = 2; + memchunk_.reset( + pre_rom_pad_size() + + rombanks * rombank_size() + + max_num_vrambanks * vrambank_size() + + rambanks * rambank_size() + + wrambanks * wrambank_size() + + num_disabled_ram_areas * rambank_size()); romdata_[0] = romdata(); - rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000; - wramdata_[0] = rambankdata_ + rambanks * 0x2000ul; - wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul; + rambankdata_ = romdata_[0] + rombanks * rombank_size() + max_num_vrambanks * vrambank_size(); + wramdata_[0] = rambankdata_ + rambanks * rambank_size(); + wramdataend_ = wramdata_[0] + wrambanks * wrambank_size(); - std::memset(rdisabledRamw(), 0xFF, 0x2000); + std::fill_n(rdisabledRamw(), rambank_size(), 0xFF); oamDmaSrc_ = oam_dma_src_off; rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; - rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; - rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; + rmem_[0xC] = wmem_[0xC] = wramdata_[0] - mm_wram_begin; + rmem_[0xE] = wmem_[0xE] = wramdata_[0] - mm_wram_mirror_begin; setRombank(1); setRambank(0, 0); setVrambank(0); @@ -76,38 +119,40 @@ void MemPtrs::reset(unsigned const rombanks, unsigned const rambanks, unsigned c } void MemPtrs::setRombank0(unsigned bank) { - romdata_[0] = romdata() + bank * 0x4000ul; + romdata_[0] = romdata() + bank * rombank_size(); rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; disconnectOamDmaAreas(); } void MemPtrs::setRombank(unsigned bank) { curRomBank_ = bank; - romdata_[1] = romdata() + bank * 0x4000ul - 0x4000; + romdata_[1] = romdata() + bank * rombank_size() - mm_rom1_begin; rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; disconnectOamDmaAreas(); } void MemPtrs::setRambank(unsigned const flags, unsigned const rambank) { - unsigned char *srambankptr = 0; + unsigned char* srambankptr = 0; if (!(flags & rtc_en)) { srambankptr = rambankdata() != rambankdataend() - ? rambankdata_ + rambank * 0x2000ul - 0xA000 - : wdisabledRam() - 0xA000; + ? rambankdata_ + rambank * rambank_size() - mm_sram_begin + : wdisabledRam() - mm_sram_begin; } - rsrambankptr_ = (flags & read_en) && srambankptr != wdisabledRam() - 0xA000 - ? srambankptr - : rdisabledRamw() - 0xA000; - wsrambankptr_ = flags & write_en ? srambankptr : wdisabledRam() - 0xA000; + rsrambankptr_ = (flags & read_en) && srambankptr != wdisabledRam() - mm_sram_begin + ? srambankptr + : rdisabledRamw() - mm_sram_begin; + wsrambankptr_ = flags & write_en + ? srambankptr + : wdisabledRam() - mm_sram_begin; rmem_[0xB] = rmem_[0xA] = rsrambankptr_; wmem_[0xB] = wmem_[0xA] = wsrambankptr_; disconnectOamDmaAreas(); } void MemPtrs::setWrambank(unsigned bank) { - wramdata_[1] = wramdata_[0] + (bank & 0x07 ? bank & 0x07 : 1) * 0x1000; - rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; + wramdata_[1] = wramdata_[0] + (bank & 0x07 ? bank & 0x07 : 1) * wrambank_size(); + rmem_[0xD] = wmem_[0xD] = wramdata_[1] - mm_wram1_begin; disconnectOamDmaAreas(); } @@ -116,51 +161,25 @@ void MemPtrs::setOamDmaSrc(OamDmaSrc oamDmaSrc) { rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; rmem_[0xB] = rmem_[0xA] = rsrambankptr_; wmem_[0xB] = wmem_[0xA] = wsrambankptr_; - rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; - rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; - rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; + rmem_[0xC] = wmem_[0xC] = wramdata_[0] - mm_wram_begin; + rmem_[0xD] = wmem_[0xD] = wramdata_[1] - mm_wram1_begin; + rmem_[0xE] = wmem_[0xE] = wramdata_[0] - mm_wram_mirror_begin; oamDmaSrc_ = oamDmaSrc; disconnectOamDmaAreas(); } void MemPtrs::disconnectOamDmaAreas() { - if (isCgb(*this)) { - switch (oamDmaSrc_) { - case oam_dma_src_rom: // fall through - case oam_dma_src_sram: - case oam_dma_src_invalid: - std::fill(rmem_, rmem_ + 8, static_cast(0)); - rmem_[0xB] = rmem_[0xA] = 0; - wmem_[0xB] = wmem_[0xA] = 0; - break; - case oam_dma_src_vram: - break; - case oam_dma_src_wram: - rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; - wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; - break; - case oam_dma_src_off: - break; - } - } else { - switch (oamDmaSrc_) { - case oam_dma_src_rom: // fall through - case oam_dma_src_sram: - case oam_dma_src_wram: - case oam_dma_src_invalid: - std::fill(rmem_, rmem_ + 8, static_cast(0)); - rmem_[0xB] = rmem_[0xA] = 0; - wmem_[0xB] = wmem_[0xA] = 0; - rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; - wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; - break; - case oam_dma_src_vram: - break; - case oam_dma_src_off: - break; - } - } + return isCgb(*this) + ? ::disconnectOamDmaAreas(rmem_, wmem_, oamDmaSrc_) + : ::disconnectOamDmaAreas(rmem_, wmem_, oamDmaSrc_); +} + +bool MemPtrs::isInOamDmaConflictArea(unsigned p) const +{ + return isCgb(*this) + ? ::isInOamDmaConflictArea(oamDmaSrc_, p) + : ::isInOamDmaConflictArea(oamDmaSrc_, p); } // all pointers here are relative to memchunk_ @@ -220,4 +239,3 @@ SYNCFUNC(MemPtrs) NSS(curRomBank_); } -} diff --git a/libgambatte/src/mem/memptrs.h b/libgambatte/src/mem/memptrs.h index d41a265394..6191624481 100644 --- a/libgambatte/src/mem/memptrs.h +++ b/libgambatte/src/mem/memptrs.h @@ -20,46 +20,65 @@ #define MEMPTRS_H #include "newstate.h" +#include "array.h" namespace gambatte { -enum OamDmaSrc { oam_dma_src_rom, - oam_dma_src_sram, - oam_dma_src_vram, - oam_dma_src_wram, - oam_dma_src_invalid, - oam_dma_src_off, }; +enum OamDmaSrc { + oam_dma_src_rom, + oam_dma_src_sram, + oam_dma_src_vram, + oam_dma_src_wram, + oam_dma_src_invalid, + oam_dma_src_off }; + +enum { + mm_rom_begin = 0x0000, + mm_rom1_begin = 0x4000, + mm_vram_begin = 0x8000, + mm_sram_begin = 0xA000, + mm_wram_begin = 0xC000, + mm_wram1_begin = 0xD000, + mm_wram_mirror_begin = 0xE000, + mm_oam_begin = 0xFE00, + mm_io_begin = 0xFF00, + mm_hram_begin = 0xFF80 }; + +enum { max_num_vrambanks = 2 }; +inline std::size_t rambank_size() { return 0x2000; } +inline std::size_t rombank_size() { return 0x4000; } +inline std::size_t vrambank_size() { return 0x2000; } +inline std::size_t wrambank_size() { return 0x1000; } class MemPtrs { public: enum RamFlag { read_en = 1, write_en = 2, rtc_en = 4 }; MemPtrs(); - ~MemPtrs(); void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks); - unsigned char const * rmem(unsigned area) const { return rmem_[area]; } - unsigned char * wmem(unsigned area) const { return wmem_[area]; } - unsigned char * vramdata() const { return rambankdata_ - 0x4000; } - unsigned char * vramdataend() const { return rambankdata_; } - unsigned char * romdata() const { return memchunk_ + 0x4000; } - unsigned char * romdata(unsigned area) const { return romdata_[area]; } - unsigned char * romdataend() const { return rambankdata_ - 0x4000; } - unsigned char * wramdata(unsigned area) const { return wramdata_[area]; } - unsigned char * wramdataend() const { return wramdataend_; } - unsigned char * rambankdata() const { return rambankdata_; } - unsigned char * rambankdataend() const { return wramdata_[0]; } - unsigned char const * rdisabledRam() const { return rdisabledRamw(); } - unsigned char const * rsrambankptr() const { return rsrambankptr_; } - unsigned char * wsrambankptr() const { return wsrambankptr_; } - unsigned char * vrambankptr() const { return vrambankptr_; } + unsigned char const* rmem(unsigned area) const { return rmem_[area]; } + unsigned char* wmem(unsigned area) const { return wmem_[area]; } + unsigned char* romdata() const { return memchunk_ + pre_rom_pad_size(); } + unsigned char* romdata(unsigned area) const { return romdata_[area]; } + unsigned char* romdataend() const { return rambankdata_ - max_num_vrambanks * vrambank_size(); } + unsigned char* vramdata() const { return romdataend(); } + unsigned char* vramdataend() const { return rambankdata_; } + unsigned char* rambankdata() const { return rambankdata_; } + unsigned char* rambankdataend() const { return wramdata_[0]; } + unsigned char* wramdata(unsigned area) const { return wramdata_[area]; } + unsigned char* wramdataend() const { return wramdataend_; } + unsigned char const* rdisabledRam() const { return rdisabledRamw(); } + unsigned char const* rsrambankptr() const { return rsrambankptr_; } + unsigned char* wsrambankptr() const { return wsrambankptr_; } + unsigned char* vrambankptr() const { return vrambankptr_; } OamDmaSrc oamDmaSrc() const { return oamDmaSrc_; } - unsigned curRomBank() const { return curRomBank_; } + bool isInOamDmaConflictArea(unsigned p) const; void setRombank0(unsigned bank); void setRombank(unsigned bank); void setRambank(unsigned ramFlags, unsigned rambank); - void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; } + void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * vrambank_size() - mm_vram_begin; } void setWrambank(unsigned bank); void setOamDmaSrc(OamDmaSrc oamDmaSrc); @@ -71,7 +90,7 @@ private: unsigned char *vrambankptr_; unsigned char *rsrambankptr_; unsigned char *wsrambankptr_; - unsigned char *memchunk_; + SimpleArray memchunk_; unsigned char *rambankdata_; unsigned char *wramdataend_; OamDmaSrc oamDmaSrc_; @@ -82,18 +101,19 @@ private: int memchunk_saveoffs; int memchunk_savelen; - MemPtrs(MemPtrs const &); - MemPtrs & operator=(MemPtrs const &); + static std::size_t pre_rom_pad_size() { return mm_rom1_begin; } void disconnectOamDmaAreas(); - unsigned char * rdisabledRamw() const { return wramdataend_ ; } - unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; } + unsigned char * rdisabledRamw() const { return wramdataend_; } + unsigned char * wdisabledRam() const { return wramdataend_ + rambank_size(); } public: templatevoid SyncState(NewState *ns); }; -inline bool isCgb(MemPtrs const &memptrs) { - return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000; +inline bool isCgb(MemPtrs const& memptrs) { + int const num_cgb_wrambanks = 8; + std::size_t const wramsize = memptrs.wramdataend() - memptrs.wramdata(0); + return wramsize == num_cgb_wrambanks * wrambank_size(); } } diff --git a/libgambatte/src/memory.cpp b/libgambatte/src/memory.cpp index 6ddce9da05..b63a243fe8 100644 --- a/libgambatte/src/memory.cpp +++ b/libgambatte/src/memory.cpp @@ -20,11 +20,27 @@ #include "savestate.h" #include "sound.h" #include "video.h" +#include #include -namespace gambatte { +using namespace gambatte; -Memory::Memory(unsigned short &sp, unsigned short &pc) +namespace { + + int const oam_size = 4 * lcd_num_oam_entries; + + void decCycles(unsigned long& counter, unsigned long dec) { + if (counter != disabled_time) + counter -= dec; + } + + int serialCntFrom(unsigned long cyclesUntilDone, bool cgbFast) { + return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9; + } + +} // unnamed namespace. + +Memory::Memory(Interrupter const& interrupter) : readCallback_(0) , writeCallback_(0) , execCallback_(0) @@ -35,17 +51,18 @@ Memory::Memory(unsigned short &sp, unsigned short &pc) , divLastUpdate_(0) , lastOamDmaUpdate_(disabled_time) , lcd_(ioamhram_, 0, VideoInterruptRequester(intreq_)) +, interrupter_(interrupter) , dmaSource_(0) , dmaDestination_(0) -, oamDmaPos_(0xFE) +, oamDmaPos_(-2u & 0xFF) +, oamDmaStartPos_(0) , serialCnt_(0) , blanklcd_(false) , LINKCABLE_(false) , linkClockTrigger_(false) -, sp_(sp) -, pc_(pc) +, haltHdmaState_(hdma_low) { - intreq_.setEventTime(144 * 456ul); + intreq_.setEventTime(1l * lcd_vres * lcd_cycles_per_line); intreq_.setEventTime(0); } @@ -61,11 +78,6 @@ void Memory::setStatePtrs(SaveState &state) { psg_.setStatePtrs(state); } - -static int serialCntFrom(unsigned long cyclesUntilDone, bool cgbFast) { - return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9; -} - void Memory::loadState(SaveState const &state) { biosMode_ = state.mem.biosMode; cgbSwitching_ = state.mem.cgbSwitching; @@ -73,51 +85,55 @@ void Memory::loadState(SaveState const &state) { gbIsCgb_ = state.mem.gbIsCgb; stopped_ = state.mem.stopped; psg_.loadState(state); - lcd_.loadState(state, state.mem.oamDmaPos < 0xA0 ? cart_.rdisabledRam() : ioamhram_); + lcd_.loadState(state, state.mem.oamDmaPos < oam_size ? cart_.rdisabledRam() : ioamhram_); tima_.loadState(state, TimaInterruptRequester(intreq_)); cart_.loadState(state); intreq_.loadState(state); - divLastUpdate_ = state.mem.divLastUpdate; intreq_.setEventTime(state.mem.nextSerialtime > state.cpu.cycleCounter ? state.mem.nextSerialtime : state.cpu.cycleCounter); intreq_.setEventTime(state.mem.unhaltTime); - halttime_ = state.mem.halttime; lastOamDmaUpdate_ = state.mem.lastOamDmaUpdate; dmaSource_ = state.mem.dmaSource; dmaDestination_ = state.mem.dmaDestination; oamDmaPos_ = state.mem.oamDmaPos; + oamDmaStartPos_ = 0; + haltHdmaState_ = static_cast(std::min(1u * state.mem.haltHdmaState, 1u * hdma_requested)); serialCnt_ = intreq_.eventTime(intevent_serial) != disabled_time - ? serialCntFrom(intreq_.eventTime(intevent_serial) - state.cpu.cycleCounter, - ioamhram_[0x102] & isCgb() * 2) - : 8; + ? serialCntFrom(intreq_.eventTime(intevent_serial) - state.cpu.cycleCounter, + ioamhram_[0x102] & isCgb() * 2) + : 8; cart_.setVrambank(ioamhram_[0x14F] & isCgb()); cart_.setOamDmaSrc(oam_dma_src_off); cart_.setWrambank(isCgb() && (ioamhram_[0x170] & 0x07) ? ioamhram_[0x170] & 0x07 : 1); if (lastOamDmaUpdate_ != disabled_time) { + if (lastOamDmaUpdate_ > state.cpu.cycleCounter) { + oamDmaStartPos_ = (oamDmaPos_ + (lastOamDmaUpdate_ - state.cpu.cycleCounter) / 4) & 0xFF; + lastOamDmaUpdate_ = state.cpu.cycleCounter; + } oamDmaInitSetup(); - unsigned oamEventPos = oamDmaPos_ < 0xA0 ? 0xA0 : 0x100; + unsigned oamEventPos = oamDmaPos_ < oam_size ? oam_size : oamDmaStartPos_; intreq_.setEventTime( - lastOamDmaUpdate_ + (oamEventPos - oamDmaPos_) * 4); + lastOamDmaUpdate_ + ((oamEventPos - oamDmaPos_) & 0xFF) * 4); } intreq_.setEventTime(ioamhram_[0x140] & lcdc_en - ? lcd_.nextMode1IrqTime() - : state.cpu.cycleCounter); + ? lcd_.nextMode1IrqTime() + : state.cpu.cycleCounter); blanklcd_ = false; if (!isCgb()) - std::memset(cart_.vramdata() + 0x2000, 0, 0x2000); + std::fill_n(cart_.vramdata() + vrambank_size(), vrambank_size(), 0); } void Memory::setEndtime(unsigned long cc, unsigned long inc) { if (intreq_.eventTime(intevent_blit) <= cc) { intreq_.setEventTime(intreq_.eventTime(intevent_blit) - + (70224 << isDoubleSpeed())); + + (lcd_cycles_per_frame << isDoubleSpeed())); } intreq_.setEventTime(cc + (inc << isDoubleSpeed())); @@ -129,11 +145,11 @@ void Memory::updateSerial(unsigned long const cc) { if (intreq_.eventTime(intevent_serial) <= cc) { ioamhram_[0x101] = (((ioamhram_[0x101] + 1) << serialCnt_) - 1) & 0xFF; ioamhram_[0x102] &= 0x7F; + intreq_.flagIrq(8, intreq_.eventTime(intevent_serial)); intreq_.setEventTime(disabled_time); - intreq_.flagIrq(8); } else { int const targetCnt = serialCntFrom(intreq_.eventTime(intevent_serial) - cc, - ioamhram_[0x102] & isCgb() * 2); + ioamhram_[0x102] & isCgb() * 2); ioamhram_[0x101] = (((ioamhram_[0x101] + 1) << (serialCnt_ - targetCnt)) - 1) & 0xFF; serialCnt_ = targetCnt; } @@ -167,11 +183,14 @@ unsigned long Memory::event(unsigned long cc) { switch (intreq_.minEventId()) { case intevent_unhalt: + if ((lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc) && haltHdmaState_ == hdma_low) + || haltHdmaState_ == hdma_requested) { + flagHdmaReq(intreq_); + } + intreq_.unhalt(); intreq_.setEventTime(disabled_time); - nontrivial_ff_write(0xFF04, 0, cc); - pc_ = (pc_ + 1) & 0xFFFF; - cc += 4; + stopped_ = false; break; case intevent_end: intreq_.setEventTime(disabled_time - 1); @@ -197,7 +216,7 @@ unsigned long Memory::event(unsigned long cc) { while (cc >= intreq_.minEventTime()) cc = event(cc); } else - blitTime += 70224 << isDoubleSpeed(); + blitTime += lcd_cycles_per_frame << isDoubleSpeed(); blanklcd_ = lcden ^ 1; intreq_.setEventTime(blitTime); @@ -207,77 +226,23 @@ unsigned long Memory::event(unsigned long cc) { updateSerial(cc); break; case intevent_oam: - intreq_.setEventTime(lastOamDmaUpdate_ == disabled_time - ? static_cast(disabled_time) - : intreq_.eventTime(intevent_oam) + 0xA0 * 4); + if (lastOamDmaUpdate_ != disabled_time) { + unsigned const oamEventPos = oamDmaPos_ < oam_size ? oam_size : oamDmaStartPos_; + intreq_.setEventTime( + lastOamDmaUpdate_ + ((oamEventPos - oamDmaPos_) & 0xFF) * 4); + } + else + intreq_.setEventTime(disabled_time); + break; case intevent_dma: - { - bool const doubleSpeed = isDoubleSpeed(); - unsigned dmaSrc = dmaSource_; - unsigned dmaDest = dmaDestination_; - unsigned dmaLength = ((ioamhram_[0x155] & 0x7F) + 0x1) * 0x10; - unsigned length = hdmaReqFlagged(intreq_) ? 0x10 : dmaLength; - - ackDmaReq(intreq_); - - if ((static_cast(dmaDest) + length) & 0x10000) { - length = 0x10000 - dmaDest; - ioamhram_[0x155] |= 0x80; - } - - dmaLength -= length; - - if (!(ioamhram_[0x140] & lcdc_en)) - dmaLength = 0; - - { - unsigned long lOamDmaUpdate = lastOamDmaUpdate_; - lastOamDmaUpdate_ = disabled_time; - - while (length--) { - unsigned const src = dmaSrc++ & 0xFFFF; - unsigned const data = (src & 0xE000) == 0x8000 || src > 0xFDFF - ? 0xFF - : read(src, cc); - - cc += 2 << doubleSpeed; - - if (cc - 3 > lOamDmaUpdate) { - oamDmaPos_ = (oamDmaPos_ + 1) & 0xFF; - lOamDmaUpdate += 4; - - if (oamDmaPos_ < 0xA0) { - if (oamDmaPos_ == 0) - startOamDma(lOamDmaUpdate - 1); - - ioamhram_[src & 0xFF] = data; - } else if (oamDmaPos_ == 0xA0) { - endOamDma(lOamDmaUpdate - 1); - lOamDmaUpdate = disabled_time; - } - } - - nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cc); - } - - lastOamDmaUpdate_ = lOamDmaUpdate; - } - - cc += 4; - - dmaSource_ = dmaSrc; - dmaDestination_ = dmaDest; - ioamhram_[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram_[0x155] & 0x80); - - if ((ioamhram_[0x155] & 0x80) && lcd_.hdmaIsEnabled()) { - if (lastOamDmaUpdate_ != disabled_time) - updateOamDma(cc); - - lcd_.disableHdma(cc); - } + interrupter_.prefetch(cc, *this); + cc = dma(cc); + if (haltHdmaState_ == hdma_requested) { + haltHdmaState_ = hdma_low; + intreq_.setMinIntTime(cc); + cc -= 4; } - break; case intevent_tima: tima_.doIrqEvent(TimaInterruptRequester(intreq_)); @@ -292,45 +257,21 @@ unsigned long Memory::event(unsigned long cc) { } if (halted()) { - if (gbIsCgb_ || (!gbIsCgb_ && cc <= halttime_ + 4)) - cc += 4; + cc += 4 * (isCgb() || cc - intreq_.eventTime(intevent_interrupts) < 2); + if (cc > lastOamDmaUpdate_) + updateOamDma(cc); + if ((lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc) && haltHdmaState_ == hdma_low) + || haltHdmaState_ == hdma_requested) { + flagHdmaReq(intreq_); + } intreq_.unhalt(); intreq_.setEventTime(disabled_time); } if (ime()) { - cc += 12; - - sp_ = (sp_ - 1) & 0xFFFF; - write(sp_, pc_ >> 8, cc); - - cc += 4; - - updateIrqs(cc); - unsigned const pendingIrqs = intreq_.pendingIrqs(); - - sp_ = (sp_ - 1) & 0xFFFF; - write(sp_, pc_ & 0xFF, cc); - - cc += 2; - - unsigned const n = pendingIrqs & -pendingIrqs; - unsigned address; - if (n == 0) { - address = 0; - } else if (n <= 4) { - static unsigned char const lut[] = { 0x40, 0x48, 0x48, 0x50 }; - address = lut[n-1]; - } else - address = 0x50 + n; - - updateIrqs(cc); - intreq_.ackIrq(n); - - cc += 2; - - pc_ = address; + di(); + cc = interrupter_.interrupt(cc, *this); } break; @@ -339,38 +280,175 @@ unsigned long Memory::event(unsigned long cc) { return cc; } -unsigned long Memory::stop(unsigned long cc) { +unsigned long Memory::dma(unsigned long cc) { + bool const doubleSpeed = isDoubleSpeed(); + unsigned dmaSrc = dmaSource_; + unsigned dmaDest = dmaDestination_; + unsigned dmaLength = ((ioamhram_[0x155] & 0x7F) + 1) * 0x10; + unsigned length = hdmaReqFlagged(intreq_) ? 0x10 : dmaLength; + + if (1ul * dmaDest + length >= 0x10000) { + length = 0x10000 - dmaDest; + ioamhram_[0x155] |= 0x80; + } + + dmaLength -= length; + + if (!(ioamhram_[0x140] & lcdc_en)) + dmaLength = 0; + + unsigned long lOamDmaUpdate = lastOamDmaUpdate_; + lastOamDmaUpdate_ = disabled_time; + + while (length--) { + unsigned const src = dmaSrc++ & 0xFFFF; + unsigned const data = (src & -vrambank_size()) == mm_vram_begin || src >= mm_oam_begin + ? 0xFF + : read(src, cc); + + cc += 2 + 2 * doubleSpeed; + + if (cc - 3 > lOamDmaUpdate && !halted()) { + oamDmaPos_ = (oamDmaPos_ + 1) & 0xFF; + lOamDmaUpdate += 4; + if (oamDmaPos_ == oamDmaStartPos_) + startOamDma(lOamDmaUpdate); + + if (oamDmaPos_ < oam_size) { + ioamhram_[src & 0xFF] = data; + } + else if (oamDmaPos_ == oam_size) { + endOamDma(lOamDmaUpdate); + if (oamDmaStartPos_ == 0) + lOamDmaUpdate = disabled_time; + } + } + + nontrivial_write(mm_vram_begin | dmaDest++ % vrambank_size(), data, cc); + } + + lastOamDmaUpdate_ = lOamDmaUpdate; + ackDmaReq(intreq_); cc += 4; - if (ioamhram_[0x14D] & isCgb()) { - psg_.generateSamples(cc + 4, isDoubleSpeed()); - lcd_.speedChange((cc + 7) & ~7); - cart_.speedChange(cc); - ioamhram_[0x14D] ^= 0x81; - intreq_.setEventTime(ioamhram_[0x140] & lcdc_en - ? lcd_.nextMode1IrqTime() - : cc + (70224 << isDoubleSpeed())); + dmaSource_ = dmaSrc; + dmaDestination_ = dmaDest; + ioamhram_[0x155] = halted() + ? ioamhram_[0x155] | 0x80 + : ((dmaLength / 0x10 - 1) & 0xFF) | (ioamhram_[0x155] & 0x80); - if (intreq_.eventTime(intevent_end) > cc) { - intreq_.setEventTime(cc - + ( isDoubleSpeed() - ? (intreq_.eventTime(intevent_end) - cc) << 1 - : (intreq_.eventTime(intevent_end) - cc) >> 1)); - } - intreq_.halt(); - intreq_.setEventTime(cc + 0x20000); - } - else { - stopped_ = true; - intreq_.halt(); + if ((ioamhram_[0x155] & 0x80) && lcd_.hdmaIsEnabled()) { + if (lastOamDmaUpdate_ != disabled_time) + updateOamDma(cc); + + lcd_.disableHdma(cc); } return cc; } -static void decCycles(unsigned long &counter, unsigned long dec) { - if (counter != disabled_time) - counter -= dec; +void Memory::freeze(unsigned long cc) { + // permanently halt CPU. + // simply halt and clear IE to avoid unhalt from occuring, + // which avoids additional state to represent a "frozen" state. + nontrivial_ff_write(0xFF, 0, cc); + ackDmaReq(intreq_); + intreq_.halt(); +} + +bool Memory::halt(unsigned long cc) { + if (lastOamDmaUpdate_ != disabled_time) + updateOamDma(cc); + + haltHdmaState_ = lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc) + ? hdma_high : hdma_low; + bool const hdmaReq = hdmaReqFlagged(intreq_); + if (hdmaReq) + haltHdmaState_ = hdma_requested; + if (lastOamDmaUpdate_ != disabled_time) + updateOamDma(cc + 4); + + ackDmaReq(intreq_); + intreq_.halt(); + return hdmaReq; +} + +unsigned Memory::pendingIrqs(unsigned long cc) { + if (lastOamDmaUpdate_ != disabled_time) + updateOamDma(cc); + + updateIrqs(cc); + return intreq_.pendingIrqs(); +} + +void Memory::ackIrq(unsigned bit, unsigned long cc) { + if (lastOamDmaUpdate_ != disabled_time) + updateOamDma(cc); + + // TODO: adjust/extend IRQ assertion time rather than use the odd cc offsets? + // NOTE: a minimum offset of 2 is required for the LCD due to implementation assumptions w.r.t. cc headroom. + updateSerial(cc + 3 + isCgb()); + updateTimaIrq(cc + 2 + isCgb()); + lcd_.update(cc + 2); + intreq_.ackIrq(bit); +} + +unsigned long Memory::stop(unsigned long cc, bool &skip) { + // FIXME: this is incomplete. + intreq_.setEventTime(cc + 0x20000 + 4); + + // speed change. + if (ioamhram_[0x14D] & isCgb()) { + tima_.speedChange(TimaInterruptRequester(intreq_)); + // DIV reset. + nontrivial_ff_write(0x04, 0, cc); + haltHdmaState_ = lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc) + ? hdma_high : hdma_low; + skip = hdmaReqFlagged(intreq_); + if (skip && isDoubleSpeed()) + haltHdmaState_ = hdma_requested; + unsigned long const cc_ = cc + 8 * !isDoubleSpeed(); + if (cc_ >= cc + 4) { + if (lastOamDmaUpdate_ != disabled_time) + updateOamDma(cc + 4); + if (!skip || isDoubleSpeed()) + ackDmaReq(intreq_); + intreq_.halt(); + } + psg_.speedChange(cc_, isDoubleSpeed()); + lcd_.speedChange(cc_); + cart_.speedChange(cc_); + ioamhram_[0x14D] ^= 0x81; + // TODO: perhaps make this a bit nicer? + intreq_.setEventTime(ioamhram_[0x140] & lcdc_en + ? lcd_.nextMode1IrqTime() + : cc + (lcd_cycles_per_frame << isDoubleSpeed())); + if (intreq_.eventTime(intevent_end) > cc_) { + intreq_.setEventTime(cc_ + + (isDoubleSpeed() + ? (intreq_.eventTime(intevent_end) - cc_) * 2 + : (intreq_.eventTime(intevent_end) - cc_) / 2)); + } + if (cc_ < cc + 4) { + if (lastOamDmaUpdate_ != disabled_time) + updateOamDma(cc + 4); + if (!skip || !isDoubleSpeed()) + ackDmaReq(intreq_); + intreq_.halt(); + } + // ensure that no updates with a previous cc occur. + cc += 8; + } + else { + // FIXME: test and implement stop correctly. + skip = halt(cc); + cc += 4; + + stopped_ = true; + intreq_.setEventTime(disabled_time); + } + + return cc; } void Memory::decEventCycles(IntEventId eventId, unsigned long dec) { @@ -384,15 +462,9 @@ unsigned long Memory::resetCounters(unsigned long cc) { updateIrqs(cc); - { - unsigned long divinc = (cc - divLastUpdate_) >> 8; - ioamhram_[0x104] = (ioamhram_[0x104] + divinc) & 0xFF; - divLastUpdate_ += divinc << 8; - } - - unsigned long const dec = cc < 0x10000 - ? 0 - : (cc & ~0x7FFFul) - 0x8000; + unsigned long const dec = cc < 0x20000 + ? 0 + : (cc & -0x10000l) - 0x10000; decCycles(divLastUpdate_, dec); decCycles(lastOamDmaUpdate_, dec); decEventCycles(intevent_serial, dec); @@ -434,62 +506,68 @@ void Memory::updateOamDma(unsigned long const cc) { unsigned char const *const oamDmaSrc = oamDmaSrcPtr(); unsigned cycles = (cc - lastOamDmaUpdate_) >> 2; - while (cycles--) { + if (halted()) { + lastOamDmaUpdate_ += 4 * cycles; + } + else while (cycles--) { oamDmaPos_ = (oamDmaPos_ + 1) & 0xFF; lastOamDmaUpdate_ += 4; + if (oamDmaPos_ == oamDmaStartPos_) + startOamDma(lastOamDmaUpdate_); - if (oamDmaPos_ < 0xA0) { - if (oamDmaPos_ == 0) - startOamDma(lastOamDmaUpdate_ - 1); - - ioamhram_[oamDmaPos_] = oamDmaSrc ? oamDmaSrc[oamDmaPos_] : cart_.rtcRead(); - } else if (oamDmaPos_ == 0xA0) { - endOamDma(lastOamDmaUpdate_ - 1); - lastOamDmaUpdate_ = disabled_time; - break; + if (oamDmaPos_ < oam_size) { + ioamhram_[oamDmaPos_] = ((oamDmaSrc) ? oamDmaSrc[oamDmaPos_] : cart_.rtcRead()); + } + else if (oamDmaPos_ == oam_size) { + endOamDma(lastOamDmaUpdate_); + if (oamDmaStartPos_ == 0) { + lastOamDmaUpdate_ = disabled_time; + break; + } } } } void Memory::oamDmaInitSetup() { - if (ioamhram_[0x146] < 0xA0) { - cart_.setOamDmaSrc(ioamhram_[0x146] < 0x80 ? oam_dma_src_rom : oam_dma_src_vram); - } else if (ioamhram_[0x146] < 0xFE - isCgb() * 0x1E) { - cart_.setOamDmaSrc(ioamhram_[0x146] < 0xC0 ? oam_dma_src_sram : oam_dma_src_wram); - } else + if (ioamhram_[0x146] < mm_sram_begin / 0x100) { + cart_.setOamDmaSrc(ioamhram_[0x146] < mm_vram_begin / 0x100 ? oam_dma_src_rom : oam_dma_src_vram); + } + else if (ioamhram_[0x146] < 0x100 - isCgb() * 0x20) { + cart_.setOamDmaSrc(ioamhram_[0x146] < mm_wram_begin / 0x100 ? oam_dma_src_sram : oam_dma_src_wram); + } + else cart_.setOamDmaSrc(oam_dma_src_invalid); } -static unsigned char const * oamDmaSrcZero() { - static unsigned char zeroMem[0xA0]; - return zeroMem; -} - -unsigned char const * Memory::oamDmaSrcPtr() const { +unsigned char const* Memory::oamDmaSrcPtr() const { switch (cart_.oamDmaSrc()) { case oam_dma_src_rom: - return cart_.romdata(ioamhram_[0x146] >> 6) + (ioamhram_[0x146] << 8); + return cart_.romdata(ioamhram_[0x146] >> 6) + ioamhram_[0x146] * 0x100l; case oam_dma_src_sram: - return cart_.rsrambankptr() ? cart_.rsrambankptr() + (ioamhram_[0x146] << 8) : 0; + return cart_.rsrambankptr() ? cart_.rsrambankptr() + ioamhram_[0x146] * 0x100l : 0; case oam_dma_src_vram: - return cart_.vrambankptr() + (ioamhram_[0x146] << 8); + return cart_.vrambankptr() + ioamhram_[0x146] * 0x100l; case oam_dma_src_wram: - return cart_.wramdata(ioamhram_[0x146] >> 4 & 1) + (ioamhram_[0x146] << 8 & 0xFFF); + return cart_.wramdata(ioamhram_[0x146] >> 4 & 1) + (ioamhram_[0x146] * 0x100l & 0xFFF); case oam_dma_src_invalid: case oam_dma_src_off: break; } - return ioamhram_[0x146] == 0xFF && !isCgb() ? oamDmaSrcZero() : cart_.rdisabledRam(); + return cart_.rdisabledRam(); } void Memory::startOamDma(unsigned long cc) { + oamDmaPos_ = 0; + oamDmaStartPos_ = 0; lcd_.oamChange(cart_.rdisabledRam(), cc); } void Memory::endOamDma(unsigned long cc) { - oamDmaPos_ = 0xFE; - cart_.setOamDmaSrc(oam_dma_src_off); + if (oamDmaStartPos_ == 0) { + oamDmaPos_ = -2u & 0xFF; + cart_.setOamDmaSrc(oam_dma_src_off); + } lcd_.oamChange(ioamhram_, cc); } @@ -506,13 +584,7 @@ unsigned Memory::nontrivial_ff_read(unsigned const p, unsigned long const cc) { updateSerial(cc); break; case 0x04: - { - unsigned long divcycles = (cc - divLastUpdate_) >> 8; - ioamhram_[0x104] = (ioamhram_[0x104] + divcycles) & 0xFF; - divLastUpdate_ += divcycles << 8; - } - - break; + return (cc - tima_.divLastUpdate()) >> 8 & 0xFF; case 0x05: ioamhram_[0x105] = tima_.tima(cc); break; @@ -561,47 +633,28 @@ unsigned Memory::nontrivial_ff_read(unsigned const p, unsigned long const cc) { return ioamhram_[p + 0x100]; } -static bool isInOamDmaConflictArea(OamDmaSrc const oamDmaSrc, unsigned const p, bool const cgb) { - struct Area { unsigned short areaUpper, exceptAreaLower, exceptAreaWidth, pad; }; - - static Area const cgbAreas[] = { - { 0xC000, 0x8000, 0x2000, 0 }, - { 0xC000, 0x8000, 0x2000, 0 }, - { 0xA000, 0x0000, 0x8000, 0 }, - { 0xFE00, 0x0000, 0xC000, 0 }, - { 0xC000, 0x8000, 0x2000, 0 }, - { 0x0000, 0x0000, 0x0000, 0 } - }; - - static Area const dmgAreas[] = { - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xA000, 0x0000, 0x8000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0x0000, 0x0000, 0x0000, 0 } - }; - - Area const *a = cgb ? cgbAreas : dmgAreas; - return p < a[oamDmaSrc].areaUpper - && p - a[oamDmaSrc].exceptAreaLower >= a[oamDmaSrc].exceptAreaWidth; -} - unsigned Memory::nontrivial_read(unsigned const p, unsigned long const cc) { - if (p < 0xFF80) { + if (p < mm_hram_begin) { if (lastOamDmaUpdate_ != disabled_time) { updateOamDma(cc); - if (isInOamDmaConflictArea(cart_.oamDmaSrc(), p, isCgb()) && oamDmaPos_ < 0xA0) - return ioamhram_[oamDmaPos_]; + if (cart_.isInOamDmaConflictArea(p) && oamDmaPos_ < oam_size) { + int const r = isCgb() && cart_.oamDmaSrc() != oam_dma_src_wram && p >= mm_wram_begin + ? cart_.wramdata(ioamhram_[0x146] >> 4 & 1)[p & 0xFFF] + : ioamhram_[oamDmaPos_]; + if (isCgb() && cart_.oamDmaSrc() == oam_dma_src_vram) + ioamhram_[oamDmaPos_] = 0; + + return r; + } } - if (p < 0xC000) { - if (p < 0x8000) + if (p < mm_wram_begin) { + if (p < mm_vram_begin) return cart_.romdata(p >> 14)[p]; - if (p < 0xA000) { - if (!lcd_.vramAccessible(cc)) + if (p < mm_sram_begin) { + if (!lcd_.vramReadable(cc)) return 0xFF; return cart_.vrambankptr()[p]; @@ -613,18 +666,18 @@ unsigned Memory::nontrivial_read(unsigned const p, unsigned long const cc) { return cart_.rtcRead(); } - if (p < 0xFE00) + if (p < mm_oam_begin) return cart_.wramdata(p >> 12 & 1)[p & 0xFFF]; - long const ffp = long(p) - 0xFF00; + long const ffp = static_cast(p) - mm_io_begin; if (ffp >= 0) return nontrivial_ff_read(ffp, cc); - if (!lcd_.oamReadable(cc) || oamDmaPos_ < 0xA0) + if (!lcd_.oamReadable(cc) || oamDmaPos_ < oam_size) return 0xFF; } - return ioamhram_[p - 0xFE00]; + return ioamhram_[p - mm_oam_begin]; } unsigned Memory::nontrivial_peek(unsigned const p) { @@ -674,17 +727,25 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long if ((data & 0x81) == 0x81) { intreq_.setEventTime(data & isCgb() * 2 - ? (cc & ~0x07ul) + 0x010 * 8 - : (cc & ~0xFFul) + 0x200 * 8); + ? cc - (cc - tima_.divLastUpdate()) % 8 + 0x10 * serialCnt_ + : cc - (cc - tima_.divLastUpdate()) % 0x100 + 0x200 * serialCnt_); } else intreq_.setEventTime(disabled_time); data |= 0x7E - isCgb() * 2; break; case 0x04: - ioamhram_[0x104] = 0; - divLastUpdate_ = cc; - tima_.resTac(cc, TimaInterruptRequester(intreq_)); + if (intreq_.eventTime(intevent_serial) != disabled_time + && intreq_.eventTime(intevent_serial) > cc) { + unsigned long const t = intreq_.eventTime(intevent_serial); + unsigned long const n = ioamhram_[0x102] & isCgb() * 2 + ? t + (cc - t) % 8 - 2 * ((cc - t) & 4) + : t + (cc - t) % 0x100 - 2 * ((cc - t) & 0x80); + intreq_.setEventTime(std::max(cc, n)); + } + psg_.generateSamples(cc, isDoubleSpeed()); + psg_.divReset(isDoubleSpeed()); + tima_.divReset(cc, TimaInterruptRequester(intreq_)); return; case 0x05: tima_.setTima(data, cc, TimaInterruptRequester(intreq_)); @@ -697,7 +758,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long tima_.setTac(data, cc, TimaInterruptRequester(intreq_), agbMode_); break; case 0x0F: - updateIrqs(cc); + updateIrqs(cc + 1 + isDoubleSpeed()); intreq_.setIfreg(0xE0 | data); return; case 0x10: @@ -739,7 +800,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long return; psg_.generateSamples(cc, isDoubleSpeed()); - psg_.setNr14(data); + psg_.setNr14(data, isDoubleSpeed()); data |= 0xBF; break; case 0x16: @@ -773,7 +834,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long return; psg_.generateSamples(cc, isDoubleSpeed()); - psg_.setNr24(data); + psg_.setNr24(data, isDoubleSpeed()); data |= 0xBF; break; case 0x1A: @@ -866,8 +927,9 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long ff_write(i, 0, cc); psg_.setEnabled(false); - } else { - psg_.reset(); + } + else { + psg_.reset(isDoubleSpeed()); psg_.setEnabled(true); } } @@ -896,8 +958,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long case 0x40: if (ioamhram_[0x140] != data) { if ((ioamhram_[0x140] ^ data) & lcdc_en) { - unsigned const lyc = lcd_.getStat(ioamhram_[0x145], cc) - & lcdstat_lycflag; + unsigned const stat = data & lcdc_en ? ioamhram_[0x141] : lcd_.getStat(ioamhram_[0x145], cc); bool const hdmaEnabled = lcd_.hdmaIsEnabled(); lcd_.lcdcChange(data, cc); @@ -905,19 +966,23 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long ioamhram_[0x141] &= 0xF8; if (data & lcdc_en) { + if (ioamhram_[0x141] & lcdstat_lycirqen && ioamhram_[0x145] == 0 && !(stat & lcdstat_lycflag)) + intreq_.flagIrq(2); + intreq_.setEventTime(blanklcd_ ? lcd_.nextMode1IrqTime() - : lcd_.nextMode1IrqTime() - + (70224 << isDoubleSpeed())); - } else { - ioamhram_[0x141] |= lyc; + : lcd_.nextMode1IrqTime() + (lcd_cycles_per_frame << isDoubleSpeed())); + } + else { + ioamhram_[0x141] |= stat & lcdstat_lycflag; intreq_.setEventTime( - cc + (456 * 4 << isDoubleSpeed())); + cc + (lcd_cycles_per_line * 4 << isDoubleSpeed())); if (hdmaEnabled) flagHdmaReq(intreq_); } - } else + } + else lcd_.lcdcChange(data, cc); ioamhram_[0x140] = data; @@ -926,6 +991,10 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long return; case 0x41: lcd_.lcdstatChange(data, cc); + if (!(ioamhram_[0x140] & lcdc_en) && (ioamhram_[0x141] & lcdstat_lycflag) + && (~ioamhram_[0x141] & lcdstat_lycirqen & (isCgb() ? data : -1))) { + intreq_.flagIrq(2); + } data = (ioamhram_[0x141] & 0x87) | (data & 0x78); break; case 0x42: @@ -938,11 +1007,9 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long lcd_.lycRegChange(data, cc); break; case 0x46: - if (lastOamDmaUpdate_ != disabled_time) - endOamDma(cc); - lastOamDmaUpdate_ = cc; - intreq_.setEventTime(cc + 8); + oamDmaStartPos_ = (oamDmaPos_ + 2) & 0xFF; + intreq_.setEventTime(std::min(intreq_.eventTime(intevent_oam), cc + 8)); ioamhram_[0x146] = data; oamDmaInitSetup(); return; @@ -1033,8 +1100,8 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long if (isCgb()) { unsigned index = ioamhram_[0x168] & 0x3F; lcd_.cgbBgColorChange(index, data, cc); - ioamhram_[0x168] = (ioamhram_[0x168] & ~0x3F) - | ((index + (ioamhram_[0x168] >> 7)) & 0x3F); + ioamhram_[0x168] = (ioamhram_[0x168] & ~0x3Fu) + | ((index + (ioamhram_[0x168] >> 7)) & 0x3F); } return; @@ -1047,8 +1114,8 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long if (isCgb()) { unsigned index = ioamhram_[0x16A] & 0x3F; lcd_.cgbSpColorChange(index, data, cc); - ioamhram_[0x16A] = (ioamhram_[0x16A] & ~0x3F) - | ((index + (ioamhram_[0x16A] >> 7)) & 0x3F); + ioamhram_[0x16A] = (ioamhram_[0x16A] & ~0x3Fu) + | ((index + (ioamhram_[0x16A] >> 7)) & 0x3F); } return; @@ -1092,38 +1159,56 @@ void Memory::nontrivial_write(unsigned const p, unsigned const data, unsigned lo if (lastOamDmaUpdate_ != disabled_time) { updateOamDma(cc); - if (isInOamDmaConflictArea(cart_.oamDmaSrc(), p, isCgb()) && oamDmaPos_ < 0xA0) { - ioamhram_[oamDmaPos_] = data; + if (cart_.isInOamDmaConflictArea(p) && oamDmaPos_ < oam_size) { + if (isCgb()) { + if (p < mm_wram_begin) + ioamhram_[oamDmaPos_] = cart_.oamDmaSrc() != oam_dma_src_vram ? data : 0; + else if (cart_.oamDmaSrc() != oam_dma_src_wram) + cart_.wramdata(ioamhram_[0x146] >> 4 & 1)[p & 0xFFF] = data; + } + else { + ioamhram_[oamDmaPos_] = cart_.oamDmaSrc() == oam_dma_src_wram + ? ioamhram_[oamDmaPos_] & data + : data; + } + return; } } - if (p < 0xFE00) { - if (p < 0xA000) { - if (p < 0x8000) { + if (p < mm_oam_begin) { + if (p < mm_sram_begin) { + if (p < mm_vram_begin) { cart_.mbcWrite(p, data, cc); - } else if (lcd_.vramAccessible(cc)) { + } + else if (lcd_.vramWritable(cc)) { lcd_.vramChange(cc); cart_.vrambankptr()[p] = data; } - } else if (p < 0xC000) { + } + else if (p < mm_wram_begin) { if (cart_.wsrambankptr()) cart_.wsrambankptr()[p] = data; else cart_.rtcWrite(data, cc); - } else + } + else cart_.wramdata(p >> 12 & 1)[p & 0xFFF] = data; - } else if (p - 0xFF80u >= 0x7Fu) { - long const ffp = long(p) - 0xFF00; + } + else if (p - mm_hram_begin >= 0x7Fu) { + long const ffp = static_cast(p) - mm_io_begin; if (ffp < 0) { - if (lcd_.oamWritable(cc) && oamDmaPos_ >= 0xA0 && (p < 0xFEA0 || isCgb())) { + if (lcd_.oamWritable(cc) && oamDmaPos_ >= oam_size + && (p < mm_oam_begin + oam_size || isCgb())) { lcd_.oamChange(cc); - ioamhram_[p - 0xFE00] = data; + ioamhram_[p - mm_oam_begin] = data; } - } else + } + else nontrivial_ff_write(ffp, data, cc); - } else - ioamhram_[p - 0xFE00] = data; + } + else + ioamhram_[p - mm_oam_begin] = data; } LoadRes Memory::loadROM(char const *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { @@ -1223,5 +1308,3 @@ SYNCFUNC(Memory) NSS(LINKCABLE_); NSS(linkClockTrigger_); } - -} diff --git a/libgambatte/src/memory.h b/libgambatte/src/memory.h index 7d7dcc1d73..babf029853 100644 --- a/libgambatte/src/memory.h +++ b/libgambatte/src/memory.h @@ -22,6 +22,7 @@ static unsigned char const agbOverride[0xD] = { 0xFF, 0x00, 0xCD, 0x03, 0x35, 0xAA, 0x31, 0x90, 0x94, 0x00, 0x00, 0x00, 0x00 }; #include "mem/cartridge.h" +#include "interrupter.h" #include "sound.h" #include "tima.h" #include "video.h" @@ -34,7 +35,7 @@ class FilterInfo; class Memory { public: - explicit Memory(unsigned short &sp, unsigned short &pc); + explicit Memory(Interrupter const &interrupter); ~Memory(); bool loaded() const { return cart_.loaded(); } @@ -58,7 +59,7 @@ public: bool getMemoryArea(int which, unsigned char **data, int *length); - unsigned long stop(unsigned long cycleCounter); + unsigned long stop(unsigned long cycleCounter, bool& skip); bool isCgb() const { return lcd_.isCgb(); } bool ime() const { return intreq_.ime(); } bool halted() const { return intreq_.halted(); } @@ -73,10 +74,14 @@ public: return (cc - intreq_.eventTime(intevent_blit)) >> isDoubleSpeed(); } - void halt(unsigned long cycleCounter) { halttime_ = cycleCounter; intreq_.halt(); } + void freeze(unsigned long cc); + bool halt(unsigned long cc); void ei(unsigned long cycleCounter) { if (!ime()) { intreq_.ei(cycleCounter); } } void di() { intreq_.di(); } + unsigned pendingIrqs(unsigned long cc); + void ackIrq(unsigned bit, unsigned long cc); + unsigned readBios(unsigned p) { if(agbMode_ && p >= 0xF3 && p < 0x100) { return (agbOverride[p-0xF3] + bios_[p]) & 0xFF; @@ -262,6 +267,10 @@ public: lcd_.setDmgPaletteColor(palNum, colorNum, rgb32); } + void blackScreen() { + lcd_.blackScreen(); + } + void setCgbPalette(unsigned *lut); void setTimeMode(bool useCycles, unsigned long const cc) { cart_.setTimeMode(useCycles, cc); @@ -282,20 +291,20 @@ private: Tima tima_; LCD lcd_; PSG psg_; + Interrupter interrupter_; unsigned short dmaSource_; unsigned short dmaDestination_; unsigned char oamDmaPos_; + unsigned char oamDmaStartPos_; unsigned char serialCnt_; bool blanklcd_; bool biosMode_; bool cgbSwitching_; bool agbMode_; bool gbIsCgb_; - unsigned short &sp_; - unsigned short &pc_; unsigned long basetime_; - unsigned long halttime_; bool stopped_; + enum HdmaState { hdma_low, hdma_high, hdma_requested } haltHdmaState_; MemoryCallback readCallback_; MemoryCallback writeCallback_; @@ -311,6 +320,7 @@ private: void startOamDma(unsigned long cycleCounter); void endOamDma(unsigned long cycleCounter); unsigned char const * oamDmaSrcPtr() const; + unsigned long dma(unsigned long cc); unsigned nontrivial_ff_read(unsigned p, unsigned long cycleCounter); unsigned nontrivial_read(unsigned p, unsigned long cycleCounter); void nontrivial_ff_write(unsigned p, unsigned data, unsigned long cycleCounter); diff --git a/libgambatte/src/savestate.h b/libgambatte/src/savestate.h index c130ab5ecb..5e5bf02349 100644 --- a/libgambatte/src/savestate.h +++ b/libgambatte/src/savestate.h @@ -54,6 +54,8 @@ struct SaveState { unsigned char f; unsigned char h; unsigned char l; + unsigned char opcode; + unsigned char /*bool*/ prefetched; unsigned char /*bool*/ skip; } cpu; @@ -63,19 +65,18 @@ struct SaveState { Ptr wram; Ptr ioamhram; unsigned long divLastUpdate; - unsigned long timaBasetime; unsigned long timaLastUpdate; unsigned long tmatime; unsigned long nextSerialtime; unsigned long lastOamDmaUpdate; unsigned long minIntTime; unsigned long unhaltTime; - unsigned long halttime; unsigned short rombank; unsigned short dmaSource; unsigned short dmaDestination; unsigned char rambank; unsigned char oamDmaPos; + unsigned char haltHdmaState; unsigned char /*bool*/ IME; unsigned char /*bool*/ halted; unsigned char /*bool*/ enableRam; @@ -147,7 +148,7 @@ struct SaveState { unsigned long counter; unsigned short shadow; unsigned char nr0; - unsigned char /*bool*/ negging; + unsigned char /*bool*/ neg; } sweep; Duty duty; Env env; @@ -188,6 +189,7 @@ struct SaveState { } ch4; unsigned long cycleCounter; + unsigned char lastUpdate; } spu; struct Time { diff --git a/libgambatte/src/sound.cpp b/libgambatte/src/sound.cpp index 69ebe39f18..db98678b2e 100644 --- a/libgambatte/src/sound.cpp +++ b/libgambatte/src/sound.cpp @@ -43,12 +43,13 @@ Clock) clock timer on transition to step. */ -namespace gambatte { +using namespace gambatte; PSG::PSG() : buffer_(0) , bufferPos_(0) , lastUpdate_(0) +, cycleCounter_(0) , soVol_(0) , rsum_(0x8000) // initialize to 0x8000 to prevent borrows from high word, xor away later , enabled_(false) @@ -60,11 +61,41 @@ void PSG::init(bool cgb) { ch3_.init(cgb); } -void PSG::reset() { +void PSG::reset(bool ds) { + int const divOffset = lastUpdate_ & ds; + unsigned long const cc = cycleCounter_ + divOffset; + // cycleCounter >> 12 & 7 represents the frame sequencer position. + cycleCounter_ = (cc & 0xFFF) + 2 * (~(cc + 1 + !ds) & 0x800); + lastUpdate_ = ((lastUpdate_ + 3) & -4) - !ds; ch1_.reset(); ch2_.reset(); ch3_.reset(); - ch4_.reset(); + ch4_.reset(cycleCounter_); +} + +void PSG::divReset(bool ds) { + int const divOffset = lastUpdate_ & ds; + unsigned long const cc = cycleCounter_ + divOffset; + cycleCounter_ = (cc & -0x1000) + 2 * (cc & 0x800) - divOffset; + ch1_.resetCc(cc - divOffset, cycleCounter_); + ch2_.resetCc(cc - divOffset, cycleCounter_); + ch3_.resetCc(cc - divOffset, cycleCounter_); + ch4_.resetCc(cc - divOffset, cycleCounter_); +} + +void PSG::speedChange(unsigned long const cpuCc, bool const ds) { + generateSamples(cpuCc, ds); + lastUpdate_ -= ds; + // correct for cycles since DIV reset (if any). + if (!ds) { + unsigned long const cc = cycleCounter_; + unsigned const divCycles = cc & 0xFFF; + cycleCounter_ = cc - divCycles / 2 - lastUpdate_ % 2; + ch1_.resetCc(cc, cycleCounter_); + ch2_.resetCc(cc, cycleCounter_); + ch3_.resetCc(cc, cycleCounter_); + ch4_.resetCc(cc, cycleCounter_); + } } void PSG::setStatePtrs(SaveState &state) { @@ -77,23 +108,26 @@ void PSG::loadState(SaveState const &state) { ch3_.loadState(state); ch4_.loadState(state); - lastUpdate_ = state.cpu.cycleCounter; + cycleCounter_ = state.spu.cycleCounter; + lastUpdate_ = state.cpu.cycleCounter - (1 - state.spu.lastUpdate) % 4u; setSoVolume(state.mem.ioamhram.get()[0x124]); mapSo(state.mem.ioamhram.get()[0x125]); enabled_ = state.mem.ioamhram.get()[0x126] >> 7 & 1; } -void PSG::accumulateChannels(unsigned long const cycles) { - uint_least32_t *const buf = buffer_ + bufferPos_; - std::memset(buf, 0, cycles * sizeof *buf); - ch1_.update(buf, soVol_, cycles); - ch2_.update(buf, soVol_, cycles); - ch3_.update(buf, soVol_, cycles); - ch4_.update(buf, soVol_, cycles); +inline void PSG::accumulateChannels(unsigned long const cycles) { + unsigned long const cc = cycleCounter_; + uint_least32_t* const buf = buffer_ + bufferPos_; + std::memset(buf, 0, cycles * sizeof * buf); + ch1_.update(buf, soVol_, cc, cc + cycles); + ch2_.update(buf, soVol_, cc, cc + cycles); + ch3_.update(buf, soVol_, cc, cc + cycles); + ch4_.update(buf, soVol_, cc, cc + cycles); + cycleCounter_ = (cc + cycles) % SoundUnit::counter_max; } -void PSG::generateSamples(unsigned long const cycleCounter, bool const doubleSpeed) { - unsigned long const cycles = (cycleCounter - lastUpdate_) >> (1 + doubleSpeed); +void PSG::generateSamples(unsigned long const cpuCc, bool const doubleSpeed) { + unsigned long const cycles = (cpuCc - lastUpdate_) >> (1 + doubleSpeed); lastUpdate_ += cycles << (1 + doubleSpeed); if (cycles) @@ -168,10 +202,10 @@ void PSG::setSoVolume(unsigned nr50) { void PSG::mapSo(unsigned nr51) { unsigned long so = nr51 * so1Mul() + (nr51 >> 4) * so2Mul(); - ch1_.setSo((so & 0x00010001) * 0xFFFF); - ch2_.setSo((so >> 1 & 0x00010001) * 0xFFFF); + ch1_.setSo((so & 0x00010001) * 0xFFFF, cycleCounter_); + ch2_.setSo((so >> 1 & 0x00010001) * 0xFFFF, cycleCounter_); ch3_.setSo((so >> 2 & 0x00010001) * 0xFFFF); - ch4_.setSo((so >> 3 & 0x00010001) * 0xFFFF); + ch4_.setSo((so >> 3 & 0x00010001) * 0xFFFF, cycleCounter_); } unsigned PSG::getStatus() const { @@ -193,5 +227,3 @@ SYNCFUNC(PSG) NSS(rsum_); NSS(enabled_); } - -} diff --git a/libgambatte/src/sound.h b/libgambatte/src/sound.h index b664d8c616..232d52a518 100644 --- a/libgambatte/src/sound.h +++ b/libgambatte/src/sound.h @@ -31,12 +31,14 @@ class PSG { public: PSG(); void init(bool cgb); - void reset(); + void reset(bool ds); + void divReset(bool ds); void setStatePtrs(SaveState &state); void loadState(SaveState const &state); void generateSamples(unsigned long cycleCounter, bool doubleSpeed); void resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed); + void speedChange(unsigned long cc, bool doubleSpeed); std::size_t fillBuffer(); void setBuffer(uint_least32_t *buf) { buffer_ = buf; bufferPos_ = 0; } @@ -44,28 +46,28 @@ public: void setEnabled(bool value) { enabled_ = value; } void setNr10(unsigned data) { ch1_.setNr0(data); } - void setNr11(unsigned data) { ch1_.setNr1(data); } - void setNr12(unsigned data) { ch1_.setNr2(data); } - void setNr13(unsigned data) { ch1_.setNr3(data); } - void setNr14(unsigned data) { ch1_.setNr4(data); } + void setNr11(unsigned data) { ch1_.setNr1(data, cycleCounter_); } + void setNr12(unsigned data) { ch1_.setNr2(data, cycleCounter_); } + void setNr13(unsigned data) { ch1_.setNr3(data, cycleCounter_); } + void setNr14(unsigned data, bool ds) { ch1_.setNr4(data, cycleCounter_, !(lastUpdate_ & ds)); } - void setNr21(unsigned data) { ch2_.setNr1(data); } - void setNr22(unsigned data) { ch2_.setNr2(data); } - void setNr23(unsigned data) { ch2_.setNr3(data); } - void setNr24(unsigned data) { ch2_.setNr4(data); } + void setNr21(unsigned data) { ch2_.setNr1(data, cycleCounter_); } + void setNr22(unsigned data) { ch2_.setNr2(data, cycleCounter_); } + void setNr23(unsigned data) { ch2_.setNr3(data, cycleCounter_); } + void setNr24(unsigned data, bool ds) { ch2_.setNr4(data, cycleCounter_, !(lastUpdate_ & ds)); } void setNr30(unsigned data) { ch3_.setNr0(data); } - void setNr31(unsigned data) { ch3_.setNr1(data); } + void setNr31(unsigned data) { ch3_.setNr1(data, cycleCounter_); } void setNr32(unsigned data) { ch3_.setNr2(data); } void setNr33(unsigned data) { ch3_.setNr3(data); } - void setNr34(unsigned data) { ch3_.setNr4(data); } - unsigned waveRamRead(unsigned index) const { return ch3_.waveRamRead(index); } - void waveRamWrite(unsigned index, unsigned data) { ch3_.waveRamWrite(index, data); } + void setNr34(unsigned data) { ch3_.setNr4(data, cycleCounter_); } + unsigned waveRamRead(unsigned index) const { return ch3_.waveRamRead(index, cycleCounter_); } + void waveRamWrite(unsigned index, unsigned data) { ch3_.waveRamWrite(index, data, cycleCounter_); } - void setNr41(unsigned data) { ch4_.setNr1(data); } - void setNr42(unsigned data) { ch4_.setNr2(data); } - void setNr43(unsigned data) { ch4_.setNr3(data); } - void setNr44(unsigned data) { ch4_.setNr4(data); } + void setNr41(unsigned data) { ch4_.setNr1(data, cycleCounter_); } + void setNr42(unsigned data) { ch4_.setNr2(data, cycleCounter_); } + void setNr43(unsigned data) { ch4_.setNr3(data, cycleCounter_); } + void setNr44(unsigned data) { ch4_.setNr4(data, cycleCounter_); } void setSoVolume(unsigned nr50); void mapSo(unsigned nr51); @@ -79,6 +81,7 @@ private: uint_least32_t *buffer_; std::size_t bufferPos_; unsigned long lastUpdate_; + unsigned long cycleCounter_; unsigned long soVol_; uint_least32_t rsum_; bool enabled_; diff --git a/libgambatte/src/sound/channel1.cpp b/libgambatte/src/sound/channel1.cpp index 9e94ab2a20..58d90edbf1 100644 --- a/libgambatte/src/sound/channel1.cpp +++ b/libgambatte/src/sound/channel1.cpp @@ -17,30 +17,28 @@ // #include "channel1.h" +#include "psgdef.h" #include "../savestate.h" #include -namespace gambatte { +using namespace gambatte; Channel1::SweepUnit::SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit) : disableMaster_(disabler) , dutyUnit_(dutyUnit) , shadow_(0) , nr0_(0) -, negging_(false) +, neg_(false) , cgb_(false) { } unsigned Channel1::SweepUnit::calcFreq() { - unsigned freq = shadow_ >> (nr0_ & 0x07); - - if (nr0_ & 0x08) { - freq = shadow_ - freq; - negging_ = true; - } else - freq = shadow_ + freq; - + unsigned const freq = nr0_ & psg_nr10_neg + ? shadow_ - (shadow_ >> (nr0_ & psg_nr10_rsh)) + : shadow_ + (shadow_ >> (nr0_ & psg_nr10_rsh)); + if (nr0_ & psg_nr10_neg) + neg_ = true; if (freq & 2048) disableMaster_(); @@ -48,12 +46,12 @@ unsigned Channel1::SweepUnit::calcFreq() { } void Channel1::SweepUnit::event() { - unsigned long const period = nr0_ >> 4 & 0x07; + unsigned long const period = (nr0_ & psg_nr10_time) / (1u * psg_nr10_time & -psg_nr10_time); if (period) { unsigned const freq = calcFreq(); - if (!(freq & 2048) && (nr0_ & 0x07)) { + if (!(freq & 2048) && nr0_ & psg_nr10_rsh) { shadow_ = freq; dutyUnit_.setFreq(freq, counter_); calcFreq(); @@ -65,25 +63,25 @@ void Channel1::SweepUnit::event() { } void Channel1::SweepUnit::nr0Change(unsigned newNr0) { - if (negging_ && !(newNr0 & 0x08)) + if (neg_ && !(newNr0 & 0x08)) disableMaster_(); nr0_ = newNr0; } void Channel1::SweepUnit::nr4Init(unsigned long const cc) { - negging_ = false; + neg_ = false; shadow_ = dutyUnit_.freq(); - unsigned const period = nr0_ >> 4 & 0x07; - unsigned const shift = nr0_ & 0x07; + unsigned const period = (nr0_ & psg_nr10_time) / (1u * psg_nr10_time & -psg_nr10_time); + unsigned const rsh = nr0_ & psg_nr10_rsh; - if (period | shift) + if (period | rsh) counter_ = ((((cc + 2 + cgb_ * 2) >> 14) + (period ? period : 8)) << 14) + 2; else counter_ = counter_disabled; - if (shift) + if (rsh) calcFreq(); } @@ -95,7 +93,7 @@ void Channel1::SweepUnit::loadState(SaveState const &state) { counter_ = std::max(state.spu.ch1.sweep.counter, state.spu.cycleCounter); shadow_ = state.spu.ch1.sweep.shadow; nr0_ = state.spu.ch1.sweep.nr0; - negging_ = state.spu.ch1.sweep.negging; + neg_ = state.spu.ch1.sweep.neg; } template @@ -104,7 +102,7 @@ void Channel1::SweepUnit::SyncState(NewState *ns) NSS(counter_); NSS(shadow_); NSS(nr0_); - NSS(negging_); + NSS(neg_); NSS(cgb_); } @@ -115,7 +113,6 @@ Channel1::Channel1() , envelopeUnit_(staticOutputTest_) , sweepUnit_(disableMaster_, dutyUnit_) , nextEventUnit_(0) -, cycleCounter_(0) , soMask_(0) , prevOut_(0) , nr4_(0) @@ -137,52 +134,48 @@ void Channel1::setNr0(unsigned data) { setEvent(); } -void Channel1::setNr1(unsigned data) { - lengthCounter_.nr1Change(data, nr4_, cycleCounter_); - dutyUnit_.nr1Change(data, cycleCounter_); +void Channel1::setNr1(unsigned data, unsigned long cc) { + lengthCounter_.nr1Change(data, nr4_, cc); + dutyUnit_.nr1Change(data, cc); setEvent(); } -void Channel1::setNr2(unsigned data) { +void Channel1::setNr2(unsigned data, unsigned long cc) { if (envelopeUnit_.nr2Change(data)) disableMaster_(); else - staticOutputTest_(cycleCounter_); + staticOutputTest_(cc); setEvent(); } -void Channel1::setNr3(unsigned data) { - dutyUnit_.nr3Change(data, cycleCounter_); +void Channel1::setNr3(unsigned data, unsigned long cc) { + dutyUnit_.nr3Change(data, cc); setEvent(); } -void Channel1::setNr4(unsigned const data) { - lengthCounter_.nr4Change(nr4_, data, cycleCounter_); +void Channel1::setNr4(unsigned data, unsigned long cc, unsigned long ref) { + lengthCounter_.nr4Change(nr4_, data, cc); + dutyUnit_.nr4Change(data, cc, ref, master_); nr4_ = data; - dutyUnit_.nr4Change(data, cycleCounter_, master_); - if (data & 0x80) { // init-bit - nr4_ &= 0x7F; - master_ = !envelopeUnit_.nr4Init(cycleCounter_); - sweepUnit_.nr4Init(cycleCounter_); - staticOutputTest_(cycleCounter_); + if (nr4_ & psg_nr4_init) { + nr4_ -= psg_nr4_init; + master_ = !envelopeUnit_.nr4Init(cc); + sweepUnit_.nr4Init(cc); + staticOutputTest_(cc); } setEvent(); } -void Channel1::setSo(unsigned long soMask) { +void Channel1::setSo(unsigned long soMask, unsigned long cc) { soMask_ = soMask; - staticOutputTest_(cycleCounter_); + staticOutputTest_(cc); setEvent(); } void Channel1::reset() { - // cycleCounter >> 12 & 7 represents the frame sequencer position. - cycleCounter_ &= 0xFFF; - cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000; - dutyUnit_.reset(); envelopeUnit_.reset(); sweepUnit_.reset(); @@ -196,58 +189,51 @@ void Channel1::init(bool cgb) { void Channel1::loadState(SaveState const &state) { sweepUnit_.loadState(state); dutyUnit_.loadState(state.spu.ch1.duty, state.mem.ioamhram.get()[0x111], - state.spu.ch1.nr4, state.spu.cycleCounter); + state.spu.ch1.nr4, state.spu.cycleCounter); envelopeUnit_.loadState(state.spu.ch1.env, state.mem.ioamhram.get()[0x112], - state.spu.cycleCounter); + state.spu.cycleCounter); lengthCounter_.loadState(state.spu.ch1.lcounter, state.spu.cycleCounter); - cycleCounter_ = state.spu.cycleCounter; nr4_ = state.spu.ch1.nr4; master_ = state.spu.ch1.master; } -void Channel1::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) { +void Channel1::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) { unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0; - unsigned long const outLow = outBase * (0 - 15ul); - unsigned long const endCycles = cycleCounter_ + cycles; + unsigned long const outLow = outBase * -15; - for (;;) { + while (cc < end) { unsigned long const outHigh = master_ - ? outBase * (envelopeUnit_.getVolume() * 2 - 15ul) - : outLow; - unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles); + ? outBase * (envelopeUnit_.getVolume() * 2l - 15) + : outLow; + unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), end); unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow; while (dutyUnit_.counter() <= nextMajorEvent) { *buf = out - prevOut_; prevOut_ = out; - buf += dutyUnit_.counter() - cycleCounter_; - cycleCounter_ = dutyUnit_.counter(); - + buf += dutyUnit_.counter() - cc; + cc = dutyUnit_.counter(); dutyUnit_.event(); out = dutyUnit_.isHighState() ? outHigh : outLow; } - - if (cycleCounter_ < nextMajorEvent) { + if (cc < nextMajorEvent) { *buf = out - prevOut_; prevOut_ = out; - buf += nextMajorEvent - cycleCounter_; - cycleCounter_ = nextMajorEvent; + buf += nextMajorEvent - cc; + cc = nextMajorEvent; } - if (nextEventUnit_->counter() == nextMajorEvent) { nextEventUnit_->event(); setEvent(); - } else - break; + } } - if (cycleCounter_ >= SoundUnit::counter_max) { - dutyUnit_.resetCounters(cycleCounter_); - lengthCounter_.resetCounters(cycleCounter_); - envelopeUnit_.resetCounters(cycleCounter_); - sweepUnit_.resetCounters(cycleCounter_); - cycleCounter_ -= SoundUnit::counter_max; + if (cc >= SoundUnit::counter_max) { + dutyUnit_.resetCounters(cc); + lengthCounter_.resetCounters(cc); + envelopeUnit_.resetCounters(cc); + sweepUnit_.resetCounters(cc); } } @@ -265,12 +251,9 @@ SYNCFUNC(Channel1) EVS(nextEventUnit_, &lengthCounter_, 4); EES(nextEventUnit_, NULL); - NSS(cycleCounter_); NSS(soMask_); NSS(prevOut_); NSS(nr4_); NSS(master_); } - -} diff --git a/libgambatte/src/sound/channel1.h b/libgambatte/src/sound/channel1.h index 36c62e646b..99e03431f8 100644 --- a/libgambatte/src/sound/channel1.h +++ b/libgambatte/src/sound/channel1.h @@ -35,14 +35,15 @@ class Channel1 { public: Channel1(); void setNr0(unsigned data); - void setNr1(unsigned data); - void setNr2(unsigned data); - void setNr3(unsigned data); - void setNr4(unsigned data); - void setSo(unsigned long soMask); + void setNr1(unsigned data, unsigned long cc); + void setNr2(unsigned data, unsigned long cc); + void setNr3(unsigned data, unsigned long cc); + void setNr4(unsigned data, unsigned long cc, unsigned long ref); + void setSo(unsigned long soMask, unsigned long cc); bool isActive() const { return master_; } - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end); void reset(); + void resetCc(unsigned long cc, unsigned long ncc) { dutyUnit_.resetCc(cc, ncc); } void init(bool cgb); void loadState(SaveState const &state); @@ -54,6 +55,7 @@ private: void nr0Change(unsigned newNr0); void nr4Init(unsigned long cycleCounter); void reset(); + void resetCc(unsigned long cc, unsigned long ncc) { dutyUnit_.resetCc(cc, ncc); } void init(bool cgb) { cgb_ = cgb; } void loadState(SaveState const &state); @@ -62,7 +64,7 @@ private: DutyUnit &dutyUnit_; unsigned short shadow_; unsigned char nr0_; - bool negging_; + bool neg_; bool cgb_; unsigned calcFreq(); @@ -80,7 +82,6 @@ private: EnvelopeUnit envelopeUnit_; SweepUnit sweepUnit_; SoundUnit *nextEventUnit_; - unsigned long cycleCounter_; unsigned long soMask_; unsigned long prevOut_; unsigned char nr4_; diff --git a/libgambatte/src/sound/channel2.cpp b/libgambatte/src/sound/channel2.cpp index 6f32e4dd46..efbfa18670 100644 --- a/libgambatte/src/sound/channel2.cpp +++ b/libgambatte/src/sound/channel2.cpp @@ -17,17 +17,17 @@ // #include "channel2.h" +#include "psgdef.h" #include "../savestate.h" #include -namespace gambatte { +using namespace gambatte; Channel2::Channel2() : staticOutputTest_(*this, dutyUnit_) , disableMaster_(master_, dutyUnit_) , lengthCounter_(disableMaster_, 0x3F) , envelopeUnit_(staticOutputTest_) -, cycleCounter_(0) , soMask_(0) , prevOut_(0) , nr4_(0) @@ -42,51 +42,47 @@ void Channel2::setEvent() { nextEventUnit = &lengthCounter_; } -void Channel2::setNr1(unsigned data) { - lengthCounter_.nr1Change(data, nr4_, cycleCounter_); - dutyUnit_.nr1Change(data, cycleCounter_); +void Channel2::setNr1(unsigned data, unsigned long cc) { + lengthCounter_.nr1Change(data, nr4_, cc); + dutyUnit_.nr1Change(data, cc); setEvent(); } -void Channel2::setNr2(unsigned data) { +void Channel2::setNr2(unsigned data, unsigned long cc) { if (envelopeUnit_.nr2Change(data)) disableMaster_(); else - staticOutputTest_(cycleCounter_); + staticOutputTest_(cc); setEvent(); } -void Channel2::setNr3(unsigned data) { - dutyUnit_.nr3Change(data, cycleCounter_); +void Channel2::setNr3(unsigned data, unsigned long cc) { + dutyUnit_.nr3Change(data, cc); setEvent(); } -void Channel2::setNr4(unsigned const data) { - lengthCounter_.nr4Change(nr4_, data, cycleCounter_); +void Channel2::setNr4(unsigned data, unsigned long cc, unsigned long ref) { + lengthCounter_.nr4Change(nr4_, data, cc); nr4_ = data; - dutyUnit_.nr4Change(data, cycleCounter_, master_); - if (data & 0x80) { // init-bit - nr4_ &= 0x7F; - master_ = !envelopeUnit_.nr4Init(cycleCounter_); - staticOutputTest_(cycleCounter_); + if (nr4_ & psg_nr4_init) { + nr4_ -= psg_nr4_init; + master_ = !envelopeUnit_.nr4Init(cc); + staticOutputTest_(cc); } + dutyUnit_.nr4Change(data, cc, ref, master_); setEvent(); } -void Channel2::setSo(unsigned long soMask) { +void Channel2::setSo(unsigned long soMask, unsigned long cc) { soMask_ = soMask; - staticOutputTest_(cycleCounter_); + staticOutputTest_(cc); setEvent(); } void Channel2::reset() { - // cycleCounter >> 12 & 7 represents the frame sequencer position. - cycleCounter_ &= 0xFFF; - cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000; - dutyUnit_.reset(); envelopeUnit_.reset(); setEvent(); @@ -94,57 +90,50 @@ void Channel2::reset() { void Channel2::loadState(SaveState const &state) { dutyUnit_.loadState(state.spu.ch2.duty, state.mem.ioamhram.get()[0x116], - state.spu.ch2.nr4, state.spu.cycleCounter); + state.spu.ch2.nr4, state.spu.cycleCounter); envelopeUnit_.loadState(state.spu.ch2.env, state.mem.ioamhram.get()[0x117], - state.spu.cycleCounter); + state.spu.cycleCounter); lengthCounter_.loadState(state.spu.ch2.lcounter, state.spu.cycleCounter); - cycleCounter_ = state.spu.cycleCounter; nr4_ = state.spu.ch2.nr4; master_ = state.spu.ch2.master; } -void Channel2::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) { +void Channel2::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) { unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0; - unsigned long const outLow = outBase * (0 - 15ul); - unsigned long const endCycles = cycleCounter_ + cycles; + unsigned long const outLow = outBase * -15; - for (;;) { + while (cc < end) { unsigned long const outHigh = master_ - ? outBase * (envelopeUnit_.getVolume() * 2 - 15ul) - : outLow; - unsigned long const nextMajorEvent = std::min(nextEventUnit->counter(), endCycles); + ? outBase * (envelopeUnit_.getVolume() * 2l - 15) + : outLow; + unsigned long const nextMajorEvent = std::min(nextEventUnit->counter(), end); unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow; while (dutyUnit_.counter() <= nextMajorEvent) { *buf += out - prevOut_; prevOut_ = out; - buf += dutyUnit_.counter() - cycleCounter_; - cycleCounter_ = dutyUnit_.counter(); - + buf += dutyUnit_.counter() - cc; + cc = dutyUnit_.counter(); dutyUnit_.event(); out = dutyUnit_.isHighState() ? outHigh : outLow; } - - if (cycleCounter_ < nextMajorEvent) { + if (cc < nextMajorEvent) { *buf += out - prevOut_; prevOut_ = out; - buf += nextMajorEvent - cycleCounter_; - cycleCounter_ = nextMajorEvent; + buf += nextMajorEvent - cc; + cc = nextMajorEvent; } - if (nextEventUnit->counter() == nextMajorEvent) { nextEventUnit->event(); setEvent(); - } else - break; + } } - if (cycleCounter_ >= SoundUnit::counter_max) { - dutyUnit_.resetCounters(cycleCounter_); - lengthCounter_.resetCounters(cycleCounter_); - envelopeUnit_.resetCounters(cycleCounter_); - cycleCounter_ -= SoundUnit::counter_max; + if (cc >= SoundUnit::counter_max) { + dutyUnit_.resetCounters(cc); + lengthCounter_.resetCounters(cc); + envelopeUnit_.resetCounters(cc); } } @@ -160,7 +149,6 @@ SYNCFUNC(Channel2) EVS(nextEventUnit, &lengthCounter_, 3); EES(nextEventUnit, NULL); - NSS(cycleCounter_); NSS(soMask_); NSS(prevOut_); @@ -168,4 +156,3 @@ SYNCFUNC(Channel2) NSS(master_); } -} diff --git a/libgambatte/src/sound/channel2.h b/libgambatte/src/sound/channel2.h index 8934cc7991..978b9bc8ed 100644 --- a/libgambatte/src/sound/channel2.h +++ b/libgambatte/src/sound/channel2.h @@ -33,14 +33,15 @@ struct SaveState; class Channel2 { public: Channel2(); - void setNr1(unsigned data); - void setNr2(unsigned data); - void setNr3(unsigned data); - void setNr4(unsigned data); - void setSo(unsigned long soMask); + void setNr1(unsigned data, unsigned long cc); + void setNr2(unsigned data, unsigned long cc); + void setNr3(unsigned data, unsigned long cc); + void setNr4(unsigned data, unsigned long cc, unsigned long ref); + void setSo(unsigned long soMask, unsigned long cc); bool isActive() const { return master_; } - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end); void reset(); + void resetCc(unsigned long cc, unsigned long ncc) { dutyUnit_.resetCc(cc, ncc); } void loadState(SaveState const &state); private: @@ -52,7 +53,6 @@ private: DutyUnit dutyUnit_; EnvelopeUnit envelopeUnit_; SoundUnit *nextEventUnit; - unsigned long cycleCounter_; unsigned long soMask_; unsigned long prevOut_; unsigned char nr4_; diff --git a/libgambatte/src/sound/channel3.cpp b/libgambatte/src/sound/channel3.cpp index 223378b1ab..0f6cdb3eee 100644 --- a/libgambatte/src/sound/channel3.cpp +++ b/libgambatte/src/sound/channel3.cpp @@ -17,20 +17,22 @@ // #include "channel3.h" +#include "psgdef.h" #include "../savestate.h" #include #include -static inline unsigned toPeriod(unsigned nr3, unsigned nr4) { - return 0x800 - ((nr4 << 8 & 0x700) | nr3); -} +using namespace gambatte; -namespace gambatte { +namespace { + unsigned toPeriod(unsigned nr3, unsigned nr4) { + return 0x800 - ((nr4 << 8 & 0x700) | nr3); + } +} Channel3::Channel3() : disableMaster_(master_, waveCounter_) , lengthCounter_(disableMaster_, 0xFF) -, cycleCounter_(0) , soMask_(0) , prevOut_(0) , waveCounter_(SoundUnit::counter_disabled) @@ -47,25 +49,22 @@ Channel3::Channel3() } void Channel3::setNr0(unsigned data) { - nr0_ = data & 0x80; - - if (!(data & 0x80)) + nr0_ = data & psg_nr4_init; + if (!nr0_) disableMaster_(); } void Channel3::setNr2(unsigned data) { - rshift_ = (data >> 5 & 3U) - 1; - if (rshift_ > 3) - rshift_ = 4; + rshift_ = std::min((data >> 5 & 3) - 1, 4u); } -void Channel3::setNr4(unsigned const data) { - lengthCounter_.nr4Change(nr4_, data, cycleCounter_); - nr4_ = data & 0x7F; +void Channel3::setNr4(unsigned const data, unsigned long const cc) { + lengthCounter_.nr4Change(nr4_, data, cc); + nr4_ = data & ~(1u * psg_nr4_init); - if (data & nr0_/* & 0x80*/) { - if (!cgb_ && waveCounter_ == cycleCounter_ + 1) { - unsigned const pos = ((wavePos_ + 1) & 0x1F) >> 1; + if (data & nr0_) { + if (!cgb_ && waveCounter_ == cc + 1) { + int const pos = (wavePos_ + 1) / 2 % sizeof waveRam_; if (pos < 4) waveRam_[0] = waveRam_[pos]; @@ -75,7 +74,7 @@ void Channel3::setNr4(unsigned const data) { master_ = true; wavePos_ = 0; - lastReadTime_ = waveCounter_ = cycleCounter_ + toPeriod(nr3_, data) + 3; + lastReadTime_ = waveCounter_ = cc + toPeriod(nr3_, data) + 3; } } @@ -84,13 +83,15 @@ void Channel3::setSo(unsigned long soMask) { } void Channel3::reset() { - // cycleCounter >> 12 & 7 represents the frame sequencer position. - cycleCounter_ &= 0xFFF; - cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000; - sampleBuf_ = 0; } +void Channel3::resetCc(unsigned long cc, unsigned long newCc) { + lastReadTime_ -= cc - newCc; + if (waveCounter_ != SoundUnit::counter_disabled) + waveCounter_ -= cc - newCc; +} + void Channel3::init(bool cgb) { cgb_ = cgb; } @@ -102,16 +103,15 @@ void Channel3::setStatePtrs(SaveState &state) { void Channel3::loadState(SaveState const &state) { lengthCounter_.loadState(state.spu.ch3.lcounter, state.spu.cycleCounter); - cycleCounter_ = state.spu.cycleCounter; waveCounter_ = std::max(state.spu.ch3.waveCounter, state.spu.cycleCounter); lastReadTime_ = state.spu.ch3.lastReadTime; nr3_ = state.spu.ch3.nr3; nr4_ = state.spu.ch3.nr4; - wavePos_ = state.spu.ch3.wavePos & 0x1F; + wavePos_ = state.spu.ch3.wavePos % (2 * sizeof waveRam_); sampleBuf_ = state.spu.ch3.sampleBuf; master_ = state.spu.ch3.master; - nr0_ = state.mem.ioamhram.get()[0x11A] & 0x80; + nr0_ = state.mem.ioamhram.get()[0x11A] & psg_nr4_init; setNr2(state.mem.ioamhram.get()[0x11C]); } @@ -122,77 +122,80 @@ void Channel3::updateWaveCounter(unsigned long const cc) { lastReadTime_ = waveCounter_ + periods * period; waveCounter_ = lastReadTime_ + period; - - wavePos_ += periods + 1; - wavePos_ &= 0x1F; - - sampleBuf_ = waveRam_[wavePos_ >> 1]; + wavePos_ = (wavePos_ + periods + 1) % (2 * sizeof waveRam_); + sampleBuf_ = waveRam_[wavePos_ / 2]; } } -void Channel3::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) { - unsigned long const outBase = nr0_/* & 0x80*/ ? soBaseVol & soMask_ : 0; +void Channel3::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) { + unsigned long const outBase = nr0_ ? soBaseVol & soMask_ : 0; if (outBase && rshift_ != 4) { - unsigned long const endCycles = cycleCounter_ + cycles; - - for (;;) { + while (std::min(waveCounter_, lengthCounter_.counter()) <= end) { + unsigned pos = wavePos_; + unsigned const period = toPeriod(nr3_, nr4_), rsh = rshift_; unsigned long const nextMajorEvent = - std::min(lengthCounter_.counter(), endCycles); + std::min(lengthCounter_.counter(), end); + unsigned long cnt = waveCounter_, prevOut = prevOut_; unsigned long out = master_ - ? ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul - : 0 - 15ul; + ? ((pos % 2 ? sampleBuf_ & 0xF : sampleBuf_ >> 4) >> rsh) * 2l - 15 + : -15; out *= outBase; - - while (waveCounter_ <= nextMajorEvent) { - *buf += out - prevOut_; - prevOut_ = out; - buf += waveCounter_ - cycleCounter_; - cycleCounter_ = waveCounter_; - - lastReadTime_ = waveCounter_; - waveCounter_ += toPeriod(nr3_, nr4_); - ++wavePos_; - wavePos_ &= 0x1F; - sampleBuf_ = waveRam_[wavePos_ >> 1]; - out = ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul; + while (cnt <= nextMajorEvent) { + *buf += out - prevOut; + prevOut = out; + buf += cnt - cc; + cc = cnt; + cnt += period; + ++pos; + unsigned const s = waveRam_[pos / 2 % sizeof waveRam_]; + out = ((pos % 2 ? s & 0xF : s >> 4) >> rsh) * 2l - 15; out *= outBase; } - - if (cycleCounter_ < nextMajorEvent) { + if (cnt != waveCounter_) { + wavePos_ = pos % (2 * sizeof waveRam_); + sampleBuf_ = waveRam_[wavePos_ / 2]; + prevOut_ = prevOut; + waveCounter_ = cnt; + lastReadTime_ = cc; + } + if (cc < nextMajorEvent) { *buf += out - prevOut_; prevOut_ = out; - buf += nextMajorEvent - cycleCounter_; - cycleCounter_ = nextMajorEvent; + buf += nextMajorEvent - cc; + cc = nextMajorEvent; } - - if (lengthCounter_.counter() == nextMajorEvent) { + if (lengthCounter_.counter() == nextMajorEvent) lengthCounter_.event(); - } else - break; } - } else { - unsigned long const out = outBase * (0 - 15ul); + if (cc < end) { + unsigned long out = master_ + ? ((wavePos_ % 2 ? sampleBuf_ & 0xF : sampleBuf_ >> 4) >> rshift_) * 2l - 15 + : -15; + out *= outBase; + *buf += out - prevOut_; + prevOut_ = out; + cc = end; + } + } + else { + unsigned long const out = outBase * -15; *buf += out - prevOut_; prevOut_ = out; - cycleCounter_ += cycles; - - while (lengthCounter_.counter() <= cycleCounter_) { + cc = end; + while (lengthCounter_.counter() <= cc) { updateWaveCounter(lengthCounter_.counter()); lengthCounter_.event(); } - updateWaveCounter(cycleCounter_); + updateWaveCounter(cc); } - if (cycleCounter_ >= SoundUnit::counter_max) { - lengthCounter_.resetCounters(cycleCounter_); - + if (cc >= SoundUnit::counter_max) { + lengthCounter_.resetCounters(cc); + lastReadTime_ -= SoundUnit::counter_max; if (waveCounter_ != SoundUnit::counter_disabled) waveCounter_ -= SoundUnit::counter_max; - - lastReadTime_ -= SoundUnit::counter_max; - cycleCounter_ -= SoundUnit::counter_max; } } @@ -202,7 +205,6 @@ SYNCFUNC(Channel3) SSS(lengthCounter_); - NSS(cycleCounter_); NSS(soMask_); NSS(prevOut_); NSS(waveCounter_); @@ -218,5 +220,3 @@ SYNCFUNC(Channel3) NSS(master_); NSS(cgb_); } - -} diff --git a/libgambatte/src/sound/channel3.h b/libgambatte/src/sound/channel3.h index fe2e43fd1b..a64ce4f5c3 100644 --- a/libgambatte/src/sound/channel3.h +++ b/libgambatte/src/sound/channel3.h @@ -32,35 +32,37 @@ class Channel3 { public: Channel3(); bool isActive() const { return master_; } + bool isCgb() const { return cgb_; } void reset(); + void resetCc(unsigned long cc, unsigned long newCc); void init(bool cgb); void setStatePtrs(SaveState &state); - void loadState(const SaveState &state); + void loadState(SaveState const &state); void setNr0(unsigned data); - void setNr1(unsigned data) { lengthCounter_.nr1Change(data, nr4_, cycleCounter_); } + void setNr1(unsigned data, unsigned long cc) { lengthCounter_.nr1Change(data, nr4_, cc); } void setNr2(unsigned data); void setNr3(unsigned data) { nr3_ = data; } - void setNr4(unsigned data); + void setNr4(unsigned data, unsigned long cc); void setSo(unsigned long soMask); - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end); - unsigned waveRamRead(unsigned index) const { + unsigned waveRamRead(unsigned index, unsigned long cc) const { if (master_) { - if (!cgb_ && cycleCounter_ != lastReadTime_) + if (!cgb_ && cc != lastReadTime_) return 0xFF; - index = wavePos_ >> 1; + index = wavePos_ / 2; } return waveRam_[index]; } - void waveRamWrite(unsigned index, unsigned data) { + void waveRamWrite(unsigned index, unsigned data, unsigned long cc) { if (master_) { - if (!cgb_ && cycleCounter_ != lastReadTime_) + if (!cgb_ && cc != lastReadTime_) return; - index = wavePos_ >> 1; + index = wavePos_ / 2; } waveRam_[index] = data; @@ -83,7 +85,6 @@ private: unsigned char waveRam_[0x10]; Ch3MasterDisabler disableMaster_; LengthCounter lengthCounter_; - unsigned long cycleCounter_; unsigned long soMask_; unsigned long prevOut_; unsigned long waveCounter_; diff --git a/libgambatte/src/sound/channel4.cpp b/libgambatte/src/sound/channel4.cpp index 9528ceb943..2b3f99533c 100644 --- a/libgambatte/src/sound/channel4.cpp +++ b/libgambatte/src/sound/channel4.cpp @@ -17,23 +17,26 @@ // #include "channel4.h" +#include "psgdef.h" #include "../savestate.h" #include -static unsigned long toPeriod(unsigned const nr3) { - unsigned s = (nr3 >> 4) + 3; - unsigned r = nr3 & 7; +using namespace gambatte; - if (!r) { - r = 1; - --s; +namespace { + static unsigned long toPeriod(unsigned const nr3) { + unsigned s = nr3 / (1u * psg_nr43_s & -psg_nr43_s) + 3; + unsigned r = nr3 & psg_nr43_r; + + if (!r) { + r = 1; + --s; + } + + return r << s; } - - return r << s; } -namespace gambatte { - Channel4::Lfsr::Lfsr() : backupCounter_(counter_disabled) , reg_(0x7FFF) @@ -48,17 +51,18 @@ void Channel4::Lfsr::updateBackupCounter(unsigned long const cc) { unsigned long periods = (cc - backupCounter_) / period + 1; backupCounter_ += periods * period; - if (master_ && nr3_ < 0xE0) { - if (nr3_ & 8) { + if (master_ && nr3_ < 0xE * (1u * psg_nr43_s & -psg_nr43_s)) { + if (nr3_ & psg_nr43_7biten) { while (periods > 6) { unsigned const xored = (reg_ << 1 ^ reg_) & 0x7E; - reg_ = (reg_ >> 6 & ~0x7E) | xored | xored << 8; + reg_ = (reg_ >> 6 & ~0x7Eu) | xored | xored << 8; periods -= 6; } unsigned const xored = ((reg_ ^ reg_ >> 1) << (7 - periods)) & 0x7F; - reg_ = (reg_ >> periods & ~(0x80 - (0x80 >> periods))) | xored | xored << 8; - } else { + reg_ = (reg_ >> periods & ~(0x80u - (0x80 >> periods))) | xored | xored << 8; + } + else { while (periods > 15) { reg_ = reg_ ^ reg_ >> 1; periods -= 15; @@ -76,13 +80,13 @@ void Channel4::Lfsr::reviveCounter(unsigned long cc) { } inline void Channel4::Lfsr::event() { - if (nr3_ < 0xE0) { + if (nr3_ < 0xE * (1u * psg_nr43_s & -psg_nr43_s)) { unsigned const shifted = reg_ >> 1; unsigned const xored = (reg_ ^ shifted) & 1; reg_ = shifted | xored << 14; - if (nr3_ & 8) - reg_ = (reg_ & ~0x40) | xored << 6; + if (nr3_ & psg_nr43_7biten) + reg_ = (reg_ & ~0x40u) | xored << 6; } counter_ += toPeriod(nr3_); @@ -108,6 +112,13 @@ void Channel4::Lfsr::reset(unsigned long cc) { backupCounter_ = cc + toPeriod(nr3_); } +void Channel4::Lfsr::resetCc(unsigned long cc, unsigned long newCc) { + updateBackupCounter(cc); + backupCounter_ -= cc - newCc; + if (counter_ != counter_disabled) + counter_ -= cc - newCc; +} + void Channel4::Lfsr::resetCounters(unsigned long oldCc) { updateBackupCounter(oldCc); backupCounter_ -= counter_max; @@ -137,7 +148,6 @@ Channel4::Channel4() , lengthCounter_(disableMaster_, 0x3F) , envelopeUnit_(staticOutputTest_) , nextEventUnit_(0) -, cycleCounter_(0) , soMask_(0) , prevOut_(0) , nr4_(0) @@ -152,49 +162,44 @@ void Channel4::setEvent() { nextEventUnit_ = &lengthCounter_; } -void Channel4::setNr1(unsigned data) { - lengthCounter_.nr1Change(data, nr4_, cycleCounter_); +void Channel4::setNr1(unsigned data, unsigned long cc) { + lengthCounter_.nr1Change(data, nr4_, cc); setEvent(); } -void Channel4::setNr2(unsigned data) { +void Channel4::setNr2(unsigned data, unsigned long cc) { if (envelopeUnit_.nr2Change(data)) disableMaster_(); else - staticOutputTest_(cycleCounter_); + staticOutputTest_(cc); setEvent(); } -void Channel4::setNr4(unsigned const data) { - lengthCounter_.nr4Change(nr4_, data, cycleCounter_); +void Channel4::setNr4(unsigned const data, unsigned long const cc) { + lengthCounter_.nr4Change(nr4_, data, cc); nr4_ = data; - if (data & 0x80) { // init-bit - nr4_ &= 0x7F; - master_ = !envelopeUnit_.nr4Init(cycleCounter_); - + if (nr4_ & psg_nr4_init) { + nr4_ -= psg_nr4_init; + master_ = !envelopeUnit_.nr4Init(cc); if (master_) - lfsr_.nr4Init(cycleCounter_); + lfsr_.nr4Init(cc); - staticOutputTest_(cycleCounter_); + staticOutputTest_(cc); } setEvent(); } -void Channel4::setSo(unsigned long soMask) { +void Channel4::setSo(unsigned long soMask, unsigned long cc) { soMask_ = soMask; - staticOutputTest_(cycleCounter_); + staticOutputTest_(cc); setEvent(); } -void Channel4::reset() { - // cycleCounter >> 12 & 7 represents the frame sequencer position. - cycleCounter_ &= 0xFFF; - cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000; - - lfsr_.reset(cycleCounter_); +void Channel4::reset(unsigned long cc) { + lfsr_.reset(cc); envelopeUnit_.reset(); setEvent(); } @@ -202,53 +207,49 @@ void Channel4::reset() { void Channel4::loadState(SaveState const &state) { lfsr_.loadState(state); envelopeUnit_.loadState(state.spu.ch4.env, state.mem.ioamhram.get()[0x121], - state.spu.cycleCounter); + state.spu.cycleCounter); lengthCounter_.loadState(state.spu.ch4.lcounter, state.spu.cycleCounter); - cycleCounter_ = state.spu.cycleCounter; nr4_ = state.spu.ch4.nr4; master_ = state.spu.ch4.master; } -void Channel4::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) { +void Channel4::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) { unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0; - unsigned long const outLow = outBase * (0 - 15ul); - unsigned long const endCycles = cycleCounter_ + cycles; + unsigned long const outLow = outBase * -15; - for (;;) { - unsigned long const outHigh = outBase * (envelopeUnit_.getVolume() * 2 - 15ul); - unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles); + while (cc < end) { + unsigned long const outHigh = outBase * (envelopeUnit_.getVolume() * 2l - 15); + unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), end); unsigned long out = lfsr_.isHighState() ? outHigh : outLow; - - while (lfsr_.counter() <= nextMajorEvent) { + if (lfsr_.counter() <= nextMajorEvent) { + Lfsr lfsr = lfsr_; + while (lfsr.counter() <= nextMajorEvent) { + *buf += out - prevOut_; + prevOut_ = out; + buf += lfsr.counter() - cc; + cc = lfsr.counter(); + lfsr.event(); + out = lfsr.isHighState() ? outHigh : outLow; + } + lfsr_ = lfsr; + } + if (cc < nextMajorEvent) { *buf += out - prevOut_; prevOut_ = out; - buf += lfsr_.counter() - cycleCounter_; - cycleCounter_ = lfsr_.counter(); - - lfsr_.event(); - out = lfsr_.isHighState() ? outHigh : outLow; + buf += nextMajorEvent - cc; + cc = nextMajorEvent; } - - if (cycleCounter_ < nextMajorEvent) { - *buf += out - prevOut_; - prevOut_ = out; - buf += nextMajorEvent - cycleCounter_; - cycleCounter_ = nextMajorEvent; - } - if (nextEventUnit_->counter() == nextMajorEvent) { nextEventUnit_->event(); setEvent(); - } else - break; + } } - if (cycleCounter_ >= SoundUnit::counter_max) { - lengthCounter_.resetCounters(cycleCounter_); - lfsr_.resetCounters(cycleCounter_); - envelopeUnit_.resetCounters(cycleCounter_); - cycleCounter_ -= SoundUnit::counter_max; + if (cc >= SoundUnit::counter_max) { + lengthCounter_.resetCounters(cc); + lfsr_.resetCounters(cc); + envelopeUnit_.resetCounters(cc); } } @@ -264,12 +265,9 @@ SYNCFUNC(Channel4) EVS(nextEventUnit_, &lengthCounter_, 3); EES(nextEventUnit_, NULL); - NSS(cycleCounter_); NSS(soMask_); NSS(prevOut_); NSS(nr4_); NSS(master_); } - -} diff --git a/libgambatte/src/sound/channel4.h b/libgambatte/src/sound/channel4.h index 0ad159181f..629ba11b92 100644 --- a/libgambatte/src/sound/channel4.h +++ b/libgambatte/src/sound/channel4.h @@ -33,14 +33,15 @@ struct SaveState; class Channel4 { public: Channel4(); - void setNr1(unsigned data); - void setNr2(unsigned data); - void setNr3(unsigned data) { lfsr_.nr3Change(data, cycleCounter_); } - void setNr4(unsigned data); - void setSo(unsigned long soMask); + void setNr1(unsigned data, unsigned long cc); + void setNr2(unsigned data, unsigned long cc); + void setNr3(unsigned data, unsigned long cc) { lfsr_.nr3Change(data, cc); } + void setNr4(unsigned data, unsigned long cc); + void setSo(unsigned long soMask, unsigned long cc); bool isActive() const { return master_; } - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); - void reset(); + void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end); + void reset(unsigned long cc); + void resetCc(unsigned long cc, unsigned long newCc) { lfsr_.resetCc(cc, newCc); } void loadState(SaveState const &state); private: @@ -53,6 +54,7 @@ private: void nr3Change(unsigned newNr3, unsigned long cc); void nr4Init(unsigned long cc); void reset(unsigned long cc); + void resetCc(unsigned long cc, unsigned long newCc); void loadState(SaveState const &state); void disableMaster() { killCounter(); master_ = false; reg_ = 0x7FFF; } void killCounter() { counter_ = counter_disabled; } @@ -87,7 +89,6 @@ private: EnvelopeUnit envelopeUnit_; Lfsr lfsr_; SoundUnit *nextEventUnit_; - unsigned long cycleCounter_; unsigned long soMask_; unsigned long prevOut_; unsigned char nr4_; diff --git a/libgambatte/src/sound/duty_unit.cpp b/libgambatte/src/sound/duty_unit.cpp index 5f9f484742..2c91fa10d3 100644 --- a/libgambatte/src/sound/duty_unit.cpp +++ b/libgambatte/src/sound/duty_unit.cpp @@ -17,17 +17,24 @@ // #include "duty_unit.h" +#include "psgdef.h" #include -static inline bool toOutState(unsigned duty, unsigned pos) { - return 0x7EE18180 >> (duty * 8 + pos) & 1; +namespace { + + int const duty_pattern_len = 8; + + bool toOutState(unsigned duty, unsigned pos) { + return 0x7EE18180 >> (duty * duty_pattern_len + pos) & 1; + } + + unsigned toPeriod(unsigned freq) { + return (2048 - freq) * 2; + } + } -static inline unsigned toPeriod(unsigned freq) { - return (2048 - freq) * 2; -} - -namespace gambatte { +using namespace gambatte; DutyUnit::DutyUnit() : nextPosUpdate_(counter_disabled) @@ -44,27 +51,26 @@ void DutyUnit::updatePos(unsigned long const cc) { if (cc >= nextPosUpdate_) { unsigned long const inc = (cc - nextPosUpdate_) / period_ + 1; nextPosUpdate_ += period_ * inc; - pos_ += inc; - pos_ &= 7; + pos_ = (pos_ + inc) % duty_pattern_len; high_ = toOutState(duty_, pos_); } } void DutyUnit::setCounter() { - static unsigned char const nextStateDistance[4 * 8] = { - 7, 6, 5, 4, 3, 2, 1, 1, - 1, 6, 5, 4, 3, 2, 1, 2, - 1, 4, 3, 2, 1, 4, 3, 2, - 1, 6, 5, 4, 3, 2, 1, 2 + static unsigned char const nextStateDistance[][duty_pattern_len] = { + { 7, 6, 5, 4, 3, 2, 1, 1 }, + { 1, 6, 5, 4, 3, 2, 1, 2 }, + { 1, 4, 3, 2, 1, 4, 3, 2 }, + { 1, 6, 5, 4, 3, 2, 1, 2 } }; if (enableEvents_ && nextPosUpdate_ != counter_disabled) { - unsigned const npos = (pos_ + 1) & 7; + unsigned const npos = (pos_ + 1) % duty_pattern_len; counter_ = nextPosUpdate_; - inc_ = nextStateDistance[duty_ * 8 + npos]; + inc_ = nextStateDistance[duty_][npos]; if (toOutState(duty_, npos) == high_) { counter_ += period_ * inc_; - inc_ = nextStateDistance[duty_ * 8 + ((npos + inc_) & 7)]; + inc_ = nextStateDistance[duty_][(npos + inc_) % duty_pattern_len]; } } else counter_ = counter_disabled; @@ -77,16 +83,16 @@ void DutyUnit::setFreq(unsigned newFreq, unsigned long cc) { } void DutyUnit::event() { - static unsigned char const inc[] = { - 1, 7, - 2, 6, - 4, 4, - 6, 2, + static unsigned char const inc[][2] = { + { 1, 7 }, + { 2, 6 }, + { 4, 4 }, + { 6, 2 } }; high_ ^= true; counter_ += inc_ * period_; - inc_ = inc[duty_ * 2 + high_]; + inc_ = inc[duty_][high_]; } void DutyUnit::nr1Change(unsigned newNr1, unsigned long cc) { @@ -99,12 +105,11 @@ void DutyUnit::nr3Change(unsigned newNr3, unsigned long cc) { setFreq((freq() & 0x700) | newNr3, cc); } -void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc, - bool const master) { +void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc, unsigned long const ref, bool const master) { setFreq((newNr4 << 8 & 0x700) | (freq() & 0xFF), cc); - if (newNr4 & 0x80) { - nextPosUpdate_ = (cc & ~1ul) + period_ + 4 - (master << 1); + if (newNr4 & psg_nr4_init) { + nextPosUpdate_ = cc - (cc - ref) % 2 + period_ + 4 - (master << 1); setCounter(); } } @@ -116,6 +121,15 @@ void DutyUnit::reset() { setCounter(); } +void DutyUnit::resetCc(unsigned long cc, unsigned long newCc) { + if (nextPosUpdate_ == counter_disabled) + return; + + updatePos(cc); + nextPosUpdate_ -= cc - newCc; + setCounter(); +} + void DutyUnit::loadState(SaveState::SPU::Duty const &dstate, unsigned const nr1, unsigned const nr4, unsigned long const cc) { nextPosUpdate_ = std::max(dstate.nextPosUpdate, cc); @@ -127,13 +141,8 @@ void DutyUnit::loadState(SaveState::SPU::Duty const &dstate, setCounter(); } -void DutyUnit::resetCounters(unsigned long const oldCc) { - if (nextPosUpdate_ == counter_disabled) - return; - - updatePos(oldCc); - nextPosUpdate_ -= counter_max; - setCounter(); +void DutyUnit::resetCounters(unsigned long cc) { + resetCc(cc, cc - counter_max); } void DutyUnit::killCounter() { @@ -158,5 +167,3 @@ SYNCFUNC(DutyUnit) NSS(high_); NSS(enableEvents_); } - -} diff --git a/libgambatte/src/sound/duty_unit.h b/libgambatte/src/sound/duty_unit.h index 0f96140c9e..902beaad74 100644 --- a/libgambatte/src/sound/duty_unit.h +++ b/libgambatte/src/sound/duty_unit.h @@ -34,8 +34,9 @@ public: bool isHighState() const { return high_; } void nr1Change(unsigned newNr1, unsigned long cc); void nr3Change(unsigned newNr3, unsigned long cc); - void nr4Change(unsigned newNr4, unsigned long cc, bool master); + void nr4Change(unsigned newNr4, unsigned long cc, unsigned long ref, bool master); void reset(); + void resetCc(unsigned long cc, unsigned long newCc); void loadState(SaveState::SPU::Duty const &dstate, unsigned nr1, unsigned nr4, unsigned long cc); void killCounter(); void reviveCounter(unsigned long cc); diff --git a/libgambatte/src/sound/envelope_unit.cpp b/libgambatte/src/sound/envelope_unit.cpp index 7e9a81bb81..02369c0a41 100644 --- a/libgambatte/src/sound/envelope_unit.cpp +++ b/libgambatte/src/sound/envelope_unit.cpp @@ -17,9 +17,10 @@ // #include "envelope_unit.h" +#include "psgdef.h" #include -namespace gambatte { +using namespace gambatte; EnvelopeUnit::VolOnOffEvent EnvelopeUnit::nullEvent_; @@ -41,51 +42,53 @@ void EnvelopeUnit::loadState(SaveState::SPU::Env const &estate, unsigned nr2, un } void EnvelopeUnit::event() { - unsigned long const period = nr2_ & 7; + unsigned long const period = nr2_ & psg_nr2_step; if (period) { unsigned newVol = volume_; - if (nr2_ & 8) + if (nr2_ & psg_nr2_inc) ++newVol; else --newVol; - if (newVol < 0x10U) { + if (newVol < 0x10) { volume_ = newVol; if (volume_ < 2) volOnOffEvent_(counter_); counter_ += period << 15; - } else + } + else counter_ = counter_disabled; - } else + } + else counter_ += 8ul << 15; } bool EnvelopeUnit::nr2Change(unsigned const newNr2) { - if (!(nr2_ & 7) && counter_ != counter_disabled) + if (!(nr2_ & psg_nr2_step) && counter_ != counter_disabled) ++volume_; - else if (!(nr2_ & 8)) + else if (!(nr2_ & psg_nr2_inc)) volume_ += 2; - if ((nr2_ ^ newNr2) & 8) + if ((nr2_ ^ newNr2) & psg_nr2_inc) volume_ = 0x10 - volume_; volume_ &= 0xF; nr2_ = newNr2; - return !(newNr2 & 0xF8); + return !(newNr2 & (psg_nr2_initvol | psg_nr2_inc)); } bool EnvelopeUnit::nr4Init(unsigned long const cc) { - unsigned long period = nr2_ & 7 ? nr2_ & 7 : 8; + unsigned long period = nr2_ & psg_nr2_step ? nr2_ & psg_nr2_step : 8; if (((cc + 2) & 0x7000) == 0x0000) ++period; counter_ = cc - ((cc - 0x1000) & 0x7FFF) + period * 0x8000; - volume_ = nr2_ >> 4; - return !(nr2_ & 0xF8); + volume_ = nr2_ / (1u * psg_nr2_initvol & -psg_nr2_initvol); + return !(nr2_ & (psg_nr2_initvol | psg_nr2_inc)); } SYNCFUNC(EnvelopeUnit) @@ -94,5 +97,3 @@ SYNCFUNC(EnvelopeUnit) NSS(nr2_); NSS(volume_); } - -} diff --git a/libgambatte/src/sound/length_counter.cpp b/libgambatte/src/sound/length_counter.cpp index cdfd4c18bb..50ac6310e9 100644 --- a/libgambatte/src/sound/length_counter.cpp +++ b/libgambatte/src/sound/length_counter.cpp @@ -18,9 +18,10 @@ #include "length_counter.h" #include "master_disabler.h" +#include "psgdef.h" #include -namespace gambatte { +using namespace gambatte; LengthCounter::LengthCounter(MasterDisabler &disabler, unsigned const mask) : disableMaster_(disabler) @@ -38,35 +39,30 @@ void LengthCounter::event() { void LengthCounter::nr1Change(unsigned const newNr1, unsigned const nr4, unsigned long const cc) { lengthCounter_ = (~newNr1 & lengthMask_) + 1; - counter_ = nr4 & 0x40 - ? ((cc >> 13) + lengthCounter_) << 13 - : static_cast(counter_disabled); + counter_ = nr4 & psg_nr4_lcen + ? ((cc >> 13) + lengthCounter_) << 13 + : 1 * counter_disabled; } void LengthCounter::nr4Change(unsigned const oldNr4, unsigned const newNr4, unsigned long const cc) { if (counter_ != counter_disabled) lengthCounter_ = (counter_ >> 13) - (cc >> 13); - { - unsigned dec = 0; - - if (newNr4 & 0x40) { - dec = ~cc >> 12 & 1; - - if (!(oldNr4 & 0x40) && lengthCounter_) { - if (!(lengthCounter_ -= dec)) - disableMaster_(); - } + unsigned dec = 0; + if (newNr4 & psg_nr4_lcen) { + dec = ~cc >> 12 & 1; + if (!(oldNr4 & psg_nr4_lcen) && lengthCounter_) { + if (!(lengthCounter_ -= dec)) + disableMaster_(); } - - if ((newNr4 & 0x80) && !lengthCounter_) - lengthCounter_ = lengthMask_ + 1 - dec; } - if ((newNr4 & 0x40) && lengthCounter_) - counter_ = ((cc >> 13) + lengthCounter_) << 13; - else - counter_ = counter_disabled; + if (newNr4 & psg_nr4_init && !lengthCounter_) + lengthCounter_ = lengthMask_ + 1 - dec; + + counter_ = newNr4 & psg_nr4_lcen && lengthCounter_ + ? ((cc >> 13) + lengthCounter_) << 13 + : 1 * counter_disabled; } void LengthCounter::loadState(SaveState::SPU::LCounter const &lstate, unsigned long const cc) { @@ -79,5 +75,3 @@ SYNCFUNC(LengthCounter) NSS(counter_); NSS(lengthCounter_); } - -} diff --git a/libgambatte/src/sound/psgdef.h b/libgambatte/src/sound/psgdef.h new file mode 100644 index 0000000000..e0aa66eb32 --- /dev/null +++ b/libgambatte/src/sound/psgdef.h @@ -0,0 +1,31 @@ +#ifndef PSGDEF_H +#define PSGDEF_H + +namespace gambatte { + + enum { + psg_nr10_rsh = 0x07, + psg_nr10_neg = 0x08, + psg_nr10_time = 0x70 + }; + + enum { + psg_nr2_step = 0x07, + psg_nr2_inc = 0x08, + psg_nr2_initvol = 0xF0 + }; + + enum { + psg_nr43_r = 0x07, + psg_nr43_7biten = 0x08, + psg_nr43_s = 0xF0 + }; + + enum { + psg_nr4_lcen = 0x40, + psg_nr4_init = 0x80 + }; + +} + +#endif \ No newline at end of file diff --git a/libgambatte/src/tima.cpp b/libgambatte/src/tima.cpp index 5b935c8a9c..854d247367 100644 --- a/libgambatte/src/tima.cpp +++ b/libgambatte/src/tima.cpp @@ -19,12 +19,17 @@ #include "tima.h" #include "savestate.h" -static unsigned char const timaClock[4] = { 10, 4, 6, 8 }; +using namespace gambatte; + +namespace { + unsigned char const timaClock[4] = { 10, 4, 6, 8 }; +} namespace gambatte { Tima::Tima() -: lastUpdate_(0) +: divLastUpdate_(0) +, lastUpdate_(0) , tmatime_(disabled_time) , tima_(0) , tma_(0) @@ -33,7 +38,7 @@ Tima::Tima() } void Tima::loadState(SaveState const &state, TimaInterruptRequester timaIrq) { - basetime_ = state.mem.timaBasetime; + divLastUpdate_ = state.mem.divLastUpdate - 0x100l * state.mem.ioamhram.get()[0x104]; lastUpdate_ = state.mem.timaLastUpdate; tmatime_ = state.mem.tmatime; tima_ = state.mem.ioamhram.get()[0x105]; @@ -43,8 +48,8 @@ void Tima::loadState(SaveState const &state, TimaInterruptRequester timaIrq) { unsigned long nextIrqEventTime = disabled_time; if (tac_ & 4) { nextIrqEventTime = tmatime_ != disabled_time && tmatime_ > state.cpu.cycleCounter - ? tmatime_ - : lastUpdate_ + ((256u - tima_) << timaClock[tac_ & 3]) + 3; + ? tmatime_ + : lastUpdate_ + ((256l - tima_) << timaClock[tac_ & 3]) + 3; } timaIrq.setNextIrqEventTime(nextIrqEventTime); @@ -82,7 +87,6 @@ void Tima::updateTima(unsigned long const cc) { if (tmp == 0x100) { tmp = 0; tmatime_ = lastUpdate_ + 3; - if (cc >= tmatime_) { if (cc >= tmatime_ + 4) tmatime_ = disabled_time; @@ -102,7 +106,7 @@ void Tima::setTima(unsigned const data, unsigned long const cc, TimaInterruptReq if (tmatime_ - cc < 4) tmatime_ = disabled_time; - timaIrq.setNextIrqEventTime(lastUpdate_ + ((256u - data) << timaClock[tac_ & 3]) + 3); + timaIrq.setNextIrqEventTime(lastUpdate_ + ((256l - data) << timaClock[tac_ & 3]) + 3); } tima_ = data; @@ -122,32 +126,26 @@ void Tima::setTac(unsigned const data, unsigned long const cc, TimaInterruptRequ unsigned long nextIrqEventTime = timaIrq.nextIrqEventTime(); if (tac_ & 0x04) { - updateIrq(cc, timaIrq); - updateTima(cc); - - lastUpdate_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3; - tmatime_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3; - nextIrqEventTime -= (1u << (timaClock[tac_ & 3] - 1)) + 3; - + unsigned const inc = ~(data >> 2 & (cc - divLastUpdate_) >> (timaClock[data & 3] - 1)) & 1; + lastUpdate_ -= (inc << (timaClock[tac_ & 3] - 1)) + 3; + nextIrqEventTime -= (inc << (timaClock[tac_ & 3] - 1)) + 3; if (cc >= nextIrqEventTime) timaIrq.flagIrq(); updateTima(cc); - tmatime_ = disabled_time; nextIrqEventTime = disabled_time; } if (data & 4) { - unsigned long diff = cc - basetime_; - if (agbFlag) { + unsigned long diff = cc - divLastUpdate_; if (((diff >> (timaClock[tac_ & 3] - 1)) & 1) == 1 && ((diff >> (timaClock[data & 3] - 1)) & 1) == 0) tima_++; } - lastUpdate_ = basetime_ + ((diff >> timaClock[data & 3]) << timaClock[data & 3]); - nextIrqEventTime = lastUpdate_ + ((256u - tima_) << timaClock[data & 3]) + 3; + lastUpdate_ = cc - ((cc - divLastUpdate_) & ((1u << timaClock[data & 3]) - 1)); + nextIrqEventTime = lastUpdate_ + ((256l - tima_) << timaClock[data & 3]) + 3; } timaIrq.setNextIrqEventTime(nextIrqEventTime); @@ -156,12 +154,26 @@ void Tima::setTac(unsigned const data, unsigned long const cc, TimaInterruptRequ tac_ = data; } -void Tima::resTac(unsigned long const cc, TimaInterruptRequester timaIrq) { - basetime_ = cc; - +void Tima::divReset(unsigned long cc, TimaInterruptRequester timaIrq) { if (tac_ & 0x04) { - setTac(tac_ & ~0x04, cc, timaIrq, false); - setTac(tac_ | 0x04, cc, timaIrq, false); + unsigned long nextIrqEventTime = timaIrq.nextIrqEventTime(); + lastUpdate_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3; + nextIrqEventTime -= (1u << (timaClock[tac_ & 3] - 1)) + 3; + if (cc >= nextIrqEventTime) + timaIrq.flagIrq(); + + updateTima(cc); + lastUpdate_ = cc; + timaIrq.setNextIrqEventTime(lastUpdate_ + ((256l - tima_) << timaClock[tac_ & 3]) + 3); + } + + divLastUpdate_ = cc; +} + +void Tima::speedChange(TimaInterruptRequester timaIrq) { + if ((tac_ & 0x07) >= 0x05) { + lastUpdate_ -= 4; + timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() - 4); } } @@ -173,15 +185,15 @@ unsigned Tima::tima(unsigned long cc) { } void Tima::doIrqEvent(TimaInterruptRequester timaIrq) { - timaIrq.flagIrq(); + timaIrq.flagIrq(timaIrq.nextIrqEventTime()); timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() - + ((256u - tma_) << timaClock[tac_ & 3])); + + ((256l - tma_) << timaClock[tac_ & 3])); } SYNCFUNC(Tima) { NSS(lastUpdate_); - NSS(basetime_); + NSS(divLastUpdate_); NSS(tmatime_); NSS(tima_); NSS(tma_); diff --git a/libgambatte/src/tima.h b/libgambatte/src/tima.h index d1db0854b9..8079dcccab 100644 --- a/libgambatte/src/tima.h +++ b/libgambatte/src/tima.h @@ -27,6 +27,7 @@ class TimaInterruptRequester { public: explicit TimaInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {} void flagIrq() const { intreq_.flagIrq(4); } + void flagIrq(unsigned long cc) const { intreq_.flagIrq(4, cc); } unsigned long nextIrqEventTime() const { return intreq_.eventTime(intevent_tima); } void setNextIrqEventTime(unsigned long time) const { intreq_.setEventTime(time); } @@ -42,12 +43,14 @@ public: void setTima(unsigned tima, unsigned long cc, TimaInterruptRequester timaIrq); void setTma(unsigned tma, unsigned long cc, TimaInterruptRequester timaIrq); void setTac(unsigned tac, unsigned long cc, TimaInterruptRequester timaIrq, bool agbFlag); - void resTac(unsigned long cc, TimaInterruptRequester timaIrq); + void divReset(unsigned long cc, TimaInterruptRequester); + void speedChange(TimaInterruptRequester); + unsigned long divLastUpdate() const { return divLastUpdate_; } unsigned tima(unsigned long cc); void doIrqEvent(TimaInterruptRequester timaIrq); private: - unsigned long basetime_; + unsigned long divLastUpdate_; unsigned long lastUpdate_; unsigned long tmatime_; unsigned char tima_; diff --git a/libgambatte/src/video/lcddef.h b/libgambatte/src/video/lcddef.h index 5fbf3756cb..0ef743537e 100644 --- a/libgambatte/src/video/lcddef.h +++ b/libgambatte/src/video/lcddef.h @@ -3,18 +3,38 @@ namespace gambatte { -enum { lcdc_bgen = 0x01, - lcdc_objen = 0x02, - lcdc_obj2x = 0x04, - lcdc_tdsel = 0x10, - lcdc_we = 0x20, - lcdc_en = 0x80 }; + enum { + lcdc_bgen = 0x01, + lcdc_objen = 0x02, + lcdc_obj2x = 0x04, + lcdc_bgtmsel = 0x08, + lcdc_tdsel = 0x10, + lcdc_we = 0x20, + lcdc_wtmsel = 0x40, + lcdc_en = 0x80 + }; -enum { lcdstat_lycflag = 0x04, - lcdstat_m0irqen = 0x08, - lcdstat_m1irqen = 0x10, - lcdstat_m2irqen = 0x20, - lcdstat_lycirqen = 0x40 }; + enum { + lcdstat_lycflag = 0x04, + lcdstat_m0irqen = 0x08, + lcdstat_m1irqen = 0x10, + lcdstat_m2irqen = 0x20, + lcdstat_lycirqen = 0x40 + }; + + enum { + lcd_hres = 160, + lcd_vres = 144, + lcd_lines_per_frame = 154, + lcd_max_num_sprites_per_line = 10, + lcd_num_oam_entries = 40, + lcd_cycles_per_line = 456, + lcd_force_signed_enum1 = -1 + }; + enum { + lcd_cycles_per_frame = 1l * lcd_lines_per_frame * lcd_cycles_per_line, + lcd_force_signed_enum2 = -1 + }; } From 23c1c74030e320af86e3ff8e65b2a3d85de4c778 Mon Sep 17 00:00:00 2001 From: TiKevin83 Date: Sun, 16 Feb 2020 20:07:28 -0500 Subject: [PATCH 3/6] Sinamas commits mostly done, just need to fix DMG games on CGB using LCDC --- libgambatte/libgambatte.vcxproj | 3 +- libgambatte/libgambatte.vcxproj.filters | 9 +- libgambatte/src/cpu.cpp | 5 +- libgambatte/src/mem/cartridge.cpp | 8 + libgambatte/src/memory.cpp | 1 - libgambatte/src/video.cpp | 599 ++++---- libgambatte/src/video.h | 44 +- libgambatte/src/video/ly_counter.cpp | 15 +- libgambatte/src/video/ly_counter.h | 5 +- libgambatte/src/video/lyc_irq.cpp | 62 +- libgambatte/src/video/lyc_irq.h | 2 +- libgambatte/src/video/m0_irq.h | 68 - libgambatte/src/video/mstat_irq.h | 72 + libgambatte/src/video/next_m0_time.cpp | 2 +- libgambatte/src/video/next_m0_time.h | 4 +- libgambatte/src/video/ppu.cpp | 1687 ++++++++++++----------- libgambatte/src/video/ppu.h | 22 +- libgambatte/src/video/sprite_mapper.cpp | 113 +- libgambatte/src/video/sprite_mapper.h | 28 +- 19 files changed, 1413 insertions(+), 1336 deletions(-) delete mode 100644 libgambatte/src/video/m0_irq.h create mode 100644 libgambatte/src/video/mstat_irq.h diff --git a/libgambatte/libgambatte.vcxproj b/libgambatte/libgambatte.vcxproj index 549134284f..e0e201771f 100644 --- a/libgambatte/libgambatte.vcxproj +++ b/libgambatte/libgambatte.vcxproj @@ -187,9 +187,10 @@ + - + diff --git a/libgambatte/libgambatte.vcxproj.filters b/libgambatte/libgambatte.vcxproj.filters index 93ddd6e40b..504d75698f 100644 --- a/libgambatte/libgambatte.vcxproj.filters +++ b/libgambatte/libgambatte.vcxproj.filters @@ -111,9 +111,6 @@ Header Files - - Header Files - Header Files @@ -132,6 +129,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/libgambatte/src/cpu.cpp b/libgambatte/src/cpu.cpp index 1833b0290b..67a411c0e9 100644 --- a/libgambatte/src/cpu.cpp +++ b/libgambatte/src/cpu.cpp @@ -23,7 +23,7 @@ namespace gambatte { CPU::CPU() -: mem_(sp, pc, opcode_, prefetched_) +: mem_(Interrupter(sp, pc, opcode_, prefetched_)) , cycleCounter_(0) , pc(0x100) , sp(0xFFFE) @@ -2077,7 +2077,8 @@ SYNCFUNC(CPU) NSS(e); NSS(h); NSS(l); - NSS(skip_); + NSS(opcode_); + NSS(prefetched_); } } diff --git a/libgambatte/src/mem/cartridge.cpp b/libgambatte/src/mem/cartridge.cpp index 1cdd7cc968..165f5e77be 100644 --- a/libgambatte/src/mem/cartridge.cpp +++ b/libgambatte/src/mem/cartridge.cpp @@ -375,6 +375,10 @@ public: { } + virtual unsigned char curRomBank() const { + return rombank_; + } + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) { switch (p >> 13 & 3) { case 0: @@ -443,6 +447,10 @@ public: { } + virtual unsigned char curRomBank() const { + return rombank_; + } + virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) { switch (p >> 13 & 3) { case 0: diff --git a/libgambatte/src/memory.cpp b/libgambatte/src/memory.cpp index b63a243fe8..95a02ea4a4 100644 --- a/libgambatte/src/memory.cpp +++ b/libgambatte/src/memory.cpp @@ -1303,7 +1303,6 @@ SYNCFUNC(Memory) NSS(cgbSwitching_); NSS(agbMode_); NSS(gbIsCgb_); - NSS(halttime_); NSS(stopped_); NSS(LINKCABLE_); NSS(linkClockTrigger_); diff --git a/libgambatte/src/video.cpp b/libgambatte/src/video.cpp index 997993c881..ce58c4a392 100644 --- a/libgambatte/src/video.cpp +++ b/libgambatte/src/video.cpp @@ -21,7 +21,7 @@ #include #include -namespace gambatte { +using namespace gambatte; unsigned long LCD::gbcToRgb32(const unsigned bgr15) { unsigned long const r = bgr15 & 0x1F; @@ -31,11 +31,56 @@ unsigned long LCD::gbcToRgb32(const unsigned bgr15) { return cgbColorsRgb32_[bgr15 & 0x7FFF]; } +namespace { + + // TODO: simplify cycle offsets. + + long const mode1_irq_frame_cycle = 1l * lcd_vres * lcd_cycles_per_line - 2; + int const mode2_irq_line_cycle = lcd_cycles_per_line - 4; + int const mode2_irq_line_cycle_ly0 = lcd_cycles_per_line - 2; + + unsigned long mode1IrqSchedule(LyCounter const& lyCounter, unsigned long cc) { + return lyCounter.nextFrameCycle(mode1_irq_frame_cycle, cc); + } + + unsigned long mode2IrqSchedule(unsigned const statReg, + LyCounter const& lyCounter, unsigned long const cc) { + if (!(statReg & lcdstat_m2irqen)) + return disabled_time; + + unsigned long const lastM2Fc = (lcd_vres - 1l) * lcd_cycles_per_line + mode2_irq_line_cycle; + unsigned long const ly0M2Fc = (lcd_lines_per_frame - 1l) * lcd_cycles_per_line + mode2_irq_line_cycle_ly0; + return lyCounter.frameCycles(cc) - lastM2Fc < ly0M2Fc - lastM2Fc || (statReg & lcdstat_m0irqen) + ? lyCounter.nextFrameCycle(ly0M2Fc, cc) + : lyCounter.nextLineCycle(mode2_irq_line_cycle, cc); + } + + unsigned long m0TimeOfCurrentLine( + unsigned long nextLyTime, + unsigned long lastM0Time, + unsigned long nextM0Time) { + return nextM0Time < nextLyTime ? nextM0Time : lastM0Time; + } + + bool isHdmaPeriod(LyCounter const& lyCounter, + unsigned long m0TimeOfCurrentLy, unsigned long cc) { + return lyCounter.ly() < lcd_vres + && cc + 3 + 3 * lyCounter.isDoubleSpeed() < lyCounter.time() + && cc >= m0TimeOfCurrentLy; + } + + void doCgbColorChange(unsigned char* pdata, + unsigned long* palette, unsigned index, unsigned data, bool trueColor) { + pdata[index] = data; + index /= 2; + palette[index] = gbcToRgb32(pdata[index * 2] | pdata[index * 2 + 1] * 0x100l); + } + +} // unnamed namespace. + void LCD::setDmgPalette(unsigned long palette[], const unsigned long dmgColors[], unsigned data) { - palette[0] = dmgColors[data & 3]; - palette[1] = dmgColors[data >> 2 & 3]; - palette[2] = dmgColors[data >> 4 & 3]; - palette[3] = dmgColors[data >> 6 & 3]; + for (int i = 0; i < num_palette_entries; ++i, data /= num_palette_entries) + palette[i] = gbcToRgb32(dmgColors[data % num_palette_entries]); } void LCD::setCgbPalette(unsigned *lut) { @@ -47,10 +92,10 @@ void LCD::setCgbPalette(unsigned *lut) { LCD::LCD(unsigned char const *oamram, unsigned char const *vram, VideoInterruptRequester memEventRequester) : ppu_(nextM0Time_, oamram, vram) +, bgpData_() +, objpData_() , eventTimes_(memEventRequester) , statReg_(0) -, m2IrqStatReg_(0) -, m1IrqStatReg_(0) , scanlinecallback(0) , scanlinecallbacksl(0) { @@ -60,7 +105,7 @@ LCD::LCD(unsigned char const *oamram, unsigned char const *vram, std::memset(objpData_, 0, sizeof objpData_); reset(oamram, vram, false); - setVideoBuffer(0, 160); + setVideoBuffer(0, lcd_hres); } void LCD::reset(unsigned char const *oamram, unsigned char const *vram, bool cgb) { @@ -73,40 +118,6 @@ void LCD::setCgb(bool cgb) { ppu_.setCgb(cgb); } -static unsigned long mode2IrqSchedule(unsigned const statReg, - LyCounter const &lyCounter, unsigned long const cc) { - if (!(statReg & lcdstat_m2irqen)) - return disabled_time; - - int next = lyCounter.time() - cc; - if (lyCounter.ly() >= 143 - || (lyCounter.ly() == 142 && next <= 4) - || (statReg & lcdstat_m0irqen)) { - next += (153u - lyCounter.ly()) * lyCounter.lineTime(); - } else { - next -= 4; - if (next <= 0) - next += lyCounter.lineTime(); - } - - return cc + next; -} - -static unsigned long m0IrqTimeFromXpos166Time(unsigned long xpos166Time, bool cgb, bool ds) { - return xpos166Time + cgb - ds; -} - -static unsigned long hdmaTimeFromM0Time(unsigned long m0Time, bool ds) { - return m0Time + 1 - ds; -} - -static unsigned long nextHdmaTime(unsigned long lastM0Time, - unsigned long nextM0Time, unsigned long cc, bool ds) { - return cc < hdmaTimeFromM0Time(lastM0Time, ds) - ? hdmaTimeFromM0Time(lastM0Time, ds) - : hdmaTimeFromM0Time(nextM0Time, ds); -} - void LCD::setStatePtrs(SaveState &state) { state.ppu.bgpData.set( bgpData_, sizeof bgpData_); state.ppu.objpData.set(objpData_, sizeof objpData_); @@ -115,40 +126,35 @@ void LCD::setStatePtrs(SaveState &state) { void LCD::loadState(SaveState const &state, unsigned char const *const oamram) { statReg_ = state.mem.ioamhram.get()[0x141]; - m2IrqStatReg_ = statReg_; - m1IrqStatReg_ = statReg_; ppu_.loadState(state, oamram); lycIrq_.loadState(state); - m0Irq_.loadState(state); + mstatIrq_.loadState(state); if (ppu_.lcdc() & lcdc_en) { nextM0Time_.predictNextM0Time(ppu_); lycIrq_.reschedule(ppu_.lyCounter(), ppu_.now()); - eventTimes_.setm( - state.ppu.pendingLcdstatIrq + eventTimes_.setm(state.ppu.pendingLcdstatIrq ? ppu_.now() + 1 - : static_cast(disabled_time)); + : 1 * disabled_time); eventTimes_.setm( - state.ppu.oldWy != state.mem.ioamhram.get()[0x14A] - ? ppu_.now() + 1 - : static_cast(disabled_time)); + state.ppu.oldWy != state.mem.ioamhram.get()[0x14A] + ? ppu_.now() + 2 - isDoubleSpeed() + : 1 * disabled_time); eventTimes_.set(ppu_.lyCounter().time()); eventTimes_.setm( SpriteMapper::schedule(ppu_.lyCounter(), ppu_.now())); eventTimes_.setm(lycIrq_.time()); - eventTimes_.setm( - ppu_.lyCounter().nextFrameCycle(144 * 456, ppu_.now()) - 2); + eventTimes_.setm(mode1IrqSchedule(ppu_.lyCounter(), ppu_.now())); eventTimes_.setm( mode2IrqSchedule(statReg_, ppu_.lyCounter(), ppu_.now())); eventTimes_.setm(statReg_ & lcdstat_m0irqen ? ppu_.now() + state.ppu.nextM0Irq - : static_cast(disabled_time)); + : 1 * disabled_time); eventTimes_.setm(state.mem.hdmaTransfer - ? nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), - ppu_.now(), isDoubleSpeed()) - : static_cast(disabled_time)); + ? nextM0Time_.predictedNextM0Time() + : 1 * disabled_time); } else for (int i = 0; i < num_memevents; ++i) eventTimes_.set(MemEvent(i), disabled_time); @@ -157,9 +163,9 @@ void LCD::loadState(SaveState const &state, unsigned char const *const oamram) { void LCD::refreshPalettes() { if (ppu_.cgb()) { - for (unsigned i = 0; i < 8 * 8; i += 2) { - ppu_.bgPalette()[i >> 1] = gbcToRgb32( bgpData_[i] | bgpData_[i + 1] << 8); - ppu_.spPalette()[i >> 1] = gbcToRgb32(objpData_[i] | objpData_[i + 1] << 8); + for (int i = 0; i < max_num_palettes * num_palette_entries; ++i) { + ppu_.bgPalette()[i] = gbcToRgb32(bgpData_[2 * i] | bgpData_[2 * i + 1] * 0x100l); + ppu_.spPalette()[i] = gbcToRgb32(objpData_[2 * i] | objpData_[2 * i + 1] * 0x100l); } } else { setDmgPalette(ppu_.bgPalette() , dmgColorsRgb32_ , bgpData_[0]); @@ -200,6 +206,12 @@ void LCD::updateScreen(bool const blanklcd, unsigned long const cycleCounter) { } } +void LCD::blackScreen() { + if (ppu_.frameBuf().fb()) { + clear(ppu_.frameBuf().fb(), gbcToRgb32(0x0000), ppu_.frameBuf().pitch()); + } +} + void LCD::resetCc(unsigned long const oldCc, unsigned long const newCc) { update(oldCc); ppu_.resetCc(oldCc, newCc); @@ -221,72 +233,45 @@ void LCD::resetCc(unsigned long const oldCc, unsigned long const newCc) { void LCD::speedChange(unsigned long const cc) { update(cc); - ppu_.speedChange(cc); + ppu_.speedChange(); if (ppu_.lcdc() & lcdc_en) { nextM0Time_.predictNextM0Time(ppu_); - lycIrq_.reschedule(ppu_.lyCounter(), cc); + lycIrq_.reschedule(ppu_.lyCounter(), ppu_.now()); eventTimes_.set(ppu_.lyCounter().time()); - eventTimes_.setm(SpriteMapper::schedule(ppu_.lyCounter(), cc)); + eventTimes_.setm(SpriteMapper::schedule(ppu_.lyCounter(), ppu_.now())); eventTimes_.setm(lycIrq_.time()); - eventTimes_.setm(ppu_.lyCounter().nextFrameCycle(144 * 456, cc) - 2); - eventTimes_.setm(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cc)); + eventTimes_.setm(mode1IrqSchedule(ppu_.lyCounter(), ppu_.now())); + eventTimes_.setm(mode2IrqSchedule(statReg_, ppu_.lyCounter(), ppu_.now())); - if (eventTimes_(memevent_m0irq) != disabled_time - && eventTimes_(memevent_m0irq) - cc > 1) { - eventTimes_.setm(m0IrqTimeFromXpos166Time( - ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed())); + if (eventTimes_(memevent_m0irq) != disabled_time) { + eventTimes_.setm(ppu_.predictedNextXposTime(lcd_hres + 6)); } - - if (hdmaIsEnabled() && eventTimes_(memevent_hdma) - cc > 1) { - eventTimes_.setm(nextHdmaTime(ppu_.lastM0Time(), - nextM0Time_.predictedNextM0Time(), cc, isDoubleSpeed())); + if (hdmaIsEnabled()) { + eventTimes_.setm(nextM0Time_.predictedNextM0Time()); } } } -static unsigned long m0TimeOfCurrentLine( - unsigned long nextLyTime, - unsigned long lastM0Time, - unsigned long nextM0Time) { - return nextM0Time < nextLyTime ? nextM0Time : lastM0Time; -} - unsigned long LCD::m0TimeOfCurrentLine(unsigned long const cc) { if (cc >= nextM0Time_.predictedNextM0Time()) { update(cc); nextM0Time_.predictNextM0Time(ppu_); } - return gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(), ppu_.lastM0Time(), - nextM0Time_.predictedNextM0Time()); + return ::m0TimeOfCurrentLine(ppu_.lyCounter().time(), ppu_.lastM0Time(), + nextM0Time_.predictedNextM0Time()); } -static bool isHdmaPeriod(LyCounter const &lyCounter, - unsigned long m0TimeOfCurrentLy, unsigned long cc) { - int timeToNextLy = lyCounter.time() - cc; - return lyCounter.ly() < 144 && timeToNextLy > 4 - && cc >= hdmaTimeFromM0Time(m0TimeOfCurrentLy, lyCounter.isDoubleSpeed()); -} +void LCD::enableHdma(unsigned long const cc) { + if (cc >= eventTimes_.nextEventTime()) + update(cc); -void LCD::enableHdma(unsigned long const cycleCounter) { - if (cycleCounter >= nextM0Time_.predictedNextM0Time()) { - update(cycleCounter); - nextM0Time_.predictNextM0Time(ppu_); - } else if (cycleCounter >= eventTimes_.nextEventTime()) - update(cycleCounter); - - unsigned long const m0TimeCurLy = - gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(), - ppu_.lastM0Time(), - nextM0Time_.predictedNextM0Time()); - if (isHdmaPeriod(ppu_.lyCounter(), m0TimeCurLy, cycleCounter)) + if (::isHdmaPeriod(ppu_.lyCounter(), m0TimeOfCurrentLine(cc), cc + 4)) eventTimes_.flagHdmaReq(); - eventTimes_.setm(nextHdmaTime( - ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), - cycleCounter, isDoubleSpeed())); + eventTimes_.setm(nextM0Time_.predictedNextM0Time()); } void LCD::disableHdma(unsigned long const cycleCounter) { @@ -296,14 +281,33 @@ void LCD::disableHdma(unsigned long const cycleCounter) { eventTimes_.setm(disabled_time); } -bool LCD::vramAccessible(unsigned long const cc) { +bool LCD::isHdmaPeriod(unsigned long const cc) { + if (cc >= eventTimes_.nextEventTime()) + update(cc); + + return ::isHdmaPeriod(ppu_.lyCounter(), m0TimeOfCurrentLine(cc), cc); +} + +bool LCD::vramReadable(unsigned long const cc) { if (cc >= eventTimes_.nextEventTime()) update(cc); return !(ppu_.lcdc() & lcdc_en) - || ppu_.lyCounter().ly() >= 144 - || ppu_.lyCounter().lineCycles(cc) < 80U - || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc); + || ppu_.lyCounter().ly() >= lcd_vres + || ppu_.inactivePeriodAfterDisplayEnable(cc + 1 - ppu_.cgb() + isDoubleSpeed()) + || ppu_.lyCounter().lineCycles(cc) + isDoubleSpeed() < 76u + 3 * ppu_.cgb() + || cc + 2 >= m0TimeOfCurrentLine(cc); +} + +bool LCD::vramWritable(unsigned long const cc) { + if (cc >= eventTimes_.nextEventTime()) + update(cc); + + return !(ppu_.lcdc() & lcdc_en) + || ppu_.lyCounter().ly() >= lcd_vres + || ppu_.inactivePeriodAfterDisplayEnable(cc + 1 - ppu_.cgb() + isDoubleSpeed()) + || ppu_.lyCounter().lineCycles(cc) + isDoubleSpeed() < 79 + || cc + 2 >= m0TimeOfCurrentLine(cc); } bool LCD::cgbpAccessible(unsigned long const cc) { @@ -311,9 +315,10 @@ bool LCD::cgbpAccessible(unsigned long const cc) { update(cc); return !(ppu_.lcdc() & lcdc_en) - || ppu_.lyCounter().ly() >= 144 - || ppu_.lyCounter().lineCycles(cc) < 80U + isDoubleSpeed() - || cc >= m0TimeOfCurrentLine(cc) + 3 - isDoubleSpeed(); + || ppu_.lyCounter().ly() >= lcd_vres + || ppu_.inactivePeriodAfterDisplayEnable(cc) + || ppu_.lyCounter().lineCycles(cc) + isDoubleSpeed() < 80 + || cc >= m0TimeOfCurrentLine(cc) + 2; } void LCD::doCgbColorChange(unsigned char *pdata, @@ -338,71 +343,67 @@ void LCD::doCgbSpColorChange(unsigned index, unsigned data, unsigned long cc) { } bool LCD::oamReadable(unsigned long const cc) { - if (!(ppu_.lcdc() & lcdc_en) || ppu_.inactivePeriodAfterDisplayEnable(cc)) + if (!(ppu_.lcdc() & lcdc_en) || ppu_.inactivePeriodAfterDisplayEnable(cc + 4)) return true; if (cc >= eventTimes_.nextEventTime()) update(cc); - if (ppu_.lyCounter().lineCycles(cc) + 4 - isDoubleSpeed() * 3u >= 456) - return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153; + if (ppu_.lyCounter().lineCycles(cc) + 4 - isDoubleSpeed() >= lcd_cycles_per_line) + return ppu_.lyCounter().ly() >= lcd_vres - 1 && ppu_.lyCounter().ly() < lcd_lines_per_frame - 1; - return ppu_.lyCounter().ly() >= 144 - || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc); + return ppu_.lyCounter().ly() >= lcd_vres || cc + 2 >= m0TimeOfCurrentLine(cc); } bool LCD::oamWritable(unsigned long const cc) { - if (!(ppu_.lcdc() & lcdc_en) || ppu_.inactivePeriodAfterDisplayEnable(cc)) + if (!(ppu_.lcdc() & lcdc_en) || ppu_.inactivePeriodAfterDisplayEnable(cc + 4 + isDoubleSpeed())) return true; if (cc >= eventTimes_.nextEventTime()) update(cc); - if (ppu_.lyCounter().lineCycles(cc) + 3 + ppu_.cgb() - isDoubleSpeed() * 2u >= 456) - return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153; + if (ppu_.lyCounter().lineCycles(cc) + 3 + ppu_.cgb() >= lcd_cycles_per_line) + return ppu_.lyCounter().ly() >= lcd_vres - 1 && ppu_.lyCounter().ly() < lcd_lines_per_frame - 1; - return ppu_.lyCounter().ly() >= 144 - || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc); + return ppu_.lyCounter().ly() >= lcd_vres || cc + 2 >= m0TimeOfCurrentLine(cc) + || (ppu_.lyCounter().lineCycles(cc) == 76 && !ppu_.cgb()); } void LCD::mode3CyclesChange() { - bool const ds = isDoubleSpeed(); nextM0Time_.invalidatePredictedNextM0Time(); if (eventTimes_(memevent_m0irq) != disabled_time - && eventTimes_(memevent_m0irq) - > m0IrqTimeFromXpos166Time(ppu_.now(), ppu_.cgb(), ds)) { - unsigned long t = m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), - ppu_.cgb(), ds); + && eventTimes_(memevent_m0irq) > ppu_.now()) { + unsigned long t = ppu_.predictedNextXposTime(lcd_hres + 6); eventTimes_.setm(t); } if (eventTimes_(memevent_hdma) != disabled_time - && eventTimes_(memevent_hdma) > hdmaTimeFromM0Time(ppu_.lastM0Time(), ds)) { + && eventTimes_(memevent_hdma) > ppu_.lastM0Time()) { nextM0Time_.predictNextM0Time(ppu_); - eventTimes_.setm( - hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), ds)); + eventTimes_.setm(nextM0Time_.predictedNextM0Time()); } } void LCD::wxChange(unsigned newValue, unsigned long cycleCounter) { - update(cycleCounter + isDoubleSpeed() + 1); + update(cycleCounter + 1 + ppu_.cgb()); ppu_.setWx(newValue); mode3CyclesChange(); } void LCD::wyChange(unsigned const newValue, unsigned long const cc) { - update(cc + 1); - ppu_.setWy(newValue); + update(cc + 1 + ppu_.cgb()); + ppu_.setWy(newValue); // mode3CyclesChange(); // (should be safe to wait until after wy2 delay, because no mode3 events are // close to when wy1 is read.) - // wy2 is a delayed version of wy. really just slowness of ly == wy comparison. + // wy2 is a delayed version of wy for convenience (is this really simpler?). if (ppu_.cgb() && (ppu_.lcdc() & lcdc_en)) { - eventTimes_.setm(cc + 5); - } else { + eventTimes_.setm(cc + 6 - isDoubleSpeed()); + } + else { update(cc + 2); ppu_.updateWy2(); mode3CyclesChange(); @@ -410,13 +411,13 @@ void LCD::wyChange(unsigned const newValue, unsigned long const cc) { } void LCD::scxChange(unsigned newScx, unsigned long cycleCounter) { - update(cycleCounter + ppu_.cgb() + isDoubleSpeed()); + update(cycleCounter + 2 * ppu_.cgb()); ppu_.setScx(newScx); mode3CyclesChange(); } void LCD::scyChange(unsigned newValue, unsigned long cycleCounter) { - update(cycleCounter + ppu_.cgb() + isDoubleSpeed()); + update(cycleCounter + 2 * ppu_.cgb()); ppu_.setScy(newValue); } @@ -438,18 +439,14 @@ void LCD::oamChange(unsigned char const *oamram, unsigned long cc) { void LCD::lcdcChange(unsigned const data, unsigned long const cc) { unsigned const oldLcdc = ppu_.lcdc(); - update(cc); if ((oldLcdc ^ data) & lcdc_en) { + update(cc); ppu_.setLcdc(data, cc); if (data & lcdc_en) { lycIrq_.lcdReset(); - m0Irq_.lcdReset(statReg_, lycIrq_.lycReg()); - - if (lycIrq_.lycReg() == 0 && (statReg_ & lcdstat_lycirqen)) - eventTimes_.flagIrq(2); - + mstatIrq_.lcdReset(lycIrq_.lycReg()); nextM0Time_.predictNextM0Time(ppu_); lycIrq_.reschedule(ppu_.lyCounter(), cc); @@ -457,48 +454,49 @@ void LCD::lcdcChange(unsigned const data, unsigned long const cc) { eventTimes_.setm( SpriteMapper::schedule(ppu_.lyCounter(), cc)); eventTimes_.setm(lycIrq_.time()); - eventTimes_.setm( - ppu_.lyCounter().nextFrameCycle(144 * 456, cc) - 2); + eventTimes_.setm(mode1IrqSchedule(ppu_.lyCounter(), cc)); eventTimes_.setm( mode2IrqSchedule(statReg_, ppu_.lyCounter(), cc)); if (statReg_ & lcdstat_m0irqen) { - eventTimes_.setm(m0IrqTimeFromXpos166Time( - ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed())); + eventTimes_.setm(ppu_.predictedNextXposTime(lcd_hres + 6)); } if (hdmaIsEnabled()) { - eventTimes_.setm(nextHdmaTime(ppu_.lastM0Time(), - nextM0Time_.predictedNextM0Time(), cc, isDoubleSpeed())); + eventTimes_.setm(nextM0Time_.predictedNextM0Time()); } - } else for (int i = 0; i < num_memevents; ++i) + } + else for (int i = 0; i < num_memevents; ++i) eventTimes_.set(MemEvent(i), disabled_time); - } else if (data & lcdc_en) { + } + else if (data & lcdc_en) { if (ppu_.cgb()) { - ppu_.setLcdc( (oldLcdc & ~(lcdc_tdsel | lcdc_obj2x)) - | (data & (lcdc_tdsel | lcdc_obj2x)), cc); - + update(cc + 1); + ppu_.setLcdc((oldLcdc & ~(1u * lcdc_tdsel)) | (data & lcdc_tdsel), cc + 1); + update(cc + 2); + ppu_.setLcdc(data, cc + 2); if ((oldLcdc ^ data) & lcdc_obj2x) { - unsigned long t = SpriteMapper::schedule(ppu_.lyCounter(), cc); + unsigned long t = SpriteMapper::schedule(ppu_.lyCounter(), cc + 2); eventTimes_.setm(t); } - - update(cc + isDoubleSpeed() + 1); - ppu_.setLcdc(data, cc + isDoubleSpeed() + 1); - if ((oldLcdc ^ data) & lcdc_we) mode3CyclesChange(); - } else { - ppu_.setLcdc(data, cc); - + } + else { + update(cc); + ppu_.setLcdc((oldLcdc & lcdc_obj2x) | (data & ~(1u * lcdc_obj2x)), cc); if ((oldLcdc ^ data) & lcdc_obj2x) { - unsigned long t = SpriteMapper::schedule(ppu_.lyCounter(), cc); + update(cc + 2); + ppu_.setLcdc(data, cc + 2); + unsigned long t = SpriteMapper::schedule(ppu_.lyCounter(), cc + 2); eventTimes_.setm(t); } - if ((oldLcdc ^ data) & (lcdc_we | lcdc_objen)) mode3CyclesChange(); } - } else + } + else { + update(cc); ppu_.setLcdc(data, cc); + } } namespace { @@ -508,98 +506,116 @@ struct LyCnt { LyCnt(unsigned ly, int timeToNextLy) : ly(ly), timeToNextLy(timeToNextLy) {} }; -static LyCnt const getLycCmpLy(LyCounter const &lyCounter, unsigned long cc) { +LyCnt const getLycCmpLy(LyCounter const &lyCounter, unsigned long cc) { unsigned ly = lyCounter.ly(); int timeToNextLy = lyCounter.time() - cc; - if (ly == 153) { - if (timeToNextLy - (448 << lyCounter.isDoubleSpeed()) > 0) { - timeToNextLy -= (448 << lyCounter.isDoubleSpeed()); - } else { - ly = 0; - timeToNextLy += lyCounter.lineTime(); - } + if (ly == lcd_lines_per_frame - 1) { + int const lineTime = lyCounter.lineTime(); + if ((timeToNextLy -= (lineTime - 6 - 6 * lyCounter.isDoubleSpeed())) <= 0) + ly = 0, timeToNextLy += lineTime; } + else if ((timeToNextLy -= (2 + 2 * lyCounter.isDoubleSpeed())) <= 0) + ++ly, timeToNextLy += lyCounter.lineTime(); return LyCnt(ly, timeToNextLy); } -} // anon ns +bool statChangeTriggersM2IrqCgb(unsigned const old, + unsigned const data, int const ly, int const timeToNextLy, bool const ds) { + if ((old & lcdstat_m2irqen) + || (data & (lcdstat_m2irqen | lcdstat_m0irqen)) != lcdstat_m2irqen) { + return false; + } + if (ly < lcd_vres - 1) + return timeToNextLy <= (lcd_cycles_per_line - mode2_irq_line_cycle) * (1 + ds) && timeToNextLy > 2; + if (ly == lcd_vres - 1) + return timeToNextLy <= (lcd_cycles_per_line - mode2_irq_line_cycle) * (1 + ds) && timeToNextLy > 4 + 2 * ds; + if (ly == lcd_lines_per_frame - 1) + return timeToNextLy <= (lcd_cycles_per_line - mode2_irq_line_cycle_ly0) * (1 + ds) && timeToNextLy > 2; + return false; +} + +unsigned incLy(unsigned ly) { return ly == lcd_lines_per_frame - 1 ? 0 : ly + 1; } + +} // unnamed namespace. inline bool LCD::statChangeTriggersStatIrqDmg(unsigned const old, unsigned long const cc) { LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc); - if (ppu_.lyCounter().ly() < 144) { - if (cc + 1 < m0TimeOfCurrentLine(cc)) + if (ppu_.lyCounter().ly() < lcd_vres) { + int const m0_cycles_upper_bound = lcd_cycles_per_line - 80 - 160; + unsigned long m0IrqTime = eventTimes_(memevent_m0irq); + if (m0IrqTime == disabled_time && ppu_.lyCounter().time() - cc < m0_cycles_upper_bound) { + update(cc); + m0IrqTime = ppu_.predictedNextXposTime(lcd_hres + 6); + } + if (m0IrqTime == disabled_time || m0IrqTime < ppu_.lyCounter().time()) return lycCmp.ly == lycIrq_.lycReg() && !(old & lcdstat_lycirqen); return !(old & lcdstat_m0irqen) - && !(lycCmp.ly == lycIrq_.lycReg() && (old & lcdstat_lycirqen)); + && !(lycCmp.ly == lycIrq_.lycReg() && (old & lcdstat_lycirqen)); } return !(old & lcdstat_m1irqen) - && !(lycCmp.ly == lycIrq_.lycReg() && (old & lcdstat_lycirqen)); -} - -static bool statChangeTriggersM2IrqCgb(unsigned const old, - unsigned const data, unsigned const ly, int const timeToNextLy) { - if ((old & lcdstat_m2irqen) - || (data & (lcdstat_m2irqen | lcdstat_m0irqen)) != lcdstat_m2irqen - || ly >= 144) { - return false; - } - - return timeToNextLy == 456 * 2 - || (timeToNextLy <= 4 && ly < 143); + && !(lycCmp.ly == lycIrq_.lycReg() && (old & lcdstat_lycirqen)); } inline bool LCD::statChangeTriggersM0LycOrM1StatIrqCgb( - unsigned const old, unsigned const data, unsigned long const cc) { - unsigned const ly = ppu_.lyCounter().ly(); + unsigned const old, unsigned const data, bool const lycperiod, + unsigned long const cc) { + int const ly = ppu_.lyCounter().ly(); int const timeToNextLy = ppu_.lyCounter().time() - cc; - LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc); - bool const lycperiod = lycCmp.ly == lycIrq_.lycReg() - && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4; - if (lycperiod && (old & lcdstat_lycirqen)) - return false; + bool const ds = isDoubleSpeed(); + int const m1_irq_lc_inv = lcd_cycles_per_line - mode1_irq_frame_cycle % lcd_cycles_per_line; - if (ly < 144) { - if (cc + isDoubleSpeed() * 2 < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4) + if (ly < lcd_vres - 1 || (ly == lcd_vres - 1 && timeToNextLy > m1_irq_lc_inv* (1 + ds))) { + if (eventTimes_(memevent_m0irq) < ppu_.lyCounter().time() + || timeToNextLy <= (ly < lcd_vres - 1 ? 4 + 4 * ds : 4 + 2 * ds)) { return lycperiod && (data & lcdstat_lycirqen); + } if (old & lcdstat_m0irqen) return false; return (data & lcdstat_m0irqen) - || (lycperiod && (data & lcdstat_lycirqen)); + || (lycperiod && (data & lcdstat_lycirqen)); } - if (old & lcdstat_m1irqen) + if (old & lcdstat_m1irqen && (ly < lcd_lines_per_frame - 1 || timeToNextLy > 3 + 3 * ds)) return false; - return ((data & lcdstat_m1irqen) && (ly < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4)) - || (lycperiod && (data & lcdstat_lycirqen)); + return ((data & lcdstat_m1irqen) + && (ly < lcd_lines_per_frame - 1 || timeToNextLy > 4 + 2 * ds)) + || (lycperiod && (data & lcdstat_lycirqen)); } inline bool LCD::statChangeTriggersStatIrqCgb( - unsigned const old, unsigned const data, unsigned long const cc) { - if (!(data & ~old & ( lcdstat_lycirqen - | lcdstat_m2irqen - | lcdstat_m1irqen - | lcdstat_m0irqen))) { + unsigned const old, unsigned const data, unsigned long const cc) { + if (!(data & ~old & (lcdstat_lycirqen + | lcdstat_m2irqen + | lcdstat_m1irqen + | lcdstat_m0irqen))) { return false; } - unsigned const ly = ppu_.lyCounter().ly(); + int const ly = ppu_.lyCounter().ly(); int const timeToNextLy = ppu_.lyCounter().time() - cc; - return statChangeTriggersM0LycOrM1StatIrqCgb(old, data, cc) - || statChangeTriggersM2IrqCgb(old, data, ly, timeToNextLy); + LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc); + bool const lycperiod = lycCmp.ly == lycIrq_.lycReg() + && lycCmp.timeToNextLy > 2; + if (lycperiod && (old & lcdstat_lycirqen)) + return false; + + return statChangeTriggersM0LycOrM1StatIrqCgb(old, data, lycperiod, cc) + || statChangeTriggersM2IrqCgb(old, data, ly, timeToNextLy, isDoubleSpeed()); } + inline bool LCD::statChangeTriggersStatIrq(unsigned old, unsigned data, unsigned long cc) { return ppu_.cgb() - ? statChangeTriggersStatIrqCgb(old, data, cc) - : statChangeTriggersStatIrqDmg(old, cc); + ? statChangeTriggersStatIrqCgb(old, data, cc) + : statChangeTriggersStatIrqDmg(old, cc); } void LCD::lcdstatChange(unsigned const data, unsigned long const cc) { @@ -611,56 +627,45 @@ void LCD::lcdstatChange(unsigned const data, unsigned long const cc) { lycIrq_.statRegChange(data, ppu_.lyCounter(), cc); if (ppu_.lcdc() & lcdc_en) { - if (statChangeTriggersStatIrq(old, data, cc)) - eventTimes_.flagIrq(2); - if ((data & lcdstat_m0irqen) && eventTimes_(memevent_m0irq) == disabled_time) { update(cc); - eventTimes_.setm(m0IrqTimeFromXpos166Time( - ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed())); + eventTimes_.setm(ppu_.predictedNextXposTime(lcd_hres + 6)); } eventTimes_.setm(mode2IrqSchedule(data, ppu_.lyCounter(), cc)); eventTimes_.setm(lycIrq_.time()); + + if (statChangeTriggersStatIrq(old, data, cc)) + eventTimes_.flagIrq(2); } - m2IrqStatReg_ = eventTimes_(memevent_m2irq) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U - ? data - : (m2IrqStatReg_ & lcdstat_m1irqen) | (statReg_ & ~lcdstat_m1irqen); - m1IrqStatReg_ = eventTimes_(memevent_m1irq) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U - ? data - : (m1IrqStatReg_ & lcdstat_m0irqen) | (statReg_ & ~lcdstat_m0irqen); - - m0Irq_.statRegChange(data, eventTimes_(memevent_m0irq), cc, ppu_.cgb()); + mstatIrq_.statRegChange(data, eventTimes_(memevent_m0irq), eventTimes_(memevent_m1irq), + eventTimes_(memevent_m2irq), cc, ppu_.cgb()); } -static unsigned incLy(unsigned ly) { return ly == 153 ? 0 : ly + 1; } - -inline bool LCD::lycRegChangeStatTriggerBlockedByM0OrM1Irq(unsigned long const cc) { +inline bool LCD::lycRegChangeStatTriggerBlockedByM0OrM1Irq(unsigned data, unsigned long cc) { int const timeToNextLy = ppu_.lyCounter().time() - cc; - if (ppu_.lyCounter().ly() < 144) { + if (ppu_.lyCounter().ly() < lcd_vres) { return (statReg_ & lcdstat_m0irqen) - && cc >= m0TimeOfCurrentLine(cc) - && timeToNextLy > 4 << ppu_.cgb(); + && eventTimes_(memevent_m0irq) > ppu_.lyCounter().time() + && data == ppu_.lyCounter().ly(); } return (statReg_ & lcdstat_m1irqen) - && !(ppu_.lyCounter().ly() == 153 - && timeToNextLy <= 4 - && ppu_.cgb() && !isDoubleSpeed()); + && !(ppu_.lyCounter().ly() == lcd_lines_per_frame - 1 + && timeToNextLy <= 2 + 2 * isDoubleSpeed() + 2 * ppu_.cgb()); } bool LCD::lycRegChangeTriggersStatIrq( - unsigned const old, unsigned const data, unsigned long const cc) { - if (!(statReg_ & lcdstat_lycirqen) || data >= 154 - || lycRegChangeStatTriggerBlockedByM0OrM1Irq(cc)) { + unsigned const old, unsigned const data, unsigned long const cc) { + if (!(statReg_ & lcdstat_lycirqen) || data >= lcd_lines_per_frame + || lycRegChangeStatTriggerBlockedByM0OrM1Irq(data, cc)) { return false; } LyCnt lycCmp = getLycCmpLy(ppu_.lyCounter(), cc); - if (lycCmp.timeToNextLy <= 4 << ppu_.cgb()) { - bool const ds = isDoubleSpeed(); - if (old == lycCmp.ly && !(lycCmp.timeToNextLy <= 4 && ppu_.cgb() && !ds)) + if (lycCmp.timeToNextLy <= 4 + 4 * isDoubleSpeed() + 2 * ppu_.cgb()) { + if (old == lycCmp.ly && lycCmp.timeToNextLy > 2 * ppu_.cgb()) return false; // simultaneous ly/lyc inc. lyc flag never goes low -> no trigger. lycCmp.ly = incLy(lycCmp.ly); @@ -677,8 +682,9 @@ void LCD::lycRegChange(unsigned const data, unsigned long const cc) { if (cc >= eventTimes_.nextEventTime()) update(cc); - m0Irq_.lycRegChange(data, eventTimes_(memevent_m0irq), cc, isDoubleSpeed(), ppu_.cgb()); lycIrq_.lycRegChange(data, ppu_.lyCounter(), cc); + mstatIrq_.lycRegChange(data, eventTimes_(memevent_m0irq), + eventTimes_(memevent_m2irq), cc, isDoubleSpeed(), ppu_.cgb()); if (ppu_.lcdc() & lcdc_en) { eventTimes_.setm(lycIrq_.time()); @@ -686,7 +692,8 @@ void LCD::lycRegChange(unsigned const data, unsigned long const cc) { if (lycRegChangeTriggersStatIrq(old, data, cc)) { if (ppu_.cgb() && !isDoubleSpeed()) { eventTimes_.setm(cc + 5); - } else + } + else eventTimes_.flagIrq(2); } } @@ -701,61 +708,51 @@ unsigned LCD::getStat(unsigned const lycReg, unsigned long const cc) { unsigned const ly = ppu_.lyCounter().ly(); int const timeToNextLy = ppu_.lyCounter().time() - cc; - if (ly > 143) { - if (ly < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4) + int const lineCycles = lcd_cycles_per_line - (timeToNextLy >> isDoubleSpeed()); + long const frameCycles = 1l * ly * lcd_cycles_per_line + lineCycles; + if (frameCycles >= lcd_vres * lcd_cycles_per_line - 3 && frameCycles < lcd_cycles_per_frame - 3) { + if (frameCycles >= lcd_vres * lcd_cycles_per_line - 2 + && frameCycles < lcd_cycles_per_frame - 4 + isDoubleSpeed()) { stat = 1; - } else { - int const lineCycles = 456 - (timeToNextLy >> isDoubleSpeed()); - if (lineCycles < 80) { - if (!ppu_.inactivePeriodAfterDisplayEnable(cc)) - stat = 2; - } else if (cc + isDoubleSpeed() - ppu_.cgb() + 2 < m0TimeOfCurrentLine(cc)) + } + } + else if (lineCycles < 77 || lineCycles >= lcd_cycles_per_line - 3) { + if (!ppu_.inactivePeriodAfterDisplayEnable(cc + 1)) + stat = 2; + } + else if (cc + 2 < m0TimeOfCurrentLine(cc)) { + if (!ppu_.inactivePeriodAfterDisplayEnable(cc + 1)) stat = 3; } LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc); - if (lycReg == lycCmp.ly && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4) + if (lycReg == lycCmp.ly && lycCmp.timeToNextLy > 2) stat |= lcdstat_lycflag; } return stat; } -static bool isMode2IrqEventBlockedByM1Irq(unsigned ly, unsigned statreg) { - return ly == 0 && (statreg & lcdstat_m1irqen); -} - -static bool isMode2IrqEventBlockedByLycIrq(unsigned ly, unsigned statreg, unsigned lycreg) { - return (statreg & lcdstat_lycirqen) - && (ly == 0 ? ly : ly - 1) == lycreg; -} - -static bool isMode2IrqEventBlocked(unsigned ly, unsigned statreg, unsigned lycreg) { - return isMode2IrqEventBlockedByM1Irq(ly, statreg) - || isMode2IrqEventBlockedByLycIrq(ly, statreg, lycreg); -} - inline void LCD::doMode2IrqEvent() { - unsigned const ly = eventTimes_(event_ly) - eventTimes_(memevent_m2irq) < 8 - ? incLy(ppu_.lyCounter().ly()) - : ppu_.lyCounter().ly(); - if (!isMode2IrqEventBlocked(ly, m2IrqStatReg_, lycIrq_.lycReg())) - eventTimes_.flagIrq(2); - - m2IrqStatReg_ = statReg_; + unsigned const ly = eventTimes_(event_ly) - eventTimes_(memevent_m2irq) < 16 + ? incLy(ppu_.lyCounter().ly()) + : ppu_.lyCounter().ly(); + if (mstatIrq_.doM2Event(ly, statReg_, lycIrq_.lycReg())) + eventTimes_.flagIrq(2, eventTimes_(memevent_m2irq)); + bool const ds = isDoubleSpeed(); + unsigned long next = lcd_cycles_per_frame; if (!(statReg_ & lcdstat_m0irqen)) { - unsigned long nextTime = eventTimes_(memevent_m2irq) + ppu_.lyCounter().lineTime(); + next = lcd_cycles_per_line; if (ly == 0) { - nextTime -= 4; - } else if (ly == 143) - nextTime += ppu_.lyCounter().lineTime() * 10 + 4; - - eventTimes_.setm(nextTime); - } else { - eventTimes_.setm(eventTimes_(memevent_m2irq) - + (70224 << isDoubleSpeed())); + next -= mode2_irq_line_cycle_ly0 - mode2_irq_line_cycle; + } + else if (ly == lcd_vres) { + next += lcd_cycles_per_line * (lcd_lines_per_frame - lcd_vres - 1) + + mode2_irq_line_cycle_ly0 - mode2_irq_line_cycle; + } } + eventTimes_.setm(eventTimes_(memevent_m2irq) + (next << ds)); } inline void LCD::event() { @@ -763,22 +760,18 @@ inline void LCD::event() { case event_mem: switch (eventTimes_.nextMemEvent()) { case memevent_m1irq: - eventTimes_.flagIrq((m1IrqStatReg_ & (lcdstat_m1irqen | lcdstat_m0irqen)) - == lcdstat_m1irqen - ? 3 - : 1); - m1IrqStatReg_ = statReg_; + eventTimes_.flagIrq(mstatIrq_.doM1Event(statReg_) ? 3 : 1, + eventTimes_(memevent_m1irq)); eventTimes_.setm(eventTimes_(memevent_m1irq) - + (70224 << isDoubleSpeed())); + + (lcd_cycles_per_frame << isDoubleSpeed())); break; - case memevent_lycirq: { - unsigned char ifreg = 0; - lycIrq_.doEvent(&ifreg, ppu_.lyCounter()); - eventTimes_.flagIrq(ifreg); + case memevent_lycirq: + if (lycIrq_.doEvent(ppu_.lyCounter())) + eventTimes_.flagIrq(2, eventTimes_(memevent_lycirq)); + eventTimes_.setm(lycIrq_.time()); break; - } case memevent_spritemap: eventTimes_.setm( @@ -789,8 +782,7 @@ inline void LCD::event() { case memevent_hdma: eventTimes_.flagHdmaReq(); nextM0Time_.predictNextM0Time(ppu_); - eventTimes_.setm(hdmaTimeFromM0Time( - nextM0Time_.predictedNextM0Time(), isDoubleSpeed())); + eventTimes_.setm(nextM0Time_.predictedNextM0Time()); break; case memevent_m2irq: @@ -798,17 +790,12 @@ inline void LCD::event() { break; case memevent_m0irq: - { - unsigned char ifreg = 0; - m0Irq_.doEvent(&ifreg, ppu_.lyCounter().ly(), statReg_, - lycIrq_.lycReg()); - eventTimes_.flagIrq(ifreg); - } + if (mstatIrq_.doM0Event(ppu_.lyCounter().ly(), statReg_, lycIrq_.lycReg())) + eventTimes_.flagIrq(2, eventTimes_(memevent_m0irq)); eventTimes_.setm(statReg_ & lcdstat_m0irqen - ? m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), - ppu_.cgb(), isDoubleSpeed()) - : static_cast(disabled_time)); + ? ppu_.predictedNextXposTime(lcd_hres + 6) + : 1 * disabled_time); break; case memevent_oneshot_statirq: @@ -828,8 +815,6 @@ inline void LCD::event() { case event_ly: ppu_.doLyCountEvent(); eventTimes_.set(ppu_.lyCounter().time()); - if (scanlinecallback && ppu_.lyCounter().ly() == (unsigned)scanlinecallbacksl) - scanlinecallback(); break; } } @@ -867,12 +852,10 @@ SYNCFUNC(LCD) NSS(bgpData_); NSS(objpData_); SSS(eventTimes_); - SSS(m0Irq_); + SSS(mstatIrq_); SSS(lycIrq_); SSS(nextM0Time_); NSS(statReg_); NSS(m2IrqStatReg_); NSS(m1IrqStatReg_); } - -} diff --git a/libgambatte/src/video.h b/libgambatte/src/video.h index abc5ec8f0e..cedd2cf86a 100644 --- a/libgambatte/src/video.h +++ b/libgambatte/src/video.h @@ -22,7 +22,7 @@ #include "interruptrequester.h" #include "minkeeper.h" #include "video/lyc_irq.h" -#include "video/m0_irq.h" +#include "video/mstat_irq.h" #include "video/next_m0_time.h" #include "video/ppu.h" #include "newstate.h" @@ -36,8 +36,9 @@ public: { } - void flagHdmaReq() const { gambatte::flagHdmaReq(intreq_); } + void flagHdmaReq() const { if (!intreq_.halted()) gambatte::flagHdmaReq(intreq_); } void flagIrq(unsigned bit) const { intreq_.flagIrq(bit); } + void flagIrq(unsigned bit, unsigned long cc) const { intreq_.flagIrq(bit, cc); } void setNextEventTime(unsigned long time) const { intreq_.setEventTime(time); } private: @@ -89,17 +90,19 @@ public: } unsigned cgbBgColorRead(unsigned index, unsigned long cycleCounter) { - return ppu_.cgb() & cgbpAccessible(cycleCounter) ? bgpData_[index] : 0xFF; + return ppu_.cgb() && cgbpAccessible(cycleCounter) ? bgpData_[index] : 0xFF; } unsigned cgbSpColorRead(unsigned index, unsigned long cycleCounter) { - return ppu_.cgb() & cgbpAccessible(cycleCounter) ? objpData_[index] : 0xFF; + return ppu_.cgb() && cgbpAccessible(cycleCounter) ? objpData_[index] : 0xFF; } void updateScreen(bool blanklcd, unsigned long cc); + void blackScreen(); void resetCc(unsigned long oldCC, unsigned long newCc); void speedChange(unsigned long cycleCounter); - bool vramAccessible(unsigned long cycleCounter); + bool vramReadable(unsigned long cycleCounter); + bool vramWritable(unsigned long cycleCounter); bool oamReadable(unsigned long cycleCounter); bool oamWritable(unsigned long cycleCounter); void wxChange(unsigned newValue, unsigned long cycleCounter); @@ -119,15 +122,16 @@ public: update(cc); lyReg = ppu_.lyCounter().ly(); - - if (lyReg == 153) { - if (isDoubleSpeed()) { - if (ppu_.lyCounter().time() - cc <= 456 * 2 - 8) - lyReg = 0; - } else + if (lyReg == lcd_lines_per_frame - 1) { + if (ppu_.lyCounter().time() - cc <= 2 * lcd_cycles_per_line - 2) lyReg = 0; - } else if (ppu_.lyCounter().time() - cc <= 4) - ++lyReg; + } + else if (ppu_.lyCounter().time() - cc <= 10 + && ppu_.lyCounter().time() - cc <= 6u + 4 * isDoubleSpeed()) { + lyReg = ppu_.lyCounter().time() - cc == 6u + 4 * isDoubleSpeed() + ? lyReg & (lyReg + 1) + : lyReg + 1; + } } return lyReg; @@ -139,6 +143,7 @@ public: void lycRegChange(unsigned data, unsigned long cycleCounter); void enableHdma(unsigned long cycleCounter); void disableHdma(unsigned long cycleCounter); + bool isHdmaPeriod(unsigned long cycleCounter); bool hdmaIsEnabled() const { return eventTimes_(memevent_hdma) != disabled_time; } void update(unsigned long cycleCounter); bool isCgb() const { return ppu_.cgb(); } @@ -189,6 +194,7 @@ private: void set(MemEvent e, unsigned long time) { memEventMin_.setValue(e, time); setMemEvent(); } void flagIrq(unsigned bit) { memEventRequester_.flagIrq(bit); } + void flagIrq(unsigned bit, unsigned long cc) { memEventRequester_.flagIrq(bit, cc); } void flagHdmaReq() { memEventRequester_.flagHdmaReq(); } private: @@ -214,15 +220,13 @@ public: PPU ppu_; unsigned long dmgColorsRgb32_[3 * 4]; unsigned long cgbColorsRgb32_[32768]; - unsigned char bgpData_[8 * 8]; - unsigned char objpData_[8 * 8]; + unsigned char bgpData_[2 * max_num_palettes * num_palette_entries]; + unsigned char objpData_[2 * max_num_palettes * num_palette_entries]; EventTimes eventTimes_; - M0Irq m0Irq_; + MStatIrqEvent mstatIrq_; LycIrq lycIrq_; NextM0Time nextM0Time_; unsigned char statReg_; - unsigned char m2IrqStatReg_; - unsigned char m1IrqStatReg_; static void setDmgPalette(unsigned long palette[], unsigned long const dmgColors[], @@ -237,9 +241,9 @@ public: void event(); unsigned long m0TimeOfCurrentLine(unsigned long cc); bool cgbpAccessible(unsigned long cycleCounter); - bool lycRegChangeStatTriggerBlockedByM0OrM1Irq(unsigned long cc); + bool lycRegChangeStatTriggerBlockedByM0OrM1Irq(unsigned data, unsigned long cc); bool lycRegChangeTriggersStatIrq(unsigned old, unsigned data, unsigned long cc); - bool statChangeTriggersM0LycOrM1StatIrqCgb(unsigned old, unsigned data, unsigned long cc); + bool statChangeTriggersM0LycOrM1StatIrqCgb(unsigned old, unsigned data, bool lycperiod, unsigned long cc); bool statChangeTriggersStatIrqCgb(unsigned old, unsigned data, unsigned long cc); bool statChangeTriggersStatIrqDmg(unsigned old, unsigned long cc); bool statChangeTriggersStatIrq(unsigned old, unsigned data, unsigned long cc); diff --git a/libgambatte/src/video/ly_counter.cpp b/libgambatte/src/video/ly_counter.cpp index d3c09184c2..1f985a23bc 100644 --- a/libgambatte/src/video/ly_counter.cpp +++ b/libgambatte/src/video/ly_counter.cpp @@ -33,7 +33,7 @@ LyCounter::LyCounter() void LyCounter::doEvent() { ++ly_; - if (ly_ == 154) + if (ly_ == lcd_lines_per_frame) ly_ = 0; time_ = time_ + lineTime_; @@ -48,21 +48,22 @@ unsigned long LyCounter::nextLineCycle(unsigned const lineCycle, unsigned long c } unsigned long LyCounter::nextFrameCycle(unsigned long const frameCycle, unsigned long const cc) const { - unsigned long tmp = time_ + (((153U - ly()) * 456U + frameCycle) << ds_); - if (tmp - cc > 70224U << ds_) - tmp -= 70224U << ds_; + unsigned long tmp = time_ + (((lcd_lines_per_frame - 1l - ly()) * lcd_cycles_per_line + frameCycle) << ds_); + if (tmp - cc > 1ul * lcd_cycles_per_frame << ds_) + tmp -= 1ul * lcd_cycles_per_frame << ds_; return tmp; } void LyCounter::reset(unsigned long videoCycles, unsigned long lastUpdate) { - ly_ = videoCycles / 456; - time_ = lastUpdate + ((456 - (videoCycles - ly_ * 456ul)) << isDoubleSpeed()); + ly_ = videoCycles / lcd_cycles_per_line; + time_ = lastUpdate + ((lcd_cycles_per_line + - (videoCycles - 1l * ly_ * lcd_cycles_per_line)) << isDoubleSpeed()); } void LyCounter::setDoubleSpeed(bool ds) { ds_ = ds; - lineTime_ = 456U << ds; + lineTime_ = lcd_cycles_per_line << ds; } SYNCFUNC(LyCounter) diff --git a/libgambatte/src/video/ly_counter.h b/libgambatte/src/video/ly_counter.h index 53a7cefee3..5700d052b1 100644 --- a/libgambatte/src/video/ly_counter.h +++ b/libgambatte/src/video/ly_counter.h @@ -20,6 +20,7 @@ #define LY_COUNTER_H #include "newstate.h" +#include "lcddef.h" namespace gambatte { @@ -32,11 +33,11 @@ public: bool isDoubleSpeed() const { return ds_; } unsigned long frameCycles(unsigned long cc) const { - return ly_ * 456ul + lineCycles(cc); + return 1l * ly_ * lcd_cycles_per_line + lineCycles(cc); } unsigned lineCycles(unsigned long cc) const { - return 456u - ((time_ - cc) >> isDoubleSpeed()); + return lcd_cycles_per_line - ((time_ - cc) >> isDoubleSpeed()); } unsigned lineTime() const { return lineTime_; } diff --git a/libgambatte/src/video/lyc_irq.cpp b/libgambatte/src/video/lyc_irq.cpp index b56a416efa..92ea67ea3c 100644 --- a/libgambatte/src/video/lyc_irq.cpp +++ b/libgambatte/src/video/lyc_irq.cpp @@ -23,7 +23,26 @@ #include "savestate.h" #include -namespace gambatte { +using namespace gambatte; + +namespace { + + unsigned long schedule(unsigned statReg, + unsigned lycReg, LyCounter const& lyCounter, unsigned long cc) { + return (statReg & lcdstat_lycirqen) && lycReg < lcd_lines_per_frame + ? lyCounter.nextFrameCycle(lycReg + ? 1l * lycReg * lcd_cycles_per_line - 2 + : (lcd_lines_per_frame - 1l) * lcd_cycles_per_line + 6, cc) + : 1 * disabled_time; + } + + bool lycIrqBlockedByM2OrM1StatIrq(unsigned ly, unsigned statreg) { + return ly <= lcd_vres && ly > 0 + ? statreg & lcdstat_m2irqen + : statreg & lcdstat_m1irqen; + } + +} LycIrq::LycIrq() : time_(disabled_time) @@ -35,53 +54,40 @@ LycIrq::LycIrq() { } -static unsigned long schedule(unsigned statReg, - unsigned lycReg, LyCounter const &lyCounter, unsigned long cc) { - return (statReg & lcdstat_lycirqen) && lycReg < 154 - ? lyCounter.nextFrameCycle(lycReg ? lycReg * 456 : 153 * 456 + 8, cc) - : static_cast(disabled_time); -} - void LycIrq::regChange(unsigned const statReg, - unsigned const lycReg, LyCounter const &lyCounter, unsigned long const cc) { + unsigned const lycReg, LyCounter const& lyCounter, unsigned long const cc) { unsigned long const timeSrc = schedule(statReg, lycReg, lyCounter, cc); statRegSrc_ = statReg; lycRegSrc_ = lycReg; time_ = std::min(time_, timeSrc); if (cgb_) { - if (time_ - cc > 8 || (timeSrc != time_ && time_ - cc > 4U - lyCounter.isDoubleSpeed() * 4U)) + if (time_ - cc > 6u + 4 * lyCounter.isDoubleSpeed() || (timeSrc != time_ && time_ - cc > 2)) lycReg_ = lycReg; - - if (time_ - cc > 4U - lyCounter.isDoubleSpeed() * 4U) + if (time_ - cc > 2) statReg_ = statReg; - } else { + } + else { if (time_ - cc > 4 || timeSrc != time_) lycReg_ = lycReg; - if (time_ - cc > 4 || lycReg_ != 0) - statReg_ = statReg; - - statReg_ = (statReg_ & lcdstat_lycirqen) | (statReg & ~lcdstat_lycirqen); + statReg_ = statReg; } } -static bool lycIrqBlockedByM2OrM1StatIrq(unsigned ly, unsigned statreg) { - return ly - 1u < 144u - 1u - ? statreg & lcdstat_m2irqen - : statreg & lcdstat_m1irqen; -} - -void LycIrq::doEvent(unsigned char *const ifreg, LyCounter const &lyCounter) { +bool LycIrq::doEvent(LyCounter const& lyCounter) { + bool flagIrq = false; if ((statReg_ | statRegSrc_) & lcdstat_lycirqen) { - unsigned cmpLy = lyCounter.time() - time_ < lyCounter.lineTime() ? 0 : lyCounter.ly(); - if (lycReg_ == cmpLy && !lycIrqBlockedByM2OrM1StatIrq(lycReg_, statReg_)) - *ifreg |= 2; + unsigned const cmpLy = lyCounter.ly() == lcd_lines_per_frame - 1 + ? 0 + : lyCounter.ly() + 1; + flagIrq = lycReg_ == cmpLy && !lycIrqBlockedByM2OrM1StatIrq(lycReg_, statReg_); } lycReg_ = lycRegSrc_; statReg_ = statRegSrc_; time_ = schedule(statReg_, lycReg_, lyCounter, time_); + return flagIrq; } void LycIrq::loadState(SaveState const &state) { @@ -110,5 +116,3 @@ SYNCFUNC(LycIrq) NSS(statReg_); NSS(cgb_); } - -} diff --git a/libgambatte/src/video/lyc_irq.h b/libgambatte/src/video/lyc_irq.h index 1f35ce47a5..92958a9e36 100644 --- a/libgambatte/src/video/lyc_irq.h +++ b/libgambatte/src/video/lyc_irq.h @@ -29,7 +29,7 @@ class LyCounter; class LycIrq { public: LycIrq(); - void doEvent(unsigned char *ifreg, LyCounter const &lyCounter); + bool doEvent(LyCounter const& lyCounter); unsigned lycReg() const { return lycRegSrc_; } void loadState(SaveState const &state); unsigned long time() const { return time_; } diff --git a/libgambatte/src/video/m0_irq.h b/libgambatte/src/video/m0_irq.h deleted file mode 100644 index 8ea69528db..0000000000 --- a/libgambatte/src/video/m0_irq.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef M0_IRQ_H -#define M0_IRQ_H - -#include "lcddef.h" -#include "../savestate.h" -#include "../newstate.h" - -namespace gambatte { - -class M0Irq { -public: - M0Irq() - : statReg_(0) - , lycReg_(0) - { - } - - void lcdReset(unsigned statReg, unsigned lycReg) { - statReg_ = statReg; - lycReg_ = lycReg; - } - - void statRegChange(unsigned statReg, - unsigned long nextM0IrqTime, unsigned long cc, bool cgb) { - if (nextM0IrqTime - cc > cgb * 2U) - statReg_ = statReg; - } - - void lycRegChange(unsigned lycReg, - unsigned long nextM0IrqTime, unsigned long cc, - bool ds, bool cgb) { - if (nextM0IrqTime - cc > cgb * 5 + 1U - ds) - lycReg_ = lycReg; - } - - void doEvent(unsigned char *ifreg, unsigned ly, unsigned statReg, unsigned lycReg) { - if (((statReg_ | statReg) & lcdstat_m0irqen) - && (!(statReg_ & lcdstat_lycirqen) || ly != lycReg_)) { - *ifreg |= 2; - } - - statReg_ = statReg; - lycReg_ = lycReg; - } - - void loadState(SaveState const &state) { - lycReg_ = state.ppu.m0lyc; - statReg_ = state.mem.ioamhram.get()[0x141]; - } - - unsigned statReg() const { return statReg_; } - -private: - unsigned char statReg_; - unsigned char lycReg_; - -public: - template - void SyncState(NewState *ns) - { - NSS(statReg_); - NSS(lycReg_); - } -}; - -} - -#endif diff --git a/libgambatte/src/video/mstat_irq.h b/libgambatte/src/video/mstat_irq.h new file mode 100644 index 0000000000..d00d1fa038 --- /dev/null +++ b/libgambatte/src/video/mstat_irq.h @@ -0,0 +1,72 @@ +#ifndef M0_IRQ_H +#define M0_IRQ_H + +#include "lcddef.h" +#include "../savestate.h" +#include "../newstate.h" + +namespace gambatte { + +class MStatIrqEvent { +public: + MStatIrqEvent() : lycReg_(0), statReg_(0) {} + void lcdReset(unsigned lycReg) { lycReg_ = lycReg; } + + void lycRegChange(unsigned lycReg, unsigned long nextM0IrqTime, + unsigned long nextM2IrqTime, unsigned long cc, bool ds, bool cgb) { + if (cc + 5 * cgb + 1 - ds < std::min(nextM0IrqTime, nextM2IrqTime)) + lycReg_ = lycReg; + } + + void statRegChange(unsigned statReg, unsigned long nextM0IrqTime, unsigned long nextM1IrqTime, + unsigned long nextM2IrqTime, unsigned long cc, bool cgb) { + if (cc + 2 * cgb < std::min(std::min(nextM0IrqTime, nextM1IrqTime), nextM2IrqTime)) + statReg_ = statReg; + } + + bool doM0Event(unsigned ly, unsigned statReg, unsigned lycReg) { + bool const flagIrq = ((statReg | statReg_) & lcdstat_m0irqen) + && (!(statReg_ & lcdstat_lycirqen) || ly != lycReg_); + lycReg_ = lycReg; + statReg_ = statReg; + return flagIrq; + } + + bool doM1Event(unsigned statReg) { + bool const flagIrq = (statReg & lcdstat_m1irqen) + && !(statReg_ & (lcdstat_m2irqen | lcdstat_m0irqen)); + statReg_ = statReg; + return flagIrq; + } + + bool doM2Event(unsigned ly, unsigned statReg, unsigned lycReg) { + bool const blockedByM1Irq = ly == 0 && (statReg_ & lcdstat_m1irqen); + bool const blockedByLycIrq = (statReg_ & lcdstat_lycirqen) + && (ly == 0 ? ly : ly - 1) == lycReg_; + bool const flagIrq = !blockedByM1Irq && !blockedByLycIrq; + lycReg_ = lycReg; + statReg_ = statReg; + return flagIrq; + } + + void loadState(SaveState const& state) { + lycReg_ = state.ppu.m0lyc; + statReg_ = state.mem.ioamhram.get()[0x141]; + } + +private: + unsigned char statReg_; + unsigned char lycReg_; + +public: + template + void SyncState(NewState *ns) + { + NSS(statReg_); + NSS(lycReg_); + } +}; + +} + +#endif diff --git a/libgambatte/src/video/next_m0_time.cpp b/libgambatte/src/video/next_m0_time.cpp index 9a853c8c38..008e3ba595 100644 --- a/libgambatte/src/video/next_m0_time.cpp +++ b/libgambatte/src/video/next_m0_time.cpp @@ -2,7 +2,7 @@ #include "ppu.h" void gambatte::NextM0Time::predictNextM0Time(PPU const &ppu) { - predictedNextM0Time_ = ppu.predictedNextXposTime(167); + predictedNextM0Time_ = ppu.predictedNextXposTime(lcd_hres + 7); } SYNCFUNC(gambatte::NextM0Time) diff --git a/libgambatte/src/video/next_m0_time.h b/libgambatte/src/video/next_m0_time.h index 547de18608..110c0a489b 100644 --- a/libgambatte/src/video/next_m0_time.h +++ b/libgambatte/src/video/next_m0_time.h @@ -10,10 +10,10 @@ public: NextM0Time() : predictedNextM0Time_(0) {} void predictNextM0Time(class PPU const &v); void invalidatePredictedNextM0Time() { predictedNextM0Time_ = 0; } - unsigned predictedNextM0Time() const { return predictedNextM0Time_; } + unsigned long predictedNextM0Time() const { return predictedNextM0Time_; } private: - unsigned predictedNextM0Time_; + unsigned long predictedNextM0Time_; public: templatevoid SyncState(NewState *ns); diff --git a/libgambatte/src/video/ppu.cpp b/libgambatte/src/video/ppu.cpp index 514beee00c..257a7906a3 100644 --- a/libgambatte/src/video/ppu.cpp +++ b/libgambatte/src/video/ppu.cpp @@ -19,13 +19,14 @@ #include "ppu.h" #include "savestate.h" #include -#include #include -namespace { +#include using namespace gambatte; +namespace { + #define PREP(u8) (((u8) << 7 & 0x80) | ((u8) << 5 & 0x40) | ((u8) << 3 & 0x20) | ((u8) << 1 & 0x10) \ | ((u8) >> 1 & 0x08) | ((u8) >> 3 & 0x04) | ((u8) >> 5 & 0x02) | ((u8) >> 7 & 0x01)) @@ -44,7 +45,7 @@ using namespace gambatte; EXPAND_ROW(0x80), EXPAND_ROW(0x90), EXPAND_ROW(0xA0), EXPAND_ROW(0xB0), \ EXPAND_ROW(0xC0), EXPAND_ROW(0xD0), EXPAND_ROW(0xE0), EXPAND_ROW(0xF0) -static unsigned short const expand_lut[0x200] = { +unsigned short const expand_lut[0x200] = { EXPAND_TABLE, #undef PREP @@ -60,9 +61,9 @@ static unsigned short const expand_lut[0x200] = { #define DECLARE_FUNC(n, id) \ enum { ID##n = id }; \ - static void f##n (PPUPriv &); \ - static unsigned predictCyclesUntilXpos_f##n (PPUPriv const &, int targetxpos, unsigned cycles); \ - static PPUState const f##n##_ = { f##n, predictCyclesUntilXpos_f##n, ID##n } + void f##n (PPUPriv &); \ + unsigned predictCyclesUntilXpos_f##n (PPUPriv const &, int targetxpos, unsigned cycles); \ + PPUState const f##n##_ = { f##n, predictCyclesUntilXpos_f##n, ID##n } namespace M2_Ly0 { DECLARE_FUNC(0, 0); } namespace M2_LyNon0 { DECLARE_FUNC(0, 0); DECLARE_FUNC(1, 0); } @@ -96,22 +97,39 @@ namespace StartWindowDraw { #undef DECLARE_FUNC +enum { + attr_cgbpalno = 0x07, attr_tdbank = 0x08, attr_dmgpalno = 0x10, attr_xflip = 0x20, + attr_yflip = 0x40, attr_bgpriority = 0x80 +}; enum { win_draw_start = 1, win_draw_started = 2 }; -enum { m2_ds_offset = 3 }; -enum { max_m3start_cycles = 80 }; -enum { attr_yflip = 0x40, attr_bgpriority = 0x80 }; -static inline int lcdcEn( PPUPriv const &p) { return p.lcdc & lcdc_en; } -static inline int lcdcWinEn(PPUPriv const &p) { return (p.lcdc & lcdc_we) && (p.layersMask & layer_mask_window); } -static inline int lcdcObj2x(PPUPriv const &p) { return p.lcdc & lcdc_obj2x; } -static inline int lcdcObjEn(PPUPriv const &p) { return (p.lcdc & lcdc_objen) && (p.layersMask & layer_mask_obj); } -static inline int lcdcBgEn( PPUPriv const &p) { return (p.lcdc & lcdc_bgen) && (p.layersMask & layer_mask_bg); } +int const max_m3start_cycles = 80; +int const tile_bpp = 2; +int const tile_bpp_mask = (1 << tile_bpp) - 1; +int const tile_len = 8; +int const tile_line_size = tile_bpp * tile_len / 8; +int const tile_size = tile_line_size * tile_len; +int const tile_map_begin = 0x1800; +int const tile_map_len = 0x20; +int const tile_map_size = tile_map_len * tile_map_len; +int const tile_pattern_table_size = 0x1000; +int const vram_bank_size = 0x2000; +int const xpos_end = 168; -static inline int weMasterCheckPriorToLyIncLineCycle(bool cgb) { return 450 - cgb; } -static inline int weMasterCheckAfterLyIncLineCycle(bool cgb) { return 454 - cgb; } -static inline int m3StartLineCycle(bool /*cgb*/) { return 83; } +inline int spx(PPUPriv::Sprite const& s) { return s.spx; } -static inline void nextCall(int const cycles, PPUState const &state, PPUPriv &p) { +inline int lcdcEn(PPUPriv const& p) { return p.lcdc & lcdc_en; } +inline int lcdcWinEn(PPUPriv const& p) { return p.lcdc & lcdc_we; } +inline int lcdcObj2x(PPUPriv const& p) { return p.lcdc & lcdc_obj2x; } +inline int lcdcObjEn(PPUPriv const& p) { return p.lcdc & lcdc_objen; } +inline int lcdcBgEn(PPUPriv const& p) { return p.lcdc & lcdc_bgen; } + +inline int weMasterCheckLy0LineCycle(bool cgb) { return 1 + cgb; } +inline int weMasterCheckPriorToLyIncLineCycle(bool /*cgb*/) { return 450; } +inline int weMasterCheckAfterLyIncLineCycle(bool /*cgb*/) { return 454; } +inline int m3StartLineCycle(bool cgb) { return 83 + cgb; } + +inline void nextCall(int const cycles, PPUState const &state, PPUPriv &p) { int const c = p.cycles - cycles; if (c >= 0) { p.cycles = c; @@ -123,24 +141,24 @@ static inline void nextCall(int const cycles, PPUState const &state, PPUPriv &p) } namespace M2_Ly0 { - static void f0(PPUPriv &p) { + void f0(PPUPriv& p) { p.weMaster = lcdcWinEn(p) && 0 == p.wy; p.winYPos = 0xFF; - nextCall(m3StartLineCycle(p.cgb), M3Start::f0_, p); + nextCall(m3StartLineCycle(p.cgb) - weMasterCheckLy0LineCycle(p.cgb), M3Start::f0_, p); } } namespace M2_LyNon0 { - static void f0(PPUPriv &p) { + void f0(PPUPriv& p) { p.weMaster |= lcdcWinEn(p) && p.lyCounter.ly() == p.wy; - nextCall( weMasterCheckAfterLyIncLineCycle(p.cgb) - - weMasterCheckPriorToLyIncLineCycle(p.cgb), f1_, p); + nextCall(weMasterCheckAfterLyIncLineCycle(p.cgb) + - weMasterCheckPriorToLyIncLineCycle(p.cgb), f1_, p); } - static void f1(PPUPriv &p) { + void f1(PPUPriv& p) { p.weMaster |= lcdcWinEn(p) && p.lyCounter.ly() + 1 == p.wy; - nextCall(456 - weMasterCheckAfterLyIncLineCycle(p.cgb) + m3StartLineCycle(p.cgb), - M3Start::f0_, p); + nextCall(lcd_cycles_per_line - weMasterCheckAfterLyIncLineCycle(p.cgb) + m3StartLineCycle(p.cgb), + M3Start::f0_, p); } } @@ -152,7 +170,7 @@ namespace M2 { } }; - static void f0(PPUPriv &p) { + void f0(PPUPriv &p) { std::memset(&p.spLut, 0, sizeof p.spLut); p.reg0 = 0; p.nextSprite = 0; @@ -160,38 +178,40 @@ namespace M2 { f1(p); } - static void f1(PPUPriv &p) { + void f1(PPUPriv &p) { + int const oam_entry_size = 4; + int const oam_size = oam_entry_size * lcd_num_oam_entries; int cycles = p.cycles; unsigned oampos = p.reg0; unsigned nextSprite = p.nextSprite; - unsigned const nly = (p.lyCounter.ly() + 1 == 154 ? 0 : p.lyCounter.ly() + 1) - + ((p.lyCounter.time()-(p.now-p.cycles)) <= 4); + unsigned const nly = (p.lyCounter.ly() + 1 == lcd_lines_per_frame ? 0 : p.lyCounter.ly() + 1) + + (p.lyCounter.time() - (p.now - p.cycles) <= 4); bool const ls = p.spriteMapper.largeSpritesSource(); do { - unsigned const spy = p.spriteMapper.oamram()[oampos ]; - unsigned const spx = p.spriteMapper.oamram()[oampos+1]; + unsigned const spy = p.spriteMapper.oamram()[oampos]; + unsigned const spx = p.spriteMapper.oamram()[oampos + 1]; unsigned const ydiff = spy - nly; - if (ls ? ydiff < 16u : ydiff - 8u < 8u) { + if (ls ? ydiff < 2u * tile_len : ydiff - 1u * tile_len < 1u * tile_len) { p.spriteList[nextSprite].spx = spx; - p.spriteList[nextSprite].line = 15u - ydiff; + p.spriteList[nextSprite].line = 2 * tile_len - 1 - ydiff; p.spriteList[nextSprite].oampos = oampos; - if (++nextSprite == 10) { - cycles -= (0xA0 - 4 - oampos) >> 1; - oampos = 0xA0 - 4; + if (++nextSprite == lcd_max_num_sprites_per_line) { + cycles -= (oam_size - oam_entry_size - oampos) >> 1; + oampos = oam_size - oam_entry_size; } } - oampos += 4; - } while ((cycles-=2) >= 0 && oampos != 0xA0); + oampos += oam_entry_size; + } while ((cycles -= 2) >= 0 && oampos != oam_size); p.reg0 = oampos; p.nextSprite = nextSprite; p.cycles = cycles; - if (oampos == 0xA0) { + if (oampos == oam_size) { insertionSort(p.spriteList, p.spriteList + nextSprite, SpriteLess()); p.spriteList[nextSprite].spx = 0xFF; p.nextSprite = 0; @@ -201,60 +221,74 @@ namespace M2 { } */ -static int loadTileDataByte0(PPUPriv const &p) { +int loadTileDataByte0(PPUPriv const& p) { unsigned const yoffset = p.winDrawState & win_draw_started - ? p.winYPos - : p.scy + p.lyCounter.ly(); + ? p.winYPos + : p.scy + p.lyCounter.ly(); - return p.vram[0x1000 + (p.nattrib << 10 & 0x2000) - - ((p.reg1 * 32 | p.lcdc << 8) & 0x1000) - + p.reg1 * 16 - + ((-(p.nattrib >> 6 & 1) ^ yoffset) & 7) * 2]; + return p.vram[tile_pattern_table_size + + vram_bank_size / attr_tdbank * (p.nattrib & attr_tdbank) + - ((2 * tile_size * p.reg1 | tile_pattern_table_size / lcdc_tdsel * p.lcdc) + & tile_pattern_table_size) + + p.reg1 * tile_size + + ((p.nattrib & attr_yflip ? -1 : 0) ^ yoffset) % tile_len * tile_line_size]; } -static int loadTileDataByte1(PPUPriv const &p) { +int loadTileDataByte1(PPUPriv const& p) { unsigned const yoffset = p.winDrawState & win_draw_started - ? p.winYPos - : p.scy + p.lyCounter.ly(); + ? p.winYPos + : p.scy + p.lyCounter.ly(); - return p.vram[0x1000 + (p.nattrib << 10 & 0x2000) - - ((p.reg1 * 32 | p.lcdc << 8) & 0x1000) - + p.reg1 * 16 - + ((-(p.nattrib >> 6 & 1) ^ yoffset) & 7) * 2 + 1]; + return p.vram[tile_pattern_table_size + + vram_bank_size / attr_tdbank * (p.nattrib & attr_tdbank) + - ((2 * tile_size * p.reg1 | tile_pattern_table_size / lcdc_tdsel * p.lcdc) + & tile_pattern_table_size) + + p.reg1 * tile_size + + ((p.nattrib & attr_yflip ? -1 : 0) ^ yoffset) % tile_len * tile_line_size + 1]; } namespace M3Start { - static void f0(PPUPriv &p) { + void f0(PPUPriv& p) { p.xpos = 0; if ((p.winDrawState & win_draw_start) && lcdcWinEn(p)) { p.winDrawState = win_draw_started; - p.wscx = 8 + (p.scx & 7); + p.wscx = tile_len + p.scx % tile_len; ++p.winYPos; - } else + } + else p.winDrawState = 0; p.nextCallPtr = &f1_; f1(p); } - static void f1(PPUPriv &p) { + void f1(PPUPriv& p) { while (p.xpos < max_m3start_cycles) { - if ((p.xpos & 7) == (p.scx & 7)) + if (p.xpos % tile_len == p.scx % tile_len) break; - switch (p.xpos & 7) { + switch (p.xpos % tile_len) { case 0: if (p.winDrawState & win_draw_started) { - p.reg1 = p.vram[(p.lcdc << 4 & 0x400) + (p.winYPos & 0xF8) * 4 - + (p.wscx >> 3 & 0x1F) + 0x1800]; - p.nattrib = p.vram[(p.lcdc << 4 & 0x400) + (p.winYPos & 0xF8) * 4 - + (p.wscx >> 3 & 0x1F) + 0x3800]; - } else { - p.reg1 = p.vram[((p.lcdc << 7 | p.scx >> 3) & 0x41F) - + ((p.scy + p.lyCounter.ly()) & 0xF8) * 4 + 0x1800]; - p.nattrib = p.vram[((p.lcdc << 7 | p.scx >> 3) & 0x41F) - + ((p.scy + p.lyCounter.ly()) & 0xF8) * 4 + 0x3800]; + p.reg1 = p.vram[tile_map_size / lcdc_wtmsel * (p.lcdc & lcdc_wtmsel) + + tile_map_len / tile_len * (p.winYPos & (0x100 - tile_len)) + + p.wscx / tile_len % tile_map_len + + tile_map_begin]; + p.nattrib = p.vram[tile_map_size / lcdc_wtmsel * (p.lcdc & lcdc_wtmsel) + + tile_map_len / tile_len * (p.winYPos & (0x100 - tile_len)) + + p.wscx / tile_len % tile_map_len + + tile_map_begin + vram_bank_size]; + } + else { + p.reg1 = p.vram[((tile_map_size / lcdc_bgtmsel * p.lcdc | p.scx / tile_len) + & (tile_map_size + tile_map_len - 1)) + + tile_map_len / tile_len * ((p.scy + p.lyCounter.ly()) & (0x100 - tile_len)) + + tile_map_begin]; + p.nattrib = p.vram[((tile_map_size / lcdc_bgtmsel * p.lcdc | p.scx / tile_len) + & (tile_map_size + tile_map_len - 1)) + + tile_map_len / tile_len * ((p.scy + p.lyCounter.ly()) & (0x100 - tile_len)) + + tile_map_begin + vram_bank_size]; } break; @@ -262,13 +296,13 @@ namespace M3Start { p.reg0 = loadTileDataByte0(p); break; case 4: - { - int const r1 = loadTileDataByte1(p); - p.ntileword = (expand_lut + (p.nattrib << 3 & 0x100))[p.reg0] - + (expand_lut + (p.nattrib << 3 & 0x100))[r1 ] * 2; - } + { + int const r1 = loadTileDataByte1(p); + p.ntileword = (expand_lut + (0x100 / attr_xflip * p.nattrib & 0x100))[p.reg0] + + (expand_lut + (0x100 / attr_xflip * p.nattrib & 0x100))[r1] * 2; + } - break; + break; } ++p.xpos; @@ -278,17 +312,16 @@ namespace M3Start { } { - unsigned const ly = p.lyCounter.ly(); - unsigned const numSprites = p.spriteMapper.numSprites(ly); - unsigned char const *const sprites = p.spriteMapper.sprites(ly); + int const ly = p.lyCounter.ly(); + int const numSprites = p.spriteMapper.numSprites(ly); + unsigned char const* const sprites = p.spriteMapper.sprites(ly); + for (int i = 0; i < numSprites; ++i) { + int const pos = sprites[i]; + int const spy = p.spriteMapper.posbuf()[pos]; + int const spx = p.spriteMapper.posbuf()[pos + 1]; - for (unsigned i = 0; i < numSprites; ++i) { - unsigned pos = sprites[i]; - unsigned spy = p.spriteMapper.posbuf()[pos ]; - unsigned spx = p.spriteMapper.posbuf()[pos+1]; - - p.spriteList[i].spx = spx; - p.spriteList[i].line = ly + 16u - spy; + p.spriteList[i].spx = spx; + p.spriteList[i].line = ly + 2 * tile_len - spy; p.spriteList[i].oampos = pos * 2; p.spwordList[i] = 0; } @@ -298,9 +331,9 @@ namespace M3Start { } p.xpos = 0; - p.endx = 8 - (p.scx & 7); + p.endx = tile_len - p.scx % tile_len; - static PPUState const *const flut[8] = { + static PPUState const* const flut[] = { &M3Loop::Tile::f0_, &M3Loop::Tile::f1_, &M3Loop::Tile::f2_, @@ -311,28 +344,229 @@ namespace M3Start { &M3Loop::Tile::f5_ }; - nextCall(1-p.cgb, *flut[p.scx & 7], p); + nextCall(1 - p.cgb, *flut[p.scx % tile_len], p); } } namespace M3Loop { -static void doFullTilesUnrolledDmg(PPUPriv &p, int const xend, uint_least32_t *const dbufline, - unsigned char const *const tileMapLine, unsigned const tileline, unsigned tileMapXpos) { - unsigned const tileIndexSign = ~p.lcdc << 3 & 0x80; - unsigned char const *const tileDataLine = p.vram + tileIndexSign * 32 + tileline * 2; - int xpos = p.xpos; + void doFullTilesUnrolledDmg(PPUPriv& p, int const xend, uint_least32_t* const dbufline, + unsigned char const* const tileMapLine, unsigned const tileline, unsigned tileMapXpos) { + int const tileIndexSign = p.lcdc & lcdc_tdsel ? 0 : tile_pattern_table_size / tile_size / 2; + unsigned char const* const tileDataLine = p.vram + 2 * tile_size * tileIndexSign + + tileline * tile_line_size; + int xpos = p.xpos; - do { - int nextSprite = p.nextSprite; + do { + int nextSprite = p.nextSprite; - if (int(p.spriteList[nextSprite].spx) < xpos + 8) { - int cycles = p.cycles - 8; + if (spx(p.spriteList[nextSprite]) < xpos + tile_len) { + int cycles = p.cycles - tile_len; - if (lcdcObjEn(p)) { - cycles -= std::max(11 - (int(p.spriteList[nextSprite].spx) - xpos), 6); + if (lcdcObjEn(p)) { + cycles -= std::max(11 - (spx(p.spriteList[nextSprite]) - xpos), 6); + for (int i = nextSprite + 1; spx(p.spriteList[i]) < xpos + tile_len; ++i) + cycles -= 6; - for (unsigned i = nextSprite + 1; int(p.spriteList[i].spx) < xpos + 8; ++i) + if (cycles < 0) + break; + + p.cycles = cycles; + + do { + unsigned char const* const oam = p.spriteMapper.oamram(); + unsigned reg0, reg1 = oam[p.spriteList[nextSprite].oampos + 2] * tile_size; + unsigned const attrib = oam[p.spriteList[nextSprite].oampos + 3]; + unsigned const spline = (attrib & attr_yflip + ? p.spriteList[nextSprite].line ^ (2 * tile_len - 1) + : p.spriteList[nextSprite].line) * tile_line_size; + unsigned const ts = tile_size; + reg0 = p.vram[(lcdcObj2x(p) ? (reg1 & ~ts) | spline : reg1 | (spline & ~ts))]; + reg1 = p.vram[(lcdcObj2x(p) ? (reg1 & ~ts) | spline : reg1 | (spline & ~ts)) + 1]; + + p.spwordList[nextSprite] = + expand_lut[reg0 + (0x100 / attr_xflip * attrib & 0x100)] + + expand_lut[reg1 + (0x100 / attr_xflip * attrib & 0x100)] * 2; + p.spriteList[nextSprite].attrib = attrib; + ++nextSprite; + } while (spx(p.spriteList[nextSprite]) < xpos + tile_len); + } + else { + if (cycles < 0) + break; + + p.cycles = cycles; + + do { + ++nextSprite; + } while (spx(p.spriteList[nextSprite]) < xpos + tile_len); + } + + p.nextSprite = nextSprite; + } + else if (nextSprite - 1 < 0 || spx(p.spriteList[nextSprite - 1]) <= xpos - tile_len) { + if (!(p.cycles & -1ul * tile_len)) + break; + + int n = (std::min(spx(p.spriteList[nextSprite]), xend + tile_len - 1) - xpos) & -1u * tile_len; + n = std::min(n, p.cycles & -1ul * tile_len); + p.cycles -= n; + + unsigned ntileword = p.ntileword; + uint_least32_t* dst = dbufline + xpos - tile_len; + uint_least32_t* const dstend = dst + n; + xpos += n; + + if (!lcdcBgEn(p)) { + do { *dst++ = p.bgPalette[0]; } while (dst != dstend); + tileMapXpos += n / (1u * tile_len); + + unsigned const tno = tileMapLine[(tileMapXpos - 1) % tile_map_len]; + int const ts = tile_size; + ntileword = expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[0]] + + expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[1]] * 2; + } + else do { + dst[0] = p.bgPalette[ntileword & tile_bpp_mask]; + dst[1] = p.bgPalette[(ntileword & tile_bpp_mask << 1 * tile_bpp) >> 1 * tile_bpp]; + dst[2] = p.bgPalette[(ntileword & tile_bpp_mask << 2 * tile_bpp) >> 2 * tile_bpp]; + dst[3] = p.bgPalette[(ntileword & tile_bpp_mask << 3 * tile_bpp) >> 3 * tile_bpp]; + dst[4] = p.bgPalette[(ntileword & tile_bpp_mask << 4 * tile_bpp) >> 4 * tile_bpp]; + dst[5] = p.bgPalette[(ntileword & tile_bpp_mask << 5 * tile_bpp) >> 5 * tile_bpp]; + dst[6] = p.bgPalette[(ntileword & tile_bpp_mask << 6 * tile_bpp) >> 6 * tile_bpp]; + dst[7] = p.bgPalette[ntileword >> 7 * tile_bpp]; + dst += tile_len; + + unsigned const tno = tileMapLine[tileMapXpos % tile_map_len]; + int const ts = tile_size; + tileMapXpos = tileMapXpos % tile_map_len + 1; + ntileword = expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[0]] + + expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[1]] * 2; + } while (dst != dstend); + + p.ntileword = ntileword; + continue; + } + else { + int cycles = p.cycles - tile_len; + if (cycles < 0) + break; + + p.cycles = cycles; + } + + uint_least32_t* const dst = dbufline + (xpos - tile_len); + unsigned const tileword = -(p.lcdc & 1u * lcdc_bgen) & p.ntileword; + + dst[0] = p.bgPalette[tileword & tile_bpp_mask]; + dst[1] = p.bgPalette[(tileword & tile_bpp_mask << 1 * tile_bpp) >> 1 * tile_bpp]; + dst[2] = p.bgPalette[(tileword & tile_bpp_mask << 2 * tile_bpp) >> 2 * tile_bpp]; + dst[3] = p.bgPalette[(tileword & tile_bpp_mask << 3 * tile_bpp) >> 3 * tile_bpp]; + dst[4] = p.bgPalette[(tileword & tile_bpp_mask << 4 * tile_bpp) >> 4 * tile_bpp]; + dst[5] = p.bgPalette[(tileword & tile_bpp_mask << 5 * tile_bpp) >> 5 * tile_bpp]; + dst[6] = p.bgPalette[(tileword & tile_bpp_mask << 6 * tile_bpp) >> 6 * tile_bpp]; + dst[7] = p.bgPalette[tileword >> 7 * tile_bpp]; + + int i = nextSprite - 1; + + if (!lcdcObjEn(p)) { + do { + int const pos = spx(p.spriteList[i]) - xpos; + int const sa = pos * tile_bpp >= 0 + ? tile_len * tile_bpp - pos * tile_bpp + : tile_len * tile_bpp + pos * tile_bpp; + p.spwordList[i] = 1l * p.spwordList[i] >> sa; + --i; + } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); + } + else { + do { + int n; + int pos = spx(p.spriteList[i]) - xpos; + if (pos < 0) { + n = pos + tile_len; + pos = 0; + } + else + n = tile_len - pos; + + unsigned const attrib = p.spriteList[i].attrib; + long spword = p.spwordList[i]; + unsigned long const* const spPalette = p.spPalette + + (attrib & attr_dmgpalno) / (attr_dmgpalno / num_palette_entries); + uint_least32_t* d = dst + pos; + + if (!(attrib & attr_bgpriority)) { + int const bpp = tile_bpp, m = tile_bpp_mask; + switch (n) { + case 8: if (spword >> 7 * bpp) { d[7] = spPalette[spword >> 7 * bpp]; } // fall through + case 7: if (spword >> 6 * bpp & m) { d[6] = spPalette[spword >> 6 * bpp & m]; } // fall through + case 6: if (spword >> 5 * bpp & m) { d[5] = spPalette[spword >> 5 * bpp & m]; } // fall through + case 5: if (spword >> 4 * bpp & m) { d[4] = spPalette[spword >> 4 * bpp & m]; } // fall through + case 4: if (spword >> 3 * bpp & m) { d[3] = spPalette[spword >> 3 * bpp & m]; } // fall through + case 3: if (spword >> 2 * bpp & m) { d[2] = spPalette[spword >> 2 * bpp & m]; } // fall through + case 2: if (spword >> 1 * bpp & m) { d[1] = spPalette[spword >> 1 * bpp & m]; } // fall through + case 1: if (spword & m) { d[0] = spPalette[spword & m]; } + } + + spword >>= n * bpp; + + /*do { + if (spword & tile_bpp_mask) + dst[pos] = spPalette[spword & tile_bpp_mask]; + + spword >>= tile_bpp; + ++pos; + } while (--n);*/ + } + else { + unsigned tw = tileword >> pos * tile_bpp; + d += n; + n = -n; + + do { + if (spword & tile_bpp_mask) { + d[n] = tw & tile_bpp_mask + ? p.bgPalette[tw & tile_bpp_mask] + : spPalette[spword & tile_bpp_mask]; + } + + spword >>= tile_bpp; + tw >>= tile_bpp; + } while (++n); + } + + p.spwordList[i] = spword; + --i; + } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); + } + + unsigned const tno = tileMapLine[tileMapXpos % tile_map_len]; + int const ts = tile_size; + tileMapXpos = tileMapXpos % tile_map_len + 1; + p.ntileword = expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[0]] + + expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[1]] * 2; + + xpos = xpos + tile_len; + } while (xpos < xend); + + p.xpos = xpos; + } + + void doFullTilesUnrolledCgb(PPUPriv& p, int const xend, uint_least32_t* const dbufline, + unsigned char const* const tileMapLine, unsigned const tileline, unsigned tileMapXpos) { + int xpos = p.xpos; + unsigned char const* const vram = p.vram; + unsigned const tdoffset = tileline * tile_line_size + + tile_pattern_table_size / lcdc_tdsel * (~p.lcdc & lcdc_tdsel); + + do { + int nextSprite = p.nextSprite; + + if (spx(p.spriteList[nextSprite]) < xpos + tile_len) { + int cycles = p.cycles - tile_len; + cycles -= std::max(11 - (spx(p.spriteList[nextSprite]) - xpos), 6); + for (int i = nextSprite + 1; spx(p.spriteList[i]) < xpos + tile_len; ++i) cycles -= 6; if (cycles < 0) @@ -341,518 +575,342 @@ static void doFullTilesUnrolledDmg(PPUPriv &p, int const xend, uint_least32_t *c p.cycles = cycles; do { - unsigned char const *const oam = p.spriteMapper.oamram(); - unsigned reg0, reg1 = oam[p.spriteList[nextSprite].oampos + 2] * 16; + unsigned char const* const oam = p.spriteMapper.oamram(); + unsigned reg0, reg1 = oam[p.spriteList[nextSprite].oampos + 2] * tile_size; unsigned const attrib = oam[p.spriteList[nextSprite].oampos + 3]; - unsigned const spline = ( attrib & attr_yflip - ? p.spriteList[nextSprite].line ^ 15 - : p.spriteList[nextSprite].line ) * 2; + unsigned const spline = (attrib & attr_yflip + ? p.spriteList[nextSprite].line ^ (2 * tile_len - 1) + : p.spriteList[nextSprite].line) * tile_line_size; + unsigned const ts = tile_size; + reg0 = vram[vram_bank_size / attr_tdbank * (attrib & attr_tdbank) + + (lcdcObj2x(p) ? (reg1 & ~ts) | spline : reg1 | (spline & ~ts))]; + reg1 = vram[vram_bank_size / attr_tdbank * (attrib & attr_tdbank) + + (lcdcObj2x(p) ? (reg1 & ~ts) | spline : reg1 | (spline & ~ts)) + 1]; - reg0 = p.vram[(lcdcObj2x(p) ? (reg1 & ~16) | spline : reg1 | (spline & ~16)) ]; - reg1 = p.vram[(lcdcObj2x(p) ? (reg1 & ~16) | spline : reg1 | (spline & ~16)) + 1]; - - p.spwordList[nextSprite] = expand_lut[reg0 + (attrib << 3 & 0x100)] - + expand_lut[reg1 + (attrib << 3 & 0x100)] * 2; + p.spwordList[nextSprite] = + expand_lut[reg0 + (0x100 / attr_xflip * attrib & 0x100)] + + expand_lut[reg1 + (0x100 / attr_xflip * attrib & 0x100)] * 2; p.spriteList[nextSprite].attrib = attrib; ++nextSprite; - } while (int(p.spriteList[nextSprite].spx) < xpos + 8); - } else { + } while (spx(p.spriteList[nextSprite]) < xpos + tile_len); + + p.nextSprite = nextSprite; + } + else if (nextSprite - 1 < 0 || spx(p.spriteList[nextSprite - 1]) <= xpos - tile_len) { + if (!(p.cycles & -1ul * tile_len)) + break; + + int n = (std::min(spx(p.spriteList[nextSprite]), xend + tile_len - 1) - xpos) & -1u * tile_len; + n = std::min(n, p.cycles & -1ul * tile_len); + p.cycles -= n; + + unsigned ntileword = p.ntileword; + unsigned nattrib = p.nattrib; + uint_least32_t* dst = dbufline + xpos - tile_len; + uint_least32_t* const dstend = dst + n; + xpos += n; + + do { + unsigned long const* const bgPalette = p.bgPalette + + (nattrib & attr_cgbpalno) * num_palette_entries; + dst[0] = bgPalette[ntileword & tile_bpp_mask]; + dst[1] = bgPalette[(ntileword & tile_bpp_mask << 1 * tile_bpp) >> 1 * tile_bpp]; + dst[2] = bgPalette[(ntileword & tile_bpp_mask << 2 * tile_bpp) >> 2 * tile_bpp]; + dst[3] = bgPalette[(ntileword & tile_bpp_mask << 3 * tile_bpp) >> 3 * tile_bpp]; + dst[4] = bgPalette[(ntileword & tile_bpp_mask << 4 * tile_bpp) >> 4 * tile_bpp]; + dst[5] = bgPalette[(ntileword & tile_bpp_mask << 5 * tile_bpp) >> 5 * tile_bpp]; + dst[6] = bgPalette[(ntileword & tile_bpp_mask << 6 * tile_bpp) >> 6 * tile_bpp]; + dst[7] = bgPalette[ntileword >> 7 * tile_bpp]; + dst += tile_len; + + unsigned const tno = tileMapLine[tileMapXpos % tile_map_len]; + nattrib = tileMapLine[tileMapXpos % tile_map_len + vram_bank_size]; + tileMapXpos = tileMapXpos % tile_map_len + 1; + + unsigned const tdo = tdoffset & ~(tno << 5); + unsigned char const* const td = vram + tno * tile_size + + (nattrib & attr_yflip ? tdo ^ tile_line_size * (tile_len - 1) : tdo) + + vram_bank_size / attr_tdbank * (nattrib & attr_tdbank); + unsigned short const* const explut = expand_lut + (0x100 / attr_xflip * nattrib & 0x100); + ntileword = explut[td[0]] + explut[td[1]] * 2; + } while (dst != dstend); + + + p.ntileword = ntileword; + p.nattrib = nattrib; + continue; + } + else { + int cycles = p.cycles - tile_len; if (cycles < 0) break; p.cycles = cycles; - - do { - ++nextSprite; - } while (int(p.spriteList[nextSprite].spx) < xpos + 8); } - p.nextSprite = nextSprite; - } else if (nextSprite-1 < 0 || int(p.spriteList[nextSprite-1].spx) <= xpos - 8) { - if (!(p.cycles & ~7)) - break; - - int n = (( xend + 7 < int(p.spriteList[nextSprite].spx) - ? xend + 7 : int(p.spriteList[nextSprite].spx)) - xpos) & ~7; - n = (p.cycles & ~7) < n ? p.cycles & ~7 : n; - p.cycles -= n; - - unsigned ntileword = p.ntileword; - uint_least32_t * dst = dbufline + xpos - 8; - uint_least32_t *const dstend = dst + n; - xpos += n; - - if (!lcdcBgEn(p)) { - do { *dst++ = p.bgPalette[0]; } while (dst != dstend); - tileMapXpos += n >> 3; - - unsigned const tno = tileMapLine[(tileMapXpos - 1) & 0x1F]; - ntileword = expand_lut[(tileDataLine + tno * 16 - (tno & tileIndexSign) * 32)[0]] - + expand_lut[(tileDataLine + tno * 16 - (tno & tileIndexSign) * 32)[1]] * 2; - } else do { - dst[0] = p.bgPalette[ ntileword & 0x0003 ]; - dst[1] = p.bgPalette[(ntileword & 0x000C) >> 2]; - dst[2] = p.bgPalette[(ntileword & 0x0030) >> 4]; - dst[3] = p.bgPalette[(ntileword & 0x00C0) >> 6]; - dst[4] = p.bgPalette[(ntileword & 0x0300) >> 8]; - dst[5] = p.bgPalette[(ntileword & 0x0C00) >> 10]; - dst[6] = p.bgPalette[(ntileword & 0x3000) >> 12]; - dst[7] = p.bgPalette[ ntileword >> 14]; - dst += 8; - - unsigned const tno = tileMapLine[tileMapXpos & 0x1F]; - tileMapXpos = (tileMapXpos & 0x1F) + 1; - ntileword = expand_lut[(tileDataLine + tno * 16 - (tno & tileIndexSign) * 32)[0]] - + expand_lut[(tileDataLine + tno * 16 - (tno & tileIndexSign) * 32)[1]] * 2; - } while (dst != dstend); - - p.ntileword = ntileword; - continue; - } else { - int cycles = p.cycles - 8; - if (cycles < 0) - break; - - p.cycles = cycles; - } - - { - uint_least32_t *const dst = dbufline + (xpos - 8); - unsigned const tileword = -(p.lcdc & 1U & p.layersMask) & p.ntileword; - - dst[0] = p.bgPalette[ tileword & 0x0003 ]; - dst[1] = p.bgPalette[(tileword & 0x000C) >> 2]; - dst[2] = p.bgPalette[(tileword & 0x0030) >> 4]; - dst[3] = p.bgPalette[(tileword & 0x00C0) >> 6]; - dst[4] = p.bgPalette[(tileword & 0x0300) >> 8]; - dst[5] = p.bgPalette[(tileword & 0x0C00) >> 10]; - dst[6] = p.bgPalette[(tileword & 0x3000) >> 12]; - dst[7] = p.bgPalette[ tileword >> 14]; + uint_least32_t* const dst = dbufline + (xpos - tile_len); + unsigned const tileword = p.ntileword; + unsigned const attrib = p.nattrib; + unsigned long const* const bgPalette = p.bgPalette + + (attrib & attr_cgbpalno) * num_palette_entries; + dst[0] = bgPalette[tileword & tile_bpp_mask]; + dst[1] = bgPalette[(tileword & tile_bpp_mask << 1 * tile_bpp) >> 1 * tile_bpp]; + dst[2] = bgPalette[(tileword & tile_bpp_mask << 2 * tile_bpp) >> 2 * tile_bpp]; + dst[3] = bgPalette[(tileword & tile_bpp_mask << 3 * tile_bpp) >> 3 * tile_bpp]; + dst[4] = bgPalette[(tileword & tile_bpp_mask << 4 * tile_bpp) >> 4 * tile_bpp]; + dst[5] = bgPalette[(tileword & tile_bpp_mask << 5 * tile_bpp) >> 5 * tile_bpp]; + dst[6] = bgPalette[(tileword & tile_bpp_mask << 6 * tile_bpp) >> 6 * tile_bpp]; + dst[7] = bgPalette[tileword >> 7 * tile_bpp]; int i = nextSprite - 1; if (!lcdcObjEn(p)) { do { - int pos = int(p.spriteList[i].spx) - xpos; - p.spwordList[i] >>= pos * 2 >= 0 ? 16 - pos * 2 : 16 + pos * 2; + int const pos = spx(p.spriteList[i]) - xpos; + int const sa = pos * tile_bpp >= 0 + ? tile_len * tile_bpp - pos * tile_bpp + : tile_len * tile_bpp + pos * tile_bpp; + p.spwordList[i] = 1l * p.spwordList[i] >> sa; --i; - } while (i >= 0 && int(p.spriteList[i].spx) > xpos - 8); - } else { - do { - int n; - int pos = int(p.spriteList[i].spx) - xpos; - if (pos < 0) { - n = pos + 8; - pos = 0; - } else - n = 8 - pos; - - unsigned const attrib = p.spriteList[i].attrib; - unsigned spword = p.spwordList[i]; - unsigned long const *const spPalette = p.spPalette + (attrib >> 2 & 4); - uint_least32_t *d = dst + pos; - - if (!(attrib & attr_bgpriority)) { - switch (n) { - case 8: if (spword >> 14 ) { d[7] = spPalette[spword >> 14 ]; } // fallthrough - case 7: if (spword >> 12 & 3) { d[6] = spPalette[spword >> 12 & 3]; } // fallthrough - case 6: if (spword >> 10 & 3) { d[5] = spPalette[spword >> 10 & 3]; } // fallthrough - case 5: if (spword >> 8 & 3) { d[4] = spPalette[spword >> 8 & 3]; } // fallthrough - case 4: if (spword >> 6 & 3) { d[3] = spPalette[spword >> 6 & 3]; } // fallthrough - case 3: if (spword >> 4 & 3) { d[2] = spPalette[spword >> 4 & 3]; } // fallthrough - case 2: if (spword >> 2 & 3) { d[1] = spPalette[spword >> 2 & 3]; } // fallthrough - case 1: if (spword & 3) { d[0] = spPalette[spword & 3]; } - } - - spword >>= n * 2; - - /*do { - if (spword & 3) - dst[pos] = spPalette[spword & 3]; - - spword >>= 2; - ++pos; - } while (--n);*/ - } else { - unsigned tw = tileword >> pos * 2; - d += n; - n = -n; - - do { - if (spword & 3) { - d[n] = tw & 3 - ? p.bgPalette[ tw & 3] - : spPalette[spword & 3]; - } - - spword >>= 2; - tw >>= 2; - } while (++n); - } - - p.spwordList[i] = spword; - --i; - } while (i >= 0 && int(p.spriteList[i].spx) > xpos - 8); + } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); } - } - - unsigned const tno = tileMapLine[tileMapXpos & 0x1F]; - tileMapXpos = (tileMapXpos & 0x1F) + 1; - p.ntileword = expand_lut[(tileDataLine + tno * 16 - (tno & tileIndexSign) * 32)[0]] - + expand_lut[(tileDataLine + tno * 16 - (tno & tileIndexSign) * 32)[1]] * 2; - - xpos = xpos + 8; - } while (xpos < xend); - - p.xpos = xpos; -} - -static void doFullTilesUnrolledCgb(PPUPriv &p, int const xend, uint_least32_t *const dbufline, - unsigned char const *const tileMapLine, unsigned const tileline, unsigned tileMapXpos) { - int xpos = p.xpos; - unsigned char const *const vram = p.vram; - unsigned const tdoffset = tileline * 2 + (~p.lcdc & 0x10) * 0x100; - - do { - int nextSprite = p.nextSprite; - - if (int(p.spriteList[nextSprite].spx) < xpos + 8) { - int cycles = p.cycles - 8; - cycles -= std::max(11 - (int(p.spriteList[nextSprite].spx) - xpos), 6); - - for (unsigned i = nextSprite + 1; int(p.spriteList[i].spx) < xpos + 8; ++i) - cycles -= 6; - - if (cycles < 0) - break; - - p.cycles = cycles; - - do { - unsigned char const *const oam = p.spriteMapper.oamram(); - unsigned reg0, reg1 = oam[p.spriteList[nextSprite].oampos + 2] * 16; - unsigned const attrib = oam[p.spriteList[nextSprite].oampos + 3]; - unsigned const spline = ( attrib & attr_yflip - ? p.spriteList[nextSprite].line ^ 15 - : p.spriteList[nextSprite].line ) * 2; - - reg0 = vram[(attrib << 10 & 0x2000) - + (lcdcObj2x(p) ? (reg1 & ~16) | spline : reg1 | (spline & ~16)) ]; - reg1 = vram[(attrib << 10 & 0x2000) - + (lcdcObj2x(p) ? (reg1 & ~16) | spline : reg1 | (spline & ~16)) + 1]; - - p.spwordList[nextSprite] = expand_lut[reg0 + (attrib << 3 & 0x100)] - + expand_lut[reg1 + (attrib << 3 & 0x100)] * 2; - p.spriteList[nextSprite].attrib = attrib; - ++nextSprite; - } while (int(p.spriteList[nextSprite].spx) < xpos + 8); - - p.nextSprite = nextSprite; - } else if (nextSprite-1 < 0 || int(p.spriteList[nextSprite-1].spx) <= xpos - 8) { - if (!(p.cycles & ~7)) - break; - - int n = (( xend + 7 < int(p.spriteList[nextSprite].spx) - ? xend + 7 : int(p.spriteList[nextSprite].spx)) - xpos) & ~7; - n = (p.cycles & ~7) < n ? p.cycles & ~7 : n; - p.cycles -= n; - - unsigned ntileword = -(p.layersMask & layer_mask_bg) & p.ntileword; - unsigned nattrib = -(p.layersMask & layer_mask_bg) & p.nattrib; - uint_least32_t * dst = dbufline + xpos - 8; - uint_least32_t *const dstend = dst + n; - xpos += n; - - do { - unsigned long const *const bgPalette = p.bgPalette + (nattrib & 7) * 4; - dst[0] = bgPalette[ ntileword & 0x0003 ]; - dst[1] = bgPalette[(ntileword & 0x000C) >> 2]; - dst[2] = bgPalette[(ntileword & 0x0030) >> 4]; - dst[3] = bgPalette[(ntileword & 0x00C0) >> 6]; - dst[4] = bgPalette[(ntileword & 0x0300) >> 8]; - dst[5] = bgPalette[(ntileword & 0x0C00) >> 10]; - dst[6] = bgPalette[(ntileword & 0x3000) >> 12]; - dst[7] = bgPalette[ ntileword >> 14]; - dst += 8; - - unsigned const tno = tileMapLine[ tileMapXpos & 0x1F ]; - nattrib = -(p.layersMask & layer_mask_bg) & tileMapLine[(tileMapXpos & 0x1F) + 0x2000]; - tileMapXpos = (tileMapXpos & 0x1F) + 1; - - unsigned const tdo = tdoffset & ~(tno << 5); - unsigned char const *const td = vram + tno * 16 - + (nattrib & attr_yflip ? tdo ^ 14 : tdo) - + (nattrib << 10 & 0x2000); - unsigned short const *const explut = expand_lut + (nattrib << 3 & 0x100); - ntileword = -(p.layersMask & layer_mask_bg) & (explut[td[0]] + explut[td[1]] * 2); - } while (dst != dstend); - - p.ntileword = ntileword; - p.nattrib = nattrib; - continue; - } else { - int cycles = p.cycles - 8; - - if (cycles < 0) - break; - - p.cycles = cycles; - } - - { - uint_least32_t *const dst = dbufline + (xpos - 8); - unsigned const tileword = -(p.layersMask & layer_mask_bg) & p.ntileword; - unsigned const attrib = -(p.layersMask & layer_mask_bg) & p.nattrib; - unsigned long const *const bgPalette = p.bgPalette + (attrib & 7) * 4; - - dst[0] = bgPalette[ tileword & 0x0003 ]; - dst[1] = bgPalette[(tileword & 0x000C) >> 2]; - dst[2] = bgPalette[(tileword & 0x0030) >> 4]; - dst[3] = bgPalette[(tileword & 0x00C0) >> 6]; - dst[4] = bgPalette[(tileword & 0x0300) >> 8]; - dst[5] = bgPalette[(tileword & 0x0C00) >> 10]; - dst[6] = bgPalette[(tileword & 0x3000) >> 12]; - dst[7] = bgPalette[ tileword >> 14]; - - int i = nextSprite - 1; - - if (!lcdcObjEn(p)) { - do { - int pos = int(p.spriteList[i].spx) - xpos; - p.spwordList[i] >>= pos * 2 >= 0 ? 16 - pos * 2 : 16 + pos * 2; - --i; - } while (i >= 0 && int(p.spriteList[i].spx) > xpos - 8); - } else { - unsigned char idtab[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + else { + unsigned char idtab[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; unsigned const bgprioritymask = p.lcdc << 7; do { int n; - int pos = int(p.spriteList[i].spx) - xpos; - + int pos = spx(p.spriteList[i]) - xpos; if (pos < 0) { - n = pos + 8; + n = pos + tile_len; pos = 0; - } else - n = 8 - pos; + } + else + n = tile_len - pos; unsigned char const id = p.spriteList[i].oampos; unsigned const sattrib = p.spriteList[i].attrib; - unsigned spword = p.spwordList[i]; - unsigned long const *const spPalette = p.spPalette + (sattrib & 7) * 4; + long spword = p.spwordList[i]; + unsigned long const* const spPalette = cgbSpPalette(p, sattrib); if (!((attrib | sattrib) & bgprioritymask)) { - unsigned char *const idt = idtab + pos; - uint_least32_t *const d = dst + pos; + unsigned char* const idt = idtab + pos; + uint_least32_t* const d = dst + pos; switch (n) { - case 8: if ((spword >> 14 ) && id < idt[7]) { - idt[7] = id; - d[7] = spPalette[spword >> 14 ]; - } // fallthrough - case 7: if ((spword >> 12 & 3) && id < idt[6]) { - idt[6] = id; - d[6] = spPalette[spword >> 12 & 3]; - } // fallthrough - case 6: if ((spword >> 10 & 3) && id < idt[5]) { - idt[5] = id; - d[5] = spPalette[spword >> 10 & 3]; - } // fallthrough - case 5: if ((spword >> 8 & 3) && id < idt[4]) { - idt[4] = id; - d[4] = spPalette[spword >> 8 & 3]; - } // fallthrough - case 4: if ((spword >> 6 & 3) && id < idt[3]) { - idt[3] = id; - d[3] = spPalette[spword >> 6 & 3]; - } // fallthrough - case 3: if ((spword >> 4 & 3) && id < idt[2]) { - idt[2] = id; - d[2] = spPalette[spword >> 4 & 3]; - } // fallthrough - case 2: if ((spword >> 2 & 3) && id < idt[1]) { - idt[1] = id; - d[1] = spPalette[spword >> 2 & 3]; - } // fallthrough - case 1: if ((spword & 3) && id < idt[0]) { - idt[0] = id; - d[0] = spPalette[spword & 3]; - } + case 8: if ((spword >> 7 * tile_bpp) && id < idt[7]) { + idt[7] = id; + d[7] = spPalette[spword >> 7 * tile_bpp]; + } // fall through + case 7: if ((spword >> 6 * tile_bpp & tile_bpp_mask) && id < idt[6]) { + idt[6] = id; + d[6] = spPalette[spword >> 6 * tile_bpp & tile_bpp_mask]; + } // fall through + case 6: if ((spword >> 5 * tile_bpp & tile_bpp_mask) && id < idt[5]) { + idt[5] = id; + d[5] = spPalette[spword >> 5 * tile_bpp & tile_bpp_mask]; + } // fall through + case 5: if ((spword >> 4 * tile_bpp & tile_bpp_mask) && id < idt[4]) { + idt[4] = id; + d[4] = spPalette[spword >> 4 * tile_bpp & tile_bpp_mask]; + } // fall through + case 4: if ((spword >> 3 * tile_bpp & tile_bpp_mask) && id < idt[3]) { + idt[3] = id; + d[3] = spPalette[spword >> 3 * tile_bpp & tile_bpp_mask]; + } // fall through + case 3: if ((spword >> 2 * tile_bpp & tile_bpp_mask) && id < idt[2]) { + idt[2] = id; + d[2] = spPalette[spword >> 2 * tile_bpp & tile_bpp_mask]; + } // fall through + case 2: if ((spword >> 1 * tile_bpp & tile_bpp_mask) && id < idt[1]) { + idt[1] = id; + d[1] = spPalette[spword >> 1 * tile_bpp & tile_bpp_mask]; + } // fall through + case 1: if ((spword & tile_bpp_mask) && id < idt[0]) { + idt[0] = id; + d[0] = spPalette[spword & tile_bpp_mask]; + } } - spword >>= n * 2; + spword >>= n * tile_bpp; /*do { - if ((spword & 3) && id < idtab[pos]) { + if ((spword & tile_bpp_mask) && id < idtab[pos]) { idtab[pos] = id; - dst[pos] = spPalette[spword & 3]; + dst[pos] = spPalette[spword & tile_bpp_mask]; } - spword >>= 2; + spword >>= tile_bpp; ++pos; } while (--n);*/ - } else { - unsigned tw = tileword >> pos * 2; + } + else { + unsigned tw = tileword >> pos * tile_bpp; do { - if ((spword & 3) && id < idtab[pos]) { + if ((spword & tile_bpp_mask) && id < idtab[pos]) { idtab[pos] = id; - dst[pos] = tw & 3 - ? bgPalette[ tw & 3] - : spPalette[spword & 3]; + dst[pos] = tw & tile_bpp_mask + ? bgPalette[tw & tile_bpp_mask] + : spPalette[spword & tile_bpp_mask]; } - spword >>= 2; - tw >>= 2; + spword >>= tile_bpp; + tw >>= tile_bpp; ++pos; } while (--n); } p.spwordList[i] = spword; --i; - } while (i >= 0 && int(p.spriteList[i].spx) > xpos - 8); + } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); } - } - { - unsigned const tno = tileMapLine[ tileMapXpos & 0x1F ]; - unsigned const nattrib = tileMapLine[(tileMapXpos & 0x1F) + 0x2000]; - tileMapXpos = (tileMapXpos & 0x1F) + 1; + { + unsigned const tno = tileMapLine[tileMapXpos % tile_map_len]; + unsigned const nattrib = tileMapLine[tileMapXpos % tile_map_len + vram_bank_size]; + tileMapXpos = tileMapXpos % tile_map_len + 1; - unsigned const tdo = tdoffset & ~(tno << 5); - unsigned char const *const td = vram + tno * 16 - + (nattrib & attr_yflip ? tdo ^ 14 : tdo) - + (nattrib << 10 & 0x2000); - unsigned short const *const explut = expand_lut + (nattrib << 3 & 0x100); - p.ntileword = explut[td[0]] + explut[td[1]] * 2; - p.nattrib = nattrib; - } + unsigned const tdo = tdoffset & ~(tno << 5); + unsigned char const* const td = vram + tno * tile_size + + (nattrib & attr_yflip ? tdo ^ tile_line_size * (tile_len - 1) : tdo) + + vram_bank_size / attr_tdbank * (nattrib & attr_tdbank); + unsigned short const* const explut = expand_lut + (0x100 / attr_xflip * nattrib & 0x100); + p.ntileword = explut[td[0]] + explut[td[1]] * 2; + p.nattrib = nattrib; + } - xpos = xpos + 8; - } while (xpos < xend); + xpos = xpos + tile_len; + } while (xpos < xend); - p.xpos = xpos; -} - -static void doFullTilesUnrolled(PPUPriv &p) { - int xpos = p.xpos; - int const xend = static_cast(p.wx) < xpos || p.wx >= 168 - ? 161 - : static_cast(p.wx) - 7; - if (xpos >= xend) - return; - - uint_least32_t *const dbufline = p.framebuf.fbline(); - unsigned char const *tileMapLine; - unsigned tileline; - unsigned tileMapXpos; - - if (p.winDrawState & win_draw_started) { - tileMapLine = p.vram + (p.lcdc << 4 & 0x400) - + (p.winYPos & 0xF8) * 4 + 0x1800; - tileMapXpos = (xpos + p.wscx) >> 3; - tileline = p.winYPos & 7; - } else { - tileMapLine = p.vram + (p.lcdc << 7 & 0x400) - + ((p.scy + p.lyCounter.ly()) & 0xF8) * 4 + 0x1800; - tileMapXpos = (p.scx + xpos + 1 - p.cgb) >> 3; - tileline = (p.scy + p.lyCounter.ly()) & 7; + p.xpos = xpos; } - if (xpos < 8) { - uint_least32_t prebuf[16]; - if (p.cgb) { - doFullTilesUnrolledCgb(p, xend < 8 ? xend : 8, prebuf + (8 - xpos), - tileMapLine, tileline, tileMapXpos); - } else { - doFullTilesUnrolledDmg(p, xend < 8 ? xend : 8, prebuf + (8 - xpos), - tileMapLine, tileline, tileMapXpos); + void doFullTilesUnrolled(PPUPriv& p) { + int xpos = p.xpos; + int const xend = p.wx < p.xpos || p.wx >= xpos_end + ? lcd_hres + 1 + : static_cast(p.wx) - 7; + if (xpos >= xend) + return; + + uint_least32_t* const dbufline = p.framebuf.fbline(); + unsigned char const* tileMapLine; + unsigned tileline; + unsigned tileMapXpos; + if (p.winDrawState & win_draw_started) { + tileMapLine = p.vram + tile_map_size / lcdc_wtmsel * (p.lcdc & lcdc_wtmsel) + + tile_map_len / tile_len * (p.winYPos & (0x100 - tile_len)) + + tile_map_begin; + tileMapXpos = (xpos + p.wscx) / (1u * tile_len); + tileline = p.winYPos % tile_len; + } + else { + tileMapLine = p.vram + tile_map_size / lcdc_bgtmsel * (p.lcdc & lcdc_bgtmsel) + + tile_map_len / tile_len * ((p.scy + p.lyCounter.ly()) & (0x100 - tile_len)) + + tile_map_begin; + tileMapXpos = (p.scx + xpos + 1 - p.cgb) / (1u * tile_len); + tileline = (p.scy + p.lyCounter.ly()) % tile_len; } - int const newxpos = p.xpos; + if (xpos < tile_len) { + uint_least32_t prebuf[2 * tile_len]; + if (p.cgb) { + doFullTilesUnrolledCgb(p, std::min(tile_len, xend), prebuf + (tile_len - xpos), + tileMapLine, tileline, tileMapXpos); + } + else { + doFullTilesUnrolledDmg(p, std::min(tile_len, xend), prebuf + (tile_len - xpos), + tileMapLine, tileline, tileMapXpos); + } - if (newxpos > 8) { - std::memcpy(dbufline, prebuf + (8 - xpos), (newxpos - 8) * sizeof *dbufline); - } else if (newxpos < 8) - return; + int const newxpos = p.xpos; + if (newxpos > tile_len) { + std::memcpy(dbufline, prebuf + (tile_len - xpos), (newxpos - tile_len) * sizeof * dbufline); + } + else if (newxpos < tile_len) + return; - if (newxpos >= xend) - return; + if (newxpos >= xend) + return; - tileMapXpos += (newxpos - xpos) >> 3; + tileMapXpos += (newxpos - xpos) / (1u * tile_len); + } + + p.cgb + ? doFullTilesUnrolledCgb(p, xend, dbufline, tileMapLine, tileline, tileMapXpos) + : doFullTilesUnrolledDmg(p, xend, dbufline, tileMapLine, tileline, tileMapXpos); } - if (p.cgb) { - doFullTilesUnrolledCgb(p, xend, dbufline, tileMapLine, tileline, tileMapXpos); - } else - doFullTilesUnrolledDmg(p, xend, dbufline, tileMapLine, tileline, tileMapXpos); -} + void plotPixel(PPUPriv& p) { + int const xpos = p.xpos; + unsigned const tileword = p.tileword; -static void plotPixel(PPUPriv &p) { - int const xpos = p.xpos; - unsigned const tileword = p.tileword; - uint_least32_t *const fbline = p.framebuf.fbline(); + uint_least32_t* const fbline = p.framebuf.fbline(); - if (static_cast(p.wx) == xpos + if (p.wx == xpos && (p.weMaster || (p.wy2 == p.lyCounter.ly() && lcdcWinEn(p))) - && xpos < 167) { - if (p.winDrawState == 0 && lcdcWinEn(p)) { - p.winDrawState = win_draw_start | win_draw_started; - ++p.winYPos; - } else if (!p.cgb && (p.winDrawState == 0 || xpos == 166)) - p.winDrawState |= win_draw_start; - } - - unsigned const twdata = tileword & (((p.lcdc & 1) | p.cgb) & p.layersMask) * 3; - unsigned long pixel = p.bgPalette[twdata + (p.attrib & 7 & -(p.layersMask & layer_mask_bg)) * 4]; - int i = static_cast(p.nextSprite) - 1; - - if (i >= 0 && int(p.spriteList[i].spx) > xpos - 8) { - unsigned spdata = 0; - unsigned attrib = 0; - - if (p.cgb) { - unsigned minId = 0xFF; - - do { - if ((p.spwordList[i] & 3) && p.spriteList[i].oampos < minId) { - spdata = p.spwordList[i] & 3; - attrib = p.spriteList[i].attrib; - minId = p.spriteList[i].oampos; - } - - p.spwordList[i] >>= 2; - --i; - } while (i >= 0 && int(p.spriteList[i].spx) > xpos - 8); - - if (spdata && lcdcObjEn(p) - && (!((attrib | p.attrib) & attr_bgpriority) || !twdata || !lcdcBgEn(p))) { - pixel = p.spPalette[(attrib & 7) * 4 + spdata]; + && xpos < lcd_hres + 7) { + if (p.winDrawState == 0 && lcdcWinEn(p)) { + p.winDrawState = win_draw_start | win_draw_started; + ++p.winYPos; } - } else { - do { - if (p.spwordList[i] & 3) { - spdata = p.spwordList[i] & 3; - attrib = p.spriteList[i].attrib; - } - - p.spwordList[i] >>= 2; - --i; - } while (i >= 0 && int(p.spriteList[i].spx) > xpos - 8); - - if (spdata && lcdcObjEn(p) && (!(attrib & attr_bgpriority) || !twdata)) - pixel = p.spPalette[(attrib >> 2 & 4) + spdata]; + else if (!p.cgb && (p.winDrawState == 0 || xpos == lcd_hres + 6)) + p.winDrawState |= win_draw_start; } + + unsigned const twdata = tileword & ((p.lcdc & lcdc_bgen) | p.cgb) * tile_bpp_mask; + unsigned long pixel = p.bgPalette[twdata + (p.attrib & attr_cgbpalno) * num_palette_entries]; + int i = static_cast(p.nextSprite) - 1; + + if (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len) { + unsigned spdata = 0; + unsigned attrib = 0; + + if (p.cgb) { + unsigned minId = 0xFF; + + do { + if ((p.spwordList[i] & tile_bpp_mask) && p.spriteList[i].oampos < minId) { + spdata = p.spwordList[i] & tile_bpp_mask; + attrib = p.spriteList[i].attrib; + minId = p.spriteList[i].oampos; + } + + p.spwordList[i] >>= tile_bpp; + --i; + } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); + + if (spdata && lcdcObjEn(p) + && (!((attrib | p.attrib) & attr_bgpriority) || !twdata || !lcdcBgEn(p))) { + pixel = *(cgbSpPalette(p, attrib) + spdata); + } + } + else { + do { + if (p.spwordList[i] & tile_bpp_mask) { + spdata = p.spwordList[i] & tile_bpp_mask; + attrib = p.spriteList[i].attrib; + } + + p.spwordList[i] >>= tile_bpp; + --i; + } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); + + if (spdata && lcdcObjEn(p) && (!(attrib & attr_bgpriority) || !twdata)) + pixel = p.spPalette[(attrib & attr_dmgpalno ? num_palette_entries : 0) + spdata]; + } + } + + if (xpos - tile_len >= 0) + fbline[xpos - tile_len] = pixel; + + + p.xpos = xpos + 1; + p.tileword = tileword >> tile_bpp; } - if (xpos - 8 >= 0) - fbline[xpos - 8] = pixel; - - p.xpos = xpos + 1; - p.tileword = tileword >> 2; -} - static void plotPixelIfNoSprite(PPUPriv &p) { if (p.spriteList[p.nextSprite].spx == p.xpos) { if (!(lcdcObjEn(p) | p.cgb)) { @@ -866,33 +924,29 @@ static void plotPixelIfNoSprite(PPUPriv &p) { plotPixel(p); } -static unsigned long nextM2Time(PPUPriv const &p) { - unsigned long nextm2 = p.lyCounter.isDoubleSpeed() - ? p.lyCounter.time() + (weMasterCheckPriorToLyIncLineCycle(true ) + m2_ds_offset) * 2 - 456 * 2 - : p.lyCounter.time() + weMasterCheckPriorToLyIncLineCycle(p.cgb) - 456 ; - if (p.lyCounter.ly() == 143) - nextm2 += (456 * 10 + 456 - weMasterCheckPriorToLyIncLineCycle(p.cgb)) << p.lyCounter.isDoubleSpeed(); - - return nextm2; +unsigned long nextM2Time(PPUPriv const& p) { + int const nm2 = p.lyCounter.ly() < lcd_vres - 1 + ? weMasterCheckPriorToLyIncLineCycle(p.cgb) + : lcd_cycles_per_line * (lcd_lines_per_frame - p.lyCounter.ly()) + + weMasterCheckLy0LineCycle(p.cgb); + return p.lyCounter.time() - p.lyCounter.lineTime() + (nm2 << p.lyCounter.isDoubleSpeed()); } -static void xpos168(PPUPriv &p) { +void xposEnd(PPUPriv& p) { p.lastM0Time = p.now - (p.cycles << p.lyCounter.isDoubleSpeed()); unsigned long const nextm2 = nextM2Time(p); - p.cycles = p.now >= nextm2 - ? long((p.now - nextm2) >> p.lyCounter.isDoubleSpeed()) - : -long((nextm2 - p.now) >> p.lyCounter.isDoubleSpeed()); - - nextCall(0, p.lyCounter.ly() == 143 ? M2_Ly0::f0_ : M2_LyNon0::f0_, p); + ? static_cast((p.now - nextm2) >> p.lyCounter.isDoubleSpeed()) + : -static_cast((nextm2 - p.now) >> p.lyCounter.isDoubleSpeed()); + nextCall(0, p.lyCounter.ly() == lcd_vres - 1 ? M2_Ly0::f0_ : M2_LyNon0::f0_, p); } -static bool handleWinDrawStartReq(PPUPriv const &p, int const xpos, unsigned char &winDrawState) { - bool const startWinDraw = (xpos < 167 || p.cgb) - && (winDrawState &= win_draw_started); +bool handleWinDrawStartReq(PPUPriv const &p, int const xpos, unsigned char &winDrawState) { + bool const startWinDraw = (xpos < lcd_hres + 7 || p.cgb) + && (winDrawState &= win_draw_started); if (!lcdcWinEn(p)) - winDrawState &= ~win_draw_started; + winDrawState &= ~(1u * win_draw_started); return startWinDraw; } @@ -902,15 +956,16 @@ static bool handleWinDrawStartReq(PPUPriv &p) { } namespace StartWindowDraw { - static void inc(PPUState const &nextf, PPUPriv &p) { + void inc(PPUState const& nextf, PPUPriv& p) { if (!lcdcWinEn(p) && p.cgb) { plotPixelIfNoSprite(p); if (p.xpos == p.endx) { - if (p.xpos < 168) { + if (p.xpos < xpos_end) { nextCall(1, Tile::f0_, p); - } else - xpos168(p); + } + else + xposEnd(p); return; } @@ -919,76 +974,83 @@ namespace StartWindowDraw { nextCall(1, nextf, p); } - static void f0(PPUPriv &p) { + void f0(PPUPriv& p) { if (p.xpos == p.endx) { p.tileword = p.ntileword; - p.attrib = p.nattrib; - p.endx = p.xpos < 160 ? p.xpos + 8 : 168; + p.attrib = p.nattrib; + p.endx = std::min(1u * xpos_end, p.xpos + 1u * tile_len); } - p.wscx = 8 - p.xpos; + p.wscx = tile_len - p.xpos; if (p.winDrawState & win_draw_started) { - p.reg1 = p.vram[(p.lcdc << 4 & 0x400) - + (p.winYPos & 0xF8) * 4 + 0x1800]; - p.nattrib = p.vram[(p.lcdc << 4 & 0x400) - + (p.winYPos & 0xF8) * 4 + 0x3800]; - } else { - p.reg1 = p.vram[(p.lcdc << 7 & 0x400) - + ((p.scy + p.lyCounter.ly()) & 0xF8) * 4 + 0x1800]; - p.nattrib = p.vram[(p.lcdc << 7 & 0x400) - + ((p.scy + p.lyCounter.ly()) & 0xF8) * 4 + 0x3800]; + p.reg1 = p.vram[tile_map_size / lcdc_wtmsel * (p.lcdc & lcdc_wtmsel) + + tile_map_len / tile_len * (p.winYPos & (0x100 - tile_len)) + + tile_map_begin]; + p.nattrib = p.vram[tile_map_size / lcdc_wtmsel * (p.lcdc & lcdc_wtmsel) + + tile_map_len / tile_len * (p.winYPos & (0x100 - tile_len)) + + tile_map_begin + vram_bank_size]; + } + else { + p.reg1 = p.vram[tile_map_size / lcdc_bgtmsel * (p.lcdc & lcdc_bgtmsel) + + tile_map_len / tile_len * ((p.scy + p.lyCounter.ly()) & (0x100 - tile_len)) + + tile_map_begin]; + p.nattrib = p.vram[tile_map_size / lcdc_bgtmsel * (p.lcdc & lcdc_bgtmsel) + + tile_map_len / tile_len * ((p.scy + p.lyCounter.ly()) & (0x100 - tile_len)) + + tile_map_begin + vram_bank_size]; } inc(f1_, p); } - static void f1(PPUPriv &p) { + void f1(PPUPriv& p) { inc(f2_, p); } - static void f2(PPUPriv &p) { + void f2(PPUPriv& p) { p.reg0 = loadTileDataByte0(p); inc(f3_, p); } - static void f3(PPUPriv &p) { + void f3(PPUPriv& p) { inc(f4_, p); } - static void f4(PPUPriv &p) { + void f4(PPUPriv& p) { int const r1 = loadTileDataByte1(p); - p.ntileword = (expand_lut + (p.nattrib << 3 & 0x100))[p.reg0] - + (expand_lut + (p.nattrib << 3 & 0x100))[r1 ] * 2; + p.ntileword = (expand_lut + (0x100 / attr_xflip * p.nattrib & 0x100))[p.reg0] + + (expand_lut + (0x100 / attr_xflip * p.nattrib & 0x100))[r1] * 2; inc(f5_, p); } - static void f5(PPUPriv &p) { + void f5(PPUPriv& p) { inc(Tile::f0_, p); } } namespace LoadSprites { - static void inc(PPUState const &nextf, PPUPriv &p) { + void inc(PPUState const& nextf, PPUPriv& p) { plotPixelIfNoSprite(p); if (p.xpos == p.endx) { - if (p.xpos < 168) { + if (p.xpos < xpos_end) { nextCall(1, Tile::f0_, p); - } else - xpos168(p); - } else + } + else + xposEnd(p); + } + else nextCall(1, nextf, p); } - static void f0(PPUPriv &p) { + void f0(PPUPriv& p) { p.reg1 = p.spriteMapper.oamram()[p.spriteList[p.currentSprite].oampos + 2]; nextCall(1, f1_, p); } - static void f1(PPUPriv &p) { + void f1(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); @@ -997,40 +1059,44 @@ namespace LoadSprites { inc(f2_, p); } - static void f2(PPUPriv &p) { + void f2(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); unsigned const spline = - ( p.spriteList[p.currentSprite].attrib & attr_yflip - ? p.spriteList[p.currentSprite].line ^ 15 - : p.spriteList[p.currentSprite].line ) * 2; - p.reg0 = p.vram[(p.spriteList[p.currentSprite].attrib << 10 & p.cgb * 0x2000) - + (lcdcObj2x(p) ? (p.reg1 * 16 & ~16) | spline : p.reg1 * 16 | (spline & ~16))]; + (p.spriteList[p.currentSprite].attrib & attr_yflip + ? p.spriteList[p.currentSprite].line ^ (2 * tile_len - 1) + : p.spriteList[p.currentSprite].line) * tile_line_size; + unsigned const ts = tile_size; + p.reg0 = p.vram[vram_bank_size / attr_tdbank + * (p.spriteList[p.currentSprite].attrib & p.cgb * attr_tdbank) + + (lcdcObj2x(p) ? (p.reg1 * ts & ~ts) | spline : p.reg1 * ts | (spline & ~ts))]; inc(f3_, p); } - static void f3(PPUPriv &p) { + void f3(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); inc(f4_, p); } - static void f4(PPUPriv &p) { + void f4(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); unsigned const spline = - ( p.spriteList[p.currentSprite].attrib & attr_yflip - ? p.spriteList[p.currentSprite].line ^ 15 - : p.spriteList[p.currentSprite].line ) * 2; - p.reg1 = p.vram[(p.spriteList[p.currentSprite].attrib << 10 & p.cgb * 0x2000) - + (lcdcObj2x(p) ? (p.reg1 * 16 & ~16) | spline : p.reg1 * 16 | (spline & ~16)) + 1]; + (p.spriteList[p.currentSprite].attrib & attr_yflip + ? p.spriteList[p.currentSprite].line ^ (2 * tile_len - 1) + : p.spriteList[p.currentSprite].line) * tile_line_size; + unsigned const ts = tile_size; + p.reg1 = p.vram[vram_bank_size / attr_tdbank + * (p.spriteList[p.currentSprite].attrib & p.cgb * attr_tdbank) + + (lcdcObj2x(p) ? (p.reg1 * ts & ~ts) | spline : p.reg1 * ts | (spline & ~ts)) + 1]; inc(f5_, p); } - static void f5(PPUPriv &p) { + void f5(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); @@ -1040,21 +1106,25 @@ namespace LoadSprites { if (entry == p.nextSprite) { ++p.nextSprite; - } else { + } + else { entry = p.nextSprite - 1; p.spriteList[entry] = p.spriteList[p.currentSprite]; } - p.spwordList[entry] = expand_lut[p.reg0 + (p.spriteList[entry].attrib << 3 & 0x100)] - + expand_lut[p.reg1 + (p.spriteList[entry].attrib << 3 & 0x100)] * 2; + p.spwordList[entry] = + expand_lut[p.reg0 + (0x100 / attr_xflip * p.spriteList[entry].attrib & 0x100)] + + expand_lut[p.reg1 + (0x100 / attr_xflip * p.spriteList[entry].attrib & 0x100)] * 2; p.spriteList[entry].spx = p.xpos; if (p.xpos == p.endx) { - if (p.xpos < 168) { + if (p.xpos < xpos_end) { nextCall(1, Tile::f0_, p); - } else - xpos168(p); - } else { + } + else + xposEnd(p); + } + else { p.nextCallPtr = &Tile::f5_; nextCall(1, Tile::f5_, p); } @@ -1062,55 +1132,62 @@ namespace LoadSprites { } namespace Tile { - static void inc(PPUState const &nextf, PPUPriv &p) { + void inc(PPUState const& nextf, PPUPriv& p) { plotPixelIfNoSprite(p); - if (p.xpos == 168) { - xpos168(p); - } else + if (p.xpos == xpos_end) { + xposEnd(p); + } + else nextCall(1, nextf, p); } - static void f0(PPUPriv &p) { + void f0(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); doFullTilesUnrolled(p); - if (p.xpos == 168) { + if (p.xpos == xpos_end) { ++p.cycles; - return xpos168(p); + return xposEnd(p); } p.tileword = p.ntileword; - p.attrib = p.nattrib; - p.endx = p.xpos < 160 ? p.xpos + 8 : 168; + p.attrib = p.nattrib; + p.endx = std::min(1u * xpos_end, p.xpos + 1u * tile_len); if (p.winDrawState & win_draw_started) { - p.reg1 = p.vram[(p.lcdc << 4 & 0x400) - + (p.winYPos & 0xF8) * 4 - + ((p.xpos + p.wscx) >> 3 & 0x1F) + 0x1800]; - p.nattrib = p.vram[(p.lcdc << 4 & 0x400) - + (p.winYPos & 0xF8) * 4 - + ((p.xpos + p.wscx) >> 3 & 0x1F) + 0x3800]; - } else { - p.reg1 = p.vram[((p.lcdc << 7 | (p.scx + p.xpos + 1 - p.cgb) >> 3) & 0x41F) - + ((p.scy + p.lyCounter.ly()) & 0xF8) * 4 + 0x1800]; - p.nattrib = p.vram[((p.lcdc << 7 | (p.scx + p.xpos + 1 - p.cgb) >> 3) & 0x41F) - + ((p.scy + p.lyCounter.ly()) & 0xF8) * 4 + 0x3800]; + p.reg1 = p.vram[tile_map_size / lcdc_wtmsel * (p.lcdc & lcdc_wtmsel) + + tile_map_len / tile_len * (p.winYPos & (0x100 - tile_len)) + + (p.xpos + p.wscx) / tile_len % tile_map_len + tile_map_begin]; + p.nattrib = p.vram[tile_map_size / lcdc_wtmsel * (p.lcdc & lcdc_wtmsel) + + tile_map_len / tile_len * (p.winYPos & (0x100 - tile_len)) + + (p.xpos + p.wscx) / tile_len % tile_map_len + tile_map_begin + + vram_bank_size]; + } + else { + p.reg1 = p.vram[((tile_map_size / lcdc_bgtmsel * p.lcdc | (p.scx + p.xpos + 1u - p.cgb) / tile_len) + & (tile_map_size + tile_map_len - 1)) + + tile_map_len / tile_len * ((p.scy + p.lyCounter.ly()) & (0x100 - tile_len)) + + tile_map_begin]; + p.nattrib = p.vram[((tile_map_size / lcdc_bgtmsel * p.lcdc | (p.scx + p.xpos + 1u - p.cgb) / tile_len) + & (tile_map_size + tile_map_len - 1)) + + tile_map_len / tile_len * ((p.scy + p.lyCounter.ly()) & (0x100 - tile_len)) + + tile_map_begin + vram_bank_size]; } inc(f1_, p); } - static void f1(PPUPriv &p) { + void f1(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); inc(f2_, p); } - static void f2(PPUPriv &p) { + void f2(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); @@ -1118,31 +1195,32 @@ namespace Tile { inc(f3_, p); } - static void f3(PPUPriv &p) { + void f3(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); inc(f4_, p); } - static void f4(PPUPriv &p) { + void f4(PPUPriv& p) { if ((p.winDrawState & win_draw_start) && handleWinDrawStartReq(p)) return StartWindowDraw::f0(p); int const r1 = loadTileDataByte1(p); - p.ntileword = (expand_lut + (p.nattrib << 3 & 0x100))[p.reg0] - + (expand_lut + (p.nattrib << 3 & 0x100))[r1 ] * 2; + p.ntileword = (expand_lut + (0x100 / attr_xflip * p.nattrib & 0x100))[p.reg0] + + (expand_lut + (0x100 / attr_xflip * p.nattrib & 0x100))[r1] * 2; plotPixelIfNoSprite(p); - if (p.xpos == 168) { - xpos168(p); - } else + if (p.xpos == xpos_end) { + xposEnd(p); + } + else nextCall(1, f5_, p); } - static void f5(PPUPriv &p) { + void f5(PPUPriv& p) { int endx = p.endx; p.nextCallPtr = &f5_; @@ -1164,10 +1242,11 @@ namespace Tile { plotPixel(p); if (p.xpos == endx) { - if (endx < 168) { + if (endx < xpos_end) { nextCall(1, f0_, p); - } else - xpos168(p); + } + else + xposEnd(p); return; } @@ -1179,29 +1258,30 @@ namespace Tile { namespace M2_Ly0 { static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, unsigned winDrawState, - int targetxpos, unsigned cycles); + int targetxpos, unsigned cycles); } namespace M2_LyNon0 { static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, unsigned winDrawState, - int targetxpos, unsigned cycles); + int targetxpos, unsigned cycles); } namespace M3Loop { -static unsigned predictCyclesUntilXposNextLine( - PPUPriv const &p, unsigned winDrawState, int const targetx) { - if (p.wx == 166 && !p.cgb && p.xpos < 167 + unsigned predictCyclesUntilXposNextLine( + PPUPriv const& p, unsigned winDrawState, int const targetx) { + if (p.wx == lcd_hres + 6 && !p.cgb && p.xpos < lcd_hres + 7 && (p.weMaster || (p.wy2 == p.lyCounter.ly() && lcdcWinEn(p)))) { - winDrawState = win_draw_start | (lcdcWinEn(p) ? win_draw_started : 0); + winDrawState = win_draw_start | (lcdcWinEn(p) ? win_draw_started : 0); + } + + unsigned const cycles = (nextM2Time(p) - p.now) >> p.lyCounter.isDoubleSpeed(); + + return p.lyCounter.ly() == lcd_vres - 1 + ? M2_Ly0::predictCyclesUntilXpos_f0(p, winDrawState, targetx, cycles) + : M2_LyNon0::predictCyclesUntilXpos_f0(p, winDrawState, targetx, cycles); } - unsigned const cycles = (nextM2Time(p) - p.now) >> p.lyCounter.isDoubleSpeed(); - - return p.lyCounter.ly() == 143 - ? M2_Ly0::predictCyclesUntilXpos_f0(p, winDrawState, targetx, cycles) - : M2_LyNon0::predictCyclesUntilXpos_f0(p, winDrawState, targetx, cycles); -} namespace StartWindowDraw { static unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, int xpos, @@ -1210,23 +1290,22 @@ namespace StartWindowDraw { } namespace Tile { - static unsigned char const * addSpriteCycles(unsigned char const *nextSprite, - unsigned char const *spriteEnd, unsigned char const *const spxOf, - unsigned const maxSpx, unsigned const firstTileXpos, - unsigned prevSpriteTileNo, unsigned *const cyclesAccumulator) { - unsigned sum = 0; + unsigned char const* addSpriteCycles(unsigned char const* nextSprite, + unsigned char const* spriteEnd, unsigned char const* const spxOf, + unsigned const maxSpx, unsigned const firstTileXpos, + unsigned prevSpriteTileNo, unsigned* const cyclesAccumulator) { + int sum = 0; - while (nextSprite < spriteEnd && spxOf[*nextSprite] <= maxSpx) { - unsigned cycles = 6; - unsigned const distanceFromTileStart = (spxOf[*nextSprite] - firstTileXpos) & 7; - unsigned const tileNo = (spxOf[*nextSprite] - firstTileXpos) & ~7; + for (; nextSprite < spriteEnd && spxOf[*nextSprite] <= maxSpx; ++nextSprite) { + int cycles = 6; + int const distanceFromTileStart = (spxOf[*nextSprite] - firstTileXpos) % tile_len; + unsigned const tileNo = (spxOf[*nextSprite] - firstTileXpos) & -tile_len; if (distanceFromTileStart < 5 && tileNo != prevSpriteTileNo) cycles = 11 - distanceFromTileStart; prevSpriteTileNo = tileNo; sum += cycles; - ++nextSprite; } *cyclesAccumulator += sum; @@ -1234,12 +1313,12 @@ namespace Tile { return nextSprite; } - static unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, int const xpos, - int const endx, unsigned const ly, unsigned const nextSprite, - bool const weMaster, unsigned char winDrawState, int const fno, - int const targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_fn(PPUPriv const& p, int const xpos, + int const endx, unsigned const ly, unsigned const nextSprite, + bool const weMaster, unsigned char winDrawState, int const fno, + int const targetx, unsigned cycles) { if ((winDrawState & win_draw_start) - && handleWinDrawStartReq(p, xpos, winDrawState)) { + && handleWinDrawStartReq(p, xpos, winDrawState)) { return StartWindowDraw::predictCyclesUntilXpos_fn(p, xpos, endx, ly, nextSprite, weMaster, winDrawState, 0, targetx, cycles); } @@ -1247,31 +1326,31 @@ namespace Tile { if (xpos > targetx) return predictCyclesUntilXposNextLine(p, winDrawState, targetx); - enum { NO_TILE_NUMBER = 1 }; // low bit set, so it will never be equal to an actual tile number. + enum { tileno_none = 1 }; // low bit set, so it will never be equal to an actual tile number. int nwx = 0xFF; cycles += targetx - xpos; - if (p.wx - unsigned(xpos) < targetx - unsigned(xpos) - && lcdcWinEn(p) && (weMaster || p.wy2 == ly) - && !(winDrawState & win_draw_started) - && (p.cgb || p.wx != 166)) { + if (p.wx - 1u * xpos < targetx - 1u * xpos + && lcdcWinEn(p) && (weMaster || p.wy2 == ly) + && !(winDrawState & win_draw_started) + && (p.cgb || p.wx != lcd_hres + 6)) { nwx = p.wx; cycles += 6; } if (lcdcObjEn(p) | p.cgb) { - unsigned char const *sprite = p.spriteMapper.sprites(ly); - unsigned char const *const spriteEnd = sprite + p.spriteMapper.numSprites(ly); + unsigned char const* sprite = p.spriteMapper.sprites(ly); + unsigned char const* const spriteEnd = sprite + p.spriteMapper.numSprites(ly); sprite += nextSprite; if (sprite < spriteEnd) { int const spx = p.spriteMapper.posbuf()[*sprite + 1]; - unsigned firstTileXpos = endx & 7u; // ok even if endx is capped at 168, - // because fno will be used. - unsigned prevSpriteTileNo = (xpos - firstTileXpos) & ~7; // this tile. all sprites on this - // tile will now add 6 cycles. - // except this one + unsigned firstTileXpos = endx % (1u * tile_len); // ok even if endx is capped at 168, + // because fno will be used. + unsigned prevSpriteTileNo = (xpos - firstTileXpos) & -tile_len; // this tile. all sprites on this + // tile will now add 6 cycles. + // except this one. if (fno + spx - xpos < 5 && spx <= nwx) { cycles += 11 - (fno + spx - xpos); sprite += 1; @@ -1279,56 +1358,56 @@ namespace Tile { if (nwx < targetx) { sprite = addSpriteCycles(sprite, spriteEnd, p.spriteMapper.posbuf() + 1, - nwx, firstTileXpos, prevSpriteTileNo, &cycles); + nwx, firstTileXpos, prevSpriteTileNo, &cycles); firstTileXpos = nwx + 1; - prevSpriteTileNo = NO_TILE_NUMBER; + prevSpriteTileNo = tileno_none; } addSpriteCycles(sprite, spriteEnd, p.spriteMapper.posbuf() + 1, - targetx, firstTileXpos, prevSpriteTileNo, &cycles); + targetx, firstTileXpos, prevSpriteTileNo, &cycles); } } return cycles; } - static unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, - int endx, int fno, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_fn(PPUPriv const& p, + int endx, int fno, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.xpos, endx, p.lyCounter.ly(), p.nextSprite, p.weMaster, p.winDrawState, fno, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { - return predictCyclesUntilXpos_fn(p, p.xpos < 160 ? p.xpos + 8 : 168, 0, targetx, cycles); + unsigned predictCyclesUntilXpos_f0(PPUPriv const& p, int targetx, unsigned cycles) { + return predictCyclesUntilXpos_fn(p, std::min(1u * xpos_end, p.xpos + 1u * tile_len), 0, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f1(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 1, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f2(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f2(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 2, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f3(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f3(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 3, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f4(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f4(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 4, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f5(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f5(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 5, targetx, cycles); } } namespace StartWindowDraw { - static unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, int xpos, + unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, int xpos, int const endx, unsigned const ly, unsigned const nextSprite, bool const weMaster, unsigned const winDrawState, int const fno, int const targetx, unsigned cycles) { if (xpos > targetx) return predictCyclesUntilXposNextLine(p, winDrawState, targetx); - unsigned cinc = 6 - fno; + int cinc = 6 - fno; if (!lcdcWinEn(p) && p.cgb) { - unsigned xinc = std::min(cinc, std::min(endx, targetx + 1) - xpos); + int xinc = std::min(cinc, std::min(endx, targetx + 1) - xpos); if ((lcdcObjEn(p) | p.cgb) && p.spriteList[nextSprite].spx < xpos + xinc) { xpos = p.spriteList[nextSprite].spx; @@ -1341,44 +1420,44 @@ namespace StartWindowDraw { cycles += cinc; if (xpos <= targetx) { - return Tile::predictCyclesUntilXpos_fn(p, xpos, xpos < 160 ? xpos + 8 : 168, + return Tile::predictCyclesUntilXpos_fn(p, xpos, std::min(xpos_end, xpos + tile_len), ly, nextSprite, weMaster, winDrawState, 0, targetx, cycles); } return cycles - 1; } - static unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, + unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, int endx, int fno, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.xpos, endx, p.lyCounter.ly(), p.nextSprite, p.weMaster, p.winDrawState, fno, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { int endx = p.xpos == p.endx - ? (p.xpos < 160 ? p.xpos + 8 : 168) - : p.endx; + ? std::min(1u * xpos_end, p.xpos + 1u * tile_len) + : p.endx; return predictCyclesUntilXpos_fn(p, endx, 0, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 1, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f2(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f2(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 2, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f3(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f3(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 3, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f4(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f4(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 4, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f5(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f5(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, p.endx, 5, targetx, cycles); } } namespace LoadSprites { - static unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, + unsigned predictCyclesUntilXpos_fn(PPUPriv const &p, int const fno, int const targetx, unsigned cycles) { unsigned nextSprite = p.nextSprite; if (lcdcObjEn(p) | p.cgb) { @@ -1390,22 +1469,22 @@ namespace LoadSprites { nextSprite, p.weMaster, p.winDrawState, 5, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, 0, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, 1, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f2(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f2(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, 2, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f3(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f3(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, 3, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f4(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f4(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, 4, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f5(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f5(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_fn(p, 5, targetx, cycles); } } @@ -1413,82 +1492,81 @@ namespace LoadSprites { } // namespace M3Loop namespace M3Start { - static unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, unsigned xpos, unsigned ly, - bool weMaster, unsigned winDrawState, int targetx, unsigned cycles) { - cycles += std::min(unsigned(p.scx - xpos) & 7, max_m3start_cycles - xpos) + 1 - p.cgb; - return M3Loop::Tile::predictCyclesUntilXpos_fn(p, 0, 8 - (p.scx & 7), ly, 0, - weMaster, winDrawState, std::min(p.scx & 7, 5), targetx, cycles); + unsigned predictCyclesUntilXpos_f1(PPUPriv const& p, unsigned xpos, unsigned ly, + bool weMaster, unsigned winDrawState, int targetx, unsigned cycles) { + cycles += std::min((p.scx - xpos) % tile_len, max_m3start_cycles - xpos) + 1 - p.cgb; + return M3Loop::Tile::predictCyclesUntilXpos_fn(p, 0, tile_len - p.scx % tile_len, ly, 0, + weMaster, winDrawState, std::min(p.scx % (1u * tile_len), 5u), targetx, cycles); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, unsigned ly, + unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, unsigned ly, bool weMaster, unsigned winDrawState, int targetx, unsigned cycles) { winDrawState = (winDrawState & win_draw_start) && lcdcWinEn(p) ? win_draw_started : 0; return predictCyclesUntilXpos_f1(p, 0, ly, weMaster, winDrawState, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { unsigned ly = p.lyCounter.ly() + (p.lyCounter.time() - p.now < 16); return predictCyclesUntilXpos_f0(p, ly, p.weMaster, p.winDrawState, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_f1(p, p.xpos, p.lyCounter.ly(), p.weMaster, p.winDrawState, targetx, cycles); } } namespace M2_Ly0 { - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, - unsigned winDrawState, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f0(PPUPriv const& p, + unsigned winDrawState, int targetx, unsigned cycles) { bool weMaster = lcdcWinEn(p) && 0 == p.wy; unsigned ly = 0; - return M3Start::predictCyclesUntilXpos_f0(p, ly, weMaster, - winDrawState, targetx, cycles + m3StartLineCycle(p.cgb)); + return M3Start::predictCyclesUntilXpos_f0(p, ly, weMaster, winDrawState, targetx, + cycles + m3StartLineCycle(p.cgb) - weMasterCheckLy0LineCycle(p.cgb)); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f0(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_f0(p, p.winDrawState, targetx, cycles); } } namespace M2_LyNon0 { - static unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, bool weMaster, - unsigned winDrawState, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f1(PPUPriv const& p, bool weMaster, + unsigned winDrawState, int targetx, unsigned cycles) { unsigned ly = p.lyCounter.ly() + 1; weMaster |= lcdcWinEn(p) && ly == p.wy; return M3Start::predictCyclesUntilXpos_f0(p, ly, weMaster, winDrawState, targetx, - cycles + 456 - weMasterCheckAfterLyIncLineCycle(p.cgb) + m3StartLineCycle(p.cgb)); + cycles + lcd_cycles_per_line - weMasterCheckAfterLyIncLineCycle(p.cgb) + m3StartLineCycle(p.cgb)); } - static unsigned predictCyclesUntilXpos_f1(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f1(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_f1(p, p.weMaster, p.winDrawState, targetx, cycles); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, - unsigned winDrawState, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f0(PPUPriv const& p, + unsigned winDrawState, int targetx, unsigned cycles) { bool weMaster = p.weMaster || (lcdcWinEn(p) && p.lyCounter.ly() == p.wy); return predictCyclesUntilXpos_f1(p, weMaster, winDrawState, targetx, cycles + weMasterCheckAfterLyIncLineCycle(p.cgb) - - weMasterCheckPriorToLyIncLineCycle(p.cgb)); + - weMasterCheckPriorToLyIncLineCycle(p.cgb)); } - static unsigned predictCyclesUntilXpos_f0(PPUPriv const &p, int targetx, unsigned cycles) { + unsigned predictCyclesUntilXpos_f0(PPUPriv const& p, int targetx, unsigned cycles) { return predictCyclesUntilXpos_f0(p, p.winDrawState, targetx, cycles); } } } // anon namespace -namespace gambatte { - PPUPriv::PPUPriv(NextM0Time &nextM0Time, unsigned char const *const oamram, unsigned char const *const vram) -: nextSprite(0) +: spriteList() +, spwordList() +, nextSprite(0) , currentSprite(0xFF) -, layersMask(layer_mask_bg | layer_mask_window | layer_mask_obj) , vram(vram) , nextCallPtr(&M2_Ly0::f0_) , now(0) @@ -1515,19 +1593,6 @@ PPUPriv::PPUPriv(NextM0Time &nextM0Time, unsigned char const *const oamram, unsi , cgb(false) , weMaster(false) { - std::memset(spriteList, 0, sizeof spriteList); - std::memset(spwordList, 0, sizeof spwordList); -} - -static void saveSpriteList(PPUPriv const &p, SaveState &ss) { - for (unsigned i = 0; i < 10; ++i) { - ss.ppu.spAttribList[i] = p.spriteList[i].attrib; - ss.ppu.spByte0List[i] = p.spwordList[i] & 0xFF; - ss.ppu.spByte1List[i] = p.spwordList[i] >> 8; - } - - ss.ppu.nextSprite = p.nextSprite; - ss.ppu.currentSprite = p.currentSprite; } namespace { @@ -1560,7 +1625,7 @@ struct CycleState { operator long() const { return cycle; } }; -static PPUState const * decodeM3LoopState(unsigned state) { +PPUState const * decodeM3LoopState(unsigned state) { switch (state) { case M3Loop::Tile::ID0: return &M3Loop::Tile::f0_; case M3Loop::Tile::ID1: return &M3Loop::Tile::f1_; @@ -1587,57 +1652,65 @@ static PPUState const * decodeM3LoopState(unsigned state) { return 0; } -static long cyclesUntilM0Upperbound(PPUPriv const &p) { - long cycles = 168 - p.xpos + 6; - for (unsigned i = p.nextSprite; i < 10 && p.spriteList[i].spx < 168; ++i) +long cyclesUntilM0Upperbound(PPUPriv const& p) { + long cycles = xpos_end - p.xpos + 6; + for (int i = p.nextSprite; i < lcd_max_num_sprites_per_line && p.spriteList[i].spx < xpos_end; ++i) cycles += 11; return cycles; } -static void loadSpriteList(PPUPriv &p, SaveState const &ss) { - if (ss.ppu.videoCycles < 144 * 456UL && ss.ppu.xpos < 168) { - unsigned const ly = ss.ppu.videoCycles / 456; - unsigned const numSprites = p.spriteMapper.numSprites(ly); - unsigned char const *const sprites = p.spriteMapper.sprites(ly); +void saveSpriteList(PPUPriv const& p, SaveState& ss) { + for (int i = 0; i < lcd_max_num_sprites_per_line; ++i) { + ss.ppu.spAttribList[i] = p.spriteList[i].attrib; + ss.ppu.spByte0List[i] = p.spwordList[i] & 0xFF; + ss.ppu.spByte1List[i] = p.spwordList[i] >> 8; + } - for (unsigned i = 0; i < numSprites; ++i) { - unsigned pos = sprites[i]; - unsigned spy = p.spriteMapper.posbuf()[pos ]; - unsigned spx = p.spriteMapper.posbuf()[pos+1]; + ss.ppu.nextSprite = p.nextSprite; + ss.ppu.currentSprite = p.currentSprite; +} - p.spriteList[i].spx = spx; - p.spriteList[i].line = ly + 16u - spy; +void loadSpriteList(PPUPriv& p, SaveState const& ss) { + if (ss.ppu.videoCycles < 1ul * lcd_vres * lcd_cycles_per_line && ss.ppu.xpos < xpos_end) { + int const ly = ss.ppu.videoCycles / lcd_cycles_per_line; + int const numSprites = p.spriteMapper.numSprites(ly); + unsigned char const* const sprites = p.spriteMapper.sprites(ly); + + for (int i = 0; i < numSprites; ++i) { + int const pos = sprites[i]; + int const spy = p.spriteMapper.posbuf()[pos]; + int const spx = p.spriteMapper.posbuf()[pos + 1]; + + p.spriteList[i].spx = spx; + p.spriteList[i].line = ly + 2 * tile_len - spy; p.spriteList[i].oampos = pos * 2; p.spriteList[i].attrib = ss.ppu.spAttribList[i] & 0xFF; - p.spwordList[i] = (ss.ppu.spByte1List[i] * 0x100 + ss.ppu.spByte0List[i]) & 0xFFFF; + p.spwordList[i] = (ss.ppu.spByte1List[i] * 0x100l + ss.ppu.spByte0List[i]) & 0xFFFF; } p.spriteList[numSprites].spx = 0xFF; - p.nextSprite = std::min(ss.ppu.nextSprite, numSprites); + p.nextSprite = std::min(1u * ss.ppu.nextSprite, 1u * numSprites); while (p.spriteList[p.nextSprite].spx < ss.ppu.xpos) ++p.nextSprite; - p.currentSprite = std::min(p.nextSprite, ss.ppu.currentSprite); + p.currentSprite = std::min(p.nextSprite, ss.ppu.currentSprite); } } } -void PPU::loadState(SaveState const &ss, unsigned char const *const oamram) { - PPUState const *const m3loopState = decodeM3LoopState(ss.ppu.state); - long const videoCycles = std::min(ss.ppu.videoCycles, 70223UL); +void PPU::loadState(SaveState const& ss, unsigned char const* const oamram) { + PPUState const* const m3loopState = decodeM3LoopState(ss.ppu.state); + long const videoCycles = std::min(ss.ppu.videoCycles, lcd_cycles_per_frame - 1ul); bool const ds = p_.cgb & ss.mem.ioamhram.get()[0x14D] >> 7; - long const vcycs = videoCycles - ds * m2_ds_offset < 0 - ? videoCycles - ds * m2_ds_offset + 70224 - : videoCycles - ds * m2_ds_offset; - long const lineCycles = static_cast(vcycs) % 456; + long const lineCycles = static_cast(videoCycles) % lcd_cycles_per_line; p_.now = ss.cpu.cycleCounter; p_.lcdc = ss.mem.ioamhram.get()[0x140]; p_.lyCounter.setDoubleSpeed(ds); - p_.lyCounter.reset(std::min(ss.ppu.videoCycles, 70223ul), ss.cpu.cycleCounter); + p_.lyCounter.reset(videoCycles, ss.cpu.cycleCounter); p_.spriteMapper.loadState(ss, oamram); p_.winYPos = ss.ppu.winYPos; p_.scy = ss.mem.ioamhram.get()[0x142]; @@ -1645,9 +1718,9 @@ void PPU::loadState(SaveState const &ss, unsigned char const *const oamram) { p_.wy = ss.mem.ioamhram.get()[0x14A]; p_.wy2 = ss.ppu.oldWy; p_.wx = ss.mem.ioamhram.get()[0x14B]; - p_.xpos = std::min(ss.ppu.xpos, 168); - p_.endx = (p_.xpos & ~7) + (ss.ppu.endx & 7); - p_.endx = std::min(p_.endx <= p_.xpos ? p_.endx + 8 : p_.endx, 168); + p_.xpos = std::min(1u * xpos_end, 1u * ss.ppu.xpos); + p_.endx = (p_.xpos & -1u * tile_len) + ss.ppu.endx % tile_len; + p_.endx = std::min(1u * xpos_end, p_.endx <= p_.xpos ? p_.endx + 1u * tile_len : p_.endx); p_.reg0 = ss.ppu.reg0 & 0xFF; p_.reg1 = ss.ppu.reg1 & 0xFF; p_.tileword = ss.ppu.tileword & 0xFFFF; @@ -1658,34 +1731,36 @@ void PPU::loadState(SaveState const &ss, unsigned char const *const oamram) { p_.weMaster = ss.ppu.weMaster; p_.winDrawState = ss.ppu.winDrawState & (win_draw_start | win_draw_started); p_.lastM0Time = p_.now - ss.ppu.lastM0Time; - p_.cgb = ss.ppu.isCgb; + p_.cgbDmg = !ss.ppu.notCgbDmg; loadSpriteList(p_, ss); - if (m3loopState && videoCycles < 144 * 456L && p_.xpos < 168 - && lineCycles + cyclesUntilM0Upperbound(p_) < weMasterCheckPriorToLyIncLineCycle(p_.cgb)) { + if (m3loopState && videoCycles < 1l * lcd_vres * lcd_cycles_per_line && p_.xpos < xpos_end + && lineCycles + cyclesUntilM0Upperbound(p_) < weMasterCheckPriorToLyIncLineCycle(p_.cgb)) { p_.nextCallPtr = m3loopState; p_.cycles = -1; - } else if (vcycs < 143 * 456L + static_cast(m3StartLineCycle(p_.cgb)) + max_m3start_cycles) { + } + else if (videoCycles < (lcd_vres - 1l) * lcd_cycles_per_line + m3StartLineCycle(p_.cgb) + max_m3start_cycles) { CycleState const lineCycleStates[] = { { &M3Start::f0_, m3StartLineCycle(p_.cgb) }, { &M3Start::f1_, m3StartLineCycle(p_.cgb) + max_m3start_cycles }, { &M2_LyNon0::f0_, weMasterCheckPriorToLyIncLineCycle(p_.cgb) }, { &M2_LyNon0::f1_, weMasterCheckAfterLyIncLineCycle(p_.cgb) }, - { &M3Start::f0_, m3StartLineCycle(p_.cgb) + 456 } + { &M3Start::f0_, m3StartLineCycle(p_.cgb) + lcd_cycles_per_line } }; std::size_t const pos = - upperBound(lineCycleStates, lineCycles); + upperBound(lineCycleStates, lineCycles); p_.cycles = lineCycles - lineCycleStates[pos].cycle; p_.nextCallPtr = lineCycleStates[pos].state; if (&M3Start::f1_ == lineCycleStates[pos].state) { - p_.xpos = lineCycles - m3StartLineCycle(p_.cgb) + 1; + p_.xpos = lineCycles - m3StartLineCycle(p_.cgb) + 1; p_.cycles = -1; } - } else { - p_.cycles = vcycs - 70224; + } + else { + p_.cycles = videoCycles - lcd_cycles_per_frame - weMasterCheckLy0LineCycle(p_.cgb); p_.nextCallPtr = &M2_Ly0::f0_; } } @@ -1706,20 +1781,14 @@ void PPU::resetCc(unsigned long const oldCc, unsigned long const newCc) { p_.spriteMapper.resetCycleCounter(oldCc, newCc); } -void PPU::speedChange(unsigned long const cycleCounter) { - unsigned long const videoCycles = lcdcEn(p_) ? p_.lyCounter.frameCycles(p_.now) : 0; +void PPU::speedChange() { + unsigned long const now = p_.now; + unsigned long const videoCycles = lcdcEn(p_) ? p_.lyCounter.frameCycles(now) : 0; - p_.spriteMapper.preSpeedChange(cycleCounter); + p_.now -= p_.lyCounter.isDoubleSpeed(); + p_.spriteMapper.resetCycleCounter(now, p_.now); p_.lyCounter.setDoubleSpeed(!p_.lyCounter.isDoubleSpeed()); p_.lyCounter.reset(videoCycles, p_.now); - p_.spriteMapper.postSpeedChange(cycleCounter); - - if (&M2_Ly0::f0_ == p_.nextCallPtr || &M2_LyNon0::f0_ == p_.nextCallPtr) { - if (p_.lyCounter.isDoubleSpeed()) { - p_.cycles -= m2_ds_offset; - } else - p_.cycles += m2_ds_offset; - } } unsigned long PPU::predictedNextXposTime(unsigned xpos) const { @@ -1736,12 +1805,14 @@ void PPU::setLcdc(unsigned const lcdc, unsigned long const cc) { p_.weMaster = (lcdc & lcdc_we) && 0 == p_.wy; p_.winDrawState = 0; p_.nextCallPtr = &M3Start::f0_; - p_.cycles = -int(m3StartLineCycle(p_.cgb) + m2_ds_offset * p_.lyCounter.isDoubleSpeed()); - } else if ((p_.lcdc ^ lcdc) & lcdc_we) { + p_.cycles = -(m3StartLineCycle(p_.cgb) + 2); + } + else if ((p_.lcdc ^ lcdc) & lcdc_we) { if (!(lcdc & lcdc_we)) { - if (p_.winDrawState == win_draw_started || p_.xpos == 168) - p_.winDrawState &= ~win_draw_started; - } else if (p_.winDrawState == win_draw_start) { + if (p_.winDrawState == win_draw_started || p_.xpos == xpos_end) + p_.winDrawState &= ~(1u * win_draw_started); + } + else if (p_.winDrawState == win_draw_start) { p_.winDrawState |= win_draw_started; ++p_.winYPos; } @@ -1758,7 +1829,7 @@ void PPU::setLcdc(unsigned const lcdc, unsigned long const cc) { } void PPU::update(unsigned long const cc) { - int const cycles = (cc - p_.now) >> p_.lyCounter.isDoubleSpeed(); + long const cycles = (cc - p_.now) >> p_.lyCounter.isDoubleSpeed(); p_.now += cycles << p_.lyCounter.isDoubleSpeed(); p_.cycles += cycles; @@ -1833,5 +1904,3 @@ SYNCFUNC(PPU) NSS(p_.cgb); NSS(p_.weMaster); } - -} diff --git a/libgambatte/src/video/ppu.h b/libgambatte/src/video/ppu.h index edfb6a9b9f..927613b3f1 100644 --- a/libgambatte/src/video/ppu.h +++ b/libgambatte/src/video/ppu.h @@ -29,7 +29,14 @@ namespace gambatte { -enum { layer_mask_bg = 1, layer_mask_obj = 2, layer_mask_window = 4 }; +enum { + layer_mask_bg = 1, + layer_mask_obj = 2, + layer_mask_window = 4 }; +enum { + max_num_palettes = 8, + num_palette_entries = 4, + ppu_force_signed_enum = -1 }; class PPUFrameBuf { public: @@ -57,10 +64,10 @@ struct PPUState { }; struct PPUPriv { - unsigned long bgPalette[8 * 4]; - unsigned long spPalette[8 * 4]; - struct Sprite { unsigned char spx, oampos, line, attrib; } spriteList[11]; - unsigned short spwordList[11]; + unsigned long bgPalette[max_num_palettes * num_palette_entries]; + unsigned long spPalette[max_num_palettes * num_palette_entries]; + struct Sprite { unsigned char spx, oampos, line, attrib; } spriteList[lcd_max_num_sprites_per_line + 1]; + unsigned short spwordList[lcd_max_num_sprites_per_line + 1]; unsigned char nextSprite; unsigned char currentSprite; unsigned layersMask; @@ -96,6 +103,7 @@ struct PPUPriv { unsigned char endx; bool cgb; + bool cgbDmg; bool weMaster; PPUPriv(NextM0Time &nextM0Time, unsigned char const *oamram, unsigned char const *vram); @@ -110,6 +118,7 @@ public: unsigned long * bgPalette() { return p_.bgPalette; } bool cgb() const { return p_.cgb; } + bool cgbDmg() const { return p_.cgbDmg; } void doLyCountEvent() { p_.lyCounter.doEvent(); } unsigned long doSpriteMapEvent(unsigned long time) { return p_.spriteMapper.doEvent(time); } PPUFrameBuf const & frameBuf() const { return p_.framebuf; } @@ -127,6 +136,7 @@ public: void oamChange(unsigned char const *oamram, unsigned long cc) { p_.spriteMapper.oamChange(oamram, cc); } unsigned long predictedNextXposTime(unsigned xpos) const; void reset(unsigned char const *oamram, unsigned char const *vram, bool cgb); + void setCgbDmg(bool enabled) { p_.cgbDmg = enabled; } void resetCc(unsigned long oldCc, unsigned long newCc); void setFrameBuf(uint_least32_t *buf, std::ptrdiff_t pitch) { p_.framebuf.setBuf(buf, pitch); } void setLcdc(unsigned lcdc, unsigned long cc); @@ -136,7 +146,7 @@ public: void setWx(unsigned wx) { p_.wx = wx; } void setWy(unsigned wy) { p_.wy = wy; } void updateWy2() { p_.wy2 = p_.wy; } - void speedChange(unsigned long cycleCounter); + void speedChange(); unsigned long * spPalette() { return p_.spPalette; } void update(unsigned long cc); void setLayers(unsigned mask) { p_.layersMask = mask; } diff --git a/libgambatte/src/video/sprite_mapper.cpp b/libgambatte/src/video/sprite_mapper.cpp index 87ffa52759..2bc5cdcdb8 100644 --- a/libgambatte/src/video/sprite_mapper.cpp +++ b/libgambatte/src/video/sprite_mapper.cpp @@ -21,7 +21,8 @@ #include "next_m0_time.h" #include "../insertion_sort.h" #include -#include + +using namespace gambatte; namespace { @@ -37,9 +38,15 @@ private: unsigned char const *const spxlut_; }; +unsigned toPosCycles(unsigned long const cc, LyCounter const& lyCounter) { + unsigned lc = lyCounter.lineCycles(cc) + 1; + if (lc >= lcd_cycles_per_line) + lc -= lcd_cycles_per_line; + + return lc; } -namespace gambatte { +} SpriteMapper::OamReader::OamReader(LyCounter const &lyCounter, unsigned char const *oamram) : lyCounter_(lyCounter) @@ -49,45 +56,35 @@ SpriteMapper::OamReader::OamReader(LyCounter const &lyCounter, unsigned char con reset(oamram, false); } -void SpriteMapper::OamReader::reset(unsigned char const *const oamram, bool const cgb) { +void SpriteMapper::OamReader::reset(unsigned char const* const oamram, bool const cgb) { oamram_ = oamram; cgb_ = cgb; setLargeSpritesSrc(false); lu_ = 0; lastChange_ = 0xFF; - std::fill(szbuf_, szbuf_ + 40, largeSpritesSrc_); - - unsigned pos = 0; - unsigned distance = 80; - while (distance--) { - buf_[pos] = oamram[((pos * 2) & ~3) | (pos & 1)]; - ++pos; + std::fill_n(lsbuf_, sizeof lsbuf_ / sizeof * lsbuf_, largeSpritesSrc_); + for (int i = 0; i < lcd_num_oam_entries; ++i) { + buf_[2 * i] = oamram[4 * i]; + buf_[2 * i + 1] = oamram[4 * i + 1]; } } -static unsigned toPosCycles(unsigned long const cc, LyCounter const &lyCounter) { - unsigned lc = lyCounter.lineCycles(cc) + 3 - lyCounter.isDoubleSpeed() * 3u; - if (lc >= 456) - lc -= 456; - - return lc; -} - void SpriteMapper::OamReader::update(unsigned long const cc) { if (cc > lu_) { if (changed()) { unsigned const lulc = toPosCycles(lu_, lyCounter_); - unsigned pos = std::min(lulc, 80u); - unsigned distance = 80; + unsigned pos = std::min(lulc, 2u * lcd_num_oam_entries); + unsigned distance = 2 * lcd_num_oam_entries; - if ((cc - lu_) >> lyCounter_.isDoubleSpeed() < 456) { + if ((cc - lu_) >> lyCounter_.isDoubleSpeed() < lcd_cycles_per_line) { unsigned cclc = toPosCycles(cc, lyCounter_); - distance = std::min(cclc, 80u) - pos + (cclc < lulc ? 80 : 0); + distance = std::min(cclc, 2u * lcd_num_oam_entries) + - pos + (cclc < lulc ? 2 * lcd_num_oam_entries : 0); } { unsigned targetDistance = - lastChange_ - pos + (lastChange_ <= pos ? 80 : 0); + lastChange_ - pos + (lastChange_ <= pos ? 2 * lcd_num_oam_entries : 0); if (targetDistance <= distance) { distance = targetDistance; lastChange_ = 0xFF; @@ -96,16 +93,16 @@ void SpriteMapper::OamReader::update(unsigned long const cc) { while (distance--) { if (!(pos & 1)) { - if (pos == 80) + if (pos == 2 * lcd_num_oam_entries) pos = 0; - if (cgb_) - szbuf_[pos >> 1] = largeSpritesSrc_; + lsbuf_[pos / 2] = largeSpritesSrc_; - buf_[pos ] = oamram_[pos * 2 ]; - buf_[pos + 1] = oamram_[pos * 2 + 1]; - } else - szbuf_[pos >> 1] = (szbuf_[pos >> 1] & cgb_) | largeSpritesSrc_; + buf_[pos] = oamram_[2 * pos]; + buf_[pos + 1] = oamram_[2 * pos + 1]; + } + else + lsbuf_[pos / 2] = (lsbuf_[pos / 2] & cgb_) | largeSpritesSrc_; ++pos; } @@ -117,15 +114,15 @@ void SpriteMapper::OamReader::update(unsigned long const cc) { void SpriteMapper::OamReader::change(unsigned long cc) { update(cc); - lastChange_ = std::min(toPosCycles(lu_, lyCounter_), 80u); + lastChange_ = std::min(toPosCycles(lu_, lyCounter_), 2u * lcd_num_oam_entries); } -void SpriteMapper::OamReader::setStatePtrs(SaveState &state) { - state.ppu.oamReaderBuf.set(buf_, sizeof buf_); - state.ppu.oamReaderSzbuf.set(szbuf_, sizeof szbuf_ / sizeof *szbuf_); +void SpriteMapper::OamReader::setStatePtrs(SaveState& state) { + state.ppu.oamReaderBuf.set(buf_, sizeof buf_ / sizeof * buf_); + state.ppu.oamReaderSzbuf.set(lsbuf_, sizeof lsbuf_ / sizeof * lsbuf_); } -void SpriteMapper::OamReader::loadState(SaveState const &ss, unsigned char const *const oamram) { +void SpriteMapper::OamReader::loadState(SaveState const& ss, unsigned char const* const oamram) { oamram_ = oamram; largeSpritesSrc_ = ss.mem.ioamhram.get()[0x140] >> 2 & 1; lu_ = ss.ppu.enableDisplayM0Time; @@ -135,7 +132,7 @@ void SpriteMapper::OamReader::loadState(SaveState const &ss, unsigned char const SYNCFUNC(SpriteMapper::OamReader) { NSS(buf_); - NSS(szbuf_); + NSS(lsbuf_); NSS(lu_); NSS(lastChange_); @@ -144,10 +141,10 @@ SYNCFUNC(SpriteMapper::OamReader) } void SpriteMapper::OamReader::enableDisplay(unsigned long cc) { - std::memset(buf_, 0x00, sizeof buf_); - std::fill(szbuf_, szbuf_ + 40, false); - lu_ = cc + (80 << lyCounter_.isDoubleSpeed()); - lastChange_ = 80; + std::fill_n(buf_, sizeof buf_ / sizeof * buf_, 0); + std::fill_n(lsbuf_, sizeof lsbuf_ / sizeof * lsbuf_, false); + lu_ = cc + (2 * lcd_num_oam_entries << lyCounter_.isDoubleSpeed()) + 1; + lastChange_ = 2 * lcd_num_oam_entries; } SpriteMapper::SpriteMapper(NextM0Time &nextM0Time, @@ -165,28 +162,24 @@ void SpriteMapper::reset(unsigned char const *oamram, bool cgb) { } void SpriteMapper::clearMap() { - std::memset(num_, need_sorting_mask, sizeof num_); + std::fill_n(num_, sizeof num_ / sizeof * num_, 1 * need_sorting_flag); } void SpriteMapper::mapSprites() { clearMap(); - for (unsigned i = 0x00; i < 0x50; i += 2) { - int const spriteHeight = 8 << largeSprites(i >> 1); - unsigned const bottomPos = posbuf()[i] - (17u - spriteHeight); + for (int i = 0; i < lcd_num_oam_entries; ++i) { + int const spriteHeight = 8 + 8 * largeSprites(i); + unsigned const bottomPos = posbuf()[2 * i] - 17 + spriteHeight; - if (bottomPos < 143u + spriteHeight) { - unsigned const startly = std::max(int(bottomPos) + 1 - spriteHeight, 0); - unsigned char *map = spritemap_ + startly * 10; - unsigned char *n = num_ + startly; - unsigned char *const nend = num_ + std::min(bottomPos, 143u) + 1; + if (bottomPos < lcd_vres - 1u + spriteHeight) { + int ly = std::max(static_cast(bottomPos) + 1 - spriteHeight, 0); + int const end = std::min(bottomPos, lcd_vres - 1u) + 1; do { - if (*n < need_sorting_mask + 10) - map[(*n)++ - need_sorting_mask] = i; - - map += 10; - } while (++n != nend); + if (num_[ly] < need_sorting_flag + lcd_max_num_sprites_per_line) + spritemap_[ly][num_[ly]++ - need_sorting_flag] = 2 * i; + } while (++ly != end); } } @@ -194,17 +187,17 @@ void SpriteMapper::mapSprites() { } void SpriteMapper::sortLine(unsigned const ly) const { - num_[ly] &= ~need_sorting_mask; - insertionSort(spritemap_ + ly * 10, spritemap_ + ly * 10 + num_[ly], - SpxLess(posbuf() + 1)); + num_[ly] &= ~(1u * need_sorting_flag); + insertionSort(spritemap_[ly], spritemap_[ly] + num_[ly], + SpxLess(posbuf() + 1)); } unsigned long SpriteMapper::doEvent(unsigned long const time) { oamReader_.update(time); mapSprites(); return oamReader_.changed() - ? time + oamReader_.lineTime() - : static_cast(disabled_time); + ? time + oamReader_.lineTime() + : static_cast(disabled_time); } SYNCFUNC(SpriteMapper) @@ -214,5 +207,3 @@ SYNCFUNC(SpriteMapper) SSS(oamReader_); } - -} diff --git a/libgambatte/src/video/sprite_mapper.h b/libgambatte/src/video/sprite_mapper.h index 35c2b3e3f9..b474f7d864 100644 --- a/libgambatte/src/video/sprite_mapper.h +++ b/libgambatte/src/video/sprite_mapper.h @@ -34,14 +34,12 @@ public: unsigned char const *oamram); void reset(unsigned char const *oamram, bool cgb); unsigned long doEvent(unsigned long time); - bool largeSprites(unsigned spNo) const { return oamReader_.largeSprites(spNo); } - unsigned numSprites(unsigned ly) const { return num_[ly] & ~need_sorting_mask; } + bool largeSprites(int spno) const { return oamReader_.largeSprites(spno); } + int numSprites(unsigned ly) const { return num_[ly] & ~(1u * need_sorting_flag); } void oamChange(unsigned long cc) { oamReader_.change(cc); } void oamChange(unsigned char const *oamram, unsigned long cc) { oamReader_.change(oamram, cc); } unsigned char const * oamram() const { return oamReader_.oam(); } unsigned char const * posbuf() const { return oamReader_.spritePosBuf(); } - void preSpeedChange(unsigned long cc) { oamReader_.update(cc); } - void postSpeedChange(unsigned long cc) { oamReader_.change(cc); } void resetCycleCounter(unsigned long oldCc, unsigned long newCc) { oamReader_.update(oldCc); @@ -50,11 +48,11 @@ public: void setLargeSpritesSource(bool src) { oamReader_.setLargeSpritesSrc(src); } - unsigned char const * sprites(unsigned ly) const { - if (num_[ly] & need_sorting_mask) + unsigned char const* sprites(unsigned ly) const { + if (num_[ly] & need_sorting_flag) sortLine(ly); - return spritemap_ + ly * 10; + return spritemap_[ly]; } void setStatePtrs(SaveState &state) { oamReader_.setStatePtrs(state); } @@ -69,8 +67,8 @@ public: return oamReader_.inactivePeriodAfterDisplayEnable(cc); } - static unsigned long schedule(LyCounter const &lyCounter, unsigned long cc) { - return lyCounter.nextLineCycle(80, cc); + static unsigned long schedule(LyCounter const& lyCounter, unsigned long cc) { + return lyCounter.nextLineCycle(2 * lcd_num_oam_entries, cc); } private: @@ -81,7 +79,7 @@ private: void change(unsigned long cc); void change(unsigned char const *oamram, unsigned long cc) { change(cc); oamram_ = oamram; } bool changed() const { return lastChange_ != 0xFF; } - bool largeSprites(unsigned spNo) const { return szbuf_[spNo]; } + bool largeSprites(int spNo) const { return lsbuf_[spNo]; } unsigned char const * oam() const { return oamram_; } void resetCycleCounter(unsigned long oldCc, unsigned long newCc) { lu_ -= oldCc - newCc; } void setLargeSpritesSrc(bool src) { largeSpritesSrc_ = src; } @@ -94,8 +92,8 @@ private: unsigned lineTime() const { return lyCounter_.lineTime(); } private: - unsigned char buf_[80]; - bool szbuf_[40]; + unsigned char buf_[2 * lcd_num_oam_entries]; + bool lsbuf_[lcd_num_oam_entries]; LyCounter const &lyCounter_; unsigned char const *oamram_; unsigned long lu_; @@ -107,10 +105,10 @@ private: templatevoid SyncState(NewState *ns); }; - enum { need_sorting_mask = 0x80 }; + enum { need_sorting_flag = 0x80 }; - mutable unsigned char spritemap_[144 * 10]; - mutable unsigned char num_[144]; + mutable unsigned char spritemap_[lcd_vres][lcd_max_num_sprites_per_line]; + mutable unsigned char num_[lcd_vres]; NextM0Time &nextM0Time_; OamReader oamReader_; From 0a6a221653f0662331289d7e019c961bef77adae Mon Sep 17 00:00:00 2001 From: TiKevin83 Date: Sun, 16 Feb 2020 21:45:04 -0500 Subject: [PATCH 4/6] Fix LCDC for DMG on CGB --- libgambatte/src/cpu.h | 5 ++-- libgambatte/src/gambatte.cpp | 6 ++-- libgambatte/src/initstate.cpp | 7 ++--- libgambatte/src/initstate.h | 2 +- libgambatte/src/mem/time.cpp | 2 +- libgambatte/src/memory.cpp | 45 +++++++++++++++++------------- libgambatte/src/memory.h | 6 ++-- libgambatte/src/savestate.h | 7 ++--- libgambatte/src/sound/channel4.cpp | 1 + libgambatte/src/video.cpp | 27 ++++++------------ libgambatte/src/video.h | 3 +- libgambatte/src/video/ppu.cpp | 13 ++++++++- libgambatte/src/video/ppu.h | 1 - 13 files changed, 62 insertions(+), 63 deletions(-) diff --git a/libgambatte/src/cpu.h b/libgambatte/src/cpu.h index 2b75eef7a4..85df4edfe6 100644 --- a/libgambatte/src/cpu.h +++ b/libgambatte/src/cpu.h @@ -73,8 +73,8 @@ public: mem_.setLinkCallback(callback); } - LoadRes load(char const *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat) { - return mem_.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat); + LoadRes load(char const *romfiledata, unsigned romfilelength, unsigned flags) { + return mem_.loadROM(romfiledata, romfilelength, flags); } bool loaded() const { return mem_.loaded(); } @@ -94,7 +94,6 @@ public: void setRtcDivisorOffset(long const rtcDivisorOffset) { mem_.setRtcDivisorOffset(rtcDivisorOffset); } void setBios(char const *buffer, std::size_t size) { mem_.setBios(buffer, size); } - bool gbIsCgb() { return mem_.gbIsCgb(); } unsigned char externalRead(unsigned short addr) {return mem_.peek(addr); } diff --git a/libgambatte/src/gambatte.cpp b/libgambatte/src/gambatte.cpp index 0bc0639a76..50a3447fce 100644 --- a/libgambatte/src/gambatte.cpp +++ b/libgambatte/src/gambatte.cpp @@ -88,7 +88,7 @@ void GB::reset() { SaveState state; p_->cpu.setStatePtrs(state); - setInitState(state, !(p_->loadflags & FORCE_DMG), p_->loadflags & GBA_CGB); + setInitState(state, !(p_->loadflags & FORCE_DMG)); p_->cpu.loadState(state); if (length > 0) { @@ -139,13 +139,13 @@ void GB::setRtcDivisorOffset(long const rtcDivisorOffset) { } LoadRes GB::load(char const *romfiledata, unsigned romfilelength, unsigned const flags) { - LoadRes const loadres = p_->cpu.load(romfiledata, romfilelength, flags & FORCE_DMG, flags & MULTICART_COMPAT); + LoadRes const loadres = p_->cpu.load(romfiledata, romfilelength, flags); if (loadres == LOADRES_OK) { SaveState state; p_->cpu.setStatePtrs(state); p_->loadflags = flags; - setInitState(state, !(flags & FORCE_DMG), flags & GBA_CGB); + setInitState(state, !(flags & FORCE_DMG)); p_->cpu.loadState(state); } diff --git a/libgambatte/src/initstate.cpp b/libgambatte/src/initstate.cpp index d71543e0ad..fb3588dc1b 100644 --- a/libgambatte/src/initstate.cpp +++ b/libgambatte/src/initstate.cpp @@ -1147,7 +1147,7 @@ static void setInitialDmgIoamhram(unsigned char ioamhram[]) { } // anon namespace -void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbMode) { +void gambatte::setInitState(SaveState &state, const bool cgb) { static unsigned char const cgbObjpDump[0x40] = { 0x00, 0x00, 0xF2, 0xAB, 0x61, 0xC2, 0xD9, 0xBA, @@ -1182,8 +1182,6 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.cpu.prefetched = false; state.cpu.skip = false; state.mem.biosMode = true; - state.mem.cgbSwitching = false; - state.mem.agbMode = gbaCgbMode; std::memset(state.mem.sram.ptr, 0xFF, state.mem.sram.size()); @@ -1222,7 +1220,6 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.mem.enableRam = false; state.mem.rambankMode = false; state.mem.hdmaTransfer = false; - state.mem.gbIsCgb = cgb; state.mem.stopped = false; @@ -1269,7 +1266,7 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.ppu.nextM0Irq = 0; state.ppu.oldWy = state.mem.ioamhram.get()[0x14A]; state.ppu.pendingLcdstatIrq = false; - state.ppu.isCgb = cgb; + state.ppu.notCgbDmg = true; // spu.cycleCounter >> 12 & 7 represents the frame sequencer position. state.spu.cycleCounter = state.cpu.cycleCounter >> 1; diff --git a/libgambatte/src/initstate.h b/libgambatte/src/initstate.h index 0e31dd35bb..13b0f28e16 100644 --- a/libgambatte/src/initstate.h +++ b/libgambatte/src/initstate.h @@ -23,7 +23,7 @@ namespace gambatte { -void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode); +void setInitState(struct SaveState &state, bool cgb); } #endif diff --git a/libgambatte/src/mem/time.cpp b/libgambatte/src/mem/time.cpp index 87ffb7e10c..f85433bea9 100644 --- a/libgambatte/src/mem/time.cpp +++ b/libgambatte/src/mem/time.cpp @@ -43,7 +43,7 @@ void Time::loadState(SaveState const &state) { lastTime_.tv_sec = state.time.lastTimeSec; lastTime_.tv_usec = state.time.lastTimeUsec; lastCycles_ = state.time.lastCycles; - ds_ = state.ppu.isCgb & state.mem.ioamhram.get()[0x14D] >> 7; + ds_ = state.mem.ioamhram.get()[0x14D] >> 7; } std::uint32_t Time::get(unsigned long const cc) { diff --git a/libgambatte/src/memory.cpp b/libgambatte/src/memory.cpp index 95a02ea4a4..3b255d7d62 100644 --- a/libgambatte/src/memory.cpp +++ b/libgambatte/src/memory.cpp @@ -17,6 +17,7 @@ // #include "memory.h" +#include "gambatte.h" #include "savestate.h" #include "sound.h" #include "video.h" @@ -80,9 +81,6 @@ void Memory::setStatePtrs(SaveState &state) { void Memory::loadState(SaveState const &state) { biosMode_ = state.mem.biosMode; - cgbSwitching_ = state.mem.cgbSwitching; - agbMode_ = state.mem.agbMode; - gbIsCgb_ = state.mem.gbIsCgb; stopped_ = state.mem.stopped; psg_.loadState(state); lcd_.loadState(state, state.mem.oamDmaPos < oam_size ? cart_.rdisabledRam() : ioamhram_); @@ -1014,17 +1012,17 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long oamDmaInitSetup(); return; case 0x47: - if (!isCgb()) + if (!isCgb() || isCgbDmg()) lcd_.dmgBgPaletteChange(data, cc); break; case 0x48: - if (!isCgb()) + if (!isCgb() || isCgbDmg()) lcd_.dmgSpPalette1Change(data, cc); break; case 0x49: - if (!isCgb()) + if (!isCgb() || isCgbDmg()) lcd_.dmgSpPalette2Change(data, cc); break; @@ -1033,25 +1031,34 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long break; case 0x4B: lcd_.wxChange(data, cc); + break; + case 0x4C: + if (!biosMode_) + return; + break; case 0x4D: - if (isCgb()) + if (isCgb() && !isCgbDmg()) ioamhram_[0x14D] = (ioamhram_[0x14D] & ~1u) | (data & 1); return; case 0x4F: - if (isCgb()) { + if (isCgb() && !isCgbDmg()) { cart_.setVrambank(data & 1); ioamhram_[0x14F] = 0xFE | data; } return; case 0x50: - biosMode_ = false; - if(cgbSwitching_) { + if (!biosMode_) + return; + + if (isCgb() && (ioamhram_[0x14C] & 0x04)) { lcd_.copyCgbPalettesToDmg(); - lcd_.setCgb(false); + lcd_.setCgbDmg(true); } + + biosMode_ = false; return; case 0x51: dmaSource_ = data << 8 | (dmaSource_ & 0xFF); @@ -1120,14 +1127,12 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long return; case 0x6C: - if (isCgb()) { + if (isCgb()) ioamhram_[0x16C] = data | 0xFE; - cgbSwitching_ = true; - } return; case 0x70: - if (isCgb()) { + if (isCgb() && !isCgbDmg()) { cart_.setWrambank(data & 0x07 ? data & 0x07 : 1); ioamhram_[0x170] = data | 0xF8; } @@ -1211,13 +1216,18 @@ void Memory::nontrivial_write(unsigned const p, unsigned const data, unsigned lo ioamhram_[p - mm_oam_begin] = data; } -LoadRes Memory::loadROM(char const *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { +LoadRes Memory::loadROM(char const *romfiledata, unsigned romfilelength, unsigned const flags) { + bool const forceDmg = flags & GB::LoadFlag::FORCE_DMG; + bool const multicartCompat = flags & GB::LoadFlag::MULTICART_COMPAT; + if (LoadRes const fail = cart_.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat)) return fail; psg_.init(cart_.isCgb()); lcd_.reset(ioamhram_, cart_.vramdata(), cart_.isCgb()); + agbMode_ = flags & GB::LoadFlag::GBA_CGB; + return LOADRES_OK; } @@ -1300,9 +1310,6 @@ SYNCFUNC(Memory) NSS(serialCnt_); NSS(blanklcd_); NSS(biosMode_); - NSS(cgbSwitching_); - NSS(agbMode_); - NSS(gbIsCgb_); NSS(stopped_); NSS(LINKCABLE_); NSS(linkClockTrigger_); diff --git a/libgambatte/src/memory.h b/libgambatte/src/memory.h index babf029853..601c0bf0c9 100644 --- a/libgambatte/src/memory.h +++ b/libgambatte/src/memory.h @@ -55,12 +55,12 @@ public: memcpy(bios_, buffer, size); biosSize_ = size; } - bool gbIsCgb() { return gbIsCgb_; } bool getMemoryArea(int which, unsigned char **data, int *length); unsigned long stop(unsigned long cycleCounter, bool& skip); bool isCgb() const { return lcd_.isCgb(); } + bool isCgbDmg() const { return lcd_.isCgbDmg(); } bool ime() const { return intreq_.ime(); } bool halted() const { return intreq_.halted(); } unsigned long nextEventTime() const { return intreq_.minEventTime(); } @@ -226,7 +226,7 @@ public: unsigned long event(unsigned long cycleCounter); unsigned long resetCounters(unsigned long cycleCounter); - LoadRes loadROM(char const *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); + LoadRes loadROM(char const *romfiledata, unsigned romfilelength, unsigned flags); void setInputGetter(unsigned (*getInput)()) { getInput_ = getInput; @@ -299,9 +299,7 @@ private: unsigned char serialCnt_; bool blanklcd_; bool biosMode_; - bool cgbSwitching_; bool agbMode_; - bool gbIsCgb_; unsigned long basetime_; bool stopped_; enum HdmaState { hdma_low, hdma_high, hdma_requested } haltHdmaState_; diff --git a/libgambatte/src/savestate.h b/libgambatte/src/savestate.h index 5e5bf02349..e07908cacf 100644 --- a/libgambatte/src/savestate.h +++ b/libgambatte/src/savestate.h @@ -36,7 +36,7 @@ struct SaveState { void set(T *p, std::size_t size) { ptr = p; size_ = size; } friend class SaverList; - friend void setInitState(SaveState &, bool, bool); + friend void setInitState(SaveState &, bool); private: T *ptr; std::size_t size_; @@ -83,9 +83,6 @@ struct SaveState { unsigned char /*bool*/ rambankMode; unsigned char /*bool*/ hdmaTransfer; unsigned char /*bool*/ biosMode; - unsigned char /*bool*/ cgbSwitching; - unsigned char /*bool*/ agbMode; - unsigned char /*bool*/ gbIsCgb; unsigned char /*bool*/ stopped; } mem; @@ -122,7 +119,7 @@ struct SaveState { unsigned char wscx; unsigned char /*bool*/ weMaster; unsigned char /*bool*/ pendingLcdstatIrq; - unsigned char /*bool*/ isCgb; + unsigned char /*bool*/ notCgbDmg; } ppu; struct SPU { diff --git a/libgambatte/src/sound/channel4.cpp b/libgambatte/src/sound/channel4.cpp index 2b3f99533c..17c7fde5a1 100644 --- a/libgambatte/src/sound/channel4.cpp +++ b/libgambatte/src/sound/channel4.cpp @@ -96,6 +96,7 @@ inline void Channel4::Lfsr::event() { void Channel4::Lfsr::nr3Change(unsigned newNr3, unsigned long cc) { updateBackupCounter(cc); nr3_ = newNr3; + counter_ = cc; } void Channel4::Lfsr::nr4Init(unsigned long cc) { diff --git a/libgambatte/src/video.cpp b/libgambatte/src/video.cpp index ce58c4a392..732d5e4572 100644 --- a/libgambatte/src/video.cpp +++ b/libgambatte/src/video.cpp @@ -24,8 +24,8 @@ using namespace gambatte; unsigned long LCD::gbcToRgb32(const unsigned bgr15) { - unsigned long const r = bgr15 & 0x1F; - unsigned long const g = bgr15 >> 5 & 0x1F; + unsigned long const r = bgr15 & 0x1F; + unsigned long const g = bgr15 >> 5 & 0x1F; unsigned long const b = bgr15 >> 10 & 0x1F; return cgbColorsRgb32_[bgr15 & 0x7FFF]; @@ -69,18 +69,13 @@ namespace { && cc >= m0TimeOfCurrentLy; } - void doCgbColorChange(unsigned char* pdata, - unsigned long* palette, unsigned index, unsigned data, bool trueColor) { - pdata[index] = data; - index /= 2; - palette[index] = gbcToRgb32(pdata[index * 2] | pdata[index * 2 + 1] * 0x100l); - } - } // unnamed namespace. void LCD::setDmgPalette(unsigned long palette[], const unsigned long dmgColors[], unsigned data) { - for (int i = 0; i < num_palette_entries; ++i, data /= num_palette_entries) - palette[i] = gbcToRgb32(dmgColors[data % num_palette_entries]); + palette[0] = dmgColors[data & 3]; + palette[1] = dmgColors[data >> 2 & 3]; + palette[2] = dmgColors[data >> 4 & 3]; + palette[3] = dmgColors[data >> 6 & 3]; } void LCD::setCgbPalette(unsigned *lut) { @@ -114,10 +109,6 @@ void LCD::reset(unsigned char const *oamram, unsigned char const *vram, bool cgb refreshPalettes(); } -void LCD::setCgb(bool cgb) { - ppu_.setCgb(cgb); -} - void LCD::setStatePtrs(SaveState &state) { state.ppu.bgpData.set( bgpData_, sizeof bgpData_); state.ppu.objpData.set(objpData_, sizeof objpData_); @@ -162,7 +153,7 @@ void LCD::loadState(SaveState const &state, unsigned char const *const oamram) { } void LCD::refreshPalettes() { - if (ppu_.cgb()) { + if (isCgb() && !isCgbDmg()) { for (int i = 0; i < max_num_palettes * num_palette_entries; ++i) { ppu_.bgPalette()[i] = gbcToRgb32(bgpData_[2 * i] | bgpData_[2 * i + 1] * 0x100l); ppu_.spPalette()[i] = gbcToRgb32(objpData_[2 * i] | objpData_[2 * i + 1] * 0x100l); @@ -186,7 +177,7 @@ void LCD::copyCgbPalettesToDmg() { namespace { template -static void clear(T *buf, unsigned long color, std::ptrdiff_t dpitch) { +void clear(T *buf, unsigned long color, std::ptrdiff_t dpitch) { unsigned lines = 144; while (lines--) { @@ -856,6 +847,4 @@ SYNCFUNC(LCD) SSS(lycIrq_); SSS(nextM0Time_); NSS(statReg_); - NSS(m2IrqStatReg_); - NSS(m1IrqStatReg_); } diff --git a/libgambatte/src/video.h b/libgambatte/src/video.h index cedd2cf86a..ab8b25475c 100644 --- a/libgambatte/src/video.h +++ b/libgambatte/src/video.h @@ -50,13 +50,13 @@ public: LCD(unsigned char const *oamram, unsigned char const *vram, VideoInterruptRequester memEventRequester); void reset(unsigned char const *oamram, unsigned char const *vram, bool cgb); + void setCgbDmg(bool enabled) { ppu_.setCgbDmg(enabled); } void setStatePtrs(SaveState &state); void loadState(SaveState const &state, unsigned char const *oamram); void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32); void setCgbPalette(unsigned *lut); void setVideoBuffer(uint_least32_t *videoBuf, std::ptrdiff_t pitch); void setLayers(unsigned mask) { ppu_.setLayers(mask); } - void setCgb(bool cgb); void copyCgbPalettesToDmg(); int debugGetLY() const { return ppu_.lyCounter().ly(); } @@ -147,6 +147,7 @@ public: bool hdmaIsEnabled() const { return eventTimes_(memevent_hdma) != disabled_time; } void update(unsigned long cycleCounter); bool isCgb() const { return ppu_.cgb(); } + bool isCgbDmg() const { return ppu_.cgbDmg(); } bool isDoubleSpeed() const { return ppu_.lyCounter().isDoubleSpeed(); } unsigned long *bgPalette() { return ppu_.bgPalette(); } diff --git a/libgambatte/src/video/ppu.cpp b/libgambatte/src/video/ppu.cpp index 257a7906a3..61a8e470db 100644 --- a/libgambatte/src/video/ppu.cpp +++ b/libgambatte/src/video/ppu.cpp @@ -140,6 +140,13 @@ inline void nextCall(int const cycles, PPUState const &state, PPUPriv &p) { p.nextCallPtr = &state; } +inline unsigned long const* cgbSpPalette(PPUPriv const& p, unsigned const attrib) { + if (!p.cgbDmg) + return p.spPalette + (attrib & attr_cgbpalno) * num_palette_entries; + else + return p.spPalette + (attrib & attr_dmgpalno ? num_palette_entries : 0); +} + namespace M2_Ly0 { void f0(PPUPriv& p) { p.weMaster = lcdcWinEn(p) && 0 == p.wy; @@ -444,6 +451,7 @@ namespace M3Loop { + expand_lut[(tileDataLine + ts * tno - 2 * ts * (tno & tileIndexSign))[1]] * 2; } while (dst != dstend); + p.ntileword = ntileword; continue; } @@ -455,6 +463,7 @@ namespace M3Loop { p.cycles = cycles; } + uint_least32_t* const dst = dbufline + (xpos - tile_len); unsigned const tileword = -(p.lcdc & 1u * lcdc_bgen) & p.ntileword; @@ -541,6 +550,7 @@ namespace M3Loop { } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); } + unsigned const tno = tileMapLine[tileMapXpos % tile_map_len]; int const ts = tile_size; tileMapXpos = tileMapXpos % tile_map_len + 1; @@ -766,6 +776,7 @@ namespace M3Loop { } while (i >= 0 && spx(p.spriteList[i]) > xpos - tile_len); } + { unsigned const tno = tileMapLine[tileMapXpos % tile_map_len]; unsigned const nattrib = tileMapLine[tileMapXpos % tile_map_len + vram_bank_size]; @@ -786,7 +797,6 @@ namespace M3Loop { p.xpos = xpos; } - void doFullTilesUnrolled(PPUPriv& p) { int xpos = p.xpos; int const xend = p.wx < p.xpos || p.wx >= xpos_end @@ -1768,6 +1778,7 @@ void PPU::loadState(SaveState const& ss, unsigned char const* const oamram) { void PPU::reset(unsigned char const *oamram, unsigned char const *vram, bool cgb) { p_.vram = vram; p_.cgb = cgb; + p_.cgbDmg = false; p_.spriteMapper.reset(oamram, cgb); } diff --git a/libgambatte/src/video/ppu.h b/libgambatte/src/video/ppu.h index 927613b3f1..b711cea3dd 100644 --- a/libgambatte/src/video/ppu.h +++ b/libgambatte/src/video/ppu.h @@ -150,7 +150,6 @@ public: unsigned long * spPalette() { return p_.spPalette; } void update(unsigned long cc); void setLayers(unsigned mask) { p_.layersMask = mask; } - void setCgb(bool cgb) { p_.cgb = cgb; } private: PPUPriv p_; From 612e1ef800196d381b771b49476f9f3e555c3d45 Mon Sep 17 00:00:00 2001 From: TiKevin83 Date: Mon, 17 Feb 2020 14:44:45 -0500 Subject: [PATCH 5/6] temp fix for traceback PC__READ_FIRST() calls conflicting with the prefetch fixes from upstream --- libgambatte/src/cpu.cpp | 4 ++-- output/dll/libgambatte.dll | Bin 176640 -> 185344 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libgambatte/src/cpu.cpp b/libgambatte/src/cpu.cpp index 67a411c0e9..72e3d43fbd 100644 --- a/libgambatte/src/cpu.cpp +++ b/libgambatte/src/cpu.cpp @@ -541,13 +541,13 @@ void CPU::process(unsigned long const cycles) { result[9] = h; result[10] = l; result[11] = prefetched_; - PC_READ_FIRST(opcode); + //PC_READ_FIRST(opcode); result[12] = opcode; result[13] = mem_.debugGetLY(); tracecallback((void *)result); } else { - PC_READ_FIRST(opcode); + //PC_READ_FIRST(opcode); } if (!prefetched_) { diff --git a/output/dll/libgambatte.dll b/output/dll/libgambatte.dll index e597cd2560715dd535e7386eb4674592374bcfbc..07267f28e496988b7efd63f102486b458f60ffd0 100644 GIT binary patch literal 185344 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%0D=Tm$h;sNnL-%$dazBVNXB?E*(g&a zW1MoUOp%On%6DZ7Rc_O{h*O4QtnU?KjEXX2r^w=xF(#NVQzT=&fH-lBa!C=D zDUvb9*(p;bW1O-l zdku2FOp%N+PN__hjB&~doH7(+zkQh`Mu^PV`x99jk})p+x=fLbQAJj_Op%On%1W6c z8RL{+$P}Vr54{Ivie!v&f}AoGV`G>xLS)9K%i@wTF0RQG$rzEfz9v&7W1MobOp%On zN}f!SjB(0=v$@GcQ_f-5KRIP6#_rle5~HHb*cw?}GR6d-LJHX_;taDe5w-`HH52nU zWf6-RS(=?iyk=y5+0G)SGqUpRELjQ9@UV!OnXFItkU_Q^u{CZtzTAKV zUq}0G3nX0=k8MU)>fY)lwaDe@wg zND8lCk*@lpn)Acx!FOUY+OjQ1K`Qkg;-wpD(Piv*I=h{g)qosgk))W>!ZWxXEKGad zq!YtZhIY!iGDUVX(XfhTitJ`iIaa2~Zsrt4rcj^RbUwuUH5t`kjg_Kp?olUoshoG5 z-Eu!`3igojd>|(s>nWzfDK?u&Ez*!ygxYb`B2{TcsG&zKqNWugNgTDvU%y5YiMNGB zb<`qHbCID&&jsBHc}j7?1Hubb(nUE}$DK*sNmns7Rt%HhWVTC1`nQ8G>JNMK)iJIU z^M77D4rL`u*i{f$DW5E=IV1f~pcIY1ez*&H6WhNI0TN&TN(4wK{m&slP3`|hVgRKs z$jHYQy{#)2lQk3LVl%BhwxVwGbI>Bq+0oyK($ogIH^=U<6Pu;H4(S5IK}Vd;ODq?h zx(V&)MoETCvB0oK+ES7JRNEoPvia6oJP;@hj;U2&sF*vg7%Lk~_9(UWel@i=R=6)x z?6I_BtaM+bSbbVCR=h7#YyuY>O8MG1CY6tsuGIF8!09tc>hZv0k45qh)U+ zVB375XGq>yu<}mgPsok;FS?ubaa?Cy?eaR%;Y1ppZL6TK6b?Vq4__&Mq`^y5JjjH^ zcZEMlp%^DL4(iuN{mTlJq(-zs5wvWgSt%9{MUlGxKcd)Knrw9%kE9j<5yikz6iH$J zBZ{XFMUiyrKcd)kW@2{8vL^oC`c#7umx|atZY!B3{C0@d7Z!qsH1!ydXJ2WAx6*MV zhH*^lW$)6ZT9U^iVb%7(Zo3lNc;2!7OUxP1PDN}gZGFv2j96R8Y^@z8@!1a2R+SW? zeB2+k&9l{(R)7R?qVaca#E3 zS^*O1Q3`DPa%!WGJ||f};Q~WRe&V{sY$WF!8tA3na?sQ4B8|G7m#lAt7SxSKeR4Lk zCLuL`2nrQdn@Xw5Rc+*|u#YcaektK-@`Y>Yppi1M4u?l;c``5Q&Z5&#V`IM6H$FiM z_Kp_rjY)LkIISMJ?T%gz6!t8WPT2k}>&KvEbE!6Y$1+*_jMb+6O`NUSo+8cYjGu-g zlYV!@td1cJY-~`m?RFyyR;6SzotpJ}lA7d`@})0aQtKiN>A=O+#3E?kwP zuG$-IJ1W&5+Enpc7d(UHhtePpc<@a=RhjK`@b3kUjrg&=-9@jETsM(i?aX+Ny0X(+ z^tRkCk}OH!k{UVHY-AL+@uSTtH0Z?AP6r0fMKs`|*8#;Ej@BYCU0|zmvS^Fnx*CT} zRAkH-sMG_B_IW?mIhZXFibUUgk(y^d!yVQ#E+K8!q2@b~5O1_Ra6j=+%nj>srp6v# zo5&liv1cVo*?P4ti9SeUN&l1Cac$^3J;7g~V79>>mqSWt^&n@EpEN(Fr6Dx7DvZT1|MUPfN8{&fND`HX51bAcy z2*dg#W6SL*_OsLv=y6HEH`;5B#P8^zp=H1AA8nTSBy@V|;Yj%0F_%ev68q8cxogHt zd=mW8@VRq7J(XH(AI%+^AMT!w5}(9;H2T~@KVtl$JE#P1c2Y|g1YQ=fUj*rmlKrr* zJe-&)k5(J^##c~V;y|2612i3*CTGOz#i2_gha`&}XN33Sqzbf5=WdcB2=SiU+I30_ zITG(t3ULoCOD#k)K1w0(hFek#k)V%Ki2I@R=ssI=@&%cnpPV8x>XW1J#N9R* zh3v&-+JSnWeVE?+o@sA-QZ3whvJ?*`K4i6z;Rlx@l=$#=$x=L^j$DdR;-$9I(nWfU zj=~{zXS~!SvJ}bsD5bbNqy}2o$x=MTj?5vU#Os>CrG_48>;8bAk}uMUZXA8%%^Y7| zM7lCH3^zFht?0qzh&yuS)N}F5pQp7`d}^{2?WxIjTT#bZA&Exakua_4Xq07LE73^) zL!j|MJ5QpKz=uHNp>>=@BMA?IMp@STCvp==q(h+b@Y%>{LyLLF3K}x@R+voKk^>}A z-V+W%mxsX3OxK=AYzZbN64Blp4ekz(wC!Yw(X8`i5!V0sEHiWwmh5m@gq8p3MOeyz zJ|U_3M=!z>|CubpYJc=1Ed5(#5!U&m7vY|$nQ?sUcy6-nw;||LW*q+oi7z{C2z<(n zbdQZ)p0XCeE57J4ywfgT#pEo%Ef#%@`ZV41@^#6C?581^pv*WEE{QKY3B#q= zvQ}Z=1LK}Kka{giPA&$d9$6c|#4^epK}lT7v2h=jxKjQ$+gxLuF6G#`H%r{iF_WZA zIX12)aWe-?5|?sp+%b$hw9KFSExiXq>PX`5jfz)!%+LU#{plI#lEGsmGA8TTNHRbI zw;6bdae3E*pDfCum*O03kl7^r{x@MKNqqe~5g^(0zk&cYynh1%)T;iqwnH*)?IPbz z_}g>rfare&xWqvJVg#t4jlLXqQc@)dTem0Oixi!cbShHxJ4sg}MK6?5s+^*g9A(E& z78b6QED++cvK%o&QKyVbc3jpcys+iXxzXpc-jPviJVkfPD2afgT{237peR1`6L)D+ zl$?@73#I#6778H-WjV=_%le^=N{-ytbuubt~lu^l%$Lf($$&trumr==)$NIgDN*Q^qWil!`@>mba zsN~3FHFI<*j(+isEhCRLQo-4RB}{gT_B^9qay2TGAcPLvgi|m&^XCakyRk0 zlA|IkM@A(_Mb^LQm0bJn@wAOApD`XA=cvfq%!sVeMqiP&Rz@X9Uh7#Il^l7kCuLM} zspQuC8OYPY#Dj2a#>Ew$ZMS;qr}m;F6$&2BZd$g zC1b=7VzJK>0z&K~86$2Gdt1hc8N~j=v7vbR1l|#N8iDtmEJds!_EW@y%ZQP~*2x9< z-On;&W5CP0KeK+p<*Dxd)*~FIy7yc6bC~MhZv{C_b?>(V9HzSW zTlAsq;4-Rvzcmx#Bh|g<*VOL)79Ft2y7yZpT%PLQV~yo7)xF2^aG2`eW4Sm?b?>nT zj)^P49_xJ$Q{8*4K7@}{_k6B7@@~JFU?y&YCe5A@3p>m_!VU3`=`JC2at>yC6@Eq%T4pZGZ z)-n!L-8t679HzQ+EWu%_JI7kcVX8aFnvd|2>K?{*=U8E~Zd`Yc^(`(>b-S#I9HzQm z)>k-8b-S#SI81fBES1Alx65*JnCf;}`}2-m_kF*hcDt;CMBOfHJ<3R#yRB6mrn=qM zGaRP6-PTVzOm(}hHV#wWZmWgERJYr@6X7GZ`xvg#YP^v?gy7qyFJzkiMl=37L<|gF0$5fnCdRF zR&bc=F0y{jVXC{xdYr>lcagQ2!&G;XWgvW{c7KWMF0!74<3P5%$ePCGsoh?yn!{AL z*ZL-hscx@T%3-S8Yn{qrs@rQF$6>15YYj*ENOeDk55F7TUhA|(-Ck?QhwNo#2X&8E=Qrn`Qg?7 zzvKn5)-V*p< zb#8b+E1dI6tNjO>T)^hkl{oVeHT4!gSth zE8`u{MNsrdF7ybbj>>!SgVUj{Li?uh`$~T*grW21NpY} zCw7%~C4bHw9_^U8dA7-hU}CrQ!}A9wa-~JGt|H25FOjROwyMwf&d%#WF&MwnJu;@=tMujAQIc8 z$11wlj?htV^k0bB?yW!Oa4*r6Xpf+GSH+oO<-;9C>`7aQWUB>{w4Wm$fTrM7hTLtU zTb>cgcYmVaLLKp*Awoy6(P%>B6IsWL54Qwh2-YLlaucVSljRHG%2rQn?`vP&N)>?O z(T7R@dq!dmdYog^5igaXB{TKu*;20T_)nQTB{-xd!-4IFG3n`dGL8HylNwV zXJYctJS}_?I6iDhY?$T9TBHsgQVi=5xEdslXK>zWbHQb$v{HwTnZ%%1sxV$4Umh~o zJR0wI>nEUX&%Go52+1$WR@b*c2~KC)u-jG2x8X~>tvNfsct?aD`yw529k5oHgAMzA zB)D|f1re=$hCcnFE;<`Fn(ma?5`6@}8M41JBBE;ZYtTZvYhx~5`>>!^JX=h~YK8pB zh%V$uN9eP>5u}?2eXgW`n8}|lq3;mRqVEtk{mwz(;C%!bxG-aO>+ExJ4Hn=Xz_4#B z%2xrG0fK-PfNz4+R=|sZKEUt_Mfn=wO2D0fF2M2UDax6EivaTgj{!CUJ^-9hi8R1Z z0UZDfP%ueRCIP+=SP2+*KAHiz8SoI`Pk?^{?!ExuZvv>_Qk3%m-vu-Qo&~%LcptDI zaFG|h00f{9@C{8-?g6v{9tZ3NoKl64%>k|iECM_SIJ7`f*5TO;cpLC3V0bln222Lb z01(a(@VpD~0AMNL_kcG69|4>`i8BVzuK+3l7XcOk?gKml_$`3wMDW}W_y?e%26wvv zz5$p5_zr+@Zp8C$z;6J50&E5J0X_z}CQFA}Yy(6AjylK*$q&yP?YllHv%37 zbOBDB0lI(%fTsYv0PgQ7N;zN-;8%d1fW3h6S3(~Eivb$|2LLm!!nIz2^?(C_ugz4H z?*SeM^a2XMt0)%(8URlK)&t%H6kLru05dVuQzt$;rPqJZ=3Q3oIdSORzqunh1l;E#Y^fZc!(0G|W$ zu7^wjX8_6p*8=7O76KjsJPCLfuokcx@Gn5l_s||d5#Vcp3jk9AGXc$jhXBg}zX!Af zdH`<%J_H;;TT#9Y_y)iSm=3rO@Iycl@HAjGpbM}Q@DAV;fb;v%O+Y2!Lck2bY(N08 z2+#`n37`}3XFwm|eZT;~H3y%52aE+=2Dlb*GvE$DGhhkeNx;*9Uclb~7T{BW;|4_; z1vmjP9xxT~9l-YhKLp$jFabXT{0y)O@E5>tzz2W>fZQKIUVxJUHGoS1-v!(NXaF<< z!hlBs?Ev~H{cC`C03QJk0Y==YC}RNU1N?x?0M`O;2HXK?1}p)r0dxYk1O5(p53moA za}(|o1)K}00$dEZ5-=Mu4{#6QKEUq*F99|Kb^_i4dPzAUca3x?iU>@LcK=SLA_I(}p_w#aaRR?`CJ3Qu|F02;Bwpbm$ zV9ghr{qb&?1u-01_32ZCyWn-p3G{*H|dORy#AACqo>H~d9JVhbC$(eD5h-cawaZ1)qTP1R{lC^c> z{SDdbw3T8<)=XO~)@04J)#7KI8CQ!qx3*q{vS!+fackC0TQe@tnrW-X`B^h<-6+nQ zX)8xw)=XPFaJ4CVC9^5EdhAZkOjVCr?sWYqyzFv$qhhUSEC(?iUsQdX?SS|gU)%{z z(J@2;PK4q$l2l!FH3)f6VC$69?1Mejwwa$OV%Z4?&zGX!4 zz+tty6Qfa6U%D8V<8|Vy1`1z}k2SwE*-3Xm=?Gmw@0lMUoLmYWBn%gS?Sx-IM~Kje z<@EsHOTpp%stUe5?~9GeT|lYOfk*ZIpV6IDH*_eQAFGtn8+tkH?9o@RcxdOB zqj_f)==dUm$OfeoIFdJ}d7n#RY$pU5?9L!FzDE5B6)i`6b*8i5=b6=;Rlv zT$w5X4J_w5!+qyBe3~V^Itgf4Ff5 zOr76sY()w#fw<>Vjy*y#y6AL5b@4u;xZ^CtP zvm$=90A%#=ANP);--(9_S3F4-#M(<=-_bAD@94wVbnXp-2i%s(SFvu5c5VdLdfjnv z5XtCCdWhyGP?}2jvi*pd=Py+AF!i@vPWn##ofn4lU!j)N;>YBtHc?1%<_P?z?SN{I zbXK|S;Ds({l{+4GJFD{J;e2P6Cm!}Vt47Deqn%ZS@o=HDYHU0_7MD~O`SHd1hDUIJ zL7))#cRF#&&bJd4eeS0lu=-7AT^G$HYCrCLtKZffTX4MIdw{M-lA4k1P&)w`$+Uz4 z^0Mt|d>@1$f!tSWAc*7@Qo1+;Um|Xg=HMgG#rVkiM;boF7ozJP#4FLS{|F+EsLj2k z;i6fmNBBo%4fWDyGE(~aN)Uf>PZas-fWkv6{A8VrQvXy;-#W%+r%w%66tDVFSEn(@ z4GGITpuC|Me?fPe;oW@QG>A4*x>Owah1Px_XwmhL!N7!t2ZgoOttj{VVD#GO$ogyn zO4A7F+R_|EQ!bnmTYD|yKTKC8OclfYsTWX%Tj`c3zX&puUsb%|r0c(X50&|lyiw}t zcTlBWS@rK;KxHnk!|?F#+vyFy>+q^8cSff`Tz-KYR_PY1k$3@uoLgg^Ab-4ITXX7F~@oi>*N%YBQs1NTfq$?A*$-AebCek$ViXOh=*xK;;6XwIC z86S_h4Wj9^y%*xQNL7bvq;UKr#Bm*XPN)hO4<6_S?Rl?~UMe+Bv4+YuFd0yt#jX>u zCB&EHa5J6X5sUE9`na>jgX9yCU5w}K)_AUtOG9vf32p(@`1A~v)TV!uk_&up^5S5e zAF$n}kTugytZc$wk50qc(!(wE39}c1s2V6H?4;MtXsIY8j{!;%P-Z9E=MBvn2ze5O z_P(Teuz@?Q04s2u*a`t5t~E>%!( z_tOqVEy=xgANn?&JLglx3R)ro3X^x*2nBDJ8Z2=Ow+%MkY>?nS%~@jAq3AwCPwxp>Z1Jt6Md zg8O6#2V2q4(58bJ+3FRW)PhBnpcdS>R6dr=M?^k)K>)Kwh-j?z`V0Dli7N%K>h=Fr5aQ^nub4MSnO%u@)~*6*Au zHrLj7(Y*-%aNR+=7}lTHi%**8_`~0h;k#RTc#Q?M61G3={aiQtKG*ZG9jMj`sVd|< zAfwsM9RG2C4Y(JqPj9T!o`*> zssu+J!%{dRQdnpVCgsjTYWN^hBQr>0-)~E&Dwg0XKMSc5gGe2dL26_+t}tBbQV&h29f$w2B~ARkt)nWN*zS%#0*lNY^1Qn9@M~sL8MN}Aa!guQbk!v z9XE*7=^3O(XCsAsN(OO-3xqTD>dP6VzLbqrNfuJLuq%VqnHi)CvyqyRg%mb)8Kln2 zAccMWU==OPLJAj@W{^5NgVfk;T%D7J)X9TLotr`Glx(DMRo)=6pE`)tc^RaNvXPpU zh16+-NL`RY>hx@+yje(%n;O2Vgvc44c(8n>MXJi6_<1&pzAO|E4PtI`2C2h?NEuyq znC{^y7$mh=W9(P3$m;$12WdB9tft5nk%^d=G1FibZ0bc5BWi)Mi|n>(MDZ{UnuhT; zvQO~94Iqj*&NMzp$ZUHVe=*s0%g3MbAZ~l#gV&~prWirXD@sZvI5^=Vo#sfIaWO|a z@#r#W;@^%9Cob~35D6gzn?jlmY5FqT?#EwT8a@mU#ep)d2y_{4{FT%2Gx?lAyID#g z>SGWF=4j3+v@?(}mJ^EX1i~yvn4Tq^Ga)6X3}F;Gn={U_Gmucu36tyuDoaZSdeVA9 z^)ymJh_uU?k;GXs6JdyPmYoFbIS7Ir`JRM9Z8Rb-)0m6EZ1WjfR&*H)@R!>ddgd-p z2-*o$TNA?c4B?5#Ba}7fj9<|*0{60(PyoI?babk3FQ$JH-=5j_1XFwCmo~N6IdBuM zca$Upbocs@bhquF{`M~)-%vz8L%Oc;0Om}(j1X?aZhiF|=d65n(ce$1*7sZRC?1>i zDBjTFty1whB2z3y?ZRmqzo4fkb%E(p9IGqL$(&rFn5mH1qkasA zEa1*XnIPCrI7)2~5Z^Cagk^MnKlKOhcXjORa0EuK@t{Do;{i%sPo6>h!c83n;X_W4 zNtDmaR({c-@*Sznfrmu-74N0j@22{z29+m&0|b02`5S7B-IraS=5c_ZlQv{UkXH24 z#~sQec@T3CHxCD;Rc7+i{E_>)RZ2s z!5!|?YwNo;@%hyHSN%rE$cw|H+iEfOObd?{n%FnH)sK60w8}$%b!vNUz0WA|*B@6U zTt1`V7|iD;O#m7(oY(;Xrik|d|1;QYiQN$t4l<_VO~_so}n8r z#l~i)FLdK9NNy6$bXT(vf2r*liO=Z>`$ErMh)3MD7!H&Buoi-8@1xW(IfA3#cnU}I zwc$Uu!bwGUL*d=7>v0pja23Nn>c??{+iy;GQ^13OhXREN6izj7DFT!L%BG?Sd{%cA z&zUL~mQ1buXW;UwrJwu7e!cy0j*izc`QNwn%DrmKRO;REIe&yWF+jFP$1k9fIk^Z> zqB}eBDD#VZil&M#zxXp8S<#<93iS@VE+7OntQ&lGHNT9%hp9)*=l;r!KSvj(LlRN> z*ef4&J021hc=2X;HM{YEoT}YHsHY}2Hr|;}A@#@T>Kw7xobS`?r+e{3KG`jF5t+}d zK1Z*=v%EGu8ix`v9@NFI6DwT8r&f0%cS3FbRb>~4^Pf!Q?f}9GK+x2x9&>sLZYYzz zhVF8$M&lK;+G|W&p{P^#ik;?kT-Z(YR;bmln$suf^|-)}i=+0}edbkAll8K^P9MdZ zS-gM`8{(!F=Qq*m*!Z?4>;VjsFnI{U4?j{D4jd;zE#N*BJaWk(S;hC^SSFnN^t&Y5 zG56t7Yc}eT4$Rs0GFE~Z650Q3F4@atvT+w4NTl(qn@eaYBj%NlBKd$oDKCmjZCeeL zR>W#eZ7!e=0Q-JPA?#(s7LrM|?HTh=V?VmkB)cY-qYrhoWg0FRi%___&d zWvq}f+Xmp;)=kmJ52KqOZLKnKfsB>Ohetm4!uO5>yZB*5_|X=Wk6H5Jm5(BR2sb|r z8jRFh65kPe?gtQhY|L`_mF;OL@v!~LfnpCI-38Yyf($?s`f!kL_-pJzi-KzmO6B-2 zYGGqLTY<&D#w54!&ZYB5)XD{3|M1$dLF$T=0zP$s%k0WNqBn(>jxOYgDKqfdm{NQ` zi_DKtOqq$_(RdLQSIjco$QTha{MVseVEaOXY?57m7*ayQpG+8z1U3{QK{jh&yDRY3 z%ASC`^7z|aqN5)hg7!6}T1FqP#2&R5?bk}-|H#L80KLu~Z#g?EjZbJwsBbGm@!EPL z9}ldPk;azXC-6-tQH(^u_I-H)$&eFz3^Ksr9SCH@(ST#9fb%j1L?I!R&f$EpS4yq^vZp-W> zb!BS|DaKo;nr(FBp}KO)J^OZ@xGA_hC%Eu#(u5p*{Fr__5Xkj4cQg#s@LE0Q&w4@a z)%Z?a)G$D872ADgTM3f!pz9IwV8K<~&$7S`xn@>Th-ueXs*rLa%BYjo3O>Iy#Xmrk zr(pW+G+uN^?x4=;Kr!kSkX4u4>q6?jGC}>8p^Ppt08!-P1jBpDAPq z;UY-xAkitiNKBhsDd-MGyAr4kF`>5MOGwnY_!OBSp7GH=TW2^KIz#$E{MgJS^?{g4 z=#$M;Cd)K2WS7&FIXLV^#pXbrf-Rcd?G)&HgNBGQWX=!pKjI417< z#u4&x5LiR&xsb6H6meu9(Z_YepFd1*-#toq3NrUFaAfXvtR=?db152MknJsBK@|o@ zq2>~n-c!RHoSN8T>>jR(^=eH!HYe@7^FhNSX%y0BuEBgxGl1k;xJkmJOCROJP<@5ZS60yW#B9A(dh2& z%FTDH!2=QP1<+M$)T%B0(0249G@CD^`Od%iRePrX!cs~Hxp~7O6|RZ9Ml0Q9TNicZ zrts)cWgA^+YPQjBr|5XHFegRAI)1l9hA3TGd4P~MldVX%F<($`P*AE)nyG>02hjGr z@nX~35atnx${0qQd=~!%O_s16}`U7&A4n6J*BQR>4k>Xb;sgZr~W}|_KrT>A6!#; zA4#Idyf0M-HRd83=$dF+#74ZL=|OrzX*y7tdI%V{y#W$u;{MnBBvLgYk>LXp8A)Cg zrX#`b^q{Xa^FgvaxP)BRW8OzI1IE;S#O&01=3>X&6F1=wNXTSnv0JOKF&B|t!e#~c z9h=;o>I`OWM#&icHkzQf&GNG4)C56K8Dr&UnoZu?^5Fm zdz#>QD$*(=P{c@$$w_{B^Sfs9o$z?jtb~W`oW=q0TT{_wQn$sgZ(m|}h`jvWG`T~J z7f3r|{`w`PI44E4E^aGLgxDcsdP04+!pL$JO-qsrvqUOPV^U!_6AHsM)4%~`o2R70 zq#-2~CL4e2AyG1|_*^`0(QY!?l#kSmh^mn>&(j(sWX%y70o{rVe+dh}wEJ*JO{kGN zWcyo(H)|nIUeBHNdx+O=dS~MZ?2d3)V@m+dPRfAiXBL2H;`XOBA0?>yKJ&lVd_O!# zU{1%2Jx)E=XKN(gpKQEeV!Xg!0RgAhYk}P8&XdUO8Pv(ffLm>u0`~yDU*W|*=frQ* zJcCzxXrDU+V{*bOWYVp?#@dHqHe-R4?A5OLOY}Loh49wk2>kpBqEq65oQ8$Am9Lq& zPp@A~ef|PXm(t$*%oQ{p_$vOu>qK=5RzxJ8q`|LfTFdHKeb8Pem}^NT@FG&?T=8ml zV;@{b!{Shb-(I4<1vkX%<7Y?Z1GnA~uiWAJXT#?&v+s_YX)r$O5hd=wIICF$W6 zBq9jdxZtoRI`#U?%k}zud{~!ZMRu{bYqASJS&s?VOBJpMR4{YTIl}d0;sR0P;PNu_ z9&DwzA(b&+H7}R@@O#aRi{-D}=ACT+t}QTH{?zTcL!cO~{OV#Vio?(mCE|9S?-B-Zf0ywGZx zxlGQQ=x<+WEiHmhir8X?jU}g9kr*&}&?R;m(soplg`h5wVkCV|+xmQw!Is^GSf_)k zY|EDi@RQWzC>mdF2PR|HOzZ1OOXs_#*7Va`XuJm28`qP-e5Ku(XxiZ}s}1KjR$esP zya?`7TJ>I}3s)PwU;RZkDjP>a?5g%qm)RKAqiHcb9pm~=IM_Pa83+A0MUvencA|wUZvPI;2194+NxaNPu&ReA*i_WF_@ID&s_~X#Tl)9hGy8?_yzJU(FQp3vrL!wSaQ1bRj>q; z)8$=I;$jf8^?>J%|MR(WvfXiFXHr~)MQJ1^%HHhb#vYIVM-yds9^(Dy2+Wczfx``O zTeGufaEp?yO}1^QML<}zLiO=m2q8EU19qW+R)BZ<{LSEsIGYruMBO8=2byb zv5iusB)jIEv4a*9_91TC}4~bQ^s^T+ub$m)C9gn@_FT1TQwB z)HH3n<4m-Vb`62k5Fow6PbT;>%;_rGd1=pgMr+=NMs{N4=vUhZ!M4Xf!(SiY$I$N{ zv5z?hMB-)^?^H`_uSSpm2bRNrQUyCEh~ z0(s#p=zB)dKj2kqV=cL>u*7B4oamY^ra-gw&>aI?L)(kYZB5))PMh7NGZw1@UTzB8 zSzaMmf>=9uXZmENM>fcw=p12?PoSBd*tALx)Cl%*vkO4Fprr>|I-qU$bjAI=&Xil} z&Gr)RA>RC$ZM4tl->Ar6tKb;SelH<@fI|Htk4`eW!WaeF@^4QM;NtI!wREO%`zB}4 z+ZVpig}0xf5I&PV2@^5eKK50bv{&pMa3T0Pdwu2- zS_s?gM}K{Cxm&#*N0(;)Wo|RgHN1cOzVn= zlZa-Jsh`DWbyBXn=%>(AgAa*5H~4VobHR^nr#JugbDQiFY|X?7K3Rp4qT#$ex-wxg!K5s;8YYh%}xoEMM*EnJ#4+b9O!oY)dGO?^4VnHU_7Qp&p zi%f@@SZORHQHl>s-_ydi#f}{xh>bh?!ucL^aR>&o`GbH*ZlgbX09c%+mA;H+jiV)q ze2G=l^bfW9QHWDKe?JoO!nN3lNf6&c|I7FooD;s%2(H@H!_(?~W(#3NtMM*_PjDE& zGN5k53hFFQ{52tHVngXhN6X8U(b7%5ir0$rN;hiG4JOCIRCuvYqy}&dYopIV9Q$|t zO+}}8+@9==KDwP^Yv&MUOg-D8-HR}zFZ5sv!=O`FUQrnMlGs{0EkEoU(Q7qrSAE^0 z69=|a@)3S05}HyNTtS1kG01)gQ`{5WWt7Q7n^icxXSoBv-t zzNO_#ov{O0Bxj6rULgL5Lmi9#uiHoJ*niMFh1XoQUe?NFDW7&G@f^7ddvYKAbuhxb zQj*?9c(>V~VjuHn`ZyD(ysSiYu9E0^?(w8@(jw%p6{{T|R> zkzn@M#R04i_p%A)A^*L1u?DYL#fy^D^zg$pRP6Q0V)ijKLE~+N+xlz6>*{1#Kg`9` zcrin?(7SbbrA!kaHoik1&!TnjBVGc!H0F`tj5O!6XTX1ZfzUgb<0%$b?4*qGlVQxE@?DiTi0 zA5}`;AJZ8d^DwgTI^U%=;@z6k4ZhM(Yn+|Fiuy}2A~)h~txJtNi(-K@5GvxOq+Wl~ z1ao?wIen5jz0Ak%CZ1`g+i%)wcm*xInCxrxW(-L3MTb|BRJGC#juv_yc;Dn>aNFjO zVN-mS^@a0JO1^KXiyicSTQ1HRC~N*6B8G(Pu{S>B7S|a!7K-Y^%IdN3jp?=Z)#oIa zI~Ua^UO>aUDPYXJsGQzPaT!$=E(3-Kidj7Yem2rb`Jx%kz;ULJYo?fEdCZHl=k{=h|o;6<2_$#N58X1 zY}CYJdRf|6`ij7DBNHm!*t`igHBriJA&k|3n=vdgtgM19dG%=i|H54niEHy%mSE=Zk$E$=5<2Anxmx*+}Ql8Va#p#ZPVhr@B`%< z^Z5`xM5jN@C8MY0QL(@I!>h`WK|eI~iD7h587T0TZbY#nB#Rx<*(=dfyq4SPGCp(P z^@b)|sO?%s3*~EOOEEpvHnevmViPDPn&O!yMB`z(rmtzn0X487xEn7>s10*POBu8rFQ5e1k+)DWIUbnN+(NsTK%ID) zS8@D3W4(Sc?HsTik}J3txq@q<6`c9-02r!xh^+#dRC+_lY?0Gq3yolNG40HPza}>K zx_>GgMsEOQ?$9#!%h(l(kA+zWWy0%nbMaf@z(w@uY~z3D7 z9Y*9}HnG1WlUTa9{lgsFZxA>k>{>2gQpwzak^|}uh*#<>myB&a%zhgUKRkfbog6=y zhKS~Q?166>m+l`*I)^^POOJL5dJ6`xl=b4h+4FR~Jd9WUjaD$Hu2e!zC$hV!rlH#Q z{rIHof3Vl+!k&cJs0MHC+g_uGP>o$OP)J^&YWNE>8`dS;k|ig;-R9rF<5hI}={xqk zMfMb@i2<7AUJJl-^+erpFp+1j%sBl2aQ80oQB~Le_)KO(0s$sG0zptBM2il#sZg6Z zR_7!$$qWn-9xA?RX;Y}SA|wLU>Vyf&9FN08TClghmG<`b+S;nOwIN8AnM9KCPKeKH zy@6JpAyq+JUMl&2zk8oEGYK=){I0j3`@ejcoOAZt&$ZWHYwfky-uup#ld*(*<8+>r zS$E~r2Y~5^*ZA=e3%E3Fy#6ul7WM1{#1J+yj-KPv{q<80S}q|U!)UEN<45CdfrV(U zK9Fs72Z>`0;xf6hjr`bS50K^7_R?@}3;YH#pp27;V{G3CH$d%RBsNsq*K2ZcuEO_A zb3>Sq4i>K_yjFD`j%@XJxuQJ(dq>2N5awOp22v0Uob_eGJCM|iTMmiJ{dLHt&&^f#=O`=piY^sEl?&K z;fM7fj<}F5Q5?8aC-^_MTNlS}mvOhUksqRwj^C|9KYKz?kc5^CJMsRg{R|lCb0lt) zE%Y4z`kx~iZ?FZhsshscfgn~}<6187Yt4#1+#AM3d@YqC+nR?F$EAfhCUDRU&huex zduvw|zK$X1rAo-^tZk;M<>*+xSX)bV$#HCXDJMt!qfeh^;^?8q7bGFiN&GkbP!R<8F*xP}5fzx&zgo1$O zL;TJ6_|FVpd3Vp({bx3;xT8N(Z$Y-73aO&V)JP2*NL@bIzk>w6bw!roD?P(;4oZd) ze!6HAUXpk95p8kV$`_nZWEhl-cw9K$SE4wL$VFGF)2@>Lq)tIG+Zp{S=E@x2i^PE% z7zMj%wOvB1?J_v^)eX56!}?T>t>Hnu;2fFGsmKhx&-x5V7|xA#FMyuBmVLd44Sh2* zZP(*aO6MRx^i1cKy9$f0jG&u?4OH-=MSn-%T?wExTmAGEe2$KPK7vneS8D@(*0L|D zMgLt>TQeK86%KMu%xW#+wY#*|>hbo7PtG_4-C6cyf%p7M$vto+yk}_76>*TQvE6=L zp6(Kd+Vo^@M0X>);fC&36_1(8DLu(b)Te$r?R9~;U9e&t3v6%f5v|TOA~V3D5f7BI0_s z5$@XSxul~Vs2*zHqeXCEAAG2L#5=r>FYzO)C2VgGSx0ylZ=uoOaZEb^(kWTb`Lw#8 znHx=lx-G45LftN@6BYut3rTa46mXii&(Q2f}l&C@KGY!Iw`xF%knbG8q z(9p}ZNbO;o3ImL>d+~9Ie$u?E5Y4>Uw@^kLd}ES@cWMC6BOPOyXfcgA;#cl0R8qRUhLg3=z~J zZH;BaABQF2Yv2^x7FuKAASDkt#8E*H7i=xfFf~EU_xA2qy1K#3F#|T2Sp!Ecjt`c{ zr(4#4(sWCj$o}|mqTWNr#u|Hvy28p<_!bL#K=tDl50#58HlT(AG}Y1YHxgL`8<{X# ztS=-D)b%4W*e`SnT2ERNduv1#m{G-3S~CQB>EuVFhNQss^)o>#jcg31(h!SRd>55y2e zYV>|2imjKiIsQVon=McW4r*Od^C8T29%Fy6yil)A)dmBTg(zPFvECV4PYnl2W*>)m z-`|ab43d7T2oPJ{H7H^2Q^JY>KgDe{Bj^SMr3vGjyk-i*@JKQvpmS(PfLrZ9G9<*_ zn8+f7Xf6snh!4p@>~J;_wi+;^_q0|qv677F1Q-#?LNKCNs*wOjzQ_FBl_&X;|Gvql z>RjiEUD-9*PdPJJ?7Yf$2Jf8YQ6GDp+@~zNq+tnOR^VW`#}+YV5@3oBXpsB;8koZN z?7La9$%&fQz|JBk9BU^aH1O7S1FH4KMy*8=Iv3rxi^3R%Rh@(xX#NE{SFD=cQPtaA zrOwYk5phY#fx1VJmXTylX8+l*}Pl9j8f2PGQs7K2{;(t z?c@A^wkg4AF`4l=aij98>gmsQZ7ahtS|q14-$zmtrdL59N?1b^FV;9 zPK-Om!Kx6ui~cF_FN$jSl11z!j`9=5Ck=%W2p#B97{h9C0E>ON$wOfgqH`NeyNt#KX01o00R#DDo~Jr-Nz zYan}P@JZ$p{0U=7s`qH+aBr9`sotXv0!4%BJ)!bt9<`p7&lZ-0yuW<`+g^?>+#)!3 z8@}UhyaOKtYU4NWmTeT8+BxL?!|$iXGv?Lf1W@j`7M&35Mo_#iUHBMKU0(l=tjl9b z`1XbFD+05jJe0I7WCr{JAiCHEU=FZEYz2PcGeW|S{{DxQy-zU6S@-Vh)uFY1yaodd zfT2O03#XijWK|D_O;y3P1$z*M;8QS}ezx~PTy9`O$15R^S%EbZ`mSPgw@+@49Jayl zMEYHaqgJdJ1Ox0o7ILVRGZQ1tulQIx(Fr^amTl7!)Ge=f@h7~+ot7TYp5sre7#V?fP+lp(SeY8$V<-|L@%dDV(n-P zu^VmQOm>d`eK6R-0B7A(?B?q4`FBdAuo5aWmx!5G%~cpjv>}B2#eZK@w&C0igrJ#S6I8Lu>Vg8cv}A9T%W8)N2tK00?X(9{mU`m)toOfkEu% z0|q)X;o(smD*5GDmTe16M#X$GAT~Bpby!~pTH}$T##pItPt@4fSiU4KHF6*|7FK7T zEnnJ`4Uu8>moJ69n7uTOu=viB;O$HQftaX-JP?ve7Zc^9$z_C?U~udze8*XHF+K*= z2hOEBJ#t>I_km=<+RM?FHRQNY%?3!_#wmGh_o$@8_Tpneg>@}qVIX(CARUn|Z6`Ut zFH{AIu=!Cm%B$^jLp^N)Lxn_T2spkC$IW2!B)NV)1HtuYFG<7lE6!p04SiUC62Cu) z6U0vi%a^LN7n0N0L(T|`_m`^F&2@N(^j-!JBA0sas@D1j1el_~tc3zq@5ifE6A!gq zhhb;TAmMH#rTlDk-#XEK(Cy2My|&VQauDgkv6Ul}FuEEa1Hx$SH;GXkLoInTMsnmZ zcAE&3d=`9A%qB}D(mhCR@U!=FlFJ$vFDsocYH3yjiTMImQQkh3q=Yi)3m~A_Z%2r! zFc-t7?WzmgRPL{n4f)iTs>UiLF)UT+?p1c7TVX#Pge+V~x!`{=n(Kj~rzP;wrORWj z4Pq`D`!dSXHHm2_^g`Pw@39E8R9%?5L#W(rfsKnR6el}`l?z5LY1h&x0HIP#t1qi;7*y@a8r$sU4QtVqY^c3pk*k!BZr=+JCLR{KgO6^}* zORjr7#x>0zoc*#zE_n*`2(2yGcA+vj#Na7zAwU<1Eh`mpJb4h)@Yce`xvAlAlP5A=Y|2Tf3`LiWZ^c!*Mg#d^t$WS^ht|l zSNH7DgdYIECOC~cghgEy)^)uZI8JDNO1nBup1JHz97ur&w0!U4-RGLt#bG+a?g2YUx*hM?>L723QyN^cQ+6AD&gB;L^#uqjc}DsGR&=6VRaAw@Ff6l%ZGR8y zXGL^)Jx-(_4kGJjSNO`oEmzp|u@yEY7FS|E#AP_$-1ky+&UPV#1&VJ5un$H5tc}8h zFdTDAlwwGcoM|tw$EU9KpX`7u(%)T^k0~cwoz-!h_&Ajq>M2OG4uqWy^vr)E_7#ZT2RozpB6O_GgDZZa z+1ctguFY99f(_i9B|Rt|?RuB%_IX=byJJ1OUR_TIOS$`25I|pALud9U0>k#sRV-xs zk5+in4k)gyJN=(*T`_*z39$nSAAX2GTj|Tu;lIX`UgeND?sVg>9fGcLmlEm5VLajQ z6(B+QS=1hT)WOv-aXm=Vk9Oj!o7gBMmUAcw)SV15BSfbaxJK`60#0G$ePb{Y`WP9g zv_y6Nk$v;T)@(3Va(SFszTXpx4TAsqvTWcB-@CA4p8<#wREC{V46`CEa5xd1jg3np zP1(>`HOe`+^xONFe!VQcJ4NY!mLBknh9=cO2G_-d6P1tx zXdguEc}*bVaEhvI5LMxXzYj8GFsv?maH1*_LUk4>ksm*1Rc=Q{7|wEdh)xLI0OfM8 zr6W2M#rwsTGwhCA7_sgGbhCfQLcGK0VHiGipB#9Bd>t$F_G56Xz7;Q_*@LE?DADN4 z$-}y)QpK%W7hwrLq2O393T@8Wr$S&K`hqpqUPn1B@@w>razMd3AA z6n+HN*n>D&6rMqMT?UtxSl4ic%dM79#rGpzD=x7Y1CB1B*tXts3dj3nCq3cFq?5nwjIA+rfFei-rnJ3A<- zZFSA~h&?Gn166i`(&gd zxnS`{s%+=hsY6D(QbwAVI?{_W(sUW=OQ|EhDkEJbBYin_q&H-wVi~C1r9t zkvh^}WTb0kq_3oo6cv%yUne3pT$?)5UlFObfdpl4Z4liZ^MNl{UA%>xZL1=0WmgS; zim$ORAsA$K3=|^1FOku>4OfrG*VynxH1T~FgrajkTitPdI3Z4I-|~Z>&<4f5x!CI< zw&hM<`>t*60R#;7>qA3(S;mO|=YN1Ee+Lk@gq7-=$@fOZV@2=5dU0$M;BhTW>Q_S5 zQu}FHJzv&iNg3uU5gQ(`eo5i{kHxYs&J!wHvUEl+)X{VyeU6I;`ABN%9D4~vK8~n% zA*lCX3uF&RrD9FVz$YZo2_StPV?Rm;uEPiH7uyKYWs4Gc`#L)RTtoru*j0U#l)6_u zF8m^A%C9+?-YCIei#V{1E1j{j&}htu9=?F7aJvsOt;^@T80a{*Iop>L@l9?fYu^ff zSE}OdkH!X;3xjDmmZfAIxU$`U__{0GgX={-KLoRgxA1LzfbXyobh(VZoDAHJ4>p33 z{*9oVMk8*M)m{R^z@blbDM>yVmi`J%Tgq@H>RxOMgZ_f5H97 z*dTQfzl-02u*UCg{GN&7uH~aR&F@_d_xEt`Gk$mCSBu{--UnYgW->j6-_^%4{GA!{1=M8cx61k@iJ}tm#+FUK8hVBB_&r&H^YAZz)3e}#$EYOyR~D& zo8MLkq+z+fGkVp5-d_EPVV~{{d{#OGUk_FT?T>z_8n$0I(-GJb_ST*<`RR0+c`J_K z5DF2`Z%91CH&u_uwzv7@h(FpeD9A02c66lypL z{~j%jI|+l|qPD_0Soamy_P`~u0yad$^>rkp)JF#K3MLKPWU?OM}?d}PK~BV*l7*cF^UC`R6;_so}p zmLh!e=Tbs_bA}!w%#a0|E1p*$pcm(7lP{ zy)aB>|Nqttx{J2(CPptrxEVV8zI#^HZX935A4rS5FJ{Ic4!TS30` zJ{EI?>U`OC@BbU|NC#wB@R^hj$Y9I3-bUPmD`}fEnN?lbW%E)Weqo$X?%}20+|CNm z|Kk`e!r=hERfF)o8eMLDV}u-xnl59{6Lh-)g5N#AZ#KSV(#MvdUATPwUDT8n%ZE1_2>^8-zaB2c?gypkV0^fDH0pE8d3BlkFM=g;uax5L&H=A zl3sZ2axP0rKrjL zVE}xBW0DPvSvFj@eXAUWP_1Ef>K4C7lLn7TaJ3C3u2w-|r$ZHo(p-zEXYdbog4B0QZE!m&wS zZe*B9Lz(b%0Wo^WD2GB}b1F#Qqrg-$YuR9Yb9fq3!R9a;e2)Rv8@$x`MuUY^P@abU zwU1ONH=^iC);!p!L_;d9KyX4T=oL4_LX`j-EZP9-a^a>_z`R?GVsysw!~>$({(|8R z1osgP2FfTI+61s)u|hO#7Z5`<=xOpQWElqQnNQhJmNE|thcBgsP&!Ob30VY4o`Cf0 z-n_dJn%s%sG`@8*q?3Ig`EaSB@{kUZ?VzLr`w%F}&@32Be=Lv;d%s8I^{b7L5wC%C z&V^7&w55XN{T?|bx8OI%H_6bD5|8gP$Z(wj)@iEV_|`l7QmGZ7v-?Vk^0`J1Rzq*l zbNDzRQWH|*1MKN(;>`b)-lvXprIRwH_&0oJ2GX?!P3iP$%c=>6ooAtn4sbL6r|Q;`qX)q9+)QZTDFR2$ z)l2zjMBt3sww6JzNqbyD@2D0ywpskENAU#npm#)UJ`lpp3MVSz!V_rgb7t>u(n>*#SK%Zbdg1~zBpw8%B29eIm9V6lnB z^Vul#>G0GZl^Go!-n(aVhq{p&c_K%Kd$Gu-Zp`MFI=28n+)p{T2*2Xk-f`@2xMllK zOClL545tPTc#tEy=vJTh8afAWUkfTD{NMeixO__)>HqE$`8F#0HQ>Ul&CEv4%5p!8 zs$m{JB{mh+OY|o6Ezq=2i-2tAjszhJYSz|}ntdS9iC#M|105FfaA&V`guL@76E@{x zUd!(OWbUpzR1GR$L(2Dt{7F=fsCxLUP>@uPs6O=}N#r7;Pn`iGcY(-fq$l#bL1f95 z$NRbZBFNc>`GH6KBdUCg^z6}&V?@C6APKuV6Hewm8Juk4*038!Ai;Yp6U=L`v#z&a z6b_Lm&L3qIqBQl7GAaebIwy!)Z{i=uhZr1KWDtYn3mhDIVsOB!jKR_D3MwOG*8|1= zujWov{Mb(#?#^^&E4eXt9tj(P6MYyLB=E2wewjc-7|C+0C*j;ZGO*4l_*juEC(KP= z7>?~`j0_y19PXJqOO7I~W0qZvDqMzI8|5fKBrisk(u?($i8fbETMlXcKBP3t0f!KqBz1 zBO7nH-A3}1!{8|@^YE%F(dSjYxct&P1wZT=^A-cgqBVCRFIET4PL!BQapqCQCA#3n zo}?ReqfW$iv;O@HXNPH`#_V%kq#}kr_Dv`8P1j@+2Nv+rxDKPECC~GM=i!IC%|+ej z@~qk^u`YOT1I;PN{$Uyz$nfwa?9$W0*Aipw*@&&$nXP}fUvU0- z)LgAOc;iJl2Jgy9q4~;4u;Rj{&Hg9P_FAG%yMFhxjm#})Y4fq;y}iL3Cn8V{27?UD zv^6&0*?vv%#scT%F=z1Zi+P+?F;3iq_kRUW0Ta!vj$7TG!F$H>m@C~1ADaNAIQ?%w z=)?+Dkhg3nsZVLto+c#0c`@`V_!J6Ia$!4j(F1SWoc^;?6~Z!Cq9%_ZSM+DHJ_L_w z>OQrJj@sDN>C&C%2W)4)U($5?O^Bx0db%}+P<9+9MlE*uU=6w3p zIA}sYL4rQxy|2o30ixW}Kgy?L3Gw;IeRs1?iTOtX2qpTp5e!V8@5%UJ6^IKyQ9Y0P zl!JdR0&l3fMJiOIqCZD+j96d;7a>=G;;C72t^mDhM9t+g{0lPtI_gp~1oIuK)cEx& z;Yb;NJ+ot8$-Ijgmi6Hb02TTNGXRigqE5fV2hy`jxZ60p`XVoG=0NFQ`vzU_8{uIy zd-r%Ici;|yL%8Lh0H{nijhv-z5K6hjFXpLOEtv6<7fQU((%zFbEA$Xmb+7)=I(?4a z-q0GV>9{NzN_mI7?sWz*lDdIPyAPVWSFNm^r3N`2#e>sPJUAT%U}212c5JVh!DXQm zN^VasAGj9@8Y=enK>L)6j8vUcDj~|`ur6h#9+FPj-k_JZZy!7aqQfv)ZFNteDuJlE zdQ9LHWWdw-qMR4n^XCL!ws~`*1r+hFjwTaAs$7%XtK2#5Js07Uk4P1UytxV$%@)TW zxoz9pt0p&BxpLZZF@!N^-1gbb8MnaCTd2kc6^)A3p}wRY!5_DY`C^r7Oc*gu)F+Ii zqPGy^Vw$+^Gn^)7qDJh#guJ*@v0^^XG?ih8S#2)Lqmc;vAZI()u!Z&-+W0 z`C0P(kV`KqCVvNz;5__{lRvG0luyUcV0H+EKl#eXD0gK6R<0jDe74u~DEvw?9)%)< z!z?%8Ar7;IW$Mas%R~4KQN@9FBmSGI<`{2Dfe8OU`mawPd$bo^K~KSr$^uXAUmpc3 zeA*GX55Sk95+AlT--27QM_rh$>|ob=)Mfd&de{?k6&CrlL%i=UCDaL(JO~`qFRy>uj%W1dA&zK+If*7a>^zL!7SA{7(?d0O)7mFN8e-Hy+iV5!6vr zeOKW0BwGVH1L$MSl1oV+^-erImIJ+qafn;bN=5siXSt$%*z@hC4{>0oNBeQ%)5yf5 zJyRIwv-SnzNV)Zn7IB+jPkB=e(RyA%3P%%<0`1w?^d=v>ns}RH&-1l0sJUaHLaC9(kbHg=^%+sUdXpjnUs0!E$ zD)QSJNVY^NYADi$YE4d_!`INwK(%6#2O$L_OA6E z0A|_WHvo~hVA9w-OWP#`d-VR%W-tv&pJ2c_Z=8iIq9k8jj0z6?uoz)`dJk_$ai-op z&Y`C5w%rI$fYy01qYTSi^E{zp7k&dJs;khS*|GmXZ9#G!c$liM*Sp2vRH1-h*SP&n zUc8uUu2BMKoi$e}q1mtY=B-PW^=-%iS)ciOJnL3xt~tiO(`$5Ns${4&lYP`L88l=g zSwX5~@J(g1>cxFagL|xLdjbBS{o~Ch*?RCP*?LZ%bwuO#HnuP{*Nxvh`1RoT0e%-;jItoC z2WMmqhDWl+Vi|1ZzrmTAnSG`g)Qlgr)S~3~CFaRp58PnYs02$^bkThl&p8!Gu z>0@;iF`Oa>2eGgn#-lMD=0vB+=xMXkX68FJnB>~-+Xva`(hi+yw$^-Jua(Ck3ark> zI*~UJ%QWUGSV2%(AFsZq#shyb72U`%kDQ%w$s%)>7U3*fg!|+)Mmf=pnQ`ruq?oEw z>BA((rz*IMN-Y8e53)Qw$ns*eNTaH#lqf)ALEBi#Jj6|ZHDiID`3bjEdcr-H%_IZi!?xr7=YR7i##@b5 zYw5}I*y{zCSKS3jcm|P?&=b18KyLpOg?hAlKO2WMnV7-%PxMlnMCIA(Qv5DW3UMAu zxJBq}GHOIfkI(xec_V5Lh`ktPui+e%Z+C23NKqeRjrGNnmWUXU)un|Gw?o2}>R zo9$^5l}|}m%$77M#C@3Q5-m@Y2&c)UL0V;+6!lM^r#1F8iOP%96*D+Ziu$JW6!Vwc zQ)9Z^nXZ`TG%4!C=PBl2(G4DcoO37>ln@v+pdzwV$i_#VIi!>?fyU&BP+BAvEm!vD^jx;Iig?LEQnyb?! z!Wn*PQsk#eQ7^<#nnWK$ex$;5`Kolqbf!sBf9gC)`*pfRCW@dmb>kI8sp?jreV#;L zOP7e+QM!`8k}gsHc@pKPOH^>4L?7OgrcFZ2NrQ+3=@L-^O4p`erc2~JPolbXiG(%< zO&rthS3UNhP%%K)Kri?jQb>isquy3P^}x_ZJfY20yAWr#h=YkE(nu`^Fiv^_D@}%R ztDV``U?b;|7{a=w3*84Ewy!8TRKxOF8|q_Y5?Z(7}-K zrvpt`PIdUcfu^e83i?38c>_&Y&b>2J2bxOgWJqWkXu|R$r1}#al?K!WLN`OgX9k+E zycnt04K$U|(U9;P15H@&M5^lsnyNl5`)ac#XU37R6?gi zLT#W4%aUv1{qX8$R zzK4X%2b_>P9};>8oRE4S5*7?NA$30_JUMruSf&1lga-zkkUAg|{`+|oQrQ!lp2q@* zjh_e=Q0jCTNJzaBsT0O2p$#SUx^E=|Q|Wb!vXe}T{?(JPT2+ss3O|Dh*H~9Rpmku? z>Fcz`&E%ge`DYxha^LZU6u5gSIL|!u8%TiH+!lmgq=$__(~Pi*df4}p!V2`T(@9|$ z>tWx>(9_cLhZ2~zsHN^-bC-UfZ_@b)CzK1FxQj{L=6jruXE){dz-wh3j?ITBnKcXR zBM#v`=Fu)X`Hr|DbT3>KB1d!K(}*g-Q^DS>y|gs!2f{3;qKB~ZsXB1N?}f^=RvbHg zp*TPE7tCL_FOsIev3g(#Gv1?7|)@*VB#qi%S}1&5!X0sn=NX z)uQ4ha#P++Wbe_&YcFfpv9xQ%C$3$Tj&Sdw`Y<}%qf2+O+QOI3(#FHsQeE~*d+b2V z-z!$S;^qHAe6oDuW%o|g6TkY^ue3dpSkB4Ak)zg-bzTH_yR_S5OLyfCXU&0;^HpfT z^>OsA&(wH*goEEZ!q1QO$z**p?`*z?mAP}*HEC+X@?rnIt1w@9M)vQI)5(M+92mJG z?a{77e~iacUG&%Zl)i<3mc?0isad^wAu~GzD^d@Om%HFQ`9sgikJObzr3)YYf|fR$ z_WX-O1hcn_Jxe=90JcUn9XvVz&;S0T7YaNlK@)pCk<++plp6`I^H|zFO|MUHd}Ym> z5hu&Go)i^}q<6&xuTJ0-D%b zJ^_Gg0U&Dhnlr_P0DLnQ;35ESNClV(fIAhS0D$S|01)`Oq(E>rV~6$pNq~(?g2e%o z0T_}DAf;hme^-27f4e@fH<*-8AIAN$vcGPeNoxK8GidEJ#!C@unRSpvUyqEmY=XCeH$m|qiE4!<^GIk9KBC~8<6E;Yn*DP-pz zps^dlw%PkXoe}SSQI6QUd?Pz}lYkf<%o#6gb|ZUuZAu8cctuJG`}nIVA?)NiDIpgF z@|Bbj_Om@DgdKfxGDNZz7;xnMyq;cN$~y&z^RLb|fotH_`^ol-q0#%b2xEcP2Pqn+ubB0R3L7FJ)&D#z)8o|?=_(FE(IhTzUC9|>LegEZ4qdA`+K{ggS z1Mg*nQEAQg3adFwy)lV1Neb*of7%%=zeOu}!&_d7QGPLAY&XJ#;E?BsSV%`jKJ?fw zf5oST&taK5MNeXmIcy6LX zl;e=-y^Hc?Sp2&{|7QECkH|%7soyRTRH0(mQfO`^w?+9vVz(p=Qr=}sYv~2o`9ih3 zA&;;e56f?+4KoBTn1?QWs{(H!1s?FPdDT*+`%(^IJ{2tdD={zlUt4K52bVAOm51=@ zRj>Ahu6P=6&pVM_m7*@s$L^_Ixc+Rwx6-DF*hV76R>w_yUA?IwcvE3pX)beWD;-U@ zrK+~leE6x9USRgBmsKGlzVy^o=DDxSt7=Wi)T{m|%)Em?qP0Z{=0hJ*O3GHP7(7cx z=&@ikfr8LkatAwr#j`?6kyn%HLmPj}Y;aC2n!W%H)-#@k`@_M*y{g$8@>Zf5Q@p^0 zXpUN$4IFq?XYMRu70rZOW1Ca7&8rqGt@y(zy{L9>8*A5ar- z)MZtGVD}jj6}-S3+Vrw0{D+Tl^yVk2+#0_N}&y->=|%SpO@`b=jHlTC#bR#l_>F| z`xIc{I-uGm8474SWQD#Pa~i4GPdU9voou zBN!}4xmEgmpe2SsvrAi(jYfO4GuoPm#mjM*BfP_eH3Y?02IqNJ!?tbBP{69x+Nb%| zxMn!Mg0;V8&d=KLF18%ka)*KTkj$mj17H0$QvkCyZs0^%y7W0BIAC0zy$H98RO0x` zJOB!_g5_2Ion!QD!bb-MZ}=uW?Gd~bQAQ)2d+VwqN3yGOI$F!WW!74%?9Ek{aFtsA z_2|&gvGx@(Z>}p~G3!}A${fiL;W?<6xeA$0KwwZ@WER{^Y-yZx;by$6vL6AO50{C3pnuJLShx@o`9g zd<&1liNX3V`FW>+y@bF1op<4{tPOb!d%)Hhynvv?{@}Wfc$l9Z-T!W^;5v?Tt$_{e z(}aZ>BI>$I{6#~m0YyV@#g~7liVs=d9r)JEn}opNI^M;eAOUp!#mu&075?hDEnuuE?p4$h&bcf#LNy^&8@mH^3Rt6un9R777Ir zOh-^0l^a|q`ARjW>hbBcu%{I8PVfcF^}6m>ta0DmELujfSTOxaHPaW;C zDB8Q)>TH(-#<2Z3^!Q`{juOVTx%Zdsn1Z*JGpaUd5qp4SXC9<#d}`wUdEBvwYjWrq zrxhU36PjMHA3=%vnw||ZjpuZ4lgn{(#e_s=S{pKuIl9mykzUldWOa7U6&`Vq9Wr+^ zb5#H!J4*uD_~vysnkfk&TKzZMnh}(A@G$kG4GK=cMB|r3&k;2gyH9y0sGc_Yn}x)` z^*%r}l5$c%L=Qo$NkCui2Yr#pR@k2X+eDZ8Z?f}$Tu2rRQYjL$=QeB_z-hWdzfH&= zVR4BZ$&I{cwS1s$WJ;|SRA@FWh4VHDyf(~C417>F0v}YHhz}@9IiAqWWywLs3@U>f z5fHI9iOPg_bTA1{^*q=aP0qf7^39`Vq1!MeM@9`Sf>#Gsh>Ym&E`yAW*l>{%b{B}8 zBJWvbU2ETFz12V%F_yN9+KZ&|>?@I%5IcRd7ibd7JG6!5u|D z@J^Pk4jqL3f!n+VI3)mg(|0P`9x$4&qopD15SM)q1Y0%^2xxc1fNpIqn`{Pw9L}+= z`3;i6r4||R&8L;+Yh~Hk6*sfMtyShKj^^dpVh5dPv|?|bcIf2s-Y{n4ie--iB@oil zGbW=Q59e6_VTjF6C0IEoG_<+NUwin0u2Y3(CFmU2MEwPO533Y=go=WqI!8y3TqG(x zvjFFzOhRSR9-L)?EZYA;g;!wp!ML9}^3HJceXxhbr&_fNisF?Z@enzWG5+fG-giDj zyJHb1xS@NB;O^c|bZD=^T^Z(k*d%ZT4qQB<&6f!I!D)>rGzYhB?&WQpyf5+P=nVxX z99_MQDV=pEZ0gk@Ij6tX|12tF0MNemL45YTBhCU|EN%f6I$p?~A?^jmVc7bWpqEe? z#jT)r;y8jUK`r}(;=*HOb4s1Fird5+M4hyT$B{crp(5DYK=ls{BcedixV28vfq^4g zRh=I(XuF9cndQwAD4_}OB29aDF(k_EH{E5h#(}4LyFnePAd5xXyn4CfN?G0 zRP+}LhIe~>O7EP1JvzJiL@m4Gl%mCakZX%DjC`2m7WtrvPl1nhb&j?%j0lR{dh0^& z%UpLJp+sBacI>wWhM|KYOj!#uPn3wFeE3t~-@)FH0#3zCXu2-oKA?d6n-FmCE80E? zxKI4|EroPDP;%eGmDk!P`k9U*=>(G+G^514t+EHd~xTCX_XwXMkq1@MIS0*PU;$x0vo0CU8l z288l31&F(=JX%>LYp%T{ju3?$2+b@qK9*5d$d8aF_&f+e2*tn)#ka|=*75z}zr$wj zbNFd#7rP;upOQZZn!+gER6X9`B>Q5dzlmMJ1HHhvV~>LK#2NT4eUH#0f5`2({vr-B zrr{6UTdJ>OH9)1X?_}Y$q2lLg(ucLP3ji;@`3)aTz939!0c14c$!c0l;CIKB=vn$%z0t$Rwyk0y&$>5&(HFo+e&FvLHa7 zI0uq9MFxoquH(>y2J6@>mS&1D9>pq<^wW2=%TV&Mo{Moq=`tM8CWFMf3{*C_nPWEO zdWoh}bOcI#4f8f1dQdJV7~Qxzj649{t3%`cyO{$MfM?GEv`fHm05CQg$8#e=BGJDl z!^pwF#<(blQ6x_GxWiZm=}(V_Jyy`QDOB6ub1jHA5pon}` zoR3Aad=e>)(*}`FLh>Fp@&N%LpTsvP^8rw17y2)1zmQoHyt-fX_&4Z<)gWa)CkoQ5 zVH9LgXBhoyAShl7gFxB;=K%Ba`jr{~mSq|=TAs34olZ*xkfb4yCL3>%YM}sA)%O>XHje&yy+kjMi7J z9$3~hcp6T)w|Z?Z?CX492%nIkPnq|Wc;3l%*U5BLN}x`v83Ne9)jF*nhrJ};Fw&|= znAfHC9y3p2JvSC~B>#Ps4I}^ksd(wffAcXniSysrY~7PTYmeY9Wn_&11+yq^UAhA5|R z1q}M`PQt2D2ZOx#rGVvqIjO+S?E7iEJ&vv<1_wJPSy@LCcgrIyQ^bL50AM|tEA2&+ zqwP#ot{+ol2ch;SAXFtaCb8xpOW2WAu&okyNWdh$#9PL`MxM>dFrBp}mi_c)1*5wS z|84j?80-JZoNW;D611$d6mqt2CDjX@?cTne?ZwY0`aRB5@}5U%d=#Q*@n)WfQGhjwwQYknSil-^ zE1TdO@VH_xOV%(fWo`AIK6OQc3gS51tatT00@%MPdSAbs3`}ATDqH=$>w$!6c z#`}|~oqzSMxLWS#cynosrYO(RsN_?Zb^Fv+uRhD^r>A2A)h3A1KQq~_m(KPzr^Y4`bf8GSpZOm0Uv&L()lf8Xt<>WdNM zHbpytBWjLXd5|D>uwtgvbtW|WdKOQ8|0UZ~FDY8Y?PyHIB~Lngd5{n7#QA;}C=cIJc+ksGc=)MV9ye7TUE7 zdt@gm{v#7HKWx_8gJrjPI%Bp*>$qUqZ7%gbAExn@>U|3o$Eg)#u*_ZNGvi9|u)R&4 zk1GvvoaXd;fgV>$S&?lmkBWfG4mLA$3gl6&8Q((D7wWx2TV6hq9dABxb}OpcZ37BU zl-(lR?+Q&72*9fSyNdQPE@2kMy3{*;vDZM7)8570VVNSzy>tI@U;wqsK*y*veU7%3 zOW8S!BeERU?Sg@u;`G}iMb^Xn>=|;>o^gZ2vawA!)-3`OyQQ@z#M@EJ4tRm_wy@42Jz5mO0! zk92(*Z6_y#M1(E3Q)$XY$^`4qdl_pYNg{H75g5tUmnTNI!K>#x0r({%pCcl_Z{#3F zz|8>6 zPL<*o04__Fg2%lVqypRqfGri^b^uPm>};q$aopVjz_8@P$@q$d4c^e&A2~+eRRdA{tc_^@d2d);CFwn2Z6g~LP=aPHS$fFaXB9Ejl9F{T<_Tfh&kN&x{ z6St>K!(MzUC4}AhmdMTMEu*gNN80HVs5Bt#$h9fcuqW?GnTB0?eM$)XvMeQpoq1Wx za@d>KB}0;Xa}l~lxA!W!+i8l^SLIusW3epc8Ql`MPn1n0*8ACef7VQTPPR`t=R?FG zUdh~CTf<5qs!1RVmLsf8t{#I+m2e%F5{cpD!R{=#hRd>D9n<9D=3qy+Joc`W!TOb#2un3opcAjXlNB|zpn^hvib4V{$E!M{ z$UQ7hX6iv;$95cfXsYTwiW;Ee9Zl@4DqzUA4c{niG7IQn0iwnRmxY8^#?YU`f2w;0 z7Mtge)|!#^w2rF45wmUEo~p><;Z-@ngX$fP!yB`!M>p*RN5}H_peh_Mm$OGeL?`n2 zUoPq$&Vsd-y@nGzeh_ANS=vz%^IRMl3|Er4>$qSNI{j#`MUTLHw6=^49daQ%SuSLg zx9Ami&Zn_@;R$64jF-o0c3cH&qVcj4Y=ss`XZ27l_;L2&o_wkY$He!#c>E2&X}NqI z6amX+5Z^R&!Dr;>g?@a{^y#VE;KE^-ZpTrG!lm1(#6T9Wg2wZbD^w`$n2mut#6HpS zI93nbuwp)S(CyeG6)52HNA&qGs2P_KiJaHH4TdYqwWWWZFHPG$}Ji z5KhZ)$*vxYN;HGcb}_;rNw_R|?YpAKpq`0-dRO+7tq~x_a^OTq*Adxx%bvc?tiKH? z;o(?BpMln(XdLvcRW4QY1At11mb-!y9gYv=QlrtX=0v+bV7ok;Ch{wpn_UAv0Txi0 zz-PjyEP_EZa4-@JYc#|%rsDX!ZOuHPo1xQun(RT&%9l}Fb5aihIe<3wWyjA!`zS)5 zMYv#(mdmnb_*iCqRI)Vr5qM$Y?Z(g$^a)~{LLL}sA_bYMi~LPL6fd9iuUZNnnEFY9 zN4p7`?u|}_$E#MBtCoRnG8hN4;)oe&`qGt2d?!Ss%%ApFLq6h+cdWNS3=YTYy_l~d zTTIAqxoYK8veCr7*tO`=)xCDUDO-{%v zBgY$;0x%{8Ag|WQS}!Wrukx#q@b{vQQpy@RZt(+=fJIVNV+%(E(+QYitCDr!!Z8L| ziGX1^i-zhjjx)f%BFgIv%d0dhhKgc_ly;r1(A>KOD3nFYvM1YA>uO&wUU=OB(>86Rh1*~riwvhCY?v7*_NlmWOQu|8S zOHeB=o%kG%otvrvCaK_-D3% z^#zF9S-si?UWi)~daLh-sPv9;sZa-cF}?MUg<6DLm9bzS@kWkVyq0EFC|2^Y)TemU}b7?%rLR;~MMmyn{a0qBYei=Aj9BIY)x)J;yYAkNM z*~w*om!oC*we&j}?XtIEYL1%~=p{g>eZ}&cdJ{BTuXGY9jXMd@eBTnt4(AShyKQGg&NWE~+8ghgpez&r9}o{vD81 zco+`Ix;r@T)4|F)8U_s!3+E@{6>xK|QhV40h1Zp{$BmX>GwqPujur55+%saYqRz-g zjb@~5bi5Z)zTp`GEz?KawLtQOX$dihn;4$+hmiC1nnT+?ChK-Hk*>D>{W-^XPmSSIU*(S>@2g==To z=;#+wEqFJqzSOpD=4cHo%-F4HM=q|yN=uk`5?gV8g|=5M%Z&~~RHCzpBdD0h5Mzgd z-X6>uplRl!B_yNWO5hAm@#k$p$_`SL;yASYE1)O^+BgUb&IBiq!R@569W)hIGQ4AS zUlTEOf^$)O_#bq2o$()mzscNjZcB&D-lcHdDWTCr#6a?(%9jYx*-pIq3BDux-_ZBK%YO{Zt8Nbo0r&c4zHwU%b_fg`j5?hI z7!80)0HoB4rx*jk`!G@_Yz-LSFs;lV3&5MH0QmsyPX*w~_kTzQ;Cc5?Bm*R^Lr}!m zZH;d3-`-!3N!?ybPS%nQ&KFrr%|ZCX2nbs|TtJK#8)?|&kAcTTlMM*ld^9D5jea#H zgspB(31PFJNroghTetKl+Dpj-Hl>|{Yg)Ar6gTei>`-ZMXr399uE3ex6_;Y9`p;z0 z4+wi|<^uto`9$8a_4Vj7WS3wkL~)Do@v?$!46|F3Oq&+g z|EmCX#XJ-DXW%;f!0Fy!Ne*gmFwDHFz5QKhR}34|@`-By=nm&;K$!Lt~qddS(L@vL5cAe`k}Ts9rH zIM^k8nl(^j-GtijL9`--6nhs(5LjAb4Y+M(5u})^SvPqXx0hHoIHqBJV~S^SvuvX? zxN>-p75C2I5Y}`3RFa0Cph&HBA@+8;v>8}73%j&=`Mg2JrIs#K)<$Y(xYxEN=yqRWkIUIOa=%`8Dc{LXcV6 zFp&Jd7ce7SJY%}V7jQ$saG+L@epfei;#}Y2Qjeo_f?AqCJ`BXTU{zJ;0V!+H{xvXe zbsz#tng^^j%g~2_l|3kXYAL?Gi+7h;5u*3KQ3Kg>eLk=ln^#}+1R_{Z&g+~DZzt7S z>*+kzJGxn$Uoc6H`Z&8lIH`?UF$O3A$ZS&K`jE9$%n@`4&5XN3(aG9ufS;VcDPQ~{l=r_cCh#iMsLuOMHAPGt+m4Cx< zsPhZz7jErUY&nGHb0ShuGej^8F-yQLCRmcpfZ__{TZdxr@<1(E`f5Aquj{DRVsF0U znX@L-R}PIZnU-IP)z0jisUFNQo_&E4XleCY6-x*A7VJ^%EiU_R2ks#(hh`QL**CpB zDZ}(^)^3Gz?@@p0VW9fUg(C7I{C)8~XtnAuzb?YJ7oZ4_x^W5~&fqVv;#Y0t zaU_j;{vGe3b-8#~gxT!3{^;A=P-KObxb%e@3q(OrI$2B9UwyJtyx~CYCl}(aiP6+7 z-R_`1xrmvEG7GS)$D`JJ@Z}9XknIdUxfHKH$4T3W`mNa+u(($2!t2v`#Um^q9pbUB zTRx7-hY97|My$)lW95a=0W+Q@m!y~TwWuczvIf6bu*(>`F-itWzG&MNfk!cR4TJ9L z)htv46kk!`ah&p@@RcLtd0_!6`avTYbp?`_WGTW;5~EaXnS)Priu; zMgR3@294?j3sQCb9@M5C2>yf>joyblSU8JY1Oj~QrZ@q5u;)Vk2?>}|q0-YDIxmcY}EJpL4cc{W1m+K(KN>H zjr3$-Z`I;Ww<6ZGUWYavPp!?jIPi76s9F*JO6j02!fUBk7hq6Hoq$mGVMSpfLbSR~ z1W-^Lu(D>r7Kp&G_OM8&b*fq=W4Af9LOfNi0DtuYrV>ymLe#(%eE76Y%kcQ@Wf27t z<&hzaWDm{&K8n|6;BWP4tP2sf5=r)Uj6mY4M6_CK)A6Na3Ds96-rBJN3l;vqf~BTP zkV_J-V6LHlqm3!q#BNhJu?y36o=%)l2W{K5Ye=7%lf(X|b=U=++B@mwRNmp(dM@fn=KMCXyZX#Q)7?rv*4hO9I8BuA4|X zYTz3D6#|G#swzfd-}Yh5HS6@J4?T=Iee|7o=(XuXuk)Wb^px#`Mr4>$D1%8vB}fWq zhYf;yJUeS?2NevcTtXXz>ehAGZ5a-=OVM`gbhF37_7htmpWP0P7*jPO@qZWI(8dW= zoLMVq_OM))CaccPvoJRRvqmtqXN(J>JyX4JhvH48dMecWGO23|9Un2Lh?r7?#sH%< z(%F9Wxxe-HIs!g~06QE(YQQ6K!nv1IRsjUVL6wrAILrY5|M{3Y5g<;ot8KPz^@qT{ zEP~g7cke0@=!oNnh%$QS|2|rr#n0CQ8`XofCxU2r$rI$ZeOc`Iu=)f!ahwDHH+in) zHp#rkY1`DMwW)!rC(5hVxF*HDC=BU?=#@=A1o+(|U#WBm8x^QwUq0 zS(#U&2eGNKw=K|{eSZ`R78OTmeson3+TtCz$8bWQNuQ|d5C3-Rsqkrf1olw>_9fzD z?OJ@`tQo8FEN!7UZ00YV*U-Nf{LT3X{Hc5nH+yA!ayn{{&=W|j+qe};VX*(0G5DwQ zlQc2zXN>0=V`u6Z@O)`>o@IpI)Ddd^s1+m88c~UZ@>15s3Ls-uno~!ptJSG zSh+uSBxM3)5J8p0Qpc#JBRwM>V5EboBPj)pF`O}Msbkb`)w7`ywGy|{r6e1gzMKNj zxF9xFjM|^;*&JdvU8y4}QwSWzyhhFnd7X@fnP@?Z5wnDmo?-Ogr^)NDjPW*$Cg&bDne%C`>aOD?P_e(fG)lF! z>!@9v1@b=P5ujT>6zwby#ju}JpJ#s2I@DLOpEW;UB%(Yo=H|uEGlA{<;h;!~^(Zk?3W z>qL0{6vP%YLLDuf*sWvLrD3(%E=q9OBf_={9euM z0#;I|@cr$S!|f!!If6f4d-{5 z*>Oszt|QtU>xeI)gUI_|hc+zh^j#S_XL^T0_urV>t$P@))(36$$lI9O)%zqmIA~yL z*O9`BCFp^JDkUw6A&5h&02h{$e>9#9Qj)_+QWMDPQ;w&8IQegA?)}D7;CZ$ljyTw( z66bio0K@lasX5-_&BRkzw?zjl=ZHfGWU zyDv#*!#ayr1&99Zc(Pmj-ro_>z zVn!$YFNED+Y+`e4oPx?O&x~5XK#~(?bneJ=%40Cz`@9HE61_x6X*=M3@=F3cjPZVazQ9))^ST_phuY}iXA3km#x?7ac0Egb>&bY!s{3 zsG*HUYb08O3w3YUm5nBL(NOUni=tMm2wA~aWy1#9<+6fewc1vFY^zvXU;BhNmAnWE zpc23r@TG#bbz@XeE3d--zGv>;%>uF2R-fnh|9^hT=kCm$nL9Ig=FFTkXU?1vR64Z9 zk{Nt+LIO|)F>%`$O9`YWEA-*tG`_IzPb*ql>;24mm<(pTf1IX8mr66G_Lrq}sP>Yb zmanU+D%oVP7@hykt&{zx7?F=yr!u~1{5)UIIwpHE5=sVhtFw$Qm)aw{yzj=8)07UGP$jm;q1N6v9Y7O+DI*VI0j;0rvY$d&m2Z zcMo;5w)R|!M;Tq2p?V(y?^0pwT{G_J9ky02WM|_wEMc)qXmiSPA-W?p`C1Op$d~U# zt4Ep7X+HK#eYutA`c38zl%1U=$tV4Fov_K+z2fBb06s@XMAirL$?6056<1}Q_muk! zHpUmL3>$^8qWJA=YNQO!C9<>Ws)ooiU|plzVzW+Cug#TJy5E>3v&#B+Tnp#!yK%W9 zy=hrHVV1Dm-n)e=#_u*M<=sLlO*U%om3Ud&&Z@v2-ZWPnb7?y(!-`vRcT1dF+TNX6 z+Nvlf{JGYIKev=L@wp}ZnwO_CQy756UPW%dSr<~Q_dMd1m(QMOc)Z(XZtGa)kpBGIl8y%?q05{rsH3@HFc@L5_iN-@S_ z5ipo*iM6t*kE!&seiH?l1jy2P2^z0jIxmJW9tTx^Xlg4M%T0X+-IQS|jYq>AMp-H+ z2WTx=YJ(U?S&=2$GMQqpn*3qw1#dF5_SZF0Q{jJ);S-$6+IT)-6)yUkvd1R6oBvvG zbHA1N|FF_RT3={+_D13tshoU0`a)k`uyq!Kt<(DwYznkuo(~4G>%0jZ}eO>dMM_Q%fmE`AZkUlJr?#aLDZ{5d`UWjiYZbomR3Ea~Y zAzSjO!)VV}1BugZl|rhc@3<>pO&))R#QrEpXt#|7MCX{4e6Yl9>)^Z#7N63xE0e6E z?dP|vHMynqCmMg{jQ_4^GN+g6Qmevj#XoC}V=`Sz|L-empHzOhzs}$Ph22J@mAIOI zL!(KfUW3X>_dMwX%yi$6t>4Pem@1=xcMw_m8L&q$Pq(w^7N`3nOv>KAm_?d2&tj@; z{=XjhC#O#6fukCZkkrP{D!f^xVms|bz&cMu-(@&^;!*5^NQw9SrDHn6LDwet>H zDvsKV&Nu~zkJh6*b8b0Yf70gLw%pfnut<*NHcu9FC2}aZOux82Fl;4NnoE@?UmfnB zaNI0fOx~{P7xRj{;b5z}t{Gx5GBMhgDD-*PPM?nXA$P-JnaE8N>A0C$npa(O*WzMh zyDFb}2<`oHtsAg$BCx^dH@TCM7uv+!|2Mw8U)S1Da z57v#99l*4(81#22p3ONo>wb0O3*VcUHF8S7E~GhaJX_ zo+8UAzOkyj6BNivb)==FHyTP9hj1!9s&Ur_zII9tw;2fbR~+=E*p4*6K^6@Iy=q>c zoXaV~{GKDzxV59j|TE}&!nS4_d*^(E3ld|D|r#ZgPGmiUl z`QPYTnWjhbAR?_?S}XPt8}h%sJNV8=Ch^_lvG4JYfy80!pf&4ilc*tHDt zjNqwYyRt+d?um;yRh5Ijpi!u{F3tJQFoM!3^OX__tV`--dSuWAP2noyZW z#_uyT$^A#J=J_JxvL+@52J=^mH8CY^LnMK!3v-KEU72|jg%Q9)2ryc5y3O9o*01c` z>fRQq-%>d&mYKq24PauJOputx{B4O;ak#$}E$aGGpN=^>&hB!SQ5`MYnH@Qi0 zr&~0QBoQs@FXi|_CsB7w)EuHlwFN5QkO(A1ExJ+LFaz_Z z4B6J2-=Ggy)bNJkY~s6`4kmTa_qMgV8;&6f^gb7W)g=}`ZSRCB0IGPH_k;s)+@Z)Z z446817j4VM5R5w8lyG301TnR}Yv#J?!vbOib8XMDR6`|P1iyfVhLw_qQy z&6G8Q++@?#EfG!YTr)S}shdE*Cr)?jUVkYzd9eTE$DAieW$96HWK;$t6gJ41`T~r| zMh6oa(wM+3gN-4q82J@0G-cz$H@r*VbIyL#2s22a>b&v)(JUg9MM$O%%mpyAg{_f8 zqZM)|SvH5KxOjCRfW`kaQIu4^!5qPj#25>#x<|z1uEfvjn0=;)rwhT9b_ab3C0Zk`&&uV(t_+sAByNe|eXF)k;an#a4;NXlie2qdN^RKj z%G!#bmC+#;6E9RrRj$jt(vd2*`pw#-klMyIWs;#)Z)2|Lt8gusZmeUA28n^Ub;?o9 zWZqe_pELR3rOa7ckGmJ*Tq+|>rsIn8S226EmhsVIEhNNziQ0fmIDGaNMw>OhN>HEa zImF(g7C_~l_O*;TMw3d433L0}fcKMdKAie3_ULL>(;R=o(5>Q5>ClXo61Bg95v|HM zT+MN5b={aD`)qpo44ILmJKR$SHSUyQ#wl&Tv6_+A*~47Al4{NnYJhvMu$xqQhENVx zVB!1)?gqzGww|0}>lXML4hO_=ma)&vl1^rD?t5`OAxT=A)`jz%TGF9FOhpG`_eyRM z$*by&8M3JtFz%Ha>4tBdSR@Et7+uMM@Zh+aZQ;{ru5GB+tHzCQ7!epgu3p{h#SyY` z^?~@f`kD#BA!K@7fz!NRAU@+7ff`LS z)NbeSraQznsn8wc@RGvX7~MaSY>n#iJFLA?-I!x$5uY8B9KEwum&Iob?PDS`J33#F z&p1w^DNqr`_>3}wb;cdioZqg%jim=FoO|802!ZtiA9}|ztCx78#s?0JBb(aZ>KaB3kbIo#6A{IdI9=SI~ z6E>{kxV(K;YBQzIAveFF4p4%;1Ghy5`^px}NNsSc{nwZR1A>V z4#-xfy6bY)b5uV0VjN&{~;d1b^S^mJ#=a9Y#k_jvNY@FUR~iOjuBzq!S!q#u`!6mDh8Mc8C=`>$$I(|Tje zt!|r!mBB>^9SIVftc%$WR->4@)1ot7-exKZij|$T-n}|T9Dubj74Ib@_zlcDm=R6u z8m_YXj&2@7wdg|3n$U&JBm4BCuj*$^B>zb-{`*Uw?q4H-5pr4qmn;e$#?wBj>#NDi z34*%DBxa)I!pme4FFXFJh9bF06(3BP4`V|sG9Pa4%UUvJnnCjUk5v~dQ(sEL{7s&4 zgilXzEq#$y9XY!wO_-L&T+;1)T~=L6Z4@(MN^x}T0g9t~{6>#He8g8%991-AOlI|! zmXbUSg7@y&v+(QP#_HAQ`_J^9%(br!?R}3j3$4iXQny?nOz~N5(Qk*?$eu4V9wSTU zIc_Fl*sH->(7GVMfu(|v!^6!={h&%s#0?g)W*7_gj?I_%ggW;Qs#+jlIH>;fsWE-1 zd04uM|A`=qwVt^NBZ_>P5wXwVXR*U&N(R-rbOP5%{nM5`lobUb&DH?=XN{dTD7yPm z;C}4**RrZNXEQ~JsH*S{?Dt)Uv!Ahz=t;n21=GEEr*w1l0wHR9wbJjp$;rcI%7 z;A^;PjiiGN^_GMO)=+ZpABGlqy1#$uh6 z@`fmL=vhX${v!pB-r>+bao+O~ptDeu3%A*{8`^D}W4TSc0G4lThfTYJ=YaK&;%CzO zcp?gMCH}#9M^{uBENl8cxk#2GELdmUL|n$6kW%J_f~6__`Xp;vM9gh|o6-cPVL_p3 zrvwddRi^9UmWnNE7<4@z4GApaY3#A1;uDO|7h_Xuc&iqxCU{U+bvAz5r=MAyiYcYLn zoys~8uIsJkQdLgmQAxGv+Ipq{a*1k7N3jn>`0h!Rjxo!Hfb~TcYASt6zD?rfJSTCF zsyN@iadJeHxcmEvD}HKmZ}C}W1{3#}&`_#YxxBlcWR{IVg9{bY#h#N-+%gp>TO?T# zg+xf+X%ndFH;OOa_oI5l@*%u*m3pZ}a>K4@R*slc98lj!j^~_2#Ap?P6hEsD#ku>iQANii+B;qH1MMygh?<+zs1l`)6`HD;xRX@2}4~!`O1F7XR<@!|V z{|D1yv+j4tsJq#RWWC3VG~%Q)nltt^;f}qruI_7YfUf)(N$)8dB@)zQ@Ch zZM~hM#)BJuT(fRZrK7C!tnu(L;NI~dMr5-F#JwugXCX#9(i#zoz9Nh7#{isa66-S` zH;dhWNVJe_pCMsouG;!h73tWw*mGo1{HCv%;xo-MGTo2~OC5OY9Q9U-6|R|%7o3fO%IIHf_(?bo9RgF^Xt>$eMd+g;4dB_*d(^b$4#Z~8ZOy0HYlt0+uD2$ z=OJiTtHcmDoHw`wSGD)COk|O&i^-&#jLD=nCA@+83)pQUm}x2ZmzH6|5@SkUOx2&> z2{*V{7KVE4d?)+B*t2&8yx5=^H3&U~_@MGyGyoz$%hwuCK!}kQdYSl&mv(Jq5ZM8q%Up3Bt@?ySP{}ES38Xt2}&%g(q8p{Y*92+oN z>2zt1A+BbwJ#;ktV$Z19+Jr}|wXfwOp3y=3&RXwlQ^wSB+f=Q0&6J>$F46yo)EKRG zG?ZtD|*uu6@uU>d#tBty3RlzLZwxaQJ>Z>)Bru*wq zKKOh@uhG<6VbzCXi@T2Gshim4kKoGyr zmyW10R;x=A8sE`?CjVQMbJ);?lfP7A5lv%w(6#W+vo`r*b6g9XP8Uz25V5#%C2w?p zNNvd^xf^X9J*M7Lmx1crq6#DGjQP@AlxIj+He7)(D%io*C_c)WFRh_BZrVXU+y~eA z&JoC{Zq7@simuwkjjD;p2yb)2ML*+U{!*EqLRI=XNu16o0 z(ZJ;qzmo27#ded0bj6HYX+BC*r;}?;p_}m$6)%JbPwRJz)TkBnY({pNDNMC_jbuvvYqN0+iA!wo-^W_7$n}%0cM62;@ zsa5zC^78c)NmOSvy;O0hbh^2_YG>0^y(Y83YCYxd$*YU2)uT*>Rcfb5=EKYuQTFop zg<3YdOKREW93qyU&p|Uluh+*~r((*OC)9o)le^KR?zDrd`;9wGe@V??NxuD^y(zu* zU3-nO74&&bs;O^ZuQ$FMA>JRqQ@x@aZ}{VfmA1a?OcWxY-&kY6PcTo-^}O|*e5RiG zY5roN%yLCuz<7@vVw;|m2J(BCyAn}JVcezK=KXwq+pU7JIj*K3DEM-{yd{9zUbLk@ zJ>v7W{xIRV)m7Wd79$3~yvuJsbYibPV?aL468jv{_jA3=Z`wL@ohx>x;8X9HB=K30`n(!QUOxtuBAUA!AMxHALf8 zcE+ObY)OphXLR_DxBIv0(Ra77Q{Dfi$h!`gzqS7^@LYXg|GzZ7VIsH7zZ(NKsSZ8W z0ick(B#NIarG4!T`-OL&A>(_ZeceReerL7vkBZ+pQ808=?2ixGr6M$4-Gukl7f9B$ z+#FFK^c#m^PmMBomizJdY(O-FP9`+C5PChkS%Yn7#&XS$R@n zE*4M#he}L>(WH|UxfuXD90JJC0MJeqKtTq8?tlOaGk_uFeOr9=VR~XTQe_j(f)lnf z=ARBG4tu&T;h7w!Z#Xx#99*8ca#}yNM^fI}V^8RAE=mkWdmLPUc`PQYa)Qy1Y_8Zs zzRA@*ST$7F^!DzD30HoY(QkJ(W3PQViVNkhpg8My1z#+0rBiCa-Gw=4Bu32jC#u=4 zK8G#Z*K06q3?4aZqU|Nv+P)8Uh}c`I6N0JePPW533DDEAqv+`g*wnr=UbtEAmsy*7 zXQbw1tC}kXI;CCNAvNMZJnRvzf|-A?(b>3Je8kXZ5;NoxjSX}w1|Km6W{l?%jjpz0 z@DW49%RBA`+{htWwNMMdxhA-9K}Cbx=!|Zz07j=t()x+8~s zRxvVe3$JJHr89bHH?vcw9qelEFC8M&B8|Hi44yj$5+Ngq_6r`lR}C7O=;rY;e&a*M zRbs&*XgpII0~FnazO^})^eA_Mr`o`gDq|HU%pCgBt6MiagXL?vs)=iWTnp#uGF~BI z-x5Nt0trvBTjVtX<2ISq^zE$(@j1%SLLk}(va|063eVXQ3>SD6MAg|{N+u1yp+hA&OaD|I6`&Pn=KM~!!7_!FVK zeDJv?dsEzQgjz+45But0@)0dko6r%djVvUqY~-g*fGOxi{e&v7f3);B22XJWF= z0=%gJ>A_5jO$v}6%mDtX0O`RDAgKW9!3^L71xOEO09$1=5Zi zY7)L3HQrX&%nG`Q);i`0Cgr$le{{pRgw(7#p#FN9*^Oj9p%d?Q@H#{*OAZo zgywGNfXkCYrlNmygP<}E7;UY8%d0hZ+W%PELeT!95c)ExE5ck#5Mmz$;`7IkD^)93 zW?BbR^+KV>fka(C+H!sp6k^+7i19+1y90@Has#wP!knn@R;X(T{f)k#5BZhDHt5L+ z6@~`SLSLuNB*JXs-h5ETUTB*B0=;RAP@r$YHtBJdUBKwx=YHB6W3G^4A!-i`S%xw# z+m{ggjaP&mJ^V$q*l+wr)KrB2M&HSY>ui0eAaNLT%If~Bjr9=e{%u@f2T|_q|4D`@ zzu{|2`Cb0M=m`gx3)Qm3O|deT+kFyeq8<;sRGT=wek4LS;=YCq;GFDN3%io@l4J!WE+#PcK%;g?=W{l^Kd9c(lg;8m~%6a!Cqmbc{v0 z?4zodpP+2Z#B&TRshV~bC0OlHVHu+;x;kHlWr&*LLKT(~YKGk^ECbXGdsJA)r_2N< zY>2@>zNhB0rQrN8& z_I+r*q!40%C@FHL6sy|M9%-^K(L(nn+Ga)DqMOqkfJ6%m;0c^MWQt2(9%8ZrL=+?n z4}}#thuPL6?0{%6R;_LY%f%UrIkCE_BU(S5CDsI4K*Q8Hq1bOaS7q=zB-_ zi7J%2^mMgOHM$bBv=U7tROn81AUlDX|9HsW)b@2U&mIj{2togNV5!gLU&US74zP_| z#Wv@+ZY~JgUqXwiI^lUy$_8&t834n`xpP?0!rJG}^EM zW3HN=(~O0%p#kH58SjZu9uxiDGCR~JhF(CeL@%^V7!KGDcn-J?cn(;L4A1eR;yHl8 zXuwhg;wtl?y|tFPu>{sbk&(n<(O|TF6HHgKE>Rda=X+)z^k^CM%)799t1*6CA}D+& z7sjYLK;J-o_C%w>a;Qfvnz9Vd))i#@P|{w!3Ej z9cI%axT{oe#Z}J)VrtIzdy_Xqbsz%WKY=X_#2yzSFJx$3u=0iEJ-P&bUCN%I==G1! zVlvw29shTmHjrn_-)!28JPUZDJQwr)fX8>vxyY;>ZsmzcJ5#DdArW&b5fxc(^d1Zb3z>@}*5PW3qpQ1X5;t4!0n0YW>hYROCIdtTD7~gq5jH92e_BRIUV+dg59e z(orQhay36HFWS3^6LF{>{Tn!77L?4(H`N)>Cr7bK-*Ye##(oj^awd~wkkYJ_aKpjUk8FK8{+rtexdQ6t;w^9WuKQH;pXO!kl{(!vYI(n(UtViUt zY7Mgog1DV3Z1zOZm{S@dEGYD&({LM~e}JHV@g><8(EBXTla4YGfmMt`1kQTOMO?MU zBhuY+JjCbi2wxvrEOK)#%*3gBs;W`+Qo`q1-Mg)|q@k_{ixR4{8&eOHcFG7;hD8R6 zlCVb-xc1Hvq*$SV_MXEdOH})ZJ?JClH>Mtv{PPQKF%{3d(*0o49g>PWarORf{=^6$ z5fc<3FL3l;__kSoqjUy9VQBq{ajC$}H(arE2?zsRR-2eDoD}mEsu0(dRu~%+*NG`= z8~oU9-z627J#pRujrH2Z9b)*3wg369rl)y_S5r8`HCOTeJp z6;fi29GsC_^;xHUQqTm`Uy_}vqqnK1&T05o)5;s2ayeI#m8~5`%iqn8P`pf2WlE%} z=&3J~%Af2E#4mexlW%W_-uh9FKmL^9jW4!D@JYW|zev9@%Lbv}h>Ly<`xAWj?V^4y zM2M|BW1i422-Pn|L@f77>hXwD#EWQzQ3GncDs8DIk7A!mDkH9(>hrO%&iozI6ShR&q_GoAB%&i z4vq0?x$Li?0MNUwl98bbGMV=F%*JATvVFGGH8ZOEMj>*;&-k_(f`>TL3e{CjGlcnX zfYo1>jDM~ayCmXNCE*R9F=D}#lPqEOv_VQ8NOTdzno+lxWYn%+D%mUM%~;A)ih)+r z2cRZ()YB5n?!q8p8hZQ#1#c%$^C zzGwj-N7Jh`=b*+|n~93Wg#)6$A32-tl9OmFwf<(olo;@sfRF;E{_XuUm<6`bzTEyj7%|!Dp)wzjp)z;EerDBAK#c;*P~nwoe@``~+TT+RW3`oE5li`88cf=ODZqeG zZnCRwKDODcg>TkYUmG0j5$DJRYwh@{4%~mouTi=!?l?2K9Jc5Q$HEHAMYe!TT42s=aTO?|D*%Gvmn`NX>Vs37lOcK}vsB(|j2@;o|_v zBw3nM5S2j|J{pm#pIsmXac!~r>Yb{ls2XKG;C}LGQ+@JFj#Sju`JhaFR({#~m>cqDebl#L0)hIh z?7p6c6@-%d;Ld5x!NW|_)F_qU-r=n-Iep*?bS6APOVtQ(-Hezbx(T6=@j%o&|az5>=u*R1Y{Tr58LenflPbC)>ona zLc{t0S;LuFxlN7b64h{Odm0?zn)1~`@-FDUQVm_hB%t$U1XtZ@s99j>{QBfTVg7N#feagdm1I%xH-p=PQH$L@L;|U zW`QMRlOedrg57S-n5N**1DR4=?8yCqut(>qo>(2f^Gp%@5hZo5j+wbzL5tLTA@#?m zr;pxGhWC)cer>G`c5^dVngUMIYLn+#Zdk98?x5au(|R-6#J;9#!CrL?>`};p&abzB zI=2gkuTDPjF`JF9X0`baOSK?8I29dF17s@gqS8iGXaHi={3Q$c$aqvZ3fe@dlXq1t zYBCb7VxCiu%~;wBvH8MMa5h2cjqD1tqEr`^Hk?a|hA4ilC8YnlQN=Ii$^jtw!GI0cu_gcI2lDn)xm0=pA1Hqx* z+HLpu?*uM9svnBixLK4JzzvffiCF3jjUp8?l^5V;EbRq=g=i}Ak*010#h7I4*Cuiw zgIGGpSD+T7;ZoNT5t>WO!f$`oue0des83QHx)VfOAlq71uBMq;>|ZZZ97-l&jQ`#9mw{W>=Y@Me=DANZqaX|KhnSyuV@)5Q*vI$PrRS%@v=Oo9 zu7;vC;dBuY+UXnyW8C)7C#q%KR=6NXF=9%UMloUvXL?ZtpUH^L7o0XFSk{sMIhKU^Ym^DO+?XMS{Gx9h<6 zy%?lAaB{NRw|n%LYv2ET{->9%53@c$^L@1DK3w1z&;MKc`o3GhsxSP$ji-OP@B8}j zXFAPo;m>@T(iHy8Hx>1)DF&&R(hrl#NDxS^xtf0^jR1uz_0g^Dpd@+du59OM2+21G}Wy9Mazl?B6B5>u)4h zibf=y`6`up$vkIyniWR@BXWv~;KCl85y(ebHgpx_qpQZ5dmf`zlBg{7USL5GbHH{;lX1T<1)ucv*I}>%}Te_enNu3Gh&$J zx3C~ePx6_3+mX?Hv3TK4q2&qZXQTRhbKm;u2p>&-NKu0HMHY*^_#v?-^Ew6y!d2F6 z8a~0AU#EO0Ei6RSHCtFXFs+p7TngHX{7c1R!ek#okCON4S9Nxhs8`Tqp4kU0XNbT@ zNqfrg5RsB@zoSYZ?@K;mksBy!Rw?pEJ+j19{&iwDuE&mfIHY~s+HDQkEkK5l!)a#& z(+p?#+(=3#-{fs2lM~h>6do*6%YK1VXGks$PT#OpkKMP>7D$YC#I6jAh1i9ys4-T-0+m&+Lxx_GtX3Dp zU&tOn^m(79&1~rlh|E@wXN8Q{1IA|7lBExjZ@@soW#_gwo2%(`+M{vz!vJr}EDZ}H z@8mQ%b!;}!04+57-7<-=@d*RX+A#7?38c>zSQ|i{yU9rgegw!*kcIBuc*_y4=5=fn zVQNEmB4R+PRfwI43oq&JW`*MiIuEGQ=|hehKGk1!w@e2OCt^ARC4mmVs}?IDyZzps zs0pk=dF~K+=wIJ26PNDYaC4q|vxkn@AZj>^$jgtWqj%%$IK{y+^pn*pYRZ@lW4;n5Yt}E=0SwK{7Bo) z#`w!E=zh}}E5z=|hb)1*ACbmlCtK~G7%z!@c~bdmVP~h&&AceJFK@Sh{H#chF(xv6 zo7(j*X{d-iqbjY1xu9^CNYZuh^Wkr8Ys(22N0v$ZY2jS(`5}x18p-I2Tx-7^Dab5| zbu}GH+1wp4Nt%(pg^Rk_KVt1XLiAkIol@gk@^N0Ry|dQ7q0YO}75$KGLCyZMIby8W z%U_GWvxE9K4HvyuI$he1ic{m(=nm)2gV4#GIzTVqP`)wR-rxRdO{$$n*1aFyd=cg! z$#KdV`{-NRDZfgx^1e8gy^S@CV8W?OEBZ_b@$z)?oKlFgfBvHWQc!t|eFIqk$+`!d``D>evuxL4x>kEEj6wKyIOI?ieF?K zN-22jOQk7jn%fnlj%-2k{R~f0o$!H>y0;xof+|vJ#(IwJZm?EbF}8IW+=OI0Aynq) zjgsU@@I^*WW($TWLJu%A04i$#f zT(zA#C!$ul>Q)6%Swd1_N=O3UuA3dkDyDd&ixS3(8KL#l>%hHn%x}z}QjhfH2@VA* zjj>Lx(0P$b6bz%w!7}g*PnGC|RbULys^`s!W|Wu7Qtj$w__2^+u^=lmI0&imc^70%isEnpIHZ%-S z6s)`rvZBkC#AaWosR3a@_RLhx*iBuS_HT%!otHXlcKrsXkiWWr0mDtD?O%X zRvek4Auf}mtz+%0WkQ#g#!Z>xH^|r*3I$5~vi9nwOZeU@ctK!dUBq!Wj;66A}f-H zA}f{O+tRzT0;bTiV3qpLLe?8uZ5(Ys&n}!&Mb;N?lhNPj7aDEgnbnJsr0x-Xk%fxN zk?$Fjt)Jcafvj^-r8=q=r&pDVp4dY~<%iLhNS%SjR=CcM(H+IES)_wSFMen19x+-r zz8Gr_VzexnIC=U>jMCq$=#zgd{(DUSnbu@uY=b_Sf~+9u6(7r@&_pm4&fu$@aYDuvX>-xlZJlWDqn^VZHSOd^P1C)tVma&iiWELvNu?kyexeOrw@hdyC|?-kB!-VY%7OC zT?^OfG5vF3z8*Y##Pb1bH|XuS>0Up3Iwh4Ku4q~+Wy(J9 zZF2vycc+horY^1%Xznt#9*9v0U@1yWIaVE~$ecIZYbi&hlR-JE%zeqpe-)TWB!HdT zN}jQ(AY^dnwubBU#?PgZZ=Q(IQxJ+>%L|q~M%r?i0=0n}jP59y@*Nsm>W3a@qp6P( z8NOhOJKEMim}-$zN0REM9(LMJ9oa^@n-9Vck?0_GsEtAzG1YC>Ky6th8ZRKkq~c1f zqAb{jaq<@O$+`+0CiM{h^DO^(l;}_X9viko(Grb^LS#uX&7aWEkp*)V8@b<8(Xv#( z{kLQ&%nO-bxq?jPtx0-gN>!`a@7;BCzkqj#&-I(-JqJCj=(USjNhz>kbh$lf{}Aof za{mWaaci5?|3QT%Zms3){&4@XJA1!Nk8C;HA5#M0o+JF_O!v-3#|!p;APQ^NYyS9e z$IAz1scJro=v!nHo2V};n?h`|6@HI(*?&^DpDgnIY>2uTt14eIT900VkT5=!s#}J2 z)nx6D&k{kb>)e)(Su%j-Z^bO(w7A2ZZSuIfk+QNS*fC4Si2UJwqSw2q*W1$T?dlo3RWYX6UL+*hC&JWOttwQhHTrm))LW*o-4^E z@9-g!ojDEtBFkiSEeIbT*}NyFB6|i!cG|)N_Rf+GAv1dfV4X&5<%$K4hRctB3Ej-I zQC=6u1QKrE8v3d`^GU+yuzdX&SBEn1dtZbAxJEBGZw)n8pt4$Oi_JQjVntq7<%=y8 zrp-K09!*~)EybGnAuIHfu|eYlSj3W`k;7=b|7VgOD<~hM0HZI8NXr;&#PC;MFmaaz zNi>^82gJ!FLfkDX?zny56P*g;CabuE_KmAH<0h&&{MqHnWX5~v?;m%oQi4nrAHyZa zl`DhZws2ubz%4I#YYco?wYTZnG+d^F?j*7M!R-R?GU1OO)0Y}0X+iL0X~rhl#Rrm;{aZr1r#LeIT+v%0P2fi`=mQ` zm%lS!C&&ucjF=`d(wV`;SYR0}mZm5ExZe2QIGOZ_8`4*t%=5$*_7x{8F~n(o#R-o^ z+&`vf>XRw2cnd(Onposxgxx3TLfWz$C9PQI35p54bk*kzgy>=wJx@L@6IWaaFq0LD zen(w`jqO#Sr+F#bK3Sa;B1v3Tmln**XMT(zap68bW&Rv9?ot&;RkPnqm~mtKio4B> z+c{b4vhRDv^}L|!Jw!D@@tZe%LD=foNmpmuF>#kH4tm?*F4<6|?oWm#iH!TKIH6+1 z{g;Z{w+?rmX2$KA)JOd;+_ye=$&^YyV*&5~EqBSvfH=LcIFZ~CcYI%QLZgT~q^~&H zQzFjUSDY*%i2Jy&ZxT91+*^Ic2{TXJ+P>n1Y7zHrUvaXqAkJ*x{n}8L8N~gjulHnO zL0r7AI9X;8cXMBHLRyKtzOOi0@(_1XUvWaiiL2==P8a~dFQH6FT9mS0MW!ibI?^JQqgCWV76zvyEn0c1 zicB|uI?^JR#~&C;eNO;d`9?4y2VIRd!g%--qa2u|lN}38!H1C(IA1@C30!7JOsgrt zJ*5CfD}Y@A9uRMhF$E}8fLW5_HwrL70Ui>7OjpsJ1H=9J zxIY2txh1;Or9#(9Xi{{iTZK-P(2t`#i&Ut;gzBR^2dU5iit0H%x^u7!ogksA=uVFc z4VTcd(VfLAwEIXxhedZDq(aY1Xkc_FM9O&&{-7(+9*kR@%U#mSCYk#E^@1acObH#Z z5XhoK^ZJam2vM}pNQ)BP&}XDYimvJ_vUmbeH4*Pii+-&p?JQbk)iF(sVk$mOjI7Aw zTYzRV0|5_`a39pjO0US#E>*2EI!(Q4Hbt5q{jIM$rwLM*ilj~VdDEgu?R`dCBx&L` z+0%Hs_AR`k>R{2NAcUdMutk&Jye!j+y)?XQV}%Zs{}9qD@!z8EFxx`aUBq>U3J4 zkrsIx)@P(epAPRc(jrjqJ|is(wfow>8o?q_fA2HWqEQ?BjI@Z<3w=gfRBBS5 zt2rlT!(gQ?ri1YjBJ7YS_)av}0jx4OYUrcx66vlKkR;mZYbWVg;yJLDjPEkzn zhvSLq1vteqy+6*7n2rWa^&v5R0M21CeIU-^F`Zq4>XI0ymYS=NjOpwKRF}r|K{!KW zI%+u8Wij1@b4*Of$aeK{G5sK%<6}A&f2u2D`VgEGWBMUDC&lz{;hY@PF$Gv%71IyH z86ML)$XY!jrhgk}WK2iTsoEFQOK_0U9f{+Q=||xNV*1fI!I)l(QxnrUicnn})4zjL z7t_Cs6N>3&IAdZus)E(^G5uJau`#_IXIxC@1XJ~SG5vU)3uF5Ca4w4JC*X{a=`7f* zFOBIZ;#?Nfhv8fv(<^bVjOi!gTouz##HL_EL|@&L8q?7$njwf0(@{5=A=6__AB|5Y@0gB+ea7v|SA*|PsMJO&8tUG)u3N8WvZH1SQPGb=ihDMY zprKC_0al6VUX_V}qGFlMib@=^-6%6&;S{Lc!I_Vi;Bza9D*vQ5KB81#79y2ytSD~_ z7;lg4B*Iwls@|%5w~N)n(H*?D{{6;X5;3K`yl4G~{_@Yt_vnp#1){sE_v+phy9ut* zYrJ~{hrSUw`fY#t2EToy7+T1$M7x5Tu!SW&Vwnh6N_5xhuhkkOO69C-7y2Mm`?E8q zuTbr)PA)gika}Z(FtvJtEqqSp%IJ;|C!f$ylu^oQD5Z5cTY3Rm(AxK_W3_`qHOA}+ z0DqzoqYttfSlF*RCdVmjY+S-5QS@-34cMTbZ=0`i%xh3cKWet*su!sIR92}L&QFKA zknmnxRNLz8EwvLi1QSukU|^o2e7&nVKeBW76jzNMGkVQybJoS`4~gv6kVn@a%HNx1JMQ=I;rAjJgMRq73^Hn&!Hh?CwVY6^v?oAq@6Dt zwu?ogkvw#OGB}#2ZXkZ~7*1_%c@*#;O!*5>azknfr8Q;pC&aQpGOy;ZN@%*UA+8=&~XiFD9>x6NAZ^ zBkaS2(aTWB3nps0?hXa0@wFV}mi>WR)c*iZl0X@V$s(pCReUk8r8=UioSUAcK;I?m z%FGwK7k;Gri*n17Q9g@|kGvcsNkc2-6OCYgk2C1)Z17aJYz?}e95Cr|M8qF)cQ6*f z+R?AwPG75cN1N~FCGP;OdJajRK-lO^*7jFfI)=6ME#h}rVDt=6wRrJVM=!Nzh7gU+!=|4lhsJ`_Al}Uq}PMq|Z zz@9Jb{yCA!d-)-EXYxjswD&t(-@*WrBwK{X92>4?XE`s=T&WbpUHi zm@Z8bprl9^pt*usls8cC;JP#QmK;_Vq;gT`I!tW*qE!WlFsQ6?z}+Tl?*h9xYUe^s znS(|@BQH^X@08Ayv(d9Rh$bX@{?U;$na?!W%;o%cIKu>F>Pyy=2=$&HCf}y3le6XL zNq*?o``qFTn2VFV%^Y3FB)idSC3y+)*P8L?O8k|?cQ{MPRAx)g7SOmCqY#8ey6|bq zn5S>B_CDQYyhcNDBYeK6wVEMtuY{n%=FaSl=DckQf<_ON|ZHdBgE4=`W$@k+%d?WxYVvy+JAd(dOm zG9}TG*8<21pOW0omnH87k&;DJH#+o3z%@SOZt7aO&Nq8a@=1JH6@Mf8t}VHT)JA*H zgJva8kxI-%iyxyz<4S7dz9DhM+3{dLHWAf16Pcz^B>fnab`3A~llAnx%9ie}6e0F2 zci5%|AX!cHHv}MD!2WK9^iGc_RlEf*#MmIZKw@j<&p>H4<29Nr`2ZE_jS_}56ZIR! zDP|P*Z^Kn&dXkzH<+VoQGJy-pgQEMHMK=sgN2H2xpkU0lIHM_h*q6!pxU4c8j^WKU zB$G<_;~k0XxZ%i=lf?O!^NF3G(|09POM~2rCI7U9>$j~_JbuG@*#a;*+G+F0o#)~0 z$paR`qqc|riQsPU)|KBhu*I*`>Gs8j}5_MbkMP1-=@+l@93Bp$vNb$c( zl4~&Wiw#7=Zo*)ticgXl@)?n4tP-14tV-0&T(t)iiKIjbdnI{_r$l(y-B8q^F|$)5 zVZcJc_)IBADx!e;B3T!j{O8OV-sGWDE$a=&?s!IY2)0o4v+D&Xx4f+LOyf*Ao4TF4_wH3TZOA5n0+YYd72J zXG#bz9robc$KO+q&)Xhu2u3d-Lauc~7~krn0$l$QaHopL6T>w3`lJ!snHX^5?|C6L^e05{eb_l7-2J>XcEULnKYeDo!sot6 zRf5s{(&XPwPfj`?XB=cRaZOD#!NlPUCrKtFpTOxE1oaUFp$ZXcm1ui@(EIk}Zgb9& z>1N+|4fEZm{oeI|!MmxU_fr4V&|38?^3K#yk>RC=iaahg^kwzCLjB63A~p0K_4{k} zdz<==sNXx(?;q9gW9l~s6|8JA`HVV4heC>JkmLZGsF%l3yiLGh)V_}X^A7AK>Dx31 znz;>wb#8d8n-Fqr-yrv!Oz5mVdTq^w_A?9*a@|pGmI*ld+x#}Ws{t7oZgkUDIfhk9 zF)J_VZsjm?YUppNL~7_v^?Mz^ly~4??|L(}Y2VnOF?&4q?e<>*Gi~~VjjdY;ci@Wmd4G1D5cradx_eN`&IXqxHv*YwVq9;8nfO!= zzMC)>%96j~RwG{~O2qYI8?V*~Oftr@2&)^P{5i){C8vsg?;e*g0SAZIfi`jH8FztZ ze9Hg(`~PwaJku^*EN1H3O!r>Peav!KS#HvDKet@B_1$BYREq9XT-fFoISnf5} z`{_2zZ!ya~)^e*YHyyvk3O|;HKX0bL$qH|?@?B?z-?ZFK7W}UCdsc=M_X~$w&HRFv zn=W^U72agI&s%P;RsUP9@HLiuzU8{D_`@vsC=0&8a?|-g@M|q1#Bu!a9Ob6rn%jhD z!e7xd?k+RG!^%x}>#L?a?={n93ft!|o&DHrufF!Wsn?cVK5fF~lNzqN?piD2$CqAK za_QvBm)=-%?d4NTuDSf$D;lnps3{GTue$b%l7{Q9E4k*nYp>9>Uz+((vg#kQ+?4jB z{p3HKIPXp?zpolDaOCDM;N8fpHq}nPLbDtzJFUxtKd>Ks-pgkCP5Z%1KC$vCF!K>{ zxBQL&*o1fWGvVp_KC#7wZ_P8|^UUO0$V$((t*T!0E%*~#&GeCc6E2*q{Iyu{trq+a z6ReHjYo>o9*Gxatf_Hsx!b29k$%4<@Zo*p}X8MQ)-@4m`Z*`jRs0DvwrwOmH;Em~g zcA4;b7JNoJpU+Hqq`#R@!h)Ar@JSYYP8$BHmA?g#S@4#BnDFry+_2!|t@J(%9=G6I zEqKU+&$i$Ye)YG>>dz+zn)R~4TFEPBIb8$xg{R|jeT=D)=Ft3_N2}LtnywXTV_^0J zTCq0Pr}=z7w=d*#`20SPuij_#>AoW07@yM@@D=;Ux;3}Y?RJOU4!7U!ao4+TZrxqv z9^-bp1MXt?*pL?Th1{V~$Pw~~JfZrKEu@EvLSsVCP#{zs8tc#;K8M>8ayT4*hsROx zusL)`kz|^<(Ot^?~~0`mrq4 zd^Wc&WOLa3Hjk~|X0z$GBHI|7(-yE5+s5je?$h0RNO$Od-GiM1o386c`WW4*2lQfn zY>`&vD{>cwiX27$B2Q6$k*!ECDk>UN+HlPK30e2u2a0L7T zPoO?v3+REOz?gtD5C{|p#ujVEzG8QAsMt~LFZLAI7u$;U;-ca)#m?eDadGk3v2@{B zx?wC$IhN)a%O{PcT4Tw2tVGk{4m#USr+esdAKmY#>qDSGy(ZB%n#M^J712D!G?q?7 z1!%A_G#1TAhdJmhH=X98!+dm~pRNnhh4l`)mk)CAF>XG}!w32JJU^cn;uGtgd_D-{ z09o81jR%DBfj)jvCj<)ByFo7+)InpqX;cpl>Z5u6G;N3`t}mkb>2L>~?WWT`bhwZ1 z_tW(uP@vuede9&a8pBPacxVtG&EuzOLNrl*G0g`;J3wYPNbLckeW14=)DD5-^*+#_ z4tLPmZaUpVhx_P$KV2UJ1>~YB4TN-njBb$9148;hPd}&`0!8cnpf4TfptIa`nuiYa z(S3fpE<_jB2k2e~goDB0W>9z-5I%;7pP>p38ZJP+r3$f7eZ#aNT8>U!qN#eEG!78zx+N`DH45ZPF#j|6szTuyU`t z?$XO9UUTUcQ%EoH8-5&~NY?xPee&p~Z5x-wu6=7l*>`u}e{%J!FNcEXUVQWqMy|U4 z$WvNYJiU8ozLq%Vw^`E*%yCt<=)8sZA`P&>h zO-n6H$awe&zOYOOM^PW<-5BxMuD~Eh5!PAxY);-}o!VI{{@)t^yn5Ns&-+to@WtDnZhiCZ`>uTLyzhMa zc9^wPFb@2#80mO^O|FxnSM)GNBgsD&U=5j?R(l)+SaX~^Y`heBdouz z{INgqsoBVD)*!KQopzc_q|sVSi|J~-WyYt|wY1pPUn(W z;nIm1>F1E{HuAWGx$~F!9|X1l_m9kA^Zw)n{97t?~(R1-XD@<(@JvkweRp8ms6ru z;V5jHgXg@-HKQ4zh&6%WKmgCT_%qi2R0dE5SOVZwp`#_Fgdkp_mJT1hp%DGJI z!oMjeq9sZDKF>$wy)~y$`vkw!{dbr@6lZLWm=Jaw>A{mF@#U%@!Gd*0brx? zpK0HsonzmrT|l~vcrLYnrd@>_Ca#gVDDNdme>=~w$nz1xPxEdo{ugoI;Cai=_wszm zJD>9G0ha41(FQsk+8~EhJIs-7)ry=iY6m+%)rJx-=Q-Xv zT|2?KOgquJMXSUOICpCo0gsY)KF_1TpLD*hJ?p$qTTT3n#INC5&$HY4infQeS}s>x z<~pXf=7ev~z%8 zz;g+(3B+H|GdFL$b`Rm-lV*M1bnPwT-zEIFyln{F_h`Eb+w+%cuKW+RBHY3G+qFaU zr)%HM-=!UcdqO^K!E+jqp8vKsDu1t5m%m$^O4{kT3Bq$pcR%sJ2lfc=qxpNZCvpFq zXBqEw@T?*2TcmxP{NK$#UweGfE-JXBHmLi>}-<{e){fe|9{f^WQ>u1xB z>gUtG(~oh4I>Jf)+*%bkx}Qq;C%Cr~j`b_i9_(k={y_LC;EQ;k1Kvh>74N>x^A7nW zfqj7ARzKM+7-YO1+-nk9_@DEcjMk) z@VWLd{y*_NN&004)3uGH+0Hwug6A|(f45fJpL(*h>&HF4zfC)iG0oVuqeVVHfd@Nk19bC9jJEU;Bc68xx?YqE^FML}&p>U~oN+Eqv z_^eh_xJUb8;dbp3(q2m36@{m0SL03rb~EqIDtuFm6E;Y9JMa9Ke16ZfuyC8Utnf5# zIsOjPt>t-z=QYx7#QhujeTKW6Cq=nA18iEsfFiAM0A@`Gj_WA{WWcTm{-X;4kxRayhiEJeoVAIo!}zcR$VT z&d~s5&w|8 zQCoof2c9Q*=g&ONq+3KCo^i*t=g6zYU7j7YMLW0%dW?T~(RNJ-eh$xhMW1Px0Q)Jh z>x*vDZY+9R`#I^R6ssifH43-7v_lP2-6GZyMy+ZUr`b5a(x! ze*kyEppf>&pnUDAL9`F)nt?4MZ41u}gG#inLDkv@;IH%im2_K4_X*Dq;GdJnUi`Mf zd$hd4)3ttsPt%4DX8aD`t<@18Gx!bdoWbvD=MLVYO&Gjg`zgWICo zt7LWUBddsC)AeC_jPh%{a?B#n;@i+QL0+az8RfTheL%|LcVbtv+{cu(-CQM=SbMiFS? zzwWe2_gb0IeUwb@-XK%aXCd>tb6tSnia!@w4fil**1@d@e+NDX_j_<7_IAqd0^b7P z>Hf3q2mgqiMlMtS2e6ILVd>(-^@z_JxzDFj2Kn5Sp+2W&m=D*SKD9CxZWg!{w80gi zulBhj>wFsI8J|*l4*Las8-0qT0^9@t4*B-_43NX*s>1#R`wO3+%nLIzmgZgjYuguw-Uddl5B&bbzr>pcq!g|uT!42d&&j9+CfU`0nphyM<{3JsI8YD8H zUZ%pw!#xymS&9OxSnC#lJnEQQjhUFEM{YYLScZKfpWn@)GA>Z6%QJ#`i%V z-m6%~qfbPp_llC(UKWY#)kYGKEc6xdD|>NGOWc#-7GmB-|1)Ke^$L{FkyFH+!9E8* zDx5R8SFZ;DhWs75gzaXD;{Meldk~9LgTy)2bDzoeo>P^4j&0$-t_$ML{o6grur|&T z2dewO-*F$evrUxz32bN+$h}-W_i**{hBL>cbCLWRsX_nTxmKLHSMqObk-OVAN;vmD z_aiPYMclvC%L^_IvVrM8?_i%t{^?RH*SU8I<9_4OcI4td>-b z`-pfX4aq<<`vhIvWa_z3Vg4_?*#Y2ALQQQ6!vNCGnBai*WkKZ zZeTZZKjPNDf%}Pi324uKF&Kh`BK_c}f-&ujWI_8vNkdO>=$XhGx4lvXKI~@E?k!3w zyMr=2iF=P6AA!g5pK<$1&SGD3E0oK`T!U{QkEeT)^mlKRVea)3?asYB_Hrb{y+KxR zAF&?&N%x=RDR=r4{QJQwxKD|%1ur7sp3pV9NYo0%JZar z1phJiDSTfcUsL8Z_F3c{(g4>;j4Rjp?GYa&sKZ|F9U5d5*Z1Q(1WGg*gTy0g9cW8@ zYdRFlI`sAEPr<*~f#rg)Af?ET4z;oe+z%cm?mgm;qJIi^9J?0%GWQtQI-Han9rC0R z`xd-w$9nPXSStY?izF26huyy;_np`g;0U;J+&@h1_@GRKi|=?+@;lPsbUY}f#O~iCwNMXyEw9{vVW=~d75d834MqHp0k_%W`L*LJeV`<*xjk$)ncI@e3r z&W+*?j_=I<64$foT-Rp7z2CW3PIFCrwR4fAaLxHW*Q{N-)^aUbFGG;nu0>oI)^lB0 zByYpN+qFTuan01j+agx3iC*CP=ryns`4jq=TsM8s_0kP*`fsjZ)^WYFy<3C0b?4rP z>yYu?i?|-KNMiST$w7V(x3RmIe2DJKx!WD-=#wWsImh?nx*-&dM5Z7y$aJ`QU_6rI zW06#!NXhgm)vhsCYS$K3@(R99=&zIOJ@n(qX>y*yz5rgtZXnN1B*2&DAVI!W($BY6 zrujC=bZ`M&D%>*PdMQHIV?T{-#9xm8b>F?R!#7WM`8LWv;@$(R;Li|K>$_Smp?`y1 z!ET_;4RSORcgy#txcaf4oZ~(HIA4hG|IX@T~fPXahWb`S12W1-e4D3`e zgV=mzHTD|pjmXQ`uYjd+uOn61A0x-%zD6#Q=N97Lqh5S_6iH7o014_*D%XeIhTmc&beh}AQ^rgL6U(mN# zg9P+q|KK_y0&Z$A#z1;i$wIhA=<&ql6SI=*ipP<)*z4fe6T69+QtWd4JK=W2?Ew!` z{yn&(_&!CxfW5IFYcyIzZ5u5}@ zgOkB2;8buL7z0iRXMnNb1K><>78nQ42Iqhef^)%n;CyfaxDZ?fE(YVl1TYay0+YcM z&IlrJ7Zf#QjD2#S!7pcm39s6;x0U68Ip zW#S!FC*6?lLDk|Dv_X6kzo2O8!E*eOofsw)W+8FJ%tqz}U62QZ zZpmC^UQm@t@GY?f7l~7Ft+e4cTAhPCOIy$tzCFAfygRlByeH@d-wD2R@H^5ac&u~{ zJ|f=1qr@k;m-q(XEq=lA(j&Ny_y>P40m1Dh5WYA3UGR6q_kq6`zAt%#f@er@@aGZ| zyhcKUr%Auy;nF|2P6h<~OBnuvEGHZs1P%^P?V1l?0Kby>LgH5uznb`B_=n*ifqxYKG5E*f*TS!Ze_tH6GpcGy8X?4lhi;og9I z6Yedzci{HH?T0%69;6P3C|ey|D(?pe$p^uH@?o%t9Krqw`(x~5*fqg9@)_knr_2|W zIT2heUsCT=*k6(BYxcL(*k{<^&VqIDe}n%!{6DbI!~Yj}0sdm}Qn?holMjC6$v44W zlw020*UDz!Z8rSF~B<{eTcm`gku0c2tF7-1U?kLKYSQD29hH@WS|TR$&|q%Q{?`T z2@(N641PG}MzGA0#E)W`QSf8n$HI?;9}hnPJ{o>9{1oy`CC@bS#E>Tz{sH)z#Lpr= zj`-Qc&xM}{KOcSp{6hFe@CopV@JS(zrH1qnYlxGi!ll72V?CF%o*AsC9qu8xEVyjA zT)2F=0=ShSRZ%hNZ|DD+LAs%u8`y%!w>~FBI zV*iu4?`hv3Xy2b`-)nH!;eMtr4b-=F_l5U^?*Z=*-xEF%zBl|`@OQ)afxid7 zFMJSuaOf2Y3GE^MLK&xp8weK;Hwf;2xCppmp|fOoXuOP|>_~Eq3SA>n@MDM_8_GUR z{l4lW1$1&aXuHd7JD816QPl^9{$PD=j18)68PW2KMVgH{PXa?hu;AI zBK%A68{uDue+9k_elz?Q)_p7W+{U`U%DR`sSHQmxzXN_J{4V%P_&4C+q`lvwz26Qk zmUluEWnXBZ><{fN2SV?XgK&pJAC|+RkH~wWUQ!j>NviR`kN*Swf5iVG{y*XWGycEe z|A@RFllLfijQV}Ta%<2(gZrFz{epHqLA#!U`wH%BxYKZF;OgN12KRTkf54rGy8w3) z?h@Q(xNqRD!u=EOd$=FqeuTRQcOC9$xCXcza7AHJe3YLt*sv;TH?Urc2S&=lfnM_C zz*^}X&Nv&gESzU<;5*^?k|$;`|IU5_t(m+*p=uf5z7$feGEA)gNM{h3G&hq=F$xDlF!lqF{Db`4s8_w zp;6Lvs6~1Wb(a3<1Bcd1Dt7VED%n1?NIpUT*HEtIBZ|Z$qDlrw@O&MNLFU6PLYAW! zMl{H(h(=k5{sdfkM7_KY?#4a@eiKnE4RAMzaUWJCA;TCy0!IyNkh#MeC4Lysy5I_i z)k+b#ZrBg<47h=qP54TQ+X?Q%zZ=|x96$~t@8PQ=_6T?k`z!QwNG);+yOA6>!S=(s z4;;?8H_~%BV|-vJH~@b1@LHJ;&OjC+8Q7V~di*aE_X^x5^c~1f_}$&kh&~N&KDZ27PFx}U zYV;!Xr_i4n5ic9CUqUvGXp|ku&Jm0;QSQwVPI3_bQ@GQ_`;26K0tp#eFT+MM7BDhT zreV(;Stao!8zdDyZDg%18+lM{=mo^B$9`#Kyi{QCA#NYCAN>Gw5IKZhHS(++A?`T# zS<0Lv?*;HO+!gX(9chv4_!{7RN7YO4D8{Wu70HNEXC)FndKCQ{ybWB9{RF-z!9R?O zl2Y{7@$EwPjLMUJ*oTNaJgQbcg1Z9#j5LmFl$(_C8O`&G(TvHC=9ww_sL`}17(MzY znLN5t7NW%5$WjT4 zWc(qL`}auZ4j|FUbhtEPRv~L5BV`?BHX@sl-Ne30%pvrzA`i>y$dhso?mOfPax=1! znaHej6zB0Mo(Dvol+Up1qVnWs6!(K;iX?0d$H5pcnK_2Bh%tO^dyJF3I;K{(26B^}D_`XE0Vt1L?AQ2O}u1B7qSS4i>>*Z7QzoP$$ z{?kPIn@P<5K|&@mUO%ZyhM*@+V%!9~5czUagPe!II;mFLM;D1F5)fT0eZl_GjS_*3 zLXV8*7(kCfUkuu!8LJ|0E&RIZdU*=I0@(?-8+<#O`GD|;qYuh^;1S}E;yZ@#TlDY1 ztI=NKK6$SMO=ek>t0Zi)MMh0#8%}PJDR9%l%*pX$gDaR^FHeIn!M%cBid0bMb>a@A zzc;y2KEghSeHr~bu*Z~o={Y4&0;X`RO`#8)!uaQuD2bf%gUpyhKZZXIS&n2PYo;{H zGsM1#yoCJLnYeo|RJc3S=j7`=>U_5n?_@j!r!-pG?h@ z({N`fUrWqIQOR|6}Z<$Z_QBX+Owm@B+)f zjQ?BmyT(+BTTGt##_-Gr>4iNYX0;3hhsV^)2z>L=7l4an>SbljUMU1u#ZVXIDPo?% zE=50#93%EPcq)dz6#TCkXZab!sIp#yzpq5F1<}cG2`IS%+@} z`X=~t^a^|jr=OG~`0A!d$v1Ef_;299h0kLK$Hj~W>5256!Lg6T%wUWTd*KY8r69H$ zJm&xlk=4XMKEq31gnI?O6xl)SPH@+Z)v^cvFmXrFznW1h=io0Q-@<=~T*r5VW!!>u zjkQQ}Y?b^5c`4Re-iYOy5;*Vyj*kZdW&Q(RQuu&Ho}&IQo<_{i zV8{8j;y0iEV?M{t{3_{>9*I5=e*XNu5|5q%w+j0i^bPY1<#limatJv*zfnHH_Z4Mp zu`eK(i2oM3iQJkWFYOob`~>N+fbr-Bh0=Ec8yB6fh0b<@m{}}E#v7ax9lGE6ikjqHJf`ifs zc34Qiv5<9M$e25N2sjFR^g{Y#a2gV`kaIS^45SEoobQ!B0kPu(_EYXt zd|xbdmcJ5Li+&mYI&tocxSm_oC;^LD4j78`LmvP?92~KTG4Msqu|j5`&&R)D5zk)n zEhZ)ne)*!kl7VhtF^my0kRt3i^v{)hp>-;$H?(Hcpbb6=NiwxgZRYL z|HgCNfsx=ea60@#^hMxeFfG18(&26Kwel?7F1X#`C&=;mDmeq5gR4a@Bi|Bt4e>|_ zlrag+XHMW;o=`7&=w%7D@;>%o5*p=bLX=!cU>;eblZ;HX$hgFMnUWYK3lhD=hP?_Y zNvxGWz`vGQB!303CDMoDw~T z8R(hdn$%iZ2R@xzCC`8x;a^5Jp&y022wo!QGI#~vJ&o;_#=Z##rE#uAhNnf!Xk;?n zH0+qPTA7~4`6`X`3HBOf9r6V7G%?R08;RMJ7B8jXF8Gh&zCz9**UA4gcmuxuGM=|C zqdx=tEo+pK$SCYcWd5=$Nnh3=Hn^4e3zzXM8@?3Tv8-11!M%s?2yzte__C972K^G{ zF5|mK*+z7~<@8@jzvawlSbkDQFJ}(da{A!q99zq)WbyJsiN~LgZNs;A`B`}aef{!0 zDTjZZ*pIP~f~WDHq0Cu)wdA{i|04Xi;5F>)%YTxeu^Yjg;4Na?r}I27y-0%6Ps#vr zcshMbdc90fXW7{C*agT+^fhqn!KbiaOy_wa_9pBK+6N=U|6G9rU9`;5I3lTjse(H9~a_}75z;5LG#89e(3t1=qoB6tb=8+;Au z?K9~Q5wA>(bk3}mfK2*{Os?C(>F`-d0kS5uRMvt|}u9{D+v&{*a5< zz^xC}O65a5r+kQc(pgpFkrgF(XVEuhF_$2#US5T(2G3<#wHpr0NB8fsCM2d2muM6(Wwa5p#_3~%*FLU?G zHSBgPxVBp1B+sv49>R)7Ikv(}YF02G0&&W#m)?1d$>lZ3yU4yg%ENt*d;$NDJjN^X z*>?Gn;*-y{NWMj;Vb92qmjz%xvKCpF&-nsbPt1PoqxrNY_%-$=qye9ML6LYA@I1eu zRyq{09~Urh53XlHm5hLoM4t>d1K&dQgaYO*;7fzc$6ig$Qw2BWX}AjPUDyZl9RWYW zK8>71E>iwl!Cq+~?iS*)@(1a-@~rr*qz^)dA(K~D$<&pM5uiV?GDkiO=6~`Ai02z%Qjm$$|2o@qmt2loUTLt$O@-=c6 zX@qlK&6vPy`q9<&Gpjidt*(+OtGQ-@n~psnS%4jnEW?++n&WBpb;-xS8g32#r_i59 zo>|Q}&T8gDp}&G}6S4cy_ag_f4j#MmEvI#O74UDjYfg*=PB0eP9YilY6pgV;mpRd64HN0Cp8xQ7Lg z7cnmaz83ugWxv7x9{aegy2VR{|MB~wa;wFE#gyiSM)l9FHMef+uZWJ;!SrwSt@lMa zuc@UybNgM=PrqHV?AfngJa}+va%P8bj-5U8 zV(G#CgGNvIX6(*6l1mMiC6rhIbvPADby zMb&Q{YPb{j>C!md%WKz!JK@1oA3A@sbMs$TKkj{Xmv5(K2mZ3++QPD>ZiTsXMpzfd zY?ZbrKkD&ur;LdI=@R^gS60mSNnO^hN{TN@>Ho!*9TxA$F9c^!>Gj^^H~zchbARU- z?C(EyV#jA;V-~G?T73)0@^obhVwu5uvYm481Va@oqmI~_%Yi3SdT81?{ zUTs3jNXwk86Y-L^B+I%KI?bx1@~36yOjNHd$M@IXT-FQ0uSiVKRXHd>N2$t@$Un)6 zSvg8ivn6JvW+i4g3Z$g1n3kBGGuxh$n4^NzF=ixYjL%4Hq9~;;FU$H+yyT>1sC1H^ zoT8<$XRDg=?sZ1uEL(0?vbI3o9&gRgNy|)BA2o^RI#}h5wHd1?+19MI#Po5QTBV3j zN>9vO?ntWaG+TCz_U1MQf7v-UyWR2fHukj1)2EIbGj`epZRyk}Epz#}bX)TBxU96L zOZ9igRk^Fi_faZe*1zxK`2I_oi~4)(VTL^?LbtsP+nSk}ly05b)PPyG zj3jDq)Ix-&ZOv|46(c^(LGZr2Jts?V71~y7zO0;NEnh~Gj%q!kUoq<8segAP%D`%0 z-H!D$zQ^INV_8QXi(hKJTum382i&&xfE7l^MQ5b-Jz3e!iM$rJ z;nh;AJ%NX0#Rq`c^8`1_b{&ZWeI+(=doU|3z@oJT{ z2;H7#b(}9!6VtWRrShg_rdad!Fi{m6pBk@Yx{lHK6dz;YEKu*q^}4DBsPo>m#GK?* zW0Bd`WLsv6<7}n-)#*9Df6j_{oLZ%fa!q{hZHLM?PJP3~NP(-#RB6jL>HTEjM7_Ug zIxiXL8HZh0s>jtBaaRrWnxvNtG%(-D7iRd2?~pe+b)eQ#$*JK64l;1ChN|9;i zj*TYdo-?=UdO`mZ+3gRrq~zul%+5@6Y!=#eg*Dw~w>r4&Jge32Flet#YktlIb**D? z*|wO(Y)+qipJm02Ts=)@mR=Xt#u?N{oz0VPe+ov?lAW5EV#|w{%&ai2-kI7J64yCP zlQcVUvvK~Kskc+LfsXTdg5j@xQ1{n4uuLzPV8llm*sR_xe{GBM35)bRd35J9o30Kx zF0gO6mcAd&v?iu#`wV$9v-I{?aksCIRGX)!EulHG6Ep1T*0H%uw6)@^BU_r+uQ*X( zZu*jJ?Z8b+OkSRAzr94q6=TBI=HS;Ce2#Vq%P`u*;A4$AuVp%4VxVdd>g(85 z#Lo4cjl;=qT;ZwxLRC2}Q||#%)0T2nI*vPS573SbOFJhQXJ;3ub}nt5+c@!2C>@;u$N@u4^3IKl;_?`RZEub27Ma z)i1{cU2FZEM15WC_FQ9YsO#RP<80}+tn8UflLk?Rq^0)TofhkBOIp^9$jHt~%$c0U zm9+ksUXZL=sy$=E9D9Yj-c8JzY3$P~QoBEhN#jad|Ks$oHKKzej~1@u?S9wD(?oEc zY;`Opoh`(1my(v8V@;XfyaM+4{z*$?6Vt6ZIaakq>}-2WJ1Z^6IxQ_b$KlVjWu@G< z)hf-hs~FwSDIkjjzll<5RbCZ0E-^hlHYZEB8A?;U>WcIxG0JP*sa8t!=cJ`u)e<#E zm6c*MXB1{U7|`J|P$>!fGvTDq|dYv-xd#LT6}jazzR)>5m!iL#q^ z8fuWOZODv7J6SSspAOW0o$Ai?8?HV%la1BX;M~P2iijV}Uu)Rv$<>ooBn#!nrVPD+#o9pYk@iwpG_EF9b*Rs6w zX@4x|*Zg&?qa$Op<-Fox^)%*oQR}4Y&sdSMklSF6Q|qAW%UI3r07I)I|F6}{Q9vCZ z5v`P0%c(Q9+1fj_bKs`t%Qn~V6lKa;55}Mzi)%txe2!RiyBujHRuxhiN=)1I->sX9 zuQcWA`){^W!X4J{YKyi}>*QD)bG_Ain*SW@rnpMVxYACH;h964>1sRpn3fgD_PgUg zN34pkGp(O_d1{|?r95L}&6c5zxh&(~wVjGn`7fBuv#&XpVP5YIraGu{ju@q@HdXD% zSgA6b{h8y`zNPjf#txgsD8n2l)IlwevBF=(1;YQ@dZ}&TC}pm{+AgjAsr9chZ4XC` z;;T)|pL>UPIa1m@Zyn1CY{99V=Jl&4*Ae0vJ8&$n3C(fWh;tBsMd!upI?+W$H|v`gNqo(c774(Ae85b$zO-wYI~E*k-ORcYbvX9I%1SQmom{? zSXZRwUjp2}YyU7)IhD(i+q`_0-~8u@QCtmW)^0^w#3)}Ge5>oJocyV*VZ=E8%y~DE z_Xs)bTFI+?j#zU&RlNDnmE5gu=Ogf~wTmOC%F*|)oEKD_seb0%N>@v(G%ZuLg(HW# zUaEb}e=2utacX(3{V6uO6`#g$yIsbSQ{_ly8Ls#k1N%kF1i04PMU`>nFxOw@Y3)z3 zWhURgJLE07L*BKfvMRr0*-BSsl%Fv@+OGcQNwQso%i`$bV# zxR&iSbJqA-v%4479vB}waaVSFQSf8lya6IIZur9SjJGk2oxyn3!m(*9qZ9L%FSO<6 z*mH9hrlhAYOixQ%svh(*IM&~ul4LAx5U51``5=?V#ZBXrQtE%{pTj6upOiG^^MZ&9 zax%CK?RjAAFt%?~Tt;S}FvgJJs++ic2^;Pt9h>X|ec&sK^EZ|5pQfI0^tm6mFgBqt zGb}852|p3SFOTyTXky0Ai++?k6d2>#St8B)_YH|CT zb6Q+=GcHbxJJOuf;%ea5#!=@iwO;g%e{ka4KMuQ(HYldXle%(&q{sAGxpHMn600j4 z%8m8qL3*FbW3*+IHRI-M%P2Q3Gft1&2e&~jJ72F$>^@_=Xm%epJSblKd4G{l zm-e^nG0yL?UEq!F!bqaXDmXRPWpLWAQljE4P26A=r+%BqN#)YF=@7*UV-ICOe?7f6 zE)(lw*fa;fHzWCMJttNKRe(DN4L6zshUUHwXfr?3hD^|l*H5x465Adw9FLwsz-)HnC1&y*-$hdBR=Ve(1(GGWu?>4qc9zM5$BAnS z-+AxFCshJPMrt{zAAEC;c`>FKZJW+?Y*ROJ%VL|V?fk4$n6EeZrc;<&&Rp8xm+v^b zS-ObYc8W`Y>wUk6-2Y<->36=P+;hT9dVbhRymLBJ7ruqc9$kg(bF7caws^XTCqJC% z8Q#9GU4l!j-UjWZ@^O*jzY%fXp|^qA*7vFQ>{IMlgW0dtKIP@Yd|18}4CfW@QP*2- zGxo!_>~pe~XKu(eBbV9s4QR_Wk+IUYu1$hdtf?*eE>S>El(-HaBVkX7Jc~SLlsDTJ ze-H6bLE_xSpY`w$_qp1wu4_%_YA<`I05`UCM+pemwsTF_>dy8~3H)FUZ6-SzhicD1 z-iWhDbs4uOLsrq*JI$l!>~rnfNV~DBg=yPvF4FD1yL4jx+NQWl#Xt*b(Nv#+p^s_Wwe|GSxCi08Dqd(ycoo-@YgV4cnr%xzSLqk0`YKoYD*7wcS55MeN$kU8 zKlGHrgFVD!rfZ!`jk8_t85Tb`@uU6yi1(u}^JBmA3-4BE;Tz=cDeblXOy751h51o( z1o_ZWZ{pQ{sJMS3HCny7hiGA&%kj%J{5;JdajSN<_wL{<9a191dGHV^<6Bf8BL2J8 zy0fRi^YVIg-j#D-+)(){knf83wn&tU^>GoOAH&4!{6KL%5w7t=@Xgio(1!3T_BJQY z%hP`5Wm~*m#XH4Kyy<7X!#mW`&&Iasdli?UpASrJJj_{!(H5LP>=qw4@nKv0u&sT_ z;}hQXYUetRWj}Y?nl@(N@r!FqTQhfF>nS-dy1H@9^V2*ty{@v<8r43k-_W-Y<*1Wi z9NTFy+i51-M;%8=#&OiyMLM&ed!BINc;Ttj9E)^9Qufe3>b%8%(jAgX6 z&E+gk7SWCmbxh?jPMd0xKOmu%s(x(0UvTX2rWmaqwiw5|w+nrSEA8M$u8vZ@%p#v6 z=_;4jM(xG(LobfO4jS*VocEoz<;XAWW@WW=6Yo4{SR@uHGtRSSTRW#x-QLc0dz=UC zyx&lc{AlDi+ZLBL;xZVq6R-6pP7;;Py9Y?%8~S!J+uC`Wc2MU>NB*)E7U{`>fWUY`1ofZI;tU>b~GR%ZS(8#(J_HmEHdY=K#gAo-OTPlwqCttwwq0)a`XL z+m?X#5-_&;HA+YNGT*yZU)x=}zS+F5ZMqKBuARE7YbWj8t@vW<)B6KA`QXDI@>Yr; z=bX;sb)pOBBVQ@G$oIXFYNOs}+oJkKb+)Z`()V-vr^?F~If;0@!+NUg9M+@l;Iysd%le+ljKjMnV-w9a`EdPg5EE&2nqkdYq#<-|!ocGU6KF;8*t_TEE(^1;>4} zDMmkT+|~86aoo5`-7O36a`4@Lqb_FK(!oVKaQ^D>tlL%BYL01ju0HQ22^OCJI&uG~ z%4pX>9ku>fac$brSH7#Sm)X|((k+}5wSJOoboxoXJ|5!stZ`44#C8mFm+pi8rMrb~ z(d?|LU+=1I)^wicT18z8Ir^GvZzoCX?j%nd%QxGW?);_*$53~Uq3#?*-NU_NwR?un z+|$tq4*pT(-t!jen8Q6{3cp4e=gfC1o#bKUgpto|TU0&T(`Qg;ZGXtpuD!zAt9#44 zL>97tshI8qoMbcd442CawR2-E=V*0Jq8-y5&x1L~aZIZ+PLw$j<|N|=I>~m*sK3|H z-!R!)y~CYiJE{Jb{?~1$i(Qpf*W#Cna|`G9l8mx-*mq%{H0%)cAtt}FhnoED{kV?d zKEN-FI|F-{1jl-05Hov_lUyc`Lah!nxz^O~p}8JmU+>tN>r(Hwtj__a<@2jwtfz(F zdGQ>~IXYZf*y}mNIYUg9!EAK`;g*Mj3%nz=j<$97?<*vZql7`OJDJm9^x-Og|DMZAU)w-tau-}hj(m}nnpbt z1r>)8pOl`KlViJW^TUc+ObTm8DvX(o5)wC5@R(fKAHLC^1_H1<#YL{%?!3w6WNXxcm zsh4TY?TL|{oS3O5&9+)nO%cxEEwyG<&dEwlZarK(2AUFySCu+G^}o5*9KTVBiMs)J z6)w!gdA-j#FkHEb3;uwd5;JSgxY&V%!}_ZZn!ot?aryZZd0WRCotT-DZq4G}^)6VJLPwIU|K@zWqxbFo z3s17m&UEM@60f>+wd-;QA!mHH-I|=XBrQ3fxx*^0$Y}NMnmT$oQ|S3~3hdVSH2NP$ z{`holviO{FGiOhan;bKNdd3gbYij0nW|&Zx*ICn2;_ZoQ1=M>WnKmt7rY*~wlbgkp zGasQvtX${vrbs&Pe~Ls3@8_h(FG)*R1ynQmNIWm|EJ<6+D#T|d(#m=9OmkkLp4PDp z&N3`zWs{3nSkkg=nd;e}y45#F*qE!%yDmBE9Z<&CC7yE)tHg2)&yw6sUR<(esy0@A zT`n)xHY*fBU3e3hvjmAavRTAxK{W7xqG0VIkCHv8#t6ryH z`oMkz`{|pxMMSDK(QbCj7tS+t(#Iv*X))eK;f$J}BVQ|Sn$5<`A`^`|Psq&4Vq5=B zdFG^L<>V%&&(37ADGb`Sot>#~=#&YLoo1rez#@fhXIXih=ML_kPLr%Ty5cwxqY2+_cloQtvxNtBakf6J}1IFl|t{zV}*09e5PBd&M2 z9k5pXNnFcnov?lm(=xRd*L2?bH*s3NtgOU>crKUKi_=SUQXNHF%gYA{GE>xhg;9!ZJ3V;tvM%?q`Xe>QJJ&d8jxWQq3TRn!F2n~u%PsztU^eojnM^1!IbF=6@E z`-di32Zs&3LpzSQE=kNy&mp{J^d0ktw~TCU`O&%K{-foy;IRKBA8~{J!?^qZ!?=Ml zta#dx!7an@)Ska+_}12Yx>bK^YeGJ!*UZFp#|ba{7o}ToUq^X!_MbN!niguvEbsa zJi2gD`I_fFsX0USG9>6&wUV4?WV?ymoQE{m>4x)`z50Co992Nxl)IE zv!28#JcXp9EBq(23SEs2UqIA4shWsSF!X(C(pI0&9c*d2LuEv+!F}7TWuEv}{ zGITZGe2KB@a(Fdn{WfFSDyJH|_Gg?~)ibI;>xigzsRrkU>3RaVW}vP|F)%*d06B}^kVQgkLvLy;A@ZTdL`(Xccrin>B@6)h1Zb2 z=<-{1L_IH;wYTS1sX10L>v&EBukb$ROy!`5ndVQac~sF)io6W3Fa>!V-3~6;$QZhc ze;FOo_IYrlp;v;Z4ZRM0uZ;Q;UkzSGRDAJf_Ma`fUJY)j(A%mE^x2`et$7}ld5)Bt zJ9YI0~uO2g5ufg#{l_XLNt+Z2kF(Dc9|->ObG>h=0fQUZ@8QG1`L~$HhMoXs zv}GQP+V?=d-=ygZk0NTj@U0|%t5nzdK9Zb3w7#Mv-`e)lIhwr9chs^ zqljUBy}(Bhb&eJo8}C!@!;s zbUqO5G0`HzTqeFhBe{s`i}(hO_WKB0A2o?@rcdFyJuxNV>FIj=)PY_P@SD`|3Wv_r zdA`3Q>k-xFCE!6r=Q}R){VYAd#PNO9*?e!5vgSEi=J{G`9@pFlSvO)7+K_T|g*%ab z=zRM`PAi>d1FZSnJ{)hf1Ncy)-ah7eTIM-iYHrtrWZH`S3JX%$ zu9Y08;5Nh`y%J1ZqOXe`^5USTj2gm}drWp3|k~dSxIB;T5h#?C8Z{a=y{d;DQ2OSKp8QtD(zEzLSlp*3mo{OwAK3M79y5a3``4y%NlQlw%d$Joih@37fv2 zxiRnxw>(ArpvOMPZ<=ppo4rXq_}nXcoy)+3n~d!X+Dnb~0v{+d)(dRARoBh)!_>U7 z9oyK?$f@vMWITE`_~>hT+m?V^-q4q8o+GB_jwQXx_jZX__`zGuCqg&R6I1iWjv^c2 z75<3qL^sdtGSB%^bHBdZ$2>@46c+F2SYzIm!lw~0bo0C|^PDa<*Xyan?El0ld;wXA zu5cTYj&7d2rRH>XsiIGZS7<}Fq1(Zu?{iFXU1Xle<@_i1lRtCLAV%TyA2Wv&-8_#= z&F`u_%I_S)D?EsNgI*0D`&948YQQU>>H1ahtIzfQwhlb;1>2f<^PDa7d@eQ5YXuU< zoGFEc$ar*x>ybEg^IR@9$E*8S%xQ&J_$-o-UIvE#jq?b47^vo6m7$yGV5zxTPh4hQ z;1#}x+(0+al`_wxGS9J6bFT({OWl~ir0^0l7hT~`NGiI*?%z=#^g3|&6@C8<``#j5 zf6(=-KUw5A_4>YTo@b@zWR1K|{VA)k2q{BXSb@BaUI{+=vr!iOra{-Qg2!&?dJPzV zQ`Zx~+FSZ^&2zNW+^q-;?MhCC^N{oC<~dsCd01+G)@J63`Y@+S;Xxz}y&4Q+eww=e z4+Eb@qToxw?+pDaIGy(_6dw!jHuOrcLpz=K0>4F~iMa}{a&?k8^kVSuh{}HzoY`L2 z6TlA)y#@?-)A=ZHi=kJ7VeUF#0^aYT=Zpe3A?n_<46HMBg`+)nJ_`IJqV92OzPi&#?)N0eaOr33eO;w=yjlbFJjTXz&-cS=3HaM_Psq{OwAh` z7DO3hG(?xZlm*WP6HlG5f}KLh0q+k^LR9}84X!eDg)yPLheb>*_&(x~ehh5ekM*wL zdp$|3>!w9`M{WSN^BOhb<(X%t=UaZvwMQ}+?~~)re5L=OjrMox-T(% za^?!#a%b6|na&3$2RwjSA9v$byPg{{}N$~LcUKHK_k3)>d8 zEqdGBZ3)}#+w!-q-Bz-#d|TzV>TO51)sX5qKzCC|?@%FXb zOSW&=Ubek_d*$}9@`&=N^62u|^10;+<*DWN^8E7R^0nn9}|fE7_|4XAZ5@gZ7;@VLMCIdk_Rn+^Es_xJt% z^W(+Iorg1L&YW{*=FH69JClA=uQ(M&aRFj6MOlL<{qyqQWB+kG6lKWBwL_Gb2EK8| z8b|pXXIwpP`mDSeGw=P`%-inHtGw-=d+x2u`{^BdGpp~(n|@E8?~2KJci(&a9p?@n zJSg8Lefr+7uKbC3CMyn;|D$Hzg80IJKjplXpHDik;^z|Q_56I+c{84l51-}uPsFoX zKS#Xf*8k)M@W^7(6yUp2jQ8r9KPiz><#<@Wjgm1~cb-4e&?QqIiF&Ki=d z3P*z$_$y1@ozoyNl%G?d0EP|)OaF82F|UzV@?%K3)ZppCGv%KbW^}GZQ|1uwE0FKT^RZrdk#lEA zL~bM6s+<74G<7k(nX_hAB8z3x1w114LN9vWb7$Uh*S$a_8Ii0QkLpkP=T(%_l<5C2 zgL-)Ibg0^T(vhWX!9U%+Dc^maZhV@ji+5tf-|%E9?TYxZThqi{3sK&17_ozm~T>@Ob^c*H*gD@0B!_Y$5OWaP z<(LF21qk^?OY8j62n;|8zxg9wfO8UscFX!P5z&RFaT4(C)PyW3px17!(k8Qf!8pYD zjxLJz+E~?aU2Kc~E2JoEW#hL+KR)PC){@|Ku{pX65o%o#5xp@fb?@YBuhWB{l6mW* z-^IU1GsnEHn>G1eXgx@4qNypGrHOa)9lH2Z3+#4;eEE4)*HvFBN^L}~rO`kabvix5 zi`USju+GD=g%#-e`bA1WQ8e|jCSPdb?}lV4cx+b5Eb6?#@&}`eS5cy~t|JCd@`?3& zYfU~%aF^4zN3}qQLl4hm!lo~OjxOd=IsN-oOmFq&S8xDXx-K5(EHRJkFKp7y^8D*{ zWKBUSP7@C^qlImhIt_HYFhCL73~F=)wd&z=NGUw8XfQIsN*mHp6Od~1+c|$;ceadn zP}GQ2u)Ji}G_;JO0b&uQcOltJSqMk*tgyq~cox{<5O8UDGoK~{+4pAPLx;NR&8RjD z#l#Rje06?N^!Ae#g$#3nRLqzy(LLP|Snzey+^x4HG|K&tdV|OAgs14@peDL>A=Y3N zi?*W_jC~T!!3PU74NBF;+vuOsxB3(BDB))A-cfu?0$Lp9U@>O)bB2h@k677wToMJ*msABtK$pgt6}ctCw9YVqi( z!Jkq?ut`A+{J~~==tieQtvxdqi_uWxPIk8~x^S5Fj{(I_OC2wv^@F0G1{^*7By}rw z)p(Eh!tSbk^x&FE)v)N%;SR+cdcY%xP#!;{2Z!}aOBpdxA6SA3n)SZ~#r4o%qPJH( zjhd3Zyd>n;MKAvb6L+L) zNSO#x?e@8`07EZSY+#>o#up> z?^G}tI6M2%_>`C0`~BfSqT7R$zPi0%aJ9hb)$Q#xV5D|?(9PWKkqr_Bf)Y_fp@;mfio36EWEC(}mQ*L&I2lZ%5CaPB`0Q!|&#K zRf;@yka+d$#2_Ju2;*SZ>@zf@+d2C*T@2L1u6jM#M8mYtTo9b$Rut>qpE%;iN%M^u zsE12kHd480DlcrF6b?ND%t_(;rFdv-U|otjvIa(jL7?KWW)5=*5B_H9VXvF}pk>v7 zD$idqGPAx7LyYVIuusGzu=&(@TUxw5Exs!)PV;hV`3{WTJ~I%-!|rGCE+OmdNG-3f z3arja@-q};U-3NZ1M1_E_0)djW!WQYRfxM6y_EbVKkJ=ud|H5x74KD5rO6)EqbIpA zn`Z7wT3B;)FH$EqVQ?*2zXrofj6-M?kH_Q&U?MCa1?b^^)2^lwq#S(-V{@J!9{yuY zO0nU$QL;3syy!5jQgk7+b7{^Z{8&Y0{D4<14(GMC5%o5?`6H82w;hQWtW4q+zm zfkGoYYysm|%!8r@;IQb%^@;J#ORkD}HPXZVP6j97w@C6k{7E7uSw(F=gf7rTcWk(l z%5de)Nrrz$1$r=ib2>M@8NM_fC5_>t64X0)SV?QOhXx%q?xy?-G{So;i~vEH1xMnR zB%c}E_v6Vu+P8*0i45(_16o2Mvr-gNM+JJc??FoGLm?GzsgddFI4KRRaHH^l(?Yhd z3h1olD&hbtWSb$Y5Lpx{&_$`60x-f-4+X{|kPEA8V$TZRL2URCF3e+DUi7IOlbU>% zaC)?~fl~Ttscm|Jbd;2adZ46=gg0dO590rUEC09rJCgig&V_CMH{O)w|9z}|dh-7! zrS!pnTvO}PaZ(xpoYWSu{HMS*f9_GxrODa{kCtZYch|}_hux1_pch!#)wGz9GkDc) zx}3fJtq~eBR8Qan4WrsnsAQ@-YnOYfu09meo1$ZN^))=ZFKO|)D$pFxx5smTEK(X6 zrzq9?Xd?1*Tci7@k_1pyxojwzgmKX0SI0;6!d>b}uQD}0AE>L`p&TbB0!Pip>aPfE z<`_K&k1wJJ50ckAbHpU!?beOv@A(VS!y*WFkGyX{Z1|omECc;;OTY6utqjPKhu8b* zXNVZDPl>A-D#CYz>~&OC(4QjXprgv8 zi5<~RpFt+8sB;&r*Tcbf403)`SpjD(qP05mP%@rH(_|l6V=WGpuSEx0oqo@{Gvlz3LqORUib)!#g@|V<;gFfqu@H}j|#V*30n{r<%u%05U zs)AtiU?dv{b7p@BUJrTq<>7vRfGkCGsk&;|z|yQ?Xm0a*%tMq3H*Of3+Ke%dTx3{7 z&Loi&nS^ELK*}3RdC*h@RbG#00MC*XoSp^GVv7cQE1}LG7+SW3r(zV+UqttRAY}r z!^7E*l(T4?O>9!jHrGZp)z_}6Yd;(`%T>6&v!8Q&)1H2sx;jXED8W6n{Ny!`mi?w? zE+X34fHAWOgSJ;Rv(yD{gKqK;%u=iaRs{#ja1MnB$*_mQ*)lwg!nA1u{9H|#wB4eK zAdwF4CdHgZyQJA|YXWFbljYLJE>eJ^mNjR$Y38LPsQ6%6JdeUdWH_G%BmF-QlHecG zu1-&J(fUjTMkLcwek{6uzQe`?AJA~JP ztat$%sam>SuWZ)LdfJ`U#DV}@t{Ljtye;M7AZG-oOgUP8fxl88&9!J|MFn6Sph#== z=F$A38RJ5+IN1ua+<<24QtXK#j_onIKT$4B^8K+vA>P%+0qUJYHPK$UXa!QmW_|l! zEw(*)h%`NG1U4+hn^^A1)o*;?>{jc^C&&h|jTScKa8xsvmyNv7X!P7<{BPH#dn^^zqt{HjJa3=#FkhhTO7LGTz@Cr9BQ1=sT^d?;sjA@;`BpadMEydw?_S~(a5 zT_-x*)KyJs4Z#@9sK|#)$D5?~-r%k4%f&)k^guvzHVYn7ypT!%F+0TO&ifIAh-RIj z^HwW{JCV~`s5&=y-in;1m}IBm83D#9-B?#d<3QErVj+#Ng$up-Tjh;yTni?ZQJXtQ z1GNLF-xup?fKzDn#K0^Tv=Y8pU&!UoqG8(ECf5Ye5o#txT?}j0Yj;!)LmN&_Z$n@D z3q7eMHhgXU+?5YkvMN89H?hjaB3b~#NYI@vn2C6I66)w>C_Cbcx+7Il2hfHdii#p3 zuBgJKqVkf8ntdouQP$feh@^I`R>V_P@)+WfoA-aAlG}T!e ztZd=>V$6=h0nS~W*CS~utop`YBJ>O1dM@Ox<*Jfco3!d(oo4~H15^r(j~&4e$#~hg z#ey$nFQC89cceLs%`CZ$Vo4i6!leNc!a=NzjPAL!JmjcgTn^Q^X$0!*7aa}T)`bxl z!ij||NeJPE3jDT&AR1)X-l5hkN81C9(&4N9H6lKVss#_QagjhCqDTO3f&1EolNIjn z>kG3xe@YokA(v?shyWvBHyVq;h*EWpFu0)xclU|5V0@$6I)6aO9Y8)G3@w-L_8o28Qa$y+LSDsE(W)gt9|*R8d_$-SYsgz`l50q8H^OMeB5GIkzWXSDbM%D;XzX%IYBk6SqG{b|{3m9Hm|C-L zJdm&pyn={@f0*?T>_TCeH-Kold3*jG?G<_fpxnGGe}?v|Os&eFR*qw;DP?0~>PSjb z_8H{EWC?@(jH+QJQPnsTbPJ=#$4+O|ItgJ)8-k}g(uY5*8%W&c#f`Mbr`Bu-4Ry7_ zuH|1HLXfmD;?YKgG!1o?L8?H)Xaq?Y2o@no%0Tb}1W6kRjzy5vf#5h{ketsVvqA&% zvfHqd&L`(mZ19b3BptzETeC518>9vl!Sl=A491F_iDm_f|* zqFHofm4TcJ;68%wMg*^pHBh5EbG5%Z&<0Y#k?uq(byc^q!Q~U*pnf7=rB3xZo+Em` z5GQ!mRjskMi?DK1)Vl2&l=7rlFn`Ov2spRKcH5G})_oZcdO-U(N!q?}nJbx(%n*ww z`cM)j#Im#Q16_4hgS2TzqIQ#noA$WN#MbO~BE1uh1+&&=jLm4Vh@{yRbt_DY@(Vd% z5H-=Hu8n4!1}QMCIZT6OY**?^MH{S;v~0Xs54?YR69$rO=Gy%U5u!?);@=2U@*U1W z-KHRQgSSCVA#RP&Y$Q%vnGFX-YR%0={N^O_UNXc{#>L5O(Pf*dvBBN+Z?{jpk-ZnAwW)$P9UBp2mi<1>cd^}F_1%sOxhb4^wT9gy8o89A!35Pi;w zCL+EN8zLL8hBjq4nGLkGlR_&EyRw{3Cfhd)gy>kmVbZKun1@Xj^+T)K?a`fI$6``0 zq_t|v2GtvDOK3IUQIOEbsUiB5WrGmoDqqBds3R3kcfs)GkQ z3upb*&C(23PI=7ue{Zl|*n~tUAiix`@&ME#2cWaQN;TNm8J7i325SqP43>(M$bZWi zf^zy0Ib~T7q@raOIpa+xymSGb_n)33XUwxDMH04XpfN}^NoS_1!@YfPyESOLlzpgy z&G(K&7$@ZXQ&rTfhHYqdnhJPd2@`2k2~N}H^uzudl3qYkFA@uggv85W+}YjY?dUp4AUqMHTN~M&AZl<7LxM-3kxYbbg?a^N z(p*8Aq@vh`R0-qup&}-;D-|uXiWqMN;iaqSkuMXi=U%~>^MNTEXf_anhS6vNI=N3e z2wJd%Tm((D?SJRA9mbw1>e`Nhvpi+Bk+pD3m6xaDk6N}pE{L-fI6HC}u09iPF~)ym zWB+`0+(320Cb8YvG(`(^Kc!CCu7z_vo&AyDcFrbaj~9CbO?$FTHV}Kplf&8GVk`-4 zdpI#ozgv;ltA)>Z^=Tr%CVVMibS#p;bBD<-7R@1!^NfPsK};w-|3XG#*QK_BN>jLj ziP)w<^9a$QdBiq>1)s+|ruEek9tH`4dZn#!>XmVo=SYqyrw^4=mi6&iW?Z6WRypIn zN_goi|J4^gRnC|Vz}&|@u9X`~YYgilB<|;FlT>akz)aGM+N314Ufl=&z`4n6xB*kq z$ERkuPBqzmwNB?|9KRRc4;6+d#;m_%6o$&l^`-vt!H-D_eM|m?&tmYF*guAA5sQeo zVU-c6MjBM~OpparL3W{OQD%jYLoxDc4~lK+6x-3-Bt>{_RtZr|*T5+-3D_M8o}8Yz zXCorEJ(CN9{iMy&0O(oY{78XUMk6I@!FJQEJxEQ_O~*mjja-e%A)}f4AlKn+mR7Be ziNKcGrfrWYyP4djgl*$(2t=qVdz||vYB`pOU+jzN!_sXsy+_+AepaT?oM@*AnL;y_opQfS zp&n|d{7j~hXzi41IEDRF=}S@`jREXSKD?KNL@qyB?b)(4@oASnS*8$z^ewG^GDY&m zZr*<`VBRHPoU&V{NWM5_n@o{>amuTl(idM}S;QA15?_mCX~`Fto-0!%U&M=bw@jg2 z+u6HOrbxaR$1hVPU!3w|nIie(l(RUcFTQ@ld=Vn?bzls)T=K=GKanYtFZLO+^mno7j zPHB)Sk}pmP$`r{Lr`#)3s3JLeSvPY^Uwl3K3CW9!5?|-Z(o$YbZzNJ^j3UPD*4ZPL z^&1S$ltmn-WzDyLJMApuIW4Qg&LZaRN^r_z6(iZkgdZ0lFlEoxundgB6p8t& z^XTBPWo-=J4Gqjc{&y@!aqOZ}924DHNEy|QuyekrWq6Fs*enF;Ccn zl#5ntJ&qEK{V0IpqPFLi)1lOy?BVxh+Vm3SKVeGszgM zQ_eTeHn|ox1#fLIf}CrtQA{OXgw%Z8B3(bACdG?TJC0lA)gDEtp~o%qaE~G+iQ^Wz zu166P)p3jDbCJF}&JEDM-BK8zlo&QJ*fW*Wb9@tsb@cYRi4oE?lG`56Naqevq26UL zwAdq{B1yLvXWq7pB#VY!Lp2n93I$0iof{A!QFp$A012>jB?2Ut&OakSjqUtRq6ekA zzdZFV{4f@im7^ily{LAv30?^HD&ftbdY`NqC)dt)a#YA)vrfXcE2uIyFV97jpY^_Vq!>%i>f0Jt}se>zOvZPvwJvM8QEi)$FLvqi4KOs33V=H6<9;evU9>v%a zzfG|p^eD#G_-%@Hk4|NhE%MtGYwA&qt@7Ixdzg#$Wtr=C_pnU1$U=LQ1iP7oWKWlR zk$vAP20`6kRA4QLty|J6+*oqYtkaMyH(=l)%3W6JpW52YZpM022nDB|BC2{V-O_+ z`LBq!p3`^p$$b7RqBD;{l#J`YBKjQ=?R)UJ>Vu4fha5R*QQl@-1V$0+=A=M*98wsu z_F%0YANJ6<$u8IgvYSv+nko&IiScj$yY^;71J6}*^Xb8}x$Dt*lEWW##5Ai-i0 zBgPkqH}Vk59#qeaQkg-#^~^Y1W{}L* zC9UNsVh;}G@+cxcM(qDyqKWPvO`IdECvo@8xK(CQ6MJS%kQvm(o*Cbl8Pvp{8N*}- zHL+*LA>PdCtBFtiGqs7bq}3+(e|o7iuCgJrp_KovW-;si?<1t=H0|bl)dR?=U^f5OfoOI-9?9%#)cegH1RI! zVEI6#?oOijBqdko(Mib1mXh2mus6S~kMqHiy$O1GukEF^5P`iVq8TlTV@}aRKVss= z%9DhFy*Mp=kG&s93|fzX>{NpfGO{SkdJqY{$lsVGpJ~T$9I`FEr5fM0S8GO{)|*PW zG)rCG6;+Q-tBZtVZz$oF9b0k`Br%TzMeHTg{uLU9Ek$ZV^m6pOcnKRF9zct=1deDt z9lDKYi8shUpz)n_F*KzxZ_Fh$PFOU|L_eq2RgUW2pr}tOk_CAQoeU*6!g|*Bjcr8F zffP_6`WkbB{i(WF}$swvuU?s7**xBk|&tw`P9ZEO>s-mRN@^VrK*=lyJS;;{G_# zwDK5Nj`&s%(UziiM(klx*!Z%jy_6(nSpPbMv1u4PHa1~cD{?3Th-=+~;7qd$er<5Nd_ z9*csej$<=G7}oC@TP_W;t|UD`&yxQ3=yq#RlTFv&p{$npBy@Un;8^&qm@wC&3>NpOy3RNNTNpGInf!SUrE2_$2(}(PstKGJf9*TJsM$moQ5f1kT*;w<)$q zp{9uS6LuNcU^Rp&>Q9+wL-WPl(@ukW+p1Z1gW%a(L0CSMd%Zbsl0j)VhVa;#WxQHEgrJIO)Ivj@_O1n)ZrL1$#aqZL1I*u zYK_|gDb>1GqLG^VK%*?{2NI2R(gzxMMYlvF1@wW&op1N4%o|C&4>ZcM)=D%IZ69dd z!5SE?Z*jM5qfTWnLTQk@_W%i%`#~Rcd2B0Ty7o+Ci#0LTh$ej8;>oSQim1ob)>G+fx$3IQtlS=xePnmK2&Xc(bQf+jR%MBx4*joars@`r%a?(|hHe@Z9#ib5xR+vg0 zmvU^}J0)&qEMmvU^}Pli)-`jNolgRn80cJx0O{H2$m*PrFja!E zH9NVfN6~4?tvrg}kletd=%q4Bl~c5Yqr9P#frTv_nFT^TL6##%DC(3^$&SnVTJDrd zj$GEqGD?j%I$YLH86^==v{gn)5ENY_qmm<+^(T(@#nFXvj$BqimXjR0tY65esPT?Te$6;v9K+U%)mWqr+oO zl4K-DdDg`;DmlutejuZgqdY55MkPmi)=(Lh9OYSAGAcRBv;KqCO5dhj(`w5oFaA2P z)LNd^$mMLa_FB)&sN~3NJu9P#S%Bd>L~jFODBQ5&pNWt2FgXts_Vh@zj#C~-v5f5|9uMA2;=?Te!+ zk|RR=gDgiJQFJk)f#-;!qt+)L{6y+G;^wIJ9*2pQqt-SK6CX#d^&BQ9j#{fZOdK4w zmLq(uvGZRWAd`-x7%*wON%c;4syF z$ZFv*)qTjKS2P08QQe2E7dT9HAF_Un@UiNC>va;uA?qu2d|CG)>mDvob?>)+%3-Q| zzjZB#sqX#Or5vWZ_gfcpnCjkdozG#ad%yKvgpXDC4P5tr>%K(Y`>n5r#M}La^=}SS z-CtPma+vD=!rIDVs{0G8nZs1~7uG5cQ{7)!e@6INb-%x!+Wm!f5Pe&=`wQznE>G?L z!1_6dsqPP~TR2R0e_&n3VXFHBYXXO;AxcC{gzZ*1^H? zcCWKO=P=d1&f3Fas(YQalfzW^I%^Y$sqS^wYaFJ!*I9o<_*iv++)V9W$77mo_d4q# zE>G>AYt7^^)jijm%3-Q|uJscRQ{8i|%Q;MS&$UW9Om)w-euVI`>Yk3e)wNv%tXhmL zvhul>g9z}7$YFhxLt(1iVOboe${p7G9Hz=0)^-k4Y9ayRDl!Om(}hD>+PcyR9;Wk5%^^uG?)r zj-3hFZnrg*%TwJRs~?A{ZjW_f5Xpe*_E?{BnCkXeyE#mCd#txOOm%y#jR+sB?!)V- z-5%?dMBN_iaV}4F=UFC)sqQ@MK@L;hdDbsDOm*j3cW{{M&a-adFx8!BO+xrsbqBca zJnLC(9>{j*S*LJ$s@rP~<}lUmwHzF#y1mvn14#x{x7V^bOm%y$_c=^;d#&vVAFFN` z*X^~=NYoAA0%d&erpm3mWtb|rZj)iE+`0zgW7Qpc4X^WM zZ?O1BZ|>zZlUZj_`S=mz`*`Ckeq>|0h+6t_&BtKhKCZF;Oh-+}J6`gkZyo=5>2zT6 zFJ`Pdya11*o8&v?YVAYxPJ9+l&g=sz`^a+btIP@=d8Qv(l9ZxPBcG2fNq+Go%kx1i zg?7zdA17PPXN^OFK+CfQuOxo2*5v0T5slV0#$xn~wND2~q}hlUZlK?w7`vU$Hm7Cn zKu#)s0k#to{Tnj-6c}+y6`zPs8Al@L$8I#TuvF)@TeJ&GuX{3yHSm%YE80IzJyzxJbj6$j(b@7&J>#U`)#5wA&SY-*8v3Or4y%`}MG5>; zWoq_O?1Z1U7%}^(aW3jh%F}urm8C?#w}(JM${FUewLueJquQ?um{NzyHt`g3#_^4Wr^ zr`w3u43LRe#6SAtcgd(03mih=*5$zHv(ZF~ke??h#m}^RnHg7a0>wJ&%Fx&kVE1+d zsluPVm!y@daX!_KpEH+$+VQ1499U~`42NI+WU6>7Dppquk|F)#(LOgW+>rdaaCn$w z%%-U(zaufGO>V;Tw*~sim$K;fhwl^V_z$jl^J6OZ?5Ae8O*I9ZO-`n|_eyOMf@~y( zuv3wEond1Vfh-`dBuE?ESlO+jJ^Eu9iS@iQgG9s0vevY3BhF7*6308XZvobt0utvB zGSzx=vQ{!cX{>uz)BEx~IXGmY6q7Am(}&5T7oVNX6Wb&OSZ_27W+}GG?Wt%#J3{-6 zQ8yy?Ue?oDq!Kn|yK~%_V1rETe~?dR`^_py@NPtU{4h|3)!-C{Ho!!ieBDmIU=zIt zIdL=ZWt!AR5C!YMCPDP>4L*Dv9hC-MMXbv52bq)kZK2@*f|lRwI^-*X;8we-;;Ncp6JZm`p9 zzS#*SB?kgKJGD>zJ&8aThK>M%a$h*~_k@`}4bt{+!ykVMq7G-O>P}DskEAoOV^=C) zMlWo$`r{}(uE0KpzlP;@ygu_Nu-1$P3--%AkZQ?t`bvg6Z$J94c#BNpjUQnilpo{} zQML93WR&Bp$y3Vl>7Hrj0-xR}7x)y1E?&tGfE0Z&cm~He7JrW;NT=EQm z9XJGxn{QlS|Md59-vQu+a~0)$zzu*PU^(DlfZKkcD6asbfI%>m^8hyh48ZRIn*qb| z6OID<83*8Az+ylXzyh3rU%cE6cnZ)6*aH}ZU;A7Ds06$Spx--PjNeDy0;mQ21@Jat z)(;hB9l!+v`~YwzU=Cm@U=!d2z~_KVevEnn4*}W%XF{TP03HB54A>1&ixp)gU;^MC zz>|QR@5MC`ct!v_03QMN0o)gYKfnY4;ardB&jG&#JOWq>*aG+$pbJ1aStW`x4DemR zSitRo`v4)p(*VMG9?#bR?SR98A>$M!4{$!f2Oykl@tg*D4DbiQ8o(yNKLMWu2uHa{ zQAPtU2HXIs0xSgl8PEi<0C!)Ex&coEo(H@JXb0>8d<`Hv11?dN3jucnmIB@c^!F-C zFDX|>9ASA9{`^LD4#(6 zxuV10sMpjdp~c z(Eq4OpbeM{_zU1Yz`)BD<;Q@B0c!wJK<*Xj+kiQMWq@6PlPb_Z08;>u12zF-fD5iv zl<9y?fEb|oD)eK(UjUx~&X}wyHv;|u=l~438ny$t2JlP3bAU~NLx3}{K{_A=Xasx? zIOAGHxf<{yU=QGg>tJVqnSkE{b^x-jSCs1jb%3V;UWrbOB<3AvZ%ufC+$W0Jj5X z0pR>P1MUON13UtF8t@0e-vFI}{Q&2!ut~sa zfbRju04@Ri3@{5Y7a#zO0nY(m1T+FZ1bhKF1Q;*{?FEbk6asz>xD{|GU^c)2ECl=t z@JGN(zV9W%J z1^56JfSUl*09AkhU;*GIKm@P_unX`JpbKyq;KrC$MB|tJFZKIpS-3icKKUIUKIG5z z3Fd9F3VeZ^->~@o-Q-*hK-Pc$WM}1ayn!4Id>;NIB+D zX!i_xxta24p9OhJraam=LSA$k$t> zEKe#tv%XJ9sUG_N;mo+co$;)m`u_Tlsrsf5ou?@L6V8k)Jf3N5d~3!`TjhVxNY>W* zGZ`~&r5hPDZLR-2W2UY4D>*Z+hB&vj-p6Llv=u)xW2UY7{uwiE)qnPfRMp4pv~~ZF zjG4Cb8#89w+W&28W~%n5xb6NfG<;IB*r7Hwi)K^H5w4)ArQTN~v=QZEd$E>VUa5`M zE3Yg#En-9l_ypd)^^XH~X|XF;G%xv8Mvxh; z@pWD(Q329K2T&xmyf`h;K}vxvoA&+SRDN{uQHJD|wQX$Ac_t7p_PB zm2FzF>tRhTZRd|5UqxRlo`;V|r`5J)i_nIZi;cya2L#8R7717MGp`(5Dw~PVbT^Yo zh$=qUooGAJjJMsN(smNL(C{g8$kPpaMJPO)_;*TqUXa2+2|#kXkvR3K<2RuxW#G`} z)TYb)fgbT!US8?Hz~)i!$zu_Cge-_h$iggB$Fs&#YJe={2cdh)BEc_s9g8}=Dg63+ z@{3D5NU|yXUR4B!A)1YpV+%Y}CAKX}`LI1gM${OXCB9P$-iy#2le7q#Zz8dgd{PJ> zLq0A^%V(Rpq~}eeHx~5pb?XG9lHPy8Ki}7oN3$lruNfZveN3F{brC&08Fysh{s`TT zo*5hdhu_fnx}HAR8NbNDmVqv=bmO*)YcNgVJIXq~X`1Xigpx8|hx=_KEPRUBbl@|* zMw2@>{45aOx86}Tcx|qnwxS;_ARPWoYo;6bP?v+Kv}zs{!1p~hs(u0`nYd)4*Y{MB zd4rva3UOQC%BAvRpE&=3)dP(pL@!ol#BU9q}=%*lnqeX(dsydTGs+`YU*p3 zJM>`7G$0^+Ek29?+5{(EIjAFaF?}TK0O9nb&_Tj*@z;;|>sbg9djITpx}uPR1NgN# ze3{f28}3{J*@#`S;aL=TO5cBou2`JjtZaI^L`J7?=diO~U$gSDo!^aSpF33FzgKTM zcq-XuQ}hxYS-8N%TlltaPH;J{bp;-xPs)Nh^Q5XlwBfWh`phztkGu&7R}|iIb5C<* zd&H-@@5;}m?{(+d-|OxvR=R;MF4hrds#xJ;#KrnVidf5^Oo&x?9!eGK?7IT5&4DnL zsv+Pn{0Ip)8f6|IxeH;s$*-u8lkq|=!cEcK&;Yu_&=bOk=vLuVu%U@g{JJ4@MHa3U zxqxzVLlf~qV8-Z27#C3X!~vlza1rB8^i}M;^1bwFY(3NWb@@~Fq|n^BNe!Gtx9xrD zw{MNA9_eqTo`o)k6E^zb34PNYLgp`RLw7a;jD85ywi__^GfwJ|m=)%oHl2Y`pzrxgo zK@|%lgujzub)tb-@;6yd`hb7crQw`6h^z9#rg~?VXj9i7P|ZQkQkNaP)a5L7$HQ)C zX-+(x<1F>W!yaepuy}Zwvotpz&UKcKh=)hu+L$~)Zs@3f68F7S<*FkUCoUbkDpAp4 zKivS>X)>qy3^%dOgCX6rS-Gt?Hs@r0`vDynMW$=TPC!OdQ}OW^rjAbpUwfT)wY=wa zdahq|m5V(X^~*~(%szvLWaEfuCI+rz-TVa>Kii^nA9pA%t{*GbiDaegND25iS1;MG z){UYhv6a4*3>(|1>LuG|kJPFEwc<p{w(`9nGT&~qF*A@M!!J%|I%f{?0$B%Z+SsuUmhi*QAMsJP&79_P6Y|3(j zzt6W>U4s!%UhR`xE;RdUpEf;A!S0c>3+YZD|9tdta|~@;duan(r(SkF4fKhz=B8V3 zSsxqzPntBMf52M{TcR{W+0#to!T0}!2S4~*Tst;AhT%AoBLh5NlD_Qw1hlKQYqV=M z9;bYvpa99TF`t1?To$wHV7WORE0=eCq3NDiW60859d10tTT@$u7`PM~a{UF-Hy@?I z1GyxjZ5rQy7hQ)mO}wFpuREbUeAdTvaB4u`uf}aPHD|2&DZU?6iZkY`Tpa%^;+U_q zLZyfG;DI&}p856~7=cn&6RR#+i%y4Lx6pM8mi71)BW}XO`>>!a-bj;?9n@ zqty^mu*F6QP@~Zd!lWyEDLMK-iyScBw@TI33(m~DFpH&k=9vhDT*aK2A5Z*lN@77g z@tl;z(ecD{5x_X%0SVY;eF5rS0y*in@|G&7Hw*Qu1^r%p7kUl%3oS)#XdUIyB|FF& z{Cni6Ll>^#Sie7VtS%z+{v`9-W!`d`*CF%%BJ;Ws^E;1XeA2_~0+fY`Hh%l)#DKhg z^a}g-(TPjt?W0%Nw~tPY$lFJ+ux}ro*dcEpy`qbC_ZO=OOup&@RbA}I_riPJzr~_u zGuRp(QI89O*{s8lkd64lrH<$TB*H^cK*Mi*?x^0Xhgb6_`q_-?F@D&e{n{G+=Ry(> z7v*6kx|tGWS>5=|QN4ro2|e0chsx)HdR(DX&aoBxQv~7)y*-{dH6?LcJn`o#iFd^l z?@39V5l@_%l2{c_ybl4WK3A$fqC)QTegUi2R8LKQ$L%y1OxFEj8J4OZng!u{)S;Jp zaMf#`I3dE-%J_XMic+OBgA=d2Bhoyo8^`wz-zqPwyI@fJf zht8t}b?C!OMC7AgK04&13lG0@C)>-T0ZL1;m(l=Os0R!6sCp@?UW)h%#8)65 zK|F$ZJL2t#cOc$@co*Wfy~vSGjMx46{eNhgi>+h4j-WRg^dVSo-g&*xymN})I{tPV z3tGqDB_CDvK+X)noYwKb1TKC7LVV)iQJ=ZI22WpTd59m43uG*eM?EHC5<`p20Z&K< zeQw8G8Vz;hfcKXlpc@vv1G5swQsAjWsrP*k3AoSKgS^okgk}l`pC?m`XwbY!M|*I` zG0`S392{X}lxa+7RY1>>=BG5xp^0xMiTBs_M`63n3Ixk5cTN(U$}3yx9t(fC;-GHq zIqc8gjyqJc{Nbx&TF8}+59pv)!uE%~hjpXluudPO9*-LVTv??d-zjk>2t}`a8^bi( z*@FyWWhLq8l~|_rA_G@~+m|5IoJxjBxcuQ#h?IH3bv;~Hvy19V;|SO6rjhEO!V!_` zpNSN%CG16NKrd2*(nw)8wWU*v_ls?=ax#z_*o)MVG*W{yafRut7gxAfIIV#vq>;+W zL~2+DQiFSuIw_6RkW8df z)>tt12aL$-f}Df2BsU@yxh^sW(==upc*UlkHzuMEH9C-?X*DfFb#Dloqz8AY5Wi-VIH~vymB4HpWT&CmWHp_Dm#%xJ9%C#E1 zPQrpKwdrf#yI|GZ^Zs?(czwTxUAYsIyK>W;y``#B*k+2Q zs2#UY8NZ{aK`k?lW%!F`K92{rdIg@zRr9UdE!xf6O}ECk>2wiPfPniWX+M{CcTEk} zt>N@8=%$jUmIo*RU~36SJR(>ilPeI#iky|Gn?bT|JjgtCVS0dryCW+IH5^pDv6$Zs zbRf@fdZ*yR*IdfzrxbB|RHjcO%sixsHXu`v0`d&&>Dj5PdZD?E?pT$(AF=~IkD_=% zZ}0zx`<>WM`o(?Jj(yFJszGZ#C=mUsni5m@)pZcWPHbEp@%`s;Kd~)C`M>ol-<--E zI7rlAm{uOEXC*qbS9#ulD92?_*fhF2-*XM^8YOoiXmyrHq7qj0p?MBvOiXXBy&4Jl z4{5*3dEsa222R9CG3DV(Y|p-0M1{h&)97KoIu;M@RT3E@UPSyQ|3pZq@)vD zs`9B5TU%=9AmnJRrH+Mn4^X5wgdp(i<%6y4)uo7(n|~q0L)HrU;EOEHSKGjk`D#1< zw$}awf2n8A9`0}I$ifSxG@fpbt_Z?qAL&)Zc$e5#>{J_?%*(uDd`|IrH=iJx<9%ZM zu;THa(;}L4gE=09l^mVR-l@MSBoOQL&oOP2gG;4~_{JR%Pt+hVBK$hV! z?WRV3k71kSft&y|O?&XupoOKpIqA!vf$%bfHPHeB^$4j?M5eaZK8DayjEz~K_XNWD zm1?>9ce*|m54vCwk9PUkB_ADl5ED^6^~%X!z48H^;oVr&I^Inci!O7PuRJ{LRWbw! zEWezbUlMuEoc( z^aF>K^B5r6<@Fx=nAOUYW<0WiC zonl{;tLmJR_9}PD$@jTLbLZ)pAn_rq4qO>OI1ih|S|QfkIrvng*ZJliXLE`1HI%Mb zHsqmfd8I+Wn1FYNG#)v8^j?Cpdarz7+N zhejgwB8T!2BKrtwX9U*$6#>?eHY)mj4T(^(hj_I1(9Q`#TZTX+mcpM22G)h zpQWVhV@NUfj5HhQW@wPF1<`%GPTd&TofTLIs$4}DzE({aFjn>R)o!ZpudyaTx|x}QDjG;h$R`Cu4?^nEt{ejqp-6SI#(JTU#xP!|^N2B3>P=ix zRU+tOOS>xA60xE--~(EuaeUuS5a*={<~SLKL`Fhf*(@cEyz~lewx&ZWt*rULJZVkjj22BW}fdpzCj8&aW2X?D-@LJ``xSq}vcDm!(I0PI33Tw%Gz)5RFAJv8){5TC2hSgth z+C5lz3i2s166VnZllvcWA=ax1;}@JOsm7|osJZ}DMBB0f)mh>VWA^~@7Vg0Ua?_q1 z-Pzz_0u8zJ2sV>nhyj`WLfxD^ExPz_G@+3_3AN@Mq6>OQ!98xrS~^R`-vG6Ry5Y{w zv+ZWCJ&Ke&+fFas?kj12a0rkheA%yB`c`MZwNMbVdYHci#G-n6DWz35(C}~}TmaUh zTKZ*SmtcX1b5@jyPtgifMDZ6d(&+f{77a8#t&dHg%#%#N=NR%WO#lU@74#TNAk1#G z@33PQWoj z<)b8qH#=~ja9PR2B$+bv;Z&KFne(U*YocZz58A~wkJ6KT85Aas0mB|pfW(?K?4->PKDw&HnKOD0WS8qZ_H|4vv zVjFWF4LNXzasRQ&%^9D@tj#EmGygZ4O(?-9f+R`2brekMh%=!hlaTzCCJ*@-{Xs_RD58gl(M(5LXCzGLgP5{f+NPPN zoT8ebD_NguvaYbK1%F1HTMN8A79lKVIYq6RhNfz@L7qfwZls6^js^x#jv!coFdj4| z;z9hNXg9b^tyr&Yvg=N(cs-;@BPKr9@7$=>m~x2Lj0lG4sH-zu3r6G+9UTY(OM$Da z7LlN}!nUK$WuYJ`VaJ}Mo3s!ouV)4Q4Nb6R-x9Qdfvt911(17E3G`(fsR2>L0x7OZ z$R8z0{>bk`{xrHlv$Bua@~1x1LrIA~vdHcusdD#=AgxL0I(VlZ>wp#Ferk4iD>VEl z6Yr~FpX421@n2~!sOlFz{X`o645DOoyVbfGgAm7C4?6kDUb=O9EDqb}-~(TD?Q{{n zL4mg>Yihbyz*?NI$2tPfk#>(%Pq5vZ*Jwd$t|TRzD@ljuN@|YJTuCbQ6~Dx@JSGLb zvc+6U_4XK|ZD-+Sm<9N1BumD3xTU868f15(rjZ`S2KV&R17;&h)1E=_&Pm*V8h;SP zsBV1q4u6r8KFhpaHYSpoDb~7haNCm?OvGF94ghv9X|*^t#y^+*A(s1km^mpM^O14! zQBAzAS6+)(cOUY>xq(}AxwmzK>nO?E^;)s(5fv;xgf~y0Al?uqzOF4Y9~w;@xt?H* zx6Nzi8gIUNdA|Hn;>=N4lKV(xE2*JX!{qN1=^_CUE#cd}XMN}^{!zScO>KDHXWobV zw6}l*Sv5@M@bT*=5nPKUBGqkO3;n@4Xrzf!T_tBDv7E*nFTQiGMZOrI4Qf?uXwwd} zXaz|guhDFazU6Qz-kNs49+%NbO~D5r4uX9O`cz-H58IG)(D?tYcbjYwDmwO%l^CyVOBf$^ z4Uh{QT$JmX<PZ_%UMbXkt@%iaghSFs#AkUV@L2a zvlH-3ad{>9=de7N(|-YALQha(>hKwEvG=TD&xl+L2+b9qJ8!X=N=qG@Inb#a*nLWvhGK~YU$ znEnT5lHi`$NYdH{b?sgp7;lb-FQ7)F1zPNNl0xkW4BS43m9)!QJhR3kfP-~7rpjydM`P%R1=G!{% zd99m346UQ-d|8%S^BMFMXrvu3rFyX7l`~F_@Sda-Qi#v`hOCLEfS~vwT6MTUHr>yJ-U{vtp zofF1US9_x4@FJqI0UM8-p&S*9;o{}ATQ>W&G*wo;1fPYo?)%g>O%(X`!9<1SVfJf8 zBYDO!)lrS)L8sKLuExtAk~jHnU#k99?qFXS8?vfj z1a;N#^2ScZYw38MVrr){rJJ5ZEo!=6Fgc#l$(^%g9p%K&b#+|E%37` zN2|~AS6(q%GiO%-#sTmPl-YS@#TP7;J{?lvAm-F*<`vj%W{H6}bd5avctR0+p3h+6XSPw`j@F9UDDt zpvZLtj6L1)_P*Oj{ct6%Q|PytTcbD3L07<=`|7G|bE{6m8}*mxgk1x-TQxgWUz=zx zY%(Y0ApBS)G$A*zlIjfx*nv_WbmJXvbpK9I&k5Pt+GFRzc4@FKDlazt=RaAY`4ZX(8Q?vn!-6_MRPIxHzx1)Nf$W2v&Vy3(vjI- zCN}kC5NkTdITm~P^;;8uS%uM6A?{N*hXcIVnk5YCGxi(hP;jk5U8kyCh|zdz`~fl) zFF*tjT9x2IqQ)jQzBB?s6-%nesLI~I#B01tmQun}F?pMse%ZIFuf5Fwmo{1xZcOK` z0h31Ptd<~+{tm(G=vrP{S5=5bsqGEQ_6B8pgKS!hsIhvbNp?{D80*d6aV6Q(@7Ger zOIs^myd#forQ~@x`Ww!;@5=PPxA8J>G140I02Y@`AW7rRw&?!PG2Xn$KDNgqulptd3c>%S>SE?=mXfV6bos?8}E^e4L%2Ak6QXoc$nKclK!I%C79ArGgwSNp`rWreS6g`fMJ8+^r;SEE@QeCGJ8jR*2#Rc9lVw-&1?=<~AC z=Hv=<@;GyHkq;+-c7L8s=YBZ!gX@E4(RdbpWEQI1fH#my=33$FjyifNWZ#4#F12Pp zNZ~hEgpENjALFhaEXMHbJNrJD@9bH*Vtj7N_z^hk)XOW!P1Q--gm*FAYP; z#m^xxlRphL@S{RQ9{r3TEnE$>o$nI%CI*f+C5vJLzm?FRH+F{_w(O z0}ys%hmm;zx6yr9!cI0opqitu2;9W^eF)gU9EcCYnJpo z;*9>ZJyme=EB4H8~7u!+2vj$8%8_o zsbd{^7}S2e&r@YP8E`GcSHi4-HNcK)LSPcV@2IMzKbIN@998*&MmkuDRi6-OBzHuq z9;k;e>fawuz-(PK>L}QG4>ni`NwgZDsCZk+MQ`rnCyBcAmGyLx(>cjTkco|SEYw*H zcd7sZh`9>x1QU??-JRbV6YD%BL6lRQqWy^+21BKg*LzK$XVvagYiJ{ry&!dD06|48 z=P`S$y%?hvj-bVu$?1UuOJ`{{8>?|@Y_G}65^J(OIJvfw)m7W^>!}NMyw8Cbu#9?C zs;*K(HK+1gEvu@3?Y`>D>kCoW5s5Ia4Vk}(T*iqWf55{{6_qkDP2Kn z`mv`VO>_(VSm&K8j3w>SvDh>-j@~&NbILMi3}LIyogMzSALwM04vSToq|tc3%hYMlviow`R5b26I&=W>2$8%XVCGx^f>Wj z%7l~M`FkR7=*Z-zg^X8W3Y~S5^c$aGrflSyGJOSc{xX;}XC+qRFC1jw&js#=j&qJ?MFtt0@G=HH@35b*;w6iaLFKJ4 zAh;DX?8qKOdXswv&av@tHPA?86f*zK6sSG{?|dRqY^=avwKj)^y0R6K?Vm%sr%yvD zU8~rHp=Gk|h~p!D(WASGS#bcV*-h!_WA^F6_3S9@9NWOt!_6g)G(yfCB!%h4A{I1h zz6c(C3i7D_zIW_Onk}k4!XV7T){8>WmOAk(u@k@9P1m+yoNdMd*X*nqKH!j!fF062 z6t@|Ma6fOwuW$K~xavEi#pt+%45=%pcT?A3VVR-*E6?mfZb_2dR{S)O?BAwY1Z7HY zc)*%kKdlkp6#_gBco|>;e)I~`0G|R*dllap1AGdYfjq_G$j)+R$N#zFfu7HPiD>_{ zf7t`Fv)zf@V+00f4@zIJrTUNb3s2hejiR>(4+bC>)i@vOwmWjw)zpD!<0WEyIK^8> zi}0b3@^HTk>7kZIaDG9s&j6ly3#1jpFn*$2!viLMTQb&alz2;#|J}HP---_2KtHd>z>i#vbTl$E zc9`Qr4$vE`1|m`Ak31%aLuNBiKD$(RHXV6$Z((ykyy>M})P_GZPtgM13)OkQ0!f6P zQfn3=WEnDAKkGrCm``qvRuY|gxvc9Ai|Uv^1*O;i5qP?oPs-8u?*b>W3OH3yNH*q^ zTc@ttWHSUNHqk~kmU&DPJki-f_!|78f7QmqHZh;vH0SoRl4kghk&^bA0|Sku(xtNo za9`>if~8p&kL`((8`DPOd^|p{d+7~Pc_dbZ?H&3UbXS@9(#MOI;y=-3NRtM9z;1en z0j^Zz5Y%iyi+P z;-aOcj?O3DX9XeLa=BRXS6=BI7pyKqJ>+rV6&zY0p=m}(w-F6s_nm$-=-gPl6;4-I zS@AF2W#%t(I&o+|-L09^bK-9cm6@6kuO0^RA(2IJns5|(ztuLAam9Q6b)z$^O!M*v&yH zCJ8|bmwkX~zIZjeAm6hW>nZ#H@v46Z8QW<0bs~`lB5)Sng7GJ~wF5Q9efZTy$Wm*T zpo-en?8o;PFOnZ`d+@x5H5Zc?Z!RV$-dxN+{EO_vKhHiqy%Mxo;WBtVFvWwnbdU|K zTYzSwjuJ#LlJs2j?2P8lps{iZy;&M~U=${Q@4gmyHC`LN%){^hxxJxTXX@sXWk3+) z-Nkj&5SWDz0xXelp(Zm5?2J13LTVkAP}hDw!1~*%2{x*+dSTCBtzC{(dN18W*@113j|Xec zsCg%TUcS%^T)PN_uQpIa7Y2x`xsaQqr8H;Zl(Ylan{k3 z$bUpPAtkro17$ZECbl7^-uUndA&iB!~jGDA?E(s}ol0jeaVK2YlgT6KnKi;wcMlHd2c_c@bEGKr?O_jiAPK1|Lz zd+q1iYp=ET+H3EPXb8ddn5Iatwyp^e>^@KmOINQvt4!O1iKz%pPGaXFBaqllRJ22n(AT<~#18FfhQc?*h=_-?whJZX2ebWyC7nD@8 z+^Y%0Hrk*RmVjoCjw8*KfM!H^>~(y{d+beoBx%O8Hw&5}fYAmi7|g-K`WCNRE~lm{EQwYzTCRb#;Bp2q3gWp20Q=BL!yrQVt2iH3CnWm zWV2EfY&1FsUCXmqnbsURY=^$&Xhr9Q!%>@J-4hJ32awZAZT4y?u5_jkhzjXe;9U*P z#uf~*l^kQWu($x{t-L6S+z5_UWG0~f+xSR=_M-2iA#h5`@dq1T0MgN@)g#dXVygk& zy3+$M-5Z69n0U+$vAtO2u`*W@m1GlHb!_s_cqXf~(@=aaYwS z`_@>^5rZ9Dk7`?fi1669u?hA54}2u4_kActZ;3?Ha;#@sgAJ`nH`BUmXp4BW)w~43 zYKeU-F{7K3XQUt_i2GeESV@e3S@QU!Me8=RbwJa4Jm8MQwvldbg6I&~yf@gu7_Qo_ zM5?`3M|J!=8iL_cvK0Jogi?6x!m1(4nUZCl z!yq-&6o1Jwa=NRQ^~2^?EmeXyEE|ZyVTcCBuJQ}go6mM{P0ZohuSi2HCcB|`KH|3q9>12;;&gUfubg&62N1D{;5BG%F%RPxvj$OudZqDmS$ zAWBZOoo`8p7_lj&CO|Zy@wO=AEG^0ei?%Ey+5n_u%=ro2vJ`(Y@~F*X6$O9A_!Fut zRP;ps3Ts8=5WkJ>pD7^gj_s47CsClY#a_$Le9p}|`@>|Cq*DeMhB^(o(JOzCTIj!`2b2*BTT2m7 ze?MMKxs0Ixc7#j*Zqf=VmV5 zYMZ$Tt5$5zLOo1#23Ce9;S(!jT<|HZ70b@FYam_*0DzCc17-|e!U}7R^&)P55_3>Q z;Tlt)D71GZpN~Qw6FIS`P)YY$r<$^d;u+k*CK^>YYU!(ZgtOd^L$gAY$ayS9p%Hn` zb)q%v*czi5*l)KkX)|DRFq*NB&G5i!wqj4;mWWNJq7{0rlZZfLjE1rmbcqrZ>mIXe zxASc&_)$gy(fv-dt{Fe|n0<#Mc)J<^!o)wN$k={~snEX0$ zXtfvxlMoktjk9qh2`H2FqgY$4ZPDzPMYc~MAojso14jMZAL3b{Zofu~=!Z`t*)}BW zQJY0A2>k^@3%3Yc;>B{&tAVykxGCCOfs z+rYlBLJ+DZ{1D`>DbWEvJ6o7YNY~wc0Ph+>`*PRRKTgqSK)o*inMYSn1|f)zV)*i- zF`%FW(bLbvP8k^%N1wTG%mv-t(T=TSQxIEj9TQG|^iBXCb~4$0NM>v$+aG<3A*&G* zSJM~a19Mkmh*mF{z`xhQE_?b>c$2Ht9dT~cOKK!G1kO-&EZ6Ye0;_r|AZAc8{J|Jz zLl~~}iwCFcwYo@CJRNM6f6WRt(qoz%?R}&PfDodO7|& zb-F)b{~-Rt2?B=aX6)d_2Xbc^{Jrqkhoe&&p>S4~x8iEGtfELdt>7r~NwCu5_-r3+ z+iPIket@nkh{Geb>AJ$;;zHXR`mT7iqR%+iJoeNOj@QEWPaf$HGSX!-(&fn`y(S}lRYv++@-%q_-BZy5m(P{vDL-mLH7mjuxH&3q<%H zB6KStLh~@RkUk1X=VeAjAz=#F(PK|rM;ZFr^3o8m$;GCj0U~#5Ye^Ns*tg)b5ywdr z+JbR*1@Jlw#m64UcO3X*33LHSzk!&Z2)qX$_QoyJp+LyIz}xg6m`q{kY^hpgh`{@P zB~JJ*Gd{ohdlVRaM#O^KxY8bzgSI#{7OT+rPh@JGLy8$?Z5H3XFthlEN6Mx(lMg=% z)>NdyWw5b<1=|~2!?B^R)E6&p^&h_S($?TQQO`e-;~R?_wXcwU_+Mfp=&cZINdyxA zXoPfQNNfaUCmONDsP!&TKyA2cuJO5PpKJtUC2Iu3%|`G&StB4#K}Ki@3-8+yQP+uy zbsZ@ia;Bw${4WLV$zgkrBbRt%WB9-ct%wPw6249Q?VdKV<9;)j>S6lH;=|l=zd4F{ zs;Luca1x4%r8=o~{;yIHH@QfLgd;Oa-HT&Ed z=S|Bw|AGs1XIwPnV#oALzVzk1uf*eBWK=QO#;R0MHQOPfISk3WQJF&sa@{KLriYsfbbpFG)T z7=WW%tjEJ0EQn!JfX|nt20kLuqh@!8h%z%JwgquQ^?vw8 z1Ww*-Uvoh!J~v4R8@rZTM%WvJ+KB*^g8<9xShvV%rojz&bsJzOOUhSd{bA>Qs{j1B09&cW_Oi@ zF~zMv5`@CgamZS?5YaV}ul^7nZWQNESfcmPy~Ezv4Et%QB-I*$`^%bd!qn&AAv|#G zYp%ja&6!6*Dl6h^EVYG<@tiSD*nkQfwfr@>grJZR{ivhcb9k6VT{bb?s1DUL91juO z9Y^7gxlo3BlfMKnRszqF)Ike z668X)>=goBD_eHllAfzfq24DA<*6u#;?wjVd#W zktBStfk^o+Y$>SSQwYWhDHzJ#s1Z9r0DDZpB<4))Y^JpZu!obtGJj&0A)B9h**&66 zOpj^A5*PIiLE3L4SZT`X(<3>6%@weoWzNe5z%2mNd*C;-;|Dz&HSZck+>bD`zs*|D zTL?gNGQc7Lo=OH-0>Cc>pl6?EZk3JdDmM_j#r&3yeq<&pPx6v=KrUzMWF-m5XBWEH ztY0EMlI%W8fW_-hvYRDZ-?Jw3Mgs7nNYM*03V`1w0c2Xux*B!=1PwGsmy|suyDvd- zNg8_71RK%+V166@Ym+TQ|8iMG^sg{D-9OLF;RwX5D8R_!b@Ll^u0vY^@RjIu-!P+Z zH^D~qr_FChUu_JFFWqQHf6xRQ(eF3E8U2tEeaqL+&4E#*r;Ubq7s&x48|j7P~P#o5^zs-Adl(*j;W4cF*$(zdHBxwh!8?$Gzv0C z2>I*&XxL$r6U#}Y?GMdw*@dS3845;hl9w3oyhza$K&SoPKrVbr2wB0&q!99g<4GZu zLGKBONfBml6hdz$gJinQ8XM!M!2C{>LR)5-4W3|vjp(DzZ!}m)A>~Qz@|H?%w&Wmx zG0|xxnQ=!CoS9O{f#7Z_WK`S~LsJCMq!ANn$f426fSG5T#mKJ6{EayL>!sd;=uHIo z5JV=*D2a9pV6S3@D0)^vOi^T{$t;y+m~uhLqT-}^P#9gF6hdhQ)CL56+g(XFPGCbc2kTLrI@4eddoB*PmMfZYU2GTfT1HbN#9_F#S{PAQZw zOa{sP9&$=-!MDwCk|C!Q6cW)LCK;BPV1uTvFu#q?zEujjK2qtIbIcrky~H4O3uBOX zOQj3vo6*lV!A60T&F@61v~-af{fsFkjp)bC??kC|(YMX$ubN;P-IPkL2sYV99Py@D z+L{z-$fchr1sa0sx*m+vq}@-%5z#0{*1?oa?;*HbG8vIg(e&R*fre~4Ab`C{)f7&< zk^&9s)RYuxh^OBq1sd|{r%8c^fZC80Xh^8qq(DPN-7bK=_<$*+zMC|qA*8NP3N)lt zc_Ofnl!{-4D`rjc^Sz%&BJBBzpD&L28rD1}Ffj3aaolv~!p!i2um{^qABtSs`@&4X zvtc#JRp8TrEwpFh7zP~rjh7&fVb{IsP0-eHQUT5?4abhb=Qn;ZpD~BSyu9dEPc8Be zE5~0m5}k7(a0K_Ug=5bnMZ#ncuRa~ukBwY~ZPPeT9_iH3+zeAYYOS()yS|m~?K-bJ zO}xE3MUw9JX0g3L&+6UY$kCA~yJ|t;XiC*r`DbL{bV~I%-RCvCgUZ=E75^ct|BkbH zrdCiH9b1b`{5NJyRl=Mu^;!I_nYS@33jGQIzv%-j+5gSby(c~9s3Myk_ zS0Q~*jL7-|DL0{WkP2_#MM?fUZeZg)fh`&6yLv?-iFVvJ zV3e~FL!h*{bt!o+2r>Jqp6ja}S3x2mf2qQRpT1P3QD7**kh$nM2%) zipvjbqUSFzD~NpyPS{Df z3N8og$yI)onK*`ekDmnoB8HI|Jc83;lz7ukP7}Qbxx~lPg}|JFVR%v7!JAAr*oOhS zgxQHmU(-9hHDPkpz>&Pjkx@Qtvrm&3SZKKJ z61QgK_6q#6qBo&FK7CHQzEj^P(QS9Xrf3fne~~|pLOxb8Km>Vot`cR+Lb&40(I&D5 zdsmSye%M5hY)2wnprjN-ZceWu_YaM?xdj}na-4CzVcdx!C}v2pN8kt@r&Rfpbo_0- zb&%t46%u^L_}kb!%I6zg@F~9$Lw+$j{9J5Uz87P|&f#E4CZi+4C^>k@NhPlYLmkz% zcUqgaL3GfOQQ)Uwc;rSZu!3JMetGy6#I& zfcDxc=1U4+$OzxVa25xus`?!nI3&Sd`!YiO_fEscco?W)!*90NZNrPban58ha(O~S z0k?f&a=kdK6RcvYu%7RW{{9jfO?U99n}1JTaS>omLp@(=KJj+s=!9ihV!QrR#I?r2 zO`<*AOz`2BOmCR;4_W0ObewC=OL*mpRL+7-MF zSA-dtJ1aNDCIKmMD7e=Jzkwic$q{M-MQy)?B7~r`jXsy3P2GuQJ(__Ic*hR+GpdC_ z1xr@l71;=Uqd$AmH|`gpzB3`N?@Ww-7ZDAbrZ(~gR#oUtpV$+q8}t=;FBdA*L>w3m z+(7+mKK_~q=RDKZ4J%^PzzdWKIriY(^QNZ6R*+5@@w~am9W9 z9taLwwaceJAXTvYqzcwvHC5!`b>Oz77<8~b*qDKX=%Irhh7JbBO4aY9!o?HE{ooVF z!zon4L>R8mmP%MFm9TayVIsUNw1G+(;->nv2c#AzV*0eJsfD%T2`%hCsfFDqwXplB zh4Ip+zzeytiN~Uo{u{A zmu2{sROe;LWQO$6>1Ft}%#LY#YWIEUebD+nCl1UEESo>#v;swR|tgW3x%R={&th~lUZ}&O(ctdMQQn<>B zTD+(B4F)ihwmug*_&`)%t-QQU3zDetAW`8#q5@!H>=#(kq|gwPrJ<;VlF^yL2hPTY z#)&Nx&?cqUV(2l+lze4cSgMRr7op$xCsDs<6o&G2933?hgH;}dgxk@p#syA725|YA zvK(DECUzm}jH70~8IbaMn7!JYEC{J|Piw983~%k6f@S+iCAu}G5*1As2M~DdTU#rq zHCMWax8gKKjCU|jM*A#|^f_2&Z1wP7M09qn4%HQ;WAMkVVm??2HBK@}OaqMx;n~sa z5LYnBtzV$f|1(^u*?;J=xe(3+%qPeE@0d^K%L*suUkh$ZzIC=Z1qF0p9r^2ZE3%nt1fm-w4de9s;Q=RhZM=*C#YEG6Tz`o|XA?QwRVY;%N zmGfy!4=I@Ahul29^T2xJR&Ctyj6G@@vogv;5BL!ccRKp@~?Kx zh~dB<&VTwdZhwT>>pF}pieoi|>K}C8t>_PR{B+4RzcN&lJg{T_36LO3F4{N zyg>Bf8ZBG}D9$DCENO}%LFbD|>ufs2*XE|T_}JdW09)5G52 zi*rxa(54iyw$6>urxD&*n*)tEf*((K%MHgp5HoOeUCF`D5&olTI6h99 z(Qe#4_MR9h*f4`Gj z1+3?=e6-WTu#Wmo^sFH%me7csn=FSg+dDjRn+2SlH{QFOt*!DPJ+)#_bm>r#wbsBC z`pcM2%Iit!4d>z{5u0&?d~32~qdC@gRa-|k2d6pYW&urE_inTJ8L%z`0WyDi89Hun z)YegAL|fq+1WzBC5F5-(F_$a_Wt3*^7G6$78EVZ8RH!E|beYz0h=E$^5{6`pkYgg+ zegrNjQsKJHc@i>a<;@vuLpndS(ro-NT`I+Nsk9(U7i>giZP=hC$S+N(LE5@p5r0m( zs?Axb;zS>uS0_Rmq>9r(X%w9()3eQtbBQI)LWJ)VvK19i;{h!Q%VHdHt0O+5tg=?R zG zde*DIrDFQ7)QI@ZMYZmOx8g;~yXsZ-mxRQ6g|=Hc;4GG2<*xXSE&G}_3x z#g&l~WA7UE`E{~nUy;eyF7BC(>XeaeWwK6Ppq1V5WCT0axXWZS`vWZQQ! zksMgKYJ~0cQfecI}b*m~wFar(}l}_ zJ7?mwpPYN#z8BB_1)SK`lnS;N+r~F5-<}j9tj z_n-JR;`bbWZ{vqE%Tk7=rVg`MhFPuFVK)97mX?+_OrA|PZ0NA`^}k6^vAl)fieV|1 zC-CdSZxNV0bqW5nH%0>CPrpvdaC4IiD%!pYu4Wxf3UXlHGYV zi(W$p=MbNYmV)W*P5>DufT{rU6F?vwux^GdVlW?v2o$#Wcr=ECm%2noPo^llQ~1sv z6WtvD(qBV_@0S`)s7=B<{ zIA7Fo9Xu!x7R8Zqtc275Q)~YfbOK7j-vI}mt4Hdgo z=WE7M*SLlD_EBKkR$LqgBTyYFTDuEU>MqQ}w@cZyoi(6ENIH}3#0jFa>h`Su4zo`W zDW77}a{D(vBjf4)XRK{MCK|V0O0n&S#MfQRQHQ1YElDUj-c{m7;6t3ZdM&)5RsB64 z5D2eIXuPMsK?(FBv}ZN6VIeRi*Oa-<9;XCx`Tt$SFcz9an-;MnyJ3Gyge@^)YK4f0 zv~K{8T#&+7QBSyP-9%axK9<}6`uu;q1-@8q(|2|bRUP$$N;OY55;n_(YUtW*iGNWj z4!-lV%}A35i$L#0|JF~UlJx#k+|^GCalAu+iRSl{sDzR!X~izjbv@BJGC*Ag#5ZL?zk%74yq}Qq(^>KruD_Br3`6ub9RCq^N&apF}0D{))M;pA_}s0gCx{KZ#1z z{)#E?Cq;eh0L4t{Cs9dxf5rTLL2@+L3wGPjV0*QnL?sLRE9SrYNl`D@c|XOh?Zh3P{Uj<` z-Cr>e_miUjMxOnBB-Cb&FL=@6yJUmUEE(Hs$cz;l-6IOtN{`o!fJmqmQ$ZS zK%(vaCCVKj(Wd?qQQPXTO}F)z$TdJBUw?^&s)o53=0UHij-M7F52=h?j1*EKP_^qZ z)wnj>)JD|MrgB7s?l4Zoff)#Cq!t4h=dOUAl?bE0q;mPHoK}71hQ-&bUD;Ha2GE!G z%%k{Eg<*@vba; z2i2+kH&o|-p-=&JQlYY9224HfUYT)EnI)4ljZmn1Boew763!oN!V)@$<_$KL(7BLs z=wK6;(6#w6I=|2G7_fEgfk7l}8f?N6`b$4B*i`k@!H{s-U=x;PBUSNWQwd!R2`3IV zVM#7heLQEd$fQn&g!=}Yup|$u9vf^bp_?J$-Gfb7Qh-$R2b)UhXh?X;U=x zeHghN(Pg_>ZBYU1ZXQdP>~bkXf5&o|iNDV-e;-x@6UzUc_+}YkwmhH-blhtQdXwdj@_N>qJczrZF48AM;23emp)+g=e=9#R_ z&AVpyQxju8b4yN^@U-lWk5h?+gzb6zqVyP0^()aI=U{m?`dfTTU&TMk;;g&0p!cj00>;t$Gr)sH9Z(P>+>YBICDOOL!)W~T#Yc@+z+p=a?k=>$Qlaq(_ zSzpd&D()2KqOy&YD6drPZZGa7nNMRky{pvFvU6d47k5~>L${~-oV%*e+U?FEcd~TO z!l2IfIm0ptBh%9PH3UtK2U!`E!=WD!;vBPcG=e7U`w^(v-7ld=E@@fcQeyHeHFOOF z#XjF+Z1Ng;3s&w_XC$HYYY_ShZpw;>iV9qT(7eP@QHNZF{s_lE$K#4>j6vuNSbsz) z>Tw1v14VM)_qC+;zzCejiOfaaP+36&CIK)>0Eil+<^)Uzz@7{+1%NXiHn>-csQ|p6 z43G`Ln|%NX+@BE0t9Ha%0m?oPuqd$8M%A9Vk1Wu-k*X+*o0w=BFb!zAM zz)72UozgiXa54=sCs%<>;n@hhHCL=VH*<0Ul7(5$xq!T#6vBS)ND5&`?@5G6mI4EgyunWG zGNin@e`d*;#_|%`*wYWbazS+XmqwF~MNY#V+GJFEv!m3OGE~D2$#KpkDbV0g=KY8I zX)M1*OL4Rd{4xG!lz#{>_62bHIG{cZ-v?CWBh`NKt3Ey4hpBpTt`oWJKh0Dft*TyP z<4&?@Hkd2OR4*Az9*fh#vpNp30*FC&iwOJ*e`LR5Sjr#sBqpdaKK)d*4D3+R?;QarvfHy1{rh9PGGG7LK*K@2xE&{e2@?%T z`a?DP1Ab~auc!(aZd%(q-tHk`e0sAtauTCwkNN5;Ss+!B5IL9EHEJac`ox2qz(yTGLIjBdDaE!~UZm?gvF-@>{o=^;Tslltv7 zf+{p@vkJ|{vie+D^~By97^J*7GrnlzmA+8Ti;zcH`-kN>ZHK}YcsTk@eu!@&1s?SW zy;>2{UA`S5J`F7V>kpG(>@G^@;PQpOJ_Da#ZH5}Uq#SS0yO3R_qOHjC()+h2LYttn zTM@C%M2M|T$bUn-E<1Q#&hDZN=Cr$LEWOJrcNb;hP{^W*DPHZON+iUWks1np1{#5k z^(JKM)qWgi-rA43LZ$?N{Cn}pf?A>!7O!48tW3u1v|^8fg3vO#(Hy{nvXFw~{AD(w z4UZuq4!hqA415aB#mK)bwDAyFxmQc^hP*f$-<#_NLPV>ya%@1umPuDe8IX(CF3(bS zyF?ScT7lByns}vAXP;KMTaoR<)fa4^c3oDBg4Ur2u<0`+-nNSf@oF0J6`be|ZG2r6 z{xFT4*w6MTAHj!zfrn_IQs4~*f{cPwrpiKV{tW;lby;W@ls8C)(8yn+Iq+fghV&x zXJlX>4}7)Zf&L1-hNq3)@rDRb^@fU|BBKhk%6*y(Bw7xnU7haLuFe4BZUf>ZZFsdp zFFIHOaxh`3qOy=S2gPxxPJyan`_L^pxx|sGrN)svH^U&QZX)6V_g?M3puoqE1ode@ z5=adG=qCb+l0u5GBX1{=hh2F_x_P%%%w<$qxl+I4eX8UIZ@VyGo~|QFK*SYTB70-2OOD4_rRQ?!IDb< z4({?|HsPZ~g7d!v&wd0iLlo|Bf^cZ7j2ua?9NyMa^4%1DXQiXL(i*PRN^Xk&_5#+v z6z0#h*(_!q%SV}T!9IHqmIfI#`067UHko0DY}Tqt~V*0PSXiO07OiM3PNkN{DPyu)Yc({v9v? zidqNjt3|{m{F{KnwT~kJx!r^Za^t{_BT__Vo-)MQio>9_dX9O$;THVW>Nz}7;Bq{I z^)Jhh+r-B!@&h}yEjd$z^{>j$l>&AUfBidHKiMhdN$eDRV{jrthyB5|f9MV)QH5h^ z{=?QYdxC4p3H+ycWl%j|ZF<9MJbE-~2?mt5b|LqPX%=H{ZMYxdC^}a} zl0CGXkVbKIW3c{id?eQC2aGsWt0#{z>c%mkM{wndIKiE_p43F-wI`S*@aB_xo^K^1 zuRkA=qwx3?-$DTaMlZlOPPGlLl`O|RKS{C~nqIQRyc>$GepKJUq8&#;)U92Sj`#Il zI2N&4R;ccMd+pDFJyl;zR^|)Y{sUk5tS5tmubr48VK!T!f2xKi1!EyvRW`H7|%J@hndqJVrLJN^uYANa;V^ zxV?*-f(R;LP%#8!Km={%Xi-DL%aA6${q51j>>DU5RDGz(zH~V}lvxDtI4Knw(dS+U zZ6u-ao~%@nKJpiVQ%|L-?nq73bIp2vYdFlplXCGq$>d&SR1 zEuuf~6Te&E25a`7hY}S1ROGL=z`H~3weKN~ee2w8Mc;z~;ntg#jwo(IO0OEBINDl@ zr==jM)5>En?b{9wx3Bpb;scJ9eDmqEvv9p7wvQEOdvqLI;%r_q1AEB4V--hp{(%!8 zbcHd^R8p03IYY_QVQ?O=Soea>kE1XMjc9K2*Brj5<77?>`F#_GF8F_?;s{f^DVj1i z`r=fM?s?~7ciZf2eJ^T>i%C$+rOI3J*6^w+#qQa2J~bs=iYIQHYQ}9-*1bV-4<@oXsm)o^eQ`MgtBaG z0Sb3okx29lwv(-U6p3WlNJL=ZVnmCvyIHb^&0^3OVfYHxumB_43_^MWF|cL-*^9)4 z7dod1Hw+De-V^NK*qesinXx|&meAuh?1PiS=l5XM2)F(-GAGZpL+FG2KfCoO!uV8m zjm0Q8zxRR18e8w*PSjee#^M474KJa~JlMjHj@j~YXR7`;{f>0E^RGB2-Cp#m|E^^g zyXU3CyB6NAR@$f5G95&XBhZScq7Bh>zUZ?TGAxW&P3K=Q!1A)tW%a6FTn{E+-L(8PYURGs5*lASfi-^9McsYc|xvDIKAaRz)B zBwU<3_qER$?%|ja2*2>myP@d{8U;@oiqmomp0goiSmWLRK-{CgpH^Cepo5PrlhNHF z&lw{)bB37%%#w)K{hgtPZU8P`rCk3nQAL8!=@mWxZIr_0~Yp002J$z+4@O=SE5e?EXX;M+dkYwJnSevA(!Cx&1!Ao@MejaZofQ7WOh^whGl? zpBS7x;i&-SOc&*lc0q1F+>%JHs}oAH-Gmh8X?92_A(?(N9}oy)c7KC%?NF!NK0S~N(I2PO|YPDUT39Qd=U>pym@Oy>EpLf_^6%Fmq92g@F)$%S8$<$DtU= z1F>XA1kk1$ILTd5azW&I3dzo~#;VIb%R7hVYBi48MI`K_ctgPdO!z}_e^==pbvK+>}+SEE4B3r%hhnvVM&Fn7Ag`yJ^7=jKBKlvYz*fXvV}`_jkrKPaKGn~J zl+BOgnF4Xmry;Icl4jhEJiA=&DXx*5-NRfcuNf2aI!DTDW|1<4;#%gwZ*9A%x5z`x zh$yZvF~xPSyNvCDi@OCgyFWIuTQK8pgCv5Px%UvvMnH|I1AzX|l)~KtQp#82UC397 z++OlED~l9oOmNN_n@ql%>pg-^8KU*vi+hPykZc{oOp-NOrQlE>Lme zQo9p(i>^l{>@T@}(Z1;y684sW$>AQ)lXQSQUrK}-(#u@^l7frlJ3|^p41a_~Ni_xO z1^Y2X1SQp)gt|adt?DVM-n+1y4&pp5^A&`Od<;ny|4x)t%6P#TK?qxWMUQ_Q)gU2! zA`0q9Qe7aZq&EvRRISm;fSL0W%S8Uu;>{d@@q^WiwRV%$Tfypa<(iNKVD;>E$?DI4 zy}RS%XxU`+e0tKRJSw~JBbc+y5!ap4==&oA*t?xZ=eH*U6IeZU`WqI*@qkP|ZvRj+ zc8fKN9@cnM-}m3K2n~Vpg9a4ozj4}L3oX~8-{w=M9kbV>2^RkyOKkR98qG;idHOO{ zvBuo`>^#L8!)<#n`tSOdn8eOr43oi)$J_mfQ^JQ+JC52LXD6 zt@yjx(S9bJGu4ePy6K#Hx8W4Dd5e!9P}`&aBPpfkVKW_4e?g)ZpwJVF&D6 zA=Jd4EunmK7|%F`-H* ziY20~RS&W(v}pxw(Z$!(6JhoZz$)-KycPBV#|J3>#Bse1D3Aq(MqK(?CPzsKi zL4*A(rkh3MY8D7Sz4)5I$*w9|MJ;~xKuz&NzSR_~d{7BVIK`^lJ|O#TjH z;aF+PK$7k$K<2H}dWfufc^JqC5M~Y_lRwUL0iXzg)GJLu=5#X$DYE8K`(FA=_uw@n z&hr5J9K zvZN69>X(z&h~0XAQV9EXp2*GYSF;A}SlPbb5ccdx*@-=CreW8|S%ikRVTF#lC>=+X6Cb)m3t{-gHCJ1jQ;IYEca4X;HP_%={w^Gyn}IjjvWv&Mj_*#l z*Zv&CP6>Q6WR+J5%|0c3ZRysU>JH6H*;WJ7#d_G&Tb01h)0I{j#`9ZofLnA(W#I5V zINYt7r@Fzb8E3eK@r!r^v6SllT(-%07T(ZUHr^Um9Q$xB?KVF@+hdjP{MM!#yjRi@ zOyAdW8mGb?zPHlRhJa|LG7Svh$a6S?hPPL0^TvL2Cu(S4f66zpX#zy_OLfoE#&-_Pkr`k z=m7y1rpe_u*lLaW>vCuhMy8^$o;k02M&HO}-IH0FFDJ7yJAP~1MLXnVmTl=dnZ=Ts z&QKs4OJ?@K}dh`4hT-^rN!mHc9dJdTp)N5o5Gt~mlbAE&{Q&jJxnUnyk z1H%Nsv`0xm=EvY6i8CBquN)~1)MPm>1>kn*%%;SS7qtw4$|QiyCwdh%0qz2cm0yj7 ze?*+#mEY_KA_4m#Ni{Z;ashigN!vEpOV~026YLkLlI`ByAYtDY<@JPR`jNoIiCA8P z*YR>rv5omOqjy40E-jA-5v!e;VwWbHV$){!`w8)e$tiZcCZt3}*bXaLpO%xB0DGi6 zad!(iN&wT7!HfyoT>{p#b(=|+NOyB0jASTRm`#O8&|A{^HR2gi%A*X565Cw2QTWTO?)G0*hY zx4?hX{dY`6)b^?>H+U)TR_UtxE`0;&pfFD9inujMWk_vRo0Q`YjopQeEW%R&{;nP% zx;QlAEX18Oft~~I;5V{n6iuj}iW_OiLg|7c7S@W=i$k{IP}p!Gs+gU^wNamx1Ad*p z({$wIJL->uv#4eGZ=1k7t>JdIrHz$=cHj+-bwQE*5NJXssW@F62{_*190?uE4I!bw zj&rMb(2K$2+_~aPp20iT`I!L%R~Z zdK{b}^xe_}qG&>-bBN-;7ITRk)X0Gf;jn+_k=eTp_K0~Mc@ zw7m&VA4R6q2edXvL^^#Gjg7qq&C4j>#CMPVY>Ivox#IxRgZhW891kglnIYrEYrsTU zZ$>kr7DFpmoTpZNHU9t#j4fx1DREli`dqR^Jtf*sS5eyJs9H4gd6g(J6>Q9OiMb6i^>=QCBp4;n^nAzZ=dn zdNY;Wz-b)bzZ3MWY$p*Z&I2o)AR=WfD9F(a6AWBv8e2g`VI|c&&LA5Q(s(-2ghs&Y zqNC$93{Ci);IXzTI4Ttyk-`Dyfg5?%a+Xl_U0@5YH#qqil*CabNNH}mNkbs1_s3$m zgOorU6O@im6#t*-lau9{aovRK^PoqwPWCOJrDo{M>Agsa$L<@W%k&Z!L#=On0uTL>g=dqsO34%lj0GT10Hjok1I7U`RRE+!iUY<2Fe(`!3xH4I|7)`E zcnY3m|6Vcx&#!+Y5g=)plxyl4qOqi3^1p=k{sA-EWbLJtqCtfXRz=okgG~rqe1(9R zEjA%+^7O>&2-(iu*yf2zA#8MdQiwReAHC9z4znCK`_GAx#AX`?hwk=LQjq1GgzH=t zET?lPJf0pZ>Iz+*g2`pzbjHdHFbMspQ&(bBGWN900|KNIn!TqJnkj$;n-I)FxTV>E zM$?*=Fdxki8}V^gI*R8(?I{V%r@i^3oPQerr||Fk0LQxHIOo8ruBr@}l5s@?j+3u+ zv{q`fv+}X?=r4h{T~!YK%!yYce;pBcH|2Pn4IIB9rr_KZZ zmsuIOE6Y+n3jA{-qjZHCtM2`(31-R+9EDl)j^$8Sm$e16r_Q0fi^Z-B*~D(%2;tLf z7d7#nvB0A*%o1}a#o1wBa|%q2O9NQUx%_t#+Ui|#d%dE&7UGTy#lLz9e5_mxt2|1b zYf;se9$md=TRIYYE7a*8O~tkVS2@4&+2qEVV1|aaFVy8TrI0mFFPk32*Y=>&7+n36y4@m z3T^AL8gT&dJcJc^ku!pe3vEG9GYH4A-eoJ!H|&8NPLv@((BF%4 zvbqHbs1T=$?&0+vp^;m{bnrW%2%kKUW3CNVl}3$@qaNJ<)$u2f^*WnJyUylpTQM#W z<^!KluaM11F!&LjX?z_4vB|sR+2B>@AjCV;v`3_I(Z(7PpxZyJo&qb76}FdjXZwor zAq5%Atgx+tq0Dk8xbukkEpY75EORR|EAOQT>M^m4{}k@2seJ%ta{l5{^krE-C{~yP zfBlsm7R%#U>vHnrj*(O?^{#mq&xq%0&5YZ5&vCFEL!oxs>|z&7ggn9UB@_0t77(aEYx6urFbTO zdD^_}!m&DfFygJi-nQageMCT;og)kX0SbQ(#dK-kPse!ywyFw!yyHXv-8oAvRbL{} zpkr980=)%Qiaw6>Ua^a=cVM;kOz8-OKW9*wNP*NN9MO)ybiC8?c3_tk!g-t<`{@*G z)OqtHwE`;z6~!PnAIA(RPP-ZG0BD1G4@*Nn-4-acZA7j2B5EE&Kz9)Y78Tk8AUi14 z#nW}$MsG!Hp-l(TwQ2KmLG!Y0?%?gXr-hz*hI$Vx2fp0F+eXDh3<01QErd!)Y~BfM z7A>qQs4H4jmG9Pzu2G)lcv3570iCme&RIa`EL5c^+g)h84^`WPDj^66U8Bv)0pS$k zNcWLPpTl4j`<&d^a)?0UP!Mv*A$LkqmR^+QESlu2C;|dz#YdZ4yBcj?gXXRApfv!a zq^mjDWaxbyYbb1b5xz0jPy@u1U0P(+fNa*7zf@qe>_IgU0gK5TjRQ5@nyp4{KiM_5 zS)Z4eA9l{OX=mFvhV@o$_6XgkgQxlQdF4vs)mdc~MVBes)sxN%dn>MH$Dl(z+AOky zH5lLzuuA{|E11PDVK7($DqS4$XhmcN51>;J^FH>9U_)1v4XuF^%^6GLVwEX>#e84v zCspCjmca`c;!dvl?^o`wDH3A>lb2n;!xXf}jZY}-7-u!*c*PJ@Lp8x+@PyW$P!Zq@ zX$9yzfFDIP1TI7%ELXd0mf+d;c@#|9C@6&je{L0(nZ(7w-INHE7&Crci+Mxl6lxSM&OT* zUBBqZdTj_A2nxdavCd;YlzIE;couXY_aO_B9I1sHDN<`!VWaVg!jg1~5vx{m8MYl4 zmSpfNO)DwTOC}YTWC;BycOfUAtgm|zF!tvWh)Eao9?xjMSK`L_pBB_ct|L@+wdnwh}mx)^;j(Y0es9vj9l;95NhULJbKB8W`DqGbaS&ojz zKG8==Q+h|G_Q?Hs2|ltGe*s;OhxW)1@t3aVtMT03a5w%EwFvOz)g64Kmhkm@b6Ln` zqj>9TUeb$(AWih&qv<5Ih9~7CEFW$1 zaY#On$%h5yu;1}O-AMkyN4|r`L=VH_M&gYPr%JouAY6l92TiH6LD8ARIFfFGt8*x= zZ5}hO%2{gAuBz`w4M?$)`Qio&ykiJi1lQ%}#kgOz!G4AP8s~zGQLDGRgBq)jTUR3H z5Io0d#Wb`6yO$OF==(A_aPSo44$nN}n`4gQ<;Ihvj^E3TC&wMX*W>94-p39?#d2dD zK=JxeUo-8G#=M{iwjwN_!|x)f5WTvf20euIL0!-UZ0j5AFjL*m(TUc3;2jY3h`6K% zBS0puL4dunE}hW88j(jp1`QKIcl1s`0<(u!q0(+WKnAw$SE7&rd7pCPHO%UL&?a4C z^+UfBHSmR=CjUcpGCr^VO2;AiuaiV+O$v~zqtm>o?{na57R&P_+^Vy5ow2(j*az^E zFZ5$FJE+u7mp3PPY(YRi%9sB=MYn#f^p9yUSx~ZDzXla57zCCf?|}8L-wsYClcHp z1!JGy<`Bf=Xm>`u*mGkqX^ZAv0Tvk$WK>w3g+9ZaVV+AcdfY>czIKBE1W9ZpNi>sc zNEW0BQiee*|2SFToXlE!yYRxF^0Oc$03G^quB{HXr2p*i3GxD6ld`yAVf$uYf@xLM9iD{g&~8e_+pf_ zUT6SGkii{~WaISNmQF9I$KWK`MF;eh44JuZ<&>FgtJ4CfZTDnie_eG(;Ha(cz`YY; zS1YmMrs~npa;D>;ovp+Mld-kEC}c|olBNS^DiD=R#UDIN=$9lN=$E}nrvRbA7D&+q zP?Cd)FB9YWe+x%B9w4%?Pt3lx4o-^}s~9%mp)EM@A=hZ`bfNJ`y7+&#rLKbKf@omX zG=pd8PcOnCaeUC#Qus6_$BMJ`rzu((q7<&ohQRQl{RJGgD|13>8o0gMM`#S(#S|Ha zz;M|&?j}1cro;%~X-JG&S!4*Dwg@C+AW!-tr!1Z4=XVJK36}&nWoipVDP^PnYINO) z5S+UMUFmm6A%v;)KvOW!s#?7Gh8Rwii)$8A!w~C5&r->OAI*b9pe$&Ua>Pw?M*g91 zrg%cL&tRNAIE+eQsQ3)-e^2*?Y_PL)`1u*C4sY|eAFVk;FBH9Y!=uQrNV9DqnA)L8 z%@-y`++GoDH~3LLBMldsArdxJmSG(VnIgExD56&~k$(-d#dUL5k^HmD2o>WRJfkK5 zL^DtxZeZ=dDLB{b3o2&lz{OBJ$bLa*09ou71j#;0TEQfpg4PThxESUsi}~k}Hhkb> z&iaCi898t2QFsJ z7gS8r-zNVH(4(gMnPXSH|cocZ&%Cl9kt)Xc| zOA&|Rr6z!jD4j}r*HS|0QG#6M2R%q&3V;WNSRdp3A4ULd^6KUP$Rlr0 zMbqmus84<%jCkuSlMw1_B&3@n*Lb2eCmmJ^cDvkNUgX=8NQBT zC0H_~D=eogj#oO47&%)vvjU1XPJcOiCknyQN7=|i`5mW+_C|H6p5fS>hme~ipLSq< zA#ER`f1ULV1q-+yQPiq9e-A5+VlRCm*l-zQ!rr68?%>nc36rZcQZ)iWpFR%z0**hO ztT)gyVKk;H4R;83T4?|@gNZ47XhoVpQ{0FltQY&0uDNK7Ko&M;cN~Jj!YVL`)^N6| zH3*BvZ3Sq7K2DW(*urs&zFV6wXSCW?uwt*(O3ZAOjz8PCt|QhwA=_AEKHKit<-{3y zFNwMAOEL{&Q3`ZdOIirbn#D;#OKFKuca2f z!H1y(XEb~55R6R6t^lrCum1$&AACv-z+aN->Ef1PISz#ZaWlD(z8A+jnM{n66$c4M zwMGg{aXB6{U!FU|_D#YC_ z6(v3(p+GCiIR{?%Y1&n4k?eZ1gCeX14nPf{1wjZ3Cb47|CE!=};8&76{EMfsoS3>e zF~#ikvpmX${1bL>;q;?#AnSFWUNIBX-HeM!JpGuUyRFaqMhYJgP@A@qsXH^B&*tK} zVkGkTeJ-d=1bgxi__tGTuD1t*f-D1+fMC{+-GP&~dv4ct_J!hXgHOK@iA8%IICKm? zaEpAp&Lx^>Ax_{BG1pgCpE-x#>N)tU31b8324>l2 zEifHH$bbe2EkLaL^o`5$U?1lxh5u?w68=5rS_z-={Uy!~qL0&}M=yYv$#x>Ct0$Ju z3GWto;%WoO&liEI{IJ9oL~jDNqy*L_t_1#i;Oc*JnY-6`ieu@8^W)1!_7E1ev36vP zg)L$%=o|||12ir4db{IQ>S}?L<@PoA$pI4tmKEp86+U_D&i9e&zyow62a;jUwKwi{ z>!KVuCRm~M2paE%uR=RU9JxMXgchVaTIE=*c(O79lJZC&r(fww1$exovkaXyHt!%x?bHdPoc~3=aAYyf%`uxom^9MO~5KPqG_$)D=e_VTt zeMNseO~~a_kjvMgAqJ)$SUJUxXIT}T>r zKrGI+h$FVoI%+p!O^iyiaO^f%88iXG|NqArKdjy@dQ1O&|6kAYAy^8Md49`r-fO;W zteBt1#Wq+%$ee2quiWC{DtXX|vX#QI8nqFqAqa}= zJSL|ZU_cl?1rQbbF3F}0mjcYzc zvz~u_Ch74-Wn+?F>e0EHQZmHt*o9g8d00>$g3VL5d$PEqe6|vZ+B|jPdtn5{8Zz{g zPFTdZ&bJvW%l0kp^i&Aha=)m#`e#nnzqnaxFWeYQ$Y2SAW5@|sa-lD@ATc`^N{cD) zzrfk~{~#KAP0j<`DYhZBM`8no!5|W5VVsY5&%(Z-`M6wE{-2$W$8ba*Jgek-uk>~A zUQ(X<2XjgJ?N}P(2~mOjV<1V6D}i@x{_Rxouy(BDucE)2|7zgKSSA0}XmO>8Ts6aF zt_?kBb_7dTLZ#~oWBQ+he)Qd-eyB8E)KZdIFs{hCZRS*4yQUIxNPB` z99LtCxO;C3CrOCM>L)q|@G=?KrNk$sy&qlyClFBqxD-3uoJa3HTcjLrq_o%a=8h**Vt>bC zLXQbyfBKYP`a~#t47(}BRIzHM-h84>nHGM0b@kBvaEvErx%H>mTq<8;#l`Wr=qK1E zQ5&wl67|ov*I5wB<$ovF?BsUBGgbZ#8Gu;|P)}oEm+`W?%QR1g^6O_Tde6 zZNbCd_!Kj)IU`78YA@0dlVwM1e%SFM&fd7{m@#Kxd8;uCjjnJ))kXKclR0qr8AQ{w z%ba4I${8r8pBQmCI%}jsbrllMv+SJZ7)EGLH7k@g}F{pt`uh zFJZj6=5aF>&+x!}a|!)gu|;Jm28d6)DF-*h;20{mvnCgGz9Jo(ZFCY^VsHEnbx~hP zwBHqcnqP+YM=Il}fD(9jPIfdjIbmA=C5#jwx59{1XIi)!CQst$xahR1YkH}&I1mks zR?5PEN|)9D;>mws*#Sg!1>R1#*OrlP3b%6}Y_D_SLyoZGvE5xXSXnFP-2Zb^Y(?Mo zziEywSie`_$dQQN=Xi0v3Jq?m=@K}u?)~a9(j#z+!9n2@c($pzNuDaa0&x_~47XGC zjbX`fYujnJ_*Ysi{%@nRm-c5&Zvq7zR23VS;GqR(8aR+Ea)F!hnbc}Sa}BIEG(vNA zvR0Us<5p*`Q3hyQrqy`>)ABB$g!{1aeBc?b|BJ>3kP}5QtbNpS=pkC5@Ac{6Of*80 zFvQ*i$8HE)956Vv5O8R;<9+ocs}` zN&s81AiOqk$jF^CjT*-dnXgNM4Z(5Be`@{8dmjhCVgg+ciX0EPoqJZmtGb;+WrMw& zV+}A-mje@ujPO&ow&K7d2T?+r7E9d#ZAb;;PS~sv1&Lupj2@%-U`D zL_Cmo4~AKbzB&~ybHP9MPu8{bX@|ll*C!~NAl$^YF3y+jwa;K`K_OS(L&$v%ewt_r zX)ZLvZxA6VP7uRxtT{ylCC~~3+N7<<5QcCErQ9=wVRY@d1o2mlvgF{V>wvg=R;d}J3G?g!j!$SEw%8>ZWJ5uB0Z+xl>sG<@VyB)V8^>f@fn zR#*jK>DU9q$Mj+uS8P0lNEbGwqFkV$dw0dY=o`54v^H$7rKEKiJWZV1YY(yi3Z52Y zyRHKtWfi6FP-&XtEQNQ*OQ>iDcb;LEFxX3|)d91#gvP&D8`A^;0zs7?!V zBnle3QSdKb8S6}9mh%+c_RpE+$dj=>Rjk=A61Pj_ExzIUgx&OUe#G;rO@0K|BY5P6;{r{8K?94pR%rov`sShRYT! z=gW9k+*EZ@d32%d3LmyceChwLF$#H;&)q9IHNrYO%hRimV_j~(Op{TmE4g!q8oqBi z#;9$(o3E%I9COsfvJI5W$O7KVg?g&3Z`JHXKZ{YkP{vpzvQTIyrM^I9{#3lBlkdG= zkkPvNdZ&DJ-MwPVh7}>qovM3H(Wm~%K;~VMd#!zSBf7{W5m{Iv6pI(?GLI~j5!tn* zQJzCW4~P+&e}a{u9;zerN8l}w-mA-!&2_n4(0yrG#?o#hD9RB2>GJTuzkbxozmod) z*ejImFx9M+(~Y5nHrIm7)0FDaJhv!ePCp;9c(BXAF{}|5pS79ON&P$8Ixrw#Z7q5I z@FYPn8eI%NDCvEhsyU)G(BjZTKwS%V0g_Tm>TBB-oUCQb(1A49ov#Wq=ACv$M}nfh z;%P31+BYY5CC_QluY|#hkoA)Ezk<9o+%ErSBdhchl>1Dg{!8_wuk?SqfdTPF5I0s! z%d$FXSf^)+)ZE}=uUmrHHx)-kypCwE)aJhwvMuQvGG>G8LC7vjH0SHh94#4eFqGVw zS31`jnf3nYyL8p_&4H^>U!Swq;))xh8)u+7%dEAPdf$g7rC~86VXSsjOk<@7tDJolKgI>D2b%At=q_%&ZuWHg*ksfDWin5ao{|)sr6r+P{)nYD%Tge<& zLgu+qipGy}rr6>z!M5$T>xo@-ogX#fI=2BXD;GETUFWP5S~9PuOy-bcx7oXqg}M>h zh`Qx}H^}_YCqvD*(y(M%ZY08(>@`MT@R3JUZQL?h+Gcb*g%QtTHL+%mY#kl&s>7>}vbX{ZIsBeSA^k2ZxZ1$SodY^cnxXLr84S z$vsug{*ot=NZDEgB&TxfmScKwLup+G*yx=v&LvtNKeD zbrDbm=~24bS2@i#9S%txzq!-dtzl(xQT{*BuB2@X`~0)%*wxq1`@9Hg5kTt6gS?TrC4Ei3@wA z{$;r!5}m|v=Rvg4srW>gZZ1R#EMIszUq~&6Ls#I42R>q*SNCm(uhE)LbRz^MmPODy zRCO5DuH$Xi_(&A91@$mYg8O%C5&x7oS*P6Ow8Q@?5AN#ZjZ+psyWFNuf6AsF`;ARS ze``~{p0KG~FteWIflW+uzG?F78ZBmXAb>?xSL|D#r*U`%lqy@WsY(_+gfj7G%2T9CtHGZ&d@79O&a+_o2$jU;GWBLcXU-YMPE>+^&3 zPOS|_Uie{BhmDfvG^Kca0n%>SWTTAJ2b_?sa7M<)v@?O&kC$~x+T zQsl2qrO0E~OVPinl_E2(c`LrSOaQ6KMMo!5=s3dv;B;yUWo`Wc8>>dr93&ofB{;cx z11?MBe23%YG7Y%Is}k>UocE8bx{{YP$CzmbV@rjb>0G4}cN@VhMN!dhUCCa=O}3o1 z8mITSI`eZ;HVC1eRPmS0sP{c`p}*Jss4l;J2$62kk@DpW(3{sSsgWQnd!TsHsUXH_ z#Gu2z+k5*FNo{!X@T3nlcKddS^wI?+gCYegjHN5}2Zi?#q_`_c=eK(Qn51KU(sN0_ zSvO0y)Wf?1tGkM$dl@>Q+d`D2YQAJqUE~C@yC;N{)AKzc(Y+0in}^$5+nU;uf`<%m4)#RKO(i82-Uq2{kpze2|(q~ z_^lSv)v+vM$)2^VD~ybWW-WMslB8sHt&5WW!@Jf)EGi{GKzFU=H%Q;QyC=%Jb1mo* zBjqLObhY6mHR)c>YkR^-|7znv_7CECe@*!AW%F>-Z+^ky9n6#bJnQqPCIcVd!>;Wq z56LceNsmaYkDb>Y`GY=|d|o7ZpHUPqvIw2Wg5rqM=vPK`JI{o`m_;D3tZmb?#YWmzBG*=BQ>cKWnEGwN^QN&7f~D&u-o*hlBUAVY_#yU_Bl;0&ebwc41_Op z!nebhIgDcNnxEaJiZ-3!i=!H&KRp*(7Y=jP`&+-;;7QyBmUWj7(jm0&#K7QdK zZmIwa-}R$RkHZECP`@}Ix@q#sl&yRiAC91ziQ3g2ZvVOB4aNmbSI0q3T* zq)RlMD6?7FcuE5sFQ+u3H~I&Zx(`pu@;mH4=zhcL(?>oJQG#n^a{Y=dI~Z%^o2x1) zEr&S|=lojNJe*aAMzjI)Ip=ojmsuBO<3yLE=$PTC4qso-``Kuo;j-eofD|R~X}@Od zFG=%Nm5}k0RFm}SkWRf}yVMnm79o0pMReOg3g3?{v8M9oQ!~e*Z;r{8FA5|dBJvSLB6@! zLrv|ky#!e*rOLHmYXwZHN?k>fsK%mtQ(`mmwj|!xGd{nnuNmJ_l~c1?rbepT+U^S1 zih8gOwUhWwgq{)+`65^39W1WDrgE6qQbK46h-P~ERfCfB5T0v=;HX*=*^^Y$HC2)^b$ije*gXouqPl#SGv<+#N4lm^WNzP@)i7MS$=R0DsMh>u$cCUJ zMRIX?`VpOeE=%Q-zR=JaXhK1p&H#{m{z(q#S2ofpY9I@-?8DOE)Uex`wa&~!Q)d>q z@8PgDn(5m%)Lu)1s_MjKeHJ7Oe;`R;2Cg8@k=~(OMYwh%{uL>*Eg9CuM^!q0)>)O! zLMb_@=>19b$6B{T9+qTK5PRCV4weVrt7)rnh3Y6b&LK%6KG(_))ZnLe=|^PfDoFE< z+32dfQU`0~^Qvy&5#nSWzI$H80kU6Gtq&!r*Qebei8M8bsosaa_j;>sb1fl(9dgTS<9zNsw^V zMpSxXRVA*FR2k15_(*4o6pT^3^8j>sW66ls)=2jBj=+3T5}PX^JD_k>S7UcfI4a-J za;fB3YG3vlA!)Wi9zwDk4ZG3;tPj=h9Ex~VyIWF;%~y;ik^qUWbPR_Zt#O%i#sTxrC7HfYyr zPavh<2pfl}{td^k3xtg}`4r>(DS^n0j$ml7t$LO03I>f0b zZZ@G6-`Eh+s#DRlk5f14fchk?6>kJrn1-8H`~;xEV(O|BQQ6W>jl>nOhdVQDF4DDti=SSFcn z-4=beV7a(LpSX>NPwb`9gEXV(B(p~JgN9F>v1~RIcyfXli%RW}NrNkm?vsAR*7H_> z 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 V&jitq8+RVtDbGeeOW3P!{|gGZj&A?} From 9d0cd2bbf48b522c0322c7fc0479a12ab3f49b6b Mon Sep 17 00:00:00 2001 From: TiKevin83 Date: Mon, 17 Feb 2020 21:09:16 -0500 Subject: [PATCH 6/6] clean up some stray fields missing from the savestate system --- libgambatte/src/sound.cpp | 1 + libgambatte/src/video.cpp | 1 + libgambatte/src/video/ppu.cpp | 2 ++ output/dll/libgambatte.dll | Bin 185344 -> 185344 bytes 4 files changed, 4 insertions(+) diff --git a/libgambatte/src/sound.cpp b/libgambatte/src/sound.cpp index db98678b2e..e6db1c9d55 100644 --- a/libgambatte/src/sound.cpp +++ b/libgambatte/src/sound.cpp @@ -223,6 +223,7 @@ SYNCFUNC(PSG) SSS(ch3_); SSS(ch4_); NSS(lastUpdate_); + NSS(cycleCounter_); NSS(soVol_); NSS(rsum_); NSS(enabled_); diff --git a/libgambatte/src/video.cpp b/libgambatte/src/video.cpp index 732d5e4572..654c8bbd50 100644 --- a/libgambatte/src/video.cpp +++ b/libgambatte/src/video.cpp @@ -840,6 +840,7 @@ SYNCFUNC(LCD) { SSS(ppu_); NSS(dmgColorsRgb32_); + NSS(cgbColorsRgb32_); NSS(bgpData_); NSS(objpData_); SSS(eventTimes_); diff --git a/libgambatte/src/video/ppu.cpp b/libgambatte/src/video/ppu.cpp index 61a8e470db..23dffe870e 100644 --- a/libgambatte/src/video/ppu.cpp +++ b/libgambatte/src/video/ppu.cpp @@ -1859,6 +1859,7 @@ SYNCFUNC(PPU) NSS(p_.spwordList); NSS(p_.nextSprite); NSS(p_.currentSprite); + NSS(p_.layersMask); EBS(p_.nextCallPtr, 0); EVS(p_.nextCallPtr, &M2_Ly0::f0_, 1); @@ -1913,5 +1914,6 @@ SYNCFUNC(PPU) NSS(p_.endx); NSS(p_.cgb); + NSS(p_.cgbDmg); NSS(p_.weMaster); } diff --git a/output/dll/libgambatte.dll b/output/dll/libgambatte.dll index 07267f28e496988b7efd63f102486b458f60ffd0..fce190d42956962d2a912e7a7015ef721113bf7a 100644 GIT binary patch delta 25760 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 delta 25799 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