From 5a6ee3aadc2abce53f03416a2da3f5c14bb03d75 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 9 Oct 2017 11:36:55 -0700 Subject: [PATCH 001/152] Python: Fix debugger not properly attaching core --- src/platform/python/mgba/core.py | 5 ++++- src/platform/python/mgba/debugger.py | 2 +- src/platform/python/mgba/gb.py | 4 ++-- src/platform/python/mgba/gba.py | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index 837f4188f..84f59caa3 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -152,6 +152,9 @@ class Core(object): return GB(core) return Core(core) + def _load(self): + self._wasReset = True + def loadFile(self, path): return bool(lib.mCoreLoadFile(self._core, path.encode('UTF-8'))) @@ -193,7 +196,7 @@ class Core(object): def reset(self): self._core.reset(self._core) - self._wasReset = True + self._load() @needsReset @protected diff --git a/src/platform/python/mgba/debugger.py b/src/platform/python/mgba/debugger.py index 2d597491c..17f4d5017 100644 --- a/src/platform/python/mgba/debugger.py +++ b/src/platform/python/mgba/debugger.py @@ -41,7 +41,7 @@ class NativeDebugger(IRunner): self._native = native self._cbs = [] self._core = Core._detect(native.core) - self._core._wasReset = True + self._core._load() def pause(self): lib.mDebuggerEnter(self._native, lib.DEBUGGER_ENTER_MANUAL, ffi.NULL) diff --git a/src/platform/python/mgba/gb.py b/src/platform/python/mgba/gb.py index f70f9173f..47b9e09ff 100644 --- a/src/platform/python/mgba/gb.py +++ b/src/platform/python/mgba/gb.py @@ -36,8 +36,8 @@ class GB(Core): if self._wasReset: self._native.video.renderer.cache = ffi.NULL - def reset(self): - super(GB, self).reset() + def _load(self): + super(GB, self)._load() self.memory = GBMemory(self._core) def attachSIO(self, link): diff --git a/src/platform/python/mgba/gba.py b/src/platform/python/mgba/gba.py index bbe544683..29f233f99 100644 --- a/src/platform/python/mgba/gba.py +++ b/src/platform/python/mgba/gba.py @@ -44,8 +44,8 @@ class GBA(Core): if self._wasReset: self._native.video.renderer.cache = ffi.NULL - def reset(self): - super(GBA, self).reset() + def _load(self): + super(GBA, self)._load() self.memory = GBAMemory(self._core, self._native.memory.romSize) def attachSIO(self, link, mode=lib.SIO_MULTI): From 6b0847c4725c89f937f8107c28cc4fef611a85d4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 9 Oct 2017 11:37:25 -0700 Subject: [PATCH 002/152] Python: Add subscripting to root memory object --- src/platform/python/mgba/memory.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/python/mgba/memory.py b/src/platform/python/mgba/memory.py index c59f5a792..cb7fedef1 100644 --- a/src/platform/python/mgba/memory.py +++ b/src/platform/python/mgba/memory.py @@ -153,3 +153,9 @@ class Memory(object): new_results = [MemorySearchResult(self, lib.mCoreMemorySearchResultsGetPointer(results, i)) for i in range(lib.mCoreMemorySearchResultsSize(results))] lib.mCoreMemorySearchResultsDeinit(results) return new_results + + def __getitem__(self, address): + if isinstance(address, slice): + return bytearray(self.u8[address]) + else: + return self.u8[address] From 5fe6eb97ea87f46e38a4a283f2228f4cf506b3f3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 9 Oct 2017 11:41:02 -0700 Subject: [PATCH 003/152] Python: gamedata integration --- src/platform/python/mgba/core.py | 4 ++++ src/platform/python/mgba/gamedata.py | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/platform/python/mgba/gamedata.py diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index 84f59caa3..84dc80e05 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -258,6 +258,10 @@ class Core(object): def addFrameCallback(self, cb): self._callbacks.videoFrameEnded.append(cb) + @property + def crc32(self): + return self._native.romCrc32 + class ICoreOwner(object): def claim(self): raise NotImplementedError diff --git a/src/platform/python/mgba/gamedata.py b/src/platform/python/mgba/gamedata.py new file mode 100644 index 000000000..40eb16c04 --- /dev/null +++ b/src/platform/python/mgba/gamedata.py @@ -0,0 +1,22 @@ +# Copyright (c) 2013-2017 Jeffrey Pfau +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +try: + import mgba_gamedata +except ImportError: + pass + +def search(core): + crc32 = None + if hasattr(core, 'PLATFORM_GBA') and core.platform() == core.PLATFORM_GBA: + platform = 'GBA' + crc32 = core.crc32 + if hasattr(core, 'PLATFORM_GB') and core.platform() == core.PLATFORM_GB: + platform = 'GB' + crc32 = core.crc32 + cls = mgba_gamedata.registry.search(platform, {'crc32': crc32}) + if not cls: + return None + return cls(core.memory.u8) From 0225803b72f49828d5f65d2c553d86e404c6a341 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 10 Oct 2017 21:37:50 -0700 Subject: [PATCH 004/152] GBA Video: Don't mask out high bits of BLDY (fixes #899) --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index e6f5695e7..581f5c1ab 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Bugfixes: - ARM: Fix MSR when T bit is set - GB Serialize: Fix game title check - GB: Revamp IRQ handling based on new information + - GBA Video: Don't mask out high bits of BLDY (fixes mgba.io/i/899) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) From 8d9d644dfefa94686a16dd09b3b931eeabc907d8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 10 Oct 2017 22:30:02 -0700 Subject: [PATCH 005/152] GB Printer: Fix some edge cases (fixes #895) --- src/gb/sio/printer.c | 73 +++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/gb/sio/printer.c b/src/gb/sio/printer.c index 4bd1bf2ea..dd2f4ecc9 100644 --- a/src/gb/sio/printer.c +++ b/src/gb/sio/printer.c @@ -69,8 +69,8 @@ static void _processByte(struct GBPrinter* printer) { static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value) { struct GBPrinter* printer = (struct GBPrinter*) driver; if ((value & 0x81) == 0x81) { - switch (printer->next) { driver->p->pendingSB = 0; + switch (printer->next) { case GB_PRINTER_BYTE_MAGIC_0: if (printer->byte == 0x88) { printer->next = GB_PRINTER_BYTE_MAGIC_1; @@ -183,51 +183,54 @@ static uint8_t GBPrinterWriteSC(struct GBSIODriver* driver, uint8_t value) { } break; case GB_PRINTER_COMMAND_STATUS: - if (!printer->printWait) { - printer->status &= ~GB_PRINTER_STATUS_READY; - printer->status |= GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ; - if (printer->print) { - size_t y; - for (y = 0; y < printer->currentIndex / (2 * GB_VIDEO_HORIZONTAL_PIXELS); ++y) { - uint8_t lineBuffer[GB_VIDEO_HORIZONTAL_PIXELS * 2]; - uint8_t* buffer = &printer->buffer[sizeof(lineBuffer) * y]; - size_t i; - for (i = 0; i < sizeof(lineBuffer); i += 2) { - uint8_t ilo = buffer[i + 0x0]; - uint8_t ihi = buffer[i + 0x1]; - uint8_t olo = 0; - uint8_t ohi = 0; - olo |= ((ihi & 0x80) >> 0) | ((ilo & 0x80) >> 1); - olo |= ((ihi & 0x40) >> 1) | ((ilo & 0x40) >> 2); - olo |= ((ihi & 0x20) >> 2) | ((ilo & 0x20) >> 3); - olo |= ((ihi & 0x10) >> 3) | ((ilo & 0x10) >> 4); - ohi |= ((ihi & 0x08) << 4) | ((ilo & 0x08) << 3); - ohi |= ((ihi & 0x04) << 3) | ((ilo & 0x04) << 2); - ohi |= ((ihi & 0x02) << 2) | ((ilo & 0x02) << 1); - ohi |= ((ihi & 0x01) << 1) | ((ilo & 0x01) << 0); - lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) & ~1)] = olo; - lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) | 1)] = ohi; - } - memcpy(buffer, lineBuffer, sizeof(lineBuffer)); - } - printer->print(printer, printer->currentIndex * 4 / GB_VIDEO_HORIZONTAL_PIXELS, printer->buffer); - } - } - if (printer->printWait >= 0) { - --printer->printWait; - } default: break; } + driver->p->pendingSB = printer->status; printer->next = GB_PRINTER_BYTE_MAGIC_0; break; } + + if (!printer->printWait) { + printer->status &= ~GB_PRINTER_STATUS_READY; + printer->status |= GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ; + if (printer->print) { + size_t y; + for (y = 0; y < printer->currentIndex / (2 * GB_VIDEO_HORIZONTAL_PIXELS); ++y) { + uint8_t lineBuffer[GB_VIDEO_HORIZONTAL_PIXELS * 2]; + uint8_t* buffer = &printer->buffer[sizeof(lineBuffer) * y]; + size_t i; + for (i = 0; i < sizeof(lineBuffer); i += 2) { + uint8_t ilo = buffer[i + 0x0]; + uint8_t ihi = buffer[i + 0x1]; + uint8_t olo = 0; + uint8_t ohi = 0; + olo |= ((ihi & 0x80) >> 0) | ((ilo & 0x80) >> 1); + olo |= ((ihi & 0x40) >> 1) | ((ilo & 0x40) >> 2); + olo |= ((ihi & 0x20) >> 2) | ((ilo & 0x20) >> 3); + olo |= ((ihi & 0x10) >> 3) | ((ilo & 0x10) >> 4); + ohi |= ((ihi & 0x08) << 4) | ((ilo & 0x08) << 3); + ohi |= ((ihi & 0x04) << 3) | ((ilo & 0x04) << 2); + ohi |= ((ihi & 0x02) << 2) | ((ilo & 0x02) << 1); + ohi |= ((ihi & 0x01) << 1) | ((ilo & 0x01) << 0); + lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) & ~1)] = olo; + lineBuffer[(((i >> 1) & 0x7) * GB_VIDEO_HORIZONTAL_PIXELS / 4) + ((i >> 3) | 1)] = ohi; + } + memcpy(buffer, lineBuffer, sizeof(lineBuffer)); + } + printer->print(printer, printer->currentIndex * 4 / GB_VIDEO_HORIZONTAL_PIXELS, printer->buffer); + } + printer->printWait = -1; + } else if (printer->printWait > 0) { + --printer->printWait; + } + printer->byte = 0; } return value; } void GBPrinterDonePrinting(struct GBPrinter* printer) { - printer->status &= ~GB_PRINTER_STATUS_PRINTING; + printer->status &= ~(GB_PRINTER_STATUS_PRINTING | GB_PRINTER_STATUS_PRINT_REQ); } From 66ce1063d419624c5829a151b320faa9510ff6c1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 10 Oct 2017 22:30:28 -0700 Subject: [PATCH 006/152] Qt: Fix printing race conditions --- src/platform/qt/CoreController.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index d7020a2d5..0860e0866 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -640,6 +640,7 @@ void CoreController::attachPrinter() { } QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image)); }; + Interrupter interrupter(this); GBSIOSetDriver(&gb->sio, &m_printer.d.d); #endif } @@ -649,6 +650,7 @@ void CoreController::detachPrinter() { if (platform() != PLATFORM_GB) { return; } + Interrupter interrupter(this); GB* gb = static_cast(m_threadContext.core->board); GBPrinterDonePrinting(&m_printer.d); GBSIOSetDriver(&gb->sio, nullptr); @@ -660,6 +662,7 @@ void CoreController::endPrint() { if (platform() != PLATFORM_GB) { return; } + Interrupter interrupter(this); GBPrinterDonePrinting(&m_printer.d); #endif } From 17dac6486b4b0f20455548db1f55f37cc068f19e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 11 Oct 2017 09:13:43 -0700 Subject: [PATCH 007/152] GBA Video: Don't mask out high bits of BLDY (fixes #899) --- src/gba/renderers/video-software.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index bd8afc88e..ca20c3f05 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -295,7 +295,6 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender value &= 0x1F1F; break; case REG_BLDY: - value &= 0x1F; if (value > 0x10) { value = 0x10; } From b9ae98601627a5def753c056660dff5de5841397 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 11 Oct 2017 19:35:58 -0700 Subject: [PATCH 008/152] GBA Video: Force align 256-color tiles --- CHANGES | 1 + src/gba/renderers/software-obj.c | 2 +- .../gba/obj/unaligned-256/baseline_0000.png | Bin 0 -> 47688 bytes .../gba/obj/unaligned-256/baseline_0001.png | Bin 0 -> 47688 bytes .../gba/obj/unaligned-256/baseline_0002.png | Bin 0 -> 47779 bytes .../gba/obj/unaligned-256/baseline_0003.png | Bin 0 -> 47908 bytes .../tests/cinema/gba/obj/unaligned-256/test.mvl | Bin 0 -> 35105 bytes 7 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0000.png create mode 100644 src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0001.png create mode 100644 src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0002.png create mode 100644 src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0003.png create mode 100644 src/platform/python/tests/cinema/gba/obj/unaligned-256/test.mvl diff --git a/CHANGES b/CHANGES index 581f5c1ab..282ce64fa 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Bugfixes: - GB Serialize: Fix game title check - GB: Revamp IRQ handling based on new information - GBA Video: Don't mask out high bits of BLDY (fixes mgba.io/i/899) + - GBA Video: Force align 256-color tiles Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 6c2bb4287..a4bdcf228 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -148,7 +148,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re int32_t x = (uint32_t) GBAObjAttributesBGetX(sprite->b) << 23; x >>= 23; uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; - unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20; + unsigned charBase = (GBAObjAttributesCGetTile(sprite->c) & ~GBAObjAttributesAGet256Color(sprite->a)) * 0x20; if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) { return 0; } diff --git a/src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0000.png b/src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb480936328e4e2bf9144ecb805fb5f37ce821e GIT binary patch literal 47688 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

%kz+Vnd8 zJ%S^*lMJ1c`pWeNbwN9tD@Xy_op+354wozVwa3SXWd@eC(;6*U(fkd;X-9n1*TzAZwf&Z> zK}_)YqyBF2e{+eSyWR_$?|=LSIEXp$VYtb!w;ic~0&npLaIERLgZl+su3gazT#}Dl05!+$#J0W2ljEndx;~y>MK9+js^ClOmC@nFcJ7# zd}6!xAAoXk(oQ1~dIVwLY$4WH9vX(e;fwzg&NwYERjHLy%pE;~;Du(#Fqp%o);8~} zB50In>yA)5&|NkaPGAE&ju%Ul8T{wre|dH+=DrQs*|U=mer20c&Dg+GOH12-_sGQa zQK4iQ%?#=K0PyAy9y$H$4Zu=r=iKq+y>t(5L-P{3KWGG=f8aNc+=J#@h<|mX={Hsz zxigOaZFubylfXl-Y;QG!DaT%3wQuhsC;9XHwu=vszX(MPc>sWKj?#x7G^tQB$^D4+ z#eLn2+2%?a9n>nTHKL^Tay9tjIr|Kgp9E0VsI4ZM1KB^`@L1o6d}BY66R&8lX|y5G z_@1b6t6UVSU6}|{nw9Uj9E%U+$E(rFoBa*9$0mV*sE&VAvcDgOB$kov<%;=gu2^ZH0MCsQb6FCO3fUdjOpmg$k}aQT=0zX7lP z7n2do^ich$zhC!*-G7rf4a!c6_J*00P zG4<5d%A_-%KUQ=+%W(p6zy|4}^57vF`|dPMcPQzxuJ!<)F&p1232`k^#L?i?TkjS! z=IysV-QD(fM33WPoV58tva@yI2!@YPN5ki&Xuz9+SE{!j*j2e>M}FvFYICgf=bB|b zx}#l0ld!#J4CW^3K*#fegITvA+ox<-N)AqicfSiCPW)t**H(FPo~M)g0q4(NxR@ZO znexY%E<8P;F_3!kxKRW>-$$_i!K*FRdHP!C>1$6d9gjbcvv2o)V>FS~*yHE8_+;;G z#4?@!sX>jIOtOhaP9T=M9tOkkB{pX@%xS zJv(jxe*d}K(*@P?8sOAhx{z_UTANBK&Ge+)C%zGpzOq9|WBT?U-`3!1KAq?HtG8UW zef;Q=A3vA^mhX1n(JX6i@&(2{$43@))kmh}{DipT>-PXn7|Rb4YXYTgq@8z+Z!XU; znb246cF81CNG7`sFP?bj#eHyg^#xWhyf^^peF@gbiy!ABkM~hBjKq3e2fm+sskhDh zV40GTll&OsJ9{`h9qMz7Z$krp24QB9UW8_^|I4wRf(67C^xD7%BTiDmodrTllYpeY zxCSVdGI^NQJI%LOIOsB4{01FI;OwT}w53Lmpq0iGUnPOAhu$M&2-LIF7=7s2yV_Eg zN*UGi0AHzGz18T3tSG;bDF-$-N`*{ik%%rzg2&u&@zXh*HNkRc&lJa%!26fqt<6oz zxeqLOiWheA?uPQLB+#53)RR;4$8#cRtEv{8TDKU zVaqLM>_Gj;TV7eiY*A~LwU_U@q7Y@PhO>K0daRqPJrd2yd7&MiB0YRePrDl%o^-|= zN(tE3cg1FuI0snm$Lt=X2> z%(zB+T?ih$C#KrU=tF0xCaaoiN5^-fC;B2Bvur>%%7dAwp6b%}2o$w(NDw%`df~!= zt23O6*y7Un3+FDrc2U(DuTuqa_;`%_2g#X_g>ncWg5##4sZjF{ob>=&pbb< z+CdaBcu`6r(&ygK1nj2|9c?Y9kJi2Qf;%fZ>n*s10BsS7lzD3%u(Wk2$qXznGj)d9dt9 zZx0vi)mHWOP^LQGSDf8*rE;QNxB%odYz#deV8BD`QD+!CY31KF`n!9jyWMClwrYUZ z4u94mkT#x-=6?Z_ZAhb}K1pq;6w zofI2mo!RY{{r$~@nz|gnCwm2pQYjO2L8XQ2qWU}4uS5iZM_$=p8k;=v%+lwcFB*7V zlIOqtY|Wr~_vve6{fMlsa`feE9eQ~>Q(;Yw0`A15j1!Fml#I@2UO#{Ik^8)t_UAnt z+e=PzIBl2YAK#B9ulO3&&>{>@CKpgX+eW0c6Mxxw=*Ul-31Zo)y_ayP8a5LLP%34r zP}!&xP@XR)AG6EV;K@1p_pL7QEx3%XLP}0cHp+}}(%}Y0oS=5xWEL!&o!;2+43uvM zO2CPmEj>3PA?28wRzOX&a<}sdky^Re`PLm)R-^TJ$|Jhd(CAyWa%nb`NvGZL44ok% zKt~2U*b@Yn#V%=Q2Ad$of#s>050!;?EBj{PQK?m0-i~mO%@+-tr(6Vdy8v5WuM_M_ z)Rm~Ah5OO<-9vawW0NH2VJDuU^Yk?mEZ#mFnROnrP0cVRBaSyFfAhxD*pa>NrKH6y z(_}-}G^iYKjf?cUK!f4_sY4Rb5^RtKH zrZ?iT5Wf&mDrIyjQ?0h%eY+N7I{MQeSA!M7-!Dvj66GPx1KDL3cYPSs1;=jtdnqS@ zlxcaQTzO-qV9SCn>t4%O%Cv~i8Ci?~KnPM6m+uH4s#buvrOAgzyz;5U7NDR54gt97ncysGeR%%L%H3vjL*J@(^Y+04 zd671yvzt+;`dSc{warEiF(V^94MwA=g?qkYa)4!e=(+84=MD|9OdooA`{L2bk#dq> zeBOU6O1+IlbN`Gftg7ra{)5()9Y}Q{`ibt!_!0|1s(e?P@o&H_zt|V`_TNT2`mmJG ztm)QTwPh%K#P^Tpck0344(E#+Kq^;_#K^%s3=D8-@y7a0Y$K zKpVk_ukU8?-hN`zuuP5GbaMQG?>2UPm~`yLlATO`?E_6yV=6)k;lK*|Mn8FDqzf1H z`dguo#pMXJr9@Y{n9C~Gy864`dN0woN95l7xKgXy(qP&!LmWW~Q4%;)-OEEcy<~e> zNpQUp_!vMtE|3IttQk;Qtx>N#Y59P|-zw)91mD_`-`d(w{`ypiW((ecZ*-&ia^hDx*;gwJ^DWo`bU_j5Xt`+zwh4B+ zcUvp!UfVB~${Ln~gxE}k=867keLdno#Ro4vKON;OjU?b^!>fj2P!Gyx-16{ePxK<` z9-5Ux_XrzWqa8QCVWL4Af$0`*2G9K0eB!_Z4qrYL0{zMLZF4Lx%o(jc&AJ~1o?j|u z77q_bZXD(JS1y>)b7x^15+ zsma(blrnClKopL@rm9&i^$nB(bRHbRre?Jla`;}-b0oKJOKBAfuJ$ayRj=%n1?yxg zq^v+`SeN6j2Dm3>H%YzU8_ng?tXaKy&h6<#J5fQO{->k>t-?Hv87xNzBEqqaZP6#| zF0m&qyZqqz>u>Yz)fHXtlecQ&PT;k0C;HEE;H(eUH|88e0TT`MW+Ne8+>d7CMO^sk zE7vx4UnxFNO8A}-#H%-moNHTBM|Vz=hgA22B&G+@Y-Zj9F7BvB!SDKnL?yGPWtiz< z$MZv$OH^;$-#yPE?q~dy$-cfki3Ygotry%`<>B_vCj5lHcp)`g*C93UeX}bC$Id&M zzVS~iJO5@i0E|92(DaLQ0yk{G4Rakffa8|)&M<4jJbBX~95rTD`m1PCa%y&*&=;r{OkX%MW%uKiF0X z)~YQ{X7O;}uEX)~zqlUy8p4NCzw$f-!0oSJg>bhNNFDJ=O^uMmPhvF3e|>i#m>y*! z45IcP|1gv7tyKX|=4yd1ZpPx)W}JaM{LSLTwe7x{eccZn$P3ao6KRT48fwhnU{mbGS>wty5FoQeeRNinZS1X(45;VsXXzmgAqk}97(w=X1l`F(4 zhZ3x4Hhs_RG3B@m0#|pcId`++iJY6fAVRds7wXBOv!c>EVa9os&;5xp=`$3VU& z>8F#Y*28yy#k=mUJNXdY$v`}+hqG9+>wDL4%=o(?1byQl?J)%l0yk=yFo$=`FXo4u zxa}C1@qKwE@XQV#&I{RAKEf6PSe+`g>UD3*vZo83jk@W}(EY48v-RD?^Xs(3DIvCW zuwz1V8v;*2%|dUv=8>GW$v{_;A}Y9;ty!R1=(=XhM(VB|HGt_@R77GEI71nmBH7GC zfhFb5%DOB}XND_cQCj-6LqY3iSDEXzqPeX3Ms2eW|z?DYE(#1j=wP**pl*_zMdl`q8$iuwSUGD!97+F$XH*wDN_E;dS|+jT>sE^ zonBhXcE@F33{VoH0hX`_hR1{Dz$0M!4zC7wWg-Bu`rMBh(-q6>ZQm!JxyH2^rEnyE zqW2*-3wvP!n0>9XUT3=C*s&Lk7i7r`r0>gnT{nsnF8lggBbrSLa9mSTaUGY2=V=U@ z4Fk9vbtZ;rjG<#rn%~9k_`Q}-5qT`Lh^%IMEs_z6KivD}g5j^+u5DuqN2itRCp;D`QEGH_{0uw;TIH4R88d zh|tYA_10?Ii3g~V3;Sg%b}+b!$igEQpd)+--Y~3vWh1by0I(A4!2vSD^qM<+v?I<49KkC-*BXi2-fYG9UJ*tgTKl3Gn+0G)~K)R#_X(c73JEZdmk`hZvBe8TX2+|-ey&xf7($d}C0x$2( zd1n5b^E`L%x#xR7_ZzAH{v9qBB^CexxQYsLn$Pc(=Y_xkKR?@=Joy2j8DCLOTFX1D z`ynu%T0ejeEnw`!?*LS-;a0hCqRFqLQJ1-^KVETq3{0edoJk*am*H1Ko!ioOQO6Sq z>5E?aSvwVhecQ}Kj92&QYBah_KJ$ZE=#J_2cgAUcY;(3*D@8A3ta-V&uDfO5cianA z+{Efb7X71pxJ^fdV1800>Q(Ocl-GTOLnC+NY5D`x{zZ^Uyzon+9 zizyCB;JI!8F^KtsUc)Gg2H*y zf)T^|w$ok@;%$CcMzvLO)*b?1*GC_J|GC*LdVB9tkqyeZfY-t|DWce4^PxR%pvK%z zs-2mtWeMOLfd^18-reLJC4wvsxO$S-pC}{y!X@y3iqnsRmmR)rUXt~6%%AdXd~L^Q z1rzfEODL?oeom&ZMBbB=zw)S^9v~8ag}Z4txbZ6PTa?t0BI{R=PfAIuc-Ia*R~hK) zXi8`U3ZIr@`h|O|gDeq#I4{sMFf)fE2&i@-y?orm-%u__4@S}_*Bw4mKHTNss>Ph5 z$K&)Dz4`6$Np@2+L(_-G6+!`a*EsZ6YUo;PiCEZLJO;?QS>g(Ki$a0O7n{UzaQ_c*0>n z7vw+ndl{1+nC|dUM*ZaOWzsefCnD+5CDY8^pJGI}?T8P#*{S#cjmV-a`Syy|=5g`v z8uus{#Xl96~^>Uh`9)J{w9GT?p*#Fu|)Gy`xTJ6)n>SlQC(>_nolVofl2zjfI z{RX=jU|zXj=gJ*@xTP4+O2u53XQ9ZDYz=6eIh@jexH@w}Ss_go-;fyR$k0^ukJ)cF zy*a$EYN`Y2p;el67yjL}pnvneBWHr*KYJ>sR|gJE#%;TK#AN1e0mmJrcDt%z}r2v-LKX^uUIy;&|9YyGszDz1#C1yrDUg{)2g@bwVLc#ILQ9P z^v(!c31n21R&gAl9&Bp!qeAXYl*)4fGEvj!*hpuunHH@-zxJDts{W1mKiIczJq$Vg z4NP8_WY|7x^%ZooYsJZEa3IIKia^^m{n!baVit;|3-yg3IIAnJg_=4~JN zm{{42UWHOwokIQeSmJO15K0ULv+hLZ&@RO7fd@PmbMK!2;BjR8-_$|xMp&5?oi~;^ z^(x!RhRAryQ*B(JId~WtxJw3e7d-rO7`CcNzT^`+!90Hea6XZNj;4I4`Fll zC((i<20aVIZT5AYN?8>9b{1)gJl6wnN_AE}eKN7;P9H!oD+;45jjMG`Pl*T(y!9oM zDA_2Q=Sr)`d;4B7#xD9aZO-JZ4*DBwOyDP0P_d@OS#bK54 zwRqM0zb%(7m781A)MxP_5QO{y#tS%W39w^%5ViP&N+Egh&9ck&{8FKN$LYn?MPc8a z3zKK9f~pNjT;j3T``Z)Fdf<_ykKmjn&F6dY2?-2LdlGJ0#FQ^+zJ&Y%6Eg!nc^x7L zvJ3OXoh(0|nUV}2+0RtYQV!Q&W;nf8QdD9ExSI%-*m_!IP^bYBP7D3+e0h63ReMdD zez{D_HGtDzT3Vj{>;;A512O(oUKL*5QTiTUUg*ZWM$_xZ+Cdwiu=w7ke}VTZkn8dk zF34r{57-vAI0(AFVA{g%Yth!wZeO*Rpr!d=&!vNlHrHT1eFVnO9n3QO-Dx>Bz3+wL zsX?1IJ%>kEZ7WOqX+oNxWm{+faq4V#Yp&DTCzfn>3O~ugh1Yrcmu#kvAiQ;1+3>IN%XG9I9DtTbXT$oU``mZ6DdBxZy?X_N;t#_L9jsLSeSK2@ zEGe08Q$`GW!XW?1ng(0j-PyZ@xwBjB zmK_BRGza3QntblJDyo*Tyni$}(Om^220fBC?9o3n~{HhxsOgm z5TIf)Cz2h8(Ke~y33I}s2D!w36Fu*wKpKsxzZry7`fP}tlMNGCCAoar$`PtA9>X7@%W5@b9mA!aN^O%4 z{p)l5zO3HHpr_feYG%K|5~8leRv1h#azIlm6`2E=E%8zbQ&Gf`z@z>^Wtp|dHl=6i z&KO`rYfM6XTtva@pNAq+gMtfdcEKf185KiyqOU;~Y*tXzdp|m<1dru@-JbG$gSd)REpTEjN z_0AC1lgOGcKqxEPG?e=D8ojNoPx;aGaeJVy(%fICMDFpZz^*G+yV%#coc6h9Lq!Rx zsF11X8+i#9WMiM|Vft0?SCH-P-qbOKZp)A0rRe@g+8du0tQAloJm7|sE-T^QYJN#1 z5bN^-9GK!d4}vql9V zaFgkwWebs>secO!EEaU?4a%AX`~It2Bt(&(*{`gqP-MO~`0TsIMH(Fnm5H#@hifvE zY-i0K5bNb?5Emr2nT$%FoKFe^Y=tky^H9hEXy(q`7X$dl^mq06c%@>7FJmj;`#IHLQfD{z0j)kFaV@7PGErx<+ zo-eJn4%;eT*XJ1BC=&|x z?S&J5{2lP1FdxSp9#0qFK}VmN<$B=*`})1pJ8D{UcYN{voyyG}wh=BXdhT)M%hsvy6Lc@vgTQ4F*Fm<8aUngUcso z%DPhQzT=>}ymPKCVf6To?}HTG)>LY9i|tk~g~B2dL%9bzH8sa4!|V*=*0mh8oo-!6 zj-3|WSGj99Apv9T4b*-A?Q_?r1tzLD1)J#bem zoWHLf*(a?Q`<(63^vDrN$*^ty=P6V2K~R5Bx@v%Xjgqiq39Jsh0lxtPyh0!Ub30Mt z@t_?G`L9Th&3`{EvMW$fLIH{I-X@AxcbGCq^3smyoUFEvB$;w75HQn0Gs3yB{KK=B z3sQsGgYbTcP6eS**WRU&IceLst-0eqK-Z6fRXE>=Z*w6o_J>DWdvV(65PJWbm#xDj zJW=_F*@y32ARM#H%Pc2cU14q>uXAQv>!+O!X`uFoLv>LNFM!5&JRn7TxRO=QtQy5H z7!tqe+;^`130?k2v*B`6i@W=^wN2UN`RCH_4Z;m6&0~lxH>iqjg9`xruzCNEti+E9>K5W zCmfFmyI5>U(MxsE0ZhgSYASO8a9?RyG2XtUigliG`0Mz0p}A4W$k4>V+EFog{t}w+H(A42>B23f@twFNjfgjeoVjdPD0$#s{hgBcmJ0Ce|%7|_| zlBbpDNodybKq6-PU_o zV;7H()Ks&exqG|HFut!MxaB)?T}4fh&)=$l4B7~%4iAcGKJ5HA*Ba9vRs--yaDO-O zVayBQB-+E#2LeDp(SRMu=!nA9s5RYT#<-Vv3kF5E(cC^j)w%1-dI=a+-37VDV<+Ba-0W(_#b8C-EH4*X% zjPRP?B1t+|a!!!6MpnHLy_7$0ewa>KeRIQJv-8Y8dr8Vn%KqV9V~6KM(a1@8>g5m~ z5HUITAqGHHy^@HtA@;WlXb3oxMH)zVL?hhur_{@K*@!b{W-kp#Y#9EO3^Rx z+&u*$m;(#~2Xd@6!kgY?)7mkedcCmG%4XIK6Jne0ocP&Sz)BZ~ji;Gchx!T=ABxMpQtpAb1e&zv#a`x+*noyteUQv42r_3 z^dKoPwGuk3m`ONvt+}#2;t(UQ@?AsbM#{S*pA*q8R;@UxUqL*IQtZJNYvBt6+r^@0 zv{5MCAfRd;WP2p^g72?=6SXnVg}xN^7c6?FUCkbj9kC`lY+C4BSMn&ESsyG`LcOwh zrKQEWJUEu&O#(im*Ir-!)rk3bd~wnDgrXPW{)lUE8=1FvOn4u(Z%6H$sJ!8vRKEfv z50QOKBBO^~evbndGC`>;A*^wtHF}20U@yN_0i}ONn@eyR%Pe9#?Y+8NkyA_&heCuy;WHfyksu^yIJ08ueX{v$@Au zDfyh8;5vhqHySEpUK&|qQM2?Uau(!g@fpM@m4?gq^?8h$$RRXxKbx;$>QF;f)`!ZMVV3Za?1et6QTPWVtKadPJ}%XqrDz{=poEh zY02Y=SlowD!hJz)qy+{E%)R)dU=>)<2#r(9JG5}J!36|M>c%zC_;5V@(={c_HhPU= z;{qi8;nRW=UY-mv@ZTm6b%$G zEbr0+c|f#{9`pq(Dr6B@rn>T#7_1>yPE5BiX0+k@7)knSeTnFn>h>V+pRO8gnR0a- z`$iriiO+}o!p)aQTgdnFH%@H8>ZR>SrC`GA{6(@apmj#H`C8ttYY^xL`Rk%ED{^at zW%6@2nh>AbRO&y{RWZi5 zg7%w*YF9B+^~Gl{Grp1869UB43)N2hCN1k0!}%r!U+Y#tl_X8& zw`J2=+b+h51V7%OnizE>yMdordM_|mR{7A7d?#%s&TVf=arDlAVEugk*c7LC@e@7T zHBNzE!RccrTkYFU<_pFz`K$=?V4+|Z);ytF)iS-3ag}w?);QHDxVlPH9f$Rq1$|Go zW)ymswhAzQL#VQUkKh26PkE5Jmi{JJQAQ;Uouo8$`s=7yk5DxrPenu`P?dJ}=1bv; zv=f|b7|#lw6cv`IA~wAW6(SGG{mEZ(Szq5f+$$Y0U-M&u$t07tklZrGYFB{V`f$NG zEycikzO!6JLPF8?<~ZmW0&sz0yItri*mw|;?u|cSaQ)B^bbvjI@gXhWLQ5Jo{Yfgb z_4$0r=+pc}yJyI*7u{eT>vKU8%2$$u&gO`TMQjz>j(QQ~uA8rhUKa%rNeXspg792F z5#XCJ#hr>&yd(FUXLK=WcU;-#=~D7LD1G^PP_H3^86t1gMZVBRw2FtK>xRLc{Jy0@ zcXe7pE)NXYia_5qo7d197#ka#n%cx{PI8G&y+?cz#`IGR4JNGJGKx}o>B9O^|Fc`l zs5%>lM)_D&XqLUAlYr|t>v!E-wo44xEbVb(7!4r#hK9zJ)YOGTq13;@g$UL{LTLVP z_h>U*XACIS73BegbNTH1#Wh>nQtH2Tua-1`%`6ueCy<`!AE*(}Dw z1aEe^y;q}#%0&-vUf_=bl_sId9N7D3N6ob+FY4RO)YzM9YipaD`bBxV>V~jXfOvK$ z@tR_agm|D0CZZyWBaPC#^707$`XuCi5d`sHt%h7e&2JVQm^zds`Fm)AR|V1#A&(LS zAP`_$PT^UHFR#^QmHFeOHjLb)z)uXTa*KPeIEVd(+GAg^q%qBfAnNY+K}4Uz8cTIR z7DW&*R$#S%TZdXM1-t*&Jy9f_F-39%B`foo}LtDaf6(=M6?U|0nU zqW^hE%u;6Pf-g-J!$&jxo;GO4|9Ir{-NVYd*Y~i&NldO>TU3~GPL(zJioh@{Pi{}A zc-6_KeCxzI@7M6r#9D3wfHztekmT!n`L3or>KL$seG`}Jh#7^HA_yEQps*-jUXACK zG)**|`vvg8DY#&~WjCy|ZlbW17-xpr;;R9Uj;>o3e;Mj1s!1u1d_7$Vd*Z*ZHiastk`4XTl#ebmYxl~{3+);j*NcU zvre3DARJhZi$4+0=njy=_IftzG(X+BFbAU=QJI_5=0X5*ly2KZuD!bb(^<8wj4M;2 zAH4M!pS^MTWCBru>!ao4V&~VkkYYoRY0F_l)zNb>EsFHY{Ud{9ozsH;dls1w%wOAH zVx+o~J3oM{-dOy){7$s?3$&6i~r}o>ygY9-c{>ja&odsV`FQppH3oCwQF&k z+gUkwFsj@FNx z&*uMNRq#`15M9iPYgZ*YEj8pEiJ=3kG+a1qaGY%5@C*?O=9NNsW3@{F+tM`Xg_A`0 zFs2Q$7-3UKCRQ4h{<8hO$sk!iUCK4*miD5MDKzc-X8#qXwP-#7gd&a6|BD#Li`w%H z6XWtwj@Tds=#e6;Fm5(z@cMz=>xD1z#elMqcuqt702GFN3sG*Yu3c!Wu4!(lal1Y; z?$x6QkSLlTqxOq?39u?IgJK!C?q1Em(jdECw(^3XKNaI z*8R#dT|RrDj~U%eM^&nd&Is|jw=HjhX|bV(Q>Sc-DK*P|4O+tVaxIz&y~A_3Nd7gq zMVP6c+%C!k`F2nMjyZyCJ>co$FJaIJBqMJy!D!DiTg(z?3azDp41*Z199WJ75C;YD z)HCJ)DOYwwFQMM?cgbOK+BwFDBCliz*qKN=h$hf;j6yHL5-Yh9zv=`1mN{|_=M3$p zGY1_-O$RX%l-kMaM4p)`IrMlRaM2gPoJriEx9>M6D#=80Ih7Qwi!2KH`{yLB12Rp{ zJ&fqmuIii8iS|rM5XSEYM*7_i>5H3fwVEm_QoyhT^b3_o`AtNxKPq*o^kVThu%i>m z`DbGr)n5!?Y#;_LGORe9x%3bBZk^T=jUB`Tu?0xcZ!%C^uYPK-um( z7oYL)YH86R=8L{uZxjo3Y8}$Bm4XYYGas zT1l4K6)?nKre5hh)r?GdGgHOW{olPYXSoQgd71YycbzCl^kYfZ(+4Dpu$H4`y| zWuDcquC(n;4M)#N;(-doxb;uB1+R`S_Gl)Y%Mf9&%Um67MOKmql~EDkLvHnQtm(Hl zgV-6aMp!CA1{`*g&1LgmE3<>&wJ*e7Vh_uwucYwVVLXcA>Nsj38UauNNIrqSKyQ-yS5JRY&(P4=m>FL8`tY#gnap|Z>A1el_@=qzNdlXiA_2OHDtEzSR#)jN z=<0PlHWmK;2EzMlcYHZuD~L9Pc_ovNRz>a?E`R}3gP}*Dv%X=EQQEy0gVeziu9ive zV9CL`~Xl+9Lx2B3N2+)s}Sf+$v%GK+`ga*}% zabht`QU6&Ecc;?s7ziEQ^{#Qlc!$t@|NZsJoP;(Ze7849PEX^A*m^HO?9ob~yS>mS$%$ z{z=uLaI)&&K0XU}BK!cB44cQqkDNU}t)+l~-cG2E85#`z<&Covtr-4L(NPrkb8{)R z%KUarsP1jr<8__#6VWNUB=3KMGu~IqH6LR@5KU<-F+Uj3zhCzqAmFBVv-`o8Kv_Xr zN~MfdBnl6N2<{F(2(2;H=t8wx2|yt1ohxPzD{3#_kLBSScF5I4Mh{=y3cJ{b-u$8YRTi`aOQ$rXFIK-y=Lz zV$A{O;G*4Ot38Sm-Hly7TJj=Hs@u(UX@EYCkKYVyzV5gEiOE~ zks@@Tf1V^s)Bju-d4@bDnV-53{z2`(y#yi6VeiBYZI_WF-@~Oglain;=V+-I7!}h_ zdvZ=S3oI>JO0y_})BEf@F{vM2iBhSVcAaH?Ff+8{sfpCL@z{$bu~Z4Je!ly|mGoD$ zz3O{i?OXTr6itR{otf$B8S&9|nxiy4Sc+q_-9Hx4&`)3Pgl?D5M;q|4z^=q;vwK9K zea8Enz-3T& zVeZHs)F9Ovd~qKAE{dNpfVU6iX|E=*V@;KnO8i~t9eTm}x$#xRFD*6NoEw!D2{qe0 zoxMNjSiV*sIy@Y4Rb>q;mUaF!DQ>KI;|)qwf-rhCzURV%B`WFUW|G-=8U-ij9p6(m z3ez7*d{v`ELnzR*&A;(9EbvjbOPaBO>o=t`q3jYget}{Xe=EjBzO>w{RUqL1p6ss* z20wnrz+Y)TdVGA;96wwNK0|=Z4fL#`i8mO;xaX38x1>o-$VeW7V;4r1@}{KEQ?Nvh zX4>u_7wIuF8Z#R64S|2Zbp>OR%K(K_F?Dj*ZY(RaPAO)qj%+AIDobP+t?(ZUbbYCE?q`CsiC~GGJ-TB<8YOT za3S-Pmth#SFx4}hZk!w~;bwdxk&nyH5E&?BH#x=_4g&OGjZp|x8IJw^{mOl37ZeZdBm{H@*o zk(kmQi6fvU_oMG1UP~L_0>`48k2XWp9sMV1rvh0Z5E>b_Lww|q{S35G#@Dc`z4LMC zxu4xvxNg24ZPZKHSDywAWHY~`D$jaqj+f-BPiyzamt%u0i1JYKx(Rh!7EPWTS_@~d zq(oXrOv8La1R=t{*4uWKf)&8yzS>7}LEWGVo;dNy{{c@!kQ`x$5C3h_dFBD3YA z1KBDFqvY@6lCuD%G?bHvr{n4IUMIoBz}`L6+k{n1!A4L9WU*OJ9~2+r^?Omf?wFNv z_iobzf z^K&E7H{>KxERd8W3Swa!Rh6>pr1ku%KTL8#7%w6$BC@Kb>(W_8h|`9_JU=)ZoaPVP zJlix(pD$xWe37~GGA!tKk#Qevc`R-%ctI?ay|&?KXuM%&W>oQWZ`XT>OH(pe&E>$} z$-e?fBOz=b!?bmDbv>PFeue{ztX%RDBc#Ek2t@ME*_oSf(Uv+j=M2&fz$0EI5%o#e z`a&q5XW->M29~a&brPU_9xJ;DE_!%5`8x%=JWX#^T+vibf?Gh_o{5}d)hO{KC}1Z# z(y$rE#Y!xHRF8RUD9QrIf^a@n+$Jze8BFJy+HGVuIJS(Dc>ywb${%k&ssi%l172XNxc*r1jU^8QD*wBt}Yk@L)@x zs>;gpC8LTTN-c{=hZoLr!WS|pDIUt-L_3B74`D&>ra^MRC(l;2KlL!j7zu-T_?);q#1$FxN^9FiM{j$(i!G`V^O6 zsMed=3#3}lX99Ul{cF6#IAilG=f3n;c9BYnD^*%K`8`%g?;T#chGb`Fvl3n()j=>@ z81zZ3!vHzGC}ja^WcY~Jz5v6KR8?q0LqjnA3YT*T2m#092AyC**VO5Er0NjblzGXS zv6&=gZmzCBT3cJ&8`^8z?DWkWqs^$;L-(7XYhmo$U-wIyXAp|D<_&9anxR+el)<`n2;Ua^<5GphIH9nDg|JPtel%#v z{3F73xcdb%0?MJaRSj^G??C>;Az0saupIK~mz@UKQHj<6B`Ez3R>IYQi7@C64Nlth z3+bDjwX9g`kOlD z&*Dvqj7YNE&t-We=Js!G3DB5#>0HBeA5_Msi^lxP!>+zG0s7+@<_j$Sc#sI8tVMX1c-IMO#mPe|)M?d!c?o`hMxI?M~<6=-$hJ zdD-F6LyUG?`aVwOXm4bLp10R1b!zds;h^bTWpHU+=pw2wZaB!Q2@*Vb3q`A_Px1Oy zCcL-sdO@kwxoV&8rp-inXj8!9tj@l|exbo>0gL@4t^GGtJRiua4|5e=jfL5^WZG;&=pUjaLX?!*klUw{z2HEDTevePQt|U9R9LOes|+ zK^J3Hz^s^AOrFy2`;Xkx$gOI#L`uJFuX^cfqybGBs7MQ;w-q$hHu2EcG|c$%P$PT( zWlqoG2iBxODTTWxod2EmP}z4o{!9%SLH~3tEx`{&7VGstsEMCBGJdF@TuQ|e6 zc2a>{D|TNQBF#|gG`Umiajqa zSZYt$4GD17du?QBI`~1d*<;4ju@F9U%1EFeR2Vv8=7fIuJ62~#H%XO}&7g$~w_Uu9 zQF2A8xcsxu)CBR!kaZ~Zuv}|bOj|lqJye{yO@YWVGeH!0vwZV}SA}2vMSrJaEHqj{ z8BE{wk%yTXSfm6D3POpD7r%{XiQ^@0QLKASIoIp71v~|SL^^j=i3KLdpf{ZubmI59 zFggQ>fy2(D#%X6UKJc|`Z`A)0yFg@Wi5T$`krj3q12?`lUNJ^M{CJaZ#c+3qiWnw7 z^=bt2Y7UTJ#42z0^b9_=MBQ#bv$Na(d!HTcg~!G~Q>}lES}>*9n>al84L$li;aV9L z8f?kM)i8&nZRYk+I0Yr(t{BDpa9R*F7UugMDuQ>K`Qc zQ-8M*p5c;?NA0|=K+T(H5?L?77%`ndky#Aq$I8dsRbc0+$AbN!njaV;c4whD;Fm9! zl>8VjuYRp#?sOR72DccG4O#T60LKW@js@JH+sW;g|6!-W8``%nu-zz0xeQb;TR z@ho*7o;77&4Oh&k$a9USCu~~@6N;mnuMnu|_tO(dlc!Cq_2(IHI;TfFSp>i+0@0>na%WJFj{nHYFAmGoDxXphKl!evXF!9hP|OXTuO+;C2)p3Sy)$;hs13BZvu6HxfYfg02PsrrOQ#lp7}_Mul7 z7l7{S#1*}X|FfhX(x0z#xTg06_yuXuO za!MZHx>Or#@J%3iYSzXmb#zO`j3f0z>1#hd`-J7XMX&J*$Ce|URaP`>_sI00zoV(d zt5RUH>yLVt*w~%&Yg7G|YDOsRc>v8ej}}B5&4m*NFuh&Ae9?BMZPS{d z#qQL9dEXm8VP8?|k9{i?LQjqtuCL!=r61?t>=fqd>)olIJx zUv_B8hIO*$9J9d1<*^*}*;U@)pstz3u55K*KpZWv#8w@lIHVwF(znjluD!?zM=}2q zy*O2`y--~g*(yUzdnWsYoOzR?J`jq?2(81Iu@f+?aBigfiz$%#WdHfn#PwL%w2j>i zMJmVd-_>6qfJf~OH{z!JYxoO*71E*Cn>y*g%nf>(D+SQuoV5Ix=vJT_w+#Wzh&Ow> zd&~hzztDGK1hiqm-zt87<#%L{+`x(=CDFK!4-H$O`#|A@BxHCRJ0co5Az7h38AEoc z|2zTKx_*17U-7(*-dm4IS&pKWyx%wd7TP>CHL9m)@o?3=V&U*vS&~bs#4e*>2S=3O zU#TSL=J!zP%m<^D=@}Qn>nq1-yPAN;qiK7=?5GjuZHXn^=Jw+jj!HZfUz3;AMVZCw zd(D^6QBc*YEZFlT>QjJXr2BTTz4rHDR0C!|)uoFgphkG%+L?3Xwph z@@tK@=D$5p4KhU?Kn7OB&X>EAuwPjInMvv{mXyjOCRHM=@=39N+BQ?Wr|P}41WWZm}>dh3mD43RYcwW&JL(dPW&FL{%T#ed)a`|dT3p= z>0{_6oX3va=!tb?AoIBEvs5u`=uqasLS-oLGtuHzRuPGLZBWrLQ)q3=XW(8w%(YiD z5YMR}3dy(*0mT=fON9owqrbk-d4IJ$J3DLZARfoPUQDsxZ+Tfbz z=82<*L*@bY?>!3j*7`cyGf-QG6J~rzD&Lv!c=>TvfC3wrJ&hjvo_Y|=G(GcnA!Uj} zwSaDnT4K*%X7XMmYO-)OEdt~?lyxn9RP#sAPLDU`*Hc+ZIw|6}(I3-N2;%U2=4^ZA zri3-!+jD(UF0HJO2y-M)jaS%DBmvVq2CZFG3-LYt_XCAqN?{Hc1AQni@c+!;<3qIu zdVXsqKE3DE^e9_&CPO~1qP=hmX3!OmUwM&RVF~_Go(Jub!j;zy3sq3)e^w>*5jP{& zi5bf{B=f~Jv2L{UmM*l@Zk?M@jobC@(QPCjVQUL)y| zr~y?f!TKTk@yOZT!zp?8^2SB27v(PA^&x834Vq{M9mYmdA+ zpd%3^ln*lEF#FyH!rc)u#_OHLJ5fuj0dT~#`tF(GIja~v)R0&uh98wXR7`dbVnR8A zO{`@ohsaMVX$Y20P!Wl9DJcVbJ5!dBjnQZ&5}&O^ons?-yjQ)0NQhGcK}v6iK7pU9 zx6nXg7p@p+984=J;oM;y#}9EywCMROZf)6Hvf2;>Kxg2E zpH%lnhA!PlgMq9`&ejWSCo=1ISHZZGJ(RGRp9bs!Np3*iM~Mk7XvKSSEaD13H%^dL zv{}L8sf772#t4ivJi|OG)Futxu|j;3jFd~JAv=BBFQjbDa;0A{Yp2tWa&i6x4>60L zZx8;8ez3U4P+;Jr!TCCQ9EbK|P^w?bkv`7cFGFXzAYN@@=8PgW>CU^21o<@6-Q%)~ znICxWl%(;G)AizQqMyVt@(sh^b-f@(wReeSQRGEfql)C}^cXZ~c{i(UN&nf8*qfaC zLuIJ@SY{^$gR_|WT#+d30y71{cIgGGTtgf3EI(cgiQ*8TCA#Korcj+=u_B0r0d8GV z`YwvFg?dUq#FYJF+vk~zmc!3Z4<I;ea=~ zPArUrO4AHm0gC`gBb7myN#qt)2c|+lVa?lukgSuKkpOtEp0$;JE|%UU80+ibA0!A4 zg2?yK+WnItdZ?zNJ<(k9*lpW-KJk`-XnjUYMscMB+=lvgC=w6ea$92SOC>H^kbQdSUdi zQqci|l@EWseIjLM=}EPzBT$_GfXupVhMR(MGxopQkzhu^C-}M83Wpt|Ij@`1h4)+^ zCa)PpY2;E-!(eJ^*js|JNm_X;`FbiU*h;pzQ=FdW0Ph!6VpGQIf&}(mrH-_=bjvL% z9E+7(pdd6nY}_cwNG!3#qxv5iTRYapc8sLvub~0@P?ha7hZwT&hC9 zoohbvXbntTq!$!X7bUOW!MAP7Y*^B`wWcovHI)V#HSt6XC;v32>!Ae2R^o@VjfdG3 zQS6uCgsU^ymyrh1Da>W&->?A3yV_*qN6|CxG7x}0C~=&k>FSRR4}F4q3ThCA()=d^ zp;Zs@?S>B(+UJUsBv})@B>U`p0A8u!8hWYs+Y%*|c-kiOGAw$a&?u;w>JH#cbj5W5 zQ?jX>M`&`dU=`BS2kUjOWj&)5gC1CtU~vYd69XWzgFsl|^5a+IhlE`pVdCN9asq!u zoArsaYogGQ!JzeGJp<}DJ+1<8ZeR4?-sWAlvc7$FlQ#tv6zo6U_x}rw!5{Ueog}>w z9}D!q+mRW-GzQZG_>&v4XGA!;A*9I4pu`WXeWEH3qS(X#2zY5Z0gP=-GL_J|&AD`S zY?&@lsbKJ_YD6;XC(II7>q-!)NGU-v8S}wWMx?=VGvYq1CcF#%Jq0pf7Kk6f-Bj1Y zv-a%0UOfyx<^V3W*PPk&2#G9Y>el^yJx2<_H>4_bxbK>zghNFg9to&9QY>(cWC+nR zvFkZGuwW=(hS;%Cb7!bA=#&`FZ>+NB8tlA+@k)}E!~`)Fn=_!2mT%%`=Vdk+h%L3T zpWy~>qxFJk$f#tr&qEV{c{Am z?@Dnjh9FXO7@JgIH8p|5&9t#Go#t+FqA^pnfH(5BsQVQK*psStInaC1B)rvp>V zbuIC$K!M)r4D7*3`$g~<`xIs|1~~u+Pg{qE@!e~wJU>>K$mZBMCIU$ak-_9_L-N;9 zeI{P!VtNdEE_u}ID5lUDl@`8$ z&=j2`ZV_V;b=wwpVz|WaPbGci41%8jgubD2^ebB|I0927t}K!)5C$4DnXM~HUFBeU zUOJ`3+#WTrFW<-}6b|w>WTc9D@OH4B#~lmwJN>jxI6RN2Wq@3nC+?Mz1P8@1j8LFK zF-(~mE%;r58I}PwWmgjSPTD1iOv*p_vtJIv-irq*60%%}8>Tu2ieJ)u?KgzAnajJA zB&8qMpj6D1!n_ZUzrz865KmPW2$2aCJZJb#l*S3Juz2iu4FQ6@Mi82iDJ?fhn6dAd zQ7l%ct;D-t=dU_zXorX(V!`v|N)^jv=dS}rD2{;;$4yvd%GM@?X$s zTcT3fP@&T3SzXruf!M9W>|U1a^bHTrCRn+^1=^2!@lJvYuQc&S&Sba~=od*@QA>g) zzvjWg!F8(C;4533KplY8RO;N@-RdhIRjHx`ok#Dns60E%qj`Y*cpz^e{# zIvps|ILWUP#?XgYP7cG#xZ>tS0+0G0+mD6RVL%(9_E))Ap%0kT1BD7%forfacMxaR z_6D`T5Ggt;O+ZLdl5{=@ zy2hgCQ7T0y#*jdgZ3*z%!z)X_$mxrHA!K2Sbn?O?lGQODQBMH#j7`e^oXqkaN2C(L zEog1G%urD=s;zGWgebuOXjf2a7{Fv!dH;8-`y)JnhLmCVN`ZM0otYR*4EL@EXxJ8& z&9cyt%kE3gq!X)x(;}lQVWU33Uj5!#&jejcqu@mK|9-6%%;D?9sittplN;pofHCti z)AQ}P3kCUo8dBJQ^!=H|8OhbF#ziSxmq`+}L>^C-_cBD4JW2tnA8;@&WS)R%rE%0_ zAdOgULSvp!>S0t>LL;~)4zLv5hBbMJBA>>v2-C2W1X^FG`Wr9`3Uq2L)16`3MKnb( zifQtd6fUg>9K-bLiNy45$(B{}lPpiiWH$_o=!=GgJe448{TQa(ps428@rxT)%>S}$ zb$~F?#8}Ey5ZTWSJrfZb_R+y5-^U?9@ctvIdQ;nZSKKw+;Ba!qKC6`UKT3+d!HX-9 zq-AC6!dei}W!C>QXK%m3VZQ|z>XOGZa3@}!IF8d66$A}9`KyR3}e-sH}w zl&P;QR=6G~Q;dV`M`axP+DV+7I?e)ityK>sF%>VwCU^s52{{R6H#VoMgQIkGkh*&) z2ut2i_Apn`;4p{O4f%xzHyutMPxn$rP`%uWgPT4--Ob9k=S9#xMhK+()hky`9o%l7 z9v=ZxOQCbnGbd%d zg!l68j7ZagyMf*)JzY2-@-m347cwYAY4(o&SIc;?)HX$z#gBjpI!Nd^!Ix8bZd}8v z2Tj2EyHuCX_jv|GUJx_jHbG5{k(?0xouSF)lN*Kvhgiiz1rIA~&&kQjugt*OkfcJ$ zc|x@x_`-_dWJM2yhOpxBmqW69z8N)+n9@- zmk|z2g4Hd_OqmS6sAE`(9`aXMQKP z!`WgaG-HgxMV3!?fH~mv=OIQvHi2@w%kVw|oXg(=D2RlRTO%9f4hJ0&yaL>B+KiC5 zUvu4zK2R=nag&e)G6|oI5OsO{CrkWmm1_zs*fE@yQ$J7Yx7X59fWB;NlySpDBJ1;a@n=pGX$d=A+SW|O#+WbuX>_$Yorpa$QVrHv7Eb<8@yKSH}tgG=aWfr6z zZU<;GDIsLS^Iau69vvKIJ4+Zm!tpbTIorY&urpB?cdayCIJ+e7n>(fP(9kY#EW`l5 zk|YrM9@9jXny%(nOQ(MNqB-D)Ofj~nCfs)#DJS9$f<(KUZNR~>?$0zS-~ug|5Dk;g zO!FBp((cfymO~3a{vocjm@OF95_Ze;*wE=zNKHus{)gs`GlGAEi#58suP+Y}ytsE; z#8?d;i)NFYY2|0)!82Iw9b%(tBGh6C;iF!>ZaZjy(PHft8I*tCy`1$OrRpX57xtF< zMsXMW85u@MqEsb5|2ga#C^9&Kf2*m#mWv|cwAKd6Q!@FPnlsg^@mp)}GretH`jGu# zEwM@vaZ&q$B^b1sLAgM5={;QlNg4_cUe+@zj>(5s>ysxVzY6uR`<%$Ir$2NGGGoKI zN$Po^e@vJs#6p1f>^CXZ>=6q^q&8jhG2LQ`Of!f%wXfby4v@<0qteP-BAewQf{pyv ze$Az1Umi{@*Ue5_#@Wk_df~QEygIpY7Uj~UQMIvVVFEG_(lQGaD*R7AZ42TC~w&6dkzH->PFaA16b(2s)AnlH&5CPp8MD85b6;-G-Q zJQNZhD+#JDQ!^R!T-+S3(PjJ)xF9ILE)*6Rn>*yG;KYT{`y*6Rk?1S_hGECdzz7-R zFN}=p<%WEux6^m{B0y)Mx_yCWFvi3mwOe|y&zQZj7GS~}z`&~TmP=2efI;su@jwH& z-@hk;byJ($MX?HzPMp$+&WlSN9U<`W^fL#_J69M1Px7FC;je|N6G3!{%!X%kf@#(g zR*Lc|8Q2B(ULS&8e4iE$bAB)yp>B3@T%hfPa5l8M5!_D=>>Xt~u+8IA=04{b;if)Ptx$dPwIFTd>e^JBWEY2+=w_@8q zN&r(X{+qMeA7z{hFWg;yLV;-iBodw*=YDQw?P)V&8Z{U$E96l`t`TkqB?c-qy)+j! zg6Z&Lp}`W+J3L=;3ImRFx;4Of7*Jt4Kwa>>JSn*}w@67v;vi|0cz7ReQV9b@~Zs znMUPFz-7@IX%?-)rz2?O5=|A*Ykr1K)KNYwg31z^%Jj;$MWaTc&erOxi?(Y3Wrs%9 zQwKdJ5>4X5#R}?9mZJ&l$sAbiik`&J94i>U%(0@B0Q5gsDu zf$$(?ND`@Ql{#PNc~h*gdzfSZnXcGdRvW8tR+~~sVze35{2SfCr9vJpR3z!qi^mU~ z`98Dj4kf_EI7SHtLIcryM8U6jMD-50D7mTO;_3G?C9KZjqQhLX27Z#*nHiz_nHX6N z`6b&Vnq?ZUi?g~2@DP3|tCSqK7`~?NO<_(|&P z(fS*#Fvb2{I%ZcGHb<8s)fg$~uEzHY&lV&ptEvjIs+rZb(^c&qB#nFqN7ZvPngvlO znUj&VR3yPV0bD%Xeh?`lv>z6qkBJVy2am_$?Bz%vFBcvjkg=v%!6eR)hf18BATNoG z%j|<2y9puY2rX!?B5S_0Wn|m|J4!>QH5U(QeG|zqt4wu$p zH*9~|SN)^8RC6)IC(~ad^_r{{gfaOT1uU!xXd$eUTr<(L%cUHvNVQIzDSN^(Ix$E) zlhRcPCR%uCFG<1@USe!Vc6^O{-fH3bb&DzuO9NG!CGq*}YbY}YCt=#AffkZWzBFg{ zpOR=;+n<_K+?dAk%NLYh1RF0JwU?X;#it}9BsDQTovu;FSuVV`bSWx9m_KPGHWu`- z>&O#9bQMA5n231Ac zn7u8%#Bn;!05QUUxZWV0EuNtq5RdUUY3J3VSpI!x&L^t%iM#VfDddX+shBTlTc0Gj zITH2~7HF-5WI;M~^pm(!$g@X!{Dx0+j~a;jNX(5v_~vX zNxyGrc6?Creu=^wAs>K?W1=uygnqsEzUTfuceJdc7?aFhR6pNuPKF7&tt}je2>I!H zx+(->A~ znB&!f0}dY^i*)sC1(hGXFbz%_s~t?fpw|4UoxX@nSF;f1Z96dtC?G^?1|uK9EodNzBGn1U(BZbWoi> z!WD5i?|P3^Zhnd@0o?JK8OH~qt$a}Le_ab#4<{9bm$L5&{)u%>`ml`AQm&%?ptmE2 zPftq*e6B*&3&6tNTs~Vzu@{qo@l%I@yP_a0@zCb~mHnk`4e<{Y(;yofx1kH^tDx8% z#HV)uyl|Ular!n#*O$Rj8V+-Bk^LuO2vLlJA8p8*?IW<5AbTrQZ*OaB@2U-(2M=vM zZC@ety4fF0Hs3z=RDT5&m7qAtlmzW^q*dv;c>tD7=zxX3^kRz3koOen36MAI%_S$q zlGhdn;VE~3`kLHu560{)P^pr(M>DW27GgRiL=lda7^*eN^C+hYtaznA8AK;>uwJJe z24y8QAE9DPn9KP!dzTSHdCQm@UCgqk#9_?NW93Vdvos+zUaeQkAK3F?D!Fkg}>y z=ChWVf{{8lC%0dP@}2jVvvwx^#U_uM=g0Uvl)Uo7zM8#!W74+dx1YZOS`K8xVPjzv zXi({I~YHQ(b-kgAy&G<(#C<1;2o7c zae{(+FhJdq-H;$QX_r77uIXg1r!(CI1}vdSco7&s#gn@ZjmDCyn@U)KKkh^K<)IRg zkdci(AW_9G915hxsv3)hEgVqn4ZEapc&F{b0exlXeeSX6_r7g~1Ogc_L%gYAa1CRc z(8S&>Dfj18cW|M;s+)I_GWl@_9KqAL6~!_pNflm6vi=+iRA(G1-fD}T$}Y4Li_#Mg zK`au_T0Qp3^vK@pg_8f;cy@4qeSN)ne=s@$x#Ji^mX2KxpwyQqkqC^aQdf6 zMW}fFmGi#Urn(b3el9_@`=cTZi5QnCpNNHy6CXM)G|`*mD^{%rvn>x!PYDMJBd+Fw z7$yRJAjc#^-prV2t8Bn@P)I$YSLMWn)OSH~NP+PRG)Xb!*vG$nk4h2h6d8x?PX>K5 z&RrEims%9^lhavp%S*RzOR}TDP*w(uVx8X3Nv&Ld5*2^k-R_NNU}j9NQvd0V-Jecf zeR_~QV#k%`o;F_W$Rz$m%ypO~k0(iN*EDXm1@y(D#SFczy&ndvJ7EtwQkJ9U1+pX5 zA%ycz3m3pmBrgB<+rPV(|KL z*ua#6&fQ)uMZHgEd(QF4hU*`4>ab$65(FXOm0Q#IUW0>wkjz;C3Fg3h{&S>OA4n=n zb?-&c!{`wsmyD*~`I_!c4id$r5hPMGF>|T!^<%HwVf8mXwFqQQb~;ysBuJoSbn%K( zz>afqra<4q(83{aWlMiYOcpsS)~$sKJ>_ zbI?Hlbuh4%ACxUqFn>uivXC_7h6@D?-z$j+_P-I2HDYNUZ{UPIaW;dsJ8nLA?J@&X zQ0KRy3LhfCJegUC(G^G`G-`)y+X3asFb4M}YDDtiw%_lYo1Xqe|JFCczu4ET9K2lG zl4ml-IcXO`_I#xev6h7_f8=5fl$}O~faWGDEIQbgc`piDTtAr@oB;R;n8ue)hSR#n zg+^=*A#|$?tDj(QG>i;S6V0X{l6`ka?m3sD%1ZPG?(I6;u&2#S2pk{@3Mc{GpCe_m zwNZ+R3_qkMHUOqR8t_ZkBnBbKAxS%Eft#$6sDmiA#x@RLgwW?t?^Y8|*(n1fL6DtV zm3}5qD^WDxtic-eq|H!2e`Dpr=vl#G|Lyy&kldd?S`nlCS5VVZ-{HCc`t2`5pCRaR ziN9Ald&sJ1_z6u6j8y_@I^%dZC3|U^8dZ}L^(I7EI)X_l7;*pur78!?%Prt0K@Rs9 z-W(J}G%nKB#lwQ9p1kFI@aLS`iDv09)PML6Q7?}|7FFa7RrBNb`;QM#s~tzVxVr1P zFBA2jgO94`z|j(*ltLOoO7e{yWZu~`8Z6;y=3P;@KAYv}T6^2w&Fj@`;1_!oZUO+v z68EL|B#a`B*;u`kef;U7Wn;!STmbp))!FXTZc2q{$zQW*0%&$8KN%r&jl0f+oFv7c z&l>T@IMx%&;vy&PooDAwO(bEY2RZ7ZGr>p!yh#@suHO5$@$_&pr){1aod5`I{)>Il zw#M`!q4u%dXvhd}MCCV@iwF*b@hx_{bF;InrKzQ_t-!Vr=8w;>l6Z}&VIg3!V6fH~ z7>{p&XD_Nw#WMDWlsC@9G$0Q$8~cQ@{LGmt#LCt@8{Cs`!dwaxtV7<#-C;Ju8%w>M z68aDw<}E=A+GDPAM>E{^Wag3EN%9lY?S|Eefh|lB`|W=nu0Sl#>d>9_^8BD{PUpjv zf!Yr0X`HvoLI2Z}2@T%yecb(17$2Z8m=~+)K4~BY_|mxsX#Jk32eLlimiqz(S9*Ls zp0plJT+``8d2drXGMjC6x_vgxom>~G(NBJ84t@RnQn9|-(%Jdj8^F{TwWTOuR%tnk z*>)3M2&xUgXWTJGe2K;omiY9;Lh;P}9!mEnMYFG7=DXY;VUWjnoN79D88Dv=cXiPy zYlNxNBglySVWF!Awe3jRQW;bfX;PS6TQBZoB-S>ZFo8H_bo;FSfchv@x$HnFp4fc& zCY*5_PgOuI8H#K2HEyVLmuq#aKmg%S?Wr^OAyLhrxo22K|IM68=6Q>XgFHyPa1AUn z%8hDHLex(B3)pTUnKn$iw<=Wz$_Y`AT4kmd^8a*c@3|Db) zcNL$EVn9?$Qb>X$Js;Qmj;Atzf$|+e&{Pq0sW+diZc?yt^UD_GH@?%_M`FpAI_kE% z^wutxpcj1y9Rh>&NNi_Cv2?DfhF;pH{?T;_T^B3`MS3^&Iq^soCL3uH!Pqer$exK1 zHCGTOKxx*fS~zuHs|zGOYV>HflfprPmz#!dq~%B)7r1TTut{|emysT8_(h(s7NQ9M zDO_S`4Ofl6VkJe!I>V2VLTLVsssI`XyD{ubkW4E~lGc?fXkE8Uj;bymNna^}Naudy zE#{vD1&~ynyd|5(zxAiTpqy3qlSRfi_gXguSfir5hN4sP#hlxXZgO5E!;XLoZ;ix^pVYkH%$E%xqDb zTnaz>cPLO`D~wWoKo^Z5o%>RJ!9VotLMPV5`xWYaJO1CUA|5E`UV$bN;Jlqz=2Kpj zp}K4RFfJz`hzUjL$i3Q!un%&{`IFuuv2*f}wK9f;5k5*C4{aE?HeH$_07;=S8wgym zw23Yvw(lg<-8CsxsX9Ir4B7-UZt#SB=_kaQa;U!@0jxAA@dAY{u~9&ojL` z`Coi5<-?71CRFg&`+6(y=}RdX9ad_B0yHDWW*-5zHh5EkcW+aS7eSDj%28QG`T|lL zolQ*O7nA!G06&MM@!t5smTa&chb@FU?D081EV=zlGKvGOQP(-^nxoA}Jtu+DRVW&3P@&$g?hZNFN**h5OZNbnJK6_h8 zYknH$HAmSDxBs%tWvGo+o}EvOBz_n+>^2^woilzMKT~ZeQXMJemuNi-#wgnmZnCnF#GPucC87(HW2vmc02=>2iE(lbJLhb8P8<>X0+^- z(iff|?Z8=*F6s$h*VF@(9U8INR_gCC!enS*HXcmSWE2W;X?D44@RFRvwaiFD$BW>;yC<|JnOYk|#WnkJI&)+<5kfAF@EwKq)g_ zB%(HoZBN*h1L4GHpk6W(v$_xevzaSnT&?w#*2ni6d=uiiXHRin1W%_9of?%o&s8tpNF=)5=k7Y$Eiu2n z0yn0QzpTWlea^fktp5XW!D~<-lLB2IQ!)Oyp$@l;pO%99OTYRQLqh4|V~zIgH%CzZ z*)gkr3QPwh3n?3^-eFHA{Qv zOS@e>VpGi3`|HDs&pcZhZz$he+AEC`R-{1d&dnN?JiEbWXD;o{i*xPO4Wg%c9+v~Q z>x_)3(Mhhb$M;nD({m{e3<=sy@&O7Q)3H}@_EF>3;u85!)43K~IxIIkan>ERA70;HJX+nYAbKKC zOWOxzrjFeyJYI4)IgtU!2+8AjIdrH$hi=bD5FiCoRq>M~ix=2j5FkMt`yy^j*5gJ=n~1cD~lFCva~wQYLahb5ut^NDzeW^UkSuir*JDh zLTs1S_xw{?Dg%_VmVFI7X4r`D5`*zMv7g<6X2S^d%+vMD@4NlSJ54R zTJ_h|N3i99m1j5|t2qSMLu%Ixp}D;L4P}Q97a*NvnVQKOO*3lg0KDwsv5@%5Vwisy z%qha(V5bbXfOp7IIX@@Lnv0KgL83v{DhH*xByx z^>K8PjTZwf3Emi>+n+8E2Z^HbUbnch&#~9|JPOOY6-oXuP`+0NPvTSjPcDQH5bC(z z%AL^Y6Up%Dv>H^~uY`zm<9-^W=JMOzVs$i8OkOjLVD5Pe*I*$$Q$NpxFFqb4AD_P( zS>dWL(D-q7z0XA@9#^3$}>CbyC_jjBbOL-a=iZr5o%HiNW!w!WL60$C>GSPzuxJV zhmF!7rfg0qO6**`xD!5nFn*wca)C){qU|0yzFZ_Sg^<2d$h@H|;YFtrnd;w-zpD)e z>4v|X00H+H%G^~Bfzl78cm#^0Ce+5o>(MNFwO}x(&pmctgd(ItcG%e&-^j#4 z=`sq<;a28i)-?#@%IEUnS|WLGQ#siy*=YH+A$UT)L>rf4Re^tPHksj_h#CLK>7(qi zw~Ub(4EkC;3VTAayMZB^73I6oBS5(Qz?SFF1qU|5-kSS+GmE|N@cmuNd7>xq z{grd7L}JNBhs>>Llb2-Zr!ylgqN5QIvMiNA@LN{-fbyr=V%uJ#k;y!7*fbaM`vIre zRP1dwQR=9w8i17ZhB4DjOzxg)hPN|$hK$fbfT+|TBmH1_L11P&s0&D6S3ezu{rQtH z|08Dp6M;6W7xBH0c=yzpn7;!9kYuXjh^jQqwOqKLgxOe**4?w62(P@g zpjU{?2|0ph3_IdNlJeN@7kCgnHAa9CnZiHTd#ee_TdtKPt2(9O%}eR)?L`Lvr-A(Q z)5O98h_b{T07&S|lLkM+N^^`WE2cibZ+|>T12+=J_--fSx#z_Hd}w1`=nh3m z#@mcKEMP%gD8YdqRpi%=(nqG4d_LFm1lhmS`?J7wgS~VVFf}&Jd}0M{S@aXh`PPEy9wl1 zWYeWjI^4p?mY+vh4}=6E&zt`{*j+D}cHS5j=*Zo8s|!8>Eg=WNLhB*xCCTG&U*ZlO z|BI*J*5Ms0q>L2g!jn%t&!`^Y2_m#Ew}NWC zK@hq zmlTV4KR6Icu*3=#KM>^Iw7yY{<8if`?Qn}@-<+bcl~C@9qNul;@iFpqiFI(17qzJ2 zX5&)e6Bv{c57yncd)vM~4GA7ZEUJSYhXMz1M$?9rG5Isp$zL#m9ctw9nIgBqiP??q z;o!;C(FpQZUA|PFp_AKlEK+jB^s6@OkCfcCc~fLZlBXrTBJ1JfJ@byts7nt zCjkm5=Mbfk3_c|#rR$?|iErv_1CR}_tT;6?Q=|KFNGPFkPE%M#$@YSs$dZ^Z2T0?*&ZD5HC`DWO7qPS62Q&V$_zh?5={em-L z^1|xkKjXhzGATSj^j|J@o0L2l=nSUHj$Y{SH2Eb!AU$sQlQrlNrp6++(rpW0bAiPj zYQt>#h}4WSU;=wHQ*~jn%}=~wmLooaajw%+PiqZ%Y3-7WFsLacHzke zqV?L>)`Ajp4k2o@i!_M+%+v8FIu0#4_$ZRkmM2Uq7c9Kr91oqsWn)Be1R@`1yyasYdfgFQgDP(`D0fWwgOBPK- zah5s?vu*5qS&wFo!w*20d@N&;B9hOT#-MK3w^W2GV0ys-Hs||$mEQ|rf`Isx_vd|R zW*{^}Fd;3D&u0TJb0sJa2w0z(%6=L1y#*Hrtw>Wl|DI`CvT<$KjwYA+{IhCN#kQJ( zkU3Fx~A&0FwL)mq*0ggCZfy-FBRt`WK~tw8*UH0KaDh61u)lB6PM zc1vL#kY5378+dYhLN+VXAGo4Dh`(9Fb}6#l9@y>2_K|p>`YX`jMC|?I#h~X&_0@P* zVJJp@wD5{BVecuM=ae0tA&kipiI%4!<#pw4J(vB6a%Qg8`C_!2d@l}PF11J;_54=& z1vCCg>K1sIVsm}oJbi6V^I`Q2$TQs*DfSFuXWeoxnltJzed=v|-Cnv3cTY+`*V+`>%ua9OeGZQ2|h>D8p zbfC8lw<1@$0BQGo26%v@{9c0|g!mN{UhYn}wyhd=N7K5Ho&tcPF! z1KjWN>&Sk$a4G(fVo0!~upr*3tF7E9oqcc|Jav~c4d#J_L-D#C@6qYf@fa+2eOG#F ztp8zP>`l_~6ESxE`b>~7&fuR*p#AdEqQroHJx+BOYg&!#uf^coBz>-#h>s71?`MT* zv1M%0Cy7I&#XvwJGo=YsyA4|JE zr}5+WQpAP9?a0XG)mPp=3F2gW*avHPn=ZP$?xmqJ0rZTh;{OV-YdNLDr|ccSRH2}PIMjTsdk<`(_ypLoc z)H4b#>L-g zflP#~ZKGPJEtt2Xu@Ppw=b;g6=Dgve!&UrLgF<;5uF~=pK) z%4r9uNaZ2&9Bi#y0U^{9a$N6ryPWtNE^z=}*h9O9Q%LL`2oc%W!gx4y%MI(-l`1G+KUEu+&4Bv7d1mYL@SIv4mzkP!e!1*Uw zSRUR^m^W$wXT=IdUV>FSuxTt}HxqrYyNn|gIM^CuR#X-4*B zVlR9)%-0h3B@Lnjc4jfMZk>5^EZ`juBN$;ZO3C_9KzLc#jdV8VNFWz6{?(t-E6rj^ zKeXv3hDL+9TqpNTNDP6>K$ zdZ`?*j7Hz*mvL|JgyTA$qmwShR)zns=CGA0(C7|x-+w&iwu_okV5~oQpGkv&F#wLi zP8~+l9T+)dv&URi_z|c3s`+9^a#0*w`*vu$LEU+EU@Ee7szoE<7~bzOHYa zo%Kr!*ETftbASAOL%S<{d)<1Cwz-%-ZMahh!B-jiZa((j;6Cmx`4+EzUk8qp!Kg0L zsy4RQ30usML0c!!(5eHQl>YT&CmtPBPftyf8z~@Y;Gz1%q1ko$hIg&0W~=2r_534- z*!Ru7vURPDlP&xdI7-F|{*w*&l{NAKEB$-#SJ9bWf5iep#C;4z`>NgMU*b*oyP=i8p6%2ymI8)^A>l{x(B^R7t5$CU= z^I6@Gj71k;dauS>F6hgTCn?__nO~Z_Uh?^^c7c=tpc2sLtQK(6<{Xnu8wMwKKSl4I zFdJ0I+=JO+_HYi?+a^t>N#&HK)Ff5ayAAI&ZE*Gpl3ybH`ihi<*O z`Wl-Ykq4GQymrr$mR0YJiG4$AHwztS%V1@87*@giHFM!>1jZx!5gR)`MwPUy;5$!3 zDvKOQep;oJ2Fv#Cz!8eaB3fz5tBZ~Hf$4|p^)xG9k*v{I_b#4iJ|J7B)9oDRL4fPa zVNETfU!j{3Xy9E>pEz|^=~TcgWiM4$?^!02er7H^8K;B}1?9+mBMNik`N5QOz;!t7 ztSt2SX~wWfKdEnjfOenOr0v(Jt6^CW9Ce9*;#lMeYaZ;=s*b=o5c{EW%7U(b6{AWXA&GzP-mB(b z3`JfP5GE142o0W%m&UyJl0+Mpob=F%A|^>s{p}wCylKXx!&f@bR>Jz`Y^?MstMBkS z#Kebsl$a#Fi&&hB@Ws2o-u7QVsItEkX6_4D`6Q ziucRX)zjBUSMg2Mv2*p=uf-;VpPr9ZmMD5E45i1^OmcF>4p&9kmv`61QJ0&p_Y9#<+{o%N?BHk3{*_JC{1c%8twH zZkF_WT|;}y3Z!8yZ3HQ^*FM-H!QCwgo%uQJM--QAeQsk2hhOj!o9F4s51`pm zIE0Jm4NCV|D~vM&K1TeQvHXw77n50v9?r+q^I|HolAI?7-f< z-~1#oYkILClGvn5#S+dJogK<@F3s(>t#CNg=d`TDZ6q|Wb{8MO7Ol*)Q@iUI2M_y) zgGF+hb%WsJd3xqP+lX-W5T;c7%D)K%lB8j9F8{r{uE7XSqJN^;1isM|k;JyenYUx> z64@pMZ8cAQ(48kOjUfwjC0){iny_UCFTWS?fzmO znf(me&47bjtmPE?Y-GoZggN^A4l()Ux%TpAr}{bUdwO69I_(`3bQp|rR64Kg21)iV z;@l8;VAtGAygYnZ{2AOBqByhDbV6f2TzrtO17==ParcMQ__@F~?IWsDUudAn;sNWm zaC$#O8d|WKtS85o$?CMN&&x@<*dW$|MojDLk|3Nx9roLZaA(X# z@;{}?6qKQu0WsKVtp_M+PVu2Br0TmZRujRtm037emw!>=Zl;|zl3CWAy27fOs}Bu> zOM9eMN((7N@)tBeip5%StXdrK`x-2x{%QIs2ukW{G_F=CEy}U&UxgZr z#n1*6B}V9X676sy-0C~(ebV~J4heryO9GJx#V9UgaD~m{u-?;FUvM%Jjk^eGQnGi; zrz|VQ)|7M<7bf0au>mSE^fpVxW7~I)M~*W{M=*>^D8a>y}t?v^FgS5|T-zPdtAJ@d> z$xXRYpYP8z}!GvI?*jro>u?!dwOMhi~HUqnp$}h^_te5avD^xR0xWK+DfG0 zcrN+LlXKMri8;ar?r9)ZNA^BP+mVh zz#XGDkS8-ZhDOG0yHR4f8bn=Bpvwfa$Mf5Z+hSZ(wpZV{eWN|=Dj}9B>sve=qkz!@ zD^=2?EjEN+2+w2W4nvyx|Aoj`73lbYOeYvuaHADRK1yltiMf_ko$e-{BQHV z0Uk$Xm1gptp|T0MzBaFby0ScVZA8xyG$(0$M()ta|4R_7n!IB}(1+=`DpugcNVSA^ z;jTncdK9ZFI4;q8zvNehViU!S*Kb8h0d2}(LifiaffW0)U$)qp-i1`HX^SN(#RtDc zaSBr%!Z=ccFL{zF|6KZzZJ_6a+Rfzx>NTWkyO&LeYQ%Q)1%sB>T&3~ ztwr|dBXenkpv6@Ms zPWwYRW_9YEPkz6 zt#q8|?-(Dh+VW}TT?V=+Mjwagw6EDj%4c>zEWgtqigKl?bboVVDdPAMxcT^9Sda{7 zAvWO))29~pHUuQa;VQ%xSLt@ z6nD?V;i-N}N9-u;yrfd@?Ge{>``D}dR_(YOkk(k=a>O+>?SH!S>&&=h8aB|$I*t6% z*p3hL2%V8uI`l0UQUFP}Ozwu(ryVe?3zLDe@|J=#dK{+^XW1tQ9oJ>58cBWx)Fx-! zn5@fhp(u54F+0|LmH!Jx!9R^{)hC^Ve0)YxC!iV^3I~nGMP}HdG&=sj01-Iu$Ji{_ zH?IAuUtf_itOru*!B84x5<8Xn+FG>KgE_`{JMkfS9)_08b~FZ;7wlQ;vxg z^PA-1luO>_-8$RCJ2LJzI*j$tcMY0bxMR^`^LDwMZFvMLo*o(6rv<|I93kcezHli_ z^d>--Lb5AWSxK*+&e&9gzSXE=tE(`A7hoB+-(5~*$V>1MgpDvaN1-`B*X530>GYU1 z8c@SIX#0&CCvO`9(pkP?$D*=^KAE z)^uTt&F%)8vaXScyde|~Jne{FD2k~~|8oZ@7iA+@@%*5&zNn_l3$n#s0TXgOz>egR z0)uIrKw;K-uw86Zr07DGAUOfe;NHeFv5#p8QhU!bGq{HqXyLfs>GUn}QEQ{J?R_=a z6`;FHSTb34>a_)UzHptJet^SDqqpm>QMc&NbtJ8&?iOY5V%Wz8TM$J0yH#{+OU1qW z1_Uvt%~C*l!5NoN3V`cv0;e;#>u$zqj(!`5&UaNHAn^QBpcWsU?bORFt1G%vt7oSV zJGKC;;~(fMqiY6y%cnktQ%}35Fl0p=lFED{y|meWH3(rHXIIR8)I7eGsU8I^h)yax z!J9e|i5z9p;>g&8l4AoaJKmgj-9kIYbOx6~P_Nt0-k2_A@Ub^NwBxq^%j4Oz0QaEu z-1dU)u9T^)*2<6o+xn?P%!>ghFgcr_R(^}$6>h%rdsw;jmFIaTMCmbNuXVK8h zfoP^2d&E>G<(0H)3rQ+E}41yTblMW@p$6OW#MbNNH=VRlkHBC>`D{%v?1YTXG2|P2z z#{xSi(-Y;OT%KQ9xhpo;xr1d(7p4Srh@9ZEaNiEBoY8`)r!}A>MIU2QjY|cLE9oZm z4oz0Bhh_zWMu*=W>->Cz8vp2DHj-!_e(-6+KE*iISgzPort<|_GQ5`Kg{G~tcza`7 zx?66r@nc6Wy>%h}@TsNAhkj-EL0s&bsMPYuiigFK6IM+yxdMRkvi|V+L!mJ3sg173 zqq*8)j+}8QMPJT=hv;c8#N%HsmdnM;YOP$U{qd)c|8aB}zqs3&z>U0hARvuBLokOu z0hi&*D2%rHvn(bI8+q=@98ni{na-kp=dq7C)$Vk?!gO=vFB&RN;SKyY@O<))j?db$ zZxVJZ3darOH^(Kqa0PXDU<)?Eoq`T$I6`yJl3rI4Jpp_Ay8>HESMae&*RTfG77Gr} z0f36+ZuFEAu&v^(vsZBJ1>04sJyk|R7TbaJf_-XX{$BU)oi){MZO;T_xe2+V`Q8Dm z6Hxa990)uOWo1)OE7djvJgEZOXxvLxUy&6%a?DY`|wdKlJ@WXiESL+w-tT=1Bi zUszjR*@T+Lp;$`WD{Pyq*ol}PgX0f*Iqgc$uw5q1>*;&N1%V^LX9b2K4P1ZX_;u(T zfwA1L@9u1O?J>)CAqO)shn<6WU^x+NwD2Zy4R!@gsB9pc8oDYQ$mW2nj%~oJzD=qQ zT~b(HVUEI_lfPA7+3`fZb=6k|b+?&uljCn1z)Gb|yrFRV7>67Q4-M2S6c>8Cvwy## zgQ(@YSE3Z*XgY!vcnuH79m|%cBqb5*bGF;~qI2dzsv7XFMN-ot&N>??M~fgTM`ks6 z@M(AzZegFX?3459fXV9~e|CH0^36@#*Fm5HrD%pZsfrz7d8achh>ljN_qNh0K(M?P zU%MnFlBPmytQM;+Vf6R&@968P$co{I&Z zT3Y(GvE1i(8z%f{NpBpSQJ8-FiOHmU9_Zn=w<_}$xsAhERI*rVVj#N4qDuq<|63Q zbg7S*cyseGi!MZqVW3q#%b7 zIah0@1Di;T_vRKporUq#Qz8qtD-*S%fo3uD zVSG8`*D0Vtolv~=|%W~FYglBL`<7wNZ`#^djN z?4N}_PEJ5}M7L|Wd|owR%CX=6u_O1+avlB*fWF}5*1r^LagiWx6t)LqPh+(fdHrV&NOqSa~p*&r$pp?4V+^F34 zjtNfXj^trYGbd;WYw`O#E0YsO|JU@}nh0Tdsl~0v=qXDK>(bHEr~d55V{WKV-1L1Z z_@DlP4ED06uknTdDS|&*n~KhpITZ1zIx%pS1(+6 zYH94~bGv?tw@>cpzrT9n!c$92KR=dxez)=5vm*`$IDh_2z{#-(e&OD=LkR|-hCezW81>0DG~-RV(RbZ6MBvWN>%zP9X#|-~n(ERj3)JI$$-u$Z-sK z>o^o1NE*gpFvxzO-24QS$FaO%rqaA)?`(KcJ6zy}2`PJ2uEQ`r1{fVS{k!ZXH$rZQ zOJftq?Yh%uEzQdwZ?%FcsYN%{iHOc7#wcT-F4?vYD>rtBLY{Z(G@Bd8AD*5B9Z_dS zPF|&j>ZSwQUOf$MDP_4dTR5mHx2jjJ-&j5Lfr4W@@QLjf$1|6`RMqUgLkd!Ga|q2X z+`BYK?JBh53q^zJ&#k;?3%Rmy<;I}u+_^7o!t4L@#C{-4j}_;89`{5>K6C!F@kY*` z9DDHP?a@nT4_Ra1znWV`Skr|mq+3%S@A}0*o_!xQ16?WKd1IxVaVZyP&?~Lr zuxuZ(-K1A82lisw?vhR(Lc!(lSX8-Hd-MY<9BB3*mg?>~+-{-m!p#b3q?F~E(tCF{ zQkCyFrQ~qNpUnK|Zps5|4BZKR{PsvR`}?8USa{)muhQ`JR3RrQ!H@jI+EYu#6QAY$ z*+JBvKmVn(XD72K_uQZXSu)-@-!G7G?k$$mlgD4Z@zl~0b5~rfE`8;T{N!_dw$$5Q z^DO`mnwNNLX{q@Z)cV7crKPKhCU)1FPECaw>;2ITlu8*<%9sR*DF`fAgT;~ztfqNh zXK;70O}g49*us4qcJLhV5ewj>HE5b0z-np;WRt{yThVsR9!FwYKpyu2IzWXx8gg(r zr4~-jKDwqW)%7*e*F&lq5ppQMm*^-B=!3;;UWh0{)u1CMzh#6qnpKk}y%Kl^I;Tr^ zzMP)0#!Ut~&Y`|j=bv4z`1zS1;(+O0j!|c#bn|b^1895Uzq?)<*sQ0M~(ms&3YRZVSM?X<)C({KG(MYDFm$@G!|=&(Jtv~>3D z(%F+s10N8YiqOY(g3!(?>#l`nGj1-|mdzU`RhTs8dk^~Ti~sQn2eo%%2C zHj-mLRhW)XYCqFxn3*rW8?b#fB={^>1Jj=d+{QC#?h*vp=5c)hw+i1u|0dczI_`CQ z^P<>nm5pDt@NP%>zTv7W;J_gcRudBZKowP(Qw#G83pUo!5}gC?xgID$5twj0F*z3q z1&}vOSNYy%lo{2qW(O$L&DDas>vUm|82v?H1DNow<}!DdkAHz2Qmdz*GXw+e2su?jOxhe)-9N-`c%g zyH)XT>c(bO=P#$_>j`+!mXZm9CFI{;0nYUj2xz`$I{C{D$0sP^!x#1t`p%UJX297NvQWGYNbG4{;^7&T5heqpQZ zZP`3Tn43#}*;M;0-0`7`)=)((!}q%fv9vqWJYAShUs2y3=1L4;xI(i z06Nah%}IS{qyAoF6UV(9iQEmK`P`+Y*OmZG-`axhzWU2!gPVqC9pGYh2{==Fy0*#> zuWwh^T1#U`%omJ&M{L94_&kqay7c(TvD`22?jJ?Ns0m4oX<#2%hx*V9hn#fx(#=}8 zOQ&4?b~PwFwL)qZsp3hV=$|IG`kn6nW?;R8SAr8{R|w)7oC^+imCiP)b#w>T%=WA( zk8G*=g{)PPBijg)Rw8VGNEE@P)KO5d+3=Fgr0i{PdfLq{%o~@ip-Xy)wjD9|l28Wf ztCbJVOwX0mx=X{N0pCwV%;w9d@_blQ0>|PY2tl>ORs{M5+cgq9ao{DTq>EN6UUhDJ zySL3a(jYMl9Es)_%E15N@0vPuI>RBqylS%dzSq3OxpQAQdpz;V)6c@3E7@#KeJYd`(uSnd}hOq-~Bz((T9fq%N& zcycTU>;>!Nf{*1ys1N<2qVW0OR&F;}Ycuo3ieE&pPce&B0(S6rAcO07?VQ!~7w-$@ zlWH~J*M2zAaij2694)2T`jye?p?om$MYv9CEJl0;SkSe zl^4&SBzyKnB8R=oYhPG;G5MXJeywr#XfAse6W=%YSeMXH_8hpl>o?FNVJ%?J5#ji2 zs)7dOOBtt9tE|>$Y$`KF&H74)w z#Q~PYCY{N!>$d9^97}4=Fq-2ws@UTG$dA5!ZS09&VDS0Bti6g@uw7v_cThVpk_haG#)b}fsJm!5KQ1^@iD-Lpr>20-SQ5LHhL z>METIn!N{w(a(<1oV*#%pXAW5zy7@+eFQl9!qV5;Pk&+Q#j7t6yJvHp%Qp`3Ed4xE3wK zCm;p&$UV0`1l$&p(zwKd@owMRHAzO)mPfWgLs2NxZ1&y6`^U`=Om$VZAdh|TMoEY> z0^lW|*_;iS={%O&v6|U~*ax!pHuDoMhcTGe+UrUwJpXv!k&Znlcs%b`HJcp-=uSA_ z`1yBsf9}9o46;wPcd2@qwuwX`IoXtNY~mliell%(_9)H+JpA?7<@|iXv7by&!XTQB zeSTC_Eua+D$z8-L5dGtSezy^N7A5Q6`Ype>7Hza>6u}dYw>)30tk(3+TKRBsxf+z6 z4a0F|?~L*YQ__(t-#aQWOjkHyH3P7_S#33eZa0fH_-M6Nc1JeNsV#sHM<8pZb02YZ zA@lAVE82+;*>S54Z?2xslnpa>*bJHKC-4IhFx6Fyj&Kkv z;&T{%Vy8Hf@46kr75Gwxke(XIW)|5c{b1ngc`9#sfVELSJzw~t;)LE!{ctASjfQH6 z4%9u`a8vOQZtSiF0Dg%W&BF4C`am%iuc1ie=pVd}f%srm*^@rT#9(V%Rk zdS6E<;1rY)UWB)gJlj_>K9P}&U;2LdMydj{f^}E0;ujI%i_*~ehwZmFYh@EsQ=4*U ziNcS)4kKAc$xC$x;etmZ^FyJIyCC&Z4JPsX%t0uw6xplqH-yop{! zz@31}Paf9P3x8g#r(m0*0z^hi= z4;*^fO-TzEmSLqEhT!cE!lg45ZJP*3ZBd6SorqnQBGu``bo{@Z3~moqPX>Im<@pB9 zmQv+{{HP;X(- z-pGGdgVTxC=Ktu~9MF7=pLng$N&mZ}IpF`e?wgVNZ%38NPmbk!u3f5voTe)?tOLt_ z@k%2aYe6dFRU6jfyQ{UbO@$(a)5PI3h%MR%0*V->vb=_>qRjaF{p$k-R@071PDwNq z;oFkU5@Iu|n7RPU?^?N1xm?Ip-Uye0#c`MG8)YRkhNBcH@4na%Z-KyX#wP3p= z8_0vOI*Winv9qPtgwU{Jx;Z*%DCH*p;+H1>BKpbS{NgU)7TVs;bgB@2DwSKH@u^() zEO+6bpW`22+V$ZTMM-c?v!VFykB{HL?fFF6j5q|#G1>oEl zrvJ^CHvZZk#E${fAS^XCHQniMEV}l#w_ef3k-qoXd~vPX3U1aoT&(!P>5|+Z({5urJWYEv5$ z5YWEcNjIAnk}UI%v09S;$C?%{X- zv+n^LzkLc=2VnWR?ZuKrs${V}WR-m31Fv0FFTC}%`SAStFa5nQOegny_UuyjB+a+T zo}~D3e(UkvfNKX*)-N6aL`G>rx^=$d1~lkw=`!6?kK zpCf~fVe49QGn&HO``1VApG!1shWqwUGGqUsw!0R1uYY0$QLbgsJoM-=sSNFVBM^D+ z2L}*&?%cyyTdLo|PcA>Vy*SUXoj?C2`l{(HeokFH3Ksw_R+nmjLiQxtlf8R58F(bq zWF1RQPEFI5hgct}yJxZ=)wNczTGKX_a{cnT3%I>qwH0!wfN`i59DBSkbC86H+E`n0+jyYTO_9 z&B#oSBs$;i1|sk@jQw|wr10w*^lw?mn1;?Cu6m#<9y{qkHj@`Hu$lp+ z$UYoQY)u57rc}yMsjUeq0*J{;wv>n4zNx?^^@Dw7-}6EV?g9PdYPCYSSS|+wB(~8% zu61pZ2(|Ak#iRRP>PQw^ZL_@{a=O$uyjwFOh5Hn@b#yL$c}*P>*7o5NJ@mhC$0Q`f zB{GHU*utD~|DE~N(o)QI?)*ylWNZA_xf2=rxeI*m0zdg2z|ohl-A@5xq?!G$RQpbO zfaerjlHPQd3m10#ZqwF%D5+^nO-)T}&5WoK>vPBd%q(lZ2vk;U%hh0c)kgIy(!=TG z6uiHVK$}v^(NJJEJ zb}@}HBAQ*TRV%=wC6|N*px?Fcd11O@I6m3O^Z=%fSC(O-TEj}m@oSXj58`+ZyzG#UHA0Oct zFUj*~)w$u%4I-2LKC|Cb4p$Rj`|`83OPMbWuu>VS9iCO^My(My!gT-Zslqf$>L~n( z|0?2Fy!g<_ea+eKdgAR!?%RW3 z+1Bq*bUtF39sS0ws;M#R>J=qH*;wU1jDV8g>Wr}5|c9_|LO`U={68hHQqpQ&# z<8u-{?;pP1>!`XaYem_;jSjVh=5;AhYqrF(2iHsXRY(_O;@~jFh&+G(OJ})omJ6RPT`-T7f#c4UsI4+U z)V_KV;PbnUWZ)6oC6+Ec(R+Kp<1bagoh>^xv4=jl2|v`;O64lc)u8UJFINMU6fg&K zhSvY5?mn4MH5U9F8~gZ@7iZ9PKodYl2)m%J5pvcX~2=e za6{$)PjjMTsTo8zD`G;PK2a=VIf9Nx??I#MCeR$~TjwHqF{7_Ml1pUTACtDqYli-o zw6C?F9>Da4C%F0o=a1Gd$@7=w`7iee7w0Z5ojp5(@6W!79Cq+u|4^>(2ZjK6iKR;{ znH~Me^Nn-S`m?LNmfTTt0`hooHPtDt(DK+Bp@3V4I zo+wuWbS3O-6p-)AQ;0c;1>2L7BYW9CP)>elIJ}On5_gZ`d&-txJ?$3STBUQ65@ams zZrJ)Z0j2R`)j zHM6gnl4=ln?`LnEJ9pd!9s`IRu!3XjKWJQhI5$85ocq%E4gb!Z$Uog}Pzcga|EqZIO*5$~wfC>N8L zKc*`wg%F%^5t=u>zJu8RzD!yQT)`(M_@LnO<}EB1j$|(Te6_Z-3BjhHT#h%k0rjI$ z-#Rl>7qs-PilC}D8&O5qklNzQ{EN}zssr4&2VdSc{y52f`|$N$AW3vKch|$OT+8_=CETIqSbH~=X*~@!@o1H1uP;2n&%tm67;(JfwHj_xQI_e6;Xxb;J`Ax!sKR` zLTVOg{oRp?>O@lSBNMco;&LYtQ_@437V*aYp?SdcC~s&!G`=-Pe7zA0H1|9-P_csy zv^?4Zp%DTH+!U*-4LJ3dAFWQ3d`8EWZH>|QrP?9ZSNCep?+$A|aKrQMV1 zkY#GPZ}DIuhn|@FV)L1wH z_dTGTaTn$nCM7^#klp1I4qw01`KGTm@NP|)i$?S{l?5+!j59&q`)87}YeHLq9q zy#ILP0psKAYgc?OAIf#0p}Fn{!)P{^lt>d%^n5Ju%sW|`=0=B2xV1sWtC;c1monk; zr+RgDKq>4Ft3D09j>(1h@5ro19@hemV~+bbNh zZb!+wHv-e~_|FMguT}2S?Xp=eYFI4Urm!-6B(aN4ik`hsFs-D?y(Cj-ajngU9<5RN zca>^GdQwtXC|JFB@5}^mLZ{yPzP=k@kU%#5sJS;OeDGd}ms*^0!eiCL4~#ub7=7pz zE30d#9M%pMt6F!W106XF91#2yvA*?0))n09glypKG*Av44|vFN$5U-5T2I#nyNhF? zU{&{4hZ(RIu7ErJs|t-ydz&VE$JNmIUymCnIR?8wbIrEo$c-C|(F_`94Ew5kt~LNzRRSn2eA^1fzn?zO-Z-*0@

%kz+Vnd8 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 b-~RsrQ%6&$qb>F&00000NkvXXu0mjf{Z0EX literal 0 HcmV?d00001 diff --git a/src/platform/python/tests/cinema/gba/obj/unaligned-256/test.mvl b/src/platform/python/tests/cinema/gba/obj/unaligned-256/test.mvl new file mode 100644 index 0000000000000000000000000000000000000000..2eead4ed3c0b3c2fd6c13c3276983f6bb55a9beb GIT binary patch literal 35105 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 WeG0@Er_(of|0XE@a7fC3$@n) Date: Fri, 13 Oct 2017 00:28:35 -0700 Subject: [PATCH 009/152] Debugger: Redo argument handling --- include/mgba/internal/debugger/cli-debugger.h | 6 +- src/arm/debugger/cli-debugger.c | 36 ++-- src/debugger/cli-debugger.c | 204 +++++++++++------- src/gb/debugger/cli.c | 6 +- src/gba/debugger/cli.c | 6 +- 5 files changed, 156 insertions(+), 102 deletions(-) diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index 6eb28b1dc..84ac16cf1 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -34,12 +34,11 @@ struct CLIDebugVector { }; typedef void (*CLIDebuggerCommand)(struct CLIDebugger*, struct CLIDebugVector*); -typedef struct CLIDebugVector* (*CLIDVParser)(struct CLIDebugger* debugger, const char* string, size_t length); struct CLIDebuggerCommandSummary { const char* name; CLIDebuggerCommand command; - CLIDVParser parser; + const char* format; const char* summary; }; @@ -82,9 +81,6 @@ struct CLIDebugger { struct CLIDebuggerBackend* backend; }; -struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* string, size_t length); -struct CLIDebugVector* CLIDVStringParse(struct CLIDebugger* debugger, const char* string, size_t length); - void CLIDebuggerCreate(struct CLIDebugger*); void CLIDebuggerAttachSystem(struct CLIDebugger*, struct CLIDebuggerSystem*); void CLIDebuggerAttachBackend(struct CLIDebugger*, struct CLIDebuggerBackend*); diff --git a/src/arm/debugger/cli-debugger.c b/src/arm/debugger/cli-debugger.c index 87a1655c5..a80ec43e2 100644 --- a/src/arm/debugger/cli-debugger.c +++ b/src/arm/debugger/cli-debugger.c @@ -23,17 +23,17 @@ static void _disassembleMode(struct CLIDebugger*, struct CLIDebugVector*, enum E static uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode); static struct CLIDebuggerCommandSummary _armCommands[] = { - { "b/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, - { "b/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, - { "break/a", _setBreakpointARM, CLIDVParse, "Set a software breakpoint as ARM" }, - { "break/t", _setBreakpointThumb, CLIDVParse, "Set a software breakpoint as Thumb" }, - { "dis/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" }, - { "dis/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" }, - { "disasm/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" }, - { "disasm/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" }, - { "disassemble/a", _disassembleArm, CLIDVParse, "Disassemble instructions as ARM" }, - { "disassemble/t", _disassembleThumb, CLIDVParse, "Disassemble instructions as Thumb" }, - { "w/r", _writeRegister, CLIDVParse, "Write a register" }, + { "b/a", _setBreakpointARM, "I", "Set a software breakpoint as ARM" }, + { "b/t", _setBreakpointThumb, "I", "Set a software breakpoint as Thumb" }, + { "break/a", _setBreakpointARM, "I", "Set a software breakpoint as ARM" }, + { "break/t", _setBreakpointThumb, "I", "Set a software breakpoint as Thumb" }, + { "dis/a", _disassembleArm, "Ii", "Disassemble instructions as ARM" }, + { "dis/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, + { "disasm/a", _disassembleArm, "Ii", "Disassemble instructions as ARM" }, + { "disasm/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, + { "disassemble/a", _disassembleArm, "Ii", "Disassemble instructions as ARM" }, + { "disassemble/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, + { "w/r", _writeRegister, "SI", "Write a register" }, { 0, 0, 0, 0 } }; @@ -148,7 +148,7 @@ static void _printStatus(struct CLIDebuggerSystem* debugger) { static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { struct CLIDebuggerBackend* be = debugger->backend; struct ARMCore* cpu = debugger->d.core->cpu; - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv || dv->type != CLIDV_CHAR_TYPE) { be->printf(be, "%s\n", ERROR_MISSING_ARGS); return; } @@ -156,11 +156,17 @@ static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* be->printf(be, "%s\n", ERROR_MISSING_ARGS); return; } - uint32_t regid = dv->intValue; - uint32_t value = dv->next->intValue; - if (regid >= ARM_PC) { + char* end; + uint32_t regid = strtoul(&dv->charValue[1], &end, 10); + if (dv->charValue[0] != 'r' || (*end && !isspace(*end)) || regid > ARM_PC) { + be->printf(be, "%s\n", "Unknown register name"); return; } + if (regid == ARM_PC) { + be->printf(be, "%s\n", "Cannot write to program counter"); + return; + } + uint32_t value = dv->next->intValue; cpu->gprs[regid] = value; } diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 871143436..e3ce9913c 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -60,50 +60,50 @@ static void _source(struct CLIDebugger*, struct CLIDebugVector*); #endif static struct CLIDebuggerCommandSummary _debuggerCommands[] = { - { "b", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, - { "break", _setBreakpoint, CLIDVParse, "Set a breakpoint" }, - { "c", _continue, 0, "Continue execution" }, - { "continue", _continue, 0, "Continue execution" }, - { "d", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" }, - { "delete", _clearBreakpoint, CLIDVParse, "Delete a breakpoint" }, - { "dis", _disassemble, CLIDVParse, "Disassemble instructions" }, - { "disasm", _disassemble, CLIDVParse, "Disassemble instructions" }, - { "disassemble", _disassemble, CLIDVParse, "Disassemble instructions" }, - { "h", _printHelp, CLIDVStringParse, "Print help" }, - { "help", _printHelp, CLIDVStringParse, "Print help" }, - { "i", _printStatus, 0, "Print the current status" }, - { "info", _printStatus, 0, "Print the current status" }, - { "n", _next, 0, "Execute next instruction" }, - { "next", _next, 0, "Execute next instruction" }, - { "p", _print, CLIDVParse, "Print a value" }, - { "p/t", _printBin, CLIDVParse, "Print a value as binary" }, - { "p/x", _printHex, CLIDVParse, "Print a value as hexadecimal" }, - { "print", _print, CLIDVParse, "Print a value" }, - { "print/t", _printBin, CLIDVParse, "Print a value as binary" }, - { "print/x", _printHex, CLIDVParse, "Print a value as hexadecimal" }, - { "q", _quit, 0, "Quit the emulator" }, - { "quit", _quit, 0, "Quit the emulator" }, - { "reset", _reset, 0, "Reset the emulation" }, - { "r/1", _readByte, CLIDVParse, "Read a byte from a specified offset" }, - { "r/2", _readHalfword, CLIDVParse, "Read a halfword from a specified offset" }, - { "r/4", _readWord, CLIDVParse, "Read a word from a specified offset" }, - { "status", _printStatus, 0, "Print the current status" }, - { "trace", _trace, CLIDVParse, "Trace a fixed number of instructions" }, - { "w", _setWatchpoint, CLIDVParse, "Set a watchpoint" }, - { "w/1", _writeByte, CLIDVParse, "Write a byte at a specified offset" }, - { "w/2", _writeHalfword, CLIDVParse, "Write a halfword at a specified offset" }, - { "w/4", _writeWord, CLIDVParse, "Write a word at a specified offset" }, - { "watch", _setWatchpoint, CLIDVParse, "Set a watchpoint" }, - { "watch/r", _setReadWatchpoint, CLIDVParse, "Set a read watchpoint" }, - { "watch/w", _setWriteWatchpoint, CLIDVParse, "Set a write watchpoint" }, - { "x/1", _dumpByte, CLIDVParse, "Examine bytes at a specified offset" }, - { "x/2", _dumpHalfword, CLIDVParse, "Examine halfwords at a specified offset" }, - { "x/4", _dumpWord, CLIDVParse, "Examine words at a specified offset" }, + { "b", _setBreakpoint, "I", "Set a breakpoint" }, + { "break", _setBreakpoint, "I", "Set a breakpoint" }, + { "c", _continue, "", "Continue execution" }, + { "continue", _continue, "", "Continue execution" }, + { "d", _clearBreakpoint, "I", "Delete a breakpoint" }, + { "delete", _clearBreakpoint, "I", "Delete a breakpoint" }, + { "dis", _disassemble, "Ii", "Disassemble instructions" }, + { "disasm", _disassemble, "Ii", "Disassemble instructions" }, + { "disassemble", _disassemble, "Ii", "Disassemble instructions" }, + { "h", _printHelp, "S", "Print help" }, + { "help", _printHelp, "S", "Print help" }, + { "i", _printStatus, "", "Print the current status" }, + { "info", _printStatus, "", "Print the current status" }, + { "n", _next, "", "Execute next instruction" }, + { "next", _next, "", "Execute next instruction" }, + { "p", _print, "I", "Print a value" }, + { "p/t", _printBin, "I", "Print a value as binary" }, + { "p/x", _printHex, "I", "Print a value as hexadecimal" }, + { "print", _print, "I", "Print a value" }, + { "print/t", _printBin, "I", "Print a value as binary" }, + { "print/x", _printHex, "I", "Print a value as hexadecimal" }, + { "q", _quit, "", "Quit the emulator" }, + { "quit", _quit, "", "Quit the emulator" }, + { "reset", _reset, "", "Reset the emulation" }, + { "r/1", _readByte, "I", "Read a byte from a specified offset" }, + { "r/2", _readHalfword, "I", "Read a halfword from a specified offset" }, + { "r/4", _readWord, "I", "Read a word from a specified offset" }, + { "status", _printStatus, "", "Print the current status" }, + { "trace", _trace, "I", "Trace a fixed number of instructions" }, + { "w", _setWatchpoint, "I", "Set a watchpoint" }, + { "w/1", _writeByte, "II", "Write a byte at a specified offset" }, + { "w/2", _writeHalfword, "II", "Write a halfword at a specified offset" }, + { "w/4", _writeWord, "II", "Write a word at a specified offset" }, + { "watch", _setWatchpoint, "I", "Set a watchpoint" }, + { "watch/r", _setReadWatchpoint, "I", "Set a read watchpoint" }, + { "watch/w", _setWriteWatchpoint, "I", "Set a write watchpoint" }, + { "x/1", _dumpByte, "Ii", "Examine bytes at a specified offset" }, + { "x/2", _dumpHalfword, "Ii", "Examine halfwords at a specified offset" }, + { "x/4", _dumpWord, "Ii", "Examine words at a specified offset" }, #ifdef ENABLE_SCRIPTING - { "source", _source, CLIDVStringParse, "Load a script" }, + { "source", _source, "S", "Load a script" }, #endif #if !defined(NDEBUG) && !defined(_WIN32) - { "!", _breakInto, 0, "Break into attached debugger (for developers)" }, + { "!", _breakInto, "", "Break into attached debugger (for developers)" }, #endif { 0, 0, 0, 0 } }; @@ -626,52 +626,28 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri parseFree(tree.lhs); parseFree(tree.rhs); - length -= adjusted; - string += adjusted; - struct CLIDebugVector* dv = malloc(sizeof(struct CLIDebugVector)); if (dvTemp.type == CLIDV_ERROR_TYPE) { dv->type = CLIDV_ERROR_TYPE; dv->next = 0; } else { *dv = dvTemp; - if (string[0] == ' ') { - dv->next = CLIDVParse(debugger, string + 1, length - 1); - if (dv->next && dv->next->type == CLIDV_ERROR_TYPE) { - dv->type = CLIDV_ERROR_TYPE; - } - } } return dv; } struct CLIDebugVector* CLIDVStringParse(struct CLIDebugger* debugger, const char* string, size_t length) { + UNUSED(debugger); if (!string || length < 1) { return 0; } struct CLIDebugVector dvTemp = { .type = CLIDV_CHAR_TYPE }; - size_t adjusted; - const char* next = strchr(string, ' '); - if (next) { - adjusted = next - string; - } else { - adjusted = length; - } - dvTemp.charValue = strndup(string, adjusted); - - length -= adjusted; - string += adjusted; + dvTemp.charValue = strndup(string, length); struct CLIDebugVector* dv = malloc(sizeof(struct CLIDebugVector)); *dv = dvTemp; - if (string[0] == ' ') { - dv->next = CLIDVStringParse(debugger, string + 1, length - 1); - if (dv->next && dv->next->type == CLIDV_ERROR_TYPE) { - dv->type = CLIDV_ERROR_TYPE; - } - } return dv; } @@ -687,8 +663,28 @@ static void _DVFree(struct CLIDebugVector* dv) { } } +static struct CLIDebugVector* _parseArg(struct CLIDebugger* debugger, const char* args, size_t argsLen, char type) { + struct CLIDebugVector* dv = NULL; + switch (type) { + case 'I': + case 'i': + return CLIDVParse(debugger, args, argsLen); + case 'S': + case 's': + return CLIDVStringParse(debugger, args, argsLen); + case '*': + dv = _parseArg(debugger, args, argsLen, 'I'); + if (!dv) { + dv = _parseArg(debugger, args, argsLen, 'S'); + } + break; + } + return dv; +} + static int _tryCommands(struct CLIDebugger* debugger, struct CLIDebuggerCommandSummary* commands, const char* command, size_t commandLen, const char* args, size_t argsLen) { - struct CLIDebugVector* dv = 0; + struct CLIDebugVector* dv = NULL; + struct CLIDebugVector* dvLast = NULL; int i; const char* name; for (i = 0; (name = commands[i].name); ++i) { @@ -696,22 +692,78 @@ static int _tryCommands(struct CLIDebugger* debugger, struct CLIDebuggerCommandS continue; } if (strncasecmp(name, command, commandLen) == 0) { - if (commands[i].parser) { - if (args) { - dv = commands[i].parser(debugger, args, argsLen); - if (dv && dv->type == CLIDV_ERROR_TYPE) { + if (commands[i].format && args) { + char lastArg = '\0'; + int arg; + for (arg = 0; commands[i].format[arg] && argsLen; ++arg) { + while (isspace(args[0]) && argsLen) { + ++args; + --argsLen; + } + if (!args[0] || !argsLen) { + debugger->backend->printf(debugger->backend, "Wrong number of arguments\n"); + _DVFree(dv); + return 0; + } + + size_t adjusted; + const char* next = strchr(args, ' '); + if (next) { + adjusted = next - args; + } else { + adjusted = argsLen; + } + + struct CLIDebugVector* dvNext = NULL; + bool nextArgMandatory = false; + + if (commands[i].format[arg] == '+') { + dvNext = _parseArg(debugger, args, adjusted, lastArg); + --args; + } else { + nextArgMandatory = isupper(commands[i].format[arg]) || (commands[i].format[arg] == '*'); + dvNext = _parseArg(debugger, args, adjusted, commands[i].format[arg]); + } + + args += adjusted; + argsLen -= adjusted; + + if (!dvNext) { + if (!nextArgMandatory) { + args = NULL; + } + break; + } + if (dvNext->type == CLIDV_ERROR_TYPE) { debugger->backend->printf(debugger->backend, "Parse error\n"); _DVFree(dv); - return false; + return 0; + } + + if (dvLast) { + dvLast->next = dvNext; + dvLast = dvNext; + } else { + dv = dvNext; + dvLast = dv; } } - } else if (args) { + } + + if (args) { + while (isspace(args[0]) && argsLen) { + ++args; + --argsLen; + } + } + if (args && argsLen) { debugger->backend->printf(debugger->backend, "Wrong number of arguments\n"); - return false; + _DVFree(dv); + return 0; } commands[i].command(debugger, dv); _DVFree(dv); - return true; + return 1; } } return -1; diff --git a/src/gb/debugger/cli.c b/src/gb/debugger/cli.c index 2f59293d1..75cc53e54 100644 --- a/src/gb/debugger/cli.c +++ b/src/gb/debugger/cli.c @@ -21,9 +21,9 @@ static void _load(struct CLIDebugger*, struct CLIDebugVector*); static void _save(struct CLIDebugger*, struct CLIDebugVector*); struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = { - { "frame", _frame, 0, "Frame advance" }, - { "load", _load, CLIDVParse, "Load a savestate" }, - { "save", _save, CLIDVParse, "Save a savestate" }, + { "frame", _frame, "", "Frame advance" }, + { "load", _load, "*", "Load a savestate" }, + { "save", _save, "*", "Save a savestate" }, { 0, 0, 0, 0 } }; diff --git a/src/gba/debugger/cli.c b/src/gba/debugger/cli.c index 4208d9021..07287eebc 100644 --- a/src/gba/debugger/cli.c +++ b/src/gba/debugger/cli.c @@ -21,9 +21,9 @@ static void _load(struct CLIDebugger*, struct CLIDebugVector*); static void _save(struct CLIDebugger*, struct CLIDebugVector*); struct CLIDebuggerCommandSummary _GBACLIDebuggerCommands[] = { - { "frame", _frame, 0, "Frame advance" }, - { "load", _load, CLIDVParse, "Load a savestate" }, - { "save", _save, CLIDVParse, "Save a savestate" }, + { "frame", _frame, "", "Frame advance" }, + { "load", _load, "*", "Load a savestate" }, + { "save", _save, "*", "Save a savestate" }, { 0, 0, 0, 0 } }; From d484c98eba5fc8281ccb16427ee326301e71ad1c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 13 Oct 2017 00:29:38 -0700 Subject: [PATCH 010/152] Debugger: Add get/set register functions --- include/mgba/debugger/debugger.h | 3 + include/mgba/internal/debugger/cli-debugger.h | 2 +- src/arm/debugger/cli-debugger.c | 56 -------- src/arm/debugger/debugger.c | 82 +++++++++++ src/debugger/cli-debugger.c | 41 ++++-- src/lr35902/debugger/cli-debugger.c | 49 ------- src/lr35902/debugger/debugger.c | 132 ++++++++++++++++++ 7 files changed, 246 insertions(+), 119 deletions(-) diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index 213236425..0a9bb67c1 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -84,6 +84,9 @@ struct mDebuggerPlatform { void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*checkBreakpoints)(struct mDebuggerPlatform*); void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length); + + bool (*getRegister)(struct mDebuggerPlatform*, const char* name, int32_t* value); + bool (*setRegister)(struct mDebuggerPlatform*, const char* name, int32_t value); }; struct mDebuggerSymbols; diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index 84ac16cf1..a6d623df3 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -14,6 +14,7 @@ CXX_GUARD_START extern const char* ERROR_MISSING_ARGS; extern const char* ERROR_OVERFLOW; +extern const char* ERROR_INVALID_ARGS; struct CLIDebugger; @@ -51,7 +52,6 @@ struct CLIDebuggerSystem { void (*disassemble)(struct CLIDebuggerSystem*, struct CLIDebugVector* dv); uint32_t (*lookupIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); - uint32_t (*lookupPlatformIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); void (*printStatus)(struct CLIDebuggerSystem*); struct CLIDebuggerCommandSummary* commands; diff --git a/src/arm/debugger/cli-debugger.c b/src/arm/debugger/cli-debugger.c index a80ec43e2..a4f37e5ef 100644 --- a/src/arm/debugger/cli-debugger.c +++ b/src/arm/debugger/cli-debugger.c @@ -17,7 +17,6 @@ static void _disassembleArm(struct CLIDebugger*, struct CLIDebugVector*); static void _disassembleThumb(struct CLIDebugger*, struct CLIDebugVector*); static void _setBreakpointARM(struct CLIDebugger*, struct CLIDebugVector*); static void _setBreakpointThumb(struct CLIDebugger*, struct CLIDebugVector*); -static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*); static void _disassembleMode(struct CLIDebugger*, struct CLIDebugVector*, enum ExecutionMode mode); static uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode); @@ -33,7 +32,6 @@ static struct CLIDebuggerCommandSummary _armCommands[] = { { "disasm/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, { "disassemble/a", _disassembleArm, "Ii", "Disassemble instructions as ARM" }, { "disassemble/t", _disassembleThumb, "Ii", "Disassemble instructions as Thumb" }, - { "w/r", _writeRegister, "SI", "Write a register" }, { 0, 0, 0, 0 } }; @@ -145,31 +143,6 @@ static void _printStatus(struct CLIDebuggerSystem* debugger) { _printLine(debugger->p, cpu->gprs[ARM_PC] - instructionLength, mode); } -static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct CLIDebuggerBackend* be = debugger->backend; - struct ARMCore* cpu = debugger->d.core->cpu; - if (!dv || dv->type != CLIDV_CHAR_TYPE) { - be->printf(be, "%s\n", ERROR_MISSING_ARGS); - return; - } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { - be->printf(be, "%s\n", ERROR_MISSING_ARGS); - return; - } - char* end; - uint32_t regid = strtoul(&dv->charValue[1], &end, 10); - if (dv->charValue[0] != 'r' || (*end && !isspace(*end)) || regid > ARM_PC) { - be->printf(be, "%s\n", "Unknown register name"); - return; - } - if (regid == ARM_PC) { - be->printf(be, "%s\n", "Cannot write to program counter"); - return; - } - uint32_t value = dv->next->intValue; - cpu->gprs[regid] = value; -} - static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { struct CLIDebuggerBackend* be = debugger->backend; if (!dv || dv->type != CLIDV_INT_TYPE) { @@ -190,38 +163,9 @@ static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVec ARMDebuggerSetSoftwareBreakpoint(debugger->d.platform, address, MODE_THUMB); } -static uint32_t _lookupPlatformIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - struct ARMCore* cpu = debugger->p->d.core->cpu; - if (strcmp(name, "sp") == 0) { - return cpu->gprs[ARM_SP]; - } - if (strcmp(name, "lr") == 0) { - return cpu->gprs[ARM_LR]; - } - if (strcmp(name, "pc") == 0) { - return cpu->gprs[ARM_PC]; - } - if (strcmp(name, "cpsr") == 0) { - return cpu->cpsr.packed; - } - // TODO: test if mode has SPSR - if (strcmp(name, "spsr") == 0) { - return cpu->spsr.packed; - } - if (name[0] == 'r' && name[1] >= '0' && name[1] <= '9') { - int reg = atoi(&name[1]); - if (reg < 16) { - return cpu->gprs[reg]; - } - } - dv->type = CLIDV_ERROR_TYPE; - return 0; -} - void ARMCLIDebuggerCreate(struct CLIDebuggerSystem* debugger) { debugger->printStatus = _printStatus; debugger->disassemble = _disassemble; - debugger->lookupPlatformIdentifier = _lookupPlatformIdentifier; debugger->platformName = "ARM"; debugger->platformCommands = _armCommands; } diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index fde61a8a8..4033f4a13 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -56,6 +56,8 @@ static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t addre static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*); static void ARMDebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); +static bool ARMDebuggerGetRegister(struct mDebuggerPlatform*, const char* name, int32_t* value); +static bool ARMDebuggerSetRegister(struct mDebuggerPlatform*, const char* name, int32_t value); struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct ARMDebugger)); @@ -69,6 +71,8 @@ struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { platform->checkBreakpoints = ARMDebuggerCheckBreakpoints; platform->hasBreakpoints = ARMDebuggerHasBreakpoints; platform->trace = ARMDebuggerTrace; + platform->getRegister = ARMDebuggerGetRegister; + platform->setRegister = ARMDebuggerSetRegister; return platform; } @@ -246,3 +250,81 @@ static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* len cpu->gprs[12], cpu->gprs[13], cpu->gprs[14], cpu->gprs[15], cpu->cpsr.packed, disassembly); } + +bool ARMDebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMCore* cpu = debugger->cpu; + + if (strcmp(name, "sp") == 0) { + *value = cpu->gprs[ARM_SP]; + return true; + } + if (strcmp(name, "lr") == 0) { + *value = cpu->gprs[ARM_LR]; + return true; + } + if (strcmp(name, "pc") == 0) { + *value = cpu->gprs[ARM_PC]; + return true; + } + if (strcmp(name, "cpsr") == 0) { + *value = cpu->cpsr.packed; + return true; + } + // TODO: test if mode has SPSR + if (strcmp(name, "spsr") == 0) { + *value = cpu->spsr.packed; + return true; + } + if (name[0] == 'r') { + char* end; + uint32_t reg = strtoul(&name[1], &end, 10); + if (reg <= ARM_PC) { + *value = cpu->gprs[reg]; + return true; + } + } + return false; +} + +bool ARMDebuggerSetRegister(struct mDebuggerPlatform* d, const char* name, int32_t value) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMCore* cpu = debugger->cpu; + + if (strcmp(name, "sp") == 0) { + cpu->gprs[ARM_SP] = value; + return true; + } + if (strcmp(name, "lr") == 0) { + cpu->gprs[ARM_LR] = value; + return true; + } + if (strcmp(name, "pc") == 0) { + cpu->gprs[ARM_PC] = value; + int32_t currentCycles = 0; + if (cpu->executionMode == MODE_ARM) { + ARM_WRITE_PC; + } else { + THUMB_WRITE_PC; + } + return true; + } + if (name[0] == 'r') { + char* end; + uint32_t reg = strtoul(&name[1], &end, 10); + if (reg > ARM_PC) { + return false; + } + cpu->gprs[reg] = value; + if (reg == ARM_PC) { + int32_t currentCycles = 0; + if (cpu->executionMode == MODE_ARM) { + ARM_WRITE_PC; + } else { + THUMB_WRITE_PC; + } + } + return true; + } + return false; +} diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index e3ce9913c..8b98d8a7b 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -26,6 +26,7 @@ const char* ERROR_MISSING_ARGS = "Arguments missing"; // TODO: share const char* ERROR_OVERFLOW = "Arguments overflow"; +const char* ERROR_INVALID_ARGS = "Invalid arguments"; #if !defined(NDEBUG) && !defined(_WIN32) static void _breakInto(struct CLIDebugger*, struct CLIDebugVector*); @@ -51,6 +52,7 @@ static void _setWriteWatchpoint(struct CLIDebugger*, struct CLIDebugVector*); static void _trace(struct CLIDebugger*, struct CLIDebugVector*); static void _writeByte(struct CLIDebugger*, struct CLIDebugVector*); static void _writeHalfword(struct CLIDebugger*, struct CLIDebugVector*); +static void _writeRegister(struct CLIDebugger*, struct CLIDebugVector*); static void _writeWord(struct CLIDebugger*, struct CLIDebugVector*); static void _dumpByte(struct CLIDebugger*, struct CLIDebugVector*); static void _dumpHalfword(struct CLIDebugger*, struct CLIDebugVector*); @@ -92,6 +94,7 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "w", _setWatchpoint, "I", "Set a watchpoint" }, { "w/1", _writeByte, "II", "Write a byte at a specified offset" }, { "w/2", _writeHalfword, "II", "Write a halfword at a specified offset" }, + { "w/r", _writeRegister, "SI", "Write a register" }, { "w/4", _writeWord, "II", "Write a word at a specified offset" }, { "watch", _setWatchpoint, "I", "Set a watchpoint" }, { "watch/r", _setReadWatchpoint, "I", "Set a read watchpoint" }, @@ -273,12 +276,12 @@ static void _readWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { } static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv || !dv->next) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { - debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + if (dv->type != CLIDV_INT_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } uint32_t address = dv->intValue; @@ -295,12 +298,12 @@ static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) } static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv || !dv->next) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { - debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); + if (dv->type != CLIDV_INT_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } uint32_t address = dv->intValue; @@ -316,15 +319,29 @@ static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* } } -static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { +static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || !dv->next) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!dv->next || dv->next->type != CLIDV_INT_TYPE) { + if (dv->type != CLIDV_CHAR_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + return; + } + if (!debugger->d.platform->setRegister(debugger->d.platform, dv->charValue, dv->next->intValue)) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } +} + +static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || !dv->next) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } + if (dv->type != CLIDV_INT_TYPE || dv->next->type != CLIDV_INT_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + return; + } uint32_t address = dv->intValue; uint32_t value = dv->next->intValue; if (dv->segmentValue >= 0) { @@ -559,12 +576,10 @@ static void _lookupIdentifier(struct mDebugger* debugger, const char* name, stru if (debugger->core->symbolTable && mDebuggerSymbolLookup(debugger->core->symbolTable, name, &dv->intValue, &dv->segmentValue)) { return; } - value = cliDebugger->system->lookupPlatformIdentifier(cliDebugger->system, name, dv); - if (dv->type != CLIDV_ERROR_TYPE) { - dv->intValue = value; + dv->type = CLIDV_INT_TYPE; + if (debugger->platform->getRegister(debugger->platform, name, &dv->intValue)) { return; } - dv->type = CLIDV_INT_TYPE; value = cliDebugger->system->lookupIdentifier(cliDebugger->system, name, dv); if (dv->type != CLIDV_ERROR_TYPE) { dv->intValue = value; diff --git a/src/lr35902/debugger/cli-debugger.c b/src/lr35902/debugger/cli-debugger.c index ac01343df..7df1ea726 100644 --- a/src/lr35902/debugger/cli-debugger.c +++ b/src/lr35902/debugger/cli-debugger.c @@ -100,58 +100,9 @@ static void _printStatus(struct CLIDebuggerSystem* debugger) { _printLine(debugger->p, cpu->pc, cpu->memory.currentSegment(cpu, cpu->pc)); } -static uint32_t _lookupPlatformIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - struct LR35902Core* cpu = debugger->p->d.core->cpu; - if (strcmp(name, "a") == 0) { - return cpu->a; - } - if (strcmp(name, "b") == 0) { - return cpu->b; - } - if (strcmp(name, "c") == 0) { - return cpu->c; - } - if (strcmp(name, "d") == 0) { - return cpu->d; - } - if (strcmp(name, "e") == 0) { - return cpu->e; - } - if (strcmp(name, "h") == 0) { - return cpu->h; - } - if (strcmp(name, "l") == 0) { - return cpu->l; - } - if (strcmp(name, "bc") == 0) { - return cpu->bc; - } - if (strcmp(name, "de") == 0) { - return cpu->de; - } - if (strcmp(name, "hl") == 0) { - return cpu->hl; - } - if (strcmp(name, "af") == 0) { - return cpu->af; - } - if (strcmp(name, "pc") == 0) { - return cpu->pc; - } - if (strcmp(name, "sp") == 0) { - return cpu->sp; - } - if (strcmp(name, "f") == 0) { - return cpu->f.packed; - } - dv->type = CLIDV_ERROR_TYPE; - return 0; -} - void LR35902CLIDebuggerCreate(struct CLIDebuggerSystem* debugger) { debugger->printStatus = _printStatus; debugger->disassemble = _disassemble; - debugger->lookupPlatformIdentifier = _lookupPlatformIdentifier; debugger->platformName = "GB-Z80"; debugger->platformCommands = _lr35902Commands; } diff --git a/src/lr35902/debugger/debugger.c b/src/lr35902/debugger/debugger.c index ae6345b79..53ae0ea57 100644 --- a/src/lr35902/debugger/debugger.c +++ b/src/lr35902/debugger/debugger.c @@ -50,6 +50,8 @@ static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t a static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool LR35902DebuggerHasBreakpoints(struct mDebuggerPlatform*); static void LR35902DebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); +static bool LR35902DebuggerGetRegister(struct mDebuggerPlatform*, const char* name, int32_t* value); +static bool LR35902DebuggerSetRegister(struct mDebuggerPlatform*, const char* name, int32_t value); struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct LR35902Debugger)); @@ -63,6 +65,8 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { platform->checkBreakpoints = LR35902DebuggerCheckBreakpoints; platform->hasBreakpoints = LR35902DebuggerHasBreakpoints; platform->trace = LR35902DebuggerTrace; + platform->getRegister = LR35902DebuggerGetRegister; + platform->setRegister = LR35902DebuggerSetRegister; return platform; } @@ -168,3 +172,131 @@ static void LR35902DebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* cpu->d, cpu->e, cpu->h, cpu->l, cpu->sp, cpu->pc, disassembly); } + +bool LR35902DebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902Core* cpu = debugger->cpu; + + if (strcmp(name, "a") == 0) { + *value = cpu->a; + return true; + } + if (strcmp(name, "b") == 0) { + *value = cpu->b; + return true; + } + if (strcmp(name, "c") == 0) { + *value = cpu->c; + return true; + } + if (strcmp(name, "d") == 0) { + *value = cpu->d; + return true; + } + if (strcmp(name, "e") == 0) { + *value = cpu->e; + return true; + } + if (strcmp(name, "h") == 0) { + *value = cpu->h; + return true; + } + if (strcmp(name, "l") == 0) { + *value = cpu->l; + return true; + } + if (strcmp(name, "bc") == 0) { + *value = cpu->bc; + return true; + } + if (strcmp(name, "de") == 0) { + *value = cpu->de; + return true; + } + if (strcmp(name, "hl") == 0) { + *value = cpu->hl; + return true; + } + if (strcmp(name, "af") == 0) { + *value = cpu->af; + return true; + } + if (strcmp(name, "pc") == 0) { + *value = cpu->pc; + return true; + } + if (strcmp(name, "sp") == 0) { + *value = cpu->sp; + return true; + } + if (strcmp(name, "f") == 0) { + *value = cpu->f.packed; + return true; + } + return false; +} + +bool LR35902DebuggerSetRegister(struct mDebuggerPlatform* d, const char* name, int32_t value) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902Core* cpu = debugger->cpu; + + if (strcmp(name, "a") == 0) { + cpu->a = value; + return true; + } + if (strcmp(name, "b") == 0) { + cpu->b = value; + return true; + } + if (strcmp(name, "c") == 0) { + cpu->c = value; + return true; + } + if (strcmp(name, "d") == 0) { + cpu->d = value; + return true; + } + if (strcmp(name, "e") == 0) { + cpu->e = value; + return true; + } + if (strcmp(name, "h") == 0) { + cpu->h = value; + return true; + } + if (strcmp(name, "l") == 0) { + cpu->l = value; + return true; + } + if (strcmp(name, "bc") == 0) { + cpu->bc = value; + return true; + } + if (strcmp(name, "de") == 0) { + cpu->de = value; + return true; + } + if (strcmp(name, "hl") == 0) { + cpu->hl = value; + return true; + } + if (strcmp(name, "af") == 0) { + cpu->af = value; + cpu->f.packed &= 0xF0; + return true; + } + if (strcmp(name, "pc") == 0) { + cpu->pc = value; + cpu->memory.setActiveRegion(cpu, cpu->pc); + return true; + } + if (strcmp(name, "sp") == 0) { + cpu->sp = value; + return true; + } + if (strcmp(name, "f") == 0) { + cpu->f.packed = value & 0xF0; + return true; + } + return false; +} From e1be18a8ff4756f7f47f1e7836d4efd3b5e168eb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 13 Oct 2017 00:31:15 -0700 Subject: [PATCH 011/152] GBA DMA: ROM reads are forced to increment --- CHANGES | 1 + src/gba/dma.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 282ce64fa..076601e3f 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Bugfixes: - GB: Revamp IRQ handling based on new information - GBA Video: Don't mask out high bits of BLDY (fixes mgba.io/i/899) - GBA Video: Force align 256-color tiles + - GBA DMA: ROM reads are forced to increment Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/dma.c b/src/gba/dma.c index 5c5499ebf..93c79edb3 100644 --- a/src/gba/dma.c +++ b/src/gba/dma.c @@ -81,6 +81,9 @@ uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) { if (!wasEnabled && GBADMARegisterIsEnable(currentDma->reg)) { currentDma->nextSource = currentDma->source; + if (currentDma->nextSource >= BASE_CART0 && currentDma->nextSource < BASE_CART_SRAM && GBADMARegisterGetSrcControl(currentDma->reg) < 3) { + currentDma->reg = GBADMARegisterClearSrcControl(currentDma->reg); + } currentDma->nextDest = currentDma->dest; GBADMASchedule(gba, dma, currentDma); } From 22807c6274057e4bac42de986c182db864542ffb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 13 Oct 2017 18:27:05 -0700 Subject: [PATCH 012/152] Third-Party: Increase max ini section name length --- src/core/input.c | 2 +- src/third-party/inih/ini.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/input.c b/src/core/input.c index 6d391ee3b..fe24d0822 100644 --- a/src/core/input.c +++ b/src/core/input.c @@ -11,7 +11,7 @@ #include -#define SECTION_NAME_MAX 50 +#define SECTION_NAME_MAX 128 #define KEY_NAME_MAX 32 #define KEY_VALUE_MAX 16 #define AXIS_INFO_MAX 12 diff --git a/src/third-party/inih/ini.c b/src/third-party/inih/ini.c index 2c278afaa..932efba8c 100755 --- a/src/third-party/inih/ini.c +++ b/src/third-party/inih/ini.c @@ -21,8 +21,8 @@ https://github.com/benhoyt/inih #include #endif -#define MAX_SECTION 50 -#define MAX_NAME 50 +#define MAX_SECTION 128 +#define MAX_NAME 128 /* Strip whitespace chars off end of given string, in place. Return s. */ static char* rstrip(char* s) From b920758dc10371c79cede9374a14b1d2386f8d5d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 13 Oct 2017 18:40:49 -0700 Subject: [PATCH 013/152] Qt: Fix up override view --- src/platform/qt/CoreController.cpp | 4 ++++ src/platform/qt/CoreController.h | 1 + src/platform/qt/OverrideView.cpp | 18 +++++++++++++++++- src/platform/qt/OverrideView.h | 3 +++ src/platform/qt/Window.cpp | 1 + 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 0860e0866..8cf21da00 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -209,6 +209,10 @@ bool CoreController::isPaused() { return mCoreThreadIsPaused(&m_threadContext); } +bool CoreController::hasStarted() { + return mCoreThreadHasStarted(&m_threadContext); +} + mPlatform CoreController::platform() const { return m_threadContext.core->platform(m_threadContext.core); } diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index c426aa483..c2fb9a9e8 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -61,6 +61,7 @@ public: color_t* drawContext(); bool isPaused(); + bool hasStarted(); mPlatform platform() const; QSize screenDimensions() const; diff --git a/src/platform/qt/OverrideView.cpp b/src/platform/qt/OverrideView.cpp index c9e68c68c..1259761ee 100644 --- a/src/platform/qt/OverrideView.cpp +++ b/src/platform/qt/OverrideView.cpp @@ -93,13 +93,16 @@ OverrideView::OverrideView(ConfigController* config, QWidget* parent) connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &OverrideView::saveOverride); connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); + + m_recheck.setInterval(200); + connect(&m_recheck, &QTimer::timeout, this, &OverrideView::recheck); } void OverrideView::setController(std::shared_ptr controller) { m_controller = controller; connect(controller.get(), &CoreController::started, this, &OverrideView::gameStarted); connect(controller.get(), &CoreController::stopping, this, &OverrideView::gameStopped); - updateOverrides(); + recheck(); } void OverrideView::saveOverride() { @@ -115,6 +118,17 @@ void OverrideView::saveOverride() { m_config->saveOverride(*override); } +void OverrideView::recheck() { + if (!m_controller) { + return; + } + if (m_controller->hasStarted()) { + gameStarted(); + } else { + updateOverrides(); + } +} + void OverrideView::updateOverrides() { if (!m_controller) { return; @@ -190,6 +204,7 @@ void OverrideView::gameStarted() { mCoreThread* thread = m_controller->thread(); m_ui.tabWidget->setEnabled(false); + m_recheck.start(); switch (thread->core->platform(thread->core)) { #ifdef M_CORE_GBA @@ -242,6 +257,7 @@ void OverrideView::gameStarted() { } void OverrideView::gameStopped() { + m_recheck.stop(); m_controller.reset(); m_ui.tabWidget->setEnabled(true); m_ui.savetype->setCurrentIndex(0); diff --git a/src/platform/qt/OverrideView.h b/src/platform/qt/OverrideView.h index 5a877b174..cbface71c 100644 --- a/src/platform/qt/OverrideView.h +++ b/src/platform/qt/OverrideView.h @@ -6,6 +6,7 @@ #pragma once #include +#include #include @@ -36,6 +37,7 @@ public: public slots: void saveOverride(); + void recheck(); private slots: void updateOverrides(); @@ -48,6 +50,7 @@ private: std::shared_ptr m_controller; ConfigController* m_config; bool m_savePending = false; + QTimer m_recheck; #ifdef M_CORE_GB uint32_t m_gbColors[12]{}; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 37f5e5887..7c83ca06d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1442,6 +1442,7 @@ void Window::setupMenu(QMenuBar* menubar) { connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close); } m_overrideView->show(); + m_overrideView->recheck(); }); addControlledAction(toolsMenu, overrides, "overrideWindow"); From 264f238ec31b3c29e3bcd84fe561d1d32d5f57e2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Oct 2017 10:02:48 -0700 Subject: [PATCH 014/152] GBA: Add override so aging cart EEPROM works --- src/gba/overrides.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gba/overrides.c b/src/gba/overrides.c index 915d220e3..1a1e9c9ec 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -180,6 +180,9 @@ static const struct GBACartridgeOverride _overrides[] = { { "KYGE", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE, false }, { "KYGP", SAVEDATA_EEPROM, HW_TILT, IDLE_LOOP_NONE, false }, + // Aging cartridge + { "TCHK", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, + { { 0, 0, 0, 0 }, 0, 0, IDLE_LOOP_NONE, false } }; From 591ab468e9df1fe7c1ab06239e2973c99401fbaa Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Oct 2017 15:36:51 -0700 Subject: [PATCH 015/152] Core: Start improving memory search --- include/mgba/core/mem-search.h | 12 +- src/core/mem-search.c | 182 ++++++++++++++++--------------- src/platform/qt/MemorySearch.cpp | 98 +++++++++-------- 3 files changed, 151 insertions(+), 141 deletions(-) diff --git a/include/mgba/core/mem-search.h b/include/mgba/core/mem-search.h index 57084b4ae..a9a3a8223 100644 --- a/include/mgba/core/mem-search.h +++ b/include/mgba/core/mem-search.h @@ -13,9 +13,7 @@ CXX_GUARD_START #include enum mCoreMemorySearchType { - mCORE_MEMORY_SEARCH_32, - mCORE_MEMORY_SEARCH_16, - mCORE_MEMORY_SEARCH_8, + mCORE_MEMORY_SEARCH_INT, mCORE_MEMORY_SEARCH_STRING, mCORE_MEMORY_SEARCH_GUESS, }; @@ -23,11 +21,11 @@ enum mCoreMemorySearchType { struct mCoreMemorySearchParams { int memoryFlags; enum mCoreMemorySearchType type; + int align; + int width; union { const char* valueStr; - uint32_t value32; - uint32_t value16; - uint32_t value8; + uint32_t valueInt; }; }; @@ -35,7 +33,9 @@ struct mCoreMemorySearchResult { uint32_t address; int segment; uint64_t guessDivisor; + uint64_t guessMultiplier; enum mCoreMemorySearchType type; + int width; }; DECLARE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult); diff --git a/src/core/mem-search.c b/src/core/mem-search.c index 5cf74140f..1c47b633c 100644 --- a/src/core/mem-search.c +++ b/src/core/mem-search.c @@ -29,33 +29,41 @@ static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBl if ((mask & 1) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; - res->type = mCORE_MEMORY_SEARCH_32; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 4; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 2) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_32; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 4; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 4) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 8; - res->type = mCORE_MEMORY_SEARCH_32; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 4; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 8) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 12; - res->type = mCORE_MEMORY_SEARCH_32; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 4; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } } @@ -86,65 +94,81 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl if ((mask & 1) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 2) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 2; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 4) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 8) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 6; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 16) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 8; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 32) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 10; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 64) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 12; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 128) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 14; - res->type = mCORE_MEMORY_SEARCH_16; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 2; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } } @@ -174,65 +198,81 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo if ((mask & 1) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 2) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 1; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 4) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 2; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 8) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 3; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 16) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 32) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 5; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 64) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 6; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } if ((mask & 128) && (!limit || found < limit)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i + 7; - res->type = mCORE_MEMORY_SEARCH_8; + res->type = mCORE_MEMORY_SEARCH_INT; + res->width = 1; res->segment = -1; // TODO res->guessDivisor = 1; + res->guessMultiplier = 1; ++found; } } @@ -240,18 +280,32 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo return found; } -static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _searchInt(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { + if (params->align == params->width || params->align == -1) { + switch (params->width) { + case 4: + return _search32(mem, size, block, params->valueInt, out, limit); + case 2: + return _search16(mem, size, block, params->valueInt, out, limit); + case 1: + return _search8(mem, size, block, params->valueInt, out, limit); + } + } + return 0; +} + +static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, int len, struct mCoreMemorySearchResults* out, size_t limit) { const char* memStr = mem; size_t found = 0; - size_t len = strlen(valueStr); uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; for (i = 0; (!limit || found < limit) && i < end - len; ++i) { - if (!strncmp(valueStr, &memStr[i], len)) { + if (!memcmp(valueStr, &memStr[i], len)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; res->type = mCORE_MEMORY_SEARCH_STRING; + res->width = len; res->segment = -1; // TODO ++found; } @@ -342,14 +396,10 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor static size_t _search(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { switch (params->type) { - case mCORE_MEMORY_SEARCH_32: - return _search32(mem, size, block, params->value32, out, limit); - case mCORE_MEMORY_SEARCH_16: - return _search16(mem, size, block, params->value16, out, limit); - case mCORE_MEMORY_SEARCH_8: - return _search8(mem, size, block, params->value8, out, limit); + case mCORE_MEMORY_SEARCH_INT: + return _searchInt(mem, size, block, params, out, limit); case mCORE_MEMORY_SEARCH_STRING: - return _searchStr(mem, size, block, params->valueStr, out, limit); + return _searchStr(mem, size, block, params->valueStr, params->width, out, limit); case mCORE_MEMORY_SEARCH_GUESS: return _searchGuess(mem, size, block, params->valueStr, out, limit); } @@ -384,26 +434,26 @@ bool _testGuess(struct mCore* core, const struct mCoreMemorySearchResult* res, c value = strtoull(params->valueStr, &end, 10); if (end) { - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { return true; } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { return true; } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { return true; } } value = strtoull(params->valueStr, &end, 16); if (end) { - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor == value) { + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { return true; } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { return true; } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor == value) { + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { return true; } } @@ -415,69 +465,27 @@ void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchP for (i = 0; i < mCoreMemorySearchResultsSize(inout); ++i) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(inout, i); switch (res->type) { - case mCORE_MEMORY_SEARCH_8: - switch (params->type) { - case mCORE_MEMORY_SEARCH_8: - if (core->rawRead8(core, res->address, res->segment) != params->value8) { + case mCORE_MEMORY_SEARCH_INT: + switch (params->width) { + case 1: + if (core->rawRead8(core, res->address, res->segment) != params->valueInt) { mCoreMemorySearchResultsShift(inout, i, 1); --i; } break; - case mCORE_MEMORY_SEARCH_16: - if (core->rawRead8(core, res->address, res->segment) != params->value16) { + case 2: + if (core->rawRead8(core, res->address, res->segment) != params->valueInt) { mCoreMemorySearchResultsShift(inout, i, 1); --i; } break; - case mCORE_MEMORY_SEARCH_32: - if (core->rawRead32(core, res->address, res->segment) != params->value32) { + case 4: + if (core->rawRead32(core, res->address, res->segment) != params->valueInt) { mCoreMemorySearchResultsShift(inout, i, 1); --i; } break; - case mCORE_MEMORY_SEARCH_GUESS: - if (!_testGuess(core, res, params)) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - default: - break; - } - break; - case mCORE_MEMORY_SEARCH_16: - switch (params->type) { - case mCORE_MEMORY_SEARCH_16: - if (core->rawRead16(core, res->address, res->segment) != params->value16) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case mCORE_MEMORY_SEARCH_32: - if (core->rawRead32(core, res->address, res->segment) != params->value32) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case mCORE_MEMORY_SEARCH_GUESS: - if (!_testGuess(core, res, params)) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - default: - break; - } - break; - case mCORE_MEMORY_SEARCH_32: - switch (params->type) { - case mCORE_MEMORY_SEARCH_32: - if (core->rawRead32(core, res->address, res->segment) != params->value32) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case mCORE_MEMORY_SEARCH_GUESS: + case -1: if (!_testGuess(core, res, params)) { mCoreMemorySearchResultsShift(inout, i, 1); --i; diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 2230f00aa..c7c1d9373 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -41,49 +41,48 @@ bool MemorySearch::createParams(mCoreMemorySearchParams* params) { QByteArray string; bool ok = false; if (m_ui.typeNum->isChecked()) { + params->type = mCORE_MEMORY_SEARCH_INT; + params->align = -1; if (m_ui.bits8->isChecked()) { - params->type = mCORE_MEMORY_SEARCH_8; + params->width = 1; } if (m_ui.bits16->isChecked()) { - params->type = mCORE_MEMORY_SEARCH_16; + params->width = 2; } if (m_ui.bits32->isChecked()) { - params->type = mCORE_MEMORY_SEARCH_32; + params->width = 4; } if (m_ui.numHex->isChecked()) { uint32_t v = m_ui.value->text().toUInt(&ok, 16); if (ok) { - switch (params->type) { - case mCORE_MEMORY_SEARCH_8: + params->valueInt = v; + switch (params->width) { + case 1: ok = v < 0x100; - params->value8 = v; break; - case mCORE_MEMORY_SEARCH_16: + case 2: ok = v < 0x10000; - params->value16 = v; break; - case mCORE_MEMORY_SEARCH_32: - params->value32 = v; + case 4: break; default: ok = false; + break; } } } if (m_ui.numDec->isChecked()) { uint32_t v = m_ui.value->text().toUInt(&ok, 10); if (ok) { - switch (params->type) { - case mCORE_MEMORY_SEARCH_8: + params->valueInt = v; + switch (params->width) { + case 1: ok = v < 0x100; - params->value8 = v; break; - case mCORE_MEMORY_SEARCH_16: + case 2: ok = v < 0x10000; - params->value16 = v; break; - case mCORE_MEMORY_SEARCH_32: - params->value32 = v; + case 4: break; default: ok = false; @@ -101,6 +100,7 @@ bool MemorySearch::createParams(mCoreMemorySearchParams* params) { params->type = mCORE_MEMORY_SEARCH_STRING; m_string = m_ui.value->text().toLocal8Bit(); params->valueStr = m_string.constData(); + params->width = m_ui.value->text().size(); ok = true; } return ok; @@ -145,61 +145,63 @@ void MemorySearch::refresh() { QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0'))); m_ui.results->setItem(i, 0, item); QTableWidgetItem* type; - if (m_ui.numHex->isChecked()) { - switch (result->type) { - case mCORE_MEMORY_SEARCH_8: + QByteArray string; + if (result->type == mCORE_MEMORY_SEARCH_INT && m_ui.numHex->isChecked()) { + switch (result->width) { + case 1: item = new QTableWidgetItem(QString("%1").arg(core->rawRead8(core, result->address, result->segment), 2, 16, QChar('0'))); break; - case mCORE_MEMORY_SEARCH_16: + case 2: item = new QTableWidgetItem(QString("%1").arg(core->rawRead16(core, result->address, result->segment), 4, 16, QChar('0'))); break; - case mCORE_MEMORY_SEARCH_GUESS: - case mCORE_MEMORY_SEARCH_32: + case 4: item = new QTableWidgetItem(QString("%1").arg(core->rawRead32(core, result->address, result->segment), 8, 16, QChar('0'))); break; - case mCORE_MEMORY_SEARCH_STRING: - item = new QTableWidgetItem("?"); // TODO } } else { switch (result->type) { - case mCORE_MEMORY_SEARCH_8: - item = new QTableWidgetItem(QString::number(core->rawRead8(core, result->address, result->segment))); - break; - case mCORE_MEMORY_SEARCH_16: - item = new QTableWidgetItem(QString::number(core->rawRead16(core, result->address, result->segment))); - break; - case mCORE_MEMORY_SEARCH_GUESS: - case mCORE_MEMORY_SEARCH_32: - item = new QTableWidgetItem(QString::number(core->rawRead32(core, result->address, result->segment))); + case mCORE_MEMORY_SEARCH_INT: + switch (result->width) { + case 1: + item = new QTableWidgetItem(QString::number(core->rawRead8(core, result->address, result->segment))); + break; + case 2: + item = new QTableWidgetItem(QString::number(core->rawRead16(core, result->address, result->segment))); + break; + case 4: + item = new QTableWidgetItem(QString::number(core->rawRead32(core, result->address, result->segment))); + break; + } break; case mCORE_MEMORY_SEARCH_STRING: - item = new QTableWidgetItem("?"); // TODO + string.reserve(result->width); + for (int i = 0; i < result->width; ++i) { + string.append(core->rawRead8(core, result->address + i, result->segment)); + } + item = new QTableWidgetItem(QLatin1String(string)); // TODO } } QString divisor; if (result->guessDivisor > 1) { - divisor = tr(" (⅟%0×)").arg(result->guessDivisor); + if (result->guessMultiplier > 1) { + divisor = tr(" (%0/%1×)").arg(result->guessMultiplier).arg(result->guessMultiplier); + } else { + divisor = tr(" (⅟%0×)").arg(result->guessDivisor); + } + } else if (result->guessMultiplier > 1) { + divisor = tr(" (%0×)").arg(result->guessMultiplier); } switch (result->type) { - case mCORE_MEMORY_SEARCH_8: - type = new QTableWidgetItem(tr("1 byte%0").arg(divisor)); - break; - case mCORE_MEMORY_SEARCH_16: - type = new QTableWidgetItem(tr("2 bytes%0").arg(divisor)); - break; - case mCORE_MEMORY_SEARCH_GUESS: - case mCORE_MEMORY_SEARCH_32: - type = new QTableWidgetItem(tr("4 bytes%0").arg(divisor)); + case mCORE_MEMORY_SEARCH_INT: + type = new QTableWidgetItem(tr("%1 byte%2").arg(result->width).arg(divisor)); break; case mCORE_MEMORY_SEARCH_STRING: - item = new QTableWidgetItem("?"); // TODO + type = new QTableWidgetItem("string"); } m_ui.results->setItem(i, 1, item); m_ui.results->setItem(i, 2, type); } m_ui.results->sortItems(0); - m_ui.results->resizeColumnsToContents(); - m_ui.results->resizeRowsToContents(); } void MemorySearch::openMemory() { From db9725a563710b73baaa5580371143706bb7c627 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Oct 2017 16:18:19 -0700 Subject: [PATCH 016/152] Core: Add memory delta search --- include/mgba/core/mem-search.h | 13 +++- src/core/mem-search.c | 119 +++++++++++++++++++------------ src/platform/qt/MemorySearch.cpp | 8 ++- src/platform/qt/MemorySearch.ui | 49 ++++++++++--- 4 files changed, 130 insertions(+), 59 deletions(-) diff --git a/include/mgba/core/mem-search.h b/include/mgba/core/mem-search.h index a9a3a8223..b879d6f09 100644 --- a/include/mgba/core/mem-search.h +++ b/include/mgba/core/mem-search.h @@ -18,24 +18,31 @@ enum mCoreMemorySearchType { mCORE_MEMORY_SEARCH_GUESS, }; +enum mCoreMemorySearchOp { + mCORE_MEMORY_SEARCH_FIXED, + mCORE_MEMORY_SEARCH_DELTA, +}; + struct mCoreMemorySearchParams { int memoryFlags; enum mCoreMemorySearchType type; + enum mCoreMemorySearchOp op; int align; int width; union { const char* valueStr; - uint32_t valueInt; + int32_t valueInt; }; }; struct mCoreMemorySearchResult { uint32_t address; int segment; - uint64_t guessDivisor; - uint64_t guessMultiplier; + uint32_t guessDivisor; + uint32_t guessMultiplier; enum mCoreMemorySearchType type; int width; + int32_t oldValue; }; DECLARE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult); diff --git a/src/core/mem-search.c b/src/core/mem-search.c index 1c47b633c..1b0ce9ae9 100644 --- a/src/core/mem-search.c +++ b/src/core/mem-search.c @@ -34,6 +34,7 @@ static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value32; ++found; } if ((mask & 2) && (!limit || found < limit)) { @@ -44,6 +45,7 @@ static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value32; ++found; } if ((mask & 4) && (!limit || found < limit)) { @@ -54,6 +56,7 @@ static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value32; ++found; } if ((mask & 8) && (!limit || found < limit)) { @@ -64,6 +67,7 @@ static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value32; ++found; } } @@ -99,6 +103,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } if ((mask & 2) && (!limit || found < limit)) { @@ -109,6 +114,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } if ((mask & 4) && (!limit || found < limit)) { @@ -119,6 +125,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } if ((mask & 8) && (!limit || found < limit)) { @@ -129,6 +136,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } if ((mask & 16) && (!limit || found < limit)) { @@ -139,6 +147,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } if ((mask & 32) && (!limit || found < limit)) { @@ -149,6 +158,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } if ((mask & 64) && (!limit || found < limit)) { @@ -159,6 +169,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } if ((mask & 128) && (!limit || found < limit)) { @@ -169,6 +180,7 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value16; ++found; } } @@ -203,6 +215,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } if ((mask & 2) && (!limit || found < limit)) { @@ -213,6 +226,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } if ((mask & 4) && (!limit || found < limit)) { @@ -223,6 +237,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } if ((mask & 8) && (!limit || found < limit)) { @@ -233,6 +248,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } if ((mask & 16) && (!limit || found < limit)) { @@ -243,6 +259,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } if ((mask & 32) && (!limit || found < limit)) { @@ -253,6 +270,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } if ((mask & 64) && (!limit || found < limit)) { @@ -263,6 +281,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } if ((mask & 128) && (!limit || found < limit)) { @@ -273,6 +292,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->segment = -1; // TODO res->guessDivisor = 1; res->guessMultiplier = 1; + res->oldValue = value8; ++found; } } @@ -317,7 +337,7 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor // TODO: As str char* end; - uint64_t value; + int64_t value; size_t found = 0; @@ -325,7 +345,7 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor mCoreMemorySearchResultsInit(&tmp, 0); // Decimal: - value = strtoull(valueStr, &end, 10); + value = strtoll(valueStr, &end, 10); if (end && !end[0]) { if (value > 0x10000) { found += _search32(mem, size, block, value, out, limit ? limit - found : 0); @@ -358,7 +378,7 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor } // Hex: - value = strtoull(valueStr, &end, 16); + value = strtoll(valueStr, &end, 16); if (end && !end[0]) { if (value > 0x10000) { found += _search32(mem, size, block, value, out, limit ? limit - found : 0); @@ -428,34 +448,42 @@ void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* } } -bool _testGuess(struct mCore* core, const struct mCoreMemorySearchResult* res, const struct mCoreMemorySearchParams* params) { - uint64_t value; +bool _testGuess(struct mCore* core, struct mCoreMemorySearchResult* res, const struct mCoreMemorySearchParams* params) { + int64_t value; + int32_t offset = 0; char* end; - - value = strtoull(params->valueStr, &end, 10); - if (end) { - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { - return true; - } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { - return true; - } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { - return true; - } + if (params->op == mCORE_MEMORY_SEARCH_DELTA) { + offset = res->oldValue; } - value = strtoull(params->valueStr, &end, 16); + value = strtoll(params->valueStr, &end, 10); if (end) { - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { + res->oldValue += value; + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { return true; } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { return true; } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value) { + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { return true; } + res->oldValue -= value; + } + + value = strtoll(params->valueStr, &end, 16); + if (end) { + res->oldValue += value; + if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + return true; + } + if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + return true; + } + if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + return true; + } + res->oldValue -= value; } return false; } @@ -466,33 +494,36 @@ void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchP struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(inout, i); switch (res->type) { case mCORE_MEMORY_SEARCH_INT: - switch (params->width) { - case 1: - if (core->rawRead8(core, res->address, res->segment) != params->valueInt) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case 2: - if (core->rawRead8(core, res->address, res->segment) != params->valueInt) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case 4: - if (core->rawRead32(core, res->address, res->segment) != params->valueInt) { - mCoreMemorySearchResultsShift(inout, i, 1); - --i; - } - break; - case -1: + if (params->type == mCORE_MEMORY_SEARCH_GUESS) { if (!_testGuess(core, res, params)) { mCoreMemorySearchResultsShift(inout, i, 1); --i; } - break; - default: - break; + } else if (params->type == mCORE_MEMORY_SEARCH_INT) { + int32_t oldValue = params->valueInt; + if (params->op == mCORE_MEMORY_SEARCH_DELTA) { + oldValue += res->oldValue; + } + int32_t value = 0; + switch (params->width) { + case 1: + value = core->rawRead8(core, res->address, res->segment); + break; + case 2: + value = core->rawRead16(core, res->address, res->segment); + break; + case 4: + value = core->rawRead32(core, res->address, res->segment); + break; + default: + break; + } + if (value != oldValue) { + mCoreMemorySearchResultsShift(inout, i, 1); + --i; + } else { + res->oldValue = value; + } } break; case mCORE_MEMORY_SEARCH_STRING: diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index c7c1d9373..7c06791dd 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -40,8 +40,9 @@ bool MemorySearch::createParams(mCoreMemorySearchParams* params) { QByteArray string; bool ok = false; - if (m_ui.typeNum->isChecked()) { + if (m_ui.typeNum->isChecked() || m_ui.typeDelta->isChecked()) { params->type = mCORE_MEMORY_SEARCH_INT; + params->op = m_ui.typeDelta->isChecked() ? mCORE_MEMORY_SEARCH_DELTA : mCORE_MEMORY_SEARCH_FIXED; params->align = -1; if (m_ui.bits8->isChecked()) { params->width = 1; @@ -140,6 +141,7 @@ void MemorySearch::refresh() { m_ui.results->clearContents(); m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&m_results)); + m_ui.typeDelta->setEnabled(false); for (size_t i = 0; i < mCoreMemorySearchResultsSize(&m_results); ++i) { mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i); QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0'))); @@ -200,6 +202,10 @@ void MemorySearch::refresh() { } m_ui.results->setItem(i, 1, item); m_ui.results->setItem(i, 2, type); + m_ui.typeDelta->setEnabled(true); + } + if (m_ui.typeDelta->isChecked() && !m_ui.typeDelta->isEnabled()) { + m_ui.typeNum->setChecked(true); } m_ui.results->sortItems(0); } diff --git a/src/platform/qt/MemorySearch.ui b/src/platform/qt/MemorySearch.ui index ccbe07076..678405867 100644 --- a/src/platform/qt/MemorySearch.ui +++ b/src/platform/qt/MemorySearch.ui @@ -6,8 +6,8 @@ 0 0 - 631 - 378 + 639 + 397 @@ -89,7 +89,7 @@ - + Text @@ -99,14 +99,14 @@ - + Width - + 1 Byte (8-bit) @@ -116,7 +116,7 @@ - + 2 Bytes (16-bit) @@ -126,7 +126,7 @@ - + 4 Bytes (32-bit) @@ -139,14 +139,14 @@ - + Number type - + Hexadecimal @@ -156,20 +156,47 @@ - + Decimal - + Guess + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + false + + + Delta + + + type + + + From 8385869652605c3a6f147134bda3c9ea9b220134 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Oct 2017 17:13:06 -0700 Subject: [PATCH 017/152] Core: Add additional memory search operations --- include/mgba/core/mem-search.h | 4 +- src/core/mem-search.c | 310 +++++-------------------------- src/platform/qt/MemorySearch.cpp | 20 +- src/platform/qt/MemorySearch.ui | 146 ++++++++++----- 4 files changed, 165 insertions(+), 315 deletions(-) diff --git a/include/mgba/core/mem-search.h b/include/mgba/core/mem-search.h index b879d6f09..c18c20922 100644 --- a/include/mgba/core/mem-search.h +++ b/include/mgba/core/mem-search.h @@ -19,7 +19,9 @@ enum mCoreMemorySearchType { }; enum mCoreMemorySearchOp { - mCORE_MEMORY_SEARCH_FIXED, + mCORE_MEMORY_SEARCH_EQUAL, + mCORE_MEMORY_SEARCH_GREATER, + mCORE_MEMORY_SEARCH_LESS, mCORE_MEMORY_SEARCH_DELTA, }; diff --git a/src/core/mem-search.c b/src/core/mem-search.c index 1b0ce9ae9..dbe8f94e6 100644 --- a/src/core/mem-search.c +++ b/src/core/mem-search.c @@ -10,23 +10,27 @@ DEFINE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult); -static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint32_t value32, struct mCoreMemorySearchResults* out, size_t limit) { +static bool _op(int32_t value, int32_t match, enum mCoreMemorySearchOp op) { + switch (op) { + case mCORE_MEMORY_SEARCH_GREATER: + return value > match; + case mCORE_MEMORY_SEARCH_LESS: + return value < match; + case mCORE_MEMORY_SEARCH_EQUAL: + case mCORE_MEMORY_SEARCH_DELTA: + return value == match; + } +} + +static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint32_t value32, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) { const uint32_t* mem32 = mem; size_t found = 0; uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; // TODO: Big endian - for (i = 0; (!limit || found < limit) && i < end; i += 16) { - int mask = 0; - mask |= (mem32[(i >> 2) + 0] == value32) << 0; - mask |= (mem32[(i >> 2) + 1] == value32) << 1; - mask |= (mem32[(i >> 2) + 2] == value32) << 2; - mask |= (mem32[(i >> 2) + 3] == value32) << 3; - if (!mask) { - continue; - } - if ((mask & 1) && (!limit || found < limit)) { + for (i = 0; (!limit || found < limit) && i < end; i += 4) { + if (_op(mem32[i >> 2], value32, op)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; res->type = mCORE_MEMORY_SEARCH_INT; @@ -37,65 +41,19 @@ static size_t _search32(const void* mem, size_t size, const struct mCoreMemoryBl res->oldValue = value32; ++found; } - if ((mask & 2) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 4; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value32; - ++found; - } - if ((mask & 4) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 8; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 4; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value32; - ++found; - } - if ((mask & 8) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 12; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 4; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value32; - ++found; - } } - // TODO: last 12 bytes return found; } -static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint16_t value16, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint16_t value16, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) { const uint16_t* mem16 = mem; size_t found = 0; uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; // TODO: Big endian - for (i = 0; (!limit || found < limit) && i < end; i += 16) { - int mask = 0; - mask |= (mem16[(i >> 1) + 0] == value16) << 0; - mask |= (mem16[(i >> 1) + 1] == value16) << 1; - mask |= (mem16[(i >> 1) + 2] == value16) << 2; - mask |= (mem16[(i >> 1) + 3] == value16) << 3; - mask |= (mem16[(i >> 1) + 4] == value16) << 4; - mask |= (mem16[(i >> 1) + 5] == value16) << 5; - mask |= (mem16[(i >> 1) + 6] == value16) << 6; - mask |= (mem16[(i >> 1) + 7] == value16) << 7; - if (!mask) { - continue; - } - if ((mask & 1) && (!limit || found < limit)) { + for (i = 0; (!limit || found < limit) && i < end; i += 2) { + if (_op(mem16[i >> 1], value16, op)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; res->type = mCORE_MEMORY_SEARCH_INT; @@ -106,108 +64,18 @@ static size_t _search16(const void* mem, size_t size, const struct mCoreMemoryBl res->oldValue = value16; ++found; } - if ((mask & 2) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 2; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 2; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value16; - ++found; - } - if ((mask & 4) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 2; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value16; - ++found; - } - if ((mask & 8) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 6; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 2; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value16; - ++found; - } - if ((mask & 16) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 8; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 2; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value16; - ++found; - } - if ((mask & 32) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 10; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 2; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value16; - ++found; - } - if ((mask & 64) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 12; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 2; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value16; - ++found; - } - if ((mask & 128) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 14; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 2; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value16; - ++found; - } } - // TODO: last 14 bytes return found; } -static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint8_t value8, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlock* block, uint8_t value8, enum mCoreMemorySearchOp op, struct mCoreMemorySearchResults* out, size_t limit) { const uint8_t* mem8 = mem; size_t found = 0; uint32_t start = block->start; uint32_t end = size; // TODO: Segments size_t i; - for (i = 0; (!limit || found < limit) && i < end; i += 8) { - int mask = 0; - mask |= (mem8[i + 0] == value8) << 0; - mask |= (mem8[i + 1] == value8) << 1; - mask |= (mem8[i + 2] == value8) << 2; - mask |= (mem8[i + 3] == value8) << 3; - mask |= (mem8[i + 4] == value8) << 4; - mask |= (mem8[i + 5] == value8) << 5; - mask |= (mem8[i + 6] == value8) << 6; - mask |= (mem8[i + 7] == value8) << 7; - if (!mask) { - continue; - } - if ((mask & 1) && (!limit || found < limit)) { + for (i = 0; (!limit || found < limit) && i < end; ++i) { + if (_op(mem8[i], value8, op)) { struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); res->address = start + i; res->type = mCORE_MEMORY_SEARCH_INT; @@ -218,85 +86,7 @@ static size_t _search8(const void* mem, size_t size, const struct mCoreMemoryBlo res->oldValue = value8; ++found; } - if ((mask & 2) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 1; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 1; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value8; - ++found; - } - if ((mask & 4) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 2; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 1; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value8; - ++found; - } - if ((mask & 8) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 3; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 1; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value8; - ++found; - } - if ((mask & 16) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 4; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 1; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value8; - ++found; - } - if ((mask & 32) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 5; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 1; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value8; - ++found; - } - if ((mask & 64) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 6; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 1; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value8; - ++found; - } - if ((mask & 128) && (!limit || found < limit)) { - struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsAppend(out); - res->address = start + i + 7; - res->type = mCORE_MEMORY_SEARCH_INT; - res->width = 1; - res->segment = -1; // TODO - res->guessDivisor = 1; - res->guessMultiplier = 1; - res->oldValue = value8; - ++found; - } } - // TODO: last 7 bytes return found; } @@ -304,11 +94,11 @@ static size_t _searchInt(const void* mem, size_t size, const struct mCoreMemoryB if (params->align == params->width || params->align == -1) { switch (params->width) { case 4: - return _search32(mem, size, block, params->valueInt, out, limit); + return _search32(mem, size, block, params->valueInt, params->op, out, limit); case 2: - return _search16(mem, size, block, params->valueInt, out, limit); + return _search16(mem, size, block, params->valueInt, params->op, out, limit); case 1: - return _search8(mem, size, block, params->valueInt, out, limit); + return _search8(mem, size, block, params->valueInt, params->op, out, limit); } } return 0; @@ -333,7 +123,7 @@ static size_t _searchStr(const void* mem, size_t size, const struct mCoreMemoryB return found; } -static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const char* valueStr, struct mCoreMemorySearchResults* out, size_t limit) { +static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemoryBlock* block, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit) { // TODO: As str char* end; @@ -345,14 +135,14 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor mCoreMemorySearchResultsInit(&tmp, 0); // Decimal: - value = strtoll(valueStr, &end, 10); + value = strtoll(params->valueStr, &end, 10); if (end && !end[0]) { if (value > 0x10000) { - found += _search32(mem, size, block, value, out, limit ? limit - found : 0); + found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else if (value > 0x100) { - found += _search16(mem, size, block, value, out, limit ? limit - found : 0); + found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0); } uint32_t divisor = 1; @@ -362,11 +152,11 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor divisor *= 10; if (value > 0x10000) { - found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else if (value > 0x100) { - found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } size_t i; for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) { @@ -378,14 +168,14 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor } // Hex: - value = strtoll(valueStr, &end, 16); + value = strtoll(params->valueStr, &end, 16); if (end && !end[0]) { if (value > 0x10000) { - found += _search32(mem, size, block, value, out, limit ? limit - found : 0); + found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else if (value > 0x100) { - found += _search16(mem, size, block, value, out, limit ? limit - found : 0); + found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, out, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0); } uint32_t divisor = 1; @@ -395,11 +185,11 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor divisor <<= 4; if (value > 0x10000) { - found += _search32(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else if (value > 0x100) { - found += _search16(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else { - found += _search8(mem, size, block, value, &tmp, limit ? limit - found : 0); + found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } size_t i; for (i = 0; i < mCoreMemorySearchResultsSize(&tmp); ++i) { @@ -421,7 +211,7 @@ static size_t _search(const void* mem, size_t size, const struct mCoreMemoryBloc case mCORE_MEMORY_SEARCH_STRING: return _searchStr(mem, size, block, params->valueStr, params->width, out, limit); case mCORE_MEMORY_SEARCH_GUESS: - return _searchGuess(mem, size, block, params->valueStr, out, limit); + return _searchGuess(mem, size, block, params, out, limit); } } @@ -459,13 +249,13 @@ bool _testGuess(struct mCore* core, struct mCoreMemorySearchResult* res, const s value = strtoll(params->valueStr, &end, 10); if (end) { res->oldValue += value; - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + if (_op(core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + if (!(res->address & 1) && (res->width >= 2 || res->width == -1) && _op(core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + if (!(res->address & 3) && (res->width >= 4 || res->width == -1) && _op(core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } res->oldValue -= value; @@ -474,13 +264,13 @@ bool _testGuess(struct mCore* core, struct mCoreMemorySearchResult* res, const s value = strtoll(params->valueStr, &end, 16); if (end) { res->oldValue += value; - if (core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + if (_op(core->rawRead8(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 1) && core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + if (!(res->address & 1) && (res->width >= 2 || res->width == -1) && _op(core->rawRead16(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } - if (!(res->address & 3) && core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier == value + offset) { + if (!(res->address & 3) && (res->width >= 4 || res->width == -1) && _op(core->rawRead32(core, res->address, res->segment) * res->guessDivisor / res->guessMultiplier, value + offset, params->op)) { return true; } res->oldValue -= value; @@ -496,7 +286,8 @@ void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchP case mCORE_MEMORY_SEARCH_INT: if (params->type == mCORE_MEMORY_SEARCH_GUESS) { if (!_testGuess(core, res, params)) { - mCoreMemorySearchResultsShift(inout, i, 1); + *res = *mCoreMemorySearchResultsGetPointer(inout, mCoreMemorySearchResultsSize(inout) - 1); + mCoreMemorySearchResultsResize(inout, -1); --i; } } else if (params->type == mCORE_MEMORY_SEARCH_INT) { @@ -518,8 +309,9 @@ void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchP default: break; } - if (value != oldValue) { - mCoreMemorySearchResultsShift(inout, i, 1); + if (!_op(value, oldValue, params->op)) { + *res = *mCoreMemorySearchResultsGetPointer(inout, mCoreMemorySearchResultsSize(inout) - 1); + mCoreMemorySearchResultsResize(inout, -1); --i; } else { res->oldValue = value; diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 7c06791dd..934d20573 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -40,9 +40,17 @@ bool MemorySearch::createParams(mCoreMemorySearchParams* params) { QByteArray string; bool ok = false; - if (m_ui.typeNum->isChecked() || m_ui.typeDelta->isChecked()) { + if (m_ui.typeNum->isChecked()) { params->type = mCORE_MEMORY_SEARCH_INT; - params->op = m_ui.typeDelta->isChecked() ? mCORE_MEMORY_SEARCH_DELTA : mCORE_MEMORY_SEARCH_FIXED; + if (m_ui.opDelta->isChecked()) { + params->op = mCORE_MEMORY_SEARCH_DELTA; + } else if (m_ui.opGreater->isChecked()) { + params->op = mCORE_MEMORY_SEARCH_GREATER; + } else if (m_ui.opLess->isChecked()) { + params->op = mCORE_MEMORY_SEARCH_LESS; + } else { + params->op = mCORE_MEMORY_SEARCH_EQUAL; + } params->align = -1; if (m_ui.bits8->isChecked()) { params->width = 1; @@ -141,7 +149,7 @@ void MemorySearch::refresh() { m_ui.results->clearContents(); m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&m_results)); - m_ui.typeDelta->setEnabled(false); + m_ui.opDelta->setEnabled(false); for (size_t i = 0; i < mCoreMemorySearchResultsSize(&m_results); ++i) { mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i); QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0'))); @@ -202,10 +210,10 @@ void MemorySearch::refresh() { } m_ui.results->setItem(i, 1, item); m_ui.results->setItem(i, 2, type); - m_ui.typeDelta->setEnabled(true); + m_ui.opDelta->setEnabled(true); } - if (m_ui.typeDelta->isChecked() && !m_ui.typeDelta->isEnabled()) { - m_ui.typeNum->setChecked(true); + if (m_ui.opDelta->isChecked() && !m_ui.opDelta->isEnabled()) { + m_ui.opEqual->setChecked(true); } m_ui.results->sortItems(0); } diff --git a/src/platform/qt/MemorySearch.ui b/src/platform/qt/MemorySearch.ui index 678405867..4fc18176b 100644 --- a/src/platform/qt/MemorySearch.ui +++ b/src/platform/qt/MemorySearch.ui @@ -6,8 +6,8 @@ 0 0 - 639 - 397 + 540 + 491 @@ -89,7 +89,7 @@ - + Text @@ -99,14 +99,21 @@ - + + + + Qt::Horizontal + + + + Width - + 1 Byte (8-bit) @@ -116,7 +123,7 @@ - + 2 Bytes (16-bit) @@ -126,7 +133,7 @@ - + 4 Bytes (32-bit) @@ -139,53 +146,93 @@ - - - - Number type - - - - - - - Hexadecimal - - - true - - - - - - - Decimal - - - - - - - Guess - - - - - - - Qt::Horizontal - - - - + Qt::Horizontal - - + + + + Number type + + + + + + + Guess + + + true + + + + + + + Decimal + + + + + + + Hexadecimal + + + + + + + Qt::Horizontal + + + + + + + Compare + + + + + + + Equal + + + true + + + op + + + + + + + Greater + + + op + + + + + + + Less + + + op + + + + + false @@ -193,7 +240,7 @@ Delta - type + op @@ -262,5 +309,6 @@ + From db69256ce9f247969ee3194a35b70f7bda52ab52 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Oct 2017 17:22:48 -0700 Subject: [PATCH 018/152] Core: Separate guessing width and type --- src/core/mem-search.c | 16 ++++++------- src/platform/qt/MemorySearch.cpp | 3 +++ src/platform/qt/MemorySearch.ui | 41 +++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/core/mem-search.c b/src/core/mem-search.c index dbe8f94e6..4f007d9ef 100644 --- a/src/core/mem-search.c +++ b/src/core/mem-search.c @@ -137,9 +137,9 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor // Decimal: value = strtoll(params->valueStr, &end, 10); if (end && !end[0]) { - if (value > 0x10000) { + if ((params->width == -1 && value > 0x10000) || params->width == 4) { found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0); - } else if (value > 0x100) { + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else { found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0); @@ -151,9 +151,9 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor value /= 10; divisor *= 10; - if (value > 0x10000) { + if ((params->width == -1 && value > 0x10000) || params->width == 4) { found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); - } else if (value > 0x100) { + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else { found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); @@ -170,9 +170,9 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor // Hex: value = strtoll(params->valueStr, &end, 16); if (end && !end[0]) { - if (value > 0x10000) { + if ((params->width == -1 && value > 0x10000) || params->width == 4) { found += _search32(mem, size, block, value, params->op, out, limit ? limit - found : 0); - } else if (value > 0x100) { + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { found += _search16(mem, size, block, value, params->op, out, limit ? limit - found : 0); } else { found += _search8(mem, size, block, value, params->op, out, limit ? limit - found : 0); @@ -184,9 +184,9 @@ static size_t _searchGuess(const void* mem, size_t size, const struct mCoreMemor value >>= 4; divisor <<= 4; - if (value > 0x10000) { + if ((params->width == -1 && value > 0x10000) || params->width == 4) { found += _search32(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); - } else if (value > 0x100) { + } else if ((params->width == -1 && value > 0x100) || params->width == 2) { found += _search16(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); } else { found += _search8(mem, size, block, value, params->op, &tmp, limit ? limit - found : 0); diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 934d20573..4ff967da5 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -61,6 +61,9 @@ bool MemorySearch::createParams(mCoreMemorySearchParams* params) { if (m_ui.bits32->isChecked()) { params->width = 4; } + if (m_ui.bitsGuess->isChecked()) { + params->width = -1; + } if (m_ui.numHex->isChecked()) { uint32_t v = m_ui.value->text().toUInt(&ok, 16); if (ok) { diff --git a/src/platform/qt/MemorySearch.ui b/src/platform/qt/MemorySearch.ui index 4fc18176b..4e8649e42 100644 --- a/src/platform/qt/MemorySearch.ui +++ b/src/platform/qt/MemorySearch.ui @@ -114,6 +114,19 @@ + + + Guess + + + true + + + width + + + + 1 Byte (8-bit) @@ -123,7 +136,7 @@ - + 2 Bytes (16-bit) @@ -133,34 +146,34 @@ - + 4 Bytes (32-bit) - true + false width - + Qt::Horizontal - + Number type - + Guess @@ -170,35 +183,35 @@ - + Decimal - + Hexadecimal - + Qt::Horizontal - + Compare - + Equal @@ -211,7 +224,7 @@ - + Greater @@ -221,7 +234,7 @@ - + Less @@ -231,7 +244,7 @@ - + false From e820e4dcbb71bd3e7486e9a828a0226075103bc3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Oct 2017 19:07:17 -0700 Subject: [PATCH 019/152] Python: Fix memory search --- src/platform/python/mgba/memory.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/platform/python/mgba/memory.py b/src/platform/python/mgba/memory.py index cb7fedef1..89f8f9098 100644 --- a/src/platform/python/mgba/memory.py +++ b/src/platform/python/mgba/memory.py @@ -100,12 +100,12 @@ class MemorySearchResult(object): class Memory(object): - SEARCH_32 = lib.mCORE_MEMORY_SEARCH_32 - SEARCH_16 = lib.mCORE_MEMORY_SEARCH_16 - SEARCH_8 = lib.mCORE_MEMORY_SEARCH_8 + SEARCH_INT = lib.mCORE_MEMORY_SEARCH_INT SEARCH_STRING = lib.mCORE_MEMORY_SEARCH_STRING SEARCH_GUESS = lib.mCORE_MEMORY_SEARCH_GUESS + SEARCH_EQUAL = lib.mCORE_MEMORY_SEARCH_EQUAL + READ = lib.mCORE_MEMORY_READ WRITE = lib.mCORE_MEMORY_READ RW = lib.mCORE_MEMORY_RW @@ -131,12 +131,9 @@ class Memory(object): params = ffi.new("struct mCoreMemorySearchParams*") params.memoryFlags = flags params.type = type - if type == self.SEARCH_8: - params.value8 = int(value) - elif type == self.SEARCH_16: - params.value16 = int(value) - elif type == self.SEARCH_32: - params.value32 = int(value) + params.op = self.SEARCH_EQUAL + if type == self.SEARCH_INT: + params.valueInt = int(value) else: params.valueStr = ffi.new("char[]", str(value).encode("ascii")) From f69b652e272492884472d8993015d189451138b4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 14 Oct 2017 19:07:28 -0700 Subject: [PATCH 020/152] Python: Fix VFS test --- src/platform/python/tests/mgba/test_vfs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/python/tests/mgba/test_vfs.py b/src/platform/python/tests/mgba/test_vfs.py index 2315e2a32..38cbacb23 100644 --- a/src/platform/python/tests/mgba/test_vfs.py +++ b/src/platform/python/tests/mgba/test_vfs.py @@ -19,7 +19,7 @@ def test_vfs_read(): vf = vfs.openPath(__file__) buffer = ffi.new('char[13]') assert vf.read(buffer, 13) == 13 - assert ffi.string(buffer) == 'import pytest' + assert ffi.string(buffer) == b'import pytest' vf.close() def test_vfs_readline(): @@ -28,9 +28,9 @@ def test_vfs_readline(): linelen = vf.readline(buffer, 16) assert linelen in (14, 15) if linelen == 14: - assert ffi.string(buffer) == 'import pytest\n' + assert ffi.string(buffer) == b'import pytest\n' elif linelen == 15: - assert ffi.string(buffer) == 'import pytest\r\n' + assert ffi.string(buffer) == b'import pytest\r\n' vf.close() def test_vfs_readAllSize(): From b05cfe7764193a0ba11487675bb7e937df479504 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 15 Oct 2017 17:11:24 -0700 Subject: [PATCH 021/152] GBA: Implement display start DMAs --- CHANGES | 1 + include/mgba/internal/gba/dma.h | 37 ++++++++++++++++++++++++++++++ include/mgba/internal/gba/memory.h | 37 +----------------------------- src/gba/audio.c | 4 ++-- src/gba/dma.c | 31 +++++++++++++++++-------- src/gba/io.c | 2 +- src/gba/video.c | 3 +++ 7 files changed, 67 insertions(+), 48 deletions(-) diff --git a/CHANGES b/CHANGES index 076601e3f..80a24221a 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Misc: - Test: Restructure test suite into multiple executables - Python: Integrate tests from cinema test suite - Util: Don't build crc32 if the function already exists + - GBA: Implement display start DMAs 0.6.1: (2017-10-01) Bugfixes: diff --git a/include/mgba/internal/gba/dma.h b/include/mgba/internal/gba/dma.h index fd86829af..6f880c971 100644 --- a/include/mgba/internal/gba/dma.h +++ b/include/mgba/internal/gba/dma.h @@ -10,6 +10,42 @@ CXX_GUARD_START +enum GBADMAControl { + GBA_DMA_INCREMENT = 0, + GBA_DMA_DECREMENT = 1, + GBA_DMA_FIXED = 2, + GBA_DMA_INCREMENT_RELOAD = 3 +}; + +enum GBADMATiming { + GBA_DMA_TIMING_NOW = 0, + GBA_DMA_TIMING_VBLANK = 1, + GBA_DMA_TIMING_HBLANK = 2, + GBA_DMA_TIMING_CUSTOM = 3 +}; + +DECL_BITFIELD(GBADMARegister, uint16_t); +DECL_BITS(GBADMARegister, DestControl, 5, 2); +DECL_BITS(GBADMARegister, SrcControl, 7, 2); +DECL_BIT(GBADMARegister, Repeat, 9); +DECL_BIT(GBADMARegister, Width, 10); +DECL_BIT(GBADMARegister, DRQ, 11); +DECL_BITS(GBADMARegister, Timing, 12, 2); +DECL_BIT(GBADMARegister, DoIRQ, 14); +DECL_BIT(GBADMARegister, Enable, 15); + +struct GBADMA { + GBADMARegister reg; + + uint32_t source; + uint32_t dest; + int32_t count; + uint32_t nextSource; + uint32_t nextDest; + int32_t nextCount; + uint32_t when; +}; + struct GBA; void GBADMAInit(struct GBA* gba); void GBADMAReset(struct GBA* gba); @@ -23,6 +59,7 @@ struct GBADMA; void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info); void GBADMARunHblank(struct GBA* gba, int32_t cycles); void GBADMARunVblank(struct GBA* gba, int32_t cycles); +void GBADMARunDisplayStart(struct GBA* gba, int32_t cycles); void GBADMAUpdate(struct GBA* gba); CXX_GUARD_END diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index 4e9839798..b780bebf2 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -13,6 +13,7 @@ CXX_GUARD_START #include #include +#include #include #include #include @@ -76,44 +77,8 @@ enum { BASE_OFFSET = 24 }; -enum DMAControl { - DMA_INCREMENT = 0, - DMA_DECREMENT = 1, - DMA_FIXED = 2, - DMA_INCREMENT_RELOAD = 3 -}; - -enum DMATiming { - DMA_TIMING_NOW = 0, - DMA_TIMING_VBLANK = 1, - DMA_TIMING_HBLANK = 2, - DMA_TIMING_CUSTOM = 3 -}; - mLOG_DECLARE_CATEGORY(GBA_MEM); -DECL_BITFIELD(GBADMARegister, uint16_t); -DECL_BITS(GBADMARegister, DestControl, 5, 2); -DECL_BITS(GBADMARegister, SrcControl, 7, 2); -DECL_BIT(GBADMARegister, Repeat, 9); -DECL_BIT(GBADMARegister, Width, 10); -DECL_BIT(GBADMARegister, DRQ, 11); -DECL_BITS(GBADMARegister, Timing, 12, 2); -DECL_BIT(GBADMARegister, DoIRQ, 14); -DECL_BIT(GBADMARegister, Enable, 15); - -struct GBADMA { - GBADMARegister reg; - - uint32_t source; - uint32_t dest; - int32_t count; - uint32_t nextSource; - uint32_t nextDest; - int32_t nextCount; - uint32_t when; -}; - struct GBAMemory { uint32_t* bios; uint32_t* wram; diff --git a/src/gba/audio.c b/src/gba/audio.c index be736e78b..87c375433 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -111,7 +111,7 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA* mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest); return; } - info->reg = GBADMARegisterSetDestControl(info->reg, DMA_FIXED); + info->reg = GBADMARegisterSetDestControl(info->reg, GBA_DMA_FIXED); info->reg = GBADMARegisterSetWidth(info->reg, 1); } @@ -235,7 +235,7 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { } if (CircleBufferSize(&channel->fifo) <= 4 * sizeof(int32_t) && channel->dmaSource > 0) { struct GBADMA* dma = &audio->p->memory.dma[channel->dmaSource]; - if (GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_CUSTOM) { + if (GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM) { dma->when = mTimingCurrentTime(&audio->p->timing) - cycles; dma->nextCount = 4; GBADMASchedule(audio->p, channel->dmaSource, dma); diff --git a/src/gba/dma.c b/src/gba/dma.c index 93c79edb3..e2d67d5b7 100644 --- a/src/gba/dma.c +++ b/src/gba/dma.c @@ -93,15 +93,15 @@ uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) { void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) { switch (GBADMARegisterGetTiming(info->reg)) { - case DMA_TIMING_NOW: + case GBA_DMA_TIMING_NOW: info->when = mTimingCurrentTime(&gba->timing) + 3; // DMAs take 3 cycles to start info->nextCount = info->count; break; - case DMA_TIMING_HBLANK: - case DMA_TIMING_VBLANK: + case GBA_DMA_TIMING_HBLANK: + case GBA_DMA_TIMING_VBLANK: // Handled implicitly return; - case DMA_TIMING_CUSTOM: + case GBA_DMA_TIMING_CUSTOM: switch (number) { case 0: mLOG(GBA_MEM, WARN, "Discarding invalid DMA0 scheduling"); @@ -111,7 +111,7 @@ void GBADMASchedule(struct GBA* gba, int number, struct GBADMA* info) { GBAAudioScheduleFifoDma(&gba->audio, number, info); break; case 3: - // GBAVideoScheduleVCaptureDma(dma, info); + // Handled implicitly break; } } @@ -124,7 +124,7 @@ void GBADMARunHblank(struct GBA* gba, int32_t cycles) { int i; for (i = 0; i < 4; ++i) { dma = &memory->dma[i]; - if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_HBLANK && !dma->nextCount) { + if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_HBLANK && !dma->nextCount) { dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles; dma->nextCount = dma->count; } @@ -138,7 +138,7 @@ void GBADMARunVblank(struct GBA* gba, int32_t cycles) { int i; for (i = 0; i < 4; ++i) { dma = &memory->dma[i]; - if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_VBLANK && !dma->nextCount) { + if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_VBLANK && !dma->nextCount) { dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles; dma->nextCount = dma->count; } @@ -146,6 +146,16 @@ void GBADMARunVblank(struct GBA* gba, int32_t cycles) { GBADMAUpdate(gba); } +void GBADMARunDisplayStart(struct GBA* gba, int32_t cycles) { + struct GBAMemory* memory = &gba->memory; + struct GBADMA* dma = &memory->dma[3]; + if (GBADMARegisterIsEnable(dma->reg) && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM && !dma->nextCount) { + dma->when = mTimingCurrentTime(&gba->timing) + 3 + cycles; + dma->nextCount = dma->count; + GBADMAUpdate(gba); + } +} + void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) { UNUSED(timing); UNUSED(cyclesLate); @@ -159,13 +169,16 @@ void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) { GBADMAService(gba, memory->activeDMA, dma); } else { dma->nextCount = 0; - if (!GBADMARegisterIsRepeat(dma->reg) || GBADMARegisterGetTiming(dma->reg) == DMA_TIMING_NOW) { + bool noRepeat = !GBADMARegisterIsRepeat(dma->reg); + noRepeat |= GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_NOW; + noRepeat |= memory->activeDMA == 3 && GBADMARegisterGetTiming(dma->reg) == GBA_DMA_TIMING_CUSTOM; + if (noRepeat) { dma->reg = GBADMARegisterClearEnable(dma->reg); // Clear the enable bit in memory memory->io[(REG_DMA0CNT_HI + memory->activeDMA * (REG_DMA1CNT_HI - REG_DMA0CNT_HI)) >> 1] &= 0x7FE0; } - if (GBADMARegisterGetDestControl(dma->reg) == DMA_INCREMENT_RELOAD) { + if (GBADMARegisterGetDestControl(dma->reg) == GBA_DMA_INCREMENT_RELOAD) { dma->nextDest = dma->dest; } if (GBADMARegisterIsDoIRQ(dma->reg)) { diff --git a/src/gba/io.c b/src/gba/io.c index 5cc1fe57c..716334519 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -979,7 +979,7 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { LOAD_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest); LOAD_32(gba->memory.dma[i].nextCount, 0, &state->dma[i].nextCount); LOAD_32(gba->memory.dma[i].when, 0, &state->dma[i].when); - if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != DMA_TIMING_NOW) { + if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != GBA_DMA_TIMING_NOW) { GBADMASchedule(gba, i, &gba->memory.dma[i]); } } diff --git a/src/gba/video.c b/src/gba/video.c index 96ee7f269..8fb76e59b 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -187,6 +187,9 @@ void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) { if (video->vcount < VIDEO_VERTICAL_PIXELS) { GBADMARunHblank(video->p, -cyclesLate); } + if (video->vcount >= 2 && video->vcount < VIDEO_VERTICAL_PIXELS + 2) { + GBADMARunDisplayStart(video->p, -cyclesLate); + } if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) { GBARaiseIRQ(video->p, IRQ_HBLANK); } From a6911437773cadc3708f92b16813b3d635dc1d65 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 16 Oct 2017 20:06:52 -0700 Subject: [PATCH 022/152] Util: Fix regression with PNGs --- src/util/png-io.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/util/png-io.c b/src/util/png-io.c index 24d301676..7ac760b93 100644 --- a/src/util/png-io.c +++ b/src/util/png-io.c @@ -47,16 +47,19 @@ static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned heigh return 0; } png_set_IHDR(png, info, width, height, 8, type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - png_write_info(png, info); return info; } png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB); + png_infop info = _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB); + png_write_info(png, info); + return info; } png_infop PNGWriteHeaderA(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB_ALPHA); + png_infop info = _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB_ALPHA); + png_write_info(png, info); + return info; } png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height) { From acbd8a3688703772861241a9b87f08cf91a0f66d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 16 Oct 2017 20:24:34 -0700 Subject: [PATCH 023/152] Qt: Prevent window from being created off-screen --- CHANGES | 1 + src/platform/qt/Window.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 80a24221a..8ff0d9afb 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Misc: - Python: Integrate tests from cinema test suite - Util: Don't build crc32 if the function already exists - GBA: Implement display start DMAs + - Qt: Prevent window from being created off-screen 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 7c83ca06d..95cf7c83f 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -550,11 +550,12 @@ void Window::showEvent(QShowEvent* event) { m_wasOpened = true; resizeFrame(m_screenWidget->sizeHint()); QVariant windowPos = m_config->getQtOption("windowPos"); - if (!windowPos.isNull()) { + QRect geom = QApplication::desktop()->availableGeometry(this); + if (!windowPos.isNull() && geom.contains(windowPos.toPoint())) { move(windowPos.toPoint()); } else { QRect rect = frameGeometry(); - rect.moveCenter(QApplication::desktop()->availableGeometry().center()); + rect.moveCenter(geom.center()); move(rect.topLeft()); } if (m_fullscreenOnStart) { From 65665324ef9124280a8a310ab9cea048d519a5a9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 17 Oct 2017 21:23:00 -0700 Subject: [PATCH 024/152] GB Serialize: Partially fix loading SGB states from a GB game --- src/gb/serialize.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/gb/serialize.c b/src/gb/serialize.c index c37b3872b..4c54c3e59 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -9,6 +9,8 @@ #include #include +#include + mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate", "gb.serialize"); const uint32_t GB_SAVESTATE_MAGIC = 0x00400000; @@ -171,6 +173,7 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { mTimingSchedule(&gb->timing, &gb->eiPending, when); } + enum GBModel oldModel = gb->model; gb->model = state->model; if (gb->model < GB_MODEL_CGB) { @@ -179,6 +182,10 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { gb->audio.style = GB_AUDIO_CGB; } + if (gb->model != GB_MODEL_SGB || oldModel != GB_MODEL_SGB) { + gb->video.sgbBorders = false; + } + GBMemoryDeserialize(gb, state); GBVideoDeserialize(&gb->video, state); GBIODeserialize(gb, state); @@ -237,22 +244,28 @@ void GBSGBDeserialize(struct GB* gb, const struct GBSerializedState* state) { memcpy(gb->sgbPacket, state->sgb.packet, sizeof(state->sgb.packet)); - if (gb->video.renderer->sgbCharRam) { - memcpy(gb->video.renderer->sgbCharRam, state->sgb.charRam, sizeof(state->sgb.charRam)); + if (!gb->video.renderer->sgbCharRam) { + gb->video.renderer->sgbCharRam = anonymousMemoryMap(SGB_SIZE_CHAR_RAM); } - if (gb->video.renderer->sgbMapRam) { - memcpy(gb->video.renderer->sgbMapRam, state->sgb.mapRam, sizeof(state->sgb.mapRam)); + if (!gb->video.renderer->sgbMapRam) { + gb->video.renderer->sgbMapRam = anonymousMemoryMap(SGB_SIZE_MAP_RAM); } - if (gb->video.renderer->sgbPalRam) { - memcpy(gb->video.renderer->sgbPalRam, state->sgb.palRam, sizeof(state->sgb.palRam)); + if (!gb->video.renderer->sgbPalRam) { + gb->video.renderer->sgbPalRam = anonymousMemoryMap(SGB_SIZE_PAL_RAM); } - if (gb->video.renderer->sgbAttributeFiles) { - memcpy(gb->video.renderer->sgbAttributeFiles, state->sgb.atfRam, sizeof(state->sgb.atfRam)); + if (!gb->video.renderer->sgbAttributeFiles) { + gb->video.renderer->sgbAttributeFiles = anonymousMemoryMap(SGB_SIZE_ATF_RAM); } - if (gb->video.renderer->sgbAttributes) { - memcpy(gb->video.renderer->sgbAttributes, state->sgb.attributes, sizeof(state->sgb.attributes)); + if (!gb->video.renderer->sgbAttributes) { + gb->video.renderer->sgbAttributes = malloc(90 * 45); } + memcpy(gb->video.renderer->sgbCharRam, state->sgb.charRam, sizeof(state->sgb.charRam)); + memcpy(gb->video.renderer->sgbMapRam, state->sgb.mapRam, sizeof(state->sgb.mapRam)); + memcpy(gb->video.renderer->sgbPalRam, state->sgb.palRam, sizeof(state->sgb.palRam)); + memcpy(gb->video.renderer->sgbAttributeFiles, state->sgb.atfRam, sizeof(state->sgb.atfRam)); + memcpy(gb->video.renderer->sgbAttributes, state->sgb.attributes, sizeof(state->sgb.attributes)); + GBVideoWriteSGBPacket(&gb->video, (uint8_t[16]) { (SGB_ATRC_EN << 3) | 1, 0 }); GBVideoWriteSGBPacket(&gb->video, gb->sgbPacket); } From 7ebd2d6e7546b91217d6dba88d0d2d01d72f4905 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 17 Oct 2017 21:25:35 -0700 Subject: [PATCH 025/152] GB Video: Fix loading states while in mode 3 --- CHANGES | 1 + include/mgba/internal/gb/video.h | 2 +- src/gb/io.c | 10 +++++----- src/gb/video.c | 8 +++++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 8ff0d9afb..1bce17f89 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Bugfixes: - GBA Video: Don't mask out high bits of BLDY (fixes mgba.io/i/899) - GBA Video: Force align 256-color tiles - GBA DMA: ROM reads are forced to increment + - GB Video: Fix loading states while in mode 3 Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index a19fa928e..90cfd7bbb 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -159,7 +159,7 @@ void GBVideoInit(struct GBVideo* video); void GBVideoReset(struct GBVideo* video); void GBVideoDeinit(struct GBVideo* video); void GBVideoAssociateRenderer(struct GBVideo* video, struct GBVideoRenderer* renderer); -void GBVideoProcessDots(struct GBVideo* video); +void GBVideoProcessDots(struct GBVideo* video, uint32_t cyclesLate); void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value); void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value); diff --git a/src/gb/io.c b/src/gb/io.c index 2db5d8c6e..8010e6a8a 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -392,7 +392,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { return; case REG_LCDC: // TODO: handle GBC differences - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value); GBVideoWriteLCDC(&gb->video, value); break; @@ -406,13 +406,13 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { case REG_SCX: case REG_WY: case REG_WX: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); value = gb->video.renderer->writeVideoRegister(gb->video.renderer, address, value); break; case REG_BGP: case REG_OBP0: case REG_OBP1: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); GBVideoWritePalette(&gb->video, address, value); break; case REG_STAT: @@ -459,7 +459,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { gb->memory.io[REG_BCPD] = gb->video.palette[gb->video.bcpIndex >> 1] >> (8 * (gb->video.bcpIndex & 1)); break; case REG_BCPD: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); GBVideoWritePalette(&gb->video, address, value); return; case REG_OCPS: @@ -468,7 +468,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { gb->memory.io[REG_OCPD] = gb->video.palette[8 * 4 + (gb->video.ocpIndex >> 1)] >> (8 * (gb->video.ocpIndex & 1)); break; case REG_OCPD: - GBVideoProcessDots(&gb->video); + GBVideoProcessDots(&gb->video, 0); GBVideoWritePalette(&gb->video, address, value); return; case REG_SVBK: diff --git a/src/gb/video.c b/src/gb/video.c index 4478b079e..952b51322 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -310,7 +310,7 @@ void _endMode2(struct mTiming* timing, void* context, uint32_t cyclesLate) { void _endMode3(struct mTiming* timing, void* context, uint32_t cyclesLate) { struct GBVideo* video = context; - GBVideoProcessDots(video); + GBVideoProcessDots(video, cyclesLate); if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) { video->p->memory.hdmaRemaining = 0x10; video->p->cpuBlocked = true; @@ -400,12 +400,12 @@ static void _cleanOAM(struct GBVideo* video, int y) { video->objMax = o; } -void GBVideoProcessDots(struct GBVideo* video) { +void GBVideoProcessDots(struct GBVideo* video, uint32_t cyclesLate) { if (video->mode != 3) { return; } int oldX = video->x; - video->x = (video->p->timing.masterCycles - video->dotClock + video->p->cpu->cycles) >> video->p->doubleSpeed; + video->x = (mTimingCurrentTime(&video->p->timing) - video->dotClock - cyclesLate) >> video->p->doubleSpeed; if (video->x > GB_VIDEO_HORIZONTAL_PIXELS) { video->x = GB_VIDEO_HORIZONTAL_PIXELS; } else if (video->x < 0) { @@ -786,6 +786,7 @@ void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* sta STORE_16LE(video->x, 0, &state->video.x); STORE_16LE(video->ly, 0, &state->video.ly); STORE_32LE(video->frameCounter, 0, &state->video.frameCounter); + STORE_32LE(video->dotClock, 0, &state->video.dotCounter); state->video.vramCurrentBank = video->vramCurrentBank; GBSerializedVideoFlags flags = 0; @@ -814,6 +815,7 @@ void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* s LOAD_16LE(video->x, 0, &state->video.x); LOAD_16LE(video->ly, 0, &state->video.ly); LOAD_32LE(video->frameCounter, 0, &state->video.frameCounter); + LOAD_32LE(video->dotClock, 0, &state->video.dotCounter); video->vramCurrentBank = state->video.vramCurrentBank; GBSerializedVideoFlags flags = state->video.flags; From c94aff135f2f80a5384b470b70520fd7fcfccf83 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 17 Oct 2017 21:39:12 -0700 Subject: [PATCH 026/152] Qt: Unify worker threads --- src/platform/qt/GBAApp.cpp | 84 ++++++++++++++++--- src/platform/qt/GBAApp.h | 39 ++++++++- src/platform/qt/library/LibraryController.cpp | 70 +++++----------- src/platform/qt/library/LibraryController.h | 20 +---- 4 files changed, 134 insertions(+), 79 deletions(-) diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index 9b994047a..39f85723b 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -67,10 +67,7 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config) } GBAApp::~GBAApp() { -#ifdef USE_SQLITE3 - m_parseThread.quit(); - m_parseThread.wait(); -#endif + m_workerThreads.waitForDone(); } bool GBAApp::event(QEvent* event) { @@ -180,14 +177,8 @@ bool GBAApp::reloadGameDB() { NoIntroDBDestroy(m_db); } if (db) { - if (m_parseThread.isRunning()) { - m_parseThread.quit(); - m_parseThread.wait(); - } GameDBParser* parser = new GameDBParser(db); - m_parseThread.start(); - parser->moveToThread(&m_parseThread); - QMetaObject::invokeMethod(parser, "parseNoIntroDB"); + submitWorkerJob(std::bind(&GameDBParser::parseNoIntroDB, parser)); m_db = db; return true; } @@ -199,6 +190,77 @@ bool GBAApp::reloadGameDB() { } #endif +qint64 GBAApp::submitWorkerJob(std::function job, std::function callback) { + return submitWorkerJob(job, nullptr, callback); +} + +qint64 GBAApp::submitWorkerJob(std::function job, QObject* context, std::function callback) { + qint64 jobId = m_nextJob; + ++m_nextJob; + WorkerJob* jobRunnable = new WorkerJob(jobId, job, this); + m_workerJobs.insert(jobId, jobRunnable); + if (callback) { + waitOnJob(jobId, context, callback); + } + m_workerThreads.start(jobRunnable); + return jobId; +} + +bool GBAApp::removeWorkerJob(qint64 jobId) { + for (auto& job : m_workerJobCallbacks.values(jobId)) { + disconnect(job); + } + m_workerJobCallbacks.remove(jobId); + if (!m_workerJobs.contains(jobId)) { + return true; + } + bool success = false; +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + success = m_workerThreads.tryTake(m_workerJobs[jobId]); +#endif + if (success) { + m_workerJobs.remove(jobId); + } + return success; +} + + +bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function callback) { + if (!m_workerJobs.contains(jobId)) { + return false; + } + if (!context) { + context = this; + } + QMetaObject::Connection connection = connect(this, &GBAApp::jobFinished, context, [jobId, callback](qint64 testedJobId) { + if (jobId != testedJobId) { + return; + } + callback(); + }); + m_workerJobCallbacks.insert(m_nextJob, connection); + return true; +} + +void GBAApp::finishJob(qint64 jobId) { + m_workerJobs.remove(jobId); + emit jobFinished(jobId); + m_workerJobCallbacks.remove(jobId); +} + +GBAApp::WorkerJob::WorkerJob(qint64 id, std::function job, GBAApp* owner) + : m_id(id) + , m_job(job) + , m_owner(owner) +{ + setAutoDelete(true); +} + +void GBAApp::WorkerJob::run() { + m_job(); + QMetaObject::invokeMethod(m_owner, "finishJob", Q_ARG(qint64, m_id)); +} + #ifdef USE_SQLITE3 GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent) : QObject(parent) diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index 8530a70b3..2e2c02af2 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -8,9 +8,14 @@ #include #include #include +#include +#include #include +#include #include -#include +#include + +#include #include "CoreManager.h" #include "MultiplayerController.h" @@ -61,10 +66,34 @@ public: const NoIntroDB* gameDB() const { return m_db; } bool reloadGameDB(); + qint64 submitWorkerJob(std::function job, std::function callback = {}); + qint64 submitWorkerJob(std::function job, QObject* context, std::function callback); + bool removeWorkerJob(qint64 jobId); + bool waitOnJob(qint64 jobId, QObject* context, std::function callback); + +signals: + void jobFinished(qint64 jobId); + protected: bool event(QEvent*); +private slots: + void finishJob(qint64 jobId); + private: + class WorkerJob : public QRunnable { + public: + WorkerJob(qint64 id, std::function job, GBAApp* owner); + + public: + void run() override; + + private: + qint64 m_id; + std::function m_job; + GBAApp* m_owner; + }; + Window* newWindowInternal(); void pauseAll(QList* paused); @@ -75,10 +104,12 @@ private: MultiplayerController m_multiplayer; CoreManager m_manager; + QMap m_workerJobs; + QMultiMap m_workerJobCallbacks; + QThreadPool m_workerThreads; + qint64 m_nextJob = 1; + NoIntroDB* m_db = nullptr; -#ifdef USE_SQLITE3 - QThread m_parseThread; -#endif }; } diff --git a/src/platform/qt/library/LibraryController.cpp b/src/platform/qt/library/LibraryController.cpp index 1a7fc8bf4..9bfad5d95 100644 --- a/src/platform/qt/library/LibraryController.cpp +++ b/src/platform/qt/library/LibraryController.cpp @@ -29,16 +29,6 @@ void AbstractGameList::removeEntries(QList items) { } } -LibraryLoaderThread::LibraryLoaderThread(QObject* parent) - : QThread(parent) -{ -} - -void LibraryLoaderThread::run() { - mLibraryLoadDirectory(m_library, m_directory.toUtf8().constData()); - m_directory = QString(); -} - LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config) : QStackedWidget(parent) , m_config(config) @@ -47,13 +37,13 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi if (!path.isNull()) { // This can return NULL if the library is already open - m_library = mLibraryLoad(path.toUtf8().constData()); + m_library = std::shared_ptr(mLibraryLoad(path.toUtf8().constData()), mLibraryDestroy); } if (!m_library) { - m_library = mLibraryCreateEmpty(); + m_library = std::shared_ptr(mLibraryCreateEmpty(), mLibraryDestroy); } - mLibraryAttachGameDB(m_library, GBAApp::app()->gameDB()); + mLibraryAttachGameDB(m_library.get(), GBAApp::app()->gameDB()); m_libraryTree = new LibraryTree(this); addWidget(m_libraryTree->widget()); @@ -61,25 +51,12 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi m_libraryGrid = new LibraryGrid(this); addWidget(m_libraryGrid->widget()); - connect(&m_loaderThread, &QThread::finished, this, &LibraryController::refresh, Qt::QueuedConnection); - setViewStyle(LibraryStyle::STYLE_LIST); refresh(); } LibraryController::~LibraryController() { mLibraryListingDeinit(&m_listing); - - if (m_loaderThread.isRunning()) { - m_loaderThread.wait(); - } - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } - if (m_library) { - mLibraryDestroy(m_library); - } } void LibraryController::setViewStyle(LibraryStyle newStyle) { @@ -117,7 +94,7 @@ LibraryEntryRef LibraryController::selectedEntry() { VFile* LibraryController::selectedVFile() { LibraryEntryRef entry = selectedEntry(); if (entry) { - return mLibraryOpenVFile(m_library, entry->entry); + return mLibraryOpenVFile(m_library.get(), entry->entry); } else { return nullptr; } @@ -129,35 +106,26 @@ QPair LibraryController::selectedPath() { } void LibraryController::addDirectory(const QString& dir) { - m_loaderThread.m_directory = dir; - m_loaderThread.m_library = m_library; - // The m_loaderThread temporarily owns the library - m_library = nullptr; - m_loaderThread.start(); + // The worker thread temporarily owns the library + std::shared_ptr library = m_library; + m_libraryJob = GBAApp::app()->submitWorkerJob(std::bind(&LibraryController::loadDirectory, this, dir), this, [this, library]() { + m_libraryJob = -1; + refresh(); + }); } void LibraryController::clear() { - if (!m_library) { - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } else { - return; - } + if (m_libraryJob > 0) { + return; } - mLibraryClear(m_library); + mLibraryClear(m_library.get()); refresh(); } void LibraryController::refresh() { - if (!m_library) { - if (!m_loaderThread.isRunning() && m_loaderThread.m_library) { - m_library = m_loaderThread.m_library; - m_loaderThread.m_library = nullptr; - } else { - return; - } + if (m_libraryJob > 0) { + return; } setDisabled(true); @@ -166,7 +134,7 @@ void LibraryController::refresh() { QList newEntries; mLibraryListingClear(&m_listing); - mLibraryGetEntries(m_library, &m_listing, 0, 0, nullptr); + mLibraryGetEntries(m_library.get(), &m_listing, 0, 0, nullptr); for (size_t i = 0; i < mLibraryListingSize(&m_listing); i++) { mLibraryEntry* entry = mLibraryListingGetPointer(&m_listing, i); QString fullpath = QString("%1/%2").arg(entry->base, entry->filename); @@ -210,4 +178,10 @@ void LibraryController::selectLastBootedGame() { } } +void LibraryController::loadDirectory(const QString& dir) { + // This class can get delted during this function (sigh) so we need to hold onto this + std::shared_ptr library = m_library; + mLibraryLoadDirectory(library.get(), dir.toUtf8().constData()); +} + } diff --git a/src/platform/qt/library/LibraryController.h b/src/platform/qt/library/LibraryController.h index 027b83f31..789550919 100644 --- a/src/platform/qt/library/LibraryController.h +++ b/src/platform/qt/library/LibraryController.h @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -66,19 +65,6 @@ public: virtual QWidget* widget() = 0; }; -class LibraryLoaderThread final : public QThread { -Q_OBJECT - -public: - LibraryLoaderThread(QObject* parent = nullptr); - - mLibrary* m_library = nullptr; - QString m_directory; - -protected: - virtual void run() override; -}; - class LibraryController final : public QStackedWidget { Q_OBJECT @@ -110,9 +96,11 @@ private slots: void refresh(); private: + void loadDirectory(const QString&); // Called on separate thread + ConfigController* m_config = nullptr; - LibraryLoaderThread m_loaderThread; - mLibrary* m_library = nullptr; + std::shared_ptr m_library; + qint64 m_libraryJob = -1; mLibraryListing m_listing; QMap m_entries; From 523aaf14973d748ff0c7fe68c5a6338976696466 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 18 Oct 2017 09:19:10 -0700 Subject: [PATCH 027/152] GB Video: Only trigger STAT write IRQs when screen is on (fixes #912) --- CHANGES | 1 + src/gb/video.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1bce17f89..6669f46f3 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,7 @@ Bugfixes: - GBA Video: Force align 256-color tiles - GBA DMA: ROM reads are forced to increment - GB Video: Fix loading states while in mode 3 + - GB Video: Only trigger STAT write IRQs when screen is on (fixes mgba.io/i/912) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gb/video.c b/src/gb/video.c index 952b51322..80a341678 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -455,7 +455,10 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) { GBRegisterSTAT oldStat = video->stat; video->stat = (video->stat & 0x7) | (value & 0x78); - if (video->p->model < GB_MODEL_CGB && !_statIRQAsserted(video, oldStat) && video->mode < 3) { + if (!GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) || video->p->model >= GB_MODEL_CGB) { + return; + } + if (!_statIRQAsserted(video, oldStat) && video->mode < 3) { // TODO: variable for the IRQ line value? video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); From 168cad7f9cb9df70fcad15b6d89769d8d96834d2 Mon Sep 17 00:00:00 2001 From: "Prof. 9" Date: Fri, 20 Oct 2017 03:06:47 +0200 Subject: [PATCH 028/152] Fix PARv3 If-AND code types (fixes #913) (#914) --- include/mgba/internal/gba/cheats.h | 2 +- src/gba/cheats/parv3.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mgba/internal/gba/cheats.h b/include/mgba/internal/gba/cheats.h index 83912f660..e8f23b26d 100644 --- a/include/mgba/internal/gba/cheats.h +++ b/include/mgba/internal/gba/cheats.h @@ -63,7 +63,7 @@ enum GBAActionReplay3Condition { PAR3_COND_GT = 0x20000000, PAR3_COND_ULT = 0x28000000, PAR3_COND_UGT = 0x30000000, - PAR3_COND_LAND = 0x38000000, + PAR3_COND_AND = 0x38000000, }; enum GBAActionReplay3Width { diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 087294d0f..79a4bde97 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -132,8 +132,8 @@ static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) case PAR3_COND_UGT: cheat->type = CHEAT_IF_UGT; break; - case PAR3_COND_LAND: - cheat->type = CHEAT_IF_LAND; + case PAR3_COND_AND: + cheat->type = CHEAT_IF_AND; break; } return true; From 5d72a2be9d5e7e74bc9fc94ac0130442ae91dc86 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 21 Oct 2017 17:24:51 -0700 Subject: [PATCH 029/152] Python: Add BIOS loading, fix up reference errors --- src/platform/python/mgba/core.py | 6 +++--- src/platform/python/mgba/gb.py | 3 +++ src/platform/python/mgba/gba.py | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index 84dc80e05..fdd04c8d7 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -139,9 +139,6 @@ class Core(object): raise RuntimeError("Failed to initialize core") return cls._detect(core) - def _deinit(self): - self._core.deinit(self._core) - @classmethod def _detect(cls, core): if hasattr(cls, 'PLATFORM_GBA') and core.platform(core) == cls.PLATFORM_GBA: @@ -164,6 +161,9 @@ class Core(object): def loadROM(self, vf): return bool(self._core.loadROM(self._core, vf.handle)) + def loadBIOS(self, vf, id=0): + return bool(self._core.loadBIOS(self._core, vf.handle, id)) + def loadSave(self, vf): return bool(self._core.loadSave(self._core, vf.handle)) diff --git a/src/platform/python/mgba/gb.py b/src/platform/python/mgba/gb.py index 47b9e09ff..01baeb29d 100644 --- a/src/platform/python/mgba/gb.py +++ b/src/platform/python/mgba/gb.py @@ -43,6 +43,9 @@ class GB(Core): def attachSIO(self, link): lib.GBSIOSetDriver(ffi.addressof(self._native.sio), link._native) + def __del__(self): + lib.GBSIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL) + createCallback("GBSIOPythonDriver", "init") createCallback("GBSIOPythonDriver", "deinit") createCallback("GBSIOPythonDriver", "writeSB") diff --git a/src/platform/python/mgba/gba.py b/src/platform/python/mgba/gba.py index 29f233f99..75f402284 100644 --- a/src/platform/python/mgba/gba.py +++ b/src/platform/python/mgba/gba.py @@ -33,6 +33,7 @@ class GBA(Core): self._native = ffi.cast("struct GBA*", native.board) self.sprites = GBAObjs(self) self.cpu = ARMCore(self._core.cpu) + self._sio = set() @needsReset def _initCache(self, cache): @@ -49,8 +50,13 @@ class GBA(Core): self.memory = GBAMemory(self._core, self._native.memory.romSize) def attachSIO(self, link, mode=lib.SIO_MULTI): + self._sio.add(mode) lib.GBASIOSetDriver(ffi.addressof(self._native.sio), link._native, mode) + def __del__(self): + for mode in self._sio: + lib.GBASIOSetDriver(ffi.addressof(self._native.sio), ffi.NULL, mode) + createCallback("GBASIOPythonDriver", "init") createCallback("GBASIOPythonDriver", "deinit") createCallback("GBASIOPythonDriver", "load") From 63d7927b60565c68fc0913d4b67f2c5813027f25 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 21 Oct 2017 17:26:05 -0700 Subject: [PATCH 030/152] GBA SIO: Add generic JOY bus implementation, Python bindings --- CMakeLists.txt | 2 +- include/mgba/gba/interface.h | 11 +++++ src/gba/sio/joybus.c | 76 +++++++++++++++++++++++++++++++++ src/platform/python/_builder.h | 1 + src/platform/python/_builder.py | 1 + src/platform/python/mgba/gba.py | 27 ++++++++++++ src/platform/python/sio.c | 12 ++++++ src/platform/python/sio.h | 1 + 8 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/gba/sio/joybus.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 22ad4989c..82825f02a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ file(GLOB UTIL_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/*.[cSs]) file(GLOB UTIL_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/test/*.c) file(GLOB GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/gui/*.c ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/gui/*.c) file(GLOB GBA_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/renderers/*.c) -file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/lockstep.c) +file(GLOB GBA_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/sio/*.c) file(GLOB GBA_EXTRA_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gba/extra/*.c) file(GLOB GB_SIO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/sio/*.c) file(GLOB GB_RENDERER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/gb/renderers/*.c) diff --git a/include/mgba/gba/interface.h b/include/mgba/gba/interface.h index 90f5e0697..0d85982f5 100644 --- a/include/mgba/gba/interface.h +++ b/include/mgba/gba/interface.h @@ -21,6 +21,13 @@ enum GBASIOMode { SIO_JOYBUS = 12 }; +enum GBASIOJOYCommand { + JOY_RESET = 0xFF, + JOY_POLL = 0x00, + JOY_TRANS = 0x14, + JOY_RECV = 0x15 +}; + struct GBA; struct GBAAudio; struct GBASIO; @@ -48,6 +55,10 @@ struct GBASIODriver { uint16_t (*writeRegister)(struct GBASIODriver* driver, uint32_t address, uint16_t value); }; +void GBASIOJOYCreate(struct GBASIODriver* sio); +int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data); + + CXX_GUARD_END #endif diff --git a/src/gba/sio/joybus.c b/src/gba/sio/joybus.c new file mode 100644 index 000000000..e7aa934e6 --- /dev/null +++ b/src/gba/sio/joybus.c @@ -0,0 +1,76 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include + +static uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value); + +void GBASIOJOYCreate(struct GBASIODriver* sio) { + sio->init = NULL; + sio->deinit = NULL; + sio->load = NULL; + sio->unload = NULL; + sio->writeRegister = GBASIOJOYWriteRegister; +} + +uint16_t GBASIOJOYWriteRegister(struct GBASIODriver* sio, uint32_t address, uint16_t value) { + switch (address) { + case REG_JOYCNT: + return (value & 0x0040) | (sio->p->p->memory.io[REG_JOYCNT >> 1] & ~(value & 0x7) & ~0x0040); + case REG_JOYSTAT: + return (value & 0x0030) | (sio->p->p->memory.io[REG_JOYSTAT >> 1] & ~0x30); + case REG_JOY_TRANS_LO: + case REG_JOY_TRANS_HI: + sio->p->p->memory.io[REG_JOYSTAT >> 1] |= 8; + break; + } + return value; +} + +int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command, uint8_t* data) { + switch (command) { + case JOY_RESET: + sio->p->p->memory.io[REG_JOYCNT >> 1] |= 1; + if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { + GBARaiseIRQ(sio->p->p, IRQ_SIO); + } + // Fall through + case JOY_POLL: + data[0] = 0x00; + data[1] = 0x04; + data[2] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; + return 3; + case JOY_RECV: + sio->p->p->memory.io[REG_JOYCNT >> 1] |= 2; + sio->p->p->memory.io[REG_JOYSTAT >> 1] |= 2; + + sio->p->p->memory.io[REG_JOY_RECV_LO >> 1] = data[0] | (data[1] << 8); + sio->p->p->memory.io[REG_JOY_RECV_HI >> 1] = data[2] | (data[3] << 8); + + data[0] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; + + if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { + GBARaiseIRQ(sio->p->p, IRQ_SIO); + } + return 1; + case JOY_TRANS: + sio->p->p->memory.io[REG_JOYCNT >> 1] |= 4; + sio->p->p->memory.io[REG_JOYSTAT >> 1] &= ~8; + data[0] = sio->p->p->memory.io[REG_JOY_TRANS_LO >> 1]; + data[1] = sio->p->p->memory.io[REG_JOY_TRANS_LO >> 1] >> 8; + data[2] = sio->p->p->memory.io[REG_JOY_TRANS_HI >> 1]; + data[3] = sio->p->p->memory.io[REG_JOY_TRANS_HI >> 1] >> 8; + data[4] = sio->p->p->memory.io[REG_JOYSTAT >> 1]; + + if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) { + GBARaiseIRQ(sio->p->p, IRQ_SIO); + } + return 5; + } + return 0; +} diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index cb823125b..bc1ee9d40 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -53,6 +53,7 @@ void free(void*); #include #endif #ifdef M_CORE_GBA +#include #include #include #include diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index 0d57d4173..ad380e10f 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -30,6 +30,7 @@ ffi.set_source("mgba._pylib", """ #include #include #include +#include #include #include #include diff --git a/src/platform/python/mgba/gba.py b/src/platform/python/mgba/gba.py index 75f402284..3074c39e4 100644 --- a/src/platform/python/mgba/gba.py +++ b/src/platform/python/mgba/gba.py @@ -26,6 +26,7 @@ class GBA(Core): SIO_NORMAL_32 = lib.SIO_NORMAL_32 SIO_MULTI = lib.SIO_MULTI SIO_UART = lib.SIO_UART + SIO_JOYBUS = lib.SIO_JOYBUS SIO_GPIO = lib.SIO_GPIO def __init__(self, native): @@ -83,6 +84,32 @@ class GBASIODriver(object): def writeRegister(self, address, value): return value +class GBASIOJOYDriver(GBASIODriver): + RESET = lib.JOY_RESET + POLL = lib.JOY_POLL + TRANS = lib.JOY_TRANS + RECV = lib.JOY_RECV + + def __init__(self): + self._handle = ffi.new_handle(self) + self._native = ffi.gc(lib.GBASIOJOYPythonDriverCreate(self._handle), lib.free) + + def sendCommand(self, cmd, data): + buffer = ffi.new('uint8_t[5]') + try: + buffer[0] = data[0] + buffer[1] = data[1] + buffer[2] = data[2] + buffer[3] = data[3] + buffer[4] = data[4] + except IndexError: + pass + + outlen = lib.GBASIOJOYSendCommand(self._native, cmd, buffer) + if outlen > 0 and outlen <= 5: + return bytes(buffer[0:outlen]) + return None + class GBAMemory(Memory): def __init__(self, core, romSize=lib.SIZE_CART0): super(GBAMemory, self).__init__(core, 0x100000000) diff --git a/src/platform/python/sio.c b/src/platform/python/sio.c index a7cba8973..ab8dd47bd 100644 --- a/src/platform/python/sio.c +++ b/src/platform/python/sio.c @@ -46,6 +46,18 @@ struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj) { return &driver->d; } +struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj) { + struct GBASIOPythonDriver* driver = malloc(sizeof(*driver)); + GBASIOJOYCreate(&driver->d); + driver->d.init = _pyGBASIOPythonDriverInitShim; + driver->d.deinit = _pyGBASIOPythonDriverDeinitShim; + driver->d.load = _pyGBASIOPythonDriverLoadShim; + driver->d.unload = _pyGBASIOPythonDriverUnloadShim; + + driver->pyobj = pyobj; + return &driver->d; +} + #endif #ifdef M_CORE_GB diff --git a/src/platform/python/sio.h b/src/platform/python/sio.h index 0403fdfd9..23b059427 100644 --- a/src/platform/python/sio.h +++ b/src/platform/python/sio.h @@ -13,6 +13,7 @@ struct GBASIOPythonDriver { }; struct GBASIODriver* GBASIOPythonDriverCreate(void* pyobj); +struct GBASIODriver* GBASIOJOYPythonDriverCreate(void* pyobj); PYEXPORT bool _pyGBASIOPythonDriverInit(void* driver); PYEXPORT void _pyGBASIOPythonDriverDeinit(void* driver); From 4d6b6fb3df0a7bc4083e71816a7c8fde045ed941 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 23 Oct 2017 06:46:05 -0700 Subject: [PATCH 031/152] GBA Cheats: Fix PARv3 slide codes (fixes #919) --- CHANGES | 1 + src/gba/cheats/parv3.c | 4 +- src/gba/test/cheats.c | 90 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 6669f46f3..f90d6c2b8 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Bugfixes: - GBA DMA: ROM reads are forced to increment - GB Video: Fix loading states while in mode 3 - GB Video: Only trigger STAT write IRQs when screen is on (fixes mgba.io/i/912) + - GBA Cheats: Fix PARv3 slide codes (fixes mgba.io/i/919) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 79a4bde97..060362e43 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -203,7 +203,7 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { case PAR3_OTHER_FILL_4: cheat = mCheatListAppend(&cheats->d.list); cheat->address = _parAddr(op2); - cheat->width = 3; + cheat->width = 4; cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; } @@ -219,7 +219,7 @@ bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uin if (cheats->incompleteCheat != COMPLETE) { struct mCheat* incompleteCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat); incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - incompleteCheat->width) * 8)); - incompleteCheat->addressOffset = op2 >> 24; + incompleteCheat->operandOffset = op2 >> 24; incompleteCheat->repeat = (op2 >> 16) & 0xFF; incompleteCheat->addressOffset = (op2 & 0xFFFF) * incompleteCheat->width; cheats->incompleteCheat = COMPLETE; diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index f3fb80b00..3309839c2 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -75,6 +75,93 @@ M_TEST_DEFINE(doPARv3Assign) { set->deinit(set); } +M_TEST_DEFINE(doPARv3Slide1) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 80300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 01020002", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 2); + assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0); + + set->deinit(set); +} + +M_TEST_DEFINE(doPARv3Slide2) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 82300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 01020002", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead16(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000002, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000006, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000008, -1), 0); + assert_int_equal(core->rawRead16(core, 0x0300000A, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead16(core, 0x03000000, -1), 1); + assert_int_equal(core->rawRead16(core, 0x03000002, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000004, -1), 2); + assert_int_equal(core->rawRead16(core, 0x03000006, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000008, -1), 0); + assert_int_equal(core->rawRead16(core, 0x0300000A, -1), 0); + + set->deinit(set); +} + +M_TEST_DEFINE(doPARv3Slide4) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 84300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 01020002", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead32(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000008, -1), 0); + assert_int_equal(core->rawRead32(core, 0x0300000C, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000010, -1), 0); + assert_int_equal(core->rawRead32(core, 0x03000014, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead16(core, 0x03000000, -1), 1); + assert_int_equal(core->rawRead16(core, 0x03000004, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000008, -1), 2); + assert_int_equal(core->rawRead16(core, 0x0300000C, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000010, -1), 0); + assert_int_equal(core->rawRead16(core, 0x03000014, -1), 0); + + set->deinit(set); +} + M_TEST_DEFINE(doPARv3If1) { struct mCore* core = *state; struct mCheatDevice* device = core->cheatDevice(core); @@ -927,6 +1014,9 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBACheats, cmocka_unit_test(createSet), cmocka_unit_test(addRawPARv3), cmocka_unit_test(doPARv3Assign), + cmocka_unit_test(doPARv3Slide1), + cmocka_unit_test(doPARv3Slide2), + cmocka_unit_test(doPARv3Slide4), cmocka_unit_test(doPARv3If1), cmocka_unit_test(doPARv3If1x1), cmocka_unit_test(doPARv3If2), From 24f3b5f11d3d6f291746eb585bb19de6cb51d5b5 Mon Sep 17 00:00:00 2001 From: rootfather Date: Sun, 22 Oct 2017 12:37:35 +0200 Subject: [PATCH 032/152] Qt: Update German GUI translation Added a few strings, minor improvements regarding savestates. --- src/platform/qt/ts/mgba-de.ts | 346 ++++++++++++++++++---------------- 1 file changed, 186 insertions(+), 160 deletions(-) diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index dfeb6dacb..3d5b7b4d2 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -522,62 +522,88 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Text - + Width Breite - + 1 Byte (8-bit) 1 Byte (8-bit) - + 2 Bytes (16-bit) 2 Bytes (16-bit) - + 4 Bytes (32-bit) 4 Bytes (32-bit) - + Number type Zahlensystem - + Hexadecimal Hexadezimal - + Decimal Dezimal - + + Guess automatisch - + + Compare + Vergleichen + + + + Equal + Gleichwertig + + + + Greater + Größer + + + + Less + Kleiner + + + + Delta + Differenz + + + Search Suchen - + Search Within Suchen innerhalb - + Open in Memory Viewer Im Speicher-Monitor öffnen - + Refresh Aktualisieren @@ -1157,22 +1183,22 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::CoreController - + Failed to open save file: %1 Fehler beim Öffnen der Speicherdatei: %1 - + Failed to open game file: %1 Fehler beim Öffnen der Spieldatei: %1 - + Failed to open snapshot file for reading: %1 Konnte Snapshot-Datei %1 nicht zum Lesen öffnen - + Failed to open snapshot file for writing: %1 Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen @@ -2827,24 +2853,24 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::MemorySearch - + + (%0/%1×) + (%0/%1×) + + + (⅟%0×) (⅟%0×) - - 1 byte%0 - 1 Byte%0 + + (%0×) + (%0×) - - 2 bytes%0 - 2 Bytes%0 - - - - 4 bytes%0 - 4 Bytes%0 + + %1 byte%2 + %1 byte%2 @@ -3190,12 +3216,12 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Video-Logs (*.mvl) - + Crash Absturz - + The game has crashed with the following error: %1 @@ -3204,528 +3230,528 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + Couldn't Load Konnte nicht geladen werden - + Could not load game. Are you sure it's in the correct format? Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt? - + Unimplemented BIOS call Nicht implementierter BIOS-Aufruf - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS. - + Really make portable? Portablen Modus wirklich aktivieren? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren? - + Restart needed Neustart benötigt - + Some changes will not take effect until the emulator is restarted. Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde. - + - Player %1 of %2 - Spieler %1 von %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 Bilder/Sekunde) - %4 - + &File &Datei - + Load &ROM... &ROM laden... - + Load ROM in archive... ROM aus Archiv laden... - + Load temporary save... Temporäre Speicherdatei laden... - + Load &patch... &Patch laden... - + Boot BIOS BIOS booten - + Replace ROM... ROM ersetzen... - + ROM &info... ROM-&Informationen... - + Recent Zuletzt verwendet - + Make portable Portablen Modus aktivieren - + &Load state - Savestate &laden + Savestate (aktueller Zustand) &laden - + F10 F10 - + &Save state - Savestate &speichern + Savestate (aktueller Zustand) &speichern - + Shift+F10 Umschalt+F10 - + Quick load Schnell laden - + Quick save Schnell speichern - + Load recent Lade zuletzt gespeicherten Savestate - + Save recent - Speichere aktuellen Stand + Speichere aktuellen Zustand - + Undo load state Laden des Savestate rückgängig machen - + F11 F11 - + Undo save state Speichern des Savestate rückgängig machen - + Shift+F11 Umschalt+F11 - - + + State &%1 Savestate &%1 - + F%1 F%1 - + Shift+F%1 Umschalt+F%1 - + Load camera image... Lade Kamerabild... - + Import GameShark Save Importiere GameShark-Speicherstand - + Export GameShark Save Exportiere GameShark-Speicherstand - + New multiplayer window Neues Multiplayer-Fenster - + About Über - + E&xit &Beenden - + &Emulation &Emulation - + &Reset Zu&rücksetzen - + Ctrl+R Strg+R - + Sh&utdown Schli&eßen - + Yank game pak Spielmodul herausziehen - + &Pause &Pause - + Ctrl+P Strg+P - + &Next frame &Nächstes Bild - + Ctrl+N Strg+N - + Fast forward (held) Schneller Vorlauf (gehalten) - + &Fast forward Schneller &Vorlauf - + Shift+Tab Umschalt+Tab - + Fast forward speed Vorlauf-Geschwindigkeit - + Unbounded Unbegrenzt - + %0x %0x - + Rewind (held) Zurückspulen (gehalten) - + Re&wind Zur&ückspulen - + ~ ~ - + Step backwards Schrittweiser Rücklauf - + Ctrl+B Strg+B - + Sync to &video Mit &Video synchronisieren - + Sync to &audio Mit &Audio synchronisieren - + Solar sensor Solar-Sensor - + Increase solar level Sonnen-Level erhöhen - + Decrease solar level Sonnen-Level verringern - + Brightest solar level Hellster Sonnen-Level - + Darkest solar level Dunkelster Sonnen-Level - + Brightness %1 Helligkeit %1 - + Audio/&Video Audio/&Video - + Frame size Bildgröße - + %1x %1x - + Toggle fullscreen Vollbildmodus umschalten - + Lock aspect ratio Seitenverhältnis korrigieren - + Force integer scaling Pixelgenaue Skalierung (Integer scaling) - + Frame&skip Frame&skip - + Mute Stummschalten - + FPS target Bildwiederholrate - + 15 15 - + 30 30 - + 45 45 - + Native (59.7) Nativ (59.7) - + 60 60 - + 90 90 - + 120 120 - + 240 240 - + Take &screenshot &Screenshot erstellen - + F12 F12 - + Record output... Ausgabe aufzeichen... - + Record GIF... GIF aufzeichen... - + Record video log... Video-Log aufzeichnen... - + Stop video log Video-Log beenden - + Game Boy Printer... Game Boy Printer... - + Video layers Video-Ebenen - + Audio channels Audio-Kanäle - + &Tools &Werkzeuge - + View &logs... &Logs ansehen... - + Game &overrides... Spiel-&Überschreibungen... - + Game &Pak sensors... Game &Pak-Sensoren... - + &Cheats... &Cheats... - + Open debugger console... Debugger-Konsole äffnen... - + Start &GDB server... &GDB-Server starten... - + Settings... Einstellungen... @@ -3735,107 +3761,107 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Ordner auswählen - + Add folder to library... Ordner zur Bibliothek hinzufügen... - + Bilinear filtering Bilineare Filterung - + View &palette... &Palette betrachten... - + View &sprites... &Sprites betrachten... - + View &tiles... &Tiles betrachten... - + View &map... &Map betrachten... - + View memory... Speicher betrachten... - + Search memory... Speicher durchsuchen... - + View &I/O registers... &I/O-Register betrachten... - + Exit fullscreen Vollbildmodus beenden - + Autofire Autofeuer - + Autofire A Autofeuer A - + Autofire B Autofeuer B - + Autofire L Autofeuer L - + Autofire R Autofeuer R - + Autofire Start Autofeuer Start - + Autofire Select Autofeuer Select - + Autofire Up Autofeuer nach oben - + Autofire Right Autofeuer rechts - + Autofire Down Autofeuer nach unten - + Autofire Left Autofeuer links From 65534d5dcd6ac54d3ed48bb76b4f622b2efae2b1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 26 Oct 2017 19:52:48 -0700 Subject: [PATCH 033/152] GBA Video: OBJWIN can change blend params after OBJ is drawn (fixes #921) --- CHANGES | 1 + src/gba/renderers/software-obj.c | 18 +++++++++--------- src/gba/renderers/software-private.h | 8 ++++---- src/gba/renderers/video-software.c | 12 ++++++++++-- .../sthg-objwin-blend/baseline_0000.png | Bin 0 -> 15046 bytes .../sthg-objwin-blend/baseline_0001.png | Bin 0 -> 15196 bytes .../sthg-objwin-blend/baseline_0002.png | Bin 0 -> 15205 bytes .../sthg-objwin-blend/baseline_0003.png | Bin 0 -> 15095 bytes .../gba/window/sthg-objwin-blend/test.mvl | Bin 0 -> 35364 bytes 9 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0000.png create mode 100644 src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0001.png create mode 100644 src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0002.png create mode 100644 src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0003.png create mode 100644 src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/test.mvl diff --git a/CHANGES b/CHANGES index f90d6c2b8..fafa088ef 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Bugfixes: - GB Video: Fix loading states while in mode 3 - GB Video: Only trigger STAT write IRQs when screen is on (fixes mgba.io/i/912) - GBA Cheats: Fix PARv3 slide codes (fixes mgba.io/i/919) + - GBA Video: OBJWIN can change blend params after OBJ is drawn (fixes mgba.io/i/921) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index a4bdcf228..354d8f671 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -156,26 +156,26 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re return 0; } + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed); int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); - if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { - int target2 = renderer->target2Bd << 4; - target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); - target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); - target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); - target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); - if ((1 << GBAObjAttributesCGetPriority(sprite->c)) <= target2) { + if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || objwinSlowPath) { + int target2 = renderer->target2Bd; + target2 |= renderer->bg[0].target2; + target2 |= renderer->bg[1].target2; + target2 |= renderer->bg[2].target2; + target2 |= renderer->bg[3].target2; + if (target2) { flags |= FLAG_REBLEND; variant = 0; - } else if (!target2) { + } else { flags &= ~FLAG_TARGET_1; } } color_t* palette = &renderer->normalPalette[0x100]; color_t* objwinPalette = palette; - int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed); if (variant) { palette = &renderer->variantPalette[0x100]; diff --git a/src/gba/renderers/software-private.h b/src/gba/renderers/software-private.h index 1a530abe5..4e4e19166 100644 --- a/src/gba/renderers/software-private.h +++ b/src/gba/renderers/software-private.h @@ -43,7 +43,7 @@ static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* render if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } } else { color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN); @@ -59,7 +59,7 @@ static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* rend if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } } else { color = color & ~FLAG_TARGET_2; @@ -73,7 +73,7 @@ static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* rend if (color < current) { color |= (current & FLAG_OBJWIN); } else { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } *pixel = color; } @@ -82,7 +82,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re uint32_t current) { UNUSED(renderer); if (color >= current) { - color = (current & 0x00FFFFFF) | (current & FLAG_REBLEND); + color = (current & 0x00FFFFFF) | (current & (FLAG_REBLEND | FLAG_OBJWIN)); } *pixel = color; } diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index ca20c3f05..f1148bc0f 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -621,6 +621,14 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render } if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) { x = 0; + uint32_t mask = 0xFF000000 & ~FLAG_OBJWIN; + uint32_t match = FLAG_REBLEND; + if (GBARegisterDISPCNTIsObjwinEnable(softwareRenderer->dispcnt)) { + mask |= FLAG_OBJWIN; + if (GBAWindowControlIsBlendEnable(softwareRenderer->objwin.packed)) { + match |= FLAG_OBJWIN; + } + } for (w = 0; w < softwareRenderer->nWindows; ++w) { if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) { continue; @@ -629,14 +637,14 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render if (softwareRenderer->blendEffect == BLEND_DARKEN) { for (; x < end; ++x) { uint32_t color = softwareRenderer->row[x]; - if ((color & 0xFF000000) == FLAG_REBLEND) { + if ((color & mask) == match) { softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy); } } } else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) { for (; x < end; ++x) { uint32_t color = softwareRenderer->row[x]; - if ((color & 0xFF000000) == FLAG_REBLEND) { + if ((color & mask) == match) { softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy); } } diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0000.png b/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..cea5ccfffc3917c2c2018f3b72d785621e460c87 GIT binary patch literal 15046 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 literal 0 HcmV?d00001 diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0001.png b/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0001.png new file mode 100644 index 0000000000000000000000000000000000000000..65dc9858899d9334197e0f487844a4043d986f0b GIT binary patch literal 15196 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 Z|Nki}(2>sZq<;Va002ovPDHLkV1gQhx+MSr literal 0 HcmV?d00001 diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/test.mvl b/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/test.mvl new file mode 100644 index 0000000000000000000000000000000000000000..6753147863f7ddcd6d2b7c335b5729c47ff48f6c GIT binary patch literal 35364 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 AtpET3 literal 0 HcmV?d00001 From 6d93a3d12bf6a93960c6932f7eca0ad44b9b1bde Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 26 Oct 2017 19:55:37 -0700 Subject: [PATCH 034/152] Python: Move cinema tests to root --- .../python/tests/cinema => cinema}/.gitignore | 0 .../tests/cinema => cinema}/gb/mooneye-gb/LICENSE | 0 .../acceptance/add_sp_e_timing/baseline_0000.png | Bin .../mooneye-gb/acceptance/add_sp_e_timing/test.gb | Bin .../mooneye-gb/acceptance/add_sp_e_timing/test.sym | 0 .../acceptance/bits/mem_oam/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/bits/mem_oam/test.gb | Bin .../gb/mooneye-gb/acceptance/bits/mem_oam/test.sym | 0 .../acceptance/bits/reg_f/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/bits/reg_f/test.gb | Bin .../gb/mooneye-gb/acceptance/bits/reg_f/test.sym | 0 .../bits/unused_hwio-GS/baseline_0000.png | Bin .../acceptance/bits/unused_hwio-GS/test.gb | Bin .../acceptance/bits/unused_hwio-GS/test.sym | 0 .../acceptance/boot_hwio-S/baseline_0000.png | Bin .../mooneye-gb/acceptance/boot_hwio-S/manifest.yml | 0 .../gb/mooneye-gb/acceptance/boot_hwio-S/test.gb | Bin .../gb/mooneye-gb/acceptance/boot_hwio-S/test.sym | 0 .../acceptance/boot_hwio-dmg0/baseline_0000.png | Bin .../acceptance/boot_hwio-dmg0/manifest.yml | 0 .../gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.gb | Bin .../mooneye-gb/acceptance/boot_hwio-dmg0/test.sym | 0 .../boot_hwio-dmgABCXmgb/baseline_0000.png | Bin .../acceptance/boot_hwio-dmgABCXmgb/test.gb | Bin .../acceptance/boot_hwio-dmgABCXmgb/test.sav | 0 .../acceptance/boot_hwio-dmgABCXmgb/test.sym | 0 .../acceptance/boot_regs-dmg/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/boot_regs-dmg/test.gb | Bin .../gb/mooneye-gb/acceptance/boot_regs-dmg/test.sym | 0 .../acceptance/boot_regs-dmg0/baseline_0000.png | Bin .../acceptance/boot_regs-dmg0/manifest.yml | 0 .../gb/mooneye-gb/acceptance/boot_regs-dmg0/test.gb | Bin .../mooneye-gb/acceptance/boot_regs-dmg0/test.sym | 0 .../acceptance/boot_regs-dmgABCX/baseline_0000.png | Bin .../mooneye-gb/acceptance/boot_regs-dmgABCX/test.gb | Bin .../acceptance/boot_regs-dmgABCX/test.sym | 0 .../acceptance/boot_regs-mgb/baseline_0000.png | Bin .../acceptance/boot_regs-mgb/manifest.yml | 0 .../gb/mooneye-gb/acceptance/boot_regs-mgb/test.gb | Bin .../gb/mooneye-gb/acceptance/boot_regs-mgb/test.sym | 0 .../acceptance/boot_regs-sgb/baseline_0000.png | Bin .../acceptance/boot_regs-sgb/manifest.yml | 0 .../gb/mooneye-gb/acceptance/boot_regs-sgb/test.gb | Bin .../gb/mooneye-gb/acceptance/boot_regs-sgb/test.sym | 0 .../acceptance/boot_regs-sgb2/baseline_0000.png | Bin .../acceptance/boot_regs-sgb2/manifest.yml | 0 .../gb/mooneye-gb/acceptance/boot_regs-sgb2/test.gb | Bin .../mooneye-gb/acceptance/boot_regs-sgb2/test.sym | 0 .../acceptance/call_cc_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/call_cc_timing/test.gb | Bin .../mooneye-gb/acceptance/call_cc_timing/test.sym | 0 .../acceptance/call_cc_timing2/baseline_0000.png | Bin .../mooneye-gb/acceptance/call_cc_timing2/test.gb | Bin .../mooneye-gb/acceptance/call_cc_timing2/test.sym | 0 .../acceptance/call_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/call_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/call_timing/test.sym | 0 .../acceptance/call_timing2/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/call_timing2/test.gb | Bin .../gb/mooneye-gb/acceptance/call_timing2/test.sym | 0 .../acceptance/di_timing-GS/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/di_timing-GS/test.gb | Bin .../gb/mooneye-gb/acceptance/di_timing-GS/test.sym | 0 .../acceptance/div_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/div_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/div_timing/test.sym | 0 .../acceptance/ei_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/ei_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/ei_timing/test.sym | 0 .../gpu/hblank_ly_scx_timing-GS/baseline_0000.png | Bin .../gpu/hblank_ly_scx_timing-GS/manifest.yml | 0 .../acceptance/gpu/hblank_ly_scx_timing-GS/test.gb | Bin .../acceptance/gpu/hblank_ly_scx_timing-GS/test.sym | 0 .../gpu/intr_1_2_timing-GS/baseline_0000.png | Bin .../acceptance/gpu/intr_1_2_timing-GS/test.gb | Bin .../acceptance/gpu/intr_1_2_timing-GS/test.sym | 0 .../gpu/intr_2_0_timing/baseline_0000.png | Bin .../acceptance/gpu/intr_2_0_timing/test.gb | Bin .../acceptance/gpu/intr_2_0_timing/test.sym | 0 .../gpu/intr_2_mode0_timing/baseline_0000.png | Bin .../acceptance/gpu/intr_2_mode0_timing/test.gb | Bin .../acceptance/gpu/intr_2_mode0_timing/test.sym | 0 .../intr_2_mode0_timing_sprites/baseline_0000.png | Bin .../gpu/intr_2_mode0_timing_sprites/test.gb | Bin .../gpu/intr_2_mode0_timing_sprites/test.sym | 0 .../gpu/intr_2_mode3_timing/baseline_0000.png | Bin .../acceptance/gpu/intr_2_mode3_timing/test.gb | Bin .../acceptance/gpu/intr_2_mode3_timing/test.sym | 0 .../gpu/intr_2_oam_ok_timing/baseline_0000.png | Bin .../acceptance/gpu/intr_2_oam_ok_timing/test.gb | Bin .../acceptance/gpu/intr_2_oam_ok_timing/test.sym | 0 .../gpu/lcdon_timing-dmgABCXmgbS/manifest.yml | 0 .../acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.gb | Bin .../gpu/lcdon_timing-dmgABCXmgbS/test.sym | 0 .../gpu/lcdon_write_timing-GS/manifest.yml | 0 .../acceptance/gpu/lcdon_write_timing-GS/test.gb | Bin .../acceptance/gpu/lcdon_write_timing-GS/test.sym | 0 .../gpu/stat_irq_blocking/baseline_0000.png | Bin .../acceptance/gpu/stat_irq_blocking/test.gb | Bin .../acceptance/gpu/stat_irq_blocking/test.sym | 0 .../gpu/vblank_stat_intr-GS/baseline_0000.png | Bin .../acceptance/gpu/vblank_stat_intr-GS/test.gb | Bin .../acceptance/gpu/vblank_stat_intr-GS/test.sym | 0 .../acceptance/halt_ime0_ei/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/halt_ime0_ei/test.gb | Bin .../gb/mooneye-gb/acceptance/halt_ime0_ei/test.sym | 0 .../halt_ime0_nointr_timing/baseline_0000.png | Bin .../acceptance/halt_ime0_nointr_timing/test.gb | Bin .../acceptance/halt_ime0_nointr_timing/test.sym | 0 .../acceptance/halt_ime1_timing/baseline_0000.png | Bin .../mooneye-gb/acceptance/halt_ime1_timing/test.gb | Bin .../mooneye-gb/acceptance/halt_ime1_timing/test.sym | 0 .../halt_ime1_timing2-GS/baseline_0000.png | Bin .../acceptance/halt_ime1_timing2-GS/test.gb | Bin .../acceptance/halt_ime1_timing2-GS/test.sym | 0 .../gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml | 0 .../gb/mooneye-gb/acceptance/hdma_lcdc/test.gb | Bin .../gb/mooneye-gb/acceptance/hdma_lcdc/test.sym | 0 .../acceptance/if_ie_registers/baseline_0000.png | Bin .../mooneye-gb/acceptance/if_ie_registers/test.gb | Bin .../mooneye-gb/acceptance/if_ie_registers/test.sym | 0 .../acceptance/interrupts/ie_push/baseline_0000.png | Bin .../acceptance/interrupts/ie_push/test.gb | Bin .../acceptance/interrupts/ie_push/test.sym | 0 .../acceptance/intr_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/intr_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/intr_timing/test.sym | 0 .../acceptance/jp_cc_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/jp_cc_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/jp_cc_timing/test.sym | 0 .../acceptance/jp_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/jp_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/jp_timing/test.sym | 0 .../acceptance/ld_hl_sp_e_timing/baseline_0000.png | Bin .../mooneye-gb/acceptance/ld_hl_sp_e_timing/test.gb | Bin .../acceptance/ld_hl_sp_e_timing/test.sym | 0 .../acceptance/oam_dma_restart/baseline_0000.png | Bin .../mooneye-gb/acceptance/oam_dma_restart/test.gb | Bin .../mooneye-gb/acceptance/oam_dma_restart/test.sym | 0 .../acceptance/oam_dma_start/baseline_0000.png | Bin .../acceptance/oam_dma_start/manifest.yml | 0 .../gb/mooneye-gb/acceptance/oam_dma_start/test.gb | Bin .../gb/mooneye-gb/acceptance/oam_dma_start/test.sym | 0 .../acceptance/oam_dma_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/oam_dma_timing/test.gb | Bin .../mooneye-gb/acceptance/oam_dma_timing/test.sym | 0 .../acceptance/pop_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/pop_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/pop_timing/test.sym | 0 .../acceptance/push_timing/baseline_0000.png | Bin .../mooneye-gb/acceptance/push_timing/manifest.yml | 0 .../gb/mooneye-gb/acceptance/push_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/push_timing/test.sym | 0 .../acceptance/rapid_di_ei/baseline_0000.png | Bin .../mooneye-gb/acceptance/rapid_di_ei/manifest.yml | 0 .../gb/mooneye-gb/acceptance/rapid_di_ei/test.gb | Bin .../gb/mooneye-gb/acceptance/rapid_di_ei/test.sym | 0 .../acceptance/ret_cc_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/ret_cc_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/ret_cc_timing/test.sym | 0 .../acceptance/ret_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/ret_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/ret_timing/test.sym | 0 .../acceptance/reti_intr_timing/baseline_0000.png | Bin .../mooneye-gb/acceptance/reti_intr_timing/test.gb | Bin .../mooneye-gb/acceptance/reti_intr_timing/test.sym | 0 .../acceptance/reti_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/reti_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/reti_timing/test.sym | 0 .../acceptance/rst_timing/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/rst_timing/test.gb | Bin .../gb/mooneye-gb/acceptance/rst_timing/test.sym | 0 .../serial/boot_sclk_align-dmgABCXmgb/manifest.yml | 0 .../serial/boot_sclk_align-dmgABCXmgb/test.gb | Bin .../serial/boot_sclk_align-dmgABCXmgb/test.sav | 0 .../serial/boot_sclk_align-dmgABCXmgb/test.sym | 0 .../acceptance/timer/div_write/baseline_0000.png | Bin .../mooneye-gb/acceptance/timer/div_write/test.gb | Bin .../mooneye-gb/acceptance/timer/div_write/test.sym | 0 .../acceptance/timer/rapid_toggle/baseline_0000.png | Bin .../acceptance/timer/rapid_toggle/manifest.yml | 0 .../acceptance/timer/rapid_toggle/test.gb | Bin .../acceptance/timer/rapid_toggle/test.sym | 0 .../acceptance/timer/tim00/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/timer/tim00/test.gb | Bin .../gb/mooneye-gb/acceptance/timer/tim00/test.sym | 0 .../timer/tim00_div_trigger/baseline_0000.png | Bin .../acceptance/timer/tim00_div_trigger/test.gb | Bin .../acceptance/timer/tim00_div_trigger/test.sym | 0 .../acceptance/timer/tim01/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/timer/tim01/test.gb | Bin .../gb/mooneye-gb/acceptance/timer/tim01/test.sym | 0 .../timer/tim01_div_trigger/baseline_0000.png | Bin .../acceptance/timer/tim01_div_trigger/manifest.yml | 0 .../acceptance/timer/tim01_div_trigger/test.gb | Bin .../acceptance/timer/tim01_div_trigger/test.sym | 0 .../acceptance/timer/tim10/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/timer/tim10/test.gb | Bin .../gb/mooneye-gb/acceptance/timer/tim10/test.sym | 0 .../timer/tim10_div_trigger/baseline_0000.png | Bin .../acceptance/timer/tim10_div_trigger/test.gb | Bin .../acceptance/timer/tim10_div_trigger/test.sym | 0 .../acceptance/timer/tim11/baseline_0000.png | Bin .../gb/mooneye-gb/acceptance/timer/tim11/test.gb | Bin .../gb/mooneye-gb/acceptance/timer/tim11/test.sym | 0 .../timer/tim11_div_trigger/baseline_0000.png | Bin .../acceptance/timer/tim11_div_trigger/test.gb | Bin .../acceptance/timer/tim11_div_trigger/test.sym | 0 .../acceptance/timer/tima_reload/baseline_0000.png | Bin .../mooneye-gb/acceptance/timer/tima_reload/test.gb | Bin .../acceptance/timer/tima_reload/test.sym | 0 .../timer/tima_write_reloading/baseline_0000.png | Bin .../timer/tima_write_reloading/manifest.yml | 0 .../acceptance/timer/tima_write_reloading/test.gb | Bin .../acceptance/timer/tima_write_reloading/test.sym | 0 .../timer/tma_write_reloading/baseline_0000.png | Bin .../timer/tma_write_reloading/manifest.yml | 0 .../acceptance/timer/tma_write_reloading/test.gb | Bin .../acceptance/timer/tma_write_reloading/test.sym | 0 .../mbc1/multicart_rom_8Mb/baseline_0000.png | Bin .../emulator-only/mbc1/multicart_rom_8Mb/test.gb | Bin .../emulator-only/mbc1/multicart_rom_8Mb/test.sym | 0 .../emulator-only/mbc1/ram_256Kb/baseline_0000.png | Bin .../mooneye-gb/emulator-only/mbc1/ram_256Kb/test.gb | Bin .../emulator-only/mbc1/ram_256Kb/test.sym | 0 .../emulator-only/mbc1/ram_64Kb/baseline_0000.png | Bin .../mooneye-gb/emulator-only/mbc1/ram_64Kb/test.gb | Bin .../mooneye-gb/emulator-only/mbc1/ram_64Kb/test.sym | 0 .../emulator-only/mbc1/rom_16Mb/baseline_0000.png | Bin .../mooneye-gb/emulator-only/mbc1/rom_16Mb/test.gb | Bin .../mooneye-gb/emulator-only/mbc1/rom_16Mb/test.sym | 0 .../emulator-only/mbc1/rom_1Mb/baseline_0000.png | Bin .../emulator-only/mbc1/rom_1Mb/manifest.yml | 0 .../mooneye-gb/emulator-only/mbc1/rom_1Mb/test.gb | Bin .../mooneye-gb/emulator-only/mbc1/rom_1Mb/test.sym | 0 .../emulator-only/mbc1/rom_2Mb/baseline_0000.png | Bin .../emulator-only/mbc1/rom_2Mb/manifest.yml | 0 .../mooneye-gb/emulator-only/mbc1/rom_2Mb/test.gb | Bin .../mooneye-gb/emulator-only/mbc1/rom_2Mb/test.sym | 0 .../emulator-only/mbc1/rom_4Mb/baseline_0000.png | Bin .../mooneye-gb/emulator-only/mbc1/rom_4Mb/test.gb | Bin .../mooneye-gb/emulator-only/mbc1/rom_4Mb/test.sym | 0 .../emulator-only/mbc1/rom_512Kb/baseline_0000.png | Bin .../mooneye-gb/emulator-only/mbc1/rom_512Kb/test.gb | Bin .../emulator-only/mbc1/rom_512Kb/test.sym | 0 .../emulator-only/mbc1/rom_8Mb/baseline_0000.png | Bin .../mooneye-gb/emulator-only/mbc1/rom_8Mb/test.gb | Bin .../mooneye-gb/emulator-only/mbc1/rom_8Mb/test.sym | 0 .../emulator-only/mbc1_rom_4banks/baseline_0000.png | Bin .../emulator-only/mbc1_rom_4banks/manifest.yml | 0 .../emulator-only/mbc1_rom_4banks/test.gb | Bin .../emulator-only/mbc1_rom_4banks/test.sym | 0 .../mgb_oam_dma_halt_sprites/baseline_0000.png | Bin .../madness/mgb_oam_dma_halt_sprites/manifest.yml | 0 .../madness/mgb_oam_dma_halt_sprites/test.gb | Bin .../madness/mgb_oam_dma_halt_sprites/test.sym | 0 .../cinema => cinema}/gb/mooneye-gb/manifest.yml | 0 .../manual-only/sprite_priority/baseline_0000.png | Bin .../manual-only/sprite_priority/manifest.yml | 0 .../mooneye-gb/manual-only/sprite_priority/test.gb | Bin .../mooneye-gb/manual-only/sprite_priority/test.sym | 0 .../misc/bits/unused_hwio-C/baseline_0000.png | Bin .../mooneye-gb/misc/bits/unused_hwio-C/manifest.yml | 0 .../gb/mooneye-gb/misc/bits/unused_hwio-C/test.gb | Bin .../gb/mooneye-gb/misc/bits/unused_hwio-C/test.sym | 0 .../mooneye-gb/misc/boot_hwio-C/baseline_0000.png | Bin .../gb/mooneye-gb/misc/boot_hwio-C/manifest.yml | 0 .../gb/mooneye-gb/misc/boot_hwio-C/test.gb | Bin .../gb/mooneye-gb/misc/boot_hwio-C/test.sav | 0 .../gb/mooneye-gb/misc/boot_hwio-C/test.sym | 0 .../mooneye-gb/misc/boot_regs-A/baseline_0000.png | Bin .../gb/mooneye-gb/misc/boot_regs-A/manifest.yml | 0 .../gb/mooneye-gb/misc/boot_regs-A/test.gb | Bin .../gb/mooneye-gb/misc/boot_regs-A/test.sym | 0 .../mooneye-gb/misc/boot_regs-cgb/baseline_0000.png | Bin .../gb/mooneye-gb/misc/boot_regs-cgb/manifest.yml | 0 .../gb/mooneye-gb/misc/boot_regs-cgb/test.gb | Bin .../gb/mooneye-gb/misc/boot_regs-cgb/test.sym | 0 .../misc/gpu/vblank_stat_intr-C/baseline_0000.png | Bin .../misc/gpu/vblank_stat_intr-C/manifest.yml | 0 .../mooneye-gb/misc/gpu/vblank_stat_intr-C/test.gb | Bin .../mooneye-gb/misc/gpu/vblank_stat_intr-C/test.sym | 0 .../tests/cinema => cinema}/gb/mooneye-gb/update.py | 0 .../gb/window/dk94-split/baseline_0000.png | Bin .../gb/window/dk94-split/baseline_0001.png | Bin .../gb/window/dk94-split/baseline_0002.png | Bin .../gb/window/dk94-split/baseline_0003.png | Bin .../gb/window/dk94-split/baseline_0004.png | Bin .../gb/window/dk94-split/baseline_0005.png | Bin .../gb/window/dk94-split/baseline_0006.png | Bin .../gb/window/dk94-split/baseline_0007.png | Bin .../gb/window/dk94-split/baseline_0008.png | Bin .../cinema => cinema}/gb/window/dk94-split/test.mvl | Bin .../cinema => cinema}/gb/window/dk94-split/test.sav | 0 .../gb/window/gsc-battle/baseline_0000.png | Bin .../gb/window/gsc-battle/baseline_0001.png | Bin .../gb/window/gsc-battle/baseline_0002.png | Bin .../gb/window/gsc-battle/baseline_0003.png | Bin .../gb/window/gsc-battle/baseline_0004.png | Bin .../gb/window/gsc-battle/manifest.yml | 0 .../cinema => cinema}/gb/window/gsc-battle/test.mvl | Bin .../cinema => cinema}/gb/window/gsc-battle/test.sav | 0 .../gb/window/zoos-intro/baseline_0000.png | Bin .../gb/window/zoos-intro/baseline_0001.png | Bin .../gb/window/zoos-intro/baseline_0002.png | Bin .../gb/window/zoos-intro/baseline_0003.png | Bin .../gb/window/zoos-intro/baseline_0004.png | Bin .../gb/window/zoos-intro/baseline_0005.png | Bin .../gb/window/zoos-intro/baseline_0006.png | Bin .../cinema => cinema}/gb/window/zoos-intro/test.mvl | Bin .../cinema => cinema}/gb/window/zoos-intro/test.sav | 0 .../gba/bg/lady-sia/baseline_0000.png | Bin .../gba/bg/lady-sia/baseline_0001.png | Bin .../gba/bg/lady-sia/baseline_0002.png | Bin .../gba/bg/lady-sia/baseline_0003.png | Bin .../cinema => cinema}/gba/bg/lady-sia/test.mvl | Bin .../gba/blend/gs-obj-modes/baseline_0000.png | Bin .../gba/blend/gs-obj-modes/baseline_0001.png | Bin .../gba/blend/gs-obj-modes/baseline_0002.png | Bin .../gba/blend/gs-obj-modes/baseline_0003.png | Bin .../gba/blend/gs-obj-modes/baseline_0004.png | Bin .../gba/blend/gs-obj-modes/baseline_0005.png | Bin .../gba/blend/gs-obj-modes/test.mvl | Bin .../gba/blend/kam-knockout/baseline_0000.png | Bin .../gba/blend/kam-knockout/baseline_0001.png | Bin .../gba/blend/kam-knockout/baseline_0002.png | Bin .../gba/blend/kam-knockout/baseline_0003.png | Bin .../gba/blend/kam-knockout/test.mvl | Bin .../gba/blend/mzm-layering/baseline_0000.png | Bin .../gba/blend/mzm-layering/baseline_0001.png | Bin .../gba/blend/mzm-layering/baseline_0002.png | Bin .../gba/blend/mzm-layering/baseline_0003.png | Bin .../gba/blend/mzm-layering/baseline_0004.png | Bin .../gba/blend/mzm-layering/baseline_0005.png | Bin .../gba/blend/mzm-layering/baseline_0006.png | Bin .../gba/blend/mzm-layering/baseline_0007.png | Bin .../gba/blend/mzm-layering/baseline_0008.png | Bin .../gba/blend/mzm-layering/baseline_0009.png | Bin .../gba/blend/mzm-layering/test.mvl | Bin .../gba/blend/mzm-layering/test.sav | 0 .../gba/blend/sma-knockout/baseline_0000.png | Bin .../gba/blend/sma-knockout/baseline_0001.png | Bin .../gba/blend/sma-knockout/baseline_0002.png | Bin .../gba/blend/sma-knockout/test.mvl | Bin .../gba/obj/unaligned-256/baseline_0000.png | Bin .../gba/obj/unaligned-256/baseline_0001.png | Bin .../gba/obj/unaligned-256/baseline_0002.png | Bin .../gba/obj/unaligned-256/baseline_0003.png | Bin .../gba/obj/unaligned-256/test.mvl | Bin .../gba/window/gs-clock-wipe/baseline_0000.png | Bin .../gba/window/gs-clock-wipe/baseline_0001.png | Bin .../gba/window/gs-clock-wipe/baseline_0002.png | Bin .../gba/window/gs-clock-wipe/baseline_0003.png | Bin .../gba/window/gs-clock-wipe/baseline_0004.png | Bin .../gba/window/gs-clock-wipe/baseline_0005.png | Bin .../gba/window/gs-clock-wipe/baseline_0006.png | Bin .../gba/window/gs-clock-wipe/baseline_0007.png | Bin .../gba/window/gs-clock-wipe/baseline_0008.png | Bin .../gba/window/gs-clock-wipe/baseline_0009.png | Bin .../gba/window/gs-clock-wipe/baseline_0010.png | Bin .../gba/window/gs-clock-wipe/baseline_0011.png | Bin .../gba/window/gs-clock-wipe/baseline_0012.png | Bin .../gba/window/gs-clock-wipe/baseline_0013.png | Bin .../gba/window/gs-clock-wipe/baseline_0014.png | Bin .../gba/window/gs-clock-wipe/baseline_0015.png | Bin .../gba/window/gs-clock-wipe/baseline_0016.png | Bin .../gba/window/gs-clock-wipe/baseline_0017.png | Bin .../gba/window/gs-clock-wipe/test.mvl | Bin .../gba/window/sthg-objwin-blend/baseline_0000.png | Bin .../gba/window/sthg-objwin-blend/baseline_0001.png | Bin .../gba/window/sthg-objwin-blend/baseline_0002.png | Bin .../gba/window/sthg-objwin-blend/baseline_0003.png | Bin .../gba/window/sthg-objwin-blend/test.mvl | Bin .../gba/window/tgr-objwin-order/baseline_0000.png | Bin .../gba/window/tgr-objwin-order/baseline_0001.png | Bin .../gba/window/tgr-objwin-order/baseline_0002.png | Bin .../gba/window/tgr-objwin-order/baseline_0003.png | Bin .../gba/window/tgr-objwin-order/baseline_0004.png | Bin .../gba/window/tgr-objwin-order/baseline_0005.png | Bin .../gba/window/tgr-objwin-order/baseline_0006.png | Bin .../gba/window/tgr-objwin-order/baseline_0007.png | Bin .../gba/window/tgr-objwin-order/baseline_0008.png | Bin .../gba/window/tgr-objwin-order/baseline_0009.png | Bin .../gba/window/tgr-objwin-order/baseline_0010.png | Bin .../gba/window/tgr-objwin-order/baseline_0011.png | Bin .../gba/window/tgr-objwin-order/baseline_0012.png | Bin .../gba/window/tgr-objwin-order/baseline_0013.png | Bin .../gba/window/tgr-objwin-order/baseline_0014.png | Bin .../gba/window/tgr-objwin-order/baseline_0015.png | Bin .../gba/window/tgr-objwin-order/baseline_0016.png | Bin .../gba/window/tgr-objwin-order/baseline_0017.png | Bin .../gba/window/tgr-objwin-order/baseline_0018.png | Bin .../gba/window/tgr-objwin-order/baseline_0019.png | Bin .../gba/window/tgr-objwin-order/baseline_0020.png | Bin .../gba/window/tgr-objwin-order/baseline_0021.png | Bin .../gba/window/tgr-objwin-order/baseline_0022.png | Bin .../gba/window/tgr-objwin-order/baseline_0023.png | Bin .../gba/window/tgr-objwin-order/baseline_0024.png | Bin .../gba/window/tgr-objwin-order/test.mvl | Bin .../gba/window/zmc-window-mosaic/baseline_0000.png | Bin .../gba/window/zmc-window-mosaic/baseline_0001.png | Bin .../gba/window/zmc-window-mosaic/baseline_0002.png | Bin .../gba/window/zmc-window-mosaic/baseline_0003.png | Bin .../gba/window/zmc-window-mosaic/baseline_0004.png | Bin .../gba/window/zmc-window-mosaic/baseline_0005.png | Bin .../gba/window/zmc-window-mosaic/baseline_0006.png | Bin .../gba/window/zmc-window-mosaic/baseline_0007.png | Bin .../gba/window/zmc-window-mosaic/baseline_0008.png | Bin .../gba/window/zmc-window-mosaic/baseline_0009.png | Bin .../gba/window/zmc-window-mosaic/baseline_0010.png | Bin .../gba/window/zmc-window-mosaic/baseline_0011.png | Bin .../gba/window/zmc-window-mosaic/baseline_0012.png | Bin .../gba/window/zmc-window-mosaic/baseline_0013.png | Bin .../gba/window/zmc-window-mosaic/baseline_0014.png | Bin .../gba/window/zmc-window-mosaic/baseline_0015.png | Bin .../gba/window/zmc-window-mosaic/baseline_0016.png | Bin .../gba/window/zmc-window-mosaic/baseline_0017.png | Bin .../gba/window/zmc-window-mosaic/baseline_0018.png | Bin .../gba/window/zmc-window-mosaic/baseline_0019.png | Bin .../gba/window/zmc-window-mosaic/baseline_0020.png | Bin .../gba/window/zmc-window-mosaic/baseline_0021.png | Bin .../gba/window/zmc-window-mosaic/baseline_0022.png | Bin .../gba/window/zmc-window-mosaic/baseline_0023.png | Bin .../gba/window/zmc-window-mosaic/baseline_0024.png | Bin .../gba/window/zmc-window-mosaic/baseline_0025.png | Bin .../gba/window/zmc-window-mosaic/baseline_0026.png | Bin .../gba/window/zmc-window-mosaic/baseline_0027.png | Bin .../gba/window/zmc-window-mosaic/test.mvl | Bin src/platform/python/test_cinema.py | 2 +- 429 files changed, 1 insertion(+), 1 deletion(-) rename {src/platform/python/tests/cinema => cinema}/.gitignore (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/LICENSE (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/add_sp_e_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/add_sp_e_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/add_sp_e_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/mem_oam/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/mem_oam/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/mem_oam/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/reg_f/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/reg_f/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/reg_f/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-S/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-S/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-S/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-S/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmg0/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmg0/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sav (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmg/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmg/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmg/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmg0/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmg0/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-mgb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-mgb/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-mgb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-mgb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb2/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb2/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_cc_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_cc_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_cc_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_cc_timing2/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_cc_timing2/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_cc_timing2/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_timing2/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_timing2/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/call_timing2/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/di_timing-GS/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/di_timing-GS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/di_timing-GS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/div_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/div_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/div_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ei_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ei_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ei_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime0_ei/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime0_ei/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime0_ei/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime1_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime1_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime1_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/hdma_lcdc/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/hdma_lcdc/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/if_ie_registers/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/if_ie_registers/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/if_ie_registers/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/interrupts/ie_push/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/interrupts/ie_push/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/interrupts/ie_push/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/intr_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/intr_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/intr_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/jp_cc_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/jp_cc_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/jp_cc_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/jp_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/jp_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/jp_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_restart/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_restart/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_restart/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_start/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_start/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_start/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_start/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/oam_dma_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/pop_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/pop_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/pop_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/push_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/push_timing/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/push_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/push_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/rapid_di_ei/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/rapid_di_ei/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/rapid_di_ei/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/rapid_di_ei/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ret_cc_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ret_cc_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ret_cc_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ret_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ret_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/ret_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/reti_intr_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/reti_intr_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/reti_intr_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/reti_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/reti_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/reti_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/rst_timing/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/rst_timing/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/rst_timing/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sav (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/div_write/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/div_write/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/div_write/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/rapid_toggle/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/rapid_toggle/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim00/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim00/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim00/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim01/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim01/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim01/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim10/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim10/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim10/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim11/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim11/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim11/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tima_reload/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tima_reload/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tima_reload/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tima_write_reloading/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tima_write_reloading/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tma_write_reloading/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tma_write_reloading/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/manual-only/sprite_priority/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/manual-only/sprite_priority/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/manual-only/sprite_priority/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/bits/unused_hwio-C/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/bits/unused_hwio-C/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/bits/unused_hwio-C/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/bits/unused_hwio-C/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_hwio-C/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_hwio-C/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_hwio-C/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_hwio-C/test.sav (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_hwio-C/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-A/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-A/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-A/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-A/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-cgb/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-cgb/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-cgb/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/boot_regs-cgb/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.gb (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.sym (100%) rename {src/platform/python/tests/cinema => cinema}/gb/mooneye-gb/update.py (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0005.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0006.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0007.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/baseline_0008.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/dk94-split/test.sav (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/manifest.yml (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/gsc-battle/test.sav (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/baseline_0005.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/baseline_0006.png (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gb/window/zoos-intro/test.sav (100%) rename {src/platform/python/tests/cinema => cinema}/gba/bg/lady-sia/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/bg/lady-sia/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/bg/lady-sia/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/bg/lady-sia/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/bg/lady-sia/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/gs-obj-modes/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/gs-obj-modes/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/gs-obj-modes/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/gs-obj-modes/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/gs-obj-modes/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/gs-obj-modes/baseline_0005.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/gs-obj-modes/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/kam-knockout/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/kam-knockout/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/kam-knockout/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/kam-knockout/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/kam-knockout/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0005.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0006.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0007.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0008.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/baseline_0009.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/mzm-layering/test.sav (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/sma-knockout/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/sma-knockout/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/sma-knockout/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/blend/sma-knockout/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/obj/unaligned-256/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/obj/unaligned-256/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/obj/unaligned-256/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/obj/unaligned-256/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/obj/unaligned-256/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0005.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0006.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0007.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0008.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0009.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0010.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0011.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0012.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0013.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0014.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0015.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0016.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/baseline_0017.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/gs-clock-wipe/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/sthg-objwin-blend/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/sthg-objwin-blend/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/sthg-objwin-blend/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/sthg-objwin-blend/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/sthg-objwin-blend/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0005.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0006.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0007.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0008.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0009.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0010.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0011.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0012.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0013.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0014.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0015.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0016.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0017.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0018.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0019.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0020.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0021.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0022.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0023.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/baseline_0024.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/tgr-objwin-order/test.mvl (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0000.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0001.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0002.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0003.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0004.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0005.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0006.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0007.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0008.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0009.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0010.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0011.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0012.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0013.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0014.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0015.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0016.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0017.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0018.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0019.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0020.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0021.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0022.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0023.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0024.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0025.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0026.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/baseline_0027.png (100%) rename {src/platform/python/tests/cinema => cinema}/gba/window/zmc-window-mosaic/test.mvl (100%) diff --git a/src/platform/python/tests/cinema/.gitignore b/cinema/.gitignore similarity index 100% rename from src/platform/python/tests/cinema/.gitignore rename to cinema/.gitignore diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/LICENSE b/cinema/gb/mooneye-gb/LICENSE similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/LICENSE rename to cinema/gb/mooneye-gb/LICENSE diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/add_sp_e_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/bits/mem_oam/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.gb b/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.gb rename to cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.sym b/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.sym rename to cinema/gb/mooneye-gb/acceptance/bits/mem_oam/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/reg_f/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/bits/reg_f/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/reg_f/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/bits/reg_f/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.gb b/cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.gb rename to cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.sym b/cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.sym rename to cinema/gb/mooneye-gb/acceptance/bits/reg_f/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.gb b/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.gb rename to cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.sym b/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.sym rename to cinema/gb/mooneye-gb/acceptance/bits/unused_hwio-GS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-S/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/manifest.yml b/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-S/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-S/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/manifest.yml b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmg0/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sav b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sav similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sav rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sav diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_hwio-dmgABCXmgb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmg/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/manifest.yml b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmg0/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_regs-dmgABCX/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/manifest.yml b/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_regs-mgb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/manifest.yml b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/manifest.yml b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.gb b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.gb rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.sym b/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.sym rename to cinema/gb/mooneye-gb/acceptance/boot_regs-sgb2/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/call_cc_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/call_cc_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/call_cc_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/call_cc_timing2/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.gb b/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.gb rename to cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.sym b/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.sym rename to cinema/gb/mooneye-gb/acceptance/call_cc_timing2/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/call_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/call_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/call_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/call_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/call_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/call_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing2/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/call_timing2/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing2/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/call_timing2/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing2/test.gb b/cinema/gb/mooneye-gb/acceptance/call_timing2/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing2/test.gb rename to cinema/gb/mooneye-gb/acceptance/call_timing2/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing2/test.sym b/cinema/gb/mooneye-gb/acceptance/call_timing2/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/call_timing2/test.sym rename to cinema/gb/mooneye-gb/acceptance/call_timing2/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/di_timing-GS/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/di_timing-GS/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/di_timing-GS/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/di_timing-GS/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.gb b/cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.gb rename to cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.sym b/cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.sym rename to cinema/gb/mooneye-gb/acceptance/di_timing-GS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/div_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/div_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/div_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/div_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/div_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/div_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/div_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/div_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/div_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/div_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/div_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/div_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ei_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/ei_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ei_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/ei_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ei_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/ei_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ei_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/ei_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ei_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/ei_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ei_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/ei_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/manifest.yml b/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/hblank_ly_scx_timing-GS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_1_2_timing-GS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_0_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode0_timing_sprites/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_mode3_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/intr_2_oam_ok_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/manifest.yml b/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/lcdon_timing-dmgABCXmgbS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/manifest.yml b/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/lcdon_write_timing-GS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/stat_irq_blocking/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.gb b/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.gb rename to cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.sym b/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.sym rename to cinema/gb/mooneye-gb/acceptance/gpu/vblank_stat_intr-GS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.gb b/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.gb rename to cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.sym b/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.sym rename to cinema/gb/mooneye-gb/acceptance/halt_ime0_ei/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/halt_ime0_nointr_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/halt_ime1_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.gb b/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.gb rename to cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.sym b/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.sym rename to cinema/gb/mooneye-gb/acceptance/halt_ime1_timing2-GS/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml b/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.gb b/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.gb rename to cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.sym b/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.sym rename to cinema/gb/mooneye-gb/acceptance/hdma_lcdc/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/if_ie_registers/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/if_ie_registers/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/if_ie_registers/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/if_ie_registers/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.gb b/cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.gb rename to cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.sym b/cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.sym rename to cinema/gb/mooneye-gb/acceptance/if_ie_registers/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.gb b/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.gb rename to cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.sym b/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.sym rename to cinema/gb/mooneye-gb/acceptance/interrupts/ie_push/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/intr_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/intr_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/intr_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/intr_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/intr_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/intr_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/intr_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/intr_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/intr_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/intr_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/intr_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/intr_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/jp_cc_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/jp_cc_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/jp_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/jp_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/jp_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/jp_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/jp_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/jp_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/jp_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/ld_hl_sp_e_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/oam_dma_restart/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.gb b/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.gb rename to cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.sym b/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.sym rename to cinema/gb/mooneye-gb/acceptance/oam_dma_restart/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/oam_dma_start/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/oam_dma_start/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/manifest.yml b/cinema/gb/mooneye-gb/acceptance/oam_dma_start/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/oam_dma_start/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.gb b/cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.gb rename to cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.sym b/cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.sym rename to cinema/gb/mooneye-gb/acceptance/oam_dma_start/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/oam_dma_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/oam_dma_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/pop_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/pop_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/pop_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/pop_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/pop_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/pop_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/pop_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/pop_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/pop_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/pop_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/pop_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/pop_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/push_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/push_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/manifest.yml b/cinema/gb/mooneye-gb/acceptance/push_timing/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/push_timing/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/push_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/push_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/push_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/push_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/push_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/rapid_di_ei/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/manifest.yml b/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/rapid_di_ei/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.gb b/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.gb rename to cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.sym b/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.sym rename to cinema/gb/mooneye-gb/acceptance/rapid_di_ei/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/ret_cc_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/ret_cc_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/ret_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/ret_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/ret_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/ret_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/ret_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/ret_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/ret_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/reti_intr_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/reti_intr_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/reti_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/reti_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/reti_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/reti_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/reti_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/reti_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/reti_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rst_timing/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/rst_timing/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rst_timing/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/rst_timing/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rst_timing/test.gb b/cinema/gb/mooneye-gb/acceptance/rst_timing/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rst_timing/test.gb rename to cinema/gb/mooneye-gb/acceptance/rst_timing/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rst_timing/test.sym b/cinema/gb/mooneye-gb/acceptance/rst_timing/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/rst_timing/test.sym rename to cinema/gb/mooneye-gb/acceptance/rst_timing/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/manifest.yml b/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.gb b/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.gb rename to cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sav b/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sav similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sav rename to cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sav diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sym b/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sym rename to cinema/gb/mooneye-gb/acceptance/serial/boot_sclk_align-dmgABCXmgb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/div_write/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/div_write/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/div_write/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/div_write/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/div_write/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/div_write/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/div_write/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/div_write/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/div_write/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/div_write/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/div_write/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/div_write/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/manifest.yml b/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/rapid_toggle/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim00/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim00/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim00/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim00/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim00/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim00/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim00_div_trigger/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim01/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim01/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim01/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim01/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim01/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim01/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/manifest.yml b/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim01_div_trigger/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim10/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim10/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim10/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim10/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim10/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim10/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim10_div_trigger/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim11/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim11/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim11/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim11/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim11/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim11/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tim11_div_trigger/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tima_reload/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tima_reload/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/manifest.yml b/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tima_write_reloading/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/baseline_0000.png rename to cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/manifest.yml b/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/manifest.yml rename to cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.gb b/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.gb rename to cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.sym b/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.sym rename to cinema/gb/mooneye-gb/acceptance/timer/tma_write_reloading/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/multicart_rom_8Mb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/ram_256Kb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/ram_64Kb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_16Mb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_4Mb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1/rom_8Mb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/baseline_0000.png rename to cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml b/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml rename to cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.gb b/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.gb rename to cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.sym b/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.sym rename to cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png b/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png rename to cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/manifest.yml b/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/manifest.yml rename to cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.gb b/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.gb rename to cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.sym b/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.sym rename to cinema/gb/mooneye-gb/madness/mgb_oam_dma_halt_sprites/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/manifest.yml b/cinema/gb/mooneye-gb/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/manifest.yml rename to cinema/gb/mooneye-gb/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png b/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png rename to cinema/gb/mooneye-gb/manual-only/sprite_priority/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/manifest.yml b/cinema/gb/mooneye-gb/manual-only/sprite_priority/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/manifest.yml rename to cinema/gb/mooneye-gb/manual-only/sprite_priority/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/test.gb b/cinema/gb/mooneye-gb/manual-only/sprite_priority/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/test.gb rename to cinema/gb/mooneye-gb/manual-only/sprite_priority/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/test.sym b/cinema/gb/mooneye-gb/manual-only/sprite_priority/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/manual-only/sprite_priority/test.sym rename to cinema/gb/mooneye-gb/manual-only/sprite_priority/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/baseline_0000.png b/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/baseline_0000.png rename to cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/manifest.yml b/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/manifest.yml rename to cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.gb b/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.gb rename to cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.sym b/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.sym rename to cinema/gb/mooneye-gb/misc/bits/unused_hwio-C/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/baseline_0000.png b/cinema/gb/mooneye-gb/misc/boot_hwio-C/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/baseline_0000.png rename to cinema/gb/mooneye-gb/misc/boot_hwio-C/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/manifest.yml b/cinema/gb/mooneye-gb/misc/boot_hwio-C/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/manifest.yml rename to cinema/gb/mooneye-gb/misc/boot_hwio-C/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.gb b/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.gb rename to cinema/gb/mooneye-gb/misc/boot_hwio-C/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sav b/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sav similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sav rename to cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sav diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sym b/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sym rename to cinema/gb/mooneye-gb/misc/boot_hwio-C/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/baseline_0000.png b/cinema/gb/mooneye-gb/misc/boot_regs-A/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/baseline_0000.png rename to cinema/gb/mooneye-gb/misc/boot_regs-A/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/manifest.yml b/cinema/gb/mooneye-gb/misc/boot_regs-A/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/manifest.yml rename to cinema/gb/mooneye-gb/misc/boot_regs-A/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/test.gb b/cinema/gb/mooneye-gb/misc/boot_regs-A/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/test.gb rename to cinema/gb/mooneye-gb/misc/boot_regs-A/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/test.sym b/cinema/gb/mooneye-gb/misc/boot_regs-A/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-A/test.sym rename to cinema/gb/mooneye-gb/misc/boot_regs-A/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/baseline_0000.png b/cinema/gb/mooneye-gb/misc/boot_regs-cgb/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/baseline_0000.png rename to cinema/gb/mooneye-gb/misc/boot_regs-cgb/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/manifest.yml b/cinema/gb/mooneye-gb/misc/boot_regs-cgb/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/manifest.yml rename to cinema/gb/mooneye-gb/misc/boot_regs-cgb/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.gb b/cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.gb rename to cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.sym b/cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.sym rename to cinema/gb/mooneye-gb/misc/boot_regs-cgb/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/baseline_0000.png b/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/baseline_0000.png rename to cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/manifest.yml b/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/manifest.yml rename to cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.gb b/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.gb similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.gb rename to cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.gb diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.sym b/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.sym similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.sym rename to cinema/gb/mooneye-gb/misc/gpu/vblank_stat_intr-C/test.sym diff --git a/src/platform/python/tests/cinema/gb/mooneye-gb/update.py b/cinema/gb/mooneye-gb/update.py similarity index 100% rename from src/platform/python/tests/cinema/gb/mooneye-gb/update.py rename to cinema/gb/mooneye-gb/update.py diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0000.png b/cinema/gb/window/dk94-split/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0000.png rename to cinema/gb/window/dk94-split/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0001.png b/cinema/gb/window/dk94-split/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0001.png rename to cinema/gb/window/dk94-split/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0002.png b/cinema/gb/window/dk94-split/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0002.png rename to cinema/gb/window/dk94-split/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0003.png b/cinema/gb/window/dk94-split/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0003.png rename to cinema/gb/window/dk94-split/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0004.png b/cinema/gb/window/dk94-split/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0004.png rename to cinema/gb/window/dk94-split/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0005.png b/cinema/gb/window/dk94-split/baseline_0005.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0005.png rename to cinema/gb/window/dk94-split/baseline_0005.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0006.png b/cinema/gb/window/dk94-split/baseline_0006.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0006.png rename to cinema/gb/window/dk94-split/baseline_0006.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0007.png b/cinema/gb/window/dk94-split/baseline_0007.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0007.png rename to cinema/gb/window/dk94-split/baseline_0007.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0008.png b/cinema/gb/window/dk94-split/baseline_0008.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/baseline_0008.png rename to cinema/gb/window/dk94-split/baseline_0008.png diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/test.mvl b/cinema/gb/window/dk94-split/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/test.mvl rename to cinema/gb/window/dk94-split/test.mvl diff --git a/src/platform/python/tests/cinema/gb/window/dk94-split/test.sav b/cinema/gb/window/dk94-split/test.sav similarity index 100% rename from src/platform/python/tests/cinema/gb/window/dk94-split/test.sav rename to cinema/gb/window/dk94-split/test.sav diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0000.png b/cinema/gb/window/gsc-battle/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0000.png rename to cinema/gb/window/gsc-battle/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0001.png b/cinema/gb/window/gsc-battle/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0001.png rename to cinema/gb/window/gsc-battle/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0002.png b/cinema/gb/window/gsc-battle/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0002.png rename to cinema/gb/window/gsc-battle/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0003.png b/cinema/gb/window/gsc-battle/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0003.png rename to cinema/gb/window/gsc-battle/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0004.png b/cinema/gb/window/gsc-battle/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/baseline_0004.png rename to cinema/gb/window/gsc-battle/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/manifest.yml b/cinema/gb/window/gsc-battle/manifest.yml similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/manifest.yml rename to cinema/gb/window/gsc-battle/manifest.yml diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/test.mvl b/cinema/gb/window/gsc-battle/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/test.mvl rename to cinema/gb/window/gsc-battle/test.mvl diff --git a/src/platform/python/tests/cinema/gb/window/gsc-battle/test.sav b/cinema/gb/window/gsc-battle/test.sav similarity index 100% rename from src/platform/python/tests/cinema/gb/window/gsc-battle/test.sav rename to cinema/gb/window/gsc-battle/test.sav diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0000.png b/cinema/gb/window/zoos-intro/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0000.png rename to cinema/gb/window/zoos-intro/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0001.png b/cinema/gb/window/zoos-intro/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0001.png rename to cinema/gb/window/zoos-intro/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0002.png b/cinema/gb/window/zoos-intro/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0002.png rename to cinema/gb/window/zoos-intro/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0003.png b/cinema/gb/window/zoos-intro/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0003.png rename to cinema/gb/window/zoos-intro/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0004.png b/cinema/gb/window/zoos-intro/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0004.png rename to cinema/gb/window/zoos-intro/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0005.png b/cinema/gb/window/zoos-intro/baseline_0005.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0005.png rename to cinema/gb/window/zoos-intro/baseline_0005.png diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0006.png b/cinema/gb/window/zoos-intro/baseline_0006.png similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/baseline_0006.png rename to cinema/gb/window/zoos-intro/baseline_0006.png diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/test.mvl b/cinema/gb/window/zoos-intro/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/test.mvl rename to cinema/gb/window/zoos-intro/test.mvl diff --git a/src/platform/python/tests/cinema/gb/window/zoos-intro/test.sav b/cinema/gb/window/zoos-intro/test.sav similarity index 100% rename from src/platform/python/tests/cinema/gb/window/zoos-intro/test.sav rename to cinema/gb/window/zoos-intro/test.sav diff --git a/src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0000.png b/cinema/gba/bg/lady-sia/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0000.png rename to cinema/gba/bg/lady-sia/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0001.png b/cinema/gba/bg/lady-sia/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0001.png rename to cinema/gba/bg/lady-sia/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0002.png b/cinema/gba/bg/lady-sia/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0002.png rename to cinema/gba/bg/lady-sia/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0003.png b/cinema/gba/bg/lady-sia/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/bg/lady-sia/baseline_0003.png rename to cinema/gba/bg/lady-sia/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/bg/lady-sia/test.mvl b/cinema/gba/bg/lady-sia/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/bg/lady-sia/test.mvl rename to cinema/gba/bg/lady-sia/test.mvl diff --git a/src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0000.png b/cinema/gba/blend/gs-obj-modes/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0000.png rename to cinema/gba/blend/gs-obj-modes/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0001.png b/cinema/gba/blend/gs-obj-modes/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0001.png rename to cinema/gba/blend/gs-obj-modes/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0002.png b/cinema/gba/blend/gs-obj-modes/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0002.png rename to cinema/gba/blend/gs-obj-modes/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0003.png b/cinema/gba/blend/gs-obj-modes/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0003.png rename to cinema/gba/blend/gs-obj-modes/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0004.png b/cinema/gba/blend/gs-obj-modes/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0004.png rename to cinema/gba/blend/gs-obj-modes/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0005.png b/cinema/gba/blend/gs-obj-modes/baseline_0005.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/gs-obj-modes/baseline_0005.png rename to cinema/gba/blend/gs-obj-modes/baseline_0005.png diff --git a/src/platform/python/tests/cinema/gba/blend/gs-obj-modes/test.mvl b/cinema/gba/blend/gs-obj-modes/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/gs-obj-modes/test.mvl rename to cinema/gba/blend/gs-obj-modes/test.mvl diff --git a/src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0000.png b/cinema/gba/blend/kam-knockout/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0000.png rename to cinema/gba/blend/kam-knockout/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0001.png b/cinema/gba/blend/kam-knockout/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0001.png rename to cinema/gba/blend/kam-knockout/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0002.png b/cinema/gba/blend/kam-knockout/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0002.png rename to cinema/gba/blend/kam-knockout/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0003.png b/cinema/gba/blend/kam-knockout/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/kam-knockout/baseline_0003.png rename to cinema/gba/blend/kam-knockout/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/blend/kam-knockout/test.mvl b/cinema/gba/blend/kam-knockout/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/kam-knockout/test.mvl rename to cinema/gba/blend/kam-knockout/test.mvl diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0000.png b/cinema/gba/blend/mzm-layering/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0000.png rename to cinema/gba/blend/mzm-layering/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0001.png b/cinema/gba/blend/mzm-layering/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0001.png rename to cinema/gba/blend/mzm-layering/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0002.png b/cinema/gba/blend/mzm-layering/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0002.png rename to cinema/gba/blend/mzm-layering/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0003.png b/cinema/gba/blend/mzm-layering/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0003.png rename to cinema/gba/blend/mzm-layering/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0004.png b/cinema/gba/blend/mzm-layering/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0004.png rename to cinema/gba/blend/mzm-layering/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0005.png b/cinema/gba/blend/mzm-layering/baseline_0005.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0005.png rename to cinema/gba/blend/mzm-layering/baseline_0005.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0006.png b/cinema/gba/blend/mzm-layering/baseline_0006.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0006.png rename to cinema/gba/blend/mzm-layering/baseline_0006.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0007.png b/cinema/gba/blend/mzm-layering/baseline_0007.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0007.png rename to cinema/gba/blend/mzm-layering/baseline_0007.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0008.png b/cinema/gba/blend/mzm-layering/baseline_0008.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0008.png rename to cinema/gba/blend/mzm-layering/baseline_0008.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0009.png b/cinema/gba/blend/mzm-layering/baseline_0009.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/baseline_0009.png rename to cinema/gba/blend/mzm-layering/baseline_0009.png diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/test.mvl b/cinema/gba/blend/mzm-layering/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/test.mvl rename to cinema/gba/blend/mzm-layering/test.mvl diff --git a/src/platform/python/tests/cinema/gba/blend/mzm-layering/test.sav b/cinema/gba/blend/mzm-layering/test.sav similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/mzm-layering/test.sav rename to cinema/gba/blend/mzm-layering/test.sav diff --git a/src/platform/python/tests/cinema/gba/blend/sma-knockout/baseline_0000.png b/cinema/gba/blend/sma-knockout/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/sma-knockout/baseline_0000.png rename to cinema/gba/blend/sma-knockout/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/blend/sma-knockout/baseline_0001.png b/cinema/gba/blend/sma-knockout/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/sma-knockout/baseline_0001.png rename to cinema/gba/blend/sma-knockout/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/blend/sma-knockout/baseline_0002.png b/cinema/gba/blend/sma-knockout/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/sma-knockout/baseline_0002.png rename to cinema/gba/blend/sma-knockout/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/blend/sma-knockout/test.mvl b/cinema/gba/blend/sma-knockout/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/blend/sma-knockout/test.mvl rename to cinema/gba/blend/sma-knockout/test.mvl diff --git a/src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0000.png b/cinema/gba/obj/unaligned-256/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0000.png rename to cinema/gba/obj/unaligned-256/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0001.png b/cinema/gba/obj/unaligned-256/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0001.png rename to cinema/gba/obj/unaligned-256/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0002.png b/cinema/gba/obj/unaligned-256/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0002.png rename to cinema/gba/obj/unaligned-256/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0003.png b/cinema/gba/obj/unaligned-256/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/obj/unaligned-256/baseline_0003.png rename to cinema/gba/obj/unaligned-256/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/obj/unaligned-256/test.mvl b/cinema/gba/obj/unaligned-256/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/obj/unaligned-256/test.mvl rename to cinema/gba/obj/unaligned-256/test.mvl diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0000.png b/cinema/gba/window/gs-clock-wipe/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0000.png rename to cinema/gba/window/gs-clock-wipe/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0001.png b/cinema/gba/window/gs-clock-wipe/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0001.png rename to cinema/gba/window/gs-clock-wipe/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0002.png b/cinema/gba/window/gs-clock-wipe/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0002.png rename to cinema/gba/window/gs-clock-wipe/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0003.png b/cinema/gba/window/gs-clock-wipe/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0003.png rename to cinema/gba/window/gs-clock-wipe/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0004.png b/cinema/gba/window/gs-clock-wipe/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0004.png rename to cinema/gba/window/gs-clock-wipe/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0005.png b/cinema/gba/window/gs-clock-wipe/baseline_0005.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0005.png rename to cinema/gba/window/gs-clock-wipe/baseline_0005.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0006.png b/cinema/gba/window/gs-clock-wipe/baseline_0006.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0006.png rename to cinema/gba/window/gs-clock-wipe/baseline_0006.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0007.png b/cinema/gba/window/gs-clock-wipe/baseline_0007.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0007.png rename to cinema/gba/window/gs-clock-wipe/baseline_0007.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0008.png b/cinema/gba/window/gs-clock-wipe/baseline_0008.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0008.png rename to cinema/gba/window/gs-clock-wipe/baseline_0008.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0009.png b/cinema/gba/window/gs-clock-wipe/baseline_0009.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0009.png rename to cinema/gba/window/gs-clock-wipe/baseline_0009.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0010.png b/cinema/gba/window/gs-clock-wipe/baseline_0010.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0010.png rename to cinema/gba/window/gs-clock-wipe/baseline_0010.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0011.png b/cinema/gba/window/gs-clock-wipe/baseline_0011.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0011.png rename to cinema/gba/window/gs-clock-wipe/baseline_0011.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0012.png b/cinema/gba/window/gs-clock-wipe/baseline_0012.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0012.png rename to cinema/gba/window/gs-clock-wipe/baseline_0012.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0013.png b/cinema/gba/window/gs-clock-wipe/baseline_0013.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0013.png rename to cinema/gba/window/gs-clock-wipe/baseline_0013.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0014.png b/cinema/gba/window/gs-clock-wipe/baseline_0014.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0014.png rename to cinema/gba/window/gs-clock-wipe/baseline_0014.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0015.png b/cinema/gba/window/gs-clock-wipe/baseline_0015.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0015.png rename to cinema/gba/window/gs-clock-wipe/baseline_0015.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0016.png b/cinema/gba/window/gs-clock-wipe/baseline_0016.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0016.png rename to cinema/gba/window/gs-clock-wipe/baseline_0016.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0017.png b/cinema/gba/window/gs-clock-wipe/baseline_0017.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/baseline_0017.png rename to cinema/gba/window/gs-clock-wipe/baseline_0017.png diff --git a/src/platform/python/tests/cinema/gba/window/gs-clock-wipe/test.mvl b/cinema/gba/window/gs-clock-wipe/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/window/gs-clock-wipe/test.mvl rename to cinema/gba/window/gs-clock-wipe/test.mvl diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0000.png b/cinema/gba/window/sthg-objwin-blend/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0000.png rename to cinema/gba/window/sthg-objwin-blend/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0001.png b/cinema/gba/window/sthg-objwin-blend/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0001.png rename to cinema/gba/window/sthg-objwin-blend/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0002.png b/cinema/gba/window/sthg-objwin-blend/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0002.png rename to cinema/gba/window/sthg-objwin-blend/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0003.png b/cinema/gba/window/sthg-objwin-blend/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/baseline_0003.png rename to cinema/gba/window/sthg-objwin-blend/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/test.mvl b/cinema/gba/window/sthg-objwin-blend/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/window/sthg-objwin-blend/test.mvl rename to cinema/gba/window/sthg-objwin-blend/test.mvl diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0000.png b/cinema/gba/window/tgr-objwin-order/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0000.png rename to cinema/gba/window/tgr-objwin-order/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0001.png b/cinema/gba/window/tgr-objwin-order/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0001.png rename to cinema/gba/window/tgr-objwin-order/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0002.png b/cinema/gba/window/tgr-objwin-order/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0002.png rename to cinema/gba/window/tgr-objwin-order/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0003.png b/cinema/gba/window/tgr-objwin-order/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0003.png rename to cinema/gba/window/tgr-objwin-order/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0004.png b/cinema/gba/window/tgr-objwin-order/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0004.png rename to cinema/gba/window/tgr-objwin-order/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0005.png b/cinema/gba/window/tgr-objwin-order/baseline_0005.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0005.png rename to cinema/gba/window/tgr-objwin-order/baseline_0005.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0006.png b/cinema/gba/window/tgr-objwin-order/baseline_0006.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0006.png rename to cinema/gba/window/tgr-objwin-order/baseline_0006.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0007.png b/cinema/gba/window/tgr-objwin-order/baseline_0007.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0007.png rename to cinema/gba/window/tgr-objwin-order/baseline_0007.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0008.png b/cinema/gba/window/tgr-objwin-order/baseline_0008.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0008.png rename to cinema/gba/window/tgr-objwin-order/baseline_0008.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0009.png b/cinema/gba/window/tgr-objwin-order/baseline_0009.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0009.png rename to cinema/gba/window/tgr-objwin-order/baseline_0009.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0010.png b/cinema/gba/window/tgr-objwin-order/baseline_0010.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0010.png rename to cinema/gba/window/tgr-objwin-order/baseline_0010.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0011.png b/cinema/gba/window/tgr-objwin-order/baseline_0011.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0011.png rename to cinema/gba/window/tgr-objwin-order/baseline_0011.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0012.png b/cinema/gba/window/tgr-objwin-order/baseline_0012.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0012.png rename to cinema/gba/window/tgr-objwin-order/baseline_0012.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0013.png b/cinema/gba/window/tgr-objwin-order/baseline_0013.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0013.png rename to cinema/gba/window/tgr-objwin-order/baseline_0013.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0014.png b/cinema/gba/window/tgr-objwin-order/baseline_0014.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0014.png rename to cinema/gba/window/tgr-objwin-order/baseline_0014.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0015.png b/cinema/gba/window/tgr-objwin-order/baseline_0015.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0015.png rename to cinema/gba/window/tgr-objwin-order/baseline_0015.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0016.png b/cinema/gba/window/tgr-objwin-order/baseline_0016.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0016.png rename to cinema/gba/window/tgr-objwin-order/baseline_0016.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0017.png b/cinema/gba/window/tgr-objwin-order/baseline_0017.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0017.png rename to cinema/gba/window/tgr-objwin-order/baseline_0017.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0018.png b/cinema/gba/window/tgr-objwin-order/baseline_0018.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0018.png rename to cinema/gba/window/tgr-objwin-order/baseline_0018.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0019.png b/cinema/gba/window/tgr-objwin-order/baseline_0019.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0019.png rename to cinema/gba/window/tgr-objwin-order/baseline_0019.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0020.png b/cinema/gba/window/tgr-objwin-order/baseline_0020.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0020.png rename to cinema/gba/window/tgr-objwin-order/baseline_0020.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0021.png b/cinema/gba/window/tgr-objwin-order/baseline_0021.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0021.png rename to cinema/gba/window/tgr-objwin-order/baseline_0021.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0022.png b/cinema/gba/window/tgr-objwin-order/baseline_0022.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0022.png rename to cinema/gba/window/tgr-objwin-order/baseline_0022.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0023.png b/cinema/gba/window/tgr-objwin-order/baseline_0023.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0023.png rename to cinema/gba/window/tgr-objwin-order/baseline_0023.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0024.png b/cinema/gba/window/tgr-objwin-order/baseline_0024.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/baseline_0024.png rename to cinema/gba/window/tgr-objwin-order/baseline_0024.png diff --git a/src/platform/python/tests/cinema/gba/window/tgr-objwin-order/test.mvl b/cinema/gba/window/tgr-objwin-order/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/window/tgr-objwin-order/test.mvl rename to cinema/gba/window/tgr-objwin-order/test.mvl diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0000.png b/cinema/gba/window/zmc-window-mosaic/baseline_0000.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0000.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0000.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0001.png b/cinema/gba/window/zmc-window-mosaic/baseline_0001.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0001.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0001.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0002.png b/cinema/gba/window/zmc-window-mosaic/baseline_0002.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0002.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0002.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0003.png b/cinema/gba/window/zmc-window-mosaic/baseline_0003.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0003.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0003.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0004.png b/cinema/gba/window/zmc-window-mosaic/baseline_0004.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0004.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0004.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0005.png b/cinema/gba/window/zmc-window-mosaic/baseline_0005.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0005.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0005.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0006.png b/cinema/gba/window/zmc-window-mosaic/baseline_0006.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0006.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0006.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0007.png b/cinema/gba/window/zmc-window-mosaic/baseline_0007.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0007.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0007.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0008.png b/cinema/gba/window/zmc-window-mosaic/baseline_0008.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0008.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0008.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0009.png b/cinema/gba/window/zmc-window-mosaic/baseline_0009.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0009.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0009.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0010.png b/cinema/gba/window/zmc-window-mosaic/baseline_0010.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0010.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0010.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0011.png b/cinema/gba/window/zmc-window-mosaic/baseline_0011.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0011.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0011.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0012.png b/cinema/gba/window/zmc-window-mosaic/baseline_0012.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0012.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0012.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0013.png b/cinema/gba/window/zmc-window-mosaic/baseline_0013.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0013.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0013.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0014.png b/cinema/gba/window/zmc-window-mosaic/baseline_0014.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0014.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0014.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0015.png b/cinema/gba/window/zmc-window-mosaic/baseline_0015.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0015.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0015.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0016.png b/cinema/gba/window/zmc-window-mosaic/baseline_0016.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0016.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0016.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0017.png b/cinema/gba/window/zmc-window-mosaic/baseline_0017.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0017.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0017.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0018.png b/cinema/gba/window/zmc-window-mosaic/baseline_0018.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0018.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0018.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0019.png b/cinema/gba/window/zmc-window-mosaic/baseline_0019.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0019.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0019.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0020.png b/cinema/gba/window/zmc-window-mosaic/baseline_0020.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0020.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0020.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0021.png b/cinema/gba/window/zmc-window-mosaic/baseline_0021.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0021.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0021.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0022.png b/cinema/gba/window/zmc-window-mosaic/baseline_0022.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0022.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0022.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0023.png b/cinema/gba/window/zmc-window-mosaic/baseline_0023.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0023.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0023.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0024.png b/cinema/gba/window/zmc-window-mosaic/baseline_0024.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0024.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0024.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0025.png b/cinema/gba/window/zmc-window-mosaic/baseline_0025.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0025.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0025.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0026.png b/cinema/gba/window/zmc-window-mosaic/baseline_0026.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0026.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0026.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0027.png b/cinema/gba/window/zmc-window-mosaic/baseline_0027.png similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/baseline_0027.png rename to cinema/gba/window/zmc-window-mosaic/baseline_0027.png diff --git a/src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/test.mvl b/cinema/gba/window/zmc-window-mosaic/test.mvl similarity index 100% rename from src/platform/python/tests/cinema/gba/window/zmc-window-mosaic/test.mvl rename to cinema/gba/window/zmc-window-mosaic/test.mvl diff --git a/src/platform/python/test_cinema.py b/src/platform/python/test_cinema.py index 61c27bf7d..addb38094 100644 --- a/src/platform/python/test_cinema.py +++ b/src/platform/python/test_cinema.py @@ -18,7 +18,7 @@ def flatten(d): def pytest_generate_tests(metafunc): if 'vtest' in metafunc.fixturenames: - tests = cinema.test.gatherTests(os.path.join(os.path.dirname(__file__), 'tests/cinema')) + tests = cinema.test.gatherTests(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'cinema')) testList = flatten(tests) params = [] for test in testList: From cda0f95464f1e2a7c9f1202b87c476bfb07ed6e1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 26 Oct 2017 20:04:33 -0700 Subject: [PATCH 035/152] Qt: Add option to disable FPS display --- CHANGES | 1 + src/platform/qt/SettingsView.cpp | 2 ++ src/platform/qt/SettingsView.ui | 10 ++++++++++ src/platform/qt/Window.cpp | 14 +++++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index fafa088ef..e41f9eef9 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,7 @@ Misc: - Util: Don't build crc32 if the function already exists - GBA: Implement display start DMAs - Qt: Prevent window from being created off-screen + - Qt: Add option to disable FPS display 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index d17134563..14a58e175 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -337,6 +337,7 @@ void SettingsView::updateConfig() { saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex()); saveSetting("showLibrary", m_ui.showLibrary); saveSetting("preload", m_ui.preload); + saveSetting("showFps", m_ui.showFps); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); @@ -454,6 +455,7 @@ void SettingsView::reloadConfig() { loadSetting("patchPath", m_ui.patchPath); loadSetting("showLibrary", m_ui.showLibrary); loadSetting("preload", m_ui.preload); + loadSetting("showFps", m_ui.showFps, true); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index be801f5f3..1fabdd8cd 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -495,6 +495,16 @@ + + + + Show FPS in title bar + + + true + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 95cf7c83f..0ffd22ab9 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -677,7 +677,9 @@ void Window::gameStarted() { #endif m_hitUnimplementedBiosCall = false; - m_fpsTimer.start(); + if (m_config->getOption("showFps", "1").toInt()) { + m_fpsTimer.start(); + } m_focusCheck.start(); if (m_display->underMouse()) { m_screenWidget->setCursor(Qt::BlankCursor); @@ -1585,6 +1587,16 @@ void Window::setupMenu(QMenuBar* menubar) { }, this); m_config->updateOption("preload"); + ConfigOption* showFps = m_config->addOption("showFps"); + showFps->connect([this](const QVariant& value) { + if (!value.toInt()) { + m_fpsTimer.stop(); + updateTitle(); + } else if (m_controller) { + m_fpsTimer.start(); + } + }, this); + QAction* exitFullScreen = new QAction(tr("Exit fullscreen"), frameMenu); connect(exitFullScreen, &QAction::triggered, this, &Window::exitFullScreen); exitFullScreen->setShortcut(QKeySequence("Esc")); From 4e296c3efc2fb1b753ef1a1085642d691d4b1567 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 28 Oct 2017 00:23:23 -0700 Subject: [PATCH 036/152] GBA Video: Cache mode 0 map data per 8 rows --- .../internal/gba/renderers/video-software.h | 2 + src/gba/renderers/software-mode0.c | 72 +++++++++++++++---- src/gba/renderers/video-software.c | 5 ++ 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/include/mgba/internal/gba/renderers/video-software.h b/include/mgba/internal/gba/renderers/video-software.h index 6ce9c2037..77aaf63fb 100644 --- a/include/mgba/internal/gba/renderers/video-software.h +++ b/include/mgba/internal/gba/renderers/video-software.h @@ -42,6 +42,8 @@ struct GBAVideoSoftwareBackground { int16_t dmy; int32_t sx; int32_t sy; + int yCache; + uint16_t mapCache[64]; }; enum BlendEffect { diff --git a/src/gba/renderers/software-mode0.c b/src/gba/renderers/software-mode0.c index 74db03180..f0925528a 100644 --- a/src/gba/renderers/software-mode0.c +++ b/src/gba/renderers/software-mode0.c @@ -8,17 +8,12 @@ #include #define BACKGROUND_TEXT_SELECT_CHARACTER \ - localX = tileX * 8 + inX; \ xBase = localX & 0xF8; \ if (background->size & 1) { \ xBase += (localX & 0x100) << 5; \ } \ screenBase = yBase + (xBase >> 3); \ LOAD_16(mapData, screenBase << 1, vram); \ - localY = inY & 0x7; \ - if (GBA_TEXT_MAP_VFLIP(mapData)) { \ - localY = 7 - localY; \ - } #define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \ paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ @@ -77,11 +72,21 @@ if (baseX < 0) { \ int disturbX = (16 + baseX) >> 3; \ inX -= disturbX << 3; \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ baseX -= disturbX << 3; \ inX += disturbX << 3; \ } else { \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ if (UNLIKELY(charBase >= 0x10000)) { \ @@ -102,8 +107,14 @@ carryData = tileData; \ } \ } \ + localX = tileX * 8 + inX; \ for (; length; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ tileData = carryData; \ for (; x < 8 && length; ++x, --length) { \ @@ -136,7 +147,12 @@ #define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \ palette = &mainPalette[paletteData]; \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \ @@ -264,7 +280,12 @@ #define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ if (UNLIKELY(charBase >= 0x10000)) { \ pixel += 8; \ @@ -308,8 +329,14 @@ } #define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \ + localX = tileX * 8 + inX; \ for (; tileX < tileEnd; ++tileX) { \ - BACKGROUND_TEXT_SELECT_CHARACTER; \ + mapData = background->mapCache[(localX >> 3) & 0x3F]; \ + localX += 8; \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \ tileData = carryData; \ for (x = 0; x < 8; ++x) { \ @@ -359,8 +386,12 @@ } \ \ if (inX & 0x7) { \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ int mod8 = inX & 0x7; \ int end = outX + 0x8 - mod8; \ if (end > renderer->end) { \ @@ -390,10 +421,15 @@ /*!*/ mLOG(GBA_VIDEO, FATAL, "Out of bounds background draw would occur!"); \ /*!*/ return; \ /*!*/ } \ + localX = (tileX * 8 + inX) & 0x1FF; \ DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \ if (length & 0x7) { \ + localX = tileX * 8 + inX; \ BACKGROUND_TEXT_SELECT_CHARACTER; \ - \ + localY = inY & 0x7; \ + if (GBA_TEXT_MAP_VFLIP(mapData)) { \ + localY = 7 - localY; \ + } \ int mod8 = length & 0x7; \ if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \ mLOG(GBA_VIDEO, FATAL, "Invariant doesn't hold in background draw!"); \ @@ -410,7 +446,7 @@ } void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) { - int inX = renderer->start + background->x; + int inX = (renderer->start + background->x) & 0x1FF; int length = renderer->end - renderer->start; if (background->mosaic) { int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; @@ -458,10 +494,20 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer uint32_t current; int pixelData; int paletteData; - int tileX = 0; + int tileX; int tileEnd = ((length + inX) >> 3) - (inX >> 3); uint16_t* vram = renderer->d.vram; + if (background->yCache != inY >> 3) { + localX = 0; + for (tileX = 0; tileX < 64; ++tileX, localX += 8) { + BACKGROUND_TEXT_SELECT_CHARACTER; + background->mapCache[tileX] = mapData; + } + background->yCache = inY >> 3; + } + + tileX = 0; if (!objwinSlowPath) { if (!(flags & FLAG_TARGET_2)) { if (!background->multipalette) { diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index f1148bc0f..04431cfe5 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -140,6 +140,7 @@ static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) { bg->dmy = 256; bg->sx = 0; bg->sy = 0; + bg->yCache = -1; } } @@ -388,6 +389,10 @@ static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, mCacheSetWriteVRAM(renderer->cache, address); } memset(softwareRenderer->scanlineDirty, 0xFFFFFFFF, sizeof(softwareRenderer->scanlineDirty)); + softwareRenderer->bg[0].yCache = -1; + softwareRenderer->bg[1].yCache = -1; + softwareRenderer->bg[2].yCache = -1; + softwareRenderer->bg[3].yCache = -1; } static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { From c6ce7b0bb6cb1582e0dc94fd2a732f9b356868df Mon Sep 17 00:00:00 2001 From: rootfather Date: Sat, 28 Oct 2017 17:03:25 +0200 Subject: [PATCH 037/152] Qt: Update German GUI translation --- src/platform/qt/ts/mgba-de.ts | 401 +++++++++++++++++----------------- 1 file changed, 203 insertions(+), 198 deletions(-) diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index 3d5b7b4d2..4382f1b5c 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -3216,12 +3216,12 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Video-Logs (*.mvl) - + Crash Absturz - + The game has crashed with the following error: %1 @@ -3230,528 +3230,528 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + Couldn't Load Konnte nicht geladen werden - + Could not load game. Are you sure it's in the correct format? Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt? - + Unimplemented BIOS call Nicht implementierter BIOS-Aufruf - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS. - + Really make portable? Portablen Modus wirklich aktivieren? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren? - + Restart needed Neustart benötigt - + Some changes will not take effect until the emulator is restarted. Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde. - + - Player %1 of %2 - Spieler %1 von %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 Bilder/Sekunde) - %4 - + &File &Datei - + Load &ROM... &ROM laden... - + Load ROM in archive... ROM aus Archiv laden... - + Load temporary save... Temporäre Speicherdatei laden... - + Load &patch... &Patch laden... - + Boot BIOS BIOS booten - + Replace ROM... ROM ersetzen... - + ROM &info... ROM-&Informationen... - + Recent Zuletzt verwendet - + Make portable Portablen Modus aktivieren - + &Load state Savestate (aktueller Zustand) &laden - + F10 F10 - + &Save state Savestate (aktueller Zustand) &speichern - + Shift+F10 Umschalt+F10 - + Quick load Schnell laden - + Quick save Schnell speichern - + Load recent Lade zuletzt gespeicherten Savestate - + Save recent Speichere aktuellen Zustand - + Undo load state Laden des Savestate rückgängig machen - + F11 F11 - + Undo save state Speichern des Savestate rückgängig machen - + Shift+F11 Umschalt+F11 - - + + State &%1 Savestate &%1 - + F%1 F%1 - + Shift+F%1 Umschalt+F%1 - + Load camera image... Lade Kamerabild... - + Import GameShark Save Importiere GameShark-Speicherstand - + Export GameShark Save Exportiere GameShark-Speicherstand - + New multiplayer window Neues Multiplayer-Fenster - + About Über - + E&xit &Beenden - + &Emulation &Emulation - + &Reset Zu&rücksetzen - + Ctrl+R Strg+R - + Sh&utdown Schli&eßen - + Yank game pak Spielmodul herausziehen - + &Pause &Pause - + Ctrl+P Strg+P - + &Next frame &Nächstes Bild - + Ctrl+N Strg+N - + Fast forward (held) Schneller Vorlauf (gehalten) - + &Fast forward Schneller &Vorlauf - + Shift+Tab Umschalt+Tab - + Fast forward speed Vorlauf-Geschwindigkeit - + Unbounded Unbegrenzt - + %0x %0x - + Rewind (held) Zurückspulen (gehalten) - + Re&wind Zur&ückspulen - + ~ ~ - + Step backwards Schrittweiser Rücklauf - + Ctrl+B Strg+B - + Sync to &video Mit &Video synchronisieren - + Sync to &audio Mit &Audio synchronisieren - + Solar sensor Solar-Sensor - + Increase solar level Sonnen-Level erhöhen - + Decrease solar level Sonnen-Level verringern - + Brightest solar level Hellster Sonnen-Level - + Darkest solar level Dunkelster Sonnen-Level - + Brightness %1 Helligkeit %1 - + Audio/&Video Audio/&Video - + Frame size Bildgröße - + %1x %1x - + Toggle fullscreen Vollbildmodus umschalten - + Lock aspect ratio Seitenverhältnis korrigieren - + Force integer scaling Pixelgenaue Skalierung (Integer scaling) - + Frame&skip Frame&skip - + Mute Stummschalten - + FPS target Bildwiederholrate - + 15 15 - + 30 30 - + 45 45 - + Native (59.7) Nativ (59.7) - + 60 60 - + 90 90 - + 120 120 - + 240 240 - + Take &screenshot &Screenshot erstellen - + F12 F12 - + Record output... Ausgabe aufzeichen... - + Record GIF... GIF aufzeichen... - + Record video log... Video-Log aufzeichnen... - + Stop video log Video-Log beenden - + Game Boy Printer... Game Boy Printer... - + Video layers Video-Ebenen - + Audio channels Audio-Kanäle - + &Tools &Werkzeuge - + View &logs... &Logs ansehen... - + Game &overrides... Spiel-&Überschreibungen... - + Game &Pak sensors... Game &Pak-Sensoren... - + &Cheats... &Cheats... - + Open debugger console... Debugger-Konsole äffnen... - + Start &GDB server... &GDB-Server starten... - + Settings... Einstellungen... @@ -3761,107 +3761,107 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Ordner auswählen - + Add folder to library... Ordner zur Bibliothek hinzufügen... - + Bilinear filtering Bilineare Filterung - + View &palette... &Palette betrachten... - + View &sprites... &Sprites betrachten... - + View &tiles... &Tiles betrachten... - + View &map... &Map betrachten... - + View memory... Speicher betrachten... - + Search memory... Speicher durchsuchen... - + View &I/O registers... &I/O-Register betrachten... - + Exit fullscreen Vollbildmodus beenden - + Autofire Autofeuer - + Autofire A Autofeuer A - + Autofire B Autofeuer B - + Autofire L Autofeuer L - + Autofire R Autofeuer R - + Autofire Start Autofeuer Start - + Autofire Select Autofeuer Select - + Autofire Up Autofeuer nach oben - + Autofire Right Autofeuer rechts - + Autofire Down Autofeuer nach unten - + Autofire Left Autofeuer links @@ -4158,7 +4158,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + frames Bilder @@ -4219,77 +4219,82 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Baumansicht - + + Show FPS in title bar + Bildwiederholrate in der Titelleiste anzeigen + + + Game Boy model Game Boy-Modell - - - - - Autodetect - Automatisch erkennen - - - - - - Game Boy (DMG) - Game Boy (DMG) - - Super Game Boy (SGB) - Super Game Boy (SGB) + Autodetect + Automatisch erkennen - Game Boy Color (CGB) - Game Boy Color (CGB) + Game Boy (DMG) + Game Boy (DMG) + Super Game Boy (SGB) + Super Game Boy (SGB) + + + + + + Game Boy Color (CGB) + Game Boy Color (CGB) + + + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Super Game Boy model Super Game Boy-Modell - + Game Boy Color model Game Boy Color-Modell - + Default BG colors: Standard-Hintergrundfarben: - + Default sprite colors 1: Standard-Sprite-Farben 1: - + Default sprite colors 2: Standard-Sprite-Farben 2: - + Super Game Boy borders Super Game Boy-Rahmen - + Camera driver: Kamera-Treiber: @@ -4309,52 +4314,52 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Cache leeren - + Fast forward speed: Vorlauf-Geschwindigkeit: - + Rewind affects save data Rücklauf beeinflusst Speicherdaten - + Preload entire ROM into memory ROM-Datei vollständig in Arbeitsspeicher vorladen - - - - - - - - + + + + + + + + Browse Durchsuchen - + Use BIOS file if found BIOS-Datei verwenden, wenn vorhanden - + Skip BIOS intro BIOS-Intro überspringen - + × × - + Unbounded unbegrenzt @@ -4374,17 +4379,17 @@ wenn vorhanden Pause, wenn inaktiv - + Run all Alle ausführen - + Remove known Bekannte entfernen - + Detect and remove Erkennen und entfernen @@ -4394,25 +4399,25 @@ wenn vorhanden Gegensätzliche Eingaberichtungen erlauben - - + + Screenshot Screenshot - - + + Save data Speicherdaten - - + + Cheat codes Cheat-Codes - + Enable rewind Rücklauf aktivieren @@ -4422,75 +4427,75 @@ wenn vorhanden Bilineare Filterung - + Rewind history: Rücklauf-Verlauf: - + Idle loops: Leerlaufprozesse: - + Savestate extra data: Zusätzliche Savestate-Daten: - + Load extra data: Lade zusätzliche Daten: - + Autofire interval: Autofeuer-Intervall: - + GB BIOS file: Datei mit GB-BIOS: - + GBA BIOS file: Datei mit GBA-BIOS: - + GBC BIOS file: Datei mit GBC-BIOS: - + SGB BIOS file: Datei mit SGB-BIOS: - + Save games Spielstände - - - - + + + + Same directory as the ROM Verzeichnis der ROM-Datei - + Save states Savestates - + Screenshots Screenshots - + Patches Patches From 20754b772e23b4aafa0c24c626c773ea2f027c03 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 29 Oct 2017 17:09:54 -0700 Subject: [PATCH 038/152] GBA Memory: Slightly simplify prefetch logic --- src/gba/memory.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 022d812a9..37da2c62d 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1531,9 +1531,11 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { int32_t previousLoads = 0; // Don't prefetch too much if we're overlapping with a previous prefetch - uint32_t dist = (memory->lastPrefetchedPc - cpu->gprs[ARM_PC]) >> 1; - if (dist < 8) { - previousLoads = dist; + uint32_t dist = (memory->lastPrefetchedPc - cpu->gprs[ARM_PC]); + int32_t maxLoads = 8; + if (dist < 16) { + previousLoads = dist >> 1; + maxLoads -= previousLoads; } int32_t s = cpu->memory.activeSeqCycles16; @@ -1543,12 +1545,9 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { int32_t stall = s; int32_t loads = 1; - if (stall < wait) { - int32_t maxLoads = 8 - previousLoads; - while (stall < wait && loads < maxLoads) { - stall += s; - ++loads; - } + while (stall < wait && loads < maxLoads) { + stall += s; + ++loads; } if (stall > wait) { // The wait cannot take less time than the prefetch stalls From 679630701eb2ea639f625f427c08981633ee9cdb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Nov 2017 16:55:31 -0700 Subject: [PATCH 039/152] GBA DMA: Fix invalid DMA reads (fixes #142) --- CHANGES | 1 + include/mgba/internal/gba/memory.h | 1 + include/mgba/internal/gba/serialize.h | 7 +++-- src/gba/dma.c | 41 ++++++++++++++++++--------- src/gba/io.c | 3 ++ 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index e41f9eef9..596cbf4f7 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,7 @@ Bugfixes: - GB Video: Only trigger STAT write IRQs when screen is on (fixes mgba.io/i/912) - GBA Cheats: Fix PARv3 slide codes (fixes mgba.io/i/919) - GBA Video: OBJWIN can change blend params after OBJ is drawn (fixes mgba.io/i/921) + - GBA DMA: Fix invalid DMA reads (fixes mgba.io/i/142) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index b780bebf2..259813b67 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -106,6 +106,7 @@ struct GBAMemory { struct GBADMA dma[4]; struct mTimingEvent dmaEvent; int activeDMA; + uint32_t dmaTransferRegister; bool mirroring; }; diff --git a/include/mgba/internal/gba/serialize.h b/include/mgba/internal/gba/serialize.h index 8b4e4a84b..26b9daf31 100644 --- a/include/mgba/internal/gba/serialize.h +++ b/include/mgba/internal/gba/serialize.h @@ -169,7 +169,8 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | bits 4 - 8: GB Player transmit position * | bits 9 - 23: Reserved * 0x002C4 - 0x002C7: Game Boy Player next event - * 0x002C8 - 0x002DF: Reserved (leave zero) + * 0x002C8 - 0x002CB: Current DMA transfer word + * 0x002CC - 0x002DF: Reserved (leave zero) * 0x002E0 - 0x002EF: Savedata state * | 0x002E0 - 0x002E0: Savedata type * | 0x002E1 - 0x002E1: Savedata command (see savedata.h) @@ -293,7 +294,9 @@ struct GBASerializedState { uint32_t gbpNextEvent; } hw; - uint32_t reservedHardware[6]; + uint32_t dmaTransferRegister; + + uint32_t reservedHardware[5]; struct { uint8_t type; diff --git a/src/gba/dma.c b/src/gba/dma.c index e2d67d5b7..0e0abeefe 100644 --- a/src/gba/dma.c +++ b/src/gba/dma.c @@ -46,6 +46,8 @@ uint32_t GBADMAWriteSAD(struct GBA* gba, int dma, uint32_t address) { address &= 0x0FFFFFFE; if (_isValidDMASAD(dma, address)) { memory->dma[dma].source = address; + } else { + memory->dma[dma].source = 0; } return memory->dma[dma].source; } @@ -242,31 +244,44 @@ void GBADMAService(struct GBA* gba, int number, struct GBADMA* info) { info->when += cycles; gba->performingDMA = 1 | (number << 1); - uint32_t word; if (width == 4) { - word = cpu->memory.load32(cpu, source, 0); - gba->bus = word; - cpu->memory.store32(cpu, dest, word, 0); + if (source) { + memory->dmaTransferRegister = cpu->memory.load32(cpu, source, 0); + } + gba->bus = memory->dmaTransferRegister; + cpu->memory.store32(cpu, dest, memory->dmaTransferRegister, 0); + memory->dmaTransferRegister &= 0xFFFF0000; + memory->dmaTransferRegister |= memory->dmaTransferRegister >> 16; } else { if (sourceRegion == REGION_CART2_EX && memory->savedata.type == SAVEDATA_EEPROM) { - word = GBASavedataReadEEPROM(&memory->savedata); - cpu->memory.store16(cpu, dest, word, 0); - } else if (destRegion == REGION_CART2_EX) { if (memory->savedata.type == SAVEDATA_AUTODETECT) { mLOG(GBA_MEM, INFO, "Detected EEPROM savegame"); GBASavedataInitEEPROM(&memory->savedata, gba->realisticTiming); } - word = cpu->memory.load16(cpu, source, 0); - GBASavedataWriteEEPROM(&memory->savedata, word, wordsRemaining); + memory->dmaTransferRegister = GBASavedataReadEEPROM(&memory->savedata); } else { - word = cpu->memory.load16(cpu, source, 0); - cpu->memory.store16(cpu, dest, word, 0); + if (source) { + memory->dmaTransferRegister = cpu->memory.load16(cpu, source, 0); + } } - gba->bus = word | (word << 16); + if (destRegion == REGION_CART2_EX) { + if (memory->savedata.type == SAVEDATA_AUTODETECT) { + mLOG(GBA_MEM, INFO, "Detected EEPROM savegame"); + GBASavedataInitEEPROM(&memory->savedata, gba->realisticTiming); + } + GBASavedataWriteEEPROM(&memory->savedata, memory->dmaTransferRegister, wordsRemaining); + } else { + cpu->memory.store16(cpu, dest, memory->dmaTransferRegister, 0); + + } + memory->dmaTransferRegister |= memory->dmaTransferRegister << 16; + gba->bus = memory->dmaTransferRegister; } int sourceOffset = DMA_OFFSET[GBADMARegisterGetSrcControl(info->reg)] * width; int destOffset = DMA_OFFSET[GBADMARegisterGetDestControl(info->reg)] * width; - source += sourceOffset; + if (source) { + source += sourceOffset; + } dest += destOffset; --wordsRemaining; gba->performingDMA = 0; diff --git a/src/gba/io.c b/src/gba/io.c index 716334519..7451c0d44 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -939,6 +939,8 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) { STORE_32(gba->memory.dma[i].when, 0, &state->dma[i].when); } + state->dmaTransferRegister = gba->memory.dmaTransferRegister; + GBAHardwareSerialize(&gba->memory.hw, state); } @@ -984,6 +986,7 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) { } } GBAAudioWriteSOUNDCNT_X(&gba->audio, gba->memory.io[REG_SOUNDCNT_X >> 1]); + gba->memory.dmaTransferRegister = state->dmaTransferRegister; GBADMAUpdate(gba); GBAHardwareDeserialize(&gba->memory.hw, state); } From ea9af9e35b0ada86be3f6227bf956c7cefe41925 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Nov 2017 16:56:36 -0700 Subject: [PATCH 040/152] Revert "GBA Video: Don't mask out high bits of BLDY (fixes #899)" This reverts commit 17dac6486b4b0f20455548db1f55f37cc068f19e. --- src/gba/renderers/video-software.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 04431cfe5..3f7738ab1 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -296,6 +296,7 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender value &= 0x1F1F; break; case REG_BLDY: + value &= 0x1F; if (value > 0x10) { value = 0x10; } From 74bd78f38226e00c900b3bb8672a828d750aced6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Nov 2017 16:57:09 -0700 Subject: [PATCH 041/152] GBA: Improve multiboot image detection --- CHANGES | 1 + src/gba/gba.c | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 596cbf4f7..8c3cd8a64 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ Misc: - GBA: Implement display start DMAs - Qt: Prevent window from being created off-screen - Qt: Add option to disable FPS display + - GBA: Improve multiboot image detection 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/gba/gba.c b/src/gba/gba.c index 9073c5233..a2385954e 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -569,16 +569,49 @@ bool GBAIsMB(struct VFile* vf) { LOAD_32(opcode, 0, &signature); struct ARMInstructionInfo info; ARMDecodeARM(opcode, &info); - if (info.branchType != ARM_BRANCH) { - return false; + if (info.branchType == ARM_BRANCH) { + if (info.op1.immediate <= 0) { + return false; + } else if (info.op1.immediate == 28) { + // Ancient toolchain that is known to throw MB detection for a loop + return false; + } else if (info.op1.immediate != 24) { + return true; + } } - if (info.op1.immediate <= 0) { - return false; - } else if (info.op1.immediate == 28) { - // Ancient toolchain that is known to throw MB detection for a loop - return false; - } else if (info.op1.immediate != 24) { - return true; + + uint32_t pc = GBA_MB_MAGIC_OFFSET; + int i; + for (i = 0; i < 80; ++i) { + if (vf->read(vf, &signature, sizeof(signature)) != sizeof(signature)) { + break; + } + pc += 4; + LOAD_32(opcode, 0, &signature); + ARMDecodeARM(opcode, &info); + if (info.mnemonic != ARM_MN_LDR) { + continue; + } + if ((info.operandFormat & ARM_OPERAND_MEMORY) && info.memory.baseReg == ARM_PC && info.memory.format & ARM_MEMORY_IMMEDIATE_OFFSET) { + uint32_t immediate = info.memory.offset.immediate; + if (info.memory.format & ARM_MEMORY_OFFSET_SUBTRACT) { + immediate = -immediate; + } + immediate += pc + 8; + if (vf->seek(vf, immediate, SEEK_SET) < 0) { + break; + } + if (vf->read(vf, &signature, sizeof(signature)) != sizeof(signature)) { + break; + } + LOAD_32(immediate, 0, &signature); + if (vf->seek(vf, pc, SEEK_SET) < 0) { + break; + } + if ((immediate & ~0x7FF) == BASE_WORKING_RAM) { + return true; + } + } } // Found a libgba-linked cart...these are a bit harder to detect. return false; From 7cb30ba83e0805cba80ee6ccca8fe9c1bdc61b39 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Nov 2017 16:57:39 -0700 Subject: [PATCH 042/152] GBA Savedata: Fix crash when resizing flash --- CHANGES | 1 + src/gba/savedata.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 8c3cd8a64..7e56d7387 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Bugfixes: - GBA Cheats: Fix PARv3 slide codes (fixes mgba.io/i/919) - GBA Video: OBJWIN can change blend params after OBJ is drawn (fixes mgba.io/i/921) - GBA DMA: Fix invalid DMA reads (fixes mgba.io/i/142) + - GBA Savedata: Fix crash when resizing flash Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 3008b9428..c8c6c8be7 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -576,9 +576,15 @@ void _flashSwitchBank(struct GBASavedata* savedata, int bank) { if (bank > 0 && savedata->type == SAVEDATA_FLASH512) { mLOG(GBA_SAVE, INFO, "Updating flash chip from 512kb to 1Mb"); savedata->type = SAVEDATA_FLASH1M; - if (savedata->vf && savedata->vf->size(savedata->vf) == SIZE_CART_FLASH512) { - savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M); - memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512); + if (savedata->vf) { + savedata->vf->unmap(savedata->vf, savedata->data, SIZE_CART_FLASH512); + if (savedata->vf->size(savedata->vf) == SIZE_CART_FLASH512) { + savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M); + savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, MAP_WRITE); + memset(&savedata->data[SIZE_CART_FLASH512], 0xFF, SIZE_CART_FLASH512); + } else { + savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, MAP_WRITE); + } } } } From 7fd3eb722bc6fd993f1df4c9e81c16f3c101f245 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Nov 2017 10:04:13 -0700 Subject: [PATCH 043/152] GBA Video: Add delay when enabling BGs (fixes #744, fixes #752) --- CHANGES | 1 + cinema/gba/bg/lady-sia/baseline_0000.png | Bin 15422 -> 15276 bytes cinema/gba/bg/lady-sia/baseline_0001.png | Bin 15430 -> 15277 bytes cinema/gba/bg/lady-sia/baseline_0002.png | Bin 15415 -> 15280 bytes cinema/gba/bg/lady-sia/baseline_0003.png | Bin 15491 -> 15354 bytes src/gba/renderers/video-software.c | 49 ++++++++++++++++++++--- 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 7e56d7387..5736255f8 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Bugfixes: - GBA Video: OBJWIN can change blend params after OBJ is drawn (fixes mgba.io/i/921) - GBA DMA: Fix invalid DMA reads (fixes mgba.io/i/142) - GBA Savedata: Fix crash when resizing flash + - GBA Video: Add delay when enabling BGs (fixes mgba.io/i/744, mgba.io/i/752) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/cinema/gba/bg/lady-sia/baseline_0000.png b/cinema/gba/bg/lady-sia/baseline_0000.png index 53ffb604ec59ed1cb65be5cf316813cd9c17c9e6..56454bbfa03b57ef90f58b78e045d3118d4b00c9 100644 GIT binary patch literal 15276 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 diff --git a/cinema/gba/bg/lady-sia/baseline_0001.png b/cinema/gba/bg/lady-sia/baseline_0001.png index f7cc5129983d07ca531c9680ecf504f05b91bc8f..d1eb4323caf82288bd313cb16aeac0d6ad2a7256 100644 GIT binary patch literal 15277 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 FV1fYvJr4i? diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 3f7738ab1..14d6ba0f5 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -577,7 +577,6 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render softwareRenderer->windows[0].control.packed = 0xFF; } - GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer); if (softwareRenderer->blendDirty) { _updatePalettes(softwareRenderer); softwareRenderer->blendDirty = false; @@ -683,6 +682,19 @@ static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* rendere softwareRenderer->bg[2].sy = softwareRenderer->bg[2].refy; softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx; softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy; + + if (softwareRenderer->bg[0].enabled > 0) { + softwareRenderer->bg[0].enabled = 4; + } + if (softwareRenderer->bg[1].enabled > 0) { + softwareRenderer->bg[1].enabled = 4; + } + if (softwareRenderer->bg[2].enabled > 0) { + softwareRenderer->bg[2].enabled = 4; + } + if (softwareRenderer->bg[3].enabled > 0) { + softwareRenderer->bg[3].enabled = 4; + } } static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) { @@ -701,11 +713,23 @@ static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, } } +static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) { + if (renderer->d.disableBG[bg] || !active) { + renderer->bg[bg].enabled = 0; + } else if (!renderer->bg[bg].enabled && active) { + if (renderer->nextY == 0) { + renderer->bg[bg].enabled = 4; + } else { + renderer->bg[bg].enabled = 1; + } + } +} + static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) { - renderer->bg[0].enabled = GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt) && !renderer->d.disableBG[0]; - renderer->bg[1].enabled = GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt) && !renderer->d.disableBG[1]; - renderer->bg[2].enabled = GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt) && !renderer->d.disableBG[2]; - renderer->bg[3].enabled = GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt) && !renderer->d.disableBG[3]; + _enableBg(renderer, 0, GBARegisterDISPCNTGetBg0Enable(renderer->dispcnt)); + _enableBg(renderer, 1, GBARegisterDISPCNTGetBg1Enable(renderer->dispcnt)); + _enableBg(renderer, 2, GBARegisterDISPCNTGetBg2Enable(renderer->dispcnt)); + _enableBg(renderer, 3, GBARegisterDISPCNTGetBg3Enable(renderer->dispcnt)); } static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value) { @@ -767,7 +791,7 @@ static void GBAVideoSoftwareRendererWriteBLDCNT(struct GBAVideoSoftwareRenderer* } #define TEST_LAYER_ENABLED(X) \ - (renderer->bg[X].enabled && \ + (renderer->bg[X].enabled == 4 && \ (GBAWindowControlIsBg ## X ## Enable(renderer->currentWindow.packed) || \ (GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlIsBg ## X ## Enable (renderer->objwin.packed))) && \ renderer->bg[X].priority == priority) @@ -865,6 +889,19 @@ static void _drawScanline(struct GBAVideoSoftwareRenderer* renderer, int y) { renderer->bg[3].sx += renderer->bg[3].dmx; renderer->bg[3].sy += renderer->bg[3].dmy; } + + if (renderer->bg[0].enabled > 0 && renderer->bg[0].enabled < 4) { + ++renderer->bg[0].enabled; + } + if (renderer->bg[1].enabled > 0 && renderer->bg[1].enabled < 4) { + ++renderer->bg[1].enabled; + } + if (renderer->bg[2].enabled > 0 && renderer->bg[2].enabled < 4) { + ++renderer->bg[2].enabled; + } + if (renderer->bg[3].enabled > 0 && renderer->bg[3].enabled < 4) { + ++renderer->bg[3].enabled; + } } static void _updatePalettes(struct GBAVideoSoftwareRenderer* renderer) { From fb939ab0426966089f4120eeff258ca1b3787256 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Nov 2017 21:05:06 -0800 Subject: [PATCH 044/152] GB MBC: Remove erroneous bank 0 wrapping --- CHANGES | 1 + .../emulator-only/mbc1/rom_1Mb/manifest.yml | 1 - .../emulator-only/mbc1/rom_2Mb/manifest.yml | 1 - .../mbc1/rom_512Kb/baseline_0000.png | Bin 1188 -> 518 bytes .../emulator-only/mbc1_rom_4banks/manifest.yml | 1 - src/gb/mbc.c | 3 --- 6 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml delete mode 100644 cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml delete mode 100644 cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml diff --git a/CHANGES b/CHANGES index 5736255f8..997e8ea96 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,7 @@ Misc: - Qt: Prevent window from being created off-screen - Qt: Add option to disable FPS display - GBA: Improve multiboot image detection + - GB MBC: Remove erroneous bank 0 wrapping 0.6.1: (2017-10-01) Bugfixes: diff --git a/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml deleted file mode 100644 index a697ada66..000000000 --- a/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_1Mb/manifest.yml +++ /dev/null @@ -1 +0,0 @@ -fail: true diff --git a/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml deleted file mode 100644 index a697ada66..000000000 --- a/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_2Mb/manifest.yml +++ /dev/null @@ -1 +0,0 @@ -fail: true diff --git a/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/baseline_0000.png b/cinema/gb/mooneye-gb/emulator-only/mbc1/rom_512Kb/baseline_0000.png index 282b639d387e28e53e036f95db55be9ea28d2419..30590ffab18e5deb12be845930a52ab18642fc14 100644 GIT binary patch literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Vn(PZ!6KiaBquU+ii&U|?{x z{qsNmo1B;Hnym>_oA=IKKFMK%PG{+y=amayKVL83P|Yc$UNGlCA=7aXVT-=? z!k=yOkD14xIsL}yU*6ou>NjH3zniQ39G*S5wqpCv-MoGwmU%1ReYcCb`1o>KP3^w? ztOf5>mpy*jtQ#kFe`4bIZO>aK*KeA(W&QeB-gmZts{j0uW&QSw+MxBj7WH4RJhN-X q{>0ypYS_V6!tEYqAjbqaH2yLz>&fNk@rld;#jmHUpUXO@geCx}Th!

b*0|QI8r;B4q#hkZu?v@==5MY}; zZ{L6I<>FB;3Df5?rAfA15&fAp1o-t_+x56Y|UK0j$$ z`?e^JrZ~`ceDIGlbbeGk5|6GxhqoU(FFGH z&s$urIaBZDxkl`L%lGrBg{1-uW94Iq-75SCw_iSIDzZ3IyZ2>o`HAC?UE;3RY~L8$ z|65E}ed|>Yv%H?gYwwk@XWLv`@vZ)o^6pz-cdUP4@cwB3ia2*uFa2F-+>df?t6+&t zJbGlZ+`luz!iQIz-H&2Ed1B4Sn*Q0EcfvQQs^#(5iKJE4=RUSoUWZT%2FZxn*7VUOTmCX9J$dxmss>ipTzZdSs8J zj@SW@mP&fXHuKiMkz>)z;Jg}0o$P5=F!nN(dm;pFNlzZ6Zm^3Z#AnSToI z+ppQ{bRpV%!>en1zrK9r=^JUw$hTdwqHOj>PJ_6Ay}?&^mW+BUGwDs|Lax1zv%BbSC7RnX8aIJbzQC-m_0B2$J~g&KlCd# zui04q{HPStr1SdO_HTbS8RmOR@coKwoXT$f^P^RT_Kk(R4*6|*;1fH=Yl3Ld!-qEd zm2WrwT+|GaUlRQ#?ajL0=zxv$RgULMhMD$Nf6j@TuC=;WR_yQY=15?=nq1c#_6g`rJ7A=gPOgeq48O@6XpWt@3_*+ciyo-`4bhR&#twgaP*_ k91)7hb4al~R0RJqJXDcW*B5zU3Mzd(UHx3vIVCg!09gb!Gynhq diff --git a/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml b/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml deleted file mode 100644 index a697ada66..000000000 --- a/cinema/gb/mooneye-gb/emulator-only/mbc1_rom_4banks/manifest.yml +++ /dev/null @@ -1 +0,0 @@ -fail: true diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 83ac2f2fc..6db033aaa 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -45,9 +45,6 @@ void GBMBCSwitchBank(struct GB* gb, int bank) { mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank); bankStart &= (gb->memory.romSize - 1); bank = bankStart / GB_SIZE_CART_BANK0; - if (!bank) { - ++bank; - } } gb->memory.romBank = &gb->memory.rom[bankStart]; gb->memory.currentBank = bank; From d054be88c7c48e42b0a3e33a9c47b281d97fc478 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Nov 2017 21:46:10 -0800 Subject: [PATCH 045/152] GB Memory: HDMAs should not start when LCD is off (fixes #310) --- CHANGES | 1 + cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml | 3 ++- include/mgba/internal/gb/memory.h | 2 +- src/gb/io.c | 3 +-- src/gb/memory.c | 7 +++++-- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 997e8ea96..6bc5f90d8 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Bugfixes: - GBA DMA: Fix invalid DMA reads (fixes mgba.io/i/142) - GBA Savedata: Fix crash when resizing flash - GBA Video: Add delay when enabling BGs (fixes mgba.io/i/744, mgba.io/i/752) + - GB Memory: HDMAs should not start when LCD is off (fixes mgba.io/i/310) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml b/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml index a697ada66..6cd567986 100644 --- a/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml +++ b/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/manifest.yml @@ -1 +1,2 @@ -fail: true +config: + gb.model: CGB diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index f971c353e..ba4a155db 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -207,7 +207,7 @@ int GBCurrentSegment(struct LR35902Core* cpu, uint16_t address); uint8_t GBView8(struct LR35902Core* cpu, uint16_t address, int segment); void GBMemoryDMA(struct GB* gb, uint16_t base); -void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value); +uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value); void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old, int segment); diff --git a/src/gb/io.c b/src/gb/io.c index 8010e6a8a..533a33e79 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -450,8 +450,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { // Handled transparently by the registers break; case REG_HDMA5: - GBMemoryWriteHDMA5(gb, value); - value &= 0x7F; + value = GBMemoryWriteHDMA5(gb, value); break; case REG_BCPS: gb->video.bcpIndex = value & 0x3F; diff --git a/src/gb/memory.c b/src/gb/memory.c index 25b562146..86af47a7b 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -454,7 +454,7 @@ void GBMemoryDMA(struct GB* gb, uint16_t base) { gb->memory.dmaRemaining = 0xA0; } -void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) { +uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) { gb->memory.hdmaSource = gb->memory.io[REG_HDMA1] << 8; gb->memory.hdmaSource |= gb->memory.io[REG_HDMA2]; gb->memory.hdmaDest = gb->memory.io[REG_HDMA3] << 8; @@ -462,7 +462,7 @@ void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) { gb->memory.hdmaSource &= 0xFFF0; if (gb->memory.hdmaSource >= 0x8000 && gb->memory.hdmaSource < 0xA000) { mLOG(GB_MEM, GAME_ERROR, "Invalid HDMA source: %04X", gb->memory.hdmaSource); - return; + return value | 0x80; } gb->memory.hdmaDest &= 0x1FF0; gb->memory.hdmaDest |= 0x8000; @@ -476,7 +476,10 @@ void GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) { } gb->cpuBlocked = true; mTimingSchedule(&gb->timing, &gb->memory.hdmaEvent, 0); + } else if (gb->memory.isHdma && !GBRegisterLCDCIsEnable(gb->memory.io[REG_LCDC])) { + return 0x80 | ((value + 1) & 0x7F); } + return value & 0x7F; } void _GBMemoryDMAService(struct mTiming* timing, void* context, uint32_t cyclesLate) { From dec7b6902ee871452345ec0284391a7b19029e4a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Nov 2017 01:07:38 -0800 Subject: [PATCH 046/152] GB IO: Use correct lockout register --- include/mgba/internal/gb/io.h | 1 + src/gb/io.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/mgba/internal/gb/io.h b/include/mgba/internal/gb/io.h index 5ed4cb0fe..665586e04 100644 --- a/include/mgba/internal/gb/io.h +++ b/include/mgba/internal/gb/io.h @@ -84,6 +84,7 @@ enum GBIORegisters { REG_WX = 0x4B, // CGB + REG_UNK4C = 0x4C, REG_KEY1 = 0x4D, REG_VBK = 0x4F, REG_HDMA1 = 0x51, diff --git a/src/gb/io.c b/src/gb/io.c index 533a33e79..bb20e44e9 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -174,6 +174,7 @@ void GBIOReset(struct GB* gb) { GBIOWrite(gb, REG_WY, 0x00); GBIOWrite(gb, REG_WX, 0x00); if (gb->model >= GB_MODEL_CGB) { + GBIOWrite(gb, REG_UNK4C, 0); GBIOWrite(gb, REG_JOYP, 0xFF); GBIOWrite(gb, REG_VBK, 0); GBIOWrite(gb, REG_BCPS, 0); @@ -424,7 +425,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { free(gb->memory.romBase); gb->memory.romBase = gb->memory.rom; } - if (gb->model >= GB_MODEL_CGB && gb->memory.io[0x6C]) { + if (gb->model >= GB_MODEL_CGB && gb->memory.io[REG_UNK4C] < 0x80) { gb->model = GB_MODEL_DMG; GBVideoDisableCGB(&gb->video); } @@ -436,6 +437,8 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { default: if (gb->model >= GB_MODEL_CGB) { switch (address) { + case REG_UNK4C: + break; case REG_KEY1: value &= 0x1; value |= gb->memory.io[address] & 0x80; From 5134e39681e4cd9c0241b8d08533ef4d75054171 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Nov 2017 01:09:47 -0800 Subject: [PATCH 047/152] GB IO: Name PCM12/34 --- include/mgba/internal/gb/io.h | 4 ++-- src/gb/io.c | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/mgba/internal/gb/io.h b/include/mgba/internal/gb/io.h index 665586e04..95afebe0f 100644 --- a/include/mgba/internal/gb/io.h +++ b/include/mgba/internal/gb/io.h @@ -102,8 +102,8 @@ enum GBIORegisters { REG_UNK72 = 0x72, REG_UNK73 = 0x73, REG_UNK74 = 0x74, - REG_UNK75 = 0x75, - REG_UNK76 = 0x76, + REG_PCM12 = 0x75, + REG_PCM34 = 0x76, REG_UNK77 = 0x77, REG_MAX = 0x100 }; diff --git a/src/gb/io.c b/src/gb/io.c index bb20e44e9..254fb5232 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -101,7 +101,6 @@ static const uint8_t _registerMask[] = { [REG_BCPS] = 0x40, [REG_UNK6C] = 0xFE, [REG_SVBK] = 0xF8, - [REG_UNK75] = 0x8F, [REG_IE] = 0xE0, }; From 15127751e12f922f38123f9409b9436d8fd8f9d6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Nov 2017 11:20:39 -0800 Subject: [PATCH 048/152] GBA Cheats: Fix slide codes not initializing properly --- CHANGES | 1 + src/gba/cheats/parv3.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 6bc5f90d8..038ec435d 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Bugfixes: - GBA Savedata: Fix crash when resizing flash - GBA Video: Add delay when enabling BGs (fixes mgba.io/i/744, mgba.io/i/752) - GB Memory: HDMAs should not start when LCD is off (fixes mgba.io/i/310) + - GBA Cheats: Fix slide codes not initializing properly Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 060362e43..d0a5e3e16 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -190,18 +190,21 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { return false; case PAR3_OTHER_FILL_1: cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; cheat->address = _parAddr(op2); cheat->width = 1; cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; case PAR3_OTHER_FILL_2: cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; cheat->address = _parAddr(op2); cheat->width = 2; cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; case PAR3_OTHER_FILL_4: cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; cheat->address = _parAddr(op2); cheat->width = 4; cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); From fe354097f2596764d7072af8358586345a932291 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Nov 2017 11:23:10 -0800 Subject: [PATCH 049/152] GB Serialize: Update docs --- include/mgba/internal/gb/serialize.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index 2acf592d4..22c4ccb3f 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -169,7 +169,9 @@ mLOG_DECLARE_CATEGORY(GB_STATE); * | 0x0C7DC - 0x0C7DF: Flags * | bits 0 - 1: Current P1 bits * | bits 2 - 3: Current render mode - * | bits 4 - 31: Reserved (leave 0) + * | bit 4: Is a mode event not scheduled? + * | bit 5: Is a frame event not scheduled? + * | bits 6 - 31: Reserved (leave 0) * | 0x0C7E0 - 0x0C7EF: Current packet * | 0x0C7F0 - 0x0C7FF: Reserved * | 0x0C800 - 0x0E7FF: Character VRAM From 764acb7d6385fc9bf078f14effd9fc198943bb49 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Nov 2017 12:30:04 -0800 Subject: [PATCH 050/152] Core: Add autosave/-load cheats --- CHANGES | 1 + include/mgba/core/cheats.h | 2 + include/mgba/core/config.h | 1 + include/mgba/core/core.h | 1 + include/mgba/core/directories.h | 1 + src/core/cheats.c | 10 ++++ src/core/config.c | 3 ++ src/core/core.c | 23 +++++++++ src/core/directories.c | 37 ++++++++++++++ src/feature/gui/gui-runner.c | 1 + src/platform/python/mgba/core.py | 3 ++ src/platform/qt/CheatsModel.cpp | 7 ++- src/platform/qt/CheatsView.cpp | 4 +- src/platform/qt/CoreManager.cpp | 1 + src/platform/qt/SettingsView.cpp | 22 +++++++++ src/platform/qt/SettingsView.ui | 85 ++++++++++++++++++++++++++++++-- src/platform/sdl/main.c | 1 + 17 files changed, 195 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 038ec435d..f251ce6f3 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Features: - Customizable autofire speed - Ability to set default Game Boy model - Map viewer + - Automatic cheat loading and saving Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba/core/cheats.h b/include/mgba/core/cheats.h index 25ad3bbe1..362b995d9 100644 --- a/include/mgba/core/cheats.h +++ b/include/mgba/core/cheats.h @@ -79,6 +79,7 @@ struct mCheatDevice { struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name); struct mCheatSets cheats; + bool autosave; }; struct VFile; @@ -98,6 +99,7 @@ void mCheatRemoveSet(struct mCheatDevice*, struct mCheatSet*); bool mCheatParseFile(struct mCheatDevice*, struct VFile*); bool mCheatSaveFile(struct mCheatDevice*, struct VFile*); +void mCheatAutosave(struct mCheatDevice*); void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*); diff --git a/include/mgba/core/config.h b/include/mgba/core/config.h index 0c6426e7a..a55d19c0d 100644 --- a/include/mgba/core/config.h +++ b/include/mgba/core/config.h @@ -51,6 +51,7 @@ struct mCoreOptions { char* savestatePath; char* screenshotPath; char* patchPath; + char* cheatsPath; int volume; bool mute; diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index d54a5c985..12304f561 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -168,6 +168,7 @@ bool mCorePreloadFile(struct mCore* core, const char* path); bool mCoreAutoloadSave(struct mCore* core); bool mCoreAutoloadPatch(struct mCore* core); +bool mCoreAutoloadCheats(struct mCore* core); bool mCoreSaveState(struct mCore* core, int slot, int flags); bool mCoreLoadState(struct mCore* core, int slot, int flags); diff --git a/include/mgba/core/directories.h b/include/mgba/core/directories.h index 8715c1025..d421e7f51 100644 --- a/include/mgba/core/directories.h +++ b/include/mgba/core/directories.h @@ -21,6 +21,7 @@ struct mDirectorySet { struct VDir* patch; struct VDir* state; struct VDir* screenshot; + struct VDir* cheats; }; void mDirectorySetInit(struct mDirectorySet* dirs); diff --git a/src/core/cheats.c b/src/core/cheats.c index 21a7b8a7b..642230b0b 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -51,6 +51,7 @@ void mCheatDeviceCreate(struct mCheatDevice* device) { device->d.id = M_CHEAT_DEVICE_ID; device->d.init = mCheatDeviceInit; device->d.deinit = mCheatDeviceDeinit; + device->autosave = false; mCheatSetsInit(&device->cheats, 4); } @@ -252,6 +253,15 @@ bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) { return true; } +void mCheatAutosave(struct mCheatDevice* device) { + if (!device->autosave) { + return; + } + struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC); + mCheatSaveFile(device, vf); + vf->close(vf); +} + void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { if (!cheats->enabled) { return; diff --git a/src/core/config.c b/src/core/config.c index 950607469..44861e3c2 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -379,6 +379,7 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) _lookupCharValue(config, "savestatePath", &opts->savestatePath); _lookupCharValue(config, "screenshotPath", &opts->screenshotPath); _lookupCharValue(config, "patchPath", &opts->patchPath); + _lookupCharValue(config, "cheatsPath", &opts->cheatsPath); } void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptions* opts) { @@ -443,10 +444,12 @@ void mCoreConfigFreeOpts(struct mCoreOptions* opts) { free(opts->savestatePath); free(opts->screenshotPath); free(opts->patchPath); + free(opts->cheatsPath); opts->bios = 0; opts->shader = 0; opts->savegamePath = 0; opts->savestatePath = 0; opts->screenshotPath = 0; opts->patchPath = 0; + opts->cheatsPath = 0; } diff --git a/src/core/core.c b/src/core/core.c index d27cc48ef..319dae407 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include #include #include @@ -165,6 +166,24 @@ bool mCoreAutoloadPatch(struct mCore* core) { core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY)); } +bool mCoreAutoloadCheats(struct mCore* core) { + bool success = true; + int cheatAuto; + if (!mCoreConfigGetIntValue(&core->config, "cheatAutoload", &cheatAuto) || cheatAuto) { + struct VFile* vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.cheats, ".cheats", O_RDONLY); + if (vf) { + struct mCheatDevice* device = core->cheatDevice(core); + success = mCheatParseFile(device, vf); + vf->close(vf); + } + } + if (!mCoreConfigGetIntValue(&core->config, "cheatAutosave", &cheatAuto) || cheatAuto) { + struct mCheatDevice* device = core->cheatDevice(core); + device->autosave = true; + } + return success; +} + bool mCoreSaveState(struct mCore* core, int slot, int flags) { struct VFile* vf = mCoreGetState(core, slot, true); if (!vf) { @@ -271,6 +290,10 @@ void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config if (core->opts.audioBuffers) { core->setAudioBufferSize(core, core->opts.audioBuffers); } + + mCoreConfigCopyValue(&core->config, config, "cheatAutosave"); + mCoreConfigCopyValue(&core->config, config, "cheatAutoload"); + core->loadConfig(core, config); } diff --git a/src/core/directories.c b/src/core/directories.c index 4d52bec43..295614a12 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -16,6 +16,7 @@ void mDirectorySetInit(struct mDirectorySet* dirs) { dirs->patch = 0; dirs->state = 0; dirs->screenshot = 0; + dirs->cheats = 0; } void mDirectorySetDeinit(struct mDirectorySet* dirs) { @@ -34,6 +35,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) { if (dirs->archive == dirs->screenshot) { dirs->screenshot = NULL; } + if (dirs->archive == dirs->cheats) { + dirs->cheats = NULL; + } dirs->archive->close(dirs->archive); dirs->archive = NULL; } @@ -48,6 +52,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) { if (dirs->save == dirs->screenshot) { dirs->screenshot = NULL; } + if (dirs->save == dirs->cheats) { + dirs->cheats = NULL; + } dirs->save->close(dirs->save); dirs->save = NULL; } @@ -59,6 +66,9 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) { if (dirs->patch == dirs->screenshot) { dirs->screenshot = NULL; } + if (dirs->patch == dirs->cheats) { + dirs->cheats = NULL; + } dirs->patch->close(dirs->patch); dirs->patch = NULL; } @@ -67,14 +77,25 @@ void mDirectorySetDeinit(struct mDirectorySet* dirs) { if (dirs->state == dirs->screenshot) { dirs->state = NULL; } + if (dirs->state == dirs->cheats) { + dirs->cheats = NULL; + } dirs->state->close(dirs->state); dirs->state = NULL; } if (dirs->screenshot) { + if (dirs->screenshot == dirs->cheats) { + dirs->cheats = NULL; + } dirs->screenshot->close(dirs->screenshot); dirs->screenshot = NULL; } + + if (dirs->cheats) { + dirs->cheats->close(dirs->cheats); + dirs->cheats = NULL; + } } void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) { @@ -91,6 +112,9 @@ void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) { if (!dirs->screenshot) { dirs->screenshot = dirs->base; } + if (!dirs->cheats) { + dirs->cheats = dirs->base; + } } void mDirectorySetDetachBase(struct mDirectorySet* dirs) { @@ -106,6 +130,9 @@ void mDirectorySetDetachBase(struct mDirectorySet* dirs) { if (dirs->screenshot == dirs->base) { dirs->screenshot = NULL; } + if (dirs->cheats == dirs->base) { + dirs->cheats = NULL; + } if (dirs->base) { dirs->base->close(dirs->base); @@ -183,5 +210,15 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio dirs->patch = dir; } } + + if (opts->cheatsPath) { + struct VDir* dir = VDirOpen(opts->cheatsPath); + if (dir) { + if (dirs->cheats && dirs->cheats != dirs->base) { + dirs->cheats->close(dirs->cheats); + } + dirs->cheats = dir; + } + } } #endif diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 0f997f395..1f9227a26 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -306,6 +306,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { mLOG(GUI_RUNNER, DEBUG, "Loading save..."); mCoreAutoloadSave(runner->core); + mCoreAutoloadCheats(runner->core); if (runner->setup) { mLOG(GUI_RUNNER, DEBUG, "Setting up runner..."); runner->setup(runner); diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index fdd04c8d7..718b87b2d 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -182,6 +182,9 @@ class Core(object): def autoloadPatch(self): return bool(lib.mCoreAutoloadPatch(self._core)) + def autoloadCheats(self): + return bool(lib.mCoreAutoloadCheats(self._core)) + def platform(self): return self._core.platform(self._core) diff --git a/src/platform/qt/CheatsModel.cpp b/src/platform/qt/CheatsModel.cpp index 79041ecea..a2c535f0c 100644 --- a/src/platform/qt/CheatsModel.cpp +++ b/src/platform/qt/CheatsModel.cpp @@ -70,10 +70,12 @@ bool CheatsModel::setData(const QModelIndex& index, const QVariant& value, int r case Qt::DisplayRole: case Qt::EditRole: mCheatSetRename(cheats, value.toString().toUtf8().constData()); + mCheatAutosave(m_device); emit dataChanged(index, index); return true; case Qt::CheckStateRole: cheats->enabled = value == Qt::Checked; + mCheatAutosave(m_device); emit dataChanged(index, index); return true; default: @@ -154,7 +156,8 @@ void CheatsModel::removeAt(const QModelIndex& index) { beginRemoveRows(QModelIndex(), row, row); mCheatRemoveSet(m_device, set); mCheatSetDeinit(set); - endInsertRows(); + endRemoveRows(); + mCheatAutosave(m_device); } QString CheatsModel::toString(const QModelIndexList& indices) const { @@ -201,6 +204,7 @@ void CheatsModel::beginAppendRow(const QModelIndex& index) { void CheatsModel::endAppendRow() { endInsertRows(); + mCheatAutosave(m_device); } void CheatsModel::loadFile(const QString& path) { @@ -232,6 +236,7 @@ void CheatsModel::addSet(mCheatSet* set) { } mCheatAddSet(m_device, set); endInsertRows(); + mCheatAutosave(m_device); } void CheatsModel::invalidated() { diff --git a/src/platform/qt/CheatsView.cpp b/src/platform/qt/CheatsView.cpp index 80e398b4b..315edcae1 100644 --- a/src/platform/qt/CheatsView.cpp +++ b/src/platform/qt/CheatsView.cpp @@ -109,14 +109,14 @@ bool CheatsView::eventFilter(QObject* object, QEvent* event) { } void CheatsView::load() { - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file")); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select cheats file"), tr(("Cheats file (*.cheats *.cht *.clt)"))); if (!filename.isEmpty()) { m_model.loadFile(filename); } } void CheatsView::save() { - QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file")); + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select cheats file"), tr(("Cheats file (*.cheats *.cht *.clt)"))); if (!filename.isEmpty()) { m_model.saveFile(filename); } diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp index 50991026b..adb002c18 100644 --- a/src/platform/qt/CoreManager.cpp +++ b/src/platform/qt/CoreManager.cpp @@ -109,6 +109,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr bytes = info.dir().canonicalPath().toUtf8(); mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); mCoreAutoloadSave(core); + mCoreAutoloadCheats(core); CoreController* cc = new CoreController(core); if (m_multiplayer) { diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 14a58e175..12ca366f8 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -106,6 +106,22 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC m_ui.patchPath->setText(path); } }); + + if (m_ui.cheatsPath->text().isEmpty()) { + m_ui.cheatsSameDir->setChecked(true); + } + connect(m_ui.cheatsSameDir, &QAbstractButton::toggled, [this] (bool e) { + if (e) { + m_ui.cheatsPath->clear(); + } + }); + connect(m_ui.cheatsBrowse, &QAbstractButton::pressed, [this] () { + QString path = GBAApp::app()->getOpenDirectoryName(this, "Select directory"); + if (!path.isNull()) { + m_ui.cheatsSameDir->setChecked(false); + m_ui.cheatsPath->setText(path); + } + }); connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig() @@ -334,10 +350,13 @@ void SettingsView::updateConfig() { saveSetting("savestatePath", m_ui.savestatePath); saveSetting("screenshotPath", m_ui.screenshotPath); saveSetting("patchPath", m_ui.patchPath); + saveSetting("cheatsPath", m_ui.cheatsPath); saveSetting("libraryStyle", m_ui.libraryStyle->currentIndex()); saveSetting("showLibrary", m_ui.showLibrary); saveSetting("preload", m_ui.preload); saveSetting("showFps", m_ui.showFps); + saveSetting("cheatAutoload", m_ui.cheatAutoload); + saveSetting("cheatAutosave", m_ui.cheatAutosave); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); @@ -453,9 +472,12 @@ void SettingsView::reloadConfig() { loadSetting("savestatePath", m_ui.savestatePath); loadSetting("screenshotPath", m_ui.screenshotPath); loadSetting("patchPath", m_ui.patchPath); + loadSetting("cheatsPath", m_ui.cheatsPath); loadSetting("showLibrary", m_ui.showLibrary); loadSetting("preload", m_ui.preload); loadSetting("showFps", m_ui.showFps, true); + loadSetting("cheatAutoload", m_ui.cheatAutoload, true); + loadSetting("cheatAutosave", m_ui.cheatAutosave, true); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 1fabdd8cd..5d4fef4f9 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -419,6 +419,13 @@ + + + + Qt::Horizontal + + + @@ -488,17 +495,37 @@ - - + + + + Show FPS in title bar + + + true + + + + + Qt::Horizontal - - + + - Show FPS in title bar + Automatically save cheats + + + true + + + + + + + Automatically load cheats true @@ -1068,6 +1095,54 @@ + + + + Qt::Horizontal + + + + + + + Cheats + + + + + + + + + + 0 + 0 + + + + + 170 + 0 + + + + + + + + Browse + + + + + + + + + Same directory as the ROM + + + diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 3ba08f9dc..4dc27e54e 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -181,6 +181,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { return 1; } mCoreAutoloadSave(renderer->core); + mCoreAutoloadCheats(renderer->core); #ifdef ENABLE_SCRIPTING struct mScriptBridge* bridge = mScriptBridgeCreate(); #ifdef ENABLE_PYTHON From dcf42fb081a877a3af32f60020dd81a6f32e74e6 Mon Sep 17 00:00:00 2001 From: rootfather Date: Sun, 12 Nov 2017 09:30:09 +0100 Subject: [PATCH 051/152] Qt: Update German GUI translation --- src/platform/qt/ts/mgba-de.ts | 193 ++++++++++++++++++---------------- 1 file changed, 105 insertions(+), 88 deletions(-) diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index 4382f1b5c..0d0716083 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -1145,7 +1145,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.(unbenannt) - + Failed to open cheats file: %1 Fehler beim Öffnen der Cheat-Datei: %1 @@ -3007,59 +3007,59 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::SettingsView - - + + Qt Multimedia Qt Multimedia - + SDL SDL - + Software (Qt) Software (Qt) - + OpenGL OpenGL - + OpenGL (force version 1.x) OpenGL (erzwinge Version 1.x) - + None (Still Image) Keiner (Standbild) - + Keyboard Tastatur - + Controllers Gamepads - + Shortcuts Tastenkürzel - - + + Shaders Shader - + Select BIOS BIOS auswählen @@ -4158,7 +4158,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + frames Bilder @@ -4209,12 +4209,12 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Englisch - + List view Listenansicht - + Tree view Baumansicht @@ -4224,147 +4224,163 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Bildwiederholrate in der Titelleiste anzeigen - + + Automatically save cheats + Cheats automatisch speichern + + + + Automatically load cheats + Cheats automatisch laden + + + + Cheats + Cheats + + + Game Boy model Game Boy-Modell - - - + + + Autodetect Automatisch erkennen - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Super Game Boy model Super Game Boy-Modell - + Game Boy Color model Game Boy Color-Modell - + Default BG colors: Standard-Hintergrundfarben: - + Default sprite colors 1: Standard-Sprite-Farben 1: - + Default sprite colors 2: Standard-Sprite-Farben 2: - + Super Game Boy borders Super Game Boy-Rahmen - + Camera driver: Kamera-Treiber: - + Library: Bibliothek: - + Show when no game open Anzeigen, wenn kein Spiel geöffnet ist - + Clear cache Cache leeren - + Fast forward speed: Vorlauf-Geschwindigkeit: - + Rewind affects save data Rücklauf beeinflusst Speicherdaten - + Preload entire ROM into memory ROM-Datei vollständig in Arbeitsspeicher vorladen - - - - - - - - + + + + + + + + + Browse Durchsuchen - + Use BIOS file if found BIOS-Datei verwenden, wenn vorhanden - + Skip BIOS intro BIOS-Intro überspringen - + × × - + Unbounded unbegrenzt - + Suspend screensaver Bildschirmschoner deaktivieren @@ -4374,50 +4390,50 @@ wenn vorhanden BIOS - + Pause when inactive Pause, wenn inaktiv - + Run all Alle ausführen - + Remove known Bekannte entfernen - + Detect and remove Erkennen und entfernen - + Allow opposing input directions Gegensätzliche Eingaberichtungen erlauben - - + + Screenshot Screenshot - - + + Save data Speicherdaten - - + + Cheat codes Cheat-Codes - + Enable rewind Rücklauf aktivieren @@ -4427,75 +4443,76 @@ wenn vorhanden Bilineare Filterung - + Rewind history: Rücklauf-Verlauf: - + Idle loops: Leerlaufprozesse: - + Savestate extra data: Zusätzliche Savestate-Daten: - + Load extra data: Lade zusätzliche Daten: - + Autofire interval: Autofeuer-Intervall: - + GB BIOS file: Datei mit GB-BIOS: - + GBA BIOS file: Datei mit GBA-BIOS: - + GBC BIOS file: Datei mit GBC-BIOS: - + SGB BIOS file: Datei mit SGB-BIOS: - + Save games Spielstände - - - - + + + + + Same directory as the ROM Verzeichnis der ROM-Datei - + Save states Savestates - + Screenshots Screenshots - + Patches Patches From 5d9e4d217a273bec4dfdddafdea85e25eda7a45f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Nov 2017 17:25:26 -0800 Subject: [PATCH 052/152] GB: Fix execution state and HALT getting out of sync --- src/gb/gb.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index c75992461..91e2503dc 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -622,6 +622,8 @@ void GBProcessEvents(struct LR35902Core* cpu) { } if (cpu->halted) { cpu->cycles = cpu->nextEvent; + cpu->executionState += cpu->nextEvent; + cpu->executionState &= 3; if (!gb->memory.ie || !gb->memory.ime) { break; } @@ -677,9 +679,14 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle } void GBHalt(struct LR35902Core* cpu) { - if (!cpu->irqPending) { + struct GB* gb = (struct GB*) cpu->master; + if (!(gb->memory.ie & gb->memory.io[REG_IF])) { + cpu->executionState += cpu->nextEvent - cpu->cycles; + cpu->executionState &= 3; cpu->cycles = cpu->nextEvent; cpu->halted = true; + } else if (gb->model < GB_MODEL_CGB) { + mLOG(GB, STUB, "Unimplemented HALT bug"); } } From 60a2f49cda145235f298cc2e87fd924a323dc567 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 13 Nov 2017 23:27:05 -0800 Subject: [PATCH 053/152] GB: Improve stepping timing accuracy --- src/lr35902/lr35902.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lr35902/lr35902.c b/src/lr35902/lr35902.c index 6dd9db9ef..23878c310 100644 --- a/src/lr35902/lr35902.c +++ b/src/lr35902/lr35902.c @@ -137,22 +137,22 @@ static void _LR35902Step(struct LR35902Core* cpu) { } void LR35902Tick(struct LR35902Core* cpu) { + if (cpu->cycles >= cpu->nextEvent) { + cpu->irqh.processEvents(cpu); + } _LR35902Step(cpu); if (cpu->cycles + 2 >= cpu->nextEvent) { int32_t diff = cpu->nextEvent - cpu->cycles; cpu->cycles = cpu->nextEvent; cpu->executionState += diff; cpu->irqh.processEvents(cpu); - cpu->cycles += 2 - diff; + cpu->cycles += LR35902_CORE_EXECUTE - cpu->executionState; } else { cpu->cycles += 2; } cpu->executionState = LR35902_CORE_FETCH; cpu->instruction(cpu); ++cpu->cycles; - if (cpu->cycles >= cpu->nextEvent) { - cpu->irqh.processEvents(cpu); - } } void LR35902Run(struct LR35902Core* cpu) { @@ -168,7 +168,7 @@ void LR35902Run(struct LR35902Core* cpu) { cpu->cycles = cpu->nextEvent; cpu->executionState += diff; cpu->irqh.processEvents(cpu); - cpu->cycles += 2 - diff; + cpu->cycles += LR35902_CORE_EXECUTE - cpu->executionState; running = false; } else { cpu->cycles += 2; From 388ed07074163f135989838633eea8f1c8416023 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 13 Nov 2017 23:31:36 -0800 Subject: [PATCH 054/152] Cinema: Add baseline for gb.mooneye-gb.acceptance.hdma_lcdc --- .../acceptance/hdma_lcdc/baseline_0000.png | Bin 0 -> 1165 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 cinema/gb/mooneye-gb/acceptance/hdma_lcdc/baseline_0000.png diff --git a/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/baseline_0000.png b/cinema/gb/mooneye-gb/acceptance/hdma_lcdc/baseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..c1dac62b31a1bc8cbfb2644cc07a1f88e28938e1 GIT binary patch literal 1165 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|QHlr;B4q#hkZu@0P7r5OCY5 z{r>+cOYxT*byC$%_|K_%Q+xS}kjzb1$7L_S)z}HY_*7iZU?9%Y-JDP;pu^X6)Nz9a zhuHIpLASQ${CX)se2t{F#Mv>-8&@pYev|tl8tU@z28r zU&U4nz20*!dd>u~bn7n?tJ&kT_9aO^?@f>X`tiz@{YLe+J8n4mO^%fn`hI`OtWSRY z+g##S)yw`;-Oe!ng^TVB0NO73~cE!OYBo0s3- z$!W3VuD!r>8>z#e^w<2$%HB3-_U&``H)gE9c~Yn2<&N?Wy`cQ^GUlJ6aaOS(7T#Fo z`XR^gXI{dDyu?&zm+sGJrc@U^y=rySX};(A6+NFtJNKK)UCv9gkC3}|@R7;A=I_`4 zGB+3>`Fj6`+tZIa+xg_Cw!HBP`c?egP1Rc;$lbcH##&lFW9iN_aYglek67J!FUh!m zv&WpTzii&kNqKsDiD&oAzqch%UJ8CJe{`;MNnG^D%H_pf-czHemhfHt*`IXFcTMxt zL|GlbJEa9H{w8S8+vsc9yx{eNfZqqhY~l@f&ogof_lx{2wlQtV`F;Dv?))=WK6m}~ z+=jBtOYSbQm~K^I|KrRq+Z~f;#Yr!`y69+e=Zn`5R!&~?&*HKFrhRX|3$8n46*0%U zc+(oik}JPTfBfAPk@&%VYWXwQVi)%l}eUJje{xp`3PyXqmTXg z=iO~w6MW?Uhnc7RPwjfYQ~14aaOIta`%;XLF6k~Y^fSD7r&+q~1uz|Nn>BAr{*vdR zdu5CGl_VF1&neeoeT|$c(A^BqDM*5gYT`=bt{#ZF-uTlh`0*U~Dd)TO!c4b*vMBUh zTG;XJ^PKi4`g|di-thb8uDfp)XwvlW{*v=ccGP*#v1OT?@-^K^TzF@@u>E7JQ_o$O zTdPGDKAPixVMBt5V|#`KD>P#wS%=82G#5N)->V+{;K+#+rJ!QQ)78&qol`;+0QdMR ACjbBd literal 0 HcmV?d00001 From e104b465640fe584cc6fd402d3c78e57e9819905 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 16 Nov 2017 09:22:15 -0800 Subject: [PATCH 055/152] GBA BIOS: Add warning for LZ77 error causing BIOS to hang (closes #879) --- src/gba/bios.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gba/bios.c b/src/gba/bios.c index 65014898e..fba9479ed 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -529,7 +529,11 @@ static void _unLz77(struct GBA* gba, int width) { source += 2; disp = dest - (block & 0x0FFF) - 1; bytes = (block >> 12) + 3; - while (bytes-- && remaining) { + while (bytes--) { + if (!remaining) { + mLOG(GBA_BIOS, GAME_ERROR, "Improperly compressed LZ77 data. Real BIOS would hang."); + break; + } --remaining; if (width == 2) { byte = (int16_t) cpu->memory.load16(cpu, disp & ~1, 0); From c9145e18d7504e07c3d67aaadae7de5e22ea3179 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Nov 2017 07:29:37 -0800 Subject: [PATCH 056/152] Partially revert "GB: Fix execution state and HALT getting out of sync" This reverts commit 5d9e4d217a273bec4dfdddafdea85e25eda7a45f. --- src/gb/gb.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index 91e2503dc..6ecaaf74f 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -622,8 +622,6 @@ void GBProcessEvents(struct LR35902Core* cpu) { } if (cpu->halted) { cpu->cycles = cpu->nextEvent; - cpu->executionState += cpu->nextEvent; - cpu->executionState &= 3; if (!gb->memory.ie || !gb->memory.ime) { break; } @@ -681,8 +679,6 @@ static void _enableInterrupts(struct mTiming* timing, void* user, uint32_t cycle void GBHalt(struct LR35902Core* cpu) { struct GB* gb = (struct GB*) cpu->master; if (!(gb->memory.ie & gb->memory.io[REG_IF])) { - cpu->executionState += cpu->nextEvent - cpu->cycles; - cpu->executionState &= 3; cpu->cycles = cpu->nextEvent; cpu->halted = true; } else if (gb->model < GB_MODEL_CGB) { From 4b40c5cd1a319866407984e560f312f7fd31702a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Nov 2017 07:43:43 -0800 Subject: [PATCH 057/152] GBA BIOS: Crash on BIOS misuse if hardCrash is enabled --- src/core/thread.c | 4 ++++ src/gba/bios.c | 6 +++++- src/platform/qt/LogController.cpp | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/core/thread.c b/src/core/thread.c index 3a0c802ea..15dafe185 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -623,6 +623,10 @@ static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level printf("%s: ", mLogCategoryName(category)); vprintf(format, args); printf("\n"); + struct mCoreThread* thread = mCoreThreadGet(); + if (thread && level == mLOG_FATAL) { + mCoreThreadMarkCrashed(thread); + } } struct mLogger* mCoreThreadLogger(void) { diff --git a/src/gba/bios.c b/src/gba/bios.c index fba9479ed..6d4835bed 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -531,7 +531,11 @@ static void _unLz77(struct GBA* gba, int width) { bytes = (block >> 12) + 3; while (bytes--) { if (!remaining) { - mLOG(GBA_BIOS, GAME_ERROR, "Improperly compressed LZ77 data. Real BIOS would hang."); + if (gba->hardCrash) { + mLOG(GBA_BIOS, FATAL, "Improperly compressed LZ77 data. Real BIOS would hang."); + } else { + mLOG(GBA_BIOS, GAME_ERROR, "Improperly compressed LZ77 data. Real BIOS would hang."); + } break; } --remaining; diff --git a/src/platform/qt/LogController.cpp b/src/platform/qt/LogController.cpp index d8870d335..32cf7a917 100644 --- a/src/platform/qt/LogController.cpp +++ b/src/platform/qt/LogController.cpp @@ -13,7 +13,7 @@ LogController::LogController(int levels, QObject* parent) : QObject(parent) { mLogFilterInit(&m_filter); - mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB); + mLogFilterSet(&m_filter, "gba.bios", mLOG_STUB | mLOG_FATAL); mLogFilterSet(&m_filter, "core.status", mLOG_ALL & ~mLOG_DEBUG); m_filter.defaultLevels = levels; From 32f7f35ee9088694c58cb22b0e3803684a42d8a1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Nov 2017 10:50:27 -0800 Subject: [PATCH 058/152] GBA Cheats: More minor fixes --- src/gba/cheats/gameshark.c | 2 ++ src/gba/cheats/parv3.c | 8 ++++---- src/gba/test/cheats.c | 8 ++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/gba/cheats/gameshark.c b/src/gba/cheats/gameshark.c index e95b23b54..3b2200ee4 100644 --- a/src/gba/cheats/gameshark.c +++ b/src/gba/cheats/gameshark.c @@ -174,6 +174,7 @@ bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t cheat->address = op2 & 0x0FFFFFFF; cheat->operand = op1 & 0xFFFF; cheat->repeat = (op1 >> 16) & 0xFF; + cheat->negativeRepeat = 0; return true; case GSA_HOOK: if (cheats->hook) { @@ -190,6 +191,7 @@ bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t } cheat->operand = op2; cheat->repeat = 1; + cheat->negativeRepeat = 0; return true; } diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index d0a5e3e16..8a0b2b547 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -223,7 +223,7 @@ bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uin struct mCheat* incompleteCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat); incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - incompleteCheat->width) * 8)); incompleteCheat->operandOffset = op2 >> 24; - incompleteCheat->repeat = (op2 >> 16) & 0xFF; + incompleteCheat->repeat = ((op2 >> 16) & 0xFF) + 1; incompleteCheat->addressOffset = (op2 & 0xFFFF) * incompleteCheat->width; cheats->incompleteCheat = COMPLETE; return true; @@ -342,7 +342,7 @@ int GBACheatProActionReplayProbability(uint32_t op1, uint32_t op2) { return 0x100; } if (!op1) { - probability += 0x20; + probability += 0x40; uint32_t address = _parAddr(op2); switch (op2 & 0xFE000000) { case PAR3_OTHER_FILL_1: @@ -363,8 +363,8 @@ int GBACheatProActionReplayProbability(uint32_t op1, uint32_t op2) { case PAR3_OTHER_BUTTON_4: case PAR3_OTHER_ENDIF: case PAR3_OTHER_ELSE: - if (op2 & 0x01FFFFFF) { - probability -= 0x20; + if (op2 & 0x01000000) { + probability -= 0x40; } break; default: diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index 3309839c2..d81f9f67b 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -421,6 +421,7 @@ M_TEST_DEFINE(doPARv3IfX) { mCheatRefresh(device, set); assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXxX) { @@ -483,6 +484,7 @@ M_TEST_DEFINE(doPARv3IfXxX) { assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21); assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x32); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElse) { @@ -517,6 +519,7 @@ M_TEST_DEFINE(doPARv3IfXElse) { assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x22); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElsexX) { @@ -587,6 +590,7 @@ M_TEST_DEFINE(doPARv3IfXElsexX) { assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x32); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x42); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x51); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElsexXElse) { @@ -664,6 +668,7 @@ M_TEST_DEFINE(doPARv3IfXElsexXElse) { assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x42); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x51); assert_int_equal(core->rawRead8(core, 0x03000006, -1), 0x62); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXContain1) { @@ -725,6 +730,7 @@ M_TEST_DEFINE(doPARv3IfXContain1) { assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21); assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x31); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXContain1Else) { @@ -794,6 +800,7 @@ M_TEST_DEFINE(doPARv3IfXContain1Else) { assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x31); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x52); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXElseContain1) { @@ -863,6 +870,7 @@ M_TEST_DEFINE(doPARv3IfXElseContain1) { assert_int_equal(core->rawRead8(core, 0x03000003, -1), 0x32); assert_int_equal(core->rawRead8(core, 0x03000004, -1), 0x41); assert_int_equal(core->rawRead8(core, 0x03000005, -1), 0x52); + set->deinit(set); } M_TEST_DEFINE(doPARv3IfXContain1ElseContain1) { From f5afadb72d369601dbcf25af70245d83ab2f8340 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Nov 2017 10:51:14 -0800 Subject: [PATCH 059/152] Core: Add support for cheat device buttons --- CHANGES | 1 + include/mgba/core/cheats.h | 5 ++++- src/core/cheats.c | 11 +++++++++++ src/gba/cheats/gameshark.c | 29 ++++++++++++++++++++++++++--- src/gba/cheats/parv3.c | 34 +++++++++++++++++++++++++++++++--- src/gba/test/cheats.c | 31 ++++++++++++++++++++++++++++++- src/platform/qt/Window.cpp | 11 +++++++++++ 7 files changed, 114 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index f251ce6f3..3c3a730da 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Features: - Ability to set default Game Boy model - Map viewer - Automatic cheat loading and saving + - GameShark and Action Replay button support Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba/core/cheats.h b/include/mgba/core/cheats.h index 362b995d9..98eb4dd91 100644 --- a/include/mgba/core/cheats.h +++ b/include/mgba/core/cheats.h @@ -30,7 +30,8 @@ enum mCheatType { CHEAT_IF_UGT, CHEAT_IF_AND, CHEAT_IF_LAND, - CHEAT_IF_NAND + CHEAT_IF_NAND, + CHEAT_IF_BUTTON, }; struct mCheat { @@ -80,6 +81,7 @@ struct mCheatDevice { struct mCheatSets cheats; bool autosave; + bool buttonDown; }; struct VFile; @@ -102,6 +104,7 @@ bool mCheatSaveFile(struct mCheatDevice*, struct VFile*); void mCheatAutosave(struct mCheatDevice*); void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*); +void mCheatPressButton(struct mCheatDevice*, bool down); CXX_GUARD_END diff --git a/src/core/cheats.c b/src/core/cheats.c index 642230b0b..a9aa253f4 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -52,6 +52,7 @@ void mCheatDeviceCreate(struct mCheatDevice* device) { device->d.init = mCheatDeviceInit; device->d.deinit = mCheatDeviceDeinit; device->autosave = false; + device->buttonDown = false; mCheatSetsInit(&device->cheats, 4); } @@ -360,6 +361,12 @@ void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { negativeConditionRemaining = cheat->negativeRepeat; operationsRemaining = 1; break; + case CHEAT_IF_BUTTON: + condition = device->buttonDown; + conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; + operationsRemaining = 1; + break; } if (performAssignment) { @@ -384,6 +391,10 @@ void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { } } +void mCheatPressButton(struct mCheatDevice* device, bool down) { + device->buttonDown = down; +} + void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) { UNUSED(cpu); struct mCheatDevice* device = (struct mCheatDevice*) component; diff --git a/src/gba/cheats/gameshark.c b/src/gba/cheats/gameshark.c index 3b2200ee4..d61ffbe6a 100644 --- a/src/gba/cheats/gameshark.c +++ b/src/gba/cheats/gameshark.c @@ -154,9 +154,32 @@ bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t cheats->romPatches[0].exists = true; return true; case GSA_BUTTON: - // TODO: Implement button - mLOG(CHEATS, STUB, "GameShark button unimplemented"); - return false; + switch (op1 & 0x00F00000) { + case 0x00100000: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = op1 & 0x0F0FFFFF; + break; + case 0x00200000: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = op1 & 0x0F0FFFFF; + break; + default: + mLOG(CHEATS, STUB, "GameShark button type unimplemented"); + return false; + } + break; case GSA_IF_EQ: if (op1 == 0xDEADFACE) { GBACheatReseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2); diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 8a0b2b547..ee11b28fc 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -144,13 +144,41 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { switch (op2 & 0xFF000000) { case PAR3_OTHER_SLOWDOWN: // TODO: Slowdown + mLOG(CHEATS, STUB, "Unimplemented PARv3 slowdown"); return false; case PAR3_OTHER_BUTTON_1: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; case PAR3_OTHER_BUTTON_2: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; case PAR3_OTHER_BUTTON_4: - // TODO: Button - mLOG(CHEATS, STUB, "GameShark button unimplemented"); - return false; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; // TODO: Fix overriding existing patches case PAR3_OTHER_PATCH_1: cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index d81f9f67b..08c8b1b82 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -1016,6 +1016,34 @@ M_TEST_DEFINE(doPARv3IfXContain1ElseContain1) { assert_int_equal(core->rawRead8(core, 0x03000006, -1), 0x62); assert_int_equal(core->rawRead8(core, 0x03000007, -1), 0x71); assert_int_equal(core->rawRead8(core, 0x03000008, -1), 0x82); + set->deinit(set); +} + +M_TEST_DEFINE(doPARv3IfButton) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 10300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 00000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatPressButton(device, true); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatPressButton(device, false); + core->rawWrite8(core, 0x03000000, -1, 0); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + set->deinit(set); } M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBACheats, @@ -1038,4 +1066,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBACheats, cmocka_unit_test(doPARv3IfXContain1), cmocka_unit_test(doPARv3IfXContain1Else), cmocka_unit_test(doPARv3IfXElseContain1), - cmocka_unit_test(doPARv3IfXContain1ElseContain1)) + cmocka_unit_test(doPARv3IfXContain1ElseContain1), + cmocka_unit_test(doPARv3IfButton)) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 0ffd22ab9..b79f0d2ed 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -52,6 +52,7 @@ #include "VideoView.h" #include +#include #ifdef M_CORE_GB #include #include @@ -1602,6 +1603,16 @@ void Window::setupMenu(QMenuBar* menubar) { exitFullScreen->setShortcut(QKeySequence("Esc")); addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); + m_shortcutController->addFunctions(toolsMenu, [this]() { + if (m_controller) { + mCheatPressButton(m_controller->cheatDevice(), true); + } + }, [this]() { + if (m_controller) { + mCheatPressButton(m_controller->cheatDevice(), false); + } + }, QKeySequence(Qt::Key_Apostrophe), tr("GameShark Button (held)"), "holdGSButton"); + QMenu* autofireMenu = new QMenu(tr("Autofire"), this); m_shortcutController->addMenu(autofireMenu); From 7026bdaed2827a62917e850b52b23ecf4bf973c9 Mon Sep 17 00:00:00 2001 From: rootfather Date: Fri, 24 Nov 2017 17:20:40 +0100 Subject: [PATCH 060/152] Qt: Update German GUI translation This adds translation for the GameShark (held) string. --- src/platform/qt/ts/mgba-de.ts | 305 +++++++++++++++++----------------- 1 file changed, 155 insertions(+), 150 deletions(-) diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index 0d0716083..001f1c9ef 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -3136,92 +3136,92 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::Window - + Game Boy Advance ROMs (%1) Game Boy Advance-ROMs (%1) - + Game Boy ROMs (%1) Game Boy-ROMs (%1) - + All ROMs (%1) Alle ROMs (%1) - + %1 Video Logs (*.mvl) %1 Video-Logs (*.mvl) - + Archives (%1) Archive (%1) - - - + + + Select ROM ROM auswählen - + Game Boy Advance save files (%1) Game Boy Advance-Speicherdateien (%1) - - - + + + Select save Speicherdatei wählen - + Select patch Patch wählen - + Patches (*.ips *.ups *.bps) Patches (*.ips *.ups *.bps) - + Select image Bild auswählen - + Image file (*.png *.gif *.jpg *.jpeg);;All files (*) Bild-Datei (*.png *.gif *.jpg *.jpeg);;Alle Dateien (*) - - + + GameShark saves (*.sps *.xps) GameShark-Speicherdaten (*.sps *.xps) - + Select video log Video-Log auswählen - + Video logs (*.mvl) Video-Logs (*.mvl) - + Crash Absturz - + The game has crashed with the following error: %1 @@ -3230,638 +3230,643 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + Couldn't Load Konnte nicht geladen werden - + Could not load game. Are you sure it's in the correct format? Konnte das Spiel nicht laden. Sind Sie sicher, dass es im korrekten Format vorliegt? - + Unimplemented BIOS call Nicht implementierter BIOS-Aufruf - + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. Dieses Spiel verwendet einen BIOS-Aufruf, der nicht implementiert ist. Bitte verwenden Sie für die beste Spielerfahrung das offizielle BIOS. - + Really make portable? Portablen Modus wirklich aktivieren? - + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? Diese Einstellung wird den Emulator so konfigurieren, dass er seine Konfiguration aus dem gleichen Verzeichnis wie die Programmdatei lädt. Möchten Sie fortfahren? - + Restart needed Neustart benötigt - + Some changes will not take effect until the emulator is restarted. Einige Änderungen werden erst übernommen, wenn der Emulator neu gestartet wurde. - + - Player %1 of %2 - Spieler %1 von %2 - + %1 - %2 %1 - %2 - + %1 - %2 - %3 %1 - %2 - %3 - + %1 - %2 (%3 fps) - %4 %1 - %2 (%3 Bilder/Sekunde) - %4 - + &File &Datei - + Load &ROM... &ROM laden... - + Load ROM in archive... ROM aus Archiv laden... - + Load temporary save... Temporäre Speicherdatei laden... - + Load &patch... &Patch laden... - + Boot BIOS BIOS booten - + Replace ROM... ROM ersetzen... - + ROM &info... ROM-&Informationen... - + Recent Zuletzt verwendet - + Make portable Portablen Modus aktivieren - + &Load state Savestate (aktueller Zustand) &laden - + F10 F10 - + &Save state Savestate (aktueller Zustand) &speichern - + Shift+F10 Umschalt+F10 - + Quick load Schnell laden - + Quick save Schnell speichern - + Load recent Lade zuletzt gespeicherten Savestate - + Save recent Speichere aktuellen Zustand - + Undo load state Laden des Savestate rückgängig machen - + F11 F11 - + Undo save state Speichern des Savestate rückgängig machen - + Shift+F11 Umschalt+F11 - - + + State &%1 Savestate &%1 - + F%1 F%1 - + Shift+F%1 Umschalt+F%1 - + Load camera image... Lade Kamerabild... - + Import GameShark Save Importiere GameShark-Speicherstand - + Export GameShark Save Exportiere GameShark-Speicherstand - + New multiplayer window Neues Multiplayer-Fenster - + About Über - + E&xit &Beenden - + &Emulation &Emulation - + &Reset Zu&rücksetzen - + Ctrl+R Strg+R - + Sh&utdown Schli&eßen - + Yank game pak Spielmodul herausziehen - + &Pause &Pause - + Ctrl+P Strg+P - + &Next frame &Nächstes Bild - + Ctrl+N Strg+N - + Fast forward (held) Schneller Vorlauf (gehalten) - + &Fast forward Schneller &Vorlauf - + Shift+Tab Umschalt+Tab - + Fast forward speed Vorlauf-Geschwindigkeit - + Unbounded Unbegrenzt - + %0x %0x - + Rewind (held) Zurückspulen (gehalten) - + Re&wind Zur&ückspulen - + ~ ~ - + Step backwards Schrittweiser Rücklauf - + Ctrl+B Strg+B - + Sync to &video Mit &Video synchronisieren - + Sync to &audio Mit &Audio synchronisieren - + Solar sensor Solar-Sensor - + Increase solar level Sonnen-Level erhöhen - + Decrease solar level Sonnen-Level verringern - + Brightest solar level Hellster Sonnen-Level - + Darkest solar level Dunkelster Sonnen-Level - + Brightness %1 Helligkeit %1 - + Audio/&Video Audio/&Video - + Frame size Bildgröße - + %1x %1x - + Toggle fullscreen Vollbildmodus umschalten - + Lock aspect ratio Seitenverhältnis korrigieren - + Force integer scaling Pixelgenaue Skalierung (Integer scaling) - + Frame&skip Frame&skip - + Mute Stummschalten - + FPS target Bildwiederholrate - + 15 15 - + 30 30 - + 45 45 - + Native (59.7) Nativ (59.7) - + 60 60 - + 90 90 - + 120 120 - + 240 240 - + Take &screenshot &Screenshot erstellen - + F12 F12 - + Record output... Ausgabe aufzeichen... - + Record GIF... GIF aufzeichen... - + Record video log... Video-Log aufzeichnen... - + Stop video log Video-Log beenden - + Game Boy Printer... Game Boy Printer... - + Video layers Video-Ebenen - + Audio channels Audio-Kanäle - + &Tools &Werkzeuge - + View &logs... &Logs ansehen... - + Game &overrides... Spiel-&Überschreibungen... - + Game &Pak sensors... Game &Pak-Sensoren... - + &Cheats... &Cheats... - + Open debugger console... Debugger-Konsole äffnen... - + Start &GDB server... &GDB-Server starten... - + Settings... Einstellungen... - + Select folder Ordner auswählen - + Add folder to library... Ordner zur Bibliothek hinzufügen... - + Bilinear filtering Bilineare Filterung - + View &palette... &Palette betrachten... - + View &sprites... &Sprites betrachten... - + View &tiles... &Tiles betrachten... - + View &map... &Map betrachten... - + View memory... Speicher betrachten... - + Search memory... Speicher durchsuchen... - + View &I/O registers... &I/O-Register betrachten... - + Exit fullscreen Vollbildmodus beenden - + + GameShark Button (held) + GameShark-Taste (gehalten) + + + Autofire Autofeuer - + Autofire A Autofeuer A - + Autofire B Autofeuer B - + Autofire L Autofeuer L - + Autofire R Autofeuer R - + Autofire Start Autofeuer Start - + Autofire Select Autofeuer Select - + Autofire Up Autofeuer nach oben - + Autofire Right Autofeuer rechts - + Autofire Down Autofeuer nach unten - + Autofire Left Autofeuer links From bc90283998f2eed665ae9db09f412622137caf03 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 25 Nov 2017 18:00:06 -0800 Subject: [PATCH 061/152] GBA Cheats: Allow multiple ROM patches in the same slot --- CHANGES | 1 + src/gba/cheats/parv3.c | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 3c3a730da..71b95e963 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,7 @@ Misc: - Qt: Add option to disable FPS display - GBA: Improve multiboot image detection - GB MBC: Remove erroneous bank 0 wrapping + - GBA Cheats: Allow multiple ROM patches in the same slot 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index ee11b28fc..979e60b40 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -140,6 +140,7 @@ static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) } static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { + int romPatch = -1; struct mCheat* cheat; switch (op2 & 0xFF000000) { case PAR3_OTHER_SLOWDOWN: @@ -179,30 +180,17 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { cheat->address = _parAddr(op2); cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; - // TODO: Fix overriding existing patches case PAR3_OTHER_PATCH_1: - cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[0].applied = false; - cheats->romPatches[0].exists = true; - cheats->incompletePatch = &cheats->romPatches[0]; + romPatch = 0; break; case PAR3_OTHER_PATCH_2: - cheats->romPatches[1].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[1].applied = false; - cheats->romPatches[1].exists = true; - cheats->incompletePatch = &cheats->romPatches[1]; + romPatch = 1; break; case PAR3_OTHER_PATCH_3: - cheats->romPatches[2].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[2].applied = false; - cheats->romPatches[2].exists = true; - cheats->incompletePatch = &cheats->romPatches[2]; + romPatch = 2; break; case PAR3_OTHER_PATCH_4: - cheats->romPatches[3].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); - cheats->romPatches[3].applied = false; - cheats->romPatches[3].exists = true; - cheats->incompletePatch = &cheats->romPatches[3]; + romPatch = 3; break; case PAR3_OTHER_ENDIF: if (cheats->currentBlock != COMPLETE) { @@ -238,6 +226,18 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; } + if (romPatch >= 0) { + while (cheats->romPatches[romPatch].exists) { + ++romPatch; + if (romPatch >= MAX_ROM_PATCHES) { + break; + } + } + cheats->romPatches[romPatch].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); + cheats->romPatches[romPatch].applied = false; + cheats->romPatches[romPatch].exists = true; + cheats->incompletePatch = &cheats->romPatches[romPatch]; + } return true; } From 3842b35ab062cb183da0259a2c90decec62d5d09 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 25 Nov 2017 18:01:18 -0800 Subject: [PATCH 062/152] GBA Cheats: Increase maximum ROM patch slots --- include/mgba/internal/gba/cheats.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mgba/internal/gba/cheats.h b/include/mgba/internal/gba/cheats.h index e8f23b26d..fe17af1a6 100644 --- a/include/mgba/internal/gba/cheats.h +++ b/include/mgba/internal/gba/cheats.h @@ -13,7 +13,7 @@ CXX_GUARD_START #include #include -#define MAX_ROM_PATCHES 4 +#define MAX_ROM_PATCHES 10 #define COMPLETE ((size_t) -1) enum GBACheatType { From 8ab698782386bd21f45ccc61bfd71dcafa4b0ce2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 27 Nov 2017 12:58:33 -0800 Subject: [PATCH 063/152] CMake: Build fixes --- CMakeLists.txt | 3 ++- include/mgba/core/cheats.h | 5 +++-- src/core/cheats.c | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 82825f02a..7d7f99e5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -502,6 +502,7 @@ if(USE_ZLIB) list(APPEND DEPENDENCY_LIB ${ZLIB_LIBRARIES}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},zlib1g") set(HAVE_CRC32 ON) + list(APPEND OS_LIB ${ZLIB_LIBRARIES}) else() # zlib pulls in crc32 check_function_exists(crc32 HAVE_CRC32) @@ -819,7 +820,7 @@ if(BUILD_OPENEMU) find_library(FOUNDATION Foundation) find_library(OPENEMUBASE OpenEmuBase) file(GLOB OE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/openemu/*.m) - add_library(${BINARY_NAME}-openemu MODULE ${CORE_SRC} ${OE_SRC}) + add_library(${BINARY_NAME}-openemu MODULE ${CORE_SRC} ${OS_SRC}) set_target_properties(${BINARY_NAME}-openemu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/openemu/Info.plist.in BUNDLE TRUE diff --git a/include/mgba/core/cheats.h b/include/mgba/core/cheats.h index 98eb4dd91..32c7cad42 100644 --- a/include/mgba/core/cheats.h +++ b/include/mgba/core/cheats.h @@ -14,8 +14,6 @@ CXX_GUARD_START #include #include -#define MAX_ROM_PATCHES 4 - enum mCheatType { CHEAT_ASSIGN, CHEAT_ASSIGN_INDIRECT, @@ -101,7 +99,10 @@ void mCheatRemoveSet(struct mCheatDevice*, struct mCheatSet*); bool mCheatParseFile(struct mCheatDevice*, struct VFile*); bool mCheatSaveFile(struct mCheatDevice*, struct VFile*); + +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 void mCheatAutosave(struct mCheatDevice*); +#endif void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*); void mCheatPressButton(struct mCheatDevice*, bool down); diff --git a/src/core/cheats.c b/src/core/cheats.c index a9aa253f4..4f5b38695 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -254,6 +254,7 @@ bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) { return true; } +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 void mCheatAutosave(struct mCheatDevice* device) { if (!device->autosave) { return; @@ -262,6 +263,7 @@ void mCheatAutosave(struct mCheatDevice* device) { mCheatSaveFile(device, vf); vf->close(vf); } +#endif void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { if (!cheats->enabled) { From 721224306c0fe2f7d9494387376bf5394966dadb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 27 Nov 2017 13:12:48 -0800 Subject: [PATCH 064/152] GBA Cheats: More fixes --- src/gba/cheats/parv3.c | 9 ++++++++- src/gba/test/cheats.c | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 979e60b40..a49333568 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -250,8 +250,15 @@ bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uin if (cheats->incompleteCheat != COMPLETE) { struct mCheat* incompleteCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat); incompleteCheat->operand = op1 & (0xFFFFFFFFU >> ((4 - incompleteCheat->width) * 8)); + if (cheats->incompleteCheat > 0) { + struct mCheat* lastCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat - 1); + if (lastCheat->type == CHEAT_IF_BUTTON) { + cheats->incompleteCheat = COMPLETE; + return true; + } + } incompleteCheat->operandOffset = op2 >> 24; - incompleteCheat->repeat = ((op2 >> 16) & 0xFF) + 1; + incompleteCheat->repeat = (op2 >> 16) & 0xFF; incompleteCheat->addressOffset = (op2 & 0xFFFF) * incompleteCheat->width; cheats->incompleteCheat = COMPLETE; return true; diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index 08c8b1b82..a18e77c5e 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -1037,9 +1037,12 @@ M_TEST_DEFINE(doPARv3IfButton) { mCheatPressButton(device, true); mCheatRefresh(device, set); - assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); mCheatPressButton(device, false); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + core->rawWrite8(core, 0x03000000, -1, 0); mCheatRefresh(device, set); assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); From 9674364358a1f951670fd51502ac02b918ffa4f5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 9 Dec 2017 12:46:35 -0800 Subject: [PATCH 065/152] Travis: Axe macOS GCC build --- .travis-deps.sh | 5 ----- .travis.yml | 2 -- 2 files changed, 7 deletions(-) diff --git a/.travis-deps.sh b/.travis-deps.sh index fe7c49b23..b249cefee 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -2,11 +2,6 @@ if [ $TRAVIS_OS_NAME = "osx" ]; then brew update brew install qt5 ffmpeg imagemagick sdl2 libzip libpng - if [ "$CC" == "gcc" ]; then - brew install gcc@5 - export CC=gcc-5 - export CXX=g++-5 - fi else sudo apt-get clean sudo add-apt-repository -y ppa:george-edison55/cmake-3.x diff --git a/.travis.yml b/.travis.yml index 3b8970eae..3f3e6077c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,6 @@ matrix: compiler: gcc - os: osx compiler: clang - - os: osx - compiler: gcc before_install: - source ./.travis-deps.sh From 4d2675e3e8b03d259631242437334a22af4f21dc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 9 Dec 2017 12:52:49 -0800 Subject: [PATCH 066/152] Qt: Fix cheats path not greying out (fixes #940) --- src/platform/qt/SettingsView.ui | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 5d4fef4f9..fd897d3e8 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -1674,5 +1674,21 @@ + + cheatsSameDir + toggled(bool) + cheatsPath + setDisabled(bool) + + + 351 + 407 + + + 343 + 372 + + + From db408920ca40417bb1826b82738aefeed06b4814 Mon Sep 17 00:00:00 2001 From: Christian Fetzer Date: Wed, 29 Nov 2017 20:50:04 +0100 Subject: [PATCH 067/152] Fix undefined symbols when compiling libretro core as debug build When building with `cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_LIBRETRO=1` the resulting lib has undefined symbols that cause issues when loading the core in GDB. Functionality is being ifdefed out with the defines MINIMAL_CORE and DISABLE_THREADING, but some symbols are still used in a few places. $ ldd -r mgba_libretro.so undefined symbol: GBAVideoProxyRendererCreate (./mgba_libretro.so) undefined symbol: GBAVideoProxyRendererUnshim (./mgba_libretro.so) undefined symbol: GBAVideoProxyRendererShim (./mgba_libretro.so) undefined symbol: GBVideoProxyRendererCreate (./mgba_libretro.so) undefined symbol: GBVideoProxyRendererUnshim (./mgba_libretro.so) undefined symbol: GBVideoProxyRendererShim (./mgba_libretro.so) undefined symbol: mVideoLogContextInitialState (./mgba_libretro.so) undefined symbol: mVideoLoggerAddChannel (./mgba_libretro.so) undefined symbol: mVideoLoggerAttachChannel (./mgba_libretro.so) undefined symbol: mVideoLoggerRendererCreate (./mgba_libretro.so) undefined symbol: mCoreThreadMarkCrashed (./mgba_libretro.so) --- src/core/thread.c | 2 ++ src/gb/core.c | 2 ++ src/gba/core.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/core/thread.c b/src/core/thread.c index 15dafe185..c08a1ab7e 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -625,7 +625,9 @@ static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level printf("\n"); struct mCoreThread* thread = mCoreThreadGet(); if (thread && level == mLOG_FATAL) { +#ifndef DISABLE_THREADING mCoreThreadMarkCrashed(thread); +#endif } } diff --git a/src/gb/core.c b/src/gb/core.c index c7e801be9..753a93bde 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -812,6 +812,7 @@ static void _GBCoreEnableAudioChannel(struct mCore* core, size_t id, bool enable } } +#ifndef MINIMAL_CORE static void _GBCoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) { struct GBCore* gbcore = (struct GBCore*) core; struct GB* gb = core->board; @@ -834,6 +835,7 @@ static void _GBCoreEndVideoLog(struct mCore* core) { free(gbcore->proxyRenderer.logger); gbcore->proxyRenderer.logger = NULL; } +#endif struct mCore* GBCoreCreate(void) { struct GBCore* gbcore = malloc(sizeof(*gbcore)); diff --git a/src/gba/core.c b/src/gba/core.c index aa7cdaeea..16c1d1040 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -825,6 +825,7 @@ static void _GBACoreEnableAudioChannel(struct mCore* core, size_t id, bool enabl } } +#ifndef MINIMAL_CORE static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) { struct GBACore* gbacore = (struct GBACore*) core; struct GBA* gba = core->board; @@ -851,6 +852,7 @@ static void _GBACoreEndVideoLog(struct mCore* core) { free(gbacore->proxyRenderer.logger); gbacore->proxyRenderer.logger = NULL; } +#endif struct mCore* GBACoreCreate(void) { struct GBACore* gbacore = malloc(sizeof(*gbacore)); From e31373560535203d826687044290a4994706c2dd Mon Sep 17 00:00:00 2001 From: ilovezfs Date: Mon, 11 Dec 2017 00:39:32 -0800 Subject: [PATCH 068/152] Qt: Fix build with Qt 5.10 Fixes "MemoryModel.cpp:102:15: error: no viable overloaded '='" --- src/platform/qt/MemoryModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/MemoryModel.cpp b/src/platform/qt/MemoryModel.cpp index 92fb2aa98..e2ad0700a 100644 --- a/src/platform/qt/MemoryModel.cpp +++ b/src/platform/qt/MemoryModel.cpp @@ -99,7 +99,7 @@ void MemoryModel::setRegion(uint32_t base, uint32_t size, const QString& name, i m_top = 0; m_base = base; m_size = size; - m_regionName = name; + m_regionName = QStaticText(name); m_regionName.prepare(QTransform(), m_font); m_currentBank = segment; verticalScrollBar()->setRange(0, (size >> 4) + 1 - viewport()->size().height() / m_cellHeight); From baabe0090bb1fd5997e531fd9568c2de09b5fc21 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 11 Dec 2017 02:01:33 -0800 Subject: [PATCH 069/152] Qt: Fix Qt 5.10-induced bitrot --- src/platform/qt/CMakeLists.txt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 8b4a823a2..8588f3b38 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -2,13 +2,6 @@ set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -if(APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7") - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - endif() -endif() - set(PLATFORM_SRC) set(QT_STATIC OFF) @@ -41,6 +34,17 @@ if(NOT Qt5Widgets_FOUND) return() endif() +if(APPLE) + if(Qt5Widgets_VERSION MATCHES "^5.1[0-9]") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.7") + endif() + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() +endif() + if(BUILD_GL) list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c) if(NOT WIN32 OR USE_EPOXY) From 53456b5bd5e7cab3b761dfad654202674155bcf8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 13 Dec 2017 10:25:34 -0800 Subject: [PATCH 070/152] 3DS: Add xml file for app takeover (fixes #891) --- src/platform/3ds/CMakeLists.txt | 11 +++++++++-- src/platform/3ds/hbl.xml | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/platform/3ds/hbl.xml diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index c0f2dd35f..9c36d82cb 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -57,6 +57,10 @@ add_custom_command(OUTPUT ${BINARY_NAME}.smdh COMMAND ${BANNERTOOL} makesmdh -s "${PROJECT_NAME}" -l "${SUMMARY}" -p "endrift" -i ${CMAKE_SOURCE_DIR}/res/mgba-48.png -o ${BINARY_NAME}.smdh DEPENDS ${CMAKE_SOURCE_DIR}/res/mgba-48.png) +add_custom_command(OUTPUT ${BINARY_NAME}.xml + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/hbl.xml ${BINARY_NAME}.xml + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/hbl.xml) + add_custom_command(OUTPUT ${BINARY_NAME}.bnr COMMAND ${BANNERTOOL} makebanner -ci ${CMAKE_CURRENT_SOURCE_DIR}/banner.cgfx -a ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav -o ${BINARY_NAME}.bnr DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/banner.cgfx ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav) @@ -84,7 +88,7 @@ add_custom_command( add_custom_command(OUTPUT ${BINARY_NAME}.3dsx COMMAND ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh - DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) + DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh ${BINARY_NAME}.xml) add_custom_target(${BINARY_NAME}.3dsx ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx) add_custom_command(OUTPUT ${BINARY_NAME}.cia @@ -112,5 +116,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cia.rsf.in ${CMAKE_CURRENT_BINARY_DIR install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.xml + DESTINATION 3dsx COMPONENT ${BINARY_NAME}-3ds) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.cia - DESTINATION . COMPONENT ${BINARY_NAME}-3ds) + DESTINATION cia COMPONENT ${BINARY_NAME}-3ds) diff --git a/src/platform/3ds/hbl.xml b/src/platform/3ds/hbl.xml new file mode 100644 index 000000000..b96036950 --- /dev/null +++ b/src/platform/3ds/hbl.xml @@ -0,0 +1,14 @@ + + 0004001000020d00 + 0004001000021d00 + 0004001000022d00 + 0004001000026d00 + 0004001000027d00 + 0004001000028d00 + 0004001020020d00 + 0004001020021d00 + 0004001020022d00 + 0004001020026d00 + 0004001020027d00 + 0004001020028d00 + From f55bf289fff78fc1d71b1680b3bfdaf32d9471ee Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Thu, 14 Dec 2017 03:37:24 +0100 Subject: [PATCH 071/152] Doc: Add German translation of the README.md file (#939) * Doc: Add German translation of the README.md file This is the German translation of the README.md file. I'll try to update it as soon as there are any changes made the README file. One step closer to a full German localization :) * Doc: Rename German readme file --- README_DE.md | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 README_DE.md diff --git a/README_DE.md b/README_DE.md new file mode 100644 index 000000000..fab766dba --- /dev/null +++ b/README_DE.md @@ -0,0 +1,178 @@ +mGBA +==== + +mGBA ist ein Emulator für Game Boy Advance-Spiele. Das Ziel von mGBA ist, schneller und genauer als viele existierende Game Boy Advance-Emulatoren zu sein. Außerdem verfügt mGBA über Funktionen, die anderen Emulatoren fehlen. Zusätzlich werden auch Game Boy- und Game Boy Color-Spiele unterstützt. + +Aktuelle Neuigkeiten und Downloads findest Du auf [mgba.io](https://mgba.io). + +[![Build-Status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)](https://travis-ci.org/mgba-emu/mgba) + +Features +-------- + +- Nahzu vollständige Unterstützung der Game Boy Advance-Hardware[[1]](#missing). +- Unterstützung der Game Boy-/Game Boy Color-Hardware. +- Schnelle Emulation. mGBA ist dafür bekannt, auch auf schwacher Hardware wie Netbooks mit voller Geschwindigkeit zu laufen. +- Qt- und SDL-Portierungen für eine vollwertige und eine "leichtgewichtige" Benutzeroberfläche. +- Lokale (gleicher Computer) Unterstützung für Link-Kabel. +- Erkennung des Speichertypes, einschließlich der Größe des Flash-Speichers[[2]](#flashdetect). +- Unterstützung für Spielmodule mit Bewegungssensoren und Rüttel-Effekten (nur verwendbar mit Spiele-Controllern). +- Unterstützung für Echtzeituhren, selbst ohne Konfiguration. +- Unterstützung für Game Boy Printer und Game Boy Camera. +- Eingebaute BIOS-Implementierung mit der Möglichkeit, externe BIOS-Dateien zu laden. +- Turbo/Vorlauf-Unterstützung durch drücken der Tab-Taste. +- Rücklauf-Unterstützung durch drücken der Akzent-Taste. +- Frameskip von bis zu 10 Bildern. +- Unterstützung für Screenshots. +- Unterstützung für Cheat-Codes. +- 9 Speicherstände für Savestates/Spielzustände. Savestates können auch als Screenshots dargestellt werden. +- Video- und GIF-Aufzeichnung. +- Frei wählbare Tastenbelegungen für Tastaturen und Controller. +- Unterstützung für ZIP- und 7z-Archive. +- Unterstützung für Patches im IPS-, UPS- und BPS-Format. +- Spiele-Debugging über ein Kommandozeilen-Interface und IDA Pro-kompatible GDB-Unterstützung. +- Einstellbare Rücklauf-Funktion. +- Unterstützung für das Laden und Exportieren von GameShark- und Action Replay-Abbildern. +- Verfügbare Cores für RetroArch/Libretro und OpenEmu. +- Viele, viele kleinere Dinge. + +### Geplante Features + +- Unterstützung für Link-Kabel-Multiplayer über ein Netzwerk. +- Unterstützung für Link-Kabel über Dolphin/JOY-Bus. +- M4A-Audio-Abmischung für höhere Audio-Qualität. +- Unterstützung für Tool-Assisted Speedruns. +- Lua-Unterstützung für Scripting. +- Eine umfangreiche Debugging-Suite. +- e-Reader-Unterstützung. +- Unterstützung für Drahtlosadapter. + +Unterstützte Plattformen +------------------------ + +- Windows Vista oder neuer +- OS X 10.7 (Lion)[[3]](#osxver) oder neuer +- Linux +- FreeBSD +- Nintendo 3DS +- Wii +- PlayStation Vita + +Andere Unix-ähnliche Plattformen wie OpenBSD sind ebenfalls dafür bekannt, mit mGBA kompatibel zu sein. Sie sind jedoch nicht getestet und werden nicht voll unterstützt. + +### Systemvoraussetzungen + +Die Systemvoraussetzungen sind minimal. Jeder Computer, der mit Windows Vista oder neuer läuft, sollte in der Lage sein, die Emulation zu bewältigen. Unterstützung für OpenGL 1.1 oder neuer ist ebenfalls voraussgesetzt. + +Downloads +--------- + +Download-Links befinden sich in der [Downloads][downloads]-Sektion auf der offizielle Website. Der Quellcode befindet sich auf [GitHub][source]. + +Steuerung +--------- + +Die Steuerung kann im Einstellungs-Menü konfiguriert werden. Viele Spiele-Controller werden automatisch erkannt und entsprechend belegt. Für Tastaturen wird standardmäßig folgende Belegung verwendet: + +- **A**: X +- **B**: Z +- **L**: A +- **R**: S +- **Start**: Enter +- **Select**: Rücktaste + +Kompilieren +----------- + +Um mGBA kompilieren zu können, wird CMake 2.8.11 oder neuer benötigt. GCC und Clang sind beide dafür bekannt, mGBA kompilieren zu können. Visual Studio 2013 und älter funktionieren nicht. Unterstützung für Visual Studio 2015 und neuer wird bald hinzugefügt. Um CMake auf einem Unix-basierten System zu verwenden, werden folgende Kommandos empfohlen: + + mkdir build + cd build + cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr .. + make + sudo make install + +Damit wird mGBA gebaut und in `/usr/bin` und `/usr/lib` installiert. Installierte Abhängigkeiten werden automatisch erkannt. Features, die aufgrund fehlender Abhängigkeiten deaktiviert werden, werden nach dem `cmake`-Kommando aufgelistet. + +Wenn Du macOS verwendest, sind die einzelnen Schritte etwas anders. Angenommen, dass Du eine Homebrew-Paketverwaltung verwendest, werden folgende Schritte zum installieren der Abhängigkeiten und anschließenden bauen von mGBA empfohlen: + + brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit + mkdir build + cd build + cmake -DCMAKE_PREFIX_PATH='brew --prefix qt5' .. + make + +Bitte beachte, dass Du unter macOS nicht 'make install' verwenden solltest, da dies nicht korrekt funktionieren wird. + +### Für Entwickler: Kompilieren unter Windows + +Um mGBA auf Windows zu kompilieren, wird MSYS2 empfohlen. Befolge die Installationsschritte auf der [MSYS2-Website](https://msys2.github.io). Stelle sicher, dass Du die 32-Bit-Version ("MSYS2 MinGW 32-bit") (oder die 64-Bit-Version "MSYS2 MinGW 64-bit", wenn Du mGBA für x86_64 kompilieren willst) verwendest und führe folgendes Kommando (einschließlich der Klammern) aus, um alle benötigten Abhängigkeiten zu installieren. Bitte beachte, dass dafür über 500MiB an Paketen heruntergeladen werden, was eine Weile dauern kann): + +Für x86 (32 Bit): + + pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + +Für x86_64 (64 Bit): + + pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + +Lade den aktuellen mGBA-Quellcode mithilfe des folgenden Kommandos herunter: + + git clone https://github.com/mgba-emu/mgba.git + +Abschließend wird mGBA über folgende Kommandos kompiliert: + + cd mgba + mkdir build + cd build + cmake .. -G "MSYS Makefiles" + make + +Bitte beachte, dass mGBA für Windows aufgrund der Vielzahl an benötigten DLLs nicht für die weitere Verteilung geeignet ist, wenn es auf diese Weise gebaut wurde. Es ist jedoch perfekt für Entwickler geeignet. Soll mGBA dennoch weiter verteilt werden (beispielsweise zu Testzwecken auf Systemen, auf denen keine MSYS2-Umgebung installier ist), kann ein Tool namens "[Dependency Walker](http://dependencywalker.com)" verwendet werden, um zu sehen, welche DLLs zusammen mit der mGBA-Programmdatei ausgeliefert werden müssen. + +### Abhängigkeiten + +mGBA hat keine "harten" Abhängigkeiten. Dennoch werden die folgenden optionalen Abhängigkeiten für einige Features benötigt. Diese Features werden automatisch deaktiviert, wenn die benötigten Abhängigkeiten nicht gefunden werden. + +- Qt 5: Für die Benutzeroberfläche. Qt Multimedia oder SDL werden für Audio-Ausgabe benötigt. +- SDL: Für eine einfachere Benutzeroberfläche und Spiele-Controller-Unterstützung in der Qt-Oberfläche. SDL 2 ist empfohlen, SDL 1.2 wird jedoch auch unterstützt. +- zlib und libpng: Für die Unterstützung von Bildschirmfotos und Savestates-in-PNG-Unterstützung. +- libedit: Für die Unterstützung des Kommandozeilen-Debuggers. +- ffmpeg oder libav: Für Videoaufzeichnungen. +- libzip oder zlib: Um ROMs aus ZIP-Dateien zu laden. +- ImageMagick: Für GIF-Aufzeichnungen. +- SQLite3: Für Spiele-Datenbanken. +- libelf: Für das Laden von ELF-Dateien. + +SQLite3, libpng und zlib werden mit dem Emulator mitgeliefert, sodass sie nicht zuerst kompiliert werden müssen. + +Fußnoten +-------- + +[1] Zurzeit fehlende Features sind + +- OBJ-Fenster für die Modi 3, 4 und 5 ([Bug #5](http://mgba.io/b/5)) +- Mosaik-Effekt für umgewandelte OBJs ([Bug #9](http://mgba.io/b/9)) + +[2] In manchen Fällen ist es nicht möglich, die Größe des Flash-Speichers automatisch zu ermitteln. Diese kann dann zur Laufzeit konfiguriert werden, es wird jedoch empfohlen, den Fehler zu melden. + +[3] 10.7 wird nur für die Qt-Portierung benötigt. Die SDL-Portierung ist dafür bekannt, mit 10.5 und möglicherweise auf älteren Versionen zu funktionieren. + +[downloads]: http://mgba.io/downloads.html +[source]: https://github.com/mgba-emu/mgba/ + +Copyright +--------- + +Copyright für mGBA © 2013 – 2017 Jeffrey Pfau. mGBA wird unter der [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/) veröffentlicht. Eine Kopie der Lizenz ist in der mitgelieferten Datei LICENSE verfügbar. + +mGBA beinhaltet die folgenden Bibliotheken von Drittanbietern: + +- [inih](https://github.com/benhoyt/inih), Copyright © 2009 Ben Hoyt, verwendet unter einer BSD 3-clause-Lizenz. +- [blip-buf](https://code.google.com/archive/b/blip-buf), Copyright © 2003 - 2009 Shay Green, verwendet unter einer Lesser GNU Public License. +- [LZMA SDK](http://www.7-zip.org/sdk.html), Public Domain. +- [MurmurHash3](https://github.com/aappleby/smhasher), Implementierung von Austin Appleby, Public Domain. +- [getopt fot MSVC](https://github.com/skandhurkat/Getopt-for-Visual-Studio/), Public Domain. +- [SQLite3](https://www.sqlite.org), Public Domain. + +Wenn Du ein Spiele-Publisher bist und mGBA für kommerzielle Verwendung lizenzieren möchtest, schreibe bitte eine e-Mail an [licensing@mgba.io](mailto:licensing@mgba.io) für weitere Informationen. From a1f1740d82a468921e24eab2d7f21d4c3c221251 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 14 Dec 2017 09:24:06 -0800 Subject: [PATCH 072/152] Qt: Fix locale being set to English on settings save (fixes #906) --- CHANGES | 1 + src/platform/qt/SettingsView.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 71b95e963..372055813 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugfixes: - GBA Video: Add delay when enabling BGs (fixes mgba.io/i/744, mgba.io/i/752) - GB Memory: HDMAs should not start when LCD is off (fixes mgba.io/i/310) - GBA Cheats: Fix slide codes not initializing properly + - Qt: Fix locale being set to English on settings save (fixes mgba.io/i/906) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 12ca366f8..8e44dc025 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -276,7 +276,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC } QLocale locale(name.remove(QString("%0-").arg(binaryName)).remove(".qm")); m_ui.languages->addItem(locale.nativeLanguageName(), locale); - if (locale == QLocale()) { + if (locale.bcp47Name() == QLocale().bcp47Name()) { m_ui.languages->setCurrentIndex(m_ui.languages->count() - 1); } } From 17801df8167e24d98b96495a43b0f9550ade8f30 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 14 Dec 2017 09:55:45 -0800 Subject: [PATCH 073/152] Python: Fix intermediate versioning --- src/platform/python/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/python/CMakeLists.txt b/src/platform/python/CMakeLists.txt index a2c1a3568..db17129a5 100644 --- a/src/platform/python/CMakeLists.txt +++ b/src/platform/python/CMakeLists.txt @@ -13,9 +13,9 @@ file(GLOB PYTHON_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.h) if(NOT GIT_TAG) if(GIT_BRANCH STREQUAL "master" OR NOT GIT_BRANCH) - set(PYLIB_VERSION -b -${GIT_REV}-${GIT_COMMIT_SHORT}) + set(PYLIB_VERSION -b .dev${GIT_REV}+g${GIT_COMMIT_SHORT}) else() - set(PYLIB_VERSION -b -${GIT_BRANCH}-${GIT_REV}-${GIT_COMMIT_SHORT}) + set(PYLIB_VERSION -b .dev${GIT_REV}+${GIT_BRANCH}.g${GIT_COMMIT_SHORT}) endif() endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in ${CMAKE_CURRENT_BINARY_DIR}/setup.py) From e655e0d9254dca7df6c4d4d2efa06a9ada56c977 Mon Sep 17 00:00:00 2001 From: rootfather Date: Thu, 21 Dec 2017 19:17:21 +0100 Subject: [PATCH 074/152] Qt/de: Improve translation of the frameskip option --- src/platform/qt/ts/mgba-de.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index 001f1c9ef..2b8bd6bab 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -4159,13 +4159,13 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. Skip every - Alle + Überspringe frames - Bilder + Bild(er) From f3ea4caf84ab2292e65b10245d9343b5ba31cdaf Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 23 Dec 2017 15:37:57 -0800 Subject: [PATCH 075/152] All: Support building on PPC Mac --- CMakeLists.txt | 10 ++++- include/mgba-util/common.h | 42 ++++++++++++++++--- include/mgba-util/platform/posix/threading.h | 5 +++ include/mgba-util/vector.h | 4 ++ include/mgba/debugger/debugger.h | 6 +-- include/mgba/internal/debugger/cli-debugger.h | 10 ++--- src/arm/debugger/debugger.c | 2 +- src/arm/debugger/memory-debugger.c | 12 +++--- src/debugger/cli-debugger.c | 8 ++-- src/debugger/gdb-stub.c | 8 ++-- src/gb/gb.c | 4 +- src/gba/gba.c | 6 +-- src/lr35902/debugger/memory-debugger.c | 8 ++-- src/platform/opengl/gl.c | 4 ++ src/platform/opengl/gles2.c | 4 ++ src/platform/sdl/sdl-audio.h | 8 ++++ src/platform/sdl/sdl-events.h | 8 ++++ 17 files changed, 108 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d7f99e5b..45719707d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,11 @@ cmake_minimum_required(VERSION 3.1) project(mGBA) set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries") if(NOT MSVC) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers -std=c99") + set(GCC_STD "c99") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_COMPILER_VERSION VERSION_LESS "4.3") + set(GCC_STD "gnu99") + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers -std=${GCC_STD}") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146") endif() @@ -218,7 +222,9 @@ endif() if(APPLE) add_definitions(-D_DARWIN_C_SOURCE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") + if(CMAKE_SYSTEM_VERSION VERSION_GREATER "10.5.8") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6") + endif() endif() if(NOT HAIKU AND NOT MSVC AND NOT PSP2) diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 63e3dd30d..45961ddbb 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -73,7 +73,7 @@ typedef intptr_t ssize_t; #define M_PI 3.141592654f #endif -#ifndef _MSC_VER +#if !defined(_MSC_VER) && (defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) #define ATOMIC_STORE(DST, SRC) __atomic_store_n(&DST, SRC, __ATOMIC_RELEASE) #define ATOMIC_LOAD(DST, SRC) DST = __atomic_load_n(&SRC, __ATOMIC_ACQUIRE) #define ATOMIC_ADD(DST, OP) __atomic_add_fetch(&DST, OP, __ATOMIC_RELEASE) @@ -101,6 +101,8 @@ typedef intptr_t ssize_t; #define PRIz "z" #endif +#if defined __BIG_ENDIAN__ +#define LOAD_32BE(DEST, ADDR, ARR) DEST = *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) #if defined(__PPC__) || defined(__POWERPC__) #define LOAD_32LE(DEST, ADDR, ARR) { \ uint32_t _addr = (ADDR); \ @@ -126,10 +128,39 @@ typedef intptr_t ssize_t; __asm__("sthbrx %0, %1, %2" : : "r"(SRC), "b"(_ptr), "r"(_addr)); \ } -#define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(((uint64_t*) ARR)[(ADDR) >> 3]) -#define STORE_64LE(SRC, ADDR, ARR) ((uint64_t*) ARR)[(ADDR) >> 3] = __builtin_bswap64(SRC) -#elif defined __BIG_ENDIAN__ -#if defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) +#define LOAD_64LE(DEST, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + union { \ + struct { \ + uint32_t hi; \ + uint32_t lo; \ + }; \ + uint64_t b64; \ + } *bswap = (void*) &DEST; \ + const void* _ptr = (ARR); \ + __asm__( \ + "lwbrx %0, %2, %3 \n" \ + "lwbrx %1, %2, %4 \n" \ + : "=r"(bswap->lo), "=r"(bswap->hi) : "b"(_ptr), "r"(_addr), "r"(_addr + 4)); \ +} + +#define STORE_64LE(SRC, ADDR, ARR) { \ + uint32_t _addr = (ADDR); \ + union { \ + struct { \ + uint32_t hi; \ + uint32_t lo; \ + }; \ + uint64_t b64; \ + } *bswap = (void*) &SRC; \ + const void* _ptr = (ARR); \ + __asm__( \ + "stwbrx %0, %2, %3 \n" \ + "stwbrx %1, %2, %4 \n" \ + : : "r"(bswap->hi), "r"(bswap->lo), "b"(_ptr), "r"(_addr), "r"(_addr + 4)); \ +} + +#elif defined(__llvm__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) #define LOAD_64LE(DEST, ADDR, ARR) DEST = __builtin_bswap64(((uint64_t*) ARR)[(ADDR) >> 3]) #define LOAD_32LE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) #define LOAD_16LE(DEST, ADDR, ARR) DEST = __builtin_bswap16(((uint16_t*) ARR)[(ADDR) >> 1]) @@ -146,6 +177,7 @@ typedef intptr_t ssize_t; #define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC +#define LOAD_32BE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2]) #endif #define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START)) diff --git a/include/mgba-util/platform/posix/threading.h b/include/mgba-util/platform/posix/threading.h index f79d97b10..468e1460c 100644 --- a/include/mgba-util/platform/posix/threading.h +++ b/include/mgba-util/platform/posix/threading.h @@ -86,7 +86,12 @@ static inline int ThreadJoin(Thread thread) { static inline int ThreadSetName(const char* name) { #ifdef __APPLE__ +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 return pthread_setname_np(name); +#else + UNUSED(name); + return 0; +#endif #elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); return 0; diff --git a/include/mgba-util/vector.h b/include/mgba-util/vector.h index ae542d41b..c18b6c8b5 100644 --- a/include/mgba-util/vector.h +++ b/include/mgba-util/vector.h @@ -10,6 +10,10 @@ CXX_GUARD_START +#ifdef vector +#undef vector +#endif + #define DECLARE_VECTOR(NAME, TYPE) \ struct NAME { \ TYPE* vector; \ diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index 0a9bb67c1..3dc813334 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -60,13 +60,13 @@ struct mDebuggerEntryInfo { uint32_t newValue; enum mWatchpointType watchType; enum mWatchpointType accessType; - }; + } wp; struct { uint32_t opcode; enum mBreakpointType breakType; - }; - }; + } bp; + } type; }; struct mDebugger; diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index a6d623df3..eabb3bcf1 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -25,13 +25,9 @@ struct CLIDebugVector { CLIDV_INT_TYPE, CLIDV_CHAR_TYPE, } type; - union { - char* charValue; - struct { - int32_t intValue; - int segmentValue; - }; - }; + char* charValue; + int32_t intValue; + int segmentValue; }; typedef void (*CLIDebuggerCommand)(struct CLIDebugger*, struct CLIDebugVector*); diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index 4033f4a13..1d646208e 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -39,7 +39,7 @@ static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { } struct mDebuggerEntryInfo info = { .address = breakpoint->address, - .breakType = BREAKPOINT_HARDWARE + .type.bp.breakType = BREAKPOINT_HARDWARE }; mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info); } diff --git a/src/arm/debugger/memory-debugger.c b/src/arm/debugger/memory-debugger.c index faa8651f4..88a8518b6 100644 --- a/src/arm/debugger/memory-debugger.c +++ b/src/arm/debugger/memory-debugger.c @@ -99,19 +99,19 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st if (!((watchpoint->address ^ address) & ~width) && watchpoint->type & type) { switch (width + 1) { case 1: - info->oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0); + info->type.wp.oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0); break; case 2: - info->oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0); + info->type.wp.oldValue = debugger->originalMemory.load16(debugger->cpu, address, 0); break; case 4: - info->oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0); + info->type.wp.oldValue = debugger->originalMemory.load32(debugger->cpu, address, 0); break; } - info->newValue = newValue; + info->type.wp.newValue = newValue; info->address = address; - info->watchType = watchpoint->type; - info->accessType = type; + info->type.wp.watchType = watchpoint->type; + info->type.wp.accessType = type; return true; } } diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 8b98d8a7b..f00a000d1 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -848,10 +848,10 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r break; case DEBUGGER_ENTER_WATCHPOINT: if (info) { - if (info->accessType & WATCHPOINT_WRITE) { - cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->newValue, info->oldValue); + if (info->type.wp.accessType & WATCHPOINT_WRITE) { + cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->type.wp.newValue, info->type.wp.oldValue); } else { - cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->oldValue); + cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->type.wp.oldValue); } } else { cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint\n"); @@ -859,7 +859,7 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r break; case DEBUGGER_ENTER_ILLEGAL_OP: if (info) { - cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode at 0x%08X: 0x%08X\n", info->address, info->opcode); + cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode at 0x%08X: 0x%08X\n", info->address, info->type.bp.opcode); } else { cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode\n"); } diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 6e2d287fa..73dd978d6 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -46,7 +46,7 @@ static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReaso break; case DEBUGGER_ENTER_BREAKPOINT: if (stub->supportsHwbreak && stub->supportsSwbreak && info) { - snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%cwbreak:;", SIGTRAP, info->breakType == BREAKPOINT_SOFTWARE ? 's' : 'h'); + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%cwbreak:;", SIGTRAP, info->type.bp.breakType == BREAKPOINT_SOFTWARE ? 's' : 'h'); } else { snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02xk", SIGTRAP); } @@ -54,9 +54,9 @@ static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReaso case DEBUGGER_ENTER_WATCHPOINT: if (info) { const char* type = 0; - switch (info->watchType) { + switch (info->type.wp.watchType) { case WATCHPOINT_WRITE: - if (info->newValue == info->oldValue) { + if (info->type.wp.newValue == info->type.wp.oldValue) { if (stub->d.state == DEBUGGER_PAUSED) { stub->d.state = DEBUGGER_RUNNING; } @@ -363,7 +363,7 @@ static void _writeRegister(struct GDBStub* stub, const char* message) { #ifdef _MSC_VER value = _byteswap_ulong(value); #else - value = __builtin_bswap32(value); + LOAD_32BE(value, 0, &value); #endif if (reg <= ARM_PC) { diff --git a/src/gb/gb.c b/src/gb/gb.c index 6ecaaf74f..617161851 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -701,7 +701,7 @@ void GBStop(struct LR35902Core* cpu) { if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) { struct mDebuggerEntryInfo info = { .address = cpu->pc - 1, - .opcode = 0x1000 | cpu->bus + .type.bp.opcode = 0x1000 | cpu->bus }; mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info); } @@ -720,7 +720,7 @@ void GBIllegal(struct LR35902Core* cpu) { if (cpu->components && cpu->components[CPU_COMPONENT_DEBUGGER]) { struct mDebuggerEntryInfo info = { .address = cpu->pc, - .opcode = cpu->bus + .type.bp.opcode = cpu->bus }; mDebuggerEnter((struct mDebugger*) cpu->components[CPU_COMPONENT_DEBUGGER], DEBUGGER_ENTER_ILLEGAL_OP, &info); } diff --git a/src/gba/gba.c b/src/gba/gba.c index a2385954e..a08c6d73f 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -662,7 +662,7 @@ void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) { if (gba->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .opcode = opcode + .type.bp.opcode = opcode }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_ILLEGAL_OP, &info); } @@ -685,7 +685,7 @@ void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) { if (gba->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .opcode = opcode + .type.bp.opcode = opcode }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_ILLEGAL_OP, &info); } else @@ -706,7 +706,7 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { if (gba->debugger) { struct mDebuggerEntryInfo info = { .address = _ARMPCAddress(cpu), - .breakType = BREAKPOINT_SOFTWARE + .type.bp.breakType = BREAKPOINT_SOFTWARE }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_BREAKPOINT, &info); } diff --git a/src/lr35902/debugger/memory-debugger.c b/src/lr35902/debugger/memory-debugger.c index 3b9f66343..f9918a1f7 100644 --- a/src/lr35902/debugger/memory-debugger.c +++ b/src/lr35902/debugger/memory-debugger.c @@ -47,11 +47,11 @@ static bool _checkWatchpoints(struct LR35902Debugger* debugger, uint16_t address for (i = 0; i < LR35902DebugWatchpointListSize(&debugger->watchpoints); ++i) { watchpoint = LR35902DebugWatchpointListGetPointer(&debugger->watchpoints, i); if (watchpoint->address == address && (watchpoint->segment < 0 || watchpoint->segment == debugger->originalMemory.currentSegment(debugger->cpu, address)) && watchpoint->type & type) { - info->oldValue = debugger->originalMemory.load8(debugger->cpu, address); - info->newValue = newValue; + info->type.wp.oldValue = debugger->originalMemory.load8(debugger->cpu, address); + info->type.wp.newValue = newValue; info->address = address; - info->watchType = watchpoint->type; - info->accessType = type; + info->type.wp.watchType = watchpoint->type; + info->type.wp.accessType = type; return true; } } diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 87b679c6b..cfa05bea9 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -45,6 +45,8 @@ static void mGLContextSetDimensions(struct VideoBackend* v, unsigned width, unsi #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif @@ -114,6 +116,8 @@ void mGLContextPostFrame(struct VideoBackend* v, const void* frame) { #else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif +#elif defined(__BIG_ENDIAN__) + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 84343e35f..94d0856ca 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -149,6 +149,8 @@ static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, u #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif @@ -324,6 +326,8 @@ void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif diff --git a/src/platform/sdl/sdl-audio.h b/src/platform/sdl/sdl-audio.h index dc21cb86c..2ae920af4 100644 --- a/src/platform/sdl/sdl-audio.h +++ b/src/platform/sdl/sdl-audio.h @@ -13,6 +13,14 @@ CXX_GUARD_START #include #include +// Altivec sometimes defines this +#ifdef vector +#undef vector +#endif +#ifdef bool +#undef bool +#define bool _Bool +#endif mLOG_DECLARE_CATEGORY(SDL_AUDIO); diff --git a/src/platform/sdl/sdl-events.h b/src/platform/sdl/sdl-events.h index 3c67222cd..c9d7b386f 100644 --- a/src/platform/sdl/sdl-events.h +++ b/src/platform/sdl/sdl-events.h @@ -16,6 +16,14 @@ CXX_GUARD_START #include #include +// Altivec sometimes defines this +#ifdef vector +#undef vector +#endif +#ifdef bool +#undef bool +#define bool _Bool +#endif mLOG_DECLARE_CATEGORY(SDL_EVENTS); From 2a80438443bf0bda84916e56f39d3de7c196286c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 23 Dec 2017 19:49:23 -0800 Subject: [PATCH 076/152] Qt: Fix fast forward toggle disable state (fixes #946) --- src/platform/qt/SettingsView.ui | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index fd897d3e8..26172ad3e 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -1690,5 +1690,21 @@ + + fastForwardUnbounded + toggled(bool) + fastForwardRatio + setDisabled(bool) + + + 445 + 38 + + + 349 + 38 + + + From 52e4c4e67cda9ff1bc8fe11e9f6f71111b0c7749 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 24 Dec 2017 17:04:03 -0800 Subject: [PATCH 077/152] Qt: Make some debug view text selectable --- src/platform/qt/AssetTile.ui | 6 ++++++ src/platform/qt/ObjView.ui | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/platform/qt/AssetTile.ui b/src/platform/qt/AssetTile.ui index 92a8623e1..e5557506b 100644 --- a/src/platform/qt/AssetTile.ui +++ b/src/platform/qt/AssetTile.ui @@ -50,6 +50,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + @@ -92,6 +95,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + diff --git a/src/platform/qt/ObjView.ui b/src/platform/qt/ObjView.ui index 37dcf9533..7f33eaeb0 100644 --- a/src/platform/qt/ObjView.ui +++ b/src/platform/qt/ObjView.ui @@ -561,6 +561,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + From e56ca6ac088cb84fe2af69acc33d263742ece373 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 24 Dec 2017 17:04:23 -0800 Subject: [PATCH 078/152] GBA DMA: Add misalign warnings --- src/gba/dma.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gba/dma.c b/src/gba/dma.c index 0e0abeefe..4516b5224 100644 --- a/src/gba/dma.c +++ b/src/gba/dma.c @@ -87,6 +87,15 @@ uint16_t GBADMAWriteCNT_HI(struct GBA* gba, int dma, uint16_t control) { currentDma->reg = GBADMARegisterClearSrcControl(currentDma->reg); } currentDma->nextDest = currentDma->dest; + + uint32_t width = 2 << GBADMARegisterGetWidth(currentDma->reg); + if (currentDma->nextSource & (width - 1)) { + mLOG(GBA_MEM, GAME_ERROR, "Misaligned DMA source address: 0x%08X", currentDma->nextSource); + } + if (currentDma->nextDest & (width - 1)) { + mLOG(GBA_MEM, GAME_ERROR, "Misaligned DMA destination address: 0x%08X", currentDma->nextDest); + } + GBADMASchedule(gba, dma, currentDma); } // If the DMA has already occurred, this value might have changed since the function started From 51af2c3af2f8c6b537bd887615b4b387506f4d65 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 24 Dec 2017 17:05:12 -0800 Subject: [PATCH 079/152] GBA Video: Fix force-alignment on 256 color linear objs --- .../obj/unaligned-256-linear/baseline_0000.png | Bin 0 -> 23051 bytes .../obj/unaligned-256-linear/baseline_0001.png | Bin 0 -> 22852 bytes .../obj/unaligned-256-linear/baseline_0002.png | Bin 0 -> 22080 bytes .../obj/unaligned-256-linear/baseline_0003.png | Bin 0 -> 21873 bytes .../obj/unaligned-256-linear/baseline_0004.png | Bin 0 -> 21873 bytes .../obj/unaligned-256-linear/baseline_0005.png | Bin 0 -> 19238 bytes cinema/gba/obj/unaligned-256-linear/test.mvl | Bin 0 -> 96840 bytes src/gba/renderers/software-obj.c | 3 ++- 8 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 cinema/gba/obj/unaligned-256-linear/baseline_0000.png create mode 100644 cinema/gba/obj/unaligned-256-linear/baseline_0001.png create mode 100644 cinema/gba/obj/unaligned-256-linear/baseline_0002.png create mode 100644 cinema/gba/obj/unaligned-256-linear/baseline_0003.png create mode 100644 cinema/gba/obj/unaligned-256-linear/baseline_0004.png create mode 100644 cinema/gba/obj/unaligned-256-linear/baseline_0005.png create mode 100644 cinema/gba/obj/unaligned-256-linear/test.mvl diff --git a/cinema/gba/obj/unaligned-256-linear/baseline_0000.png b/cinema/gba/obj/unaligned-256-linear/baseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..4be2744f72ca0f1ab799dea2fc3bb1a54810cfc4 GIT binary patch literal 23051 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

Th?LswHCOKk&0)$2PHHPr4fKI6vLnBt= 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 O0000-Sxp;io3fz2mN@zAG^tBbLGlR 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 Z{||X&TA4k~C;I>Z002ovPDHLkV1g^@d<_5q literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/unaligned-256-linear/baseline_0003.png b/cinema/gba/obj/unaligned-256-linear/baseline_0003.png new file mode 100644 index 0000000000000000000000000000000000000000..04451274afaa9a1566353d37f31bc10ede810128 GIT binary patch literal 21873 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 literal 0 HcmV?d00001 diff --git a/cinema/gba/obj/unaligned-256-linear/test.mvl b/cinema/gba/obj/unaligned-256-linear/test.mvl new file mode 100644 index 0000000000000000000000000000000000000000..6256de8fd779667d59b57522cd63d33e13bf7e84 GIT binary patch literal 96840 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


2{%V#uJwODF_-V>=rKG(;D7@ zWpH#?@j8beLU^bd_x4ON)X2sLZ?CA6(K#C)$^fg27I`5Ke5YEdjR;?)2*jMRK1VC8+|^x6X*?w55Gg85vqd)d57$koETP@`p(e?jf3+Q#XdoXiDa4NiAH~} zySpq3!P}R^&azha_1z2FY6G85xp1C3H=b0gVt%wA1>m?+VA?T4-IO~~cN9BPYPqsX zSam?)=$9<(jb`O|pZ>1WODtQg zIy?z<-q91d{c$xc*dH5Xe%u#+rI|i_#D#7iIs2Yhvl!I|wW_}?x_qT7pNlvOWsBl( z4BcKZyFnipd_x#Jbw2ykoQoCax$%dd;i-ee_<4S+skun}r05}0F@voK<{|}j^GYxS z!+Rs<+d+#32A)kX`~^xdgLx?;y}&JRV9>xFUoP;(zmNG+`|b=8&bTIYP>U16@6CSW z<)Ti8_%qm_cEJC85z>$T>mE)R|EEi)rt6O@?`z+TlWETFftyybn@NYa<(`qA`%99K z!`7aU=I0e?|C<{BM|OqYFYD{>QIYLej%1;S=4a+_6#7eQ{&tDON$~YYKTF^SkQwi5 z>=qa120LBj*0wJOG3ugJhc?I7rV6|hvx}{3!b5HE8KK{4e<1sQ=+2^7y-^F5eiwbQ zeBm#jz;fJAE($1Q(pCA+ep1A6P?l;7ueuF0$hXOFp7le247`!>J6t31u4mzAk{VslAQ<-+v*C)joLLVB_$JWOzHg5Hu zYuB5mN@$p!B0X9m{rI4rr(Or7?610B9CmBV31rspD_452qh4QC*zH5#Dw6pv;g`mw zg6in;=+LK7UNEQpWU6V7Y(ZIx-+QM#bko8*G7;amE{`9y@~Gr!Sg@LX+<~umH9e}| z`>uQ17si-Bb3~7mQ~nm=!$q*_8?c|`ucpMEpJ_EcT+)Z|RY4w&m?v?zcvFc;=GND^ z%LCQ>#k{c$d%R$0h0m$p$!eX?X|+kR72BD0@j11{9MJgTuX`RLwf0+2??bRZvdmX! zlmiJ_UA9yU7b)hPW<6N1K-EWG&-=5&1#$RYUVc&g4hVtw3`ea05moWPH}p#4^yI{% z@k4q~)c-wd@sKmtzjQ25Ezt6gI#RItQo=TSg5sY4UR{^~@Bk)e0y{x7m!Pa2 zy`oeD(C9P&*8BLH_p$On0|Fch&eSUMmw^#?!85=Q#jmCPOJEXaI)#7b%P6UTHJGF% z^!}YPAHpZZZP^*H`c9G#Wem7cCh-f{3bp5d+HO4X#Pq=`F|sQprtfK&y+^I`dG`kn z;!f~m95%-vd-COu>+9=`Da`Lwb7sguhMgc&H_-8w+!#249Q-F$@e6n1o4v_n5 zI}3aXE|Sdik3%X|Czx7S?i2c=xE1sUetnLT z00rh}<-Xql<8sqUzFv>>{9kTf4Lkgay`N5Ao5J7nVuQe_G{3(n{;c4oEZ6I7cd#t< zbnpi;U#H=(_k}tE*B~hYLPWTimk78i!kBSl6@F5`#*ex1-sgxS%e!{}No(!`FzN&U zYvkV&zXavKV0>qTR}b>a`!HSy_smm}L-{^_YJYh^Dt*mMzv|xSWs(6;dhs&PW=Q=9 zP~=ht-NizmQ5BGh{Y=uYwt*9Aq3qY$cX@9w)X1TfkazwTVqezJaWu@^pfPG;PC1fa zs!50bN277|l-uuA#?1C#8|IF)MP z{o1Kd0(@6&Cv#W%+L#Y6D_y*}Gw`vL`WnrLBz|8R--dlTS76tj;9Y`oGO-@it^oiAnRs9}FZ^TH=jdc7=q~KRf4ZaGhv47h zL0LGF!0$ZO1Iv!lU=lYk zqQs@GD5MDbgxu|l;)SXn3nmK={}Oqn)*;s2Roh|F1@ef0^zVEP`u{8eig)|MKB0uh{Ej;!Sg#@IfW7;Bvx=$E3sGzEwgk9d(tuVS9~FCXhufoYa=1Z@2k_X)_0R4K?YOM+AMqdW zi{3jJuf6Xzf}vv9wz*;niq4Rg~OnHYA$~Wm772s)6H{51wXUQA>x<24;^c~|IsoqRGGeum1VZnNar0zFSi73Cro3oO^n>$b-9*F$4ORuPaxGB%SwQG%&J?DyBnRz>}TgXazb&q&~RlBTvcZ0ODWC1OR$mZ}is8aS5^Jk98fn%I6c zO8^QYk?iosX@5mAAx;u^fBnAeKSL#ing_v+Q~$c;9lVpQ6a0=_=jWpH{xAL2cc+Z| zad}q${lh?^uan0zp|1_82lV~BxY9A;LB`8{-1sX|ilEDG4SL0>rm;T_3MS;4mb^T%2Xue+8+IH1;mHz?XMXUyh=XhFB2Xwqn25#S$>b9 z(d*95(0;2IUuY`sKlZ3eFMdbSmSdR4zheI(G{l7aKgE*!DJ*`=TBKLwW|HBuN0wZ^ z7n?R2*Vj*(G^r+O(T}I{5{jp~cs49XgeDoPZ`De(NNe2UV?j!*4E9h-vuGQYwvU-K z_nPemCdba{4@K|x3`Iq#h(yvePSACBKB#miuWe3`S_CJmH7Y6H0<_mQhp|nknQecU zO;l`RN@bW+B8d7QugxaUOx4B`Gm&B zZ)1?8;109@$Z`@(?N;z$F_IWa#5PM9p7vW&rW{{^PP~^Mhb9_Hqllrh!!9~3wrJWH zl6*}Zd7p#t!KE0;x%|YfplzdSBgnR}!_FGJBZ9nt$1Xhjf|MSybJ8j;9b0E!;(cc? zbunRYH%PG}JX!T+SUZ(6^cAf9#?e$?Ngl?IQuuBgW+H=!9wUG`fk>pd|=1W`T{A z3*wT$XxUCMNgicOsTlVW-cpcNs%Ct}O?fH^a7-=AoN|hk9BlTTvGo%3GKlUR80DvC ze|bV(-&BwMfWk5{%6=&}>H`TC^gUJNjMASi7ae=}6^eO(0+p@hN7Lk&}s zln9fM+$yfo1aU?TGj9)H*Qf|TM9N^XgS#YaQdn8w-lLV}%w29%!vkphuKdsTxG;W- znJsrpu;`heWv&?@DDMae53VhB*g7&sRwv`@FQi;RB7VewhOXVsl~qd}@}$PTz-M|A z1p9;8xda=F1t~$oloe83JsFS-K!+Km%7pfllaDM&q0jQvn?7D88yN-ZJn_MEbWR{A z-D3b8GxDP};%M(HhSL9>F_Add{+7b&Hyp@O$-slk(t{rq&io4^yZd9kzwuwOF11P! zxCM=If=c9T&L6p>e}Os;^e*xURzKNJJ&K+dQ14Y3>hXK*eUrI2GP%MQBiTa3)^PN5 zZ~LFaicuO z)nTKxCh+4EC*4>}#ZPzr72)RTXUWSm&FGxGaL+y(F~Ln;#;pzwuS%eAQLAs-2)%%( zuducyVQeo2T4)3U!`;%4@`zI^G5E|fje9fK7gs*sD58b#TB{EGvnn#w)LNw(iGRR@ zH1ps_A>0baIn+if(OA-2CrSc`xxlOEhcFyzw+ws1IW(#Kj#y*ABa{naNkj*>7;MTAwYtiAo z_pG^GCR#j{avypEawLnAAZij)f#Es60ZToipV(6zCL**~EMt8vFu|(n$kdcKO!uDb zG$Euzi92#Tp4mqQO7$`UzxtS95bC(10E6Kw~KtAlF0Z8w*g#bBz7i z|J{fw9jnjQJJsrKtn{;=u)pX}np!vG_;>RvDINm1b~znJjlS_*Qe$OqC4T^smwuWn z4k!tGYA7+LhK&`1AE^C9{|k4~edR{K3;mhvoVO7(JeC8lND}dA6wU$0h=J;mfP#K~ zUJ^wcYos?jN09%P9KJ3jmBu6D7pc^rl}gkXUM=mMef$&IM&{IW2+O^xp4m%MLa6*= zUzmRTXpOA=BtVK9<4=gRY+u8=*m_Nx8$qW=S`tza1P~O#z7&8fFPG+|0Y*O7l7La^+J6Pys+b%p!Y$4Q0|G>!El2-9!?1;jAisr^cy zt{AnRctvBJL`Li}h4^r6Bh)FbD67h%Zi{j7M2ms7sITjuokc@*{ zs(L=l>i8ub0P1QT(V#4{lAIjk`P`gC`gM53d3X>~6}(dl04>mgD6>e$2RK)+0Imdc z%p$B3QXAF`Zg)l{yUXliAFU{0sS6uw%+tYd740531?LlbF65q62i8!}GguL4Ij{P> zq}~_jUfJeX;wq(3=&3jL1V+skh|gYY4uY9)Gvmgb7hJU~*l|0Ll<^xZbMD>OAdCIy z(tJ4WJf0-?0C$Cq#P4nF;zlQlOP6D-0I@HS+mBBm-;ni)UdyK~Q@xelIbZ7Fj^8|f zf4?$Gtrd39SC|s2?D@Mun|D3qL3z=%F45HB3EJ-H9Ywmc=>v6ix+-CXi9ouN*m0!( z6W~rh=8GV)W!EE!+Wz znWkW=1A`-xG<-0e*FJmC4Ii=GeIORJ4nKwQyN4~=M`Y=o>uzAUwt@)?Bm|?>m&XEP zR}u^ReOJP`lPN1@7-uoN z;mS4_84&tW2MbQ%G6$#zP6$X;KBWz&wn=rPEm^mKNKR|FjqifCb~{s3pM$f*@C zDtnGfg0|XN;`jf9vyK0=-8tiLAFzrLdDi1N`sq32leIqb$U+>NhuL8zOI6M{NRvmm zaIRp@;xBdiNyrZu>9qfE$5c<@YM)ngsQ@~2iuL?F*D94woz^ae-d;QQ(51}LdKbMCAzvO3lw6;Q5N1R&aE?AXV;ts0UDeU1D!D>1` zN2R*q)6DGDmK~LJ7Y~e88$6(0yR@TzWbs+neQB)?2$n3^Nynb;@H5V$+;`YPZ?y#=bgwmoI@pLD@N-7;lhHkegi7h85-o>r!Pi zG-$bgm1!pRU0wAYIj6UG(QD0sew8lc^$u=QBRZ4ZSZ`3Dan_+z0pORnFzl$$=!T`R zVIO=A`XjXJ?wegEoyStpgwq!5JVZ@SY6!tZ{Xg@t9LzU%I(fD@8R*L(@Y4srF0vKu zbCUYSbCB5T>hRl^VGnl*8GFa68Z?>eI`CR_pPKYvbaqX;4f6OVUef~F{(1mhC)GaT zd|A*bSFlOE{htYRPMRYg+79J%i;rEA+T4aFgvx;}^(UpA8EJHv(&}hbD=Z!*ml$bv zOv=qdhmf+O6!pcHgDiM{cwCVX*7F7NR66oa+YO0h-fno+qx9=H;K1EDuA|d5Rapzg z+(2#~U-I^3T~;q&=2YYq_SkmPoWIp5c@~MohH_RnXQ-u8tdyuLt379wCtTzL>f7d|u?X-Ex=x4I_`Ra>Edn7e}p*%rp^p0|? z9j0HW2zwOh1{mWr7yaI#x3#~%Xomau^u&=0(tpX{zsq+F*`MIh^onU$w@z7)KKFWg ziAG=)Y0H-_W3>2w3HoIVad?w~l^CC6yx=<1S`z@y8p%4fI7Q@3@{mQFdp^J>^=1`8 zij3heD^?BDJ*zjgHIq%`&7ZweKWR})zSU$YKm*TLzVxn-e^F-s+~qbKg!iwzDjgynq@*!k;TD;DZZg{#XDy4@Xxqr;Ud0l4} z`e>G^mwEe~^EfJgz>#ta#Sr0MQvU~*!AcjNr(shYpWtkvS^Jeb{c;M$G{gF|T?0;R zX6br&2af7lik&;E$D+6ZadVA~X)Z2EiN-SMNgz=@t!dQJr`Th%Mnc#F{1gFU8CA7Beo6bKb=O>cI2p{rz zDI6V;rN5Y-CwL^%cQ8Td+!J9UQ}iJf%4%9{OI;s$A1J&KOM8;5$-{y`hF!oFL6MA9 ze&#?Rd4ch@n>Z)hl}$DZ>4?@<(4FewA3&aX{YNDo*S8ZRwkUVO6^9rPzq?*FlyT9$?OA;IVin|K z=l7)={m_@ls!Wn5Y&#%7i8(BAxuUFq9)}3uI&{E|eh`G5PBVXYecGw}(v#@PuUpsA zb+bOdQ?^*cq3?5gp(Pf=Dn%}FD+7Y#TR2TVUCV8vBvSs77S_Jg9#)7RmVnJRHjNp^ zByDA#aBn>*?V`_g#{(*poXg|D%z?^b9&(xc1y9x|Q=YLuGW<;A>S0YS-d28y+hOFC zki}|S#U}SBRexXO;UTjCF_AjP5;TQ;0kO2ot)|}kno4mB1BGkUBA)-Xgz5c5cJMe^ zfZBcEAZm2Hnk%T2t!?-c{7WZU1uGV*xwFz#X7(flbNcw^U4E*LTL0)g2{3brLan}r zeMc#wnIWS=ilU{y#7oA+QW}!6K`rw5-b>5V>sd-U|7s-hhY}|Rcp4g1v*z;RSR#so za2O6?Du<)`N)JC2Yl&)nnKpR|q#P8InIC_O&r~Z1&Iw;8YDzXTA(DBRmd=n|UQb3) z+96g2#7fBzJ&@8e6|`sJbil5CyW->}!bWSkIMH&`W=|{Ns>QXBeNl>fvL%BxEzvS_ zI~75zcF}@kQy;qP3hpE_$~iIW{ujFIr1f6*Kk2}l6A(726-2ODc+i7 zhFSPXZeg(I>YpE(iI$jfkh)Blj+|2ma;@Q)EKwSok&)_3>BYA8)^dUEttPA*ZkuMH zEKw@wLWJdhrsBVjx~9{B%1R$CFOf4U@(+Hm{p2s(ZdF*O@h**P!b` zx%6DnmsQF5$0UWtm2c9F=b-iBMiQF3@9phvdax69l}v*G&)um@#T>E~Y?2D;M>ci- z(Vgkmy>~0Qy`E~i-e0uG-~Z~KLzi;CqR}lEj{UWMp|EDOHj0lRnY3Cy znGXoDUOqvUYaiIxY&r1YAgZahVNs9Oud1$Yczm>8K1fM9VZY3r`nTljH3X19G?36w z%0RH98BLgU(%mc^tacGVqgqnoa_r=0$CtSi)W|>;gZnQ zU=xy#Fh!5XJn3fO(GFRTgfu?O)ACBz`9oC>QFS%{&l-$ zr0ikxZL8)tMj*WkrZ?AYjWrLkMgh9cB*Qki-PR##LM~zXU*jsIcp`d)jed-ZRk=vJ zc5G$Dg-Bf=F<7Kb=zelaxmqccb{hPQNn}usO#X0sSH76$4?W?DyQYaCsS>B!iXuZ( z(!OeT;p2Y>7lP^&X1iRISW5QyjnQv#=_w?>rsWmdX7PKBQh2AgU`fuP3~IHz+)DGV z66J34yKh~L54dXZEO8Gy9Cd+4bYwQnimF9)`5A<;WVFl4ZVz~gyS3Fne@^I%7Aoqz zM?%48{Z5R-k|;7r*)vxd)BOpfim9`O{uF~nm$9!cE_0SFv zBDtVVmNbPV_)~Y0T!iokwcnF8FTM$68{jvO$}e9Yot%uh1`|xb_C#8YQD_Rjh=ouj z6c;QjgIpFLY4Tt?a_orcbN9o-;%8)4mHsbuY`5>^HauAi`k^LyGV1_2AT>$~MEyRq zM-u>b5EL;aEXizzbK)=Dn+)Fu7X0MtqZ8$}hO{*8T(Db>fT>!jQ(&NR@*eZ)S1{!X zoi7sg1Q?z&x>v#YGXgVOpM@w61`>Rff5hXLjIqiTCY{&q8S8+ic`fe8f#vo`i=H4> zY$6sTHY9`WW{w0aiOuL9VKCu_%yY76=4A zlEB4zd){d$Ewrs>;0mp#ZTU5-*uEcGxas-|?`)SO&fW>u2IWOE3I73J=tx!N5?)S= zo!zL}Q2#zZs@7pE4{5t#6EM&ye}bbK94!T3*(LOj-xtKgn3PFBq-;3AkxYx@afH+Le>3;u{7d&ek=9 zV#Xu}a&VkB+J};;WF+O3QXMkA_|5mo=N*XRLO;ox+JTeWq{T6XjiAyCVi~!3KY#9OL5u)5+lt`Zja^FNI7fc+nP52^7*HA93gWFDO3;r}mOH zr!teF{(VOTUxq>2xKCl@3kUQ(hok7(-meYJqh`vA&E#skC3V`JIcK_RW|r?Z2TA5I zj5eCbAG(1Sd;#h~$`a>))?Hc7Nrq@lr{@qH9t!X8yY-Vxv-}jjKpO_sg{t_MInI&{ z;_W&j&#|>!^qqX6LCm)v`E@hIzLOrvVc3*|lm}Kac=B_bKk~{u6G=g%LkxA6Cg!z< zeu(X&fK;Ny%t!*3`mRM0eqn$}_M(=ORtpom9~;_@5RxEBg1Wi~O5!pqCjZFW4Sp3^ z6L7so^!u0e+jlwmdAAv4O~Rr2QhLr4&Hs9B?}h5StB1ske{^d&a4GA`sBc`X;50(ijxXKIv%VBhddRrxDR-_|K#U zcdyx6i=~eB3!`SIQO)YdKYxMFy**Tc8Z(_kBGZxRhvPYe48MsM^_I(>*CBJN+Q!~IiFMMrf{NRxr>DT8y4>cIu}j)EOUvAJql}ydmnrPaCve1`&2{phLYoD$ z&_Z1&cCPSD4{mDTZn4|G>pOJYUzF;ul(ntxf8pf^jCwtJon)i0R8+|9P8S@sgZAR|ErDWejJh%tN&7d zMBLwwMQesk1ML7ZT8^zfSs_pOU|xN_h>7Xe;Tk?gzKr|`Xi|yw0{{bTt+;7^7|8s-? zZEv$pz-{Ap8vx$J7n7e>v=ozNp!1Ql|noxf?CGiF|8O2av-JJgjogCC2MBy7;G<|tzsP`L*?IRM(W#_FW zy%YTo@n4i#>YorB)Ej-s1l4WQEQRoFk*5q}Blol($Kc+D2=QmSl?yd@#EEKH4|MJK2_I zRcX@Jrn3DRoVnLj)*L$iQu47n>rHf;xbVv)+dcmH ztI}{SJV0r?dTlA~jhoo=$7OkB!*V{6GT1#n^+A9VKu>Q3PLkI~e8+a-E7{ zj1-!3(u5U5@6q>+UQqPf9Y1%z9iIqpO?=p#c*P48 z?cAcHJRBjLsMtpFREyyaXi7Taz+I%^n-4sU7_G_5Z<{hDK6}Kn)zG!_gUd;gj-?IC z%{9x-^YU7%hSJ1yO`PI?&JFgk<2{L;mbyVPJWF{CCo7g@^4I=3)z}_Em#&*t-#&Y0 zh?aVO)00cM0E-V|6vaA`t4!_Zmbmf4*CwT?$1;ose_xz=Cwl<#pO(bunY$AsE1;gq z;cm-~v&i;+h(P}vgl+6JHH$AU<|U9d;FEb6k1Wh)zs`0+AOYwj=`6G0rMtjMDu6w{ zVbI**o4)amT|8gQs6=i#56C$J`27|^V0E#3miXJ^jLVyJ9()>XsnLJ)^RLz4`^s3* zCbL$!gzns3P5!}~PW>v(9WhiC2*h^HsTC+WMh7VPTHzTiyA4$vP>Vh-Qvh5`;JyGE zHv&0sKc6bw@Smi*c5yr%XQShV4>;AH+#poBlL|E}X5_ik-wL2ypuMmgMWe-P6&OH% zX((Ha6lN9dVj)D=E3iY=3W!t4TyR1bPg>v1mn91JFeoQ?m`2J{^TSufU@AhqnZx1{ zRq842b3r6%*&ynLj~^vj5b#jM`U} z-z9FQ*pl(TT&5>w=ZXLZ*gzR(nFt|dWzJy_NUz#5BI1JAbZmy@142WJ7Y+k%WqqYt zmRkHEQ-~(iV5P@S7<#?+6I^b=<)6{|RL2Dpud$cw3|aMXs!j&f<`4?VlImP2s2LkO z5DDMJZ`bp!(RL%JcD2Gw@2=u|8 zJjf}rD6g+VXQ<9G6?R^bdIax|1*q`{dk4d`q_D#I9HSHov9SnRQN{8ATmu*IVS9L` zNy%GiRtLIpS^jaQ?!U&Jk9(31;v;ceLhWdydzpzLUrb%lB3WdQeIxH0B5`?J%NZFN zXV&}+d1%U!h>!4OLw^}#GyKr%luXg zE2kotOWU&*%F^Tu;P&so%N|;x&B`LbWYB&`oO+eZdg8 z8^AhFf~kv%4oB}IL23aAJqX$NpM_&8`|pBuQK6~Qx4d{ZQr*>~h)Q2`HG%sEm7sBH z)abTuPbY+UXV}qU_XW&x7Ptcr1x9gZ%fxXQ2fMN_UwV)&)TjG9rmK$l)*EimGjTv^ zm={8gkyFjwnsuhQ+Yz#-9IZo#Gs0|V)DZ`7`7BV|%p3~g4uxT4YlC2BZ1Le5_&3fW z`NfA&QLYj!xs@z`ECq+)b)=(2O@|)g5D>*EgG}QVc~~kI=mH!-Z;)~dk^jQ~ZZR*( zZYZy8l(CiQ!(g*^GuGmIxWZw~P74Z56+ex>7oTya-6t&tQ_VOV>?)9XVR;kJ7!M$? zwBgprreg6*g?L_#H)%f68WxB5v8X!CLIK3KhG%5}vN|^_SM_W%8o$Y^DdR;F;eNG! zW(+UYFw8c{MPRmI$-nVYTf6=eZ~hT-TC}5Xkf^VvZD`W~7al*?pUVo<5>f*S8P+BV zOH@}^7go?#6B1HCR4kstP;GA)x(cRPUFKm$-ozdQaR*K8p^Ho>!$uSb)sn#iqWcpT zJb-8tbuUKP%;u*?f;3LrstV2$6m4{TbbKIgJwk#H-IxJR?LPnyE(Hla+ld-kKRKc? zeM7k%N@;G8lOYf7wHY%%fEEVs%!i(W3x*X-Vq(807#E~WK#AW1DVN43C(10RwQP0t z2jAI9@<#;vtMf+jxIfwZG)9zGZoj*Y4CRT8H8B89Bf-x#(}zvZ!PJM9B%oTLuRMuP zqcek`PY;v4;ij>ER?pA?lrs`3`w^A2YR62t1Q1~jo^HyjfjeLKU8+beh}NSJ8s6M= zl7m{048)q9m7Q{9ywPD;%&4pbjlJ zY=8sGxz4NI1`n~q(;33rh)0YL{sAJC3*1FAj(f!KZWUUAWJ~X5jiYHvq_aS_!s(d% z&^5bKi6_{zhU{^gQW6}m*9PMLqB0Za15IM>92#T*47aI3Xr%13@}H`!zr>mw60sG@ zd1Px6CtP(0x?JB}+2(gXBiV2FI3GVQ2ljrw|A`b|s9mR{BAZlweoj_l`x2=d{#k4cB5HfP@`#STzS%Olknzg-ma1@< zk7>I*DGJz01i53tyUAoAW&-f)+j~jd<(MI)#*W=F4zZkgR$Vf;Wo;VXhA|xIT=txL*h9iUC`M;o*zILbK4tU$z zwLLMNy1mYiYL@M~pCYnS@awj`9{<&3r35#v_0s%jaTcMMlDAnVUjS)HvB-H}qgj?m;FHCKhNBIc0w*kju(2nXaFTiw&U&oR7NU}1!O0hx~3TU-YyO{53* zb}+A?J$6&H#4M>;H8GG&|YWd!_Q- zE&Jwz?*$I=?!h~8iWamRB;x*(4;$=7?GSi|>~>N&I=w;H^mDqRrttjBlJSo3;Ip0} zW1CN%`!cj4ztd>ih@F?7w{A-7O!{$3{dE&j)z>hkD0pk2+b{3V9Q-`AENVEhY6rYc z(Lb@=d0)_ZKi7mSm}``|ogKV=_fSe)1L~bfiUQX&@g))#FXs+Ywb*+ zn#lTXPZ(t~ErSTC5f#x!jWUP|rc~NS+aY$N;z$&=QBiTg#t7jAqM)LN7L|6QKvdc> zjS~uyN>myYB_OTf04Wp*h?0^3sU(%Ecj*4^TlcPa?|ScB>#?#(s8pRCDu=W8{{0UX zb4_tRHRy2j$Du_om0N@Q{08}6ec($y8GB&8Jf(}?dgtDo!ip z5`Bb<-JAGswhCL`9pB3vQ+SA*;c@U<^8w*n=P|M|Bi;q9 z?>OL+GBfHYkG(fXGAU!Thjnb3U0>&TP3OLbf87#AW&1-OT|4=MZ&69fA6fU$EMM*{ z`(g7uhsUYoxfkmkZ*{oAYn%4W2>6~1cpi7ob?(~O#pj2_jY!=4$Bkzd+oH3*suZj5 zr7M1Ve7)g)@Yq8xPbZx@v1rHs!46hA-rMLXT@~ZC#;8I4bsK$(S8CDd*p{DeD)ukvos&s5b_W;YD>-A78yDdeta+ zt=Cgi?^yqS=rUK*v1pLnDl5-I*48}a_{{OkT+G6IKb=TR*|FZ`9a1n zyYj=J&4r(u^;@pdhRU(s`TAj*1C!Mg9kx$>vip_Oof`eFlvj@u$Up9tYU`55z81do z=s8*0FzI{at#*as%*ktSZSM>eth`c^{-X2gir~lB&NnMZE-N}%v3&3^;>`00I-HZQ zo}0R7UUgnwS*-34f!iRDg1obtym?jkhcB%xx;pw*;MVA=!ItHk??0_^KUwKkxF>7z zeo^fBusLtiHk9j(>hv#5t{t1a7gV?m{p#Iuo$7g?a?t9q1~Z(-wOE8-A|!6+%xkZ@Qr-cf!v&M72;~-zB##g=YzdB zmX9}L50Y9vkmzp*zgayN3)Q#Q=&JHHsG~WnhhF{V-kqe6eXBnde)VnOBu%{Yi%I5t z^D~{$Zs@5)j7krlFsaLt1DJ`Yp3-&Pl#l8RI%-O>>4)a?@ousMZCxhqbKjTw%$XOB z)!HU7-K^|t^r+LSGXO_~aYfWw0fm1mY|B`Hp?r`w-pL{nE7nl@#9 zDmUo|f)?V_`#OG+G^LqdH)^upvFYWy89n)&ffK%z6`f^pzb@@%!MP-_<72vzhrP5J z>@v~oV!FyrA+tHgwwGNRWbfCMP4;i6R&?_V!3Vm^T&UjHd81}^^PA#xN#)$ALla+Cmd`{+^+ZjtEc#)+W3X<+t#$tF+feXH5MXYm0j9l z#UvbJ!GvCj1&d{48=m1vxQ%yUvcU#|(zX{3$?u z^v@}RhnC|~w0bSVjLN9kMmG!QKA&}#v!B=VrskzxLwoZS;O#x(EmfD#Hlq#grRL(% zO>OZp1)0I+jt9zg^vOnljww{jD8Be>H2@KqZs-B-$cf5@@f{&)cCy(6;qfbW4?*`a zW{jdw>ScfUf7M#;G3IIwWi_|PSE**`o(HNPT@W)nH>M#R2EXG}h+eUGNcffv*)=@j z2)(5O?AYhI2(gR3G!LYn`-nudmsDy-=!!X<*a?3=34YkSm|iZj6lPcH`P9)nD)eR9qs7Di zG=iAi&E48z!w+L;$k3suKm!i;QjAsRbL@_##ee;>f7o1JMz%M{ITxAORr5|9R6yS z^Q&dUZrqF5IdFPj>#nW~nqOo|H_UG{HKs6Cnd)t!p7;9MN>RPG(H~V;cQt8K^eHIF zt7bg6Co`C@x3^!3Ep6f;tYdN|2hkt- zx)o{PW2%mR*9C4|-FL6C%w>MO{_cRXMvdHjKDl~ZvFwi=*`=xwwDWkq+2dMUX-n&F zp&>@p`(V5NWS`Mg!mJ%B7(^`Xt&J-8O`+V=OT=L;s30G8V56FkMwEAG|ZilR>{c@Ll)9)%X~0W8vI3BIGy`k&2NEiwV$Iq`N%jG2X%;qkN!fWFG&gkez7b!d_F@<=WT@J)7C0*FA&vPY*5b{Fp4-e6=># zwa3#|bhFo0-?3yN#t%HoTa~8@YR^W7SnuMIJ-bKynXio<64RJNC>Z#F8+=U=k_z>BT^q~Cm{HWulbW+(b^pL=HpY)T%FD=?I^Hbg{x-fVuzCJnUbX(+wjkrsR9<-=Kl$8!)#p)0(c3LO(;P(4 zW}z7$%0)Lh@HY$t-6y_Bxq1Ah?Y9GGy^nntZRYXgdZyWmKFvfU3NuZ~C97^ob;%8Z zUZS!zUT;2s<%RqFqrj>aX zpTcEf^FMQn<}|6JA2ZG*Y71^`C_=W~mNP+!#a^DmgZETL&S0{gV(R zIy0Vxl*LRY)Yx}T>c?UbMrOnwG#@f_)45*sZCq;0EF}alvi14^9JQwJFDL>pXA7B3 zFB+2|1E~2ac&R#}Hr{F-6%P5geC30{iof%0wD!?~XY2i~-g%=^S z0c&#`1W`()TFfjYLTtdwG&zv7nCWmAKw7o6Wsl;Fx-6_{#Do25_s0=08bybeM z6@yXd5f*?49bqwJhlBO+nK?dP%l0R;oyg);#EH7( zMg=;P#S&~78S-6~DRwgnte6O#!=kGl=nV)NsQ?=<$iY1pGmyYobeIi*XMPa@Ea}${ z^t2QLwPmEPE#@n>RB5}e!j~|}(!fm?vz>^t1FLMn84mSA0fcfnvgR?cp*RvOl+5ub z1D!~{7&wuoM-eW>0*LQNEi=gbHi*UzsEEh8@Gx2 z{*m3jZRW3!qRUQAQ#imJ%{`n zHKYq0KrHR6^%z(-lUQmk_)S_if;b?SjV5P{dS0;vW64JfkFEmlqm7!#<^R6Su0R>l z>rpg)-Lc!E(s9=C0m5~anKOr53xBMX_;787J1e!Ra_bgfAFhpXb7j@c=)v7dK3qGY zuu?0LTei6S$gG7ME3eIrcJJPj6_p{kZ3*_lINje=#?6ZM?w+1ie;~}Qds5|NZB}O6 z#_#8T)}HfN$&UdVsvZh#sKziQt3f>tHLwnu&+G1OY&$evQ;3W%0zX#J&f0{q<~cWWt=MYI1|?+MBA{L=)%WOM32&XOkp=` zV?%Fb3>1|d+5hpZ_ZD6A`F}L^Vy~fJQ-9+p&B0WzP(Ej(;Wd6gU%#*V742_IgZ|Zi z+ryabEc$lgH~MM2mtp+*M|q}v)wFh3j|pAby$_-Anr<)i67df3qVo8&;E`pnM$t+r zgHEE=J7$QK8+M}hf~&Ca>g@%!ngKoIzSlke_(b$Gr&e3HtU&c+d*nc4m>aiiw^T_ zz4^aL>RNM&zQwG95@-wN6H&RoebR=J+IU$rV@vTR*s_42!Ao?*l-lUaapSBh; z?~ZoU1HI^YC1XiuUOs4EQ(t8Y-9lnkAdx_A7{lFedXX2cRjR$IRholl|JP6(;6(3C zncGS3($CU06SUgv&4>*>3kS&)VJ;r%ragiwU?_ zo#O;&a%3$}4phhW5nD)AD&k3HD8TkyIVNKP2lDKOL*~$cR&2{VkSP@UAX18{)_?eZpudA>x*taWCj zxGG-*2+i|f_3X*G%7$5m3(%2T@J5VC+d^6onloS@Gq#Yx67XJdEtr!Vgv=x>x*S*8 zf`e(`K8tZ7P-`Gv(M=EXqR|f18Jzvp-4yuqGm`+Sk24jnU{)NLgNrQ2fsk1O8yj#U z9nQP5iv|(SnHt{2TxBue?xVPFrVzhprU*|$W(!7>5_hV=nN;V&uCdQd)N%@%u>rIt z!Leq#Hh0rSwp5&w^P68^{Zc|2hyzz@p%OhcLd%I)U<0WiaVAzS2Wc#3AHlU@cGxiA zIWxXQyalt4K+k=vg<`FqtKlou3zeMp3e2AB^*ChC_}F5G&BmuPbBM&n1U~>*J3({9 zIX6;^UT?;n=wc-{sDGx9bRyL(#-8Ay)Crp{~X?}s~e zUWw-6d^r9bXuSfCi~t9Evn{2SfI(zb`7=|YHvuf@VR(E#&hL>hL8P?2(iA&{00Zce zna!rh4)ka%dK=ER0^@9$1y=MIJl>9WuI;8*c+qMFGl+~3W4F_gH*DIC5S$*XMYkz1 zD=J=$@Rewc0&}HMF?Kf%9OFP+$b7UG0(hb)8mXQ3$#9IKaSH5gTfGFkk%qir(?8=_ zSacJ5vl+9brAqZMs>+@6gi@)1>elE+ordZrM&>FJm6&lQWY$do+0vPD^+3AFfgW!~ zKkP9JiHNbJ2dRWg%0GHiAL~s(pE_$2$`dHjk9J@Wp6oO?+tbtAM!LbM>)&lj^=%?pK66%%n zjjw4|^gCv|x$7P;>CmNO(@Ja8J$mmtu3&zSw6j9cUBbk?O3qpRp|^JZf6lOvbZY$C zrvlmG`4s8zq!(C|-gtNB|FyLKHhA*Y^uFwy z2s$gOD_USmbyM9-5`79dFymcU?|J`4`mLq)5z!^@Zhwi!?#V|b%9yvw(%5$oQ$8tn zsQNxe>!nHN>ZD=Ci85gL91V{(S4MsbdpF@>@(uZRZ8XE(gT|H~t^d;4sXDp?mJm`_ zt*C%MpszylH`_HQOY0?{l51e&>cS(i@f_Wgb*f+dWNId@UaS4QCrJ;lWx~_JK4>?6 zxzD`sT(>pxeOYOLbHP2P_qQACI%#d|9;Q^L;(odR(6mqgivS{ndZ~673D2zPBHXjy z(J#66O_!!st8L}ru*u$2Gw4rmhJQF&DvQW8yuCf+y?>%WR`=y%Y|^=UZ6ZMTMh;T_ zB9B6?Wns1ciGZ_iQ&wzfqrZxH7PYt4pCPX-`|I{YrN6rUWPIN<^)JX}$&7a#VQZ+o zTlKJ;`(AxzcNiyGa}sDPzvwV6Pix?|GMdoT;(HG@1@|?unl8g$$BO=qo1UqRosp<_ z+a;tMC$PoxZ(35bJksR@T4rT=90(iI9h2peK@G2*KQr2;`=_j&bh%T@Mjy|0k$`S<&LNJ08-`MHoNz40iu9Lky!5%pa$mQ9Fnxo=7(w zdTJC6hpIgJMPw>3c*|i^g7hUwo&gyEr{4<%bUseLH~ep&FwI)DeKuNkblt-%ueXTO zhKbH>>W|i2n2B9FW%dW<^X`78Iy*g4jcicZA=+*GN`E(U)--1{;SK8Xnce0=wU| zi)a1K6Z|$skLfx6dg*(O?gQMZ!tq70v4-k1)cD=axv#R%NkJ>%hOnMjHHDil-d7!n z`=Dc8_=pYm5^drclWGbZEOw&WiO^-tE?~dtY+{R+vD14kRaYRJP$5@pGuje489a~l z!=}}tm(+!qzUQ3r6Ad;}lV-oyP$BRIwW^hyd-4s>bMjS>Ae$%?KzDZo9yUz9pVu0Z zX}UA*>gx(aO6gH}Bxk5~XF-p*e^;O;F9p2^yD&W7vprBKzt1;|`fUwA_hoyyZkzus zwAQs;boEQER(A&;SL|geTKT6!{`h?VPLFdZ*Z7@l%cyuno!<5jODHs4a#kYi1Djv4Z-{Ht7qo!7m)HK)>4>P<*(f$U8TC@Alyhr?Gv0|!zYBcISGGh56voJqel5JHCQ zTFeif$n~33J`W}3qoSIa3oOQs$XVoW8U=Iu!(b{189f~8wP56`CUu1vOea^pa5u3x zJT(adaK1G&h=^6?=p(!cNCC7#MW7{22n5c^3(j0yrWn_r7^l_7_Opb_gd$9sF2^ty zvzp-8fyW6##=?qDAQGQjfHQKCmyW*nQ(ZKi#^a|)H6fK^z#}=&VKA5vZO@c`+q0Tr z=Z?~HVic$|4N{SDbDEf2EHII@d*N!TSDn(cgCR#?CKBiZ__&T>>>x%E?B+dZ|46Vy zaDfxOoJc(9tL3ayU?+|t%KzX70Yv>%7ZY=4w3f43fx$5#!EUA@&&0syg}??p@5csS zbQ})OSF0y}W0H>k$K_Z80THsepBxCB=~p-!x~2*FQw*G;(_&-+Xm(DnAZQMI;|w*qmXt`$Br ztq-E=`>jiyf^!v@2IFdLIuhp|%!XIIz=0!ma(6jx=T2oilOYn!l~kXFekVr3Oo1*` zy-dl8RWJj|;xtD0w3~*Re3b$WryxrC3Vlo(_GxwzV}t|&I;~|yu^lK$17}!FQa?;u zY{MitGl-JmJ*3k>)OC43Hdv2qWq3a|U_lP?gjTO_G0(H5I4|{VC37YXTxKy-2)zXp z=|IDn{!YnZ)4QSFfzUv00WPQYGlK{mv8I1^pxvyXae`hY(6!aPo5E$c=6*(HIJ98u zdG(`Gv6Fu@gNX#&mbql#O$Q-lwh~*dgcJkwrfhI+KCXhc%#TEDnOTHKBoS*=GJ#~` zu^hb$Iyx)*CqoQ?S-y-zvD2{pG^7gJ9wR-31VjC_0^ANJ&M1HKwlk?uL)xZ07%pabFt;c7dW9|SjXNDLSO&bf#SHYhN(8ca;rs>YJgK*d|qyWv^L zG~-%(`nCh@3wALtkq6*(8uNPP%i=yZ@s>{POf2qwGk^TU#`=*B#l01dNrsgr$*U2% z1XB9Kw$d&aS&o_ok~-fZV;I4qW3m6m-w@#{yfWf%4czxKI+JG9@)SPSNG zM6+hy0?8>O++}rp2`T6YK zM|pmm>481-zW8?SesoE#*?E+wuHD!3AJX~1xj{0&GjcW>WA#K-R{FbIV}F!a*uHOV z`|W*y+~-$MgZ%uX!7tcIS%{8>qmJPam!E&Bt^27!<=MXb|Ip59j-rX-=z$+U@I)Bg z3x<7l2kC4)!I{ui*YItFlSuxY*Rse6Z%1ggdCf413{whXN*b^)3xy!UioS`<&X4VjaIfq~@Z2=aRM*$sHBgFT6o@s2rDD2WLJ;N5Y$VurOxcPU z=K4m63v8)TO7#4Hub#9(38DJ`sGjELX@oH1rkt5$MUTU|=N)t*|EMRHiu}*&dC|~_ zt8%$oX%LRu(@mRWm`Y(cUFJpm_mlYl7xi4pAXXHN)Tv%D6{@Tul?^%XujOn|AZS~} zhQnsnrDrBPZ-Q&lual~OAi?EcNFGl70|`7RN!uZFXDoySUW6tUc#!-t@bPAnv7!vP zY?|OR_ZrTD6YmhJ2-i-X--J98gK@(^8nXVfoN>3JH{!Bv-##XIUK8?C45pCH1Qq>%@HoG<^m@&Di!0B-ebuKZ-@(+ErjYkWCSqF+fqm> zQ~wVku)*U`IBR5X)JG?>J`GrsdcR6jg*SoO(-ZnbH@dbT7DOqranVgktr*OhLGOM- z?{lCxS<%ioETp`OY&B2zqB|46G9Id%g{=OPuYAydE&1B*aDO%2mhc|1w8U+k%WQl5 z5p!%_J2~-9 zs?MxRXRgbqoqk$*TDzJO&8zHcOwylwH0A7#E{SRnc(XO6@A|YTQi-nF%9!^~{Y6AP z2x#POC-AyiIUz5HyKdryObIKKZeQK51R|#geYH&;Pd~0?^R(^Bcb%p6RA(wo{N|-FM%>Bq;`x!Q_PCbSxFm*;3`?i9y zA*3gW45+M@Soo<1pg!9rT?_dJ<(vwN_9MrYm?PPkie>Ld4l6NlQX*!aZ7o6GS~3ne z{}fR%iG*fg9v<)8EZs3uYm= z1RS%WH{pVj&HN1n$AS*S6LSc^(*)y9vMm{Voa@ufzo&p0Lwk0fUbT_nSkeJ+Vle4` zq}&p$$K$_kMk{bw$e5AJ7@bOxSC-5MTy>HtoJ>kBKp2rUm#mur5lBj`J+PH9V+mN< z(B&q8F%pDBaxEDbdvMBzhBc!j`vFhF3@M>ckLs(>CSq6N@hmFpa-DhFVA9Tl*=WzC z#lFV~Pd)cI;c@#7a;HeLj!Nv**QjqA3(9_SeBZV^Ch}APl^=-DQ{M zVJTdf6`Cd=P??z~A5xhqky}^JPot(~*GPT8-*BwNz#Y`)QTrl(!Eb^kA(;KTl*x_MWoaln>3#fh zyRM44rpRRZf)eW{mB?88@sbshQ)4(gh&r39Zq99BbZ88d6*djbJI}OMkhNx{bS6sY zsqWtTU|PQ@;;dG8_z=%-7V1<}csct%pLyB_%A$j7{Wv{_AwBc9kL?P4)|ia8jWM3H z7Imfaupm4Awc?!4$kp|Q2M+PjRNkJhOw;G&NLZ{7)QO@4Yc;!TG!tqH!((@%=kK-} z+ojwI3wx$n8IQ0;$9lifkBfejj4m4kA-~aD%=0lT_MP^Hd#OBDo`rJVGmjR=uC^3Sl<9ozH!Nv^c%tS|A}Kues91! ztx7OI3C1Dn?OjcNnp*S21tf&UGw-A%-EUC;Y}2Sdt3;g0&N&Co+p9Wg-WVz*6}S;m z2dz7ECKi}X0b~V?YzlR(eWO~npYgy^3(D|g4ESqg2{X)=8IAKZZR=etWNXTf;l@n`1hr7?uxgV?73zT0F3w||Kg$#`I@8?vp(BND3%(^{EwG58jmRk_n*JKKqYo}G0YfE- z6Z;Ro)j2^vgs(|l@BY8VjJ#NltF1Hy~;3`%M2iJPe(PI_3;9xZZ z1NreTv=SFsQ+qUr%z`;J=IkNVd@-0p!1G{H za2zk2VgpFTf}WsIfB#5Z4x>1QSuw^DC19`wI2#Ro50m#?GB%K`w`7*!D(8!)hYLxq z1vC8)tf!RDB}GI2e~IhiK0ag>TJoh?=>+9hJ&bhqrF@*|FAA+AHPMA$?3!|iAqOT< zHnf?S0DiNfxngi}KXOh9Q~R)6PiRw90Y%6O(M>b1i32nH36zl&@ zT?@?l|Ax9AX@Yfy`q&!tmBFN+6}Y({IjV#!Sw5z!!ylpBQ-LoLI-d+2Lc!G$0|-=q z(G=)O85FQ?k~5dQJ-)xNuzob9^Q0-JY{ z(qB=366RNB>)?7f{95!(LGW2pqjF9*4^5SAVTo=kqBx|3mEfeM@vtQtNIb9*oVPT7 zvP2z;S9ZL5OXK^kTYy7rB=3v<_j^d%DtpuNcWj@$EYa$^tWeKgF9iIYI4rcfbJrI} zd%f!HkX_r%?a%W2itf?7j4adgRh;7E6YF2+Y}-}Id;OZZo>z6ZH!yT)k@TJCu5F^3 zg~@Oi?1miff;b}EIf=n7Z}%;3T+~qU*2rs3cyKRzQI+(|M_&5sRs5D0{4eXbiH@1q zz`KW;jmP?Sg2x~4_6|u~^ak#3`(@3SkE!We?FhKh^u|uKofD_ekiMRg@_X~wx8|yi zd!+SUc72AIyx|)w@0xs*z($UAG6}ED|EJe*S!4WbowsRuDcDprvA(O~f$%jicMEAM z^rY!a>Ab2tNskV{t%!3rOkZ;|1#^2J`<}V@foXE>*|GIK=VH3ieDmKk8~itgVf;R~ zTW^ru6QK~*S2{}G4{Dh+Guo}Cd}j2}7GDVITU4oXx61hvxl?6TntW(w69x0I>koJ7;bJen&pJ6wC(Tgwrw4CwK+H{RJcq3d?h5d-hW z5F?q!yYx*J+6uM zZ?C%`yU(u))`^W0?G&4{7OXRuV z`R_gdaSua`?=3`ck6L!has$+bGs4l*Jia%3Cwj;ab^?u0T-zRLeI47G2p9FK3~i=_ z4egPGZxkc5-fJp-U<;jo=Z6@Vute1h-SiLcsglv+skvM+?^5AGQ_s5z|MCi`F^_*~ z^9NpWVvT@le}u=U(PlV(^W!g>N(0EVR!pkrTG4}r2_VE4c;n~?-@f{WnfgS{-ZCdj zAA88`6W2i(ds1t}>UXO?gYChMYKc;9MPj{!Oo=VE<{6%pC>8q^ZQ$HZ>w-$bv7lVd z@&6g?F=GQ!P4IwWCCocq0y7dRd77U^r9kw8dU>_c2()iW>;|YKxsquYj9_2~dZvy9*@1Vmnx;W*K z($mj&#xO6fOF&RXC(L(4$l^ci%EFxHRg zt3O3Z*F&EGHN3RqUxontJoFE2TTmm6Z>TfnqW}SiyZY_GEYoflu(4;7oah6a7|C}< zVDIQguk*2h4!KpSDTrf^mQGv1c*!u8fP&xL;DMBjzwb&_xM(%j@k?@?$c z@jr7$H|y6CGCR6j0er+=`i(^VOc`J?8|*QT1dJkpHI-=YNCCFY7@T|Z+djX!q}31@ zJ+M%s{y1S5Ko;k9sub7@7`i{)N33B~1swZ-ayI1AoiKCv2kp0l;Dw*1zY-Y%WTFK$ zB#9@9j1{#Z`U!F7y&dio?Q6|78Q>ErG8+D6ydrL#~z+*iJjy zGC$(+_S9(?dVwwA5o{ZJoeMqQ5srAl{6`3?ge(6%SPUD+aOeX>3T)*gXag83vgf}u V0aih#vaGC literal 0 HcmV?d00001 diff --git a/src/gba/renderers/software-obj.c b/src/gba/renderers/software-obj.c index 354d8f671..397e84f48 100644 --- a/src/gba/renderers/software-obj.c +++ b/src/gba/renderers/software-obj.c @@ -148,7 +148,8 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re int32_t x = (uint32_t) GBAObjAttributesBGetX(sprite->b) << 23; x >>= 23; uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1]; - unsigned charBase = (GBAObjAttributesCGetTile(sprite->c) & ~GBAObjAttributesAGet256Color(sprite->a)) * 0x20; + bool align = GBAObjAttributesAIs256Color(sprite->a) && !GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt); + unsigned charBase = (GBAObjAttributesCGetTile(sprite->c) & ~align) * 0x20; if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) { return 0; } From 500d613452d74d04e9df52201dfd610da35268d2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 25 Dec 2017 03:10:43 -0500 Subject: [PATCH 080/152] CMake: Package README translations (fixes #950) --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 45719707d..a9ab2c69b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -927,7 +927,8 @@ SET(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_STRIP_FILES ${BINARY_NAME}) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/README.md ${CMAKE_CURRENT_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME}) +file(GLOB READMES ${CMAKE_CURRENT_SOURCE_DIR}/README*.md) +install(FILES ${READMES} ${CMAKE_CURRENT_SOURCE_DIR}/CHANGES DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT lib${BINARY_NAME}) include(CPack) From 2b2a61baa1a1a265ee6997a93b3d7d96b83cd690 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 26 Dec 2017 16:32:16 -0500 Subject: [PATCH 081/152] LR35902: Fix watchpoints not reporting new value --- CHANGES | 1 + src/lr35902/debugger/memory-debugger.c | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 372055813..47ac3abb7 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Bugfixes: - GB Memory: HDMAs should not start when LCD is off (fixes mgba.io/i/310) - GBA Cheats: Fix slide codes not initializing properly - Qt: Fix locale being set to English on settings save (fixes mgba.io/i/906) + - LR35902: Fix watchpoints not reporting new value Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/lr35902/debugger/memory-debugger.c b/src/lr35902/debugger/memory-debugger.c index f9918a1f7..3caf5ac72 100644 --- a/src/lr35902/debugger/memory-debugger.c +++ b/src/lr35902/debugger/memory-debugger.c @@ -27,19 +27,19 @@ static bool _checkWatchpoints(struct LR35902Debugger* debugger, uint16_t address debuggerFound: break; \ } while(0) -#define CREATE_WATCHPOINT_SHIM(NAME, RW, RETURN, TYPES, ...) \ +#define CREATE_WATCHPOINT_SHIM(NAME, RW, VALUE, RETURN, TYPES, ...) \ static RETURN DebuggerShim_ ## NAME TYPES { \ struct LR35902Debugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_ ## RW, 0)) { \ + if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_ ## RW, VALUE)) { \ mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ } \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } -CREATE_WATCHPOINT_SHIM(load8, READ, uint8_t, (struct LR35902Core* cpu, uint16_t address), address) -CREATE_WATCHPOINT_SHIM(store8, WRITE, void, (struct LR35902Core* cpu, uint16_t address, int8_t value), address, value) +CREATE_WATCHPOINT_SHIM(load8, READ, 0, uint8_t, (struct LR35902Core* cpu, uint16_t address), address) +CREATE_WATCHPOINT_SHIM(store8, WRITE, value, void, (struct LR35902Core* cpu, uint16_t address, int8_t value), address, value) static bool _checkWatchpoints(struct LR35902Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue) { struct LR35902DebugWatchpoint* watchpoint; From a6a6e31169f98fe4ce5f7b58091878eca724d908 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 26 Dec 2017 16:35:15 -0500 Subject: [PATCH 082/152] GB Video: Improved window implementation --- cinema/gb/window/gsc-battle/manifest.yml | 1 - include/mgba/internal/gb/renderers/software.h | 2 + src/gb/renderers/software.c | 39 ++++++++++++++++--- 3 files changed, 36 insertions(+), 6 deletions(-) delete mode 100644 cinema/gb/window/gsc-battle/manifest.yml diff --git a/cinema/gb/window/gsc-battle/manifest.yml b/cinema/gb/window/gsc-battle/manifest.yml deleted file mode 100644 index a697ada66..000000000 --- a/cinema/gb/window/gsc-battle/manifest.yml +++ /dev/null @@ -1 +0,0 @@ -fail: true diff --git a/include/mgba/internal/gb/renderers/software.h b/include/mgba/internal/gb/renderers/software.h index a5bbcb80f..99ea2f4d2 100644 --- a/include/mgba/internal/gb/renderers/software.h +++ b/include/mgba/internal/gb/renderers/software.h @@ -32,6 +32,8 @@ struct GBVideoSoftwareRenderer { uint8_t wy; uint8_t wx; uint8_t currentWy; + int lastY; + bool hasWindow; GBRegisterLCDC lcdc; enum GBModel model; diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 0c3526960..486e688f5 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -146,6 +146,10 @@ static void _parseAttrBlock(struct GBVideoSoftwareRenderer* renderer, int start) } } +static bool _inWindow(struct GBVideoSoftwareRenderer* renderer) { + return GBRegisterLCDCIsWindow(renderer->lcdc) && GB_VIDEO_HORIZONTAL_PIXELS + 7 > renderer->wx; +} + void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer* renderer) { renderer->d.init = GBVideoSoftwareRendererInit; renderer->d.deinit = GBVideoSoftwareRendererDeinit; @@ -174,6 +178,8 @@ static void GBVideoSoftwareRendererInit(struct GBVideoRenderer* renderer, enum G softwareRenderer->scx = 0; softwareRenderer->wy = 0; softwareRenderer->currentWy = 0; + softwareRenderer->lastY = 0; + softwareRenderer->hasWindow = false; softwareRenderer->wx = 0; softwareRenderer->model = model; softwareRenderer->sgbTransfer = 0; @@ -193,14 +199,34 @@ static void GBVideoSoftwareRendererDeinit(struct GBVideoRenderer* renderer) { UNUSED(softwareRenderer); } +static void GBVideoSoftwareRendererUpdateWindow(struct GBVideoSoftwareRenderer* renderer, bool before, bool after) { + if (renderer->lastY >= GB_VIDEO_VERTICAL_PIXELS || after == before) { + return; + } + if (renderer->lastY >= renderer->wy) { + if (!after) { + renderer->currentWy -= renderer->lastY; + renderer->hasWindow = true; + } else { + if (!renderer->hasWindow) { + renderer->currentWy = renderer->lastY + 1 - renderer->wy; + } else { + renderer->currentWy += renderer->lastY; + } + } + } +} + static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* renderer, uint16_t address, uint8_t value) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; if (renderer->cache) { GBVideoCacheWriteVideoRegister(renderer->cache, address, value); } + bool wasWindow = _inWindow(softwareRenderer); switch (address) { case REG_LCDC: softwareRenderer->lcdc = value; + GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer)); break; case REG_SCY: softwareRenderer->scy = value; @@ -210,9 +236,11 @@ static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* break; case REG_WY: softwareRenderer->wy = value; + GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer)); break; case REG_WX: softwareRenderer->wx = value; + GBVideoSoftwareRendererUpdateWindow(softwareRenderer, wasWindow, _inWindow(softwareRenderer)); break; case REG_BGP: softwareRenderer->lookup[0] = value & 3; @@ -306,6 +334,7 @@ static void GBVideoSoftwareRendererWriteOAM(struct GBVideoRenderer* renderer, ui static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y, struct GBObj* obj, size_t oamMax) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; + softwareRenderer->lastY = y; uint8_t* maps = &softwareRenderer->d.vram[GB_BASE_MAP]; if (GBRegisterLCDCIsTileMap(softwareRenderer->lcdc)) { maps += GB_SIZE_MAP; @@ -314,7 +343,8 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i memset(&softwareRenderer->row[startX], 0, endX - startX); } if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) { - if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && endX >= softwareRenderer->wx - 7) { + int wy = softwareRenderer->wy + softwareRenderer->currentWy; + if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && wy <= y && endX >= softwareRenderer->wx - 7) { if (softwareRenderer->wx - 7 > 0 && !softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, softwareRenderer->wx - 7, softwareRenderer->scx, softwareRenderer->scy + y); } @@ -324,7 +354,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i maps += GB_SIZE_MAP; } if (!softwareRenderer->d.disableWIN) { - GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, softwareRenderer->currentWy); + GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, softwareRenderer->wx - 7, endX, 7 - softwareRenderer->wx, y - wy); } } else if (!softwareRenderer->d.disableBG) { GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx, softwareRenderer->scy + y); @@ -428,9 +458,6 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i static void GBVideoSoftwareRendererFinishScanline(struct GBVideoRenderer* renderer, int y) { struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; - if (GBRegisterLCDCIsWindow(softwareRenderer->lcdc) && softwareRenderer->wy <= y && softwareRenderer->wx - 7 < GB_VIDEO_HORIZONTAL_PIXELS) { - ++softwareRenderer->currentWy; - } if (softwareRenderer->sgbTransfer == 1) { size_t offset = 2 * ((y & 7) + (y >> 3) * GB_VIDEO_HORIZONTAL_PIXELS); if (offset >= 0x1000) { @@ -520,7 +547,9 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) break; } } + softwareRenderer->lastY = GB_VIDEO_VERTICAL_PIXELS; softwareRenderer->currentWy = 0; + softwareRenderer->hasWindow = false; } static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer* renderer, uint8_t* maps, int startX, int endX, int sx, int sy) { From 8c940089f6ac78ca514350c016b073dc49ed6425 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 27 Dec 2017 12:38:19 -0500 Subject: [PATCH 083/152] GBA Audio: Increase PSG volume (fixes #749) --- CHANGES | 1 + src/gba/audio.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 47ac3abb7..f23a0115b 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Bugfixes: - GBA Cheats: Fix slide codes not initializing properly - Qt: Fix locale being set to English on settings save (fixes mgba.io/i/906) - LR35902: Fix watchpoints not reporting new value + - GBA Audio: Increase PSG volume (fixes mgba.io/i/749) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/audio.c b/src/gba/audio.c index 87c375433..630f6fe0c 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -260,7 +260,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) { struct GBAAudio* audio = user; int16_t sampleLeft = 0; int16_t sampleRight = 0; - int psgShift = 5 - audio->volume; + int psgShift = 4 - audio->volume; GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight); sampleLeft >>= psgShift; sampleRight >>= psgShift; From c7fdb1e87246e15ca56319f4d0b1efa2a760c406 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 27 Dec 2017 12:40:31 -0500 Subject: [PATCH 084/152] Actually fixes #932 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f23a0115b..a5c80f5c5 100644 --- a/CHANGES +++ b/CHANGES @@ -32,7 +32,7 @@ Bugfixes: - GBA Cheats: Fix slide codes not initializing properly - Qt: Fix locale being set to English on settings save (fixes mgba.io/i/906) - LR35902: Fix watchpoints not reporting new value - - GBA Audio: Increase PSG volume (fixes mgba.io/i/749) + - GBA Audio: Increase PSG volume (fixes mgba.io/i/932) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) From 821c8988a385025f2f5c741d279ea0e745f47c0c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 27 Dec 2017 20:57:33 -0500 Subject: [PATCH 085/152] Debugger: Add more operators --- include/mgba/internal/debugger/parser.h | 21 ++++------ src/debugger/cli-debugger.c | 9 ++++ src/debugger/parser.c | 55 ++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h index 178254b69..b111849fc 100644 --- a/include/mgba/internal/debugger/parser.h +++ b/include/mgba/internal/debugger/parser.h @@ -12,23 +12,20 @@ CXX_GUARD_START #include -enum LexState { - LEX_ERROR = -1, - LEX_ROOT = 0, - LEX_EXPECT_IDENTIFIER, - LEX_EXPECT_BINARY, - LEX_EXPECT_DECIMAL, - LEX_EXPECT_HEX, - LEX_EXPECT_PREFIX, - LEX_EXPECT_OPERATOR -}; - enum Operation { OP_ASSIGN, OP_ADD, OP_SUBTRACT, OP_MULTIPLY, - OP_DIVIDE + OP_DIVIDE, + OP_AND, + OP_OR, + OP_XOR, + OP_LESS, + OP_GREATER, + OP_EQUAL, + OP_LE, + OP_GE, }; struct Token { diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index f00a000d1..3077dbdc8 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -560,6 +560,15 @@ static uint32_t _performOperation(enum Operation operation, uint32_t current, ui return 0; } break; + case OP_AND: + current &= next; + break; + case OP_OR: + current |= next; + break; + case OP_XOR: + current ^= next; + break; } return current; } diff --git a/src/debugger/parser.c b/src/debugger/parser.c index a755af505..bf6e0b5af 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -7,6 +7,17 @@ #include +enum LexState { + LEX_ERROR = -1, + LEX_ROOT = 0, + LEX_EXPECT_IDENTIFIER, + LEX_EXPECT_BINARY, + LEX_EXPECT_DECIMAL, + LEX_EXPECT_HEX, + LEX_EXPECT_PREFIX, + LEX_EXPECT_OPERATOR +}; + static struct LexVector* _lexOperator(struct LexVector* lv, char operator) { struct LexVector* lvNext = malloc(sizeof(struct LexVector)); lvNext->token.type = TOKEN_OPERATOR_TYPE; @@ -23,6 +34,15 @@ static struct LexVector* _lexOperator(struct LexVector* lv, char operator) { case '/': lvNext->token.operatorValue = OP_DIVIDE; break; + case '&': + lvNext->token.operatorValue = OP_AND; + break; + case '|': + lvNext->token.operatorValue = OP_OR; + break; + case '^': + lvNext->token.operatorValue = OP_XOR; + break; default: lvNext->token.type = TOKEN_ERROR_TYPE; break; @@ -102,6 +122,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '&': + case '|': + case '^': lv->token.type = TOKEN_IDENTIFIER_TYPE; lv->token.identifierValue = strndup(tokenStart, string - tokenStart - 1); lv = _lexOperator(lv, token); @@ -163,6 +186,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '&': + case '|': + case '^': lv->token.type = TOKEN_UINT_TYPE; lv->token.uintValue = next; lv = _lexOperator(lv, token); @@ -217,6 +243,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '&': + case '|': + case '^': lv->token.type = TOKEN_UINT_TYPE; lv->token.uintValue = next; lv = _lexOperator(lv, token); @@ -259,6 +288,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '&': + case '|': + case '^': lv->token.type = TOKEN_UINT_TYPE; lv->token.uintValue = next; lv = _lexOperator(lv, token); @@ -292,6 +324,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '&': + case '|': + case '^': lvNext = malloc(sizeof(struct LexVector)); lvNext->next = lv->next; lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; @@ -336,11 +371,19 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { } static const int _operatorPrecedence[] = { - 2, - 1, - 1, - 0, - 0 + 14, + 4, + 4, + 3, + 3, + 8, + 10, + 9, + 6, + 6, + 7, + 6, + 6 }; static struct ParseTree* _parseTreeCreate() { @@ -421,7 +464,7 @@ void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv) { tree->lhs = 0; tree->rhs = 0; - _parseExpression(tree, lv, _operatorPrecedence[OP_ASSIGN], 0); + _parseExpression(tree, lv, INT_MAX, 0); } void lexFree(struct LexVector* lv) { From 459d1338554b0bc3d411c72979cc363248cf0b99 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Dec 2017 14:17:53 -0500 Subject: [PATCH 086/152] GBA Memory: Don't reallocate RAM every reset --- src/gba/gba.c | 2 -- src/gba/memory.c | 13 +++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/gba/gba.c b/src/gba/gba.c index a08c6d73f..dd65f4ef2 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -300,7 +300,6 @@ bool GBALoadNull(struct GBA* gba) { GBAUnloadROM(gba); gba->romVf = NULL; gba->pristineRomSize = 0; - gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); #ifndef FIXED_ROM_BUFFER gba->memory.rom = anonymousMemoryMap(SIZE_CART0); #else @@ -328,7 +327,6 @@ bool GBALoadMB(struct GBA* gba, struct VFile* vf) { gba->pristineRomSize = SIZE_WORKING_RAM; } gba->isPristine = true; - gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); memset(gba->memory.wram, 0, SIZE_WORKING_RAM); vf->read(vf, gba->memory.wram, gba->pristineRomSize); if (!gba->memory.wram) { diff --git a/src/gba/memory.c b/src/gba/memory.c index 37da2c62d..3498d722d 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -81,6 +81,7 @@ void GBAMemoryInit(struct GBA* gba) { gba->memory.mirroring = false; gba->memory.iwram = anonymousMemoryMap(SIZE_WORKING_IRAM); + gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); GBADMAInit(gba); GBAVFameInit(&gba->memory.vfame); @@ -98,15 +99,15 @@ void GBAMemoryDeinit(struct GBA* gba) { if (gba->memory.savedata.realVf) { gba->memory.savedata.realVf->close(gba->memory.savedata.realVf); } + + if (gba->memory.agbPrintBuffer) { + mappedMemoryFree(gba->memory.agbPrintBuffer, SIZE_AGB_PRINT); + } } void GBAMemoryReset(struct GBA* gba) { - if (gba->memory.rom || gba->memory.fullBios || !gba->memory.wram) { - // Not multiboot - if (gba->memory.wram) { - mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM); - } - gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); + if (gba->memory.wram && gba->memory.rom) { + memset(gba->memory.wram, 0, SIZE_WORKING_RAM); } if (gba->memory.iwram) { From e2f4fdbdac57da87cbf500a9c32109e7162d5230 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Dec 2017 14:20:55 -0500 Subject: [PATCH 087/152] GBA Memory: AGBPrint support --- CHANGES | 1 + include/mgba/internal/gba/memory.h | 25 +++++++- src/gba/bios.c | 6 ++ src/gba/memory.c | 97 ++++++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index a5c80f5c5..47adeff9a 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ Features: - Map viewer - Automatic cheat loading and saving - GameShark and Action Replay button support + - AGBPrint support Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index 259813b67..abb6525ca 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -69,7 +69,9 @@ enum { SIZE_CART_FLASH512 = 0x00010000, SIZE_CART_FLASH1M = 0x00020000, SIZE_CART_EEPROM = 0x00002000, - SIZE_CART_EEPROM512 = 0x00000200 + SIZE_CART_EEPROM512 = 0x00000200, + + SIZE_AGB_PRINT = 0x10000 }; enum { @@ -77,8 +79,23 @@ enum { BASE_OFFSET = 24 }; +enum { + AGB_PRINT_BASE = 0x00FD0000, + AGB_PRINT_TOP = 0x00FE0000, + AGB_PRINT_PROTECT = 0x00FE2FFE, + AGB_PRINT_STRUCT = 0x01FE20F8, + AGB_PRINT_FLUSH_ADDR = 0x01FE209C, +}; + mLOG_DECLARE_CATEGORY(GBA_MEM); +struct GBAPrintContext { + uint16_t request; + uint16_t bank; + uint16_t get; + uint16_t put; +}; + struct GBAMemory { uint32_t* bios; uint32_t* wram; @@ -108,6 +125,10 @@ struct GBAMemory { int activeDMA; uint32_t dmaTransferRegister; + uint16_t agbPrint; + struct GBAPrintContext agbPrintCtx; + uint16_t* agbPrintBuffer; + bool mirroring; }; @@ -146,6 +167,8 @@ struct GBASerializedState; void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state); void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state); +void GBAPrintFlush(struct GBA* gba); + CXX_GUARD_END #endif diff --git a/src/gba/bios.c b/src/gba/bios.c index 6d4835bed..debee1e05 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -333,6 +333,12 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { mLOG(GBA_BIOS, DEBUG, "SWI: %02X r0: %08X r1: %08X r2: %08X r3: %08X", immediate, cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3]); + switch (immediate) { + case 0xFA: + GBAPrintFlush(gba); + return; + } + if (gba->memory.fullBios) { ARMRaiseSWI(cpu); return; diff --git a/src/gba/memory.c b/src/gba/memory.c index 3498d722d..079372689 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -22,7 +22,10 @@ mLOG_DEFINE_CATEGORY(GBA_MEM, "GBA Memory", "gba.memory"); static void _pristineCow(struct GBA* gba); -static uint32_t _deadbeef[1] = { 0xE710B710 }; // Illegal instruction on both ARM and Thumb +static void _agbPrintStore(struct GBA* gba, uint32_t address, int16_t value); +static int16_t _agbPrintLoad(struct GBA* gba, uint32_t address); +static uint8_t _deadbeef[4] = { 0x10, 0xB7, 0x10, 0xE7 }; // Illegal instruction on both ARM and Thumb +static uint8_t _agbPrintFunc[4] = { 0xFA, 0xDF /* swi 0xFF */, 0x70, 0x47 /* bx lr */ }; static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region); static int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait); @@ -80,6 +83,10 @@ void GBAMemoryInit(struct GBA* gba) { gba->memory.biosPrefetch = 0; gba->memory.mirroring = false; + gba->memory.agbPrint = 0; + memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); + gba->memory.agbPrintBuffer = NULL; + gba->memory.iwram = anonymousMemoryMap(SIZE_WORKING_IRAM); gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); @@ -116,6 +123,12 @@ void GBAMemoryReset(struct GBA* gba) { memset(gba->memory.io, 0, sizeof(gba->memory.io)); + gba->memory.agbPrint = 0; + memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); + if (gba->memory.agbPrintBuffer) { + gba->memory.agbPrintBuffer = NULL; + } + gba->memory.prefetch = false; gba->memory.lastPrefetchedPc = 0; @@ -295,10 +308,15 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { if ((address & (SIZE_CART0 - 1)) < memory->romSize) { break; } + if ((address & (SIZE_CART0 - 1)) == AGB_PRINT_FLUSH_ADDR && memory->agbPrint == 0x20) { + cpu->memory.activeRegion = (uint32_t*) _agbPrintFunc; + cpu->memory.activeMask = sizeof(_agbPrintFunc) - 1; + + } // Fall through default: memory->activeRegion = -1; - cpu->memory.activeRegion = _deadbeef; + cpu->memory.activeRegion = (uint32_t*) _deadbeef; cpu->memory.activeMask = 0; if (!gba->yankedRomSize && mCoreCallbacksListSize(&gba->coreCallbacks)) { @@ -528,6 +546,16 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { LOAD_16(value, address & memory->romMask, memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); + } else if ((address & (SIZE_CART0 - 1)) >= AGB_PRINT_BASE) { + uint32_t agbPrintAddr = address & 0x00FFFFFF; + if (agbPrintAddr == AGB_PRINT_PROTECT) { + value = memory->agbPrint; + } else if (agbPrintAddr < AGB_PRINT_TOP || (agbPrintAddr & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + value = _agbPrintLoad(gba, address); + } else { + mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); + value = (address >> 1) & 0xFFFF; + } } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); value = (address >> 1) & 0xFFFF; @@ -838,9 +866,23 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle if (memory->hw.devices != HW_NONE && IS_GPIO_REGISTER(address & 0xFFFFFE)) { uint32_t reg = address & 0xFFFFFE; GBAHardwareGPIOWrite(&memory->hw, reg, value); - } else { - mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); + break; } + // Fall through + case REGION_CART0_EX: + if ((address & 0x00FFFFFF) >= AGB_PRINT_BASE) { + uint32_t agbPrintAddr = address & 0x00FFFFFF; + if (agbPrintAddr == AGB_PRINT_PROTECT) { + memory->agbPrint = value; + _agbPrintStore(gba, address, value); + break; + } + if (memory->agbPrint == 0x20 && (agbPrintAddr < AGB_PRINT_TOP || (agbPrintAddr & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8))) { + _agbPrintStore(gba, address, value); + break; + } + } + mLOG(GBA_MEM, GAME_ERROR, "Bad cartridge Store16: 0x%08X", address); break; case REGION_CART2_EX: if (memory->savedata.type == SAVEDATA_AUTODETECT) { @@ -1595,3 +1637,50 @@ void _pristineCow(struct GBA* gba) { gba->memory.rom = newRom; gba->memory.hw.gpioBase = &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]; } + +void GBAPrintFlush(struct GBA* gba) { + char oolBuf[0x101]; + size_t i; + for (i = 0; gba->memory.agbPrintCtx.get != gba->memory.agbPrintCtx.put && i < 0x100; ++i) { + int16_t value; + LOAD_16(value, gba->memory.agbPrintCtx.get & -2, gba->memory.agbPrintBuffer); + if (gba->memory.agbPrintCtx.get & 1) { + value >>= 8; + } else { + value &= 0xFF; + } + oolBuf[i] = value; + ++gba->memory.agbPrintCtx.get; + } + _agbPrintStore(gba, AGB_PRINT_STRUCT + 4, gba->memory.agbPrintCtx.get); + + mLOG(GBA_DEBUG, INFO, "%s", oolBuf); +} + +static void _agbPrintStore(struct GBA* gba, uint32_t address, int16_t value) { + struct GBAMemory* memory = &gba->memory; + if ((address & 0x00FFFFFF) < AGB_PRINT_TOP) { + if (!memory->agbPrintBuffer) { + memory->agbPrintBuffer = anonymousMemoryMap(SIZE_AGB_PRINT); + } + STORE_16(value, address & (SIZE_AGB_PRINT - 2), memory->agbPrintBuffer); + } else if ((address & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + (&memory->agbPrintCtx.request)[(address & 7) >> 1] = value; + } + if (memory->romSize == SIZE_CART0) { + _pristineCow(gba); + memcpy(&memory->rom[AGB_PRINT_FLUSH_ADDR >> 2], _agbPrintFunc, sizeof(_agbPrintFunc)); + STORE_16(value, address & (SIZE_CART0 - 2), memory->rom); + } +} + +static int16_t _agbPrintLoad(struct GBA* gba, uint32_t address) { + struct GBAMemory* memory = &gba->memory; + int16_t value = 0xFFFF; + if (address < AGB_PRINT_TOP) { + LOAD_16(value, address & (SIZE_AGB_PRINT - 1), memory->agbPrintBuffer); + } else if ((address & 0x00FFFFF8) == (AGB_PRINT_STRUCT & 0x00FFFFF8)) { + value = (&memory->agbPrintCtx.request)[(address & 7) >> 1]; + } + return value; +} From 5d8403f5a3deb02c08f46ccb1e179a807f0e0a69 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Dec 2017 19:58:32 -0500 Subject: [PATCH 088/152] Debugger: Even more operators --- src/debugger/cli-debugger.c | 15 +++++++++++++++ src/debugger/parser.c | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 3077dbdc8..786afcb60 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -569,6 +569,21 @@ static uint32_t _performOperation(enum Operation operation, uint32_t current, ui case OP_XOR: current ^= next; break; + case OP_LESS: + current = current < next; + break; + case OP_GREATER: + current = current > next; + break; + case OP_EQUAL: + current = current == next; + break; + case OP_LE: + current = current <= next; + break; + case OP_GE: + current = current >= next; + break; } return current; } diff --git a/src/debugger/parser.c b/src/debugger/parser.c index bf6e0b5af..ff9e28cdf 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -43,6 +43,12 @@ static struct LexVector* _lexOperator(struct LexVector* lv, char operator) { case '^': lvNext->token.operatorValue = OP_XOR; break; + case '<': + lvNext->token.operatorValue = OP_LESS; + break; + case '>': + lvNext->token.operatorValue = OP_GREATER; + break; default: lvNext->token.type = TOKEN_ERROR_TYPE; break; @@ -125,6 +131,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '&': case '|': case '^': + case '<': + case '>': lv->token.type = TOKEN_IDENTIFIER_TYPE; lv->token.identifierValue = strndup(tokenStart, string - tokenStart - 1); lv = _lexOperator(lv, token); @@ -151,6 +159,11 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '&': + case '|': + case '^': + case '<': + case '>': lv->token.type = TOKEN_UINT_TYPE; lv->token.uintValue = next; lv = _lexOperator(lv, token); @@ -189,6 +202,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '&': case '|': case '^': + case '<': + case '>': lv->token.type = TOKEN_UINT_TYPE; lv->token.uintValue = next; lv = _lexOperator(lv, token); @@ -246,6 +261,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '&': case '|': case '^': + case '<': + case '>': lv->token.type = TOKEN_UINT_TYPE; lv->token.uintValue = next; lv = _lexOperator(lv, token); @@ -291,6 +308,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '&': case '|': case '^': + case '<': + case '>': lv->token.type = TOKEN_UINT_TYPE; lv->token.uintValue = next; lv = _lexOperator(lv, token); @@ -327,6 +346,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '&': case '|': case '^': + case '<': + case '>': lvNext = malloc(sizeof(struct LexVector)); lvNext->next = lv->next; lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; From e192973bc5a0f32dce3bf52ef8c91320d08eec37 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Dec 2017 20:34:37 -0500 Subject: [PATCH 089/152] Debugger: Migrate identifier lookups --- include/mgba/core/core.h | 1 + include/mgba/debugger/debugger.h | 4 ++- include/mgba/internal/debugger/cli-debugger.h | 1 - src/debugger/cli-debugger.c | 31 +++---------------- src/debugger/debugger.c | 20 ++++++++++++ src/gb/core.c | 15 +++++++++ src/gb/debugger/cli.c | 15 --------- src/gba/core.c | 15 +++++++++ src/gba/debugger/cli.c | 15 --------- 9 files changed, 58 insertions(+), 59 deletions(-) diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index 12304f561..59d9e429a 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -141,6 +141,7 @@ struct mCore { void (*detachDebugger)(struct mCore*); void (*loadSymbols)(struct mCore*, struct VFile*); + bool (*lookupIdentifier)(struct mCore*, const char* name, int32_t* value, int* segment); #endif struct mCheatDevice* (*cheatDevice)(struct mCore*); diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index 3dc813334..f2a030827 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -87,9 +87,9 @@ struct mDebuggerPlatform { bool (*getRegister)(struct mDebuggerPlatform*, const char* name, int32_t* value); bool (*setRegister)(struct mDebuggerPlatform*, const char* name, int32_t value); + bool (*lookupIdentifier)(struct mDebuggerPlatform*, const char* name, int32_t* value, int* segment); }; -struct mDebuggerSymbols; struct mDebugger { struct mCPUComponent d; struct mDebuggerPlatform* platform; @@ -112,6 +112,8 @@ void mDebuggerRun(struct mDebugger*); void mDebuggerRunFrame(struct mDebugger*); void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); +bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int32_t* value, int* segment); + CXX_GUARD_END #endif diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index eabb3bcf1..7a03d1df0 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -47,7 +47,6 @@ struct CLIDebuggerSystem { bool (*custom)(struct CLIDebuggerSystem*); void (*disassemble)(struct CLIDebuggerSystem*, struct CLIDebugVector* dv); - uint32_t (*lookupIdentifier)(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); void (*printStatus)(struct CLIDebuggerSystem*); struct CLIDebuggerCommandSummary* commands; diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 786afcb60..b85308692 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -588,31 +588,6 @@ static uint32_t _performOperation(enum Operation operation, uint32_t current, ui return current; } -static void _lookupIdentifier(struct mDebugger* debugger, const char* name, struct CLIDebugVector* dv) { - struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; - if (cliDebugger->system) { - uint32_t value; -#ifdef ENABLE_SCRIPTING - if (debugger->bridge && mScriptBridgeLookupSymbol(debugger->bridge, name, &dv->intValue)) { - return; - } -#endif - if (debugger->core->symbolTable && mDebuggerSymbolLookup(debugger->core->symbolTable, name, &dv->intValue, &dv->segmentValue)) { - return; - } - dv->type = CLIDV_INT_TYPE; - if (debugger->platform->getRegister(debugger->platform, name, &dv->intValue)) { - return; - } - value = cliDebugger->system->lookupIdentifier(cliDebugger->system, name, dv); - if (dv->type != CLIDV_ERROR_TYPE) { - dv->intValue = value; - return; - } - } - dv->type = CLIDV_ERROR_TYPE; -} - static void _evaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, struct CLIDebugVector* dv) { int32_t lhs, rhs; switch (tree->token.type) { @@ -632,8 +607,10 @@ static void _evaluateParseTree(struct mDebugger* debugger, struct ParseTree* tre dv->intValue = _performOperation(tree->token.operatorValue, lhs, rhs, dv); break; case TOKEN_IDENTIFIER_TYPE: - _lookupIdentifier(debugger, tree->token.identifierValue, dv); - break; + if (mDebuggerLookupIdentifier(debugger, tree->token.identifierValue, &dv->intValue, &dv->segmentValue)) { + break; + } + // Fall through case TOKEN_ERROR_TYPE: default: dv->type = CLIDV_ERROR_TYPE; diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index 79b06076a..14e4d101c 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -8,6 +8,7 @@ #include #include +#include #ifdef USE_GDB_STUB #include @@ -137,3 +138,22 @@ static void mDebuggerDeinit(struct mCPUComponent* component) { } debugger->platform->deinit(debugger->platform); } + +bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int32_t* value, int* segment) { + *segment = -1; +#ifdef ENABLE_SCRIPTING + if (debugger->bridge && mScriptBridgeLookupSymbol(debugger->bridge, name, value)) { + return true; + } +#endif + if (debugger->core->symbolTable && mDebuggerSymbolLookup(debugger->core->symbolTable, name, value, segment)) { + return true; + } + if (debugger->core->lookupIdentifier(debugger->core, name, value, segment)) { + return true; + } + if (debugger->platform && debugger->platform->getRegister(debugger->platform, name, value)) { + return true; + } + return false; +} diff --git a/src/gb/core.c b/src/gb/core.c index 753a93bde..299a6f5b6 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -723,6 +723,20 @@ static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) { } GBLoadSymbols(core->symbolTable, vf); } + +static bool _GBCoreLookupIdentifier(struct mCore* core, const char* name, int32_t* value, int* segment) { + UNUSED(core); + *segment = -1; + int i; + for (i = 0; i < REG_MAX; ++i) { + const char* reg = GBIORegisterNames[i]; + if (reg && strcasecmp(reg, name) == 0) { + *value = GB_BASE_IO | i; + return true; + } + } + return false; +} #endif static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) { @@ -905,6 +919,7 @@ struct mCore* GBCoreCreate(void) { core->attachDebugger = _GBCoreAttachDebugger; core->detachDebugger = _GBCoreDetachDebugger; core->loadSymbols = _GBCoreLoadSymbols; + core->lookupIdentifier = _GBCoreLookupIdentifier; #endif core->cheatDevice = _GBCoreCheatDevice; core->savedataClone = _GBCoreSavedataClone; diff --git a/src/gb/debugger/cli.c b/src/gb/debugger/cli.c index 75cc53e54..a051ea577 100644 --- a/src/gb/debugger/cli.c +++ b/src/gb/debugger/cli.c @@ -14,7 +14,6 @@ static void _GBCLIDebuggerInit(struct CLIDebuggerSystem*); static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem*); -static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); static void _frame(struct CLIDebugger*, struct CLIDebugVector*); static void _load(struct CLIDebugger*, struct CLIDebugVector*); @@ -34,7 +33,6 @@ struct CLIDebuggerSystem* GBCLIDebuggerCreate(struct mCore* core) { debugger->d.init = _GBCLIDebuggerInit; debugger->d.deinit = NULL; debugger->d.custom = _GBCLIDebuggerCustom; - debugger->d.lookupIdentifier = _GBCLIDebuggerLookupIdentifier; debugger->d.name = "Game Boy"; debugger->d.commands = _GBCLIDebuggerCommands; @@ -65,19 +63,6 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { return false; } -static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - UNUSED(debugger); - int i; - for (i = 0; i < REG_MAX; ++i) { - const char* reg = GBIORegisterNames[i]; - if (reg && strcasecmp(reg, name) == 0) { - return GB_BASE_IO | i; - } - } - dv->type = CLIDV_ERROR_TYPE; - return 0; -} - static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); debugger->d.state = DEBUGGER_CUSTOM; diff --git a/src/gba/core.c b/src/gba/core.c index 16c1d1040..8c307ceff 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -724,6 +724,20 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) { } #endif } + +static bool _GBACoreLookupIdentifier(struct mCore* core, const char* name, int32_t* value, int* segment) { + UNUSED(core); + *segment = -1; + int i; + for (i = 0; i < REG_MAX; i += 2) { + const char* reg = GBAIORegisterNames[i >> 1]; + if (reg && strcasecmp(reg, name) == 0) { + *value = BASE_IO | i; + return true; + } + } + return false; +} #endif static struct mCheatDevice* _GBACoreCheatDevice(struct mCore* core) { @@ -921,6 +935,7 @@ struct mCore* GBACoreCreate(void) { core->attachDebugger = _GBACoreAttachDebugger; core->detachDebugger = _GBACoreDetachDebugger; core->loadSymbols = _GBACoreLoadSymbols; + core->lookupIdentifier = _GBACoreLookupIdentifier; #endif core->cheatDevice = _GBACoreCheatDevice; core->savedataClone = _GBACoreSavedataClone; diff --git a/src/gba/debugger/cli.c b/src/gba/debugger/cli.c index 07287eebc..4eb13b0f3 100644 --- a/src/gba/debugger/cli.c +++ b/src/gba/debugger/cli.c @@ -14,7 +14,6 @@ static void _GBACLIDebuggerInit(struct CLIDebuggerSystem*); static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem*); -static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); static void _frame(struct CLIDebugger*, struct CLIDebugVector*); static void _load(struct CLIDebugger*, struct CLIDebugVector*); @@ -33,7 +32,6 @@ struct GBACLIDebugger* GBACLIDebuggerCreate(struct mCore* core) { debugger->d.init = _GBACLIDebuggerInit; debugger->d.deinit = NULL; debugger->d.custom = _GBACLIDebuggerCustom; - debugger->d.lookupIdentifier = _GBACLIDebuggerLookupIdentifier; debugger->d.name = "Game Boy Advance"; debugger->d.commands = _GBACLIDebuggerCommands; @@ -64,19 +62,6 @@ static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { return false; } -static uint32_t _GBACLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { - UNUSED(debugger); - int i; - for (i = 0; i < REG_MAX; i += 2) { - const char* reg = GBAIORegisterNames[i >> 1]; - if (reg && strcasecmp(reg, name) == 0) { - return BASE_IO | i; - } - } - dv->type = CLIDV_ERROR_TYPE; - return 0; -} - static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); debugger->d.state = DEBUGGER_CUSTOM; From c692006b9d272cebfa35c023335cd0bce2021854 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 00:02:20 -0500 Subject: [PATCH 090/152] Debugger: Expose parser evaluation --- include/mgba/internal/debugger/parser.h | 5 +- src/debugger/cli-debugger.c | 83 +----------------------- src/debugger/parser.c | 85 +++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 82 deletions(-) diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h index b111849fc..eecf7e80d 100644 --- a/include/mgba/internal/debugger/parser.h +++ b/include/mgba/internal/debugger/parser.h @@ -10,8 +10,6 @@ CXX_GUARD_START -#include - enum Operation { OP_ASSIGN, OP_ADD, @@ -62,6 +60,9 @@ void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv); void lexFree(struct LexVector* lv); void parseFree(struct ParseTree* tree); +struct mDebugger; +bool mDebuggerEvaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, int32_t* value, int* segment); + CXX_GUARD_END #endif diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index b85308692..28887a1f2 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -538,85 +538,6 @@ static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* dv debugger->system->printStatus(debugger->system); } -static uint32_t _performOperation(enum Operation operation, uint32_t current, uint32_t next, struct CLIDebugVector* dv) { - switch (operation) { - case OP_ASSIGN: - current = next; - break; - case OP_ADD: - current += next; - break; - case OP_SUBTRACT: - current -= next; - break; - case OP_MULTIPLY: - current *= next; - break; - case OP_DIVIDE: - if (next != 0) { - current /= next; - } else { - dv->type = CLIDV_ERROR_TYPE; - return 0; - } - break; - case OP_AND: - current &= next; - break; - case OP_OR: - current |= next; - break; - case OP_XOR: - current ^= next; - break; - case OP_LESS: - current = current < next; - break; - case OP_GREATER: - current = current > next; - break; - case OP_EQUAL: - current = current == next; - break; - case OP_LE: - current = current <= next; - break; - case OP_GE: - current = current >= next; - break; - } - return current; -} - -static void _evaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, struct CLIDebugVector* dv) { - int32_t lhs, rhs; - switch (tree->token.type) { - case TOKEN_UINT_TYPE: - dv->intValue = tree->token.uintValue; - break; - case TOKEN_SEGMENT_TYPE: - _evaluateParseTree(debugger, tree->lhs, dv); - dv->segmentValue = dv->intValue; - _evaluateParseTree(debugger, tree->rhs, dv); - break; - case TOKEN_OPERATOR_TYPE: - _evaluateParseTree(debugger, tree->lhs, dv); - lhs = dv->intValue; - _evaluateParseTree(debugger, tree->rhs, dv); - rhs = dv->intValue; - dv->intValue = _performOperation(tree->token.operatorValue, lhs, rhs, dv); - break; - case TOKEN_IDENTIFIER_TYPE: - if (mDebuggerLookupIdentifier(debugger, tree->token.identifierValue, &dv->intValue, &dv->segmentValue)) { - break; - } - // Fall through - case TOKEN_ERROR_TYPE: - default: - dv->type = CLIDV_ERROR_TYPE; - } -} - struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* string, size_t length) { if (!string || length < 1) { return 0; @@ -636,7 +557,9 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri if (tree.token.type == TOKEN_ERROR_TYPE) { dvTemp.type = CLIDV_ERROR_TYPE; } else { - _evaluateParseTree(&debugger->d, &tree, &dvTemp); + if (!mDebuggerEvaluateParseTree(&debugger->d, &tree, &dvTemp.intValue, &dvTemp.segmentValue)) { + dvTemp.type = CLIDV_ERROR_TYPE; + } } parseFree(tree.lhs); diff --git a/src/debugger/parser.c b/src/debugger/parser.c index ff9e28cdf..9c53cc9f3 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include enum LexState { @@ -509,3 +510,87 @@ void parseFree(struct ParseTree* tree) { } free(tree); } + +static bool _performOperation(enum Operation operation, int32_t current, int32_t next, int32_t* value) { + switch (operation) { + case OP_ASSIGN: + current = next; + break; + case OP_ADD: + current += next; + break; + case OP_SUBTRACT: + current -= next; + break; + case OP_MULTIPLY: + current *= next; + break; + case OP_DIVIDE: + if (next != 0) { + current /= next; + } else { + return false; + } + break; + case OP_AND: + current &= next; + break; + case OP_OR: + current |= next; + break; + case OP_XOR: + current ^= next; + break; + case OP_LESS: + current = current < next; + break; + case OP_GREATER: + current = current > next; + break; + case OP_EQUAL: + current = current == next; + break; + case OP_LE: + current = current <= next; + break; + case OP_GE: + current = current >= next; + break; + } + *value = current; + return true; +} + +bool mDebuggerEvaluateParseTree(struct mDebugger* debugger, struct ParseTree* tree, int32_t* value, int* segment) { + if (!value) { + return false; + } + int32_t lhs, rhs; + switch (tree->token.type) { + case TOKEN_UINT_TYPE: + if (segment) { + *segment = -1; + } + *value = tree->token.uintValue; + return true; + case TOKEN_SEGMENT_TYPE: + if (!mDebuggerEvaluateParseTree(debugger, tree->rhs, value, segment)) { + return false; + } + return mDebuggerEvaluateParseTree(debugger, tree->lhs, segment, NULL); + case TOKEN_OPERATOR_TYPE: + if (!mDebuggerEvaluateParseTree(debugger, tree->lhs, &lhs, segment)) { + return false; + } + if (!mDebuggerEvaluateParseTree(debugger, tree->rhs, &rhs, segment)) { + return false; + } + return _performOperation(tree->token.operatorValue, lhs, rhs, value); + case TOKEN_IDENTIFIER_TYPE: + return mDebuggerLookupIdentifier(debugger, tree->token.identifierValue, value, segment); + case TOKEN_ERROR_TYPE: + default: + break; + } + return false; +} From a83e76a62a045ecba81277ffc8f79822ae8b5917 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 00:21:31 -0500 Subject: [PATCH 091/152] Debugger: Refactor parser, fix prefix edge cases --- src/debugger/parser.c | 142 ++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 88 deletions(-) diff --git a/src/debugger/parser.c b/src/debugger/parser.c index 9c53cc9f3..5cdb6d346 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -12,8 +12,10 @@ enum LexState { LEX_ERROR = -1, LEX_ROOT = 0, LEX_EXPECT_IDENTIFIER, + LEX_EXPECT_BINARY_FIRST, LEX_EXPECT_BINARY, LEX_EXPECT_DECIMAL, + LEX_EXPECT_HEX_FIRST, LEX_EXPECT_HEX, LEX_EXPECT_PREFIX, LEX_EXPECT_OPERATOR @@ -64,6 +66,41 @@ static struct LexVector* _lexOperator(struct LexVector* lv, char operator) { return lvNext; } +static struct LexVector* _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexState* state) { + struct LexVector* lvNext; + + switch (token) { + case '+': + case '-': + case '*': + case '/': + case '&': + case '|': + case '^': + case '<': + case '>': + lv->token.type = TOKEN_UINT_TYPE; + lv->token.uintValue = next; + lv = _lexOperator(lv, token); + *state = LEX_ROOT; + break; + case ')': + lvNext = malloc(sizeof(struct LexVector)); + lvNext->next = lv->next; + lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; + lv->next = lvNext; + lv->token.type = TOKEN_UINT_TYPE; + lv->token.uintValue = next; + lv = lvNext; + *state = LEX_EXPECT_OPERATOR; + break; + default: + *state = LEX_ERROR; + break; + } + return lv; +} + size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { if (!string || length < 1) { return 0; @@ -148,6 +185,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { break; } break; + case LEX_EXPECT_BINARY_FIRST: + state = LEX_EXPECT_BINARY; + // Fall through case LEX_EXPECT_BINARY: switch (token) { case '0': @@ -156,27 +196,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { next <<= 1; next += token - '0'; break; - case '+': - case '-': - case '*': - case '/': - case '&': - case '|': - case '^': - case '<': - case '>': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; - break; default: - state = LEX_ERROR; + lv = _lexValue(lv, token, next, &state); break; } break; @@ -196,29 +217,14 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { next *= 10; next += token - '0'; break; - case '+': - case '-': - case '*': - case '/': - case '&': - case '|': - case '^': - case '<': - case '>': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; - break; default: - state = LEX_ERROR; + lv = _lexValue(lv, token, next, &state); + break; } break; + case LEX_EXPECT_HEX_FIRST: + state = LEX_EXPECT_HEX; + // Fall through case LEX_EXPECT_HEX: switch (token) { case '0': @@ -255,20 +261,6 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { next *= 16; next += token - 'a' + 10; break; - case '+': - case '-': - case '*': - case '/': - case '&': - case '|': - case '^': - case '<': - case '>': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; case ':': lv->token.type = TOKEN_SEGMENT_TYPE; lv->token.uintValue = next; @@ -278,15 +270,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { lv->next = lvNext; lv = lvNext; next = 0; - state = LEX_EXPECT_HEX; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; break; default: - state = LEX_ERROR; + lv = _lexValue(lv, token, next, &state); break; } break; @@ -295,31 +281,12 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case 'X': case 'x': next = 0; - state = LEX_EXPECT_HEX; + state = LEX_EXPECT_HEX_FIRST; break; case 'B': case 'b': next = 0; - state = LEX_EXPECT_BINARY; - break; - case '+': - case '-': - case '*': - case '/': - case '&': - case '|': - case '^': - case '<': - case '>': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); - state = LEX_ROOT; - break; - case ')': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - state = LEX_EXPECT_OPERATOR; + state = LEX_EXPECT_BINARY_FIRST; break; case '0': case '1': @@ -335,7 +302,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { state = LEX_EXPECT_DECIMAL; break; default: - state = LEX_ERROR; + lv = _lexValue(lv, token, next, &state); + break; } break; case LEX_EXPECT_OPERATOR: @@ -349,11 +317,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '^': case '<': case '>': - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; - lv->next = lvNext; - lv = _lexOperator(lv->next, token); + lv = _lexOperator(lv, token); state = LEX_ROOT; break; default: @@ -384,6 +348,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; lv->next = lvNext; break; + case LEX_EXPECT_BINARY_FIRST: + case LEX_EXPECT_HEX_FIRST: case LEX_ERROR: default: lv->token.type = TOKEN_ERROR_TYPE; From d7900fdf5fdaa9d432a79ac22dbd1bdde6ce2c5d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 00:47:49 -0500 Subject: [PATCH 092/152] Debugger: Refactor lexer to use Vector type --- include/mgba/internal/debugger/parser.h | 7 +- src/debugger/cli-debugger.c | 7 +- src/debugger/parser.c | 171 +++++++++++------------- 3 files changed, 89 insertions(+), 96 deletions(-) diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h index eecf7e80d..78654c524 100644 --- a/include/mgba/internal/debugger/parser.h +++ b/include/mgba/internal/debugger/parser.h @@ -8,6 +8,8 @@ #include +#include + CXX_GUARD_START enum Operation { @@ -43,10 +45,7 @@ struct Token { }; }; -struct LexVector { - struct LexVector* next; - struct Token token; -}; +DECLARE_VECTOR(LexVector, struct Token); struct ParseTree { struct Token token; diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 28887a1f2..c439a9427 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -545,11 +545,11 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri struct CLIDebugVector dvTemp = { .type = CLIDV_INT_TYPE, .segmentValue = -1 }; - struct LexVector lv = { .next = 0 }; + struct LexVector lv; + LexVectorInit(&lv, 0); size_t adjusted = lexExpression(&lv, string, length); if (adjusted > length) { dvTemp.type = CLIDV_ERROR_TYPE; - lexFree(lv.next); } struct ParseTree tree; @@ -565,6 +565,9 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri parseFree(tree.lhs); parseFree(tree.rhs); + lexFree(&lv); + LexVectorDeinit(&lv); + struct CLIDebugVector* dv = malloc(sizeof(struct CLIDebugVector)); if (dvTemp.type == CLIDV_ERROR_TYPE) { dv->type = CLIDV_ERROR_TYPE; diff --git a/src/debugger/parser.c b/src/debugger/parser.c index 5cdb6d346..8adc6fd8c 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -8,6 +8,8 @@ #include #include +DEFINE_VECTOR(LexVector, struct Token); + enum LexState { LEX_ERROR = -1, LEX_ROOT = 0, @@ -21,53 +23,45 @@ enum LexState { LEX_EXPECT_OPERATOR }; -static struct LexVector* _lexOperator(struct LexVector* lv, char operator) { - struct LexVector* lvNext = malloc(sizeof(struct LexVector)); - lvNext->token.type = TOKEN_OPERATOR_TYPE; +static void _lexOperator(struct LexVector* lv, char operator) { + struct Token* lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_OPERATOR_TYPE; switch (operator) { case '+': - lvNext->token.operatorValue = OP_ADD; + lvNext->operatorValue = OP_ADD; break; case '-': - lvNext->token.operatorValue = OP_SUBTRACT; + lvNext->operatorValue = OP_SUBTRACT; break; case '*': - lvNext->token.operatorValue = OP_MULTIPLY; + lvNext->operatorValue = OP_MULTIPLY; break; case '/': - lvNext->token.operatorValue = OP_DIVIDE; + lvNext->operatorValue = OP_DIVIDE; break; case '&': - lvNext->token.operatorValue = OP_AND; + lvNext->operatorValue = OP_AND; break; case '|': - lvNext->token.operatorValue = OP_OR; + lvNext->operatorValue = OP_OR; break; case '^': - lvNext->token.operatorValue = OP_XOR; + lvNext->operatorValue = OP_XOR; break; case '<': - lvNext->token.operatorValue = OP_LESS; + lvNext->operatorValue = OP_LESS; break; case '>': - lvNext->token.operatorValue = OP_GREATER; + lvNext->operatorValue = OP_GREATER; break; default: - lvNext->token.type = TOKEN_ERROR_TYPE; + lvNext->type = TOKEN_ERROR_TYPE; break; } - lvNext->next = lv->next; - lv->next = lvNext; - lv = lvNext; - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_ERROR_TYPE; - lv->next = lvNext; - return lvNext; } -static struct LexVector* _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexState* state) { - struct LexVector* lvNext; +static void _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexState* state) { + struct Token* lvNext; switch (token) { case '+': @@ -79,26 +73,24 @@ static struct LexVector* _lexValue(struct LexVector* lv, char token, uint32_t ne case '^': case '<': case '>': - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = _lexOperator(lv, token); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; + _lexOperator(lv, token); *state = LEX_ROOT; break; case ')': - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; - lv->next = lvNext; - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; - lv = lvNext; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_CLOSE_PAREN_TYPE; *state = LEX_EXPECT_OPERATOR; break; default: *state = LEX_ERROR; break; } - return lv; } size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { @@ -111,7 +103,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { enum LexState state = LEX_ROOT; const char* tokenStart = 0; - struct LexVector* lvNext; + struct Token* lvNext; while (length > 0 && string[0] && string[0] != ' ' && state != LEX_ERROR) { char token = string[0]; @@ -144,12 +136,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { break; case '(': state = LEX_ROOT; - lv->token.type = TOKEN_OPEN_PAREN_TYPE; - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_ERROR_TYPE; - lv->next = lvNext; - lv = lvNext; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_OPEN_PAREN_TYPE; break; default: if (tolower(token) >= 'a' && tolower(token <= 'z')) { @@ -171,14 +159,18 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '^': case '<': case '>': - lv->token.type = TOKEN_IDENTIFIER_TYPE; - lv->token.identifierValue = strndup(tokenStart, string - tokenStart - 1); - lv = _lexOperator(lv, token); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); + _lexOperator(lv, token); state = LEX_ROOT; break; case ')': - lv->token.type = TOKEN_IDENTIFIER_TYPE; - lv->token.identifierValue = strndup(tokenStart, string - tokenStart - 1); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_CLOSE_PAREN_TYPE; state = LEX_EXPECT_OPERATOR; break; default: @@ -197,7 +189,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { next += token - '0'; break; default: - lv = _lexValue(lv, token, next, &state); + _lexValue(lv, token, next, &state); break; } break; @@ -218,7 +210,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { next += token - '0'; break; default: - lv = _lexValue(lv, token, next, &state); + _lexValue(lv, token, next, &state); break; } break; @@ -262,17 +254,13 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { next += token - 'a' + 10; break; case ':': - lv->token.type = TOKEN_SEGMENT_TYPE; - lv->token.uintValue = next; - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_UINT_TYPE; - lv->next = lvNext; - lv = lvNext; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_SEGMENT_TYPE; + lvNext->uintValue = next; next = 0; break; default: - lv = _lexValue(lv, token, next, &state); + _lexValue(lv, token, next, &state); break; } break; @@ -302,7 +290,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { state = LEX_EXPECT_DECIMAL; break; default: - lv = _lexValue(lv, token, next, &state); + _lexValue(lv, token, next, &state); break; } break; @@ -317,7 +305,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '^': case '<': case '>': - lv = _lexOperator(lv, token); + _lexOperator(lv, token); state = LEX_ROOT; break; default: @@ -335,24 +323,23 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case LEX_EXPECT_DECIMAL: case LEX_EXPECT_HEX: case LEX_EXPECT_PREFIX: - lv->token.type = TOKEN_UINT_TYPE; - lv->token.uintValue = next; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; break; case LEX_EXPECT_IDENTIFIER: - lv->token.type = TOKEN_IDENTIFIER_TYPE; - lv->token.identifierValue = strndup(tokenStart, string - tokenStart); + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart); break; case LEX_EXPECT_OPERATOR: - lvNext = malloc(sizeof(struct LexVector)); - lvNext->next = lv->next; - lvNext->token.type = TOKEN_CLOSE_PAREN_TYPE; - lv->next = lvNext; break; case LEX_EXPECT_BINARY_FIRST: case LEX_EXPECT_HEX_FIRST: case LEX_ERROR: default: - lv->token.type = TOKEN_ERROR_TYPE; + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_ERROR_TYPE; break; } return adjusted; @@ -382,65 +369,67 @@ static struct ParseTree* _parseTreeCreate() { return tree; } -static struct LexVector* _parseExpression(struct ParseTree* tree, struct LexVector* lv, int precedence, int openParens) { +static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, size_t i, int precedence, int openParens) { struct ParseTree* newTree = 0; - while (lv) { + while (i < LexVectorSize(lv)) { + struct Token* token = LexVectorGetPointer(lv, i); int newPrecedence; - switch (lv->token.type) { + switch (token->type) { case TOKEN_IDENTIFIER_TYPE: case TOKEN_UINT_TYPE: if (tree->token.type == TOKEN_ERROR_TYPE) { - tree->token = lv->token; - lv = lv->next; + tree->token = *token; + if (token->type == TOKEN_IDENTIFIER_TYPE) { + tree->token.identifierValue = strdup(token->identifierValue); + } + ++i; } else { tree->token.type = TOKEN_ERROR_TYPE; - return 0; + return i + 1; } break; case TOKEN_SEGMENT_TYPE: tree->lhs = _parseTreeCreate(); tree->lhs->token.type = TOKEN_UINT_TYPE; - tree->lhs->token.uintValue = lv->token.uintValue; + tree->lhs->token.uintValue = token->uintValue; tree->rhs = _parseTreeCreate(); tree->token.type = TOKEN_SEGMENT_TYPE; - lv = _parseExpression(tree->rhs, lv->next, precedence, openParens); + i = _parseExpression(tree->rhs, lv, i + 1, precedence, openParens); if (tree->token.type == TOKEN_ERROR_TYPE) { tree->token.type = TOKEN_ERROR_TYPE; } break; case TOKEN_OPEN_PAREN_TYPE: - lv = _parseExpression(tree, lv->next, INT_MAX, openParens + 1); + i = _parseExpression(tree, lv, i + 1, INT_MAX, openParens + 1); break; case TOKEN_CLOSE_PAREN_TYPE: if (openParens <= 0) { tree->token.type = TOKEN_ERROR_TYPE; - return 0; } - return lv->next; - break; + return i + 1; case TOKEN_OPERATOR_TYPE: - newPrecedence = _operatorPrecedence[lv->token.operatorValue]; + newPrecedence = _operatorPrecedence[token->operatorValue]; if (newPrecedence < precedence) { newTree = _parseTreeCreate(); *newTree = *tree; tree->lhs = newTree; tree->rhs = _parseTreeCreate(); - tree->token = lv->token; - lv = _parseExpression(tree->rhs, lv->next, newPrecedence, openParens); + tree->token = *token; + i = _parseExpression(tree->rhs, lv, i + 1, newPrecedence, openParens); if (tree->token.type == TOKEN_ERROR_TYPE) { tree->token.type = TOKEN_ERROR_TYPE; } } else { - return lv; + return i; } break; case TOKEN_ERROR_TYPE: tree->token.type = TOKEN_ERROR_TYPE; - return 0; + return i + 1; } } - return 0; + return i; } void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv) { @@ -452,14 +441,16 @@ void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv) { tree->lhs = 0; tree->rhs = 0; - _parseExpression(tree, lv, INT_MAX, 0); + _parseExpression(tree, lv, 0, INT_MAX, 0); } void lexFree(struct LexVector* lv) { - while (lv) { - struct LexVector* lvNext = lv->next; - free(lv); - lv = lvNext; + size_t i; + for (i = 0; i < LexVectorSize(lv); ++i) { + struct Token* token = LexVectorGetPointer(lv, i); + if (token->type == TOKEN_IDENTIFIER_TYPE) { + free(token->identifierValue); + } } } From 47605b40e73704734a0f4187e323040280ad87d9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 02:20:58 -0500 Subject: [PATCH 093/152] Debugger: Improve paren parsing, add lexing tests --- CMakeLists.txt | 3 + src/debugger/parser.c | 23 +- src/debugger/test/parser.c | 459 +++++++++++++++++++++++++++++++++++++ 3 files changed, 481 insertions(+), 4 deletions(-) create mode 100644 src/debugger/test/parser.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a9ab2c69b..56b70a39f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -415,6 +415,8 @@ set(DEBUGGER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/symbols.c ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/cli-debugger.c) +file(GLOB DEBUGGER_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/debugger/test/*.c) + set(FEATURE_SRC) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6") @@ -686,6 +688,7 @@ endif() if(USE_DEBUGGERS) list(APPEND FEATURE_SRC ${DEBUGGER_SRC}) + list(APPEND TEST_SRC ${DEBUGGER_TEST_SRC}) list(APPEND FEATURES DEBUGGERS) endif() diff --git a/src/debugger/parser.c b/src/debugger/parser.c index 8adc6fd8c..076b91a38 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -308,6 +308,11 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { _lexOperator(lv, token); state = LEX_ROOT; break; + case ')': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_CLOSE_PAREN_TYPE; + state = LEX_EXPECT_OPERATOR; + break; default: state = LEX_ERROR; } @@ -332,6 +337,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { lvNext->type = TOKEN_IDENTIFIER_TYPE; lvNext->identifierValue = strndup(tokenStart, string - tokenStart); break; + case LEX_ROOT: case LEX_EXPECT_OPERATOR: break; case LEX_EXPECT_BINARY_FIRST: @@ -369,7 +375,7 @@ static struct ParseTree* _parseTreeCreate() { return tree; } -static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, size_t i, int precedence, int openParens) { +static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, size_t i, int precedence, int* openParens) { struct ParseTree* newTree = 0; while (i < LexVectorSize(lv)) { struct Token* token = LexVectorGetPointer(lv, i); @@ -400,12 +406,14 @@ static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, siz } break; case TOKEN_OPEN_PAREN_TYPE: - i = _parseExpression(tree, lv, i + 1, INT_MAX, openParens + 1); + ++*openParens; + i = _parseExpression(tree, lv, i + 1, INT_MAX, openParens); break; case TOKEN_CLOSE_PAREN_TYPE: - if (openParens <= 0) { + if (*openParens <= 0) { tree->token.type = TOKEN_ERROR_TYPE; } + --*openParens; return i + 1; case TOKEN_OPERATOR_TYPE: newPrecedence = _operatorPrecedence[token->operatorValue]; @@ -441,7 +449,14 @@ void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv) { tree->lhs = 0; tree->rhs = 0; - _parseExpression(tree, lv, 0, INT_MAX, 0); + int openParens = 0; + _parseExpression(tree, lv, 0, INT_MAX, &openParens); + if (openParens) { + if (tree->token.type == TOKEN_IDENTIFIER_TYPE) { + free(tree->token.identifierValue); + } + tree->token.type = TOKEN_ERROR_TYPE; + } } void lexFree(struct LexVector* lv) { diff --git a/src/debugger/test/parser.c b/src/debugger/test/parser.c new file mode 100644 index 000000000..f545fbe45 --- /dev/null +++ b/src/debugger/test/parser.c @@ -0,0 +1,459 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +#define LEX(STR) \ + struct LexVector lv; \ + LexVectorInit(&lv, 0); \ + size_t adjusted = lexExpression(&lv, STR, strlen(STR)); \ + assert_false(adjusted > strlen(STR)) + +#define UNLEX \ + lexFree(&lv); \ + LexVectorDeinit(&lv) + +M_TEST_DEFINE(lexEmpty) { + LEX(""); + + assert_int_equal(LexVectorSize(&lv), 0); + + UNLEX; +} + +M_TEST_DEFINE(lexInt) { + LEX("0"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 0); + + UNLEX; +} + +M_TEST_DEFINE(lexDecimal) { + LEX("10"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 10); + + UNLEX; +} + +M_TEST_DEFINE(lexBinary) { + LEX("0b10"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 2); + + UNLEX; +} + +M_TEST_DEFINE(lexHex) { + LEX("0x10"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 0x10); + + UNLEX; +} + +M_TEST_DEFINE(lexInvalidDecimal) { + LEX("1a"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexInvalidBinary) { + LEX("0b12"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexInvalidHex) { + LEX("0x1g"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexTruncatedBinary) { + LEX("0b"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexTruncatedHex) { + LEX("0x"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifier) { + LEX("x"); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + + UNLEX; +} + +M_TEST_DEFINE(lexAddOperator) { + LEX("1+"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_ADD); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierAddOperator) { + LEX("x+"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_ADD); + + UNLEX; +} + +M_TEST_DEFINE(lexSubOperator) { + LEX("1-"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_SUBTRACT); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierSubOperator) { + LEX("x-"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_SUBTRACT); + + UNLEX; +} + +M_TEST_DEFINE(lexMulOperator) { + LEX("1*"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_MULTIPLY); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierMulOperator) { + LEX("x*"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_MULTIPLY); + + UNLEX; +} + +M_TEST_DEFINE(lexDivOperator) { + LEX("1/"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_DIVIDE); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierDivOperator) { + LEX("x/"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_DIVIDE); + + UNLEX; +} + +M_TEST_DEFINE(lexAndOperator) { + LEX("1&"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_AND); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierAndOperator) { + LEX("x&"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_AND); + + UNLEX; +} + +M_TEST_DEFINE(lexOrOperator) { + LEX("1|"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_OR); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierOrOperator) { + LEX("x|"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_OR); + + UNLEX; +} + +M_TEST_DEFINE(lexXorOperator) { + LEX("1^"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_XOR); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierXorOperator) { + LEX("x^"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_XOR); + + UNLEX; +} + +M_TEST_DEFINE(lexLessOperator) { + LEX("1<"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_LESS); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierLessOperator) { + LEX("x<"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_LESS); + + UNLEX; +} + +M_TEST_DEFINE(lexGreaterOperator) { + LEX("1>"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_GREATER); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierGreaterOperator) { + LEX("x>"); + + assert_int_equal(LexVectorSize(&lv), 2); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_GREATER); + + UNLEX; +} + +M_TEST_DEFINE(lexSimpleExpression) { + LEX("1+1"); + + assert_int_equal(LexVectorSize(&lv), 3); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 2)->uintValue, 1); + + UNLEX; +} + +M_TEST_DEFINE(lexOpenParen) { + LEX("("); + + assert_int_equal(LexVectorSize(&lv), 1); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexCloseParen) { + LEX("(0)"); + + assert_int_equal(LexVectorSize(&lv), 3); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->uintValue, 0); + assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexIdentifierCloseParen) { + LEX("(x)"); + + assert_int_equal(LexVectorSize(&lv), 3); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(&lv, 1)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexParentheticalExpression) { + LEX("(1+1)"); + + assert_int_equal(LexVectorSize(&lv), 5); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(&lv, 3)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 3)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 4)->type, TOKEN_CLOSE_PAREN_TYPE); + + UNLEX; +} + +M_TEST_DEFINE(lexNestedParentheticalExpression) { + LEX("(1+(2+3))"); + + assert_int_equal(LexVectorSize(&lv), 9); + assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(&lv, 3)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 4)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 4)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(&lv, 5)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 5)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(&lv, 6)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 6)->uintValue, 3); + assert_int_equal(LexVectorGetPointer(&lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(&lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); + + UNLEX; +} + +M_TEST_SUITE_DEFINE(Lexer, + cmocka_unit_test(lexEmpty), + cmocka_unit_test(lexInt), + cmocka_unit_test(lexDecimal), + cmocka_unit_test(lexHex), + cmocka_unit_test(lexBinary), + cmocka_unit_test(lexInvalidDecimal), + cmocka_unit_test(lexInvalidHex), + cmocka_unit_test(lexInvalidBinary), + cmocka_unit_test(lexTruncatedHex), + cmocka_unit_test(lexTruncatedBinary), + cmocka_unit_test(lexIdentifier), + cmocka_unit_test(lexAddOperator), + cmocka_unit_test(lexIdentifierAddOperator), + cmocka_unit_test(lexSubOperator), + cmocka_unit_test(lexIdentifierSubOperator), + cmocka_unit_test(lexMulOperator), + cmocka_unit_test(lexIdentifierMulOperator), + cmocka_unit_test(lexDivOperator), + cmocka_unit_test(lexIdentifierDivOperator), + cmocka_unit_test(lexAndOperator), + cmocka_unit_test(lexIdentifierAndOperator), + cmocka_unit_test(lexOrOperator), + cmocka_unit_test(lexIdentifierOrOperator), + cmocka_unit_test(lexXorOperator), + cmocka_unit_test(lexIdentifierXorOperator), + cmocka_unit_test(lexLessOperator), + cmocka_unit_test(lexIdentifierLessOperator), + cmocka_unit_test(lexGreaterOperator), + cmocka_unit_test(lexIdentifierGreaterOperator), + cmocka_unit_test(lexSimpleExpression), + cmocka_unit_test(lexOpenParen), + cmocka_unit_test(lexCloseParen), + cmocka_unit_test(lexIdentifierCloseParen), + cmocka_unit_test(lexParentheticalExpression), + cmocka_unit_test(lexNestedParentheticalExpression)) From ab2437fcb8e2216b1504f60bf3835d255ccff0d4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 12:23:42 -0500 Subject: [PATCH 094/152] Debugger: Simple parser tests --- src/debugger/test/lexer.c | 401 +++++++++++++++++++++++++++++ src/debugger/test/parser.c | 506 ++++++------------------------------- 2 files changed, 484 insertions(+), 423 deletions(-) create mode 100644 src/debugger/test/lexer.c diff --git a/src/debugger/test/lexer.c b/src/debugger/test/lexer.c new file mode 100644 index 000000000..a785a20e6 --- /dev/null +++ b/src/debugger/test/lexer.c @@ -0,0 +1,401 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "util/test/suite.h" + +#include + +#define LEX(STR) \ + struct LexVector* lv = *state; \ + lexFree(lv); \ + LexVectorClear(lv); \ + size_t adjusted = lexExpression(lv, STR, strlen(STR)); \ + assert_false(adjusted > strlen(STR)) + +M_TEST_SUITE_SETUP(Lexer) { + struct LexVector* lv = malloc(sizeof(struct LexVector)); + LexVectorInit(lv, 0); + *state = lv; + return 0; +} + +M_TEST_SUITE_TEARDOWN(Lexer) { + struct LexVector* lv = *state; + lexFree(lv); + LexVectorDeinit(lv); + free(lv); + return 0; +} + +M_TEST_DEFINE(lexEmpty) { + LEX(""); + + assert_int_equal(LexVectorSize(lv), 0); +} + +M_TEST_DEFINE(lexInt) { + LEX("0"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 0); +} + +M_TEST_DEFINE(lexDecimal) { + LEX("10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 10); +} + +M_TEST_DEFINE(lexBinary) { + LEX("0b10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 2); +} + +M_TEST_DEFINE(lexHex) { + LEX("0x10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 0x10); +} + +M_TEST_DEFINE(lexInvalidDecimal) { + LEX("1a"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexInvalidBinary) { + LEX("0b12"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexInvalidHex) { + LEX("0x1g"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexTruncatedBinary) { + LEX("0b"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexTruncatedHex) { + LEX("0x"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifier) { + LEX("x"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); +} + +M_TEST_DEFINE(lexAddOperator) { + LEX("1+"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); +} + +M_TEST_DEFINE(lexIdentifierAddOperator) { + LEX("x+"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); +} + +M_TEST_DEFINE(lexSubOperator) { + LEX("1-"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SUBTRACT); +} + +M_TEST_DEFINE(lexIdentifierSubOperator) { + LEX("x-"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SUBTRACT); +} + +M_TEST_DEFINE(lexMulOperator) { + LEX("1*"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MULTIPLY); +} + +M_TEST_DEFINE(lexIdentifierMulOperator) { + LEX("x*"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MULTIPLY); +} + +M_TEST_DEFINE(lexDivOperator) { + LEX("1/"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_DIVIDE); +} + +M_TEST_DEFINE(lexIdentifierDivOperator) { + LEX("x/"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_DIVIDE); +} + +M_TEST_DEFINE(lexAndOperator) { + LEX("1&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); +} + +M_TEST_DEFINE(lexIdentifierAndOperator) { + LEX("x&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); +} + +M_TEST_DEFINE(lexOrOperator) { + LEX("1|"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); +} + +M_TEST_DEFINE(lexIdentifierOrOperator) { + LEX("x|"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); +} + +M_TEST_DEFINE(lexXorOperator) { + LEX("1^"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_XOR); +} + +M_TEST_DEFINE(lexIdentifierXorOperator) { + LEX("x^"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_XOR); +} + +M_TEST_DEFINE(lexLessOperator) { + LEX("1<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); +} + +M_TEST_DEFINE(lexIdentifierLessOperator) { + LEX("x<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); +} + +M_TEST_DEFINE(lexGreaterOperator) { + LEX("1>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); +} + +M_TEST_DEFINE(lexIdentifierGreaterOperator) { + LEX("x>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); +} + +M_TEST_DEFINE(lexSimpleExpression) { + LEX("1+1"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->uintValue, 1); +} + +M_TEST_DEFINE(lexOpenParen) { + LEX("("); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); +} + +M_TEST_DEFINE(lexCloseParen) { + LEX("(0)"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 0); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexIdentifierCloseParen) { + LEX("(x)"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 1)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexParentheticalExpression) { + LEX("(1+1)"); + + assert_int_equal(LexVectorSize(lv), 5); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 3)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexNestedParentheticalExpression) { + LEX("(1+(2+3))"); + + assert_int_equal(LexVectorSize(lv), 9); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 5)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 5)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 6)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 6)->uintValue, 3); + assert_int_equal(LexVectorGetPointer(lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, + cmocka_unit_test(lexEmpty), + cmocka_unit_test(lexInt), + cmocka_unit_test(lexDecimal), + cmocka_unit_test(lexHex), + cmocka_unit_test(lexBinary), + cmocka_unit_test(lexInvalidDecimal), + cmocka_unit_test(lexInvalidHex), + cmocka_unit_test(lexInvalidBinary), + cmocka_unit_test(lexTruncatedHex), + cmocka_unit_test(lexTruncatedBinary), + cmocka_unit_test(lexIdentifier), + cmocka_unit_test(lexAddOperator), + cmocka_unit_test(lexIdentifierAddOperator), + cmocka_unit_test(lexSubOperator), + cmocka_unit_test(lexIdentifierSubOperator), + cmocka_unit_test(lexMulOperator), + cmocka_unit_test(lexIdentifierMulOperator), + cmocka_unit_test(lexDivOperator), + cmocka_unit_test(lexIdentifierDivOperator), + cmocka_unit_test(lexAndOperator), + cmocka_unit_test(lexIdentifierAndOperator), + cmocka_unit_test(lexOrOperator), + cmocka_unit_test(lexIdentifierOrOperator), + cmocka_unit_test(lexXorOperator), + cmocka_unit_test(lexIdentifierXorOperator), + cmocka_unit_test(lexLessOperator), + cmocka_unit_test(lexIdentifierLessOperator), + cmocka_unit_test(lexGreaterOperator), + cmocka_unit_test(lexIdentifierGreaterOperator), + cmocka_unit_test(lexSimpleExpression), + cmocka_unit_test(lexOpenParen), + cmocka_unit_test(lexCloseParen), + cmocka_unit_test(lexIdentifierCloseParen), + cmocka_unit_test(lexParentheticalExpression), + cmocka_unit_test(lexNestedParentheticalExpression)) diff --git a/src/debugger/test/parser.c b/src/debugger/test/parser.c index f545fbe45..dd566be9f 100644 --- a/src/debugger/test/parser.c +++ b/src/debugger/test/parser.c @@ -7,453 +7,113 @@ #include -#define LEX(STR) \ - struct LexVector lv; \ - LexVectorInit(&lv, 0); \ - size_t adjusted = lexExpression(&lv, STR, strlen(STR)); \ - assert_false(adjusted > strlen(STR)) +struct LPTest { + struct LexVector lv; + struct ParseTree tree; +}; -#define UNLEX \ - lexFree(&lv); \ - LexVectorDeinit(&lv) +#define PARSE(STR) \ + struct LPTest* lp = *state; \ + lexFree(&lp->lv); \ + LexVectorClear(&lp->lv); \ + size_t adjusted = lexExpression(&lp->lv, STR, strlen(STR)); \ + assert_false(adjusted > strlen(STR)); \ + struct ParseTree* tree = &lp->tree; \ + parseLexedExpression(tree, &lp->lv) -M_TEST_DEFINE(lexEmpty) { - LEX(""); - - assert_int_equal(LexVectorSize(&lv), 0); - - UNLEX; +M_TEST_SUITE_SETUP(Parser) { + struct LPTest* lp = malloc(sizeof(struct LPTest)); + LexVectorInit(&lp->lv, 0); + *state = lp; + return 0; } -M_TEST_DEFINE(lexInt) { - LEX("0"); - - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 0); - - UNLEX; +M_TEST_SUITE_TEARDOWN(Parser) { + struct LPTest* lp = *state; + parseFree(lp->tree.lhs); \ + parseFree(lp->tree.rhs); \ + lexFree(&lp->lv); + LexVectorDeinit(&lp->lv); + free(lp); + return 0; } -M_TEST_DEFINE(lexDecimal) { - LEX("10"); +M_TEST_DEFINE(parseEmpty) { + PARSE(""); - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 10); - - UNLEX; + assert_int_equal(tree->token.type, TOKEN_ERROR_TYPE); } -M_TEST_DEFINE(lexBinary) { - LEX("0b10"); +M_TEST_DEFINE(parseInt) { + PARSE("0"); - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 2); - - UNLEX; + assert_int_equal(tree->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->token.uintValue, 0); } -M_TEST_DEFINE(lexHex) { - LEX("0x10"); +M_TEST_DEFINE(parseLexError) { + PARSE("@"); - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 0x10); - - UNLEX; + assert_int_equal(tree->token.type, TOKEN_ERROR_TYPE); } -M_TEST_DEFINE(lexInvalidDecimal) { - LEX("1a"); +M_TEST_DEFINE(parseSimpleExpression) { + PARSE("1+2"); - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); - - UNLEX; + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_ADD); + assert_int_equal(tree->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->token.uintValue, 1); + assert_int_equal(tree->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->token.uintValue, 2); } -M_TEST_DEFINE(lexInvalidBinary) { - LEX("0b12"); +M_TEST_DEFINE(parseAddMultplyExpression) { + PARSE("1+2*3"); - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); - - UNLEX; + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_ADD); + assert_int_equal(tree->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->token.uintValue, 1); + assert_int_equal(tree->rhs->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->rhs->token.uintValue, OP_MULTIPLY); + assert_int_equal(tree->rhs->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->lhs->token.uintValue, 2); + assert_int_equal(tree->rhs->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->rhs->token.uintValue, 3); } -M_TEST_DEFINE(lexInvalidHex) { - LEX("0x1g"); +M_TEST_DEFINE(parseParentheticalExpression) { + PARSE("(1+2)"); - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); - - UNLEX; + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_ADD); + assert_int_equal(tree->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->token.uintValue, 1); + assert_int_equal(tree->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->token.uintValue, 2); } -M_TEST_DEFINE(lexTruncatedBinary) { - LEX("0b"); +M_TEST_DEFINE(parseParentheticalAddMultplyExpression) { + PARSE("(1+2)*3"); - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); - - UNLEX; + assert_int_equal(tree->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->token.operatorValue, OP_MULTIPLY); + assert_int_equal(tree->lhs->token.type, TOKEN_OPERATOR_TYPE); + assert_int_equal(tree->lhs->token.uintValue, OP_ADD); + assert_int_equal(tree->lhs->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->lhs->token.uintValue, 1); + assert_int_equal(tree->lhs->lhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->lhs->rhs->token.uintValue, 2); + assert_int_equal(tree->rhs->token.type, TOKEN_UINT_TYPE); + assert_int_equal(tree->rhs->token.uintValue, 3); } -M_TEST_DEFINE(lexTruncatedHex) { - LEX("0x"); - - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_ERROR_TYPE); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifier) { - LEX("x"); - - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - - UNLEX; -} - -M_TEST_DEFINE(lexAddOperator) { - LEX("1+"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_ADD); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierAddOperator) { - LEX("x+"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_ADD); - - UNLEX; -} - -M_TEST_DEFINE(lexSubOperator) { - LEX("1-"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_SUBTRACT); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierSubOperator) { - LEX("x-"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_SUBTRACT); - - UNLEX; -} - -M_TEST_DEFINE(lexMulOperator) { - LEX("1*"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_MULTIPLY); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierMulOperator) { - LEX("x*"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_MULTIPLY); - - UNLEX; -} - -M_TEST_DEFINE(lexDivOperator) { - LEX("1/"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_DIVIDE); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierDivOperator) { - LEX("x/"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_DIVIDE); - - UNLEX; -} - -M_TEST_DEFINE(lexAndOperator) { - LEX("1&"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_AND); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierAndOperator) { - LEX("x&"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_AND); - - UNLEX; -} - -M_TEST_DEFINE(lexOrOperator) { - LEX("1|"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_OR); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierOrOperator) { - LEX("x|"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_OR); - - UNLEX; -} - -M_TEST_DEFINE(lexXorOperator) { - LEX("1^"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_XOR); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierXorOperator) { - LEX("x^"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_XOR); - - UNLEX; -} - -M_TEST_DEFINE(lexLessOperator) { - LEX("1<"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_LESS); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierLessOperator) { - LEX("x<"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_LESS); - - UNLEX; -} - -M_TEST_DEFINE(lexGreaterOperator) { - LEX("1>"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_GREATER); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierGreaterOperator) { - LEX("x>"); - - assert_int_equal(LexVectorSize(&lv), 2); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 0)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_GREATER); - - UNLEX; -} - -M_TEST_DEFINE(lexSimpleExpression) { - LEX("1+1"); - - assert_int_equal(LexVectorSize(&lv), 3); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 0)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->operatorValue, OP_ADD); - assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 2)->uintValue, 1); - - UNLEX; -} - -M_TEST_DEFINE(lexOpenParen) { - LEX("("); - - assert_int_equal(LexVectorSize(&lv), 1); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); - - UNLEX; -} - -M_TEST_DEFINE(lexCloseParen) { - LEX("(0)"); - - assert_int_equal(LexVectorSize(&lv), 3); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->uintValue, 0); - assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); - - UNLEX; -} - -M_TEST_DEFINE(lexIdentifierCloseParen) { - LEX("(x)"); - - assert_int_equal(LexVectorSize(&lv), 3); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_IDENTIFIER_TYPE); - assert_string_equal(LexVectorGetPointer(&lv, 1)->identifierValue, "x"); - assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_CLOSE_PAREN_TYPE); - - UNLEX; -} - -M_TEST_DEFINE(lexParentheticalExpression) { - LEX("(1+1)"); - - assert_int_equal(LexVectorSize(&lv), 5); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 2)->operatorValue, OP_ADD); - assert_int_equal(LexVectorGetPointer(&lv, 3)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 3)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 4)->type, TOKEN_CLOSE_PAREN_TYPE); - - UNLEX; -} - -M_TEST_DEFINE(lexNestedParentheticalExpression) { - LEX("(1+(2+3))"); - - assert_int_equal(LexVectorSize(&lv), 9); - assert_int_equal(LexVectorGetPointer(&lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 1)->uintValue, 1); - assert_int_equal(LexVectorGetPointer(&lv, 2)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 2)->operatorValue, OP_ADD); - assert_int_equal(LexVectorGetPointer(&lv, 3)->type, TOKEN_OPEN_PAREN_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 4)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 4)->uintValue, 2); - assert_int_equal(LexVectorGetPointer(&lv, 5)->type, TOKEN_OPERATOR_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 5)->operatorValue, OP_ADD); - assert_int_equal(LexVectorGetPointer(&lv, 6)->type, TOKEN_UINT_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 6)->uintValue, 3); - assert_int_equal(LexVectorGetPointer(&lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE); - assert_int_equal(LexVectorGetPointer(&lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); - - UNLEX; -} - -M_TEST_SUITE_DEFINE(Lexer, - cmocka_unit_test(lexEmpty), - cmocka_unit_test(lexInt), - cmocka_unit_test(lexDecimal), - cmocka_unit_test(lexHex), - cmocka_unit_test(lexBinary), - cmocka_unit_test(lexInvalidDecimal), - cmocka_unit_test(lexInvalidHex), - cmocka_unit_test(lexInvalidBinary), - cmocka_unit_test(lexTruncatedHex), - cmocka_unit_test(lexTruncatedBinary), - cmocka_unit_test(lexIdentifier), - cmocka_unit_test(lexAddOperator), - cmocka_unit_test(lexIdentifierAddOperator), - cmocka_unit_test(lexSubOperator), - cmocka_unit_test(lexIdentifierSubOperator), - cmocka_unit_test(lexMulOperator), - cmocka_unit_test(lexIdentifierMulOperator), - cmocka_unit_test(lexDivOperator), - cmocka_unit_test(lexIdentifierDivOperator), - cmocka_unit_test(lexAndOperator), - cmocka_unit_test(lexIdentifierAndOperator), - cmocka_unit_test(lexOrOperator), - cmocka_unit_test(lexIdentifierOrOperator), - cmocka_unit_test(lexXorOperator), - cmocka_unit_test(lexIdentifierXorOperator), - cmocka_unit_test(lexLessOperator), - cmocka_unit_test(lexIdentifierLessOperator), - cmocka_unit_test(lexGreaterOperator), - cmocka_unit_test(lexIdentifierGreaterOperator), - cmocka_unit_test(lexSimpleExpression), - cmocka_unit_test(lexOpenParen), - cmocka_unit_test(lexCloseParen), - cmocka_unit_test(lexIdentifierCloseParen), - cmocka_unit_test(lexParentheticalExpression), - cmocka_unit_test(lexNestedParentheticalExpression)) +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Parser, + cmocka_unit_test(parseEmpty), + cmocka_unit_test(parseInt), + cmocka_unit_test(parseLexError), + cmocka_unit_test(parseSimpleExpression), + cmocka_unit_test(parseAddMultplyExpression), + cmocka_unit_test(parseParentheticalExpression), + cmocka_unit_test(parseParentheticalAddMultplyExpression)) From 49675d7c58e548d66fe418a9ceb5ab32c5ac6c8d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 12:32:24 -0500 Subject: [PATCH 095/152] Debugger: More tests, some sigil fixes --- src/debugger/parser.c | 6 ++++- src/debugger/test/lexer.c | 47 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/debugger/parser.c b/src/debugger/parser.c index 076b91a38..1aa0a246d 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -131,7 +131,11 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { next = 0; break; case '$': - state = LEX_EXPECT_HEX; + state = LEX_EXPECT_HEX_FIRST; + next = 0; + break; + case '%': + state = LEX_EXPECT_BINARY_FIRST; next = 0; break; case '(': diff --git a/src/debugger/test/lexer.c b/src/debugger/test/lexer.c index a785a20e6..646ebcb03 100644 --- a/src/debugger/test/lexer.c +++ b/src/debugger/test/lexer.c @@ -59,6 +59,14 @@ M_TEST_DEFINE(lexBinary) { assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 2); } +M_TEST_DEFINE(lexSigilBinary) { + LEX("%10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 2); +} + M_TEST_DEFINE(lexHex) { LEX("0x10"); @@ -67,6 +75,14 @@ M_TEST_DEFINE(lexHex) { assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 0x10); } +M_TEST_DEFINE(lexSigilHex) { + LEX("$10"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 0x10); +} + M_TEST_DEFINE(lexInvalidDecimal) { LEX("1a"); @@ -95,6 +111,20 @@ M_TEST_DEFINE(lexTruncatedBinary) { assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); } +M_TEST_DEFINE(lexTruncatedSigilBinary) { + LEX("%"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexTruncatedSigilHex) { + LEX("$"); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); +} + M_TEST_DEFINE(lexTruncatedHex) { LEX("0x"); @@ -102,6 +132,16 @@ M_TEST_DEFINE(lexTruncatedHex) { assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_ERROR_TYPE); } +M_TEST_DEFINE(lexSigilSegmentHex) { + LEX("$01:0010"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_SEGMENT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 0x10); +} + M_TEST_DEFINE(lexIdentifier) { LEX("x"); @@ -367,13 +407,18 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, cmocka_unit_test(lexEmpty), cmocka_unit_test(lexInt), cmocka_unit_test(lexDecimal), - cmocka_unit_test(lexHex), cmocka_unit_test(lexBinary), + cmocka_unit_test(lexSigilBinary), + cmocka_unit_test(lexHex), + cmocka_unit_test(lexSigilHex), + cmocka_unit_test(lexSigilSegmentHex), cmocka_unit_test(lexInvalidDecimal), cmocka_unit_test(lexInvalidHex), cmocka_unit_test(lexInvalidBinary), cmocka_unit_test(lexTruncatedHex), + cmocka_unit_test(lexTruncatedSigilHex), cmocka_unit_test(lexTruncatedBinary), + cmocka_unit_test(lexTruncatedSigilBinary), cmocka_unit_test(lexIdentifier), cmocka_unit_test(lexAddOperator), cmocka_unit_test(lexIdentifierAddOperator), From 5d98f9c963100c5f369b653d843b89456057b3db Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 14:01:55 -0500 Subject: [PATCH 096/152] Debugger: Add modulo operator --- include/mgba/internal/debugger/parser.h | 1 + src/debugger/parser.c | 40 +++++++++++++++++-------- src/debugger/test/lexer.c | 22 ++++++++++++++ 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h index 78654c524..62b8f8d41 100644 --- a/include/mgba/internal/debugger/parser.h +++ b/include/mgba/internal/debugger/parser.h @@ -18,6 +18,7 @@ enum Operation { OP_SUBTRACT, OP_MULTIPLY, OP_DIVIDE, + OP_MODULO, OP_AND, OP_OR, OP_XOR, diff --git a/src/debugger/parser.c b/src/debugger/parser.c index 1aa0a246d..bc6ca1b14 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -39,6 +39,9 @@ static void _lexOperator(struct LexVector* lv, char operator) { case '/': lvNext->operatorValue = OP_DIVIDE; break; + case '%': + lvNext->operatorValue = OP_MODULO; + break; case '&': lvNext->operatorValue = OP_AND; break; @@ -68,6 +71,7 @@ static void _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexS case '-': case '*': case '/': + case '%': case '&': case '|': case '^': @@ -158,6 +162,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '%': case '&': case '|': case '^': @@ -304,6 +309,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '-': case '*': case '/': + case '%': case '&': case '|': case '^': @@ -356,19 +362,20 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { } static const int _operatorPrecedence[] = { - 14, - 4, - 4, - 3, - 3, - 8, - 10, - 9, - 6, - 6, - 7, - 6, - 6 + [OP_ASSIGN] = 14, + [OP_ADD] = 4, + [OP_SUBTRACT] = 4, + [OP_MULTIPLY] = 3, + [OP_DIVIDE] = 3, + [OP_MODULO] = 3, + [OP_AND] = 8, + [OP_OR] = 10, + [OP_XOR] = 9, + [OP_LESS] = 6, + [OP_GREATER] = 6, + [OP_EQUAL] = 7, + [OP_LE] = 6, + [OP_GE] = 6 }; static struct ParseTree* _parseTreeCreate() { @@ -508,6 +515,13 @@ static bool _performOperation(enum Operation operation, int32_t current, int32_t return false; } break; + case OP_MODULO: + if (next != 0) { + current %= next; + } else { + return false; + } + break; case OP_AND: current &= next; break; diff --git a/src/debugger/test/lexer.c b/src/debugger/test/lexer.c index 646ebcb03..034887a18 100644 --- a/src/debugger/test/lexer.c +++ b/src/debugger/test/lexer.c @@ -230,6 +230,26 @@ M_TEST_DEFINE(lexIdentifierDivOperator) { assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_DIVIDE); } +M_TEST_DEFINE(lexModOperator) { + LEX("1%"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MODULO); +} + +M_TEST_DEFINE(lexIdentifierModOperator) { + LEX("x%"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_MODULO); +} + M_TEST_DEFINE(lexAndOperator) { LEX("1&"); @@ -428,6 +448,8 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, cmocka_unit_test(lexIdentifierMulOperator), cmocka_unit_test(lexDivOperator), cmocka_unit_test(lexIdentifierDivOperator), + cmocka_unit_test(lexModOperator), + cmocka_unit_test(lexIdentifierModOperator), cmocka_unit_test(lexAndOperator), cmocka_unit_test(lexIdentifierAndOperator), cmocka_unit_test(lexOrOperator), From f5ef07bebb7cdc0df60c63cc739c44e95ec0650e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 14:46:21 -0500 Subject: [PATCH 097/152] Add two-character operators --- include/mgba/internal/debugger/parser.h | 8 + src/debugger/parser.c | 135 +++++++++- src/debugger/test/lexer.c | 320 ++++++++++++++++++++++++ 3 files changed, 454 insertions(+), 9 deletions(-) diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h index 62b8f8d41..99c43dc73 100644 --- a/include/mgba/internal/debugger/parser.h +++ b/include/mgba/internal/debugger/parser.h @@ -25,8 +25,16 @@ enum Operation { OP_LESS, OP_GREATER, OP_EQUAL, + OP_NOT_EQUAL, + OP_LOGICAL_AND, + OP_LOGICAL_OR, OP_LE, OP_GE, + OP_NEGATE, + OP_FLIP, + OP_NOT, + OP_SHIFT_L, + OP_SHIFT_R, }; struct Token { diff --git a/src/debugger/parser.c b/src/debugger/parser.c index bc6ca1b14..7cc778208 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -20,13 +20,84 @@ enum LexState { LEX_EXPECT_HEX_FIRST, LEX_EXPECT_HEX, LEX_EXPECT_PREFIX, - LEX_EXPECT_OPERATOR + LEX_EXPECT_OPERATOR, + LEX_EXPECT_OPERATOR2, }; -static void _lexOperator(struct LexVector* lv, char operator) { +static void _lexOperator(struct LexVector* lv, char operator, enum LexState* state) { + if (*state == LEX_EXPECT_OPERATOR2) { + struct Token* lvNext = LexVectorGetPointer(lv, LexVectorSize(lv) - 1); + if (lvNext->type != TOKEN_OPERATOR_TYPE) { + lvNext->type = TOKEN_ERROR_TYPE; + *state = LEX_ERROR; + return; + } + switch (lvNext->operatorValue) { + case OP_AND: + if (operator == '&') { + lvNext->operatorValue = OP_LOGICAL_AND; + *state = LEX_ROOT; + return; + } + break; + case OP_OR: + if (operator == '|') { + lvNext->operatorValue = OP_LOGICAL_OR; + *state = LEX_ROOT; + return; + } + break; + case OP_LESS: + if (operator == '=') { + lvNext->operatorValue = OP_LE; + *state = LEX_ROOT; + return; + } + if (operator == '<') { + lvNext->operatorValue = OP_SHIFT_L; + *state = LEX_ROOT; + return; + } + break; + case OP_GREATER: + if (operator == '=') { + lvNext->operatorValue = OP_GE; + *state = LEX_ROOT; + return; + } + if (operator == '>') { + lvNext->operatorValue = OP_SHIFT_R; + *state = LEX_ROOT; + return; + } + break; + case OP_ASSIGN: + if (operator == '=') { + lvNext->operatorValue = OP_EQUAL; + *state = LEX_ROOT; + return; + } + break; + case OP_NOT: + if (operator == '=') { + lvNext->operatorValue = OP_NOT_EQUAL; + *state = LEX_ROOT; + return; + } + break; + default: + break; + } + *state = LEX_ERROR; + return; + } struct Token* lvNext = LexVectorAppend(lv); lvNext->type = TOKEN_OPERATOR_TYPE; + *state = LEX_EXPECT_OPERATOR2; switch (operator) { + case '=': + lvNext->operatorValue = OP_ASSIGN; + break; case '+': lvNext->operatorValue = OP_ADD; break; @@ -57,6 +128,9 @@ static void _lexOperator(struct LexVector* lv, char operator) { case '>': lvNext->operatorValue = OP_GREATER; break; + case '!': + lvNext->operatorValue = OP_NOT; + break; default: lvNext->type = TOKEN_ERROR_TYPE; break; @@ -67,6 +141,7 @@ static void _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexS struct Token* lvNext; switch (token) { + case '=': case '+': case '-': case '*': @@ -77,11 +152,11 @@ static void _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexS case '^': case '<': case '>': + case '!': lvNext = LexVectorAppend(lv); lvNext->type = TOKEN_UINT_TYPE; lvNext->uintValue = next; - _lexOperator(lv, token); - *state = LEX_ROOT; + _lexOperator(lv, token, state); break; case ')': lvNext = LexVectorAppend(lv); @@ -115,6 +190,20 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { ++adjusted; --length; switch (state) { + case LEX_EXPECT_OPERATOR2: + switch (token) { + case '&': + case '|': + case '=': + case '<': + case '>': + _lexOperator(lv, token, &state); + break; + } + if (state != LEX_EXPECT_OPERATOR2) { + break; + } + // Fall through case LEX_ROOT: tokenStart = string - 1; switch (token) { @@ -158,6 +247,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { break; case LEX_EXPECT_IDENTIFIER: switch (token) { + case '=': case '+': case '-': case '*': @@ -168,11 +258,11 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '^': case '<': case '>': + case '!': lvNext = LexVectorAppend(lv); lvNext->type = TOKEN_IDENTIFIER_TYPE; lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); - _lexOperator(lv, token); - state = LEX_ROOT; + _lexOperator(lv, token, &state); break; case ')': lvNext = LexVectorAppend(lv); @@ -305,6 +395,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { break; case LEX_EXPECT_OPERATOR: switch (token) { + case '=': case '+': case '-': case '*': @@ -315,8 +406,8 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case '^': case '<': case '>': - _lexOperator(lv, token); - state = LEX_ROOT; + case '!': + _lexOperator(lv, token, &state); break; case ')': lvNext = LexVectorAppend(lv); @@ -349,6 +440,7 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { break; case LEX_ROOT: case LEX_EXPECT_OPERATOR: + case LEX_EXPECT_OPERATOR2: break; case LEX_EXPECT_BINARY_FIRST: case LEX_EXPECT_HEX_FIRST: @@ -374,8 +466,16 @@ static const int _operatorPrecedence[] = { [OP_LESS] = 6, [OP_GREATER] = 6, [OP_EQUAL] = 7, + [OP_NOT_EQUAL] = 7, [OP_LE] = 6, - [OP_GE] = 6 + [OP_GE] = 6, + [OP_LOGICAL_AND] = 11, + [OP_LOGICAL_OR] = 12, + [OP_NEGATE] = 2, + [OP_FLIP] = 2, + [OP_NOT] = 2, + [OP_SHIFT_L] = 5, + [OP_SHIFT_R] = 5, }; static struct ParseTree* _parseTreeCreate() { @@ -540,12 +640,29 @@ static bool _performOperation(enum Operation operation, int32_t current, int32_t case OP_EQUAL: current = current == next; break; + case OP_NOT_EQUAL: + current = current != next; + break; + case OP_LOGICAL_AND: + current = current && next; + break; + case OP_LOGICAL_OR: + current = current || next; + break; case OP_LE: current = current <= next; break; case OP_GE: current = current >= next; break; + case OP_SHIFT_L: + current <<= next; + break; + case OP_SHIFT_R: + current >>= next; + break; + default: + return false; } *value = current; return true; diff --git a/src/debugger/test/lexer.c b/src/debugger/test/lexer.c index 034887a18..e9e5ac2d2 100644 --- a/src/debugger/test/lexer.c +++ b/src/debugger/test/lexer.c @@ -350,6 +350,298 @@ M_TEST_DEFINE(lexIdentifierGreaterOperator) { assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); } +M_TEST_DEFINE(lexEqualsOperator) { + LEX("1=="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_EQUAL); +} + +M_TEST_DEFINE(lexIdentifierEqualsOperator) { + LEX("x=="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_EQUAL); +} + +M_TEST_DEFINE(lexNotEqualsOperator) { + LEX("1!="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT_EQUAL); +} + +M_TEST_DEFINE(lexIdentifierNotEqualsOperator) { + LEX("x!="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT_EQUAL); +} + +M_TEST_DEFINE(lexLEOperator) { + LEX("1<="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LE); +} + +M_TEST_DEFINE(lexIdentifierLEOperator) { + LEX("x<="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LE); +} + +M_TEST_DEFINE(lexGEOperator) { + LEX("1>="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GE); +} + +M_TEST_DEFINE(lexIdentifierGEOperator) { + LEX("x>="); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GE); +} + +M_TEST_DEFINE(lexLAndOperator) { + LEX("1&&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_AND); +} + +M_TEST_DEFINE(lexIdentifierLAndOperator) { + LEX("x&&"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_AND); +} + +M_TEST_DEFINE(lexLOrOperator) { + LEX("1||"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_OR); +} + +M_TEST_DEFINE(lexIdentifierLOrOperator) { + LEX("x||"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LOGICAL_OR); +} + +M_TEST_DEFINE(lexShiftLOperator) { + LEX("1<<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_L); +} + +M_TEST_DEFINE(lexIdentifierShiftLOperator) { + LEX("x<<"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_L); +} + +M_TEST_DEFINE(lexShiftROperator) { + LEX("1>>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_R); +} + +M_TEST_DEFINE(lexIdentifierShiftROperator) { + LEX("x>>"); + + assert_int_equal(LexVectorSize(lv), 2); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_SHIFT_R); +} + +M_TEST_DEFINE(lexEqualsInvalidOperator) { + LEX("1=|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ASSIGN); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierEqualsInvalidOperator) { + LEX("x=|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ASSIGN); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexNotInvalidOperator) { + LEX("1!|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierNotInvalidOperator) { + LEX("x!|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_NOT); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexLessInvalidOperator) { + LEX("1<|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierLessInvalidOperator) { + LEX("x<|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_LESS); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexGreaterInvalidOperator) { + LEX("1>|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierGreaterInvalidOperator) { + LEX("x>|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_GREATER); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexAndInvalidOperator) { + LEX("1&|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierAndInvalidOperator) { + LEX("x&|"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_AND); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexOrInvalidOperator) { + LEX("1|>"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + +M_TEST_DEFINE(lexIdentifierOrInvalidOperator) { + LEX("x|>"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_OR); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_ERROR_TYPE); +} + M_TEST_DEFINE(lexSimpleExpression) { LEX("1+1"); @@ -460,6 +752,34 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, cmocka_unit_test(lexIdentifierLessOperator), cmocka_unit_test(lexGreaterOperator), cmocka_unit_test(lexIdentifierGreaterOperator), + cmocka_unit_test(lexEqualsOperator), + cmocka_unit_test(lexIdentifierEqualsOperator), + cmocka_unit_test(lexNotEqualsOperator), + cmocka_unit_test(lexIdentifierNotEqualsOperator), + cmocka_unit_test(lexLEOperator), + cmocka_unit_test(lexIdentifierLEOperator), + cmocka_unit_test(lexGEOperator), + cmocka_unit_test(lexIdentifierGEOperator), + cmocka_unit_test(lexLAndOperator), + cmocka_unit_test(lexIdentifierLAndOperator), + cmocka_unit_test(lexLOrOperator), + cmocka_unit_test(lexIdentifierLOrOperator), + cmocka_unit_test(lexShiftLOperator), + cmocka_unit_test(lexIdentifierShiftLOperator), + cmocka_unit_test(lexShiftROperator), + cmocka_unit_test(lexIdentifierShiftROperator), + cmocka_unit_test(lexEqualsInvalidOperator), + cmocka_unit_test(lexIdentifierEqualsInvalidOperator), + cmocka_unit_test(lexNotInvalidOperator), + cmocka_unit_test(lexIdentifierNotInvalidOperator), + cmocka_unit_test(lexLessInvalidOperator), + cmocka_unit_test(lexIdentifierLessInvalidOperator), + cmocka_unit_test(lexGreaterInvalidOperator), + cmocka_unit_test(lexIdentifierGreaterInvalidOperator), + cmocka_unit_test(lexAndInvalidOperator), + cmocka_unit_test(lexIdentifierAndInvalidOperator), + cmocka_unit_test(lexOrInvalidOperator), + cmocka_unit_test(lexIdentifierOrInvalidOperator), cmocka_unit_test(lexSimpleExpression), cmocka_unit_test(lexOpenParen), cmocka_unit_test(lexCloseParen), From 178017a9e0ae8ad56a1273b95bbe6e071cf7ee40 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 15:06:22 -0500 Subject: [PATCH 098/152] GBA Memory: Make WRAM+IWRAM one allocation --- src/gba/memory.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 079372689..bde601e86 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -87,16 +87,15 @@ void GBAMemoryInit(struct GBA* gba) { memset(&gba->memory.agbPrintCtx, 0, sizeof(gba->memory.agbPrintCtx)); gba->memory.agbPrintBuffer = NULL; - gba->memory.iwram = anonymousMemoryMap(SIZE_WORKING_IRAM); - gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM); + gba->memory.wram = anonymousMemoryMap(SIZE_WORKING_RAM + SIZE_WORKING_IRAM); + gba->memory.iwram = &gba->memory.wram[SIZE_WORKING_RAM >> 2]; GBADMAInit(gba); GBAVFameInit(&gba->memory.vfame); } void GBAMemoryDeinit(struct GBA* gba) { - mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM); - mappedMemoryFree(gba->memory.iwram, SIZE_WORKING_IRAM); + mappedMemoryFree(gba->memory.wram, SIZE_WORKING_RAM + SIZE_WORKING_IRAM); if (gba->memory.rom) { mappedMemoryFree(gba->memory.rom, gba->memory.romSize); } From 0383c82b469e7ca38832f66d07418cd890ea68a5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 16:11:40 -0500 Subject: [PATCH 099/152] Debugger: Conditional breakpoints --- CHANGES | 1 + include/mgba/debugger/debugger.h | 2 + include/mgba/internal/arm/debugger/debugger.h | 2 + include/mgba/internal/debugger/parser.h | 7 +- .../mgba/internal/lr35902/debugger/debugger.h | 3 +- src/arm/debugger/debugger.c | 32 +++++++++ src/debugger/cli-debugger.c | 43 +++++++++-- src/debugger/parser.c | 40 +++++++++-- src/debugger/test/lexer.c | 71 ++++++++++++++++++- src/debugger/test/parser.c | 5 +- src/lr35902/debugger/debugger.c | 31 ++++++++ 11 files changed, 216 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index 47adeff9a..5e4ff85a2 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Features: - Automatic cheat loading and saving - GameShark and Action Replay button support - AGBPrint support + - Debugger: Conditional breakpoints Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index f2a030827..a8e808b32 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -70,6 +70,7 @@ struct mDebuggerEntryInfo { }; struct mDebugger; +struct ParseTree; struct mDebuggerPlatform { struct mDebugger* p; @@ -79,6 +80,7 @@ struct mDebuggerPlatform { bool (*hasBreakpoints)(struct mDebuggerPlatform*); void (*setBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); + void (*setConditionalBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); diff --git a/include/mgba/internal/arm/debugger/debugger.h b/include/mgba/internal/arm/debugger/debugger.h index d35dbed13..0941b0dd5 100644 --- a/include/mgba/internal/arm/debugger/debugger.h +++ b/include/mgba/internal/arm/debugger/debugger.h @@ -15,8 +15,10 @@ CXX_GUARD_START #include #include +struct ParseTree; struct ARMDebugBreakpoint { uint32_t address; + struct ParseTree* condition; bool isSw; struct { uint32_t opcode; diff --git a/include/mgba/internal/debugger/parser.h b/include/mgba/internal/debugger/parser.h index 99c43dc73..6092c1f8c 100644 --- a/include/mgba/internal/debugger/parser.h +++ b/include/mgba/internal/debugger/parser.h @@ -12,6 +12,9 @@ CXX_GUARD_START +struct Token; +DECLARE_VECTOR(LexVector, struct Token); + enum Operation { OP_ASSIGN, OP_ADD, @@ -54,15 +57,13 @@ struct Token { }; }; -DECLARE_VECTOR(LexVector, struct Token); - struct ParseTree { struct Token token; struct ParseTree* lhs; struct ParseTree* rhs; }; -size_t lexExpression(struct LexVector* lv, const char* string, size_t length); +size_t lexExpression(struct LexVector* lv, const char* string, size_t length, const char* eol); void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv); void lexFree(struct LexVector* lv); diff --git a/include/mgba/internal/lr35902/debugger/debugger.h b/include/mgba/internal/lr35902/debugger/debugger.h index 340e13aac..6fb7dd734 100644 --- a/include/mgba/internal/lr35902/debugger/debugger.h +++ b/include/mgba/internal/lr35902/debugger/debugger.h @@ -15,10 +15,11 @@ CXX_GUARD_START #include #include - +struct ParseTree; struct LR35902DebugBreakpoint { uint16_t address; int segment; + struct ParseTree* condition; }; struct LR35902DebugWatchpoint { diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index 1d646208e..1476f8c8a 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -10,6 +10,7 @@ #include #include #include +#include DEFINE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint); DEFINE_VECTOR(ARMDebugWatchpointList, struct ARMDebugWatchpoint); @@ -24,6 +25,13 @@ static struct ARMDebugBreakpoint* _lookupBreakpoint(struct ARMDebugBreakpointLis return 0; } +static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) { + if (breakpoint->condition) { + parseFree(breakpoint->condition); + free(breakpoint->condition); + } +} + static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; int instructionLength; @@ -37,6 +45,13 @@ static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { if (!breakpoint) { return; } + if (breakpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(d->p, breakpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return; + } + } struct mDebuggerEntryInfo info = { .address = breakpoint->address, .type.bp.breakType = BREAKPOINT_HARDWARE @@ -50,6 +65,7 @@ static void ARMDebuggerDeinit(struct mDebuggerPlatform* platform); static void ARMDebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); @@ -65,6 +81,7 @@ struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { platform->init = ARMDebuggerInit; platform->deinit = ARMDebuggerDeinit; platform->setBreakpoint = ARMDebuggerSetBreakpoint; + platform->setConditionalBreakpoint = ARMDebuggerSetConditionalBreakpoint; platform->clearBreakpoint = ARMDebuggerClearBreakpoint; platform->setWatchpoint = ARMDebuggerSetWatchpoint; platform->clearWatchpoint = ARMDebuggerClearWatchpoint; @@ -97,6 +114,10 @@ void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) { } ARMDebuggerRemoveMemoryShim(debugger); + size_t i; + for (i = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints); ++i) { + _destroyBreakpoint(ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); + } ARMDebugBreakpointListDeinit(&debugger->breakpoints); ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); ARMDebugWatchpointListDeinit(&debugger->watchpoints); @@ -168,6 +189,16 @@ static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t addre UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); + breakpoint->condition = NULL; + breakpoint->address = address; + breakpoint->isSw = false; +} + +static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { + UNUSED(segment); + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); + breakpoint->condition = condition; breakpoint->address = address; breakpoint->isSw = false; } @@ -179,6 +210,7 @@ static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t add size_t i; for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) { if (ARMDebugBreakpointListGetPointer(breakpoints, i)->address == address) { + _destroyBreakpoint(ARMDebugBreakpointListGetPointer(breakpoints, i)); ARMDebugBreakpointListShift(breakpoints, i, 1); } } diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index c439a9427..bf975d2aa 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -62,8 +62,8 @@ static void _source(struct CLIDebugger*, struct CLIDebugVector*); #endif static struct CLIDebuggerCommandSummary _debuggerCommands[] = { - { "b", _setBreakpoint, "I", "Set a breakpoint" }, - { "break", _setBreakpoint, "I", "Set a breakpoint" }, + { "b", _setBreakpoint, "Is", "Set a breakpoint" }, + { "break", _setBreakpoint, "Is", "Set a breakpoint" }, { "c", _continue, "", "Continue execution" }, { "continue", _continue, "", "Continue execution" }, { "d", _clearBreakpoint, "I", "Delete a breakpoint" }, @@ -458,7 +458,39 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } uint32_t address = dv->intValue; - debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct LexVector lv; + bool error = false; + LexVectorInit(&lv, 0); + const char* string = dv->next->charValue; + size_t length = strlen(dv->next->charValue); + size_t adjusted = lexExpression(&lv, string, length, NULL); + struct ParseTree* tree = malloc(sizeof(*tree)); + if (!adjusted) { + error = true; + } else { + parseLexedExpression(tree, &lv); + + if (adjusted > length) { + error = true; + } else { + length -= adjusted; + string += adjusted; + } + } + lexFree(&lv); + LexVectorClear(&lv); + LexVectorDeinit(&lv); + if (error) { + parseFree(tree); + free(tree); + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } else { + debugger->d.platform->setConditionalBreakpoint(debugger->d.platform, address, dv->segmentValue, tree); + } + } else { + debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); + } } static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { @@ -547,7 +579,7 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri struct LexVector lv; LexVectorInit(&lv, 0); - size_t adjusted = lexExpression(&lv, string, length); + size_t adjusted = lexExpression(&lv, string, length, " "); if (adjusted > length) { dvTemp.type = CLIDV_ERROR_TYPE; } @@ -562,8 +594,7 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri } } - parseFree(tree.lhs); - parseFree(tree.rhs); + parseFree(&tree); lexFree(&lv); LexVectorDeinit(&lv); diff --git a/src/debugger/parser.c b/src/debugger/parser.c index 7cc778208..41eafc6d4 100644 --- a/src/debugger/parser.c +++ b/src/debugger/parser.c @@ -166,13 +166,20 @@ static void _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexS lvNext->type = TOKEN_CLOSE_PAREN_TYPE; *state = LEX_EXPECT_OPERATOR; break; + case ' ': + case '\t': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; + *state = LEX_EXPECT_OPERATOR; + break; default: *state = LEX_ERROR; break; } } -size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { +size_t lexExpression(struct LexVector* lv, const char* string, size_t length, const char* eol) { if (!string || length < 1) { return 0; } @@ -184,7 +191,11 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { const char* tokenStart = 0; struct Token* lvNext; - while (length > 0 && string[0] && string[0] != ' ' && state != LEX_ERROR) { + if (!eol) { + eol = " \r\n"; + } + + while (length > 0 && string[0] && !strchr(eol, string[0]) && state != LEX_ERROR) { char token = string[0]; ++string; ++adjusted; @@ -236,6 +247,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { lvNext = LexVectorAppend(lv); lvNext->type = TOKEN_OPEN_PAREN_TYPE; break; + case ' ': + case '\t': + break; default: if (tolower(token) >= 'a' && tolower(token <= 'z')) { state = LEX_EXPECT_IDENTIFIER; @@ -272,6 +286,13 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { lvNext->type = TOKEN_CLOSE_PAREN_TYPE; state = LEX_EXPECT_OPERATOR; break; + case ' ': + case '\t': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); + state = LEX_EXPECT_OPERATOR; + break; default: break; } @@ -412,7 +433,9 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { case ')': lvNext = LexVectorAppend(lv); lvNext->type = TOKEN_CLOSE_PAREN_TYPE; - state = LEX_EXPECT_OPERATOR; + break; + case ' ': + case '\t': break; default: state = LEX_ERROR; @@ -585,13 +608,18 @@ void parseFree(struct ParseTree* tree) { return; } - parseFree(tree->lhs); - parseFree(tree->rhs); + if (tree->lhs) { + parseFree(tree->lhs); + free(tree->lhs); + } + if (tree->rhs) { + parseFree(tree->rhs); + free(tree->rhs); + } if (tree->token.type == TOKEN_IDENTIFIER_TYPE) { free(tree->token.identifierValue); } - free(tree); } static bool _performOperation(enum Operation operation, int32_t current, int32_t next, int32_t* value) { diff --git a/src/debugger/test/lexer.c b/src/debugger/test/lexer.c index e9e5ac2d2..89e1024df 100644 --- a/src/debugger/test/lexer.c +++ b/src/debugger/test/lexer.c @@ -11,7 +11,7 @@ struct LexVector* lv = *state; \ lexFree(lv); \ LexVectorClear(lv); \ - size_t adjusted = lexExpression(lv, STR, strlen(STR)); \ + size_t adjusted = lexExpression(lv, STR, strlen(STR), ""); \ assert_false(adjusted > strlen(STR)) M_TEST_SUITE_SETUP(Lexer) { @@ -715,6 +715,68 @@ M_TEST_DEFINE(lexNestedParentheticalExpression) { assert_int_equal(LexVectorGetPointer(lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); } +M_TEST_DEFINE(lexSpaceSimple) { + LEX(" 1 "); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); +} + +M_TEST_DEFINE(lexSpaceIdentifier) { + LEX(" x "); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); +} + +M_TEST_DEFINE(lexSpaceOperator) { + LEX("1 + 2"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->uintValue, 2); +} + +M_TEST_DEFINE(lexSpaceParen) { + LEX(" ( 1 + 2 ) "); + + assert_int_equal(LexVectorSize(lv), 5); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 3)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexSpaceParens) { + LEX(" ( 1 + ( 2 + 3 ) ) "); + + assert_int_equal(LexVectorSize(lv), 9); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 5)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 5)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 6)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 6)->uintValue, 3); + assert_int_equal(LexVectorGetPointer(lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, cmocka_unit_test(lexEmpty), cmocka_unit_test(lexInt), @@ -785,4 +847,9 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, cmocka_unit_test(lexCloseParen), cmocka_unit_test(lexIdentifierCloseParen), cmocka_unit_test(lexParentheticalExpression), - cmocka_unit_test(lexNestedParentheticalExpression)) + cmocka_unit_test(lexNestedParentheticalExpression), + cmocka_unit_test(lexSpaceSimple), + cmocka_unit_test(lexSpaceIdentifier), + cmocka_unit_test(lexSpaceOperator), + cmocka_unit_test(lexSpaceParen), + cmocka_unit_test(lexSpaceParens)) diff --git a/src/debugger/test/parser.c b/src/debugger/test/parser.c index dd566be9f..412fc65c9 100644 --- a/src/debugger/test/parser.c +++ b/src/debugger/test/parser.c @@ -16,7 +16,7 @@ struct LPTest { struct LPTest* lp = *state; \ lexFree(&lp->lv); \ LexVectorClear(&lp->lv); \ - size_t adjusted = lexExpression(&lp->lv, STR, strlen(STR)); \ + size_t adjusted = lexExpression(&lp->lv, STR, strlen(STR), ""); \ assert_false(adjusted > strlen(STR)); \ struct ParseTree* tree = &lp->tree; \ parseLexedExpression(tree, &lp->lv) @@ -30,8 +30,7 @@ M_TEST_SUITE_SETUP(Parser) { M_TEST_SUITE_TEARDOWN(Parser) { struct LPTest* lp = *state; - parseFree(lp->tree.lhs); \ - parseFree(lp->tree.rhs); \ + parseFree(&lp->tree); \ lexFree(&lp->lv); LexVectorDeinit(&lp->lv); free(lp); diff --git a/src/lr35902/debugger/debugger.c b/src/lr35902/debugger/debugger.c index 53ae0ea57..fa2c77620 100644 --- a/src/lr35902/debugger/debugger.c +++ b/src/lr35902/debugger/debugger.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,13 @@ static struct LR35902DebugBreakpoint* _lookupBreakpoint(struct LR35902DebugBreak return 0; } +static void _destroyBreakpoint(struct LR35902DebugBreakpoint* breakpoint) { + if (breakpoint->condition) { + parseFree(breakpoint->condition); + free(breakpoint->condition); + } +} + static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; struct LR35902DebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->pc); @@ -32,6 +40,13 @@ static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { if (breakpoint->segment >= 0 && debugger->cpu->memory.currentSegment(debugger->cpu, breakpoint->address) != breakpoint->segment) { return; } + if (breakpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(d->p, breakpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return; + } + } struct mDebuggerEntryInfo info = { .address = breakpoint->address }; @@ -44,6 +59,7 @@ static void LR35902DebuggerDeinit(struct mDebuggerPlatform* platform); static void LR35902DebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); @@ -59,6 +75,7 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { platform->init = LR35902DebuggerInit; platform->deinit = LR35902DebuggerDeinit; platform->setBreakpoint = LR35902DebuggerSetBreakpoint; + platform->setConditionalBreakpoint = LR35902DebuggerSetConditionalBreakpoint; platform->clearBreakpoint = LR35902DebuggerClearBreakpoint; platform->setWatchpoint = LR35902DebuggerSetWatchpoint; platform->clearWatchpoint = LR35902DebuggerClearWatchpoint; @@ -79,6 +96,10 @@ void LR35902DebuggerInit(void* cpu, struct mDebuggerPlatform* platform) { void LR35902DebuggerDeinit(struct mDebuggerPlatform* platform) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) platform; + size_t i; + for (i = 0; i < LR35902DebugBreakpointListSize(&debugger->breakpoints); ++i) { + _destroyBreakpoint(LR35902DebugBreakpointListGetPointer(&debugger->breakpoints, i)); + } LR35902DebugBreakpointListDeinit(&debugger->breakpoints); LR35902DebugWatchpointListDeinit(&debugger->watchpoints); } @@ -100,6 +121,15 @@ static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t a struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListAppend(&debugger->breakpoints); breakpoint->address = address; breakpoint->segment = segment; + breakpoint->condition = NULL; +} + +static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListAppend(&debugger->breakpoints); + breakpoint->address = address; + breakpoint->segment = segment; + breakpoint->condition = condition; } static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { @@ -109,6 +139,7 @@ static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t for (i = 0; i < LR35902DebugBreakpointListSize(breakpoints); ++i) { struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListGetPointer(breakpoints, i); if (breakpoint->address == address && breakpoint->segment == segment) { + _destroyBreakpoint(LR35902DebugBreakpointListGetPointer(breakpoints, i)); LR35902DebugBreakpointListShift(breakpoints, i, 1); } } From 0131a196d17494abcabf22dd6d07f6c582b6d806 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Dec 2017 16:38:46 -0500 Subject: [PATCH 100/152] Debugger: Conditional watchpoints --- CHANGES | 2 +- include/mgba/debugger/debugger.h | 1 + include/mgba/internal/arm/debugger/debugger.h | 1 + .../mgba/internal/lr35902/debugger/debugger.h | 1 + src/arm/debugger/debugger.c | 26 ++++- src/arm/debugger/memory-debugger.c | 9 ++ src/debugger/cli-debugger.c | 104 ++++++++++++------ src/lr35902/debugger/debugger.c | 24 +++- src/lr35902/debugger/memory-debugger.c | 8 ++ 9 files changed, 129 insertions(+), 47 deletions(-) diff --git a/CHANGES b/CHANGES index 5e4ff85a2..f11e81b95 100644 --- a/CHANGES +++ b/CHANGES @@ -11,7 +11,7 @@ Features: - Automatic cheat loading and saving - GameShark and Action Replay button support - AGBPrint support - - Debugger: Conditional breakpoints + - Debugger: Conditional breakpoints and watchpoints Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index a8e808b32..8de7d4c00 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -83,6 +83,7 @@ struct mDebuggerPlatform { void (*setConditionalBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); + void (*setConditionalWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition); void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*checkBreakpoints)(struct mDebuggerPlatform*); void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length); diff --git a/include/mgba/internal/arm/debugger/debugger.h b/include/mgba/internal/arm/debugger/debugger.h index 0941b0dd5..5c1852243 100644 --- a/include/mgba/internal/arm/debugger/debugger.h +++ b/include/mgba/internal/arm/debugger/debugger.h @@ -29,6 +29,7 @@ struct ARMDebugBreakpoint { struct ARMDebugWatchpoint { uint32_t address; enum mWatchpointType type; + struct ParseTree* condition; }; DECLARE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint); diff --git a/include/mgba/internal/lr35902/debugger/debugger.h b/include/mgba/internal/lr35902/debugger/debugger.h index 6fb7dd734..6b66c715a 100644 --- a/include/mgba/internal/lr35902/debugger/debugger.h +++ b/include/mgba/internal/lr35902/debugger/debugger.h @@ -26,6 +26,7 @@ struct LR35902DebugWatchpoint { uint16_t address; int segment; enum mWatchpointType type; + struct ParseTree* condition; }; struct LR35902Segment { diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index 1476f8c8a..55105a17d 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -32,6 +32,13 @@ static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) { } } +static void _destroyWatchpoint(struct ARMDebugWatchpoint* watchpoint) { + if (watchpoint->condition) { + parseFree(watchpoint->condition); + free(watchpoint->condition); + } +} + static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; int instructionLength; @@ -68,6 +75,7 @@ static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); +static void ARMDebuggerSetConditionalWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition); static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*); @@ -84,6 +92,7 @@ struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { platform->setConditionalBreakpoint = ARMDebuggerSetConditionalBreakpoint; platform->clearBreakpoint = ARMDebuggerClearBreakpoint; platform->setWatchpoint = ARMDebuggerSetWatchpoint; + platform->setConditionalWatchpoint = ARMDebuggerSetConditionalWatchpoint; platform->clearWatchpoint = ARMDebuggerClearWatchpoint; platform->checkBreakpoints = ARMDebuggerCheckBreakpoints; platform->hasBreakpoints = ARMDebuggerHasBreakpoints; @@ -119,6 +128,10 @@ void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) { _destroyBreakpoint(ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); } ARMDebugBreakpointListDeinit(&debugger->breakpoints); + + for (i = 0; i < ARMDebugWatchpointListSize(&debugger->watchpoints); ++i) { + _destroyWatchpoint(ARMDebugWatchpointListGetPointer(&debugger->watchpoints, i)); + } ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); ARMDebugWatchpointListDeinit(&debugger->watchpoints); } @@ -186,12 +199,7 @@ void ARMDebuggerClearSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t ad } static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { - UNUSED(segment); - struct ARMDebugger* debugger = (struct ARMDebugger*) d; - struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); - breakpoint->condition = NULL; - breakpoint->address = address; - breakpoint->isSw = false; + ARMDebuggerSetConditionalBreakpoint(d, address, segment, NULL); } static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { @@ -222,6 +230,10 @@ static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform* d) { } static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type) { + ARMDebuggerSetConditionalWatchpoint(d, address, segment, type, NULL); +} + +static void ARMDebuggerSetConditionalWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition) { UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; if (!ARMDebugWatchpointListSize(&debugger->watchpoints)) { @@ -230,6 +242,7 @@ static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t addre struct ARMDebugWatchpoint* watchpoint = ARMDebugWatchpointListAppend(&debugger->watchpoints); watchpoint->address = address; watchpoint->type = type; + watchpoint->condition = condition; } static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { @@ -239,6 +252,7 @@ static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t add size_t i; for (i = 0; i < ARMDebugWatchpointListSize(watchpoints); ++i) { if (ARMDebugWatchpointListGetPointer(watchpoints, i)->address == address) { + _destroyWatchpoint(ARMDebugWatchpointListGetPointer(watchpoints, i)); ARMDebugWatchpointListShift(watchpoints, i, 1); } } diff --git a/src/arm/debugger/memory-debugger.c b/src/arm/debugger/memory-debugger.c index 88a8518b6..2faca99a6 100644 --- a/src/arm/debugger/memory-debugger.c +++ b/src/arm/debugger/memory-debugger.c @@ -6,6 +6,7 @@ #include #include +#include #include @@ -97,6 +98,14 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st for (i = 0; i < ARMDebugWatchpointListSize(&debugger->watchpoints); ++i) { watchpoint = ARMDebugWatchpointListGetPointer(&debugger->watchpoints, i); if (!((watchpoint->address ^ address) & ~width) && watchpoint->type & type) { + if (watchpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(debugger->d.p, watchpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return false; + } + } + switch (width + 1) { case 1: info->type.wp.oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0); diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index bf975d2aa..d0ed6b672 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -91,14 +91,14 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "r/4", _readWord, "I", "Read a word from a specified offset" }, { "status", _printStatus, "", "Print the current status" }, { "trace", _trace, "I", "Trace a fixed number of instructions" }, - { "w", _setWatchpoint, "I", "Set a watchpoint" }, + { "w", _setWatchpoint, "Is", "Set a watchpoint" }, { "w/1", _writeByte, "II", "Write a byte at a specified offset" }, { "w/2", _writeHalfword, "II", "Write a halfword at a specified offset" }, { "w/r", _writeRegister, "SI", "Write a register" }, { "w/4", _writeWord, "II", "Write a word at a specified offset" }, - { "watch", _setWatchpoint, "I", "Set a watchpoint" }, - { "watch/r", _setReadWatchpoint, "I", "Set a read watchpoint" }, - { "watch/w", _setWriteWatchpoint, "I", "Set a write watchpoint" }, + { "watch", _setWatchpoint, "Is", "Set a watchpoint" }, + { "watch/r", _setReadWatchpoint, "Is", "Set a read watchpoint" }, + { "watch/w", _setWriteWatchpoint, "Is", "Set a write watchpoint" }, { "x/1", _dumpByte, "Ii", "Examine bytes at a specified offset" }, { "x/2", _dumpHalfword, "Ii", "Examine halfwords at a specified offset" }, { "x/4", _dumpWord, "Ii", "Examine words at a specified offset" }, @@ -452,6 +452,37 @@ static void _source(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { } #endif +static struct ParseTree* _parseTree(const char* string) { + struct LexVector lv; + bool error = false; + LexVectorInit(&lv, 0); + size_t length = strlen(string); + size_t adjusted = lexExpression(&lv, string, length, NULL); + struct ParseTree* tree = malloc(sizeof(*tree)); + if (!adjusted) { + error = true; + } else { + parseLexedExpression(tree, &lv); + + if (adjusted > length) { + error = true; + } else { + length -= adjusted; + string += adjusted; + } + } + lexFree(&lv); + LexVectorClear(&lv); + LexVectorDeinit(&lv); + if (error) { + parseFree(tree); + free(tree); + return NULL; + } else { + return tree; + } +} + static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!dv || dv->type != CLIDV_INT_TYPE) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); @@ -459,34 +490,11 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* } uint32_t address = dv->intValue; if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { - struct LexVector lv; - bool error = false; - LexVectorInit(&lv, 0); - const char* string = dv->next->charValue; - size_t length = strlen(dv->next->charValue); - size_t adjusted = lexExpression(&lv, string, length, NULL); - struct ParseTree* tree = malloc(sizeof(*tree)); - if (!adjusted) { - error = true; - } else { - parseLexedExpression(tree, &lv); - - if (adjusted > length) { - error = true; - } else { - length -= adjusted; - string += adjusted; - } - } - lexFree(&lv); - LexVectorClear(&lv); - LexVectorDeinit(&lv); - if (error) { - parseFree(tree); - free(tree); - debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); - } else { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { debugger->d.platform->setConditionalBreakpoint(debugger->d.platform, address, dv->segmentValue, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); } } else { debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); @@ -503,7 +511,16 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_RW); + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { + debugger->d.platform->setConditionalWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_RW, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } + } else { + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_RW); + } } static void _setReadWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { @@ -516,7 +533,16 @@ static void _setReadWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVect return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_READ); + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { + debugger->d.platform->setConditionalWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_READ, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } + } else { + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_READ); + } } static void _setWriteWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { @@ -529,8 +555,16 @@ static void _setWriteWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVec return; } uint32_t address = dv->intValue; - debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_WRITE); -} + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct ParseTree* tree = _parseTree(dv->next->charValue); + if (tree) { + debugger->d.platform->setConditionalWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_WRITE, tree); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } + } else { + debugger->d.platform->setWatchpoint(debugger->d.platform, address, dv->segmentValue, WATCHPOINT_WRITE); + }} static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!dv || dv->type != CLIDV_INT_TYPE) { diff --git a/src/lr35902/debugger/debugger.c b/src/lr35902/debugger/debugger.c index fa2c77620..4f899ae1e 100644 --- a/src/lr35902/debugger/debugger.c +++ b/src/lr35902/debugger/debugger.c @@ -31,6 +31,13 @@ static void _destroyBreakpoint(struct LR35902DebugBreakpoint* breakpoint) { } } +static void _destroyWatchpoint(struct LR35902DebugWatchpoint* watchpoint) { + if (watchpoint->condition) { + parseFree(watchpoint->condition); + free(watchpoint->condition); + } +} + static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; struct LR35902DebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->pc); @@ -62,6 +69,7 @@ static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t add static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); +static void LR35902DebuggerSetConditionalWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition); static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool LR35902DebuggerHasBreakpoints(struct mDebuggerPlatform*); @@ -78,6 +86,7 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) { platform->setConditionalBreakpoint = LR35902DebuggerSetConditionalBreakpoint; platform->clearBreakpoint = LR35902DebuggerClearBreakpoint; platform->setWatchpoint = LR35902DebuggerSetWatchpoint; + platform->setConditionalWatchpoint = LR35902DebuggerSetConditionalWatchpoint; platform->clearWatchpoint = LR35902DebuggerClearWatchpoint; platform->checkBreakpoints = LR35902DebuggerCheckBreakpoints; platform->hasBreakpoints = LR35902DebuggerHasBreakpoints; @@ -101,6 +110,10 @@ void LR35902DebuggerDeinit(struct mDebuggerPlatform* platform) { _destroyBreakpoint(LR35902DebugBreakpointListGetPointer(&debugger->breakpoints, i)); } LR35902DebugBreakpointListDeinit(&debugger->breakpoints); + + for (i = 0; i < LR35902DebugWatchpointListSize(&debugger->watchpoints); ++i) { + _destroyWatchpoint(LR35902DebugWatchpointListGetPointer(&debugger->watchpoints, i)); + } LR35902DebugWatchpointListDeinit(&debugger->watchpoints); } @@ -117,11 +130,7 @@ static void LR35902DebuggerEnter(struct mDebuggerPlatform* platform, enum mDebug } static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { - struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; - struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListAppend(&debugger->breakpoints); - breakpoint->address = address; - breakpoint->segment = segment; - breakpoint->condition = NULL; + LR35902DebuggerSetConditionalBreakpoint(d, address, segment, NULL); } static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { @@ -151,6 +160,10 @@ static bool LR35902DebuggerHasBreakpoints(struct mDebuggerPlatform* d) { } static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type) { + LR35902DebuggerSetConditionalWatchpoint(d, address, segment, type, NULL); +} + +static void LR35902DebuggerSetConditionalWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, enum mWatchpointType type, struct ParseTree* condition) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; if (!LR35902DebugWatchpointListSize(&debugger->watchpoints)) { LR35902DebuggerInstallMemoryShim(debugger); @@ -159,6 +172,7 @@ static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform* d, uint32_t a watchpoint->address = address; watchpoint->type = type; watchpoint->segment = segment; + watchpoint->condition = condition; } static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) { diff --git a/src/lr35902/debugger/memory-debugger.c b/src/lr35902/debugger/memory-debugger.c index 3caf5ac72..b5c36a81e 100644 --- a/src/lr35902/debugger/memory-debugger.c +++ b/src/lr35902/debugger/memory-debugger.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include #include @@ -47,6 +48,13 @@ static bool _checkWatchpoints(struct LR35902Debugger* debugger, uint16_t address for (i = 0; i < LR35902DebugWatchpointListSize(&debugger->watchpoints); ++i) { watchpoint = LR35902DebugWatchpointListGetPointer(&debugger->watchpoints, i); if (watchpoint->address == address && (watchpoint->segment < 0 || watchpoint->segment == debugger->originalMemory.currentSegment(debugger->cpu, address)) && watchpoint->type & type) { + if (watchpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(debugger->d.p, watchpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return false; + } + } info->type.wp.oldValue = debugger->originalMemory.load8(debugger->cpu, address); info->type.wp.newValue = newValue; info->address = address; From d0277a7125981d4dbe3f2f74b0367dbb4e0b21c7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 30 Dec 2017 12:49:15 -0500 Subject: [PATCH 101/152] GBA: Add more debug checks --- src/gba/gba.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/gba/gba.c b/src/gba/gba.c index dd65f4ef2..f19c2588c 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -255,6 +255,11 @@ static void GBAProcessEvents(struct ARMCore* cpu) { #endif nextEvent = cycles; do { +#ifndef NDEBUG + if (cpu->cycles) { + mLOG(GBA, FATAL, "Cycles passed inexplicably: %i", cpu->cycles); + } +#endif nextEvent = mTimingTick(&gba->timing, nextEvent); } while (gba->cpuBlocked); @@ -276,6 +281,11 @@ static void GBAProcessEvents(struct ARMCore* cpu) { } #endif } +#ifndef NDEBUG + if (gba->cpuBlocked) { + mLOG(GBA, FATAL, "CPU is blocked!"); + } +#endif } #ifdef USE_DEBUGGERS From 748e1943f760d26382c422d6d1e52168f23f3b1f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 30 Dec 2017 14:20:37 -0500 Subject: [PATCH 102/152] GB, GBA Video: Move VRAM allocation to init --- src/gb/video.c | 6 +----- src/gba/video.c | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/gb/video.c b/src/gb/video.c index 80a341678..f98bb4e06 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -56,7 +56,7 @@ void GBVideoInit(struct GBVideo* video) { video->renderer = &dummyRenderer; video->renderer->cache = NULL; video->renderer->sgbRenderMode = 0; - video->vram = 0; + video->vram = anonymousMemoryMap(GB_SIZE_VRAM); video->frameskip = 0; video->modeEvent.context = video; @@ -99,10 +99,6 @@ void GBVideoReset(struct GBVideo* video) { video->frameCounter = 0; video->frameskipCounter = 0; - if (video->vram) { - mappedMemoryFree(video->vram, GB_SIZE_VRAM); - } - video->vram = anonymousMemoryMap(GB_SIZE_VRAM); GBVideoSwitchBank(video, 0); video->renderer->vram = video->vram; memset(&video->oam, 0, sizeof(video->oam)); diff --git a/src/gba/video.c b/src/gba/video.c index 8fb76e59b..9715ccff0 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -69,7 +69,7 @@ static struct GBAVideoRenderer dummyRenderer = { void GBAVideoInit(struct GBAVideo* video) { video->renderer = &dummyRenderer; video->renderer->cache = NULL; - video->vram = 0; + video->vram = anonymousMemoryMap(SIZE_VRAM); video->frameskip = 0; video->event.name = "GBA Video"; video->event.callback = NULL; @@ -91,11 +91,6 @@ void GBAVideoReset(struct GBAVideo* video) { video->frameCounter = 0; video->frameskipCounter = 0; - - if (video->vram) { - mappedMemoryFree(video->vram, SIZE_VRAM); - } - video->vram = anonymousMemoryMap(SIZE_VRAM); video->renderer->vram = video->vram; memset(video->palette, 0, sizeof(video->palette)); From bfb674fb4f42fd7050257aeb196c3831ab89feb0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 30 Dec 2017 14:52:29 -0500 Subject: [PATCH 103/152] Python: Fix installation issues --- src/platform/python/setup.py.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/python/setup.py.in b/src/platform/python/setup.py.in index 040be3fdc..0a6fa8b52 100644 --- a/src/platform/python/setup.py.in +++ b/src/platform/python/setup.py.in @@ -5,7 +5,6 @@ import sys os.environ["BINDIR"] = "${CMAKE_BINARY_DIR}" os.environ["CPPFLAGS"] = " ".join([d for d in "${INCLUDE_FLAGS}".split(";") if d]) -os.chdir("${CMAKE_CURRENT_SOURCE_DIR}") classifiers = [ "Programming Language :: C", @@ -22,11 +21,14 @@ setup(name="${BINARY_NAME}", author_email="jeffrey@endrift.com", url="http://github.com/mgba-emu/mgba/", packages=["mgba"], + package_dir={ + "mgba": "${CMAKE_CURRENT_SOURCE_DIR}" + }, setup_requires=['cffi>=1.6', 'pytest-runner'], install_requires=['cffi>=1.6', 'cached-property'], extras_require={'pil': ['Pillow>=2.3'], 'cinema': ['pyyaml', 'pytest']}, tests_require=['pytest'], - cffi_modules=["_builder.py:ffi"], + cffi_modules=["${CMAKE_CURRENT_SOURCE_DIR}/_builder.py:ffi"], license="MPL 2.0", classifiers=classifiers ) From 44c6e94f8ba529e74690633844ed251f73c7c206 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 31 Dec 2017 21:42:51 -0500 Subject: [PATCH 104/152] Qt: Add unused RegisterView class --- src/platform/qt/CMakeLists.txt | 1 + src/platform/qt/RegisterView.cpp | 144 +++++++++++++++++++++++++++++++ src/platform/qt/RegisterView.h | 40 +++++++++ 3 files changed, 185 insertions(+) create mode 100644 src/platform/qt/RegisterView.cpp create mode 100644 src/platform/qt/RegisterView.h diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 8588f3b38..340a823cd 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -99,6 +99,7 @@ set(SOURCE_FILES OverrideView.cpp PaletteView.cpp PrinterView.cpp + RegisterView.cpp ROMInfo.cpp SavestateButton.cpp SensorView.cpp diff --git a/src/platform/qt/RegisterView.cpp b/src/platform/qt/RegisterView.cpp new file mode 100644 index 000000000..0211f13cb --- /dev/null +++ b/src/platform/qt/RegisterView.cpp @@ -0,0 +1,144 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "RegisterView.h" + +#include "CoreController.h" + +#ifdef M_CORE_GBA +#include +#endif +#ifdef M_CORE_GB +#include +#endif + +#include +#include +#include + +using namespace QGBA; + +RegisterView::RegisterView(std::shared_ptr controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) +{ + QFormLayout* layout = new QFormLayout; + setLayout(layout); + + switch (controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + addRegisters({ + "r0", + "r1", + "r2", + "r3", + "r4", + "r5", + "r6", + "r7", + "r8", + "r9", + "r10", + "r11", + "r12", + "sp", + "lr", + "pc", + "cpsr", + }); + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + addRegisters({ + "a", + "f", + "b", + "c", + "d", + "e", + "h", + "l", + "sp", + "pc" + }); + break; +#endif + default: + break; + } +} + +void RegisterView::addRegisters(const QStringList& names) { + QFormLayout* form = static_cast(layout()); + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + for (const auto& reg : names) { + QLabel* value = new QLabel; + value->setTextInteractionFlags(Qt::TextSelectableByMouse); + value->setFont(font); + form->addWidget(value); + m_registers[reg] = value; + form->addRow(reg, value); + } +} + +void RegisterView::updateRegisters() { + switch (m_controller->platform()) { +#ifdef M_CORE_GBA + case PLATFORM_GBA: + updateRegistersARM(); + break; +#endif +#ifdef M_CORE_GB + case PLATFORM_GB: + updateRegistersLR35902(); + break; +#endif + default: + break; + } +} + +#ifdef M_CORE_GBA +void RegisterView::updateRegistersARM() { + CoreController::Interrupter interrupter(m_controller); + struct ARMCore* core = static_cast(m_controller->thread()->core->cpu); + m_registers["r0"]->setText(QString("%1").arg((uint32_t) core->gprs[0], 8, 16, QChar('0')).toUpper()); + m_registers["r1"]->setText(QString("%1").arg((uint32_t) core->gprs[1], 8, 16, QChar('0')).toUpper()); + m_registers["r2"]->setText(QString("%1").arg((uint32_t) core->gprs[2], 8, 16, QChar('0')).toUpper()); + m_registers["r3"]->setText(QString("%1").arg((uint32_t) core->gprs[3], 8, 16, QChar('0')).toUpper()); + m_registers["r4"]->setText(QString("%1").arg((uint32_t) core->gprs[4], 8, 16, QChar('0')).toUpper()); + m_registers["r5"]->setText(QString("%1").arg((uint32_t) core->gprs[5], 8, 16, QChar('0')).toUpper()); + m_registers["r6"]->setText(QString("%1").arg((uint32_t) core->gprs[6], 8, 16, QChar('0')).toUpper()); + m_registers["r7"]->setText(QString("%1").arg((uint32_t) core->gprs[7], 8, 16, QChar('0')).toUpper()); + m_registers["r8"]->setText(QString("%1").arg((uint32_t) core->gprs[8], 8, 16, QChar('0')).toUpper()); + m_registers["r9"]->setText(QString("%1").arg((uint32_t) core->gprs[9], 8, 16, QChar('0')).toUpper()); + m_registers["r10"]->setText(QString("%1").arg((uint32_t) core->gprs[10], 8, 16, QChar('0')).toUpper()); + m_registers["r11"]->setText(QString("%1").arg((uint32_t) core->gprs[11], 8, 16, QChar('0')).toUpper()); + m_registers["r12"]->setText(QString("%1").arg((uint32_t) core->gprs[12], 8, 16, QChar('0')).toUpper()); + m_registers["sp"]->setText(QString("%1").arg((uint32_t) core->gprs[ARM_SP], 8, 16, QChar('0')).toUpper()); + m_registers["lr"]->setText(QString("%1").arg((uint32_t) core->gprs[ARM_LR], 8, 16, QChar('0')).toUpper()); + m_registers["pc"]->setText(QString("%1").arg((uint32_t) core->gprs[ARM_PC], 8, 16, QChar('0')).toUpper()); + m_registers["cpsr"]->setText(QString("%1").arg((uint32_t) core->cpsr.packed, 8, 16, QChar('0')).toUpper()); +} +#endif + +#ifdef M_CORE_GB +void RegisterView::updateRegistersLR35902() { + CoreController::Interrupter interrupter(m_controller); + struct LR35902Core* core = static_cast(m_controller->thread()->core->cpu); + m_registers["a"]->setText(QString("%1").arg((uint8_t) core->a, 2, 16, QChar('0')).toUpper()); + m_registers["f"]->setText(QString("%1").arg((uint8_t) core->f.packed, 2, 16, QChar('0')).toUpper()); + m_registers["b"]->setText(QString("%1").arg((uint8_t) core->b, 2, 16, QChar('0')).toUpper()); + m_registers["c"]->setText(QString("%1").arg((uint8_t) core->c, 2, 16, QChar('0')).toUpper()); + m_registers["d"]->setText(QString("%1").arg((uint8_t) core->d, 2, 16, QChar('0')).toUpper()); + m_registers["e"]->setText(QString("%1").arg((uint8_t) core->e, 2, 16, QChar('0')).toUpper()); + m_registers["h"]->setText(QString("%1").arg((uint8_t) core->h, 2, 16, QChar('0')).toUpper()); + m_registers["l"]->setText(QString("%1").arg((uint8_t) core->l, 2, 16, QChar('0')).toUpper()); + m_registers["sp"]->setText(QString("%1").arg((uint8_t) core->sp, 4, 16, QChar('0')).toUpper()); + m_registers["pc"]->setText(QString("%1").arg((uint8_t) core->pc, 4, 16, QChar('0')).toUpper()); +} +#endif diff --git a/src/platform/qt/RegisterView.h b/src/platform/qt/RegisterView.h new file mode 100644 index 000000000..03762a044 --- /dev/null +++ b/src/platform/qt/RegisterView.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2013-2017 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include +#include + +class QLabel; + +namespace QGBA { + +class CoreController; + +class RegisterView : public QWidget { +Q_OBJECT + +public: + RegisterView(std::shared_ptr controller, QWidget* parent = nullptr); + +public slots: + void updateRegisters(); + +private: + void addRegisters(const QStringList& names); +#ifdef M_CORE_GBA + void updateRegistersARM(); +#endif +#ifdef M_CORE_GB + void updateRegistersLR35902(); +#endif + + QMap m_registers; + + std::shared_ptr m_controller; +}; + +} From 667dafb3473d548b5c7ab7283ffc6742b8ffa979 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 4 Jan 2018 09:51:31 -0800 Subject: [PATCH 105/152] All: Fix gcc<4.5 build --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56b70a39f..7b707c8b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project(mGBA) set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries") if(NOT MSVC) set(GCC_STD "c99") - if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_COMPILER_VERSION VERSION_LESS "4.3") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_COMPILER_VERSION VERSION_LESS "4.3") set(GCC_STD "gnu99") endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-missing-field-initializers -std=${GCC_STD}") @@ -167,7 +167,7 @@ list(APPEND UTIL_SRC ${CMAKE_CURRENT_BINARY_DIR}/version.c) source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c) # Advanced settings -if(NOT DEFINED 3DS) +if(NOT DEFINED 3DS AND NOT (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_COMPILER_VERSION VERSION_LESS "4.5")) # LTO appears to make 3DS binary slower set(DEFAULT_LTO ON) else() From c37c781d289953c48f3acc348219a0bd8d42ac0c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 4 Jan 2018 18:09:47 -0800 Subject: [PATCH 106/152] Qt: Fix gcc build --- src/platform/qt/RegisterView.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/qt/RegisterView.h b/src/platform/qt/RegisterView.h index 03762a044..1769de445 100644 --- a/src/platform/qt/RegisterView.h +++ b/src/platform/qt/RegisterView.h @@ -8,6 +8,8 @@ #include #include +#include + class QLabel; namespace QGBA { From a8394913dcf70b73c32e5f6ef25b53445c1fad2a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 4 Jan 2018 18:11:00 -0800 Subject: [PATCH 107/152] GBA: Speculative fix for AGBPrint --- src/gba/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index bde601e86..dc913d650 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -310,7 +310,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { if ((address & (SIZE_CART0 - 1)) == AGB_PRINT_FLUSH_ADDR && memory->agbPrint == 0x20) { cpu->memory.activeRegion = (uint32_t*) _agbPrintFunc; cpu->memory.activeMask = sizeof(_agbPrintFunc) - 1; - + break; } // Fall through default: From 16131c9702ca1469008f2263dc990be369049b09 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 4 Jan 2018 21:08:40 -0800 Subject: [PATCH 108/152] GBA Memory: More AGBPrint fixes --- src/gba/memory.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gba/memory.c b/src/gba/memory.c index dc913d650..fa989d397 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1649,6 +1649,7 @@ void GBAPrintFlush(struct GBA* gba) { value &= 0xFF; } oolBuf[i] = value; + oolBuf[i + 1] = 0; ++gba->memory.agbPrintCtx.get; } _agbPrintStore(gba, AGB_PRINT_STRUCT + 4, gba->memory.agbPrintCtx.get); @@ -1670,6 +1671,9 @@ static void _agbPrintStore(struct GBA* gba, uint32_t address, int16_t value) { _pristineCow(gba); memcpy(&memory->rom[AGB_PRINT_FLUSH_ADDR >> 2], _agbPrintFunc, sizeof(_agbPrintFunc)); STORE_16(value, address & (SIZE_CART0 - 2), memory->rom); + } else if (memory->agbPrintCtx.bank == 0xFD && memory->romSize >= SIZE_CART0 / 2) { + _pristineCow(gba); + STORE_16(value, address & (SIZE_CART0 / 2 - 2), memory->rom); } } From 69aa7ac3aed1b77f214ece118eefbaa25a2545f4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 4 Jan 2018 21:34:07 -0800 Subject: [PATCH 109/152] GBA: Timing cleanup --- src/gba/gba.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/gba/gba.c b/src/gba/gba.c index f19c2588c..f295c483c 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -243,24 +243,17 @@ static void GBAProcessEvents(struct ARMCore* cpu) { int32_t nextEvent = cpu->nextEvent; while (cpu->cycles >= nextEvent) { - int32_t cycles = cpu->cycles; - - cpu->cycles = 0; cpu->nextEvent = INT_MAX; - -#ifndef NDEBUG - if (cycles < 0) { - mLOG(GBA, FATAL, "Negative cycles passed: %i", cycles); - } -#endif - nextEvent = cycles; + nextEvent = 0; do { + int32_t cycles = cpu->cycles; + cpu->cycles = 0; #ifndef NDEBUG - if (cpu->cycles) { - mLOG(GBA, FATAL, "Cycles passed inexplicably: %i", cpu->cycles); + if (cycles < 0) { + mLOG(GBA, FATAL, "Negative cycles passed: %i", cycles); } #endif - nextEvent = mTimingTick(&gba->timing, nextEvent); + nextEvent = mTimingTick(&gba->timing, nextEvent + cycles); } while (gba->cpuBlocked); cpu->nextEvent = nextEvent; From 07098984e4b3ae1a01361d43c93f7b1dcc90dc00 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 4 Jan 2018 21:42:44 -0800 Subject: [PATCH 110/152] GBA BIOS: Fix overzealous LZ77 checks --- src/gba/bios.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/gba/bios.c b/src/gba/bios.c index debee1e05..e14050184 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -536,15 +536,9 @@ static void _unLz77(struct GBA* gba, int width) { disp = dest - (block & 0x0FFF) - 1; bytes = (block >> 12) + 3; while (bytes--) { - if (!remaining) { - if (gba->hardCrash) { - mLOG(GBA_BIOS, FATAL, "Improperly compressed LZ77 data. Real BIOS would hang."); - } else { - mLOG(GBA_BIOS, GAME_ERROR, "Improperly compressed LZ77 data. Real BIOS would hang."); - } - break; + if (remaining) { + --remaining; } - --remaining; if (width == 2) { byte = (int16_t) cpu->memory.load16(cpu, disp & ~1, 0); if (dest & 1) { From a796c167e432c7106d5d892dc4917a4a38240a7f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 7 Jan 2018 17:01:08 -0800 Subject: [PATCH 111/152] GUI: Minor logging and directory fixes --- src/core/core.c | 4 ++-- src/feature/gui/gui-runner.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/core.c b/src/core/core.c index 319dae407..39dbec636 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -276,7 +276,7 @@ void mCoreInitConfig(struct mCore* core, const char* port) { } void mCoreLoadConfig(struct mCore* core) { -#ifndef MINIMAL_CORE +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 mCoreConfigLoad(&core->config); #endif mCoreLoadForeignConfig(core, &core->config); @@ -284,7 +284,7 @@ void mCoreLoadConfig(struct mCore* core) { void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config) { mCoreConfigMap(config, &core->opts); -#ifndef MINIMAL_CORE +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 mDirectorySetMapOptions(&core->dirs, &core->opts); #endif if (core->opts.audioBuffers) { diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 1f9227a26..3ba09a415 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -165,6 +165,7 @@ void mGUIInit(struct mGUIRunner* runner, const char* port) { mCoreConfigSetDefaultIntValue(&runner->config, "volume", 0x100); mCoreConfigSetDefaultValue(&runner->config, "idleOptimization", "detect"); mCoreConfigLoad(&runner->config); + mCoreConfigGetIntValue(&runner->config, "logLevel", &logger.logLevel); char path[PATH_MAX]; mCoreConfigDirectory(path, PATH_MAX); @@ -283,7 +284,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { if (runner->core) { mLOG(GUI_RUNNER, INFO, "Found core"); runner->core->init(runner->core); - mCoreConfigInit(&runner->core->config, runner->port); + mCoreInitConfig(runner->core, runner->port); mInputMapInit(&runner->core->inputMap, &GBAInputInfo); found = mCoreLoadFile(runner->core, path); if (!found) { @@ -302,7 +303,6 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { } mLOG(GUI_RUNNER, DEBUG, "Loading config..."); mCoreLoadForeignConfig(runner->core, &runner->config); - logger.logLevel = runner->core->opts.logLevel; mLOG(GUI_RUNNER, DEBUG, "Loading save..."); mCoreAutoloadSave(runner->core); From 69db3f41a3acc87467ac698718d649327e70e74b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 7 Jan 2018 17:01:56 -0800 Subject: [PATCH 112/152] 3DS: Fix opening files in directory names with trailing slashes --- CHANGES | 1 + src/platform/3ds/3ds-vfs.c | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index f11e81b95..5ddbced21 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ Bugfixes: - Qt: Fix locale being set to English on settings save (fixes mgba.io/i/906) - LR35902: Fix watchpoints not reporting new value - GBA Audio: Increase PSG volume (fixes mgba.io/i/932) + - 3DS: Fix opening files in directory names with trailing slashes Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/platform/3ds/3ds-vfs.c b/src/platform/3ds/3ds-vfs.c index 9de4b1ab3..975d63fdf 100644 --- a/src/platform/3ds/3ds-vfs.c +++ b/src/platform/3ds/3ds-vfs.c @@ -241,7 +241,11 @@ static struct VFile* _vd3dOpenFile(struct VDir* vd, const char* path, int mode) } const char* dir = vd3d->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s/%s", dir, path); + if (dir[strlen(dir) - 1] == '/') { + sprintf(combined, "%s%s", dir, path); + } else { + sprintf(combined, "%s/%s", dir, path); + } struct VFile* file = VFileOpen(combined, mode); free(combined); @@ -255,7 +259,11 @@ static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path) { } const char* dir = vd3d->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s/%s", dir, path); + if (dir[strlen(dir) - 1] == '/') { + sprintf(combined, "%s%s", dir, path); + } else { + sprintf(combined, "%s/%s", dir, path); + } struct VDir* vd2 = VDirOpen(combined); if (!vd2) { @@ -272,7 +280,11 @@ static bool _vd3dDeleteFile(struct VDir* vd, const char* path) { } const char* dir = vd3d->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); - sprintf(combined, "%s/%s", dir, path); + if (dir[strlen(dir) - 1] == '/') { + sprintf(combined, "%s%s", dir, path); + } else { + sprintf(combined, "%s/%s", dir, path); + } uint16_t utf16Path[PATH_MAX + 1]; ssize_t units = utf8_to_utf16(utf16Path, (const uint8_t*) combined, PATH_MAX); From 373fbe89708dc4fc195dec926b281c42e198c505 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 7 Jan 2018 23:29:00 -0800 Subject: [PATCH 113/152] GBA Video: Fix map cache for 256-color mode 0 backgrounds --- src/gba/renderers/cache-set.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/gba/renderers/cache-set.c b/src/gba/renderers/cache-set.c index ce28d7e20..4e4628bf6 100644 --- a/src/gba/renderers/cache-set.c +++ b/src/gba/renderers/cache-set.c @@ -57,12 +57,15 @@ void GBAVideoCacheAssociate(struct mCacheSet* cache, struct GBAVideo* video) { } static void mapParser0(struct mMapCache* cache, struct mMapCacheEntry* entry, void* vram) { - UNUSED(cache); uint16_t map = *(uint16_t*) vram; entry->tileId = GBA_TEXT_MAP_TILE(map); entry->flags = mMapCacheEntryFlagsSetHMirror(entry->flags, !!GBA_TEXT_MAP_HFLIP(map)); entry->flags = mMapCacheEntryFlagsSetVMirror(entry->flags, !!GBA_TEXT_MAP_VFLIP(map)); - entry->flags = mMapCacheEntryFlagsSetPaletteId(entry->flags, GBA_TEXT_MAP_PALETTE(map)); + if (mMapCacheSystemInfoGetPaletteBPP(cache->sysConfig) == 3) { + entry->flags = mMapCacheEntryFlagsClearPaletteId(entry->flags); + } else { + entry->flags = mMapCacheEntryFlagsSetPaletteId(entry->flags, GBA_TEXT_MAP_PALETTE(map)); + } } static void mapParser2(struct mMapCache* cache, struct mMapCacheEntry* entry, void* vram) { @@ -82,10 +85,14 @@ static void GBAVideoCacheWriteDISPCNT(struct mCacheSet* cache, uint16_t value) { mMapCacheSetGetPointer(&cache->maps, 2)->mapParser = mapParser0; mMapCacheSetGetPointer(&cache->maps, 3)->mapParser = mapParser0; - mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 2)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 3)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); + mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 0)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 1)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 2)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 2)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 3)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 3)->sysConfig) == 3); break; case 1: case 2: @@ -113,6 +120,7 @@ static void GBAVideoCacheWriteBGCNT(struct mCacheSet* cache, size_t bg, uint16_t int tilesHigh = 0; mMapCacheSystemInfo sysconfig = 0; if (map->mapParser == mapParser0) { + map->tileCache = mTileCacheSetGetPointer(&cache->tiles, p); sysconfig = mMapCacheSystemInfoSetPaletteBPP(sysconfig, 2 + p); sysconfig = mMapCacheSystemInfoSetPaletteCount(sysconfig, 4 * !p); sysconfig = mMapCacheSystemInfoSetMacroTileSize(sysconfig, 5); @@ -125,8 +133,9 @@ static void GBAVideoCacheWriteBGCNT(struct mCacheSet* cache, size_t bg, uint16_t if (size & 2) { ++tilesHigh; } - map->tileStart = tileStart * 2; + map->tileStart = tileStart * (2 - p); } else if (map->mapParser == mapParser2) { + map->tileCache = mTileCacheSetGetPointer(&cache->tiles, 1); sysconfig = mMapCacheSystemInfoSetPaletteBPP(sysconfig, 3); sysconfig = mMapCacheSystemInfoSetPaletteCount(sysconfig, 0); sysconfig = mMapCacheSystemInfoSetMacroTileSize(sysconfig, 4 + size); From 20506226c9f73791bf4070ae2fcb63e4ee18dec9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 Jan 2018 08:24:23 -0800 Subject: [PATCH 114/152] GBA Video: Fix map cache for 256-color mode 1 backgrounds --- src/gba/renderers/cache-set.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gba/renderers/cache-set.c b/src/gba/renderers/cache-set.c index 4e4628bf6..343cdcb8e 100644 --- a/src/gba/renderers/cache-set.c +++ b/src/gba/renderers/cache-set.c @@ -101,8 +101,11 @@ static void GBAVideoCacheWriteDISPCNT(struct mCacheSet* cache, uint16_t value) { mMapCacheSetGetPointer(&cache->maps, 2)->mapParser = mapParser2; mMapCacheSetGetPointer(&cache->maps, 3)->mapParser = mapParser2; - mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); - mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 0); + mMapCacheSetGetPointer(&cache->maps, 0)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 0)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 1)->tileCache = mTileCacheSetGetPointer(&cache->tiles, + mMapCacheSystemInfoGetPaletteBPP(mMapCacheSetGetPointer(&cache->maps, 1)->sysConfig) == 3); + mMapCacheSetGetPointer(&cache->maps, 2)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 1); mMapCacheSetGetPointer(&cache->maps, 3)->tileCache = mTileCacheSetGetPointer(&cache->tiles, 1); break; From caea7e070015060014409ce804e1851f4983f800 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 Jan 2018 08:39:26 -0800 Subject: [PATCH 115/152] GB: Skip BIOS option now works --- CHANGES | 1 + include/mgba/internal/gb/gb.h | 2 + src/gb/core.c | 4 + src/gb/gb.c | 169 +++++++++++++++++++--------------- src/gb/io.c | 5 +- 5 files changed, 103 insertions(+), 78 deletions(-) diff --git a/CHANGES b/CHANGES index 5ddbced21..40f714b85 100644 --- a/CHANGES +++ b/CHANGES @@ -50,6 +50,7 @@ Misc: - GBA: Improve multiboot image detection - GB MBC: Remove erroneous bank 0 wrapping - GBA Cheats: Allow multiple ROM patches in the same slot + - GB: Skip BIOS option now works 0.6.1: (2017-10-01) Bugfixes: diff --git a/include/mgba/internal/gb/gb.h b/include/mgba/internal/gb/gb.h index 1d222211a..65ffb88f7 100644 --- a/include/mgba/internal/gb/gb.h +++ b/include/mgba/internal/gb/gb.h @@ -145,6 +145,8 @@ void GBCreate(struct GB* gb); void GBDestroy(struct GB* gb); void GBReset(struct LR35902Core* cpu); +void GBSkipBIOS(struct GB* gb); +void GBUnmapBIOS(struct GB* gb); void GBDetectModel(struct GB* gb); void GBUpdateIRQs(struct GB* gb); diff --git a/src/gb/core.c b/src/gb/core.c index 299a6f5b6..3ccb8327e 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -451,6 +451,10 @@ static void _GBCoreReset(struct mCore* core) { #endif LR35902Reset(core->cpu); + + if (core->opts.skipBios) { + GBSkipBIOS(core->board); + } } static void _GBCoreRunFrame(struct mCore* core) { diff --git a/src/gb/gb.c b/src/gb/gb.c index 617161851..dd99b2bac 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -435,84 +435,11 @@ void GBReset(struct LR35902Core* cpu) { cpu->d = 0; gb->timer.internalDiv = 0; - int nextDiv = 0; - if (!gb->biosVf) { - switch (gb->model) { - case GB_MODEL_AUTODETECT: // Silence warnings - gb->model = GB_MODEL_DMG; - case GB_MODEL_DMG: - cpu->a = 1; - cpu->f.packed = 0xB0; - cpu->c = 0x13; - cpu->e = 0xD8; - cpu->h = 1; - cpu->l = 0x4D; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_SGB: - cpu->a = 1; - cpu->f.packed = 0x00; - cpu->c = 0x14; - cpu->e = 0x00; - cpu->h = 0xC0; - cpu->l = 0x60; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_MGB: - cpu->a = 0xFF; - cpu->f.packed = 0xB0; - cpu->c = 0x13; - cpu->e = 0xD8; - cpu->h = 1; - cpu->l = 0x4D; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_SGB2: - cpu->a = 0xFF; - cpu->f.packed = 0x00; - cpu->c = 0x14; - cpu->e = 0x00; - cpu->h = 0xC0; - cpu->l = 0x60; - gb->timer.internalDiv = 0xABC; - nextDiv = 4; - break; - case GB_MODEL_AGB: - cpu->a = 0x11; - cpu->b = 1; - cpu->f.packed = 0x00; - cpu->c = 0; - cpu->e = 0x08; - cpu->h = 0; - cpu->l = 0x7C; - gb->timer.internalDiv = 0x1EA; - nextDiv = 0xC; - break; - case GB_MODEL_CGB: - cpu->a = 0x11; - cpu->f.packed = 0x80; - cpu->c = 0; - cpu->e = 0x08; - cpu->h = 0; - cpu->l = 0x7C; - gb->timer.internalDiv = 0x1EA; - nextDiv = 0xC; - break; - } - - cpu->sp = 0xFFFE; - cpu->pc = 0x100; - } gb->cpuBlocked = false; gb->earlyExit = false; gb->doubleSpeed = 0; - cpu->memory.setActiveRegion(cpu, cpu->pc); - if (gb->yankedRomSize) { gb->memory.romSize = gb->yankedRomSize; gb->yankedRomSize = 0; @@ -527,15 +454,109 @@ void GBReset(struct LR35902Core* cpu) { GBMemoryReset(gb); GBVideoReset(&gb->video); GBTimerReset(&gb->timer); - mTimingSchedule(&gb->timing, &gb->timer.event, nextDiv); + if (!gb->biosVf) { + GBSkipBIOS(gb); + } else { + mTimingSchedule(&gb->timing, &gb->timer.event, 0); + } GBIOReset(gb); GBAudioReset(&gb->audio); GBSIOReset(&gb->sio); + cpu->memory.setActiveRegion(cpu, cpu->pc); + GBSavedataUnmask(gb); } +void GBSkipBIOS(struct GB* gb) { + struct LR35902Core* cpu = gb->cpu; + int nextDiv = 0; + + switch (gb->model) { + case GB_MODEL_AUTODETECT: // Silence warnings + gb->model = GB_MODEL_DMG; + case GB_MODEL_DMG: + cpu->a = 1; + cpu->f.packed = 0xB0; + cpu->c = 0x13; + cpu->e = 0xD8; + cpu->h = 1; + cpu->l = 0x4D; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_SGB: + cpu->a = 1; + cpu->f.packed = 0x00; + cpu->c = 0x14; + cpu->e = 0x00; + cpu->h = 0xC0; + cpu->l = 0x60; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_MGB: + cpu->a = 0xFF; + cpu->f.packed = 0xB0; + cpu->c = 0x13; + cpu->e = 0xD8; + cpu->h = 1; + cpu->l = 0x4D; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_SGB2: + cpu->a = 0xFF; + cpu->f.packed = 0x00; + cpu->c = 0x14; + cpu->e = 0x00; + cpu->h = 0xC0; + cpu->l = 0x60; + gb->timer.internalDiv = 0xABC; + nextDiv = 4; + break; + case GB_MODEL_AGB: + cpu->a = 0x11; + cpu->b = 1; + cpu->f.packed = 0x00; + cpu->c = 0; + cpu->e = 0x08; + cpu->h = 0; + cpu->l = 0x7C; + gb->timer.internalDiv = 0x1EA; + nextDiv = 0xC; + break; + case GB_MODEL_CGB: + cpu->a = 0x11; + cpu->f.packed = 0x80; + cpu->c = 0; + cpu->e = 0x08; + cpu->h = 0; + cpu->l = 0x7C; + gb->timer.internalDiv = 0x1EA; + nextDiv = 0xC; + break; + } + + cpu->sp = 0xFFFE; + cpu->pc = 0x100; + + mTimingDeschedule(&gb->timing, &gb->timer.event); + mTimingSchedule(&gb->timing, &gb->timer.event, 0); + + if (gb->biosVf) { + GBUnmapBIOS(gb); + } +} + +void GBUnmapBIOS(struct GB* gb) { + if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) { + free(gb->memory.romBase); + gb->memory.romBase = gb->memory.rom; + } +} + void GBDetectModel(struct GB* gb) { if (gb->model != GB_MODEL_AUTODETECT) { return; diff --git a/src/gb/io.c b/src/gb/io.c index 254fb5232..c6e065b95 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -420,10 +420,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { value = gb->video.stat; break; case 0x50: - if (gb->memory.romBase < gb->memory.rom || gb->memory.romBase > &gb->memory.rom[gb->memory.romSize - 1]) { - free(gb->memory.romBase); - gb->memory.romBase = gb->memory.rom; - } + GBUnmapBIOS(gb); if (gb->model >= GB_MODEL_CGB && gb->memory.io[REG_UNK4C] < 0x80) { gb->model = GB_MODEL_DMG; GBVideoDisableCGB(&gb->video); From 3723ebea20f8f6f591e4446494fd33a35c11209a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 Jan 2018 18:24:29 -0800 Subject: [PATCH 116/152] GB MBC: Fix MBC2 saves (fixes #954) --- CHANGES | 1 + src/gb/mbc.c | 19 +++++++++++++++++-- src/gb/memory.c | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 40f714b85..e770d2001 100644 --- a/CHANGES +++ b/CHANGES @@ -36,6 +36,7 @@ Bugfixes: - LR35902: Fix watchpoints not reporting new value - GBA Audio: Increase PSG volume (fixes mgba.io/i/932) - 3DS: Fix opening files in directory names with trailing slashes + - GB MBC: Fix MBC2 saves (fixes mgba.io/i/954) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 6db033aaa..16d3e01f8 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -31,6 +31,7 @@ static void _GBHuC3(struct GB*, uint16_t address, uint8_t value); static void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value); static void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value); +static uint8_t _GBMBC2Read(struct GBMemory*, uint16_t address); static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address); static void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value); @@ -214,7 +215,8 @@ void GBMBCInit(struct GB* gb) { break; case GB_MBC2: gb->memory.mbcWrite = _GBMBC2; - gb->sramSize = 0x200; + gb->memory.mbcRead = _GBMBC2Read; + gb->sramSize = 0x100; break; case GB_MBC3: gb->memory.mbcWrite = _GBMBC3; @@ -396,6 +398,7 @@ void _GBMBC1(struct GB* gb, uint16_t address, uint8_t value) { void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; + int shift = (address & 1) * 4; int bank = value & 0xF; switch (address >> 13) { case 0x0: @@ -405,7 +408,6 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { break; case 0xA: memory->sramAccess = true; - GBMBCSwitchSramBank(gb, memory->sramCurrentBank); break; default: // TODO @@ -419,6 +421,13 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { } GBMBCSwitchBank(gb, bank); break; + case 0x5: + if (!memory->sramAccess) { + return; + } + address &= 0x1FF; + memory->sramBank[(address >> 1)] &= 0xF0 >> shift; + memory->sramBank[(address >> 1)] |= (value & 0xF) << shift; default: // TODO mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value); @@ -426,6 +435,12 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) { } } +static uint8_t _GBMBC2Read(struct GBMemory* memory, uint16_t address) { + address &= 0x1FF; + int shift = (address & 1) * 4; + return (memory->sramBank[(address >> 1)] >> shift) | 0xF0; +} + void _GBMBC3(struct GB* gb, uint16_t address, uint8_t value) { struct GBMemory* memory = &gb->memory; int bank = value & 0x7F; diff --git a/src/gb/memory.c b/src/gb/memory.c index 86af47a7b..1dae0b988 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -294,7 +294,7 @@ void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { case GB_REGION_EXTERNAL_RAM + 1: if (memory->rtcAccess) { memory->rtcRegs[memory->activeRtcReg] = value; - } else if (memory->sramAccess && memory->sram) { + } else if (memory->sramAccess && memory->sram && memory->mbcType != GB_MBC2) { memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value; } else { memory->mbcWrite(gb, address, value); From 65207f5c0f8173ac31c5f382e1f6f274ab6e701f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 Jan 2018 23:34:24 -0800 Subject: [PATCH 117/152] GB Video: Fix SGB PAL commands --- src/gb/video.c | 96 +++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/gb/video.c b/src/gb/video.c index f98bb4e06..d6552c6a6 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -594,39 +594,39 @@ void GBVideoWriteSGBPacket(struct GBVideo* video, uint8_t* data) { video->palette[2] = data[5] | (data[6] << 8); video->palette[3] = data[7] | (data[8] << 8); - video->palette[16] = data[1] | (data[2] << 8); - video->palette[17] = data[9] | (data[10] << 8); - video->palette[18] = data[11] | (data[12] << 8); - video->palette[19] = data[13] | (data[14] << 8); + video->palette[4] = data[1] | (data[2] << 8); + video->palette[5] = data[9] | (data[10] << 8); + video->palette[6] = data[11] | (data[12] << 8); + video->palette[7] = data[13] | (data[14] << 8); - video->palette[32] = data[1] | (data[2] << 8); - video->palette[48] = data[1] | (data[2] << 8); + video->palette[8] = data[1] | (data[2] << 8); + video->palette[12] = data[1] | (data[2] << 8); video->renderer->writePalette(video->renderer, 0, video->palette[0]); video->renderer->writePalette(video->renderer, 1, video->palette[1]); video->renderer->writePalette(video->renderer, 2, video->palette[2]); video->renderer->writePalette(video->renderer, 3, video->palette[3]); - video->renderer->writePalette(video->renderer, 16, video->palette[0]); - video->renderer->writePalette(video->renderer, 17, video->palette[17]); - video->renderer->writePalette(video->renderer, 18, video->palette[18]); - video->renderer->writePalette(video->renderer, 19, video->palette[19]); - video->renderer->writePalette(video->renderer, 32, video->palette[0]); - video->renderer->writePalette(video->renderer, 48, video->palette[0]); + video->renderer->writePalette(video->renderer, 4, video->palette[0]); + video->renderer->writePalette(video->renderer, 5, video->palette[5]); + video->renderer->writePalette(video->renderer, 6, video->palette[6]); + video->renderer->writePalette(video->renderer, 7, video->palette[7]); + video->renderer->writePalette(video->renderer, 8, video->palette[0]); + video->renderer->writePalette(video->renderer, 12, video->palette[0]); break; case SGB_PAL23: - video->palette[33] = data[3] | (data[4] << 8); - video->palette[34] = data[5] | (data[6] << 8); - video->palette[35] = data[7] | (data[8] << 8); + video->palette[9] = data[3] | (data[4] << 8); + video->palette[10] = data[5] | (data[6] << 8); + video->palette[11] = data[7] | (data[8] << 8); - video->palette[49] = data[9] | (data[10] << 8); - video->palette[50] = data[11] | (data[12] << 8); - video->palette[51] = data[13] | (data[14] << 8); - video->renderer->writePalette(video->renderer, 33, video->palette[33]); - video->renderer->writePalette(video->renderer, 34, video->palette[34]); - video->renderer->writePalette(video->renderer, 35, video->palette[35]); - video->renderer->writePalette(video->renderer, 49, video->palette[49]); - video->renderer->writePalette(video->renderer, 50, video->palette[50]); - video->renderer->writePalette(video->renderer, 51, video->palette[51]); + video->palette[13] = data[9] | (data[10] << 8); + video->palette[14] = data[11] | (data[12] << 8); + video->palette[15] = data[13] | (data[14] << 8); + video->renderer->writePalette(video->renderer, 9, video->palette[9]); + video->renderer->writePalette(video->renderer, 10, video->palette[10]); + video->renderer->writePalette(video->renderer, 11, video->palette[11]); + video->renderer->writePalette(video->renderer, 13, video->palette[13]); + video->renderer->writePalette(video->renderer, 14, video->palette[14]); + video->renderer->writePalette(video->renderer, 15, video->palette[15]); break; case SGB_PAL03: video->palette[0] = data[1] | (data[2] << 8); @@ -634,38 +634,38 @@ void GBVideoWriteSGBPacket(struct GBVideo* video, uint8_t* data) { video->palette[2] = data[5] | (data[6] << 8); video->palette[3] = data[7] | (data[8] << 8); - video->palette[16] = data[1] | (data[2] << 8); - video->palette[32] = data[1] | (data[2] << 8); + video->palette[4] = data[1] | (data[2] << 8); + video->palette[8] = data[1] | (data[2] << 8); - video->palette[48] = data[1] | (data[2] << 8); - video->palette[49] = data[9] | (data[10] << 8); - video->palette[50] = data[11] | (data[12] << 8); - video->palette[51] = data[13] | (data[14] << 8); + video->palette[12] = data[1] | (data[2] << 8); + video->palette[13] = data[9] | (data[10] << 8); + video->palette[14] = data[11] | (data[12] << 8); + video->palette[15] = data[13] | (data[14] << 8); video->renderer->writePalette(video->renderer, 0, video->palette[0]); video->renderer->writePalette(video->renderer, 1, video->palette[1]); video->renderer->writePalette(video->renderer, 2, video->palette[2]); video->renderer->writePalette(video->renderer, 3, video->palette[3]); - video->renderer->writePalette(video->renderer, 16, video->palette[0]); - video->renderer->writePalette(video->renderer, 32, video->palette[0]); - video->renderer->writePalette(video->renderer, 48, video->palette[0]); - video->renderer->writePalette(video->renderer, 49, video->palette[49]); - video->renderer->writePalette(video->renderer, 50, video->palette[50]); - video->renderer->writePalette(video->renderer, 51, video->palette[51]); + video->renderer->writePalette(video->renderer, 4, video->palette[0]); + video->renderer->writePalette(video->renderer, 8, video->palette[0]); + video->renderer->writePalette(video->renderer, 12, video->palette[0]); + video->renderer->writePalette(video->renderer, 13, video->palette[13]); + video->renderer->writePalette(video->renderer, 14, video->palette[14]); + video->renderer->writePalette(video->renderer, 15, video->palette[15]); break; case SGB_PAL12: - video->palette[17] = data[3] | (data[4] << 8); - video->palette[18] = data[5] | (data[6] << 8); - video->palette[19] = data[7] | (data[8] << 8); + video->palette[5] = data[3] | (data[4] << 8); + video->palette[6] = data[5] | (data[6] << 8); + video->palette[7] = data[7] | (data[8] << 8); - video->palette[33] = data[9] | (data[10] << 8); - video->palette[34] = data[11] | (data[12] << 8); - video->palette[35] = data[13] | (data[14] << 8); - video->renderer->writePalette(video->renderer, 17, video->palette[17]); - video->renderer->writePalette(video->renderer, 18, video->palette[18]); - video->renderer->writePalette(video->renderer, 19, video->palette[19]); - video->renderer->writePalette(video->renderer, 33, video->palette[33]); - video->renderer->writePalette(video->renderer, 34, video->palette[34]); - video->renderer->writePalette(video->renderer, 35, video->palette[35]); + video->palette[9] = data[9] | (data[10] << 8); + video->palette[10] = data[11] | (data[12] << 8); + video->palette[11] = data[13] | (data[14] << 8); + video->renderer->writePalette(video->renderer, 5, video->palette[5]); + video->renderer->writePalette(video->renderer, 6, video->palette[6]); + video->renderer->writePalette(video->renderer, 7, video->palette[7]); + video->renderer->writePalette(video->renderer, 9, video->palette[9]); + video->renderer->writePalette(video->renderer, 10, video->palette[10]); + video->renderer->writePalette(video->renderer, 11, video->palette[11]); break; case SGB_PAL_SET: for (i = 0; i < 4; ++i) { From 12931fbe25e2ccbd796a826368a444e79316e426 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 Jan 2018 23:42:43 -0800 Subject: [PATCH 118/152] GB Video: Implement SGB ATTR_CHR --- include/mgba/internal/gb/renderers/software.h | 3 ++ src/gb/renderers/software.c | 45 +++++++++++++++++++ src/gb/video.c | 1 + 3 files changed, 49 insertions(+) diff --git a/include/mgba/internal/gb/renderers/software.h b/include/mgba/internal/gb/renderers/software.h index 99ea2f4d2..91f7421f4 100644 --- a/include/mgba/internal/gb/renderers/software.h +++ b/include/mgba/internal/gb/renderers/software.h @@ -45,6 +45,9 @@ struct GBVideoSoftwareRenderer { int sgbDataSets; uint8_t sgbPartialDataSet[15]; bool sgbBorders; + int sgbAttrX; + int sgbAttrY; + int sgbAttrDirection; }; void GBVideoSoftwareRendererCreate(struct GBVideoSoftwareRenderer*); diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 486e688f5..5214b3d7f 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -302,6 +302,51 @@ static void GBVideoSoftwareRendererWriteSGBPacket(struct GBVideoRenderer* render if (i < 16 && softwareRenderer->sgbDataSets) { memcpy(softwareRenderer->sgbPartialDataSet, &softwareRenderer->sgbPacket[i], 16 - i); } + break; + case SGB_ATTR_CHR: + if (softwareRenderer->sgbPacketId == 1) { + softwareRenderer->sgbAttrX = softwareRenderer->sgbPacket[1]; + softwareRenderer->sgbAttrY = softwareRenderer->sgbPacket[2]; + if (softwareRenderer->sgbAttrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) { + softwareRenderer->sgbAttrX = 0; + } + if (softwareRenderer->sgbAttrY >= GB_VIDEO_VERTICAL_PIXELS / 8) { + softwareRenderer->sgbAttrY = 0; + } + softwareRenderer->sgbDataSets = softwareRenderer->sgbPacket[3]; + softwareRenderer->sgbDataSets |= softwareRenderer->sgbPacket[4] << 8; + softwareRenderer->sgbAttrDirection = softwareRenderer->sgbPacket[5]; + i = 6; + } else { + i = 0; + } + for (; i < 16 && softwareRenderer->sgbDataSets; ++i) { + int j; + for (j = 0; j < 4 && softwareRenderer->sgbDataSets; ++j, --softwareRenderer->sgbDataSets) { + uint8_t p = softwareRenderer->sgbPacket[i] >> (6 - j * 2); + _setAttribute(renderer->sgbAttributes, softwareRenderer->sgbAttrX, softwareRenderer->sgbAttrY, p & 3); + if (softwareRenderer->sgbAttrDirection) { + ++softwareRenderer->sgbAttrY; + if (softwareRenderer->sgbAttrY >= GB_VIDEO_VERTICAL_PIXELS / 8) { + softwareRenderer->sgbAttrY = 0; + ++softwareRenderer->sgbAttrX; + } + if (softwareRenderer->sgbAttrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) { + softwareRenderer->sgbAttrX = 0; + } + } else { + ++softwareRenderer->sgbAttrX; + if (softwareRenderer->sgbAttrX >= GB_VIDEO_HORIZONTAL_PIXELS / 8) { + softwareRenderer->sgbAttrX = 0; + ++softwareRenderer->sgbAttrY; + } + if (softwareRenderer->sgbAttrY >= GB_VIDEO_VERTICAL_PIXELS / 8) { + softwareRenderer->sgbAttrY = 0; + } + } + } + } + break; case SGB_ATRC_EN: if (softwareRenderer->sgbBorders) { diff --git a/src/gb/video.c b/src/gb/video.c index d6552c6a6..aafff0714 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -685,6 +685,7 @@ void GBVideoWriteSGBPacket(struct GBVideo* video, uint8_t* data) { } break; case SGB_ATTR_BLK: + case SGB_ATTR_CHR: case SGB_PAL_TRN: case SGB_ATRC_EN: case SGB_CHR_TRN: From 36a0f43dc2c2cce7098c56f396cbcc9fdaef2ab0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 Jan 2018 23:49:48 -0800 Subject: [PATCH 119/152] GBA Memory: Fix copy-on-write memory leak --- CHANGES | 1 + src/gba/memory.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index e770d2001..d42fb845e 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,7 @@ Bugfixes: - GBA Audio: Increase PSG volume (fixes mgba.io/i/932) - 3DS: Fix opening files in directory names with trailing slashes - GB MBC: Fix MBC2 saves (fixes mgba.io/i/954) + - GBA Memory: Fix copy-on-write memory leak Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/memory.c b/src/gba/memory.c index fa989d397..4829ce23e 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1635,6 +1635,7 @@ void _pristineCow(struct GBA* gba) { } gba->memory.rom = newRom; gba->memory.hw.gpioBase = &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]; + gba->isPristine = false; } void GBAPrintFlush(struct GBA* gba) { From 715efc63bd7178fc5a0058588b81de03fe74bc6d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 9 Jan 2018 20:06:42 -0800 Subject: [PATCH 120/152] PSP2: Better truncate --- src/platform/psp2/sce-vfs.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/platform/psp2/sce-vfs.c b/src/platform/psp2/sce-vfs.c index 284479660..fad952d78 100644 --- a/src/platform/psp2/sce-vfs.c +++ b/src/platform/psp2/sce-vfs.c @@ -10,6 +10,10 @@ #include #include +#ifndef SCE_CST_SIZE +#define SCE_CST_SIZE 0x0004 +#endif + struct VFileSce { struct VFile d; @@ -120,20 +124,8 @@ static void _vfsceUnmap(struct VFile* vf, void* memory, size_t size) { static void _vfsceTruncate(struct VFile* vf, size_t size) { struct VFileSce* vfsce = (struct VFileSce*) vf; - SceOff cur = sceIoLseek(vfsce->fd, 0, SEEK_CUR); - SceOff end = sceIoLseek(vfsce->fd, 0, SEEK_END); - if (end < size) { - uint8_t buffer[2048] = {}; - size_t write = size - end; - while (write >= sizeof(buffer)) { - sceIoWrite(vfsce->fd, buffer, sizeof(buffer)); - write -= sizeof(buffer); - } - if (write) { - sceIoWrite(vfsce->fd, buffer, write); - } - } // TODO: Else - sceIoLseek(vfsce->fd, cur, SEEK_SET); + SceIoStat stat = { .st_size = size }; + sceIoChstatByFd(vfsce->fd, &stat, SCE_CST_SIZE); } ssize_t _vfsceSize(struct VFile* vf) { From 7097d249af677d955f7f58a56242a2e35253423e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 10 Jan 2018 00:39:35 -0800 Subject: [PATCH 121/152] 3DS: Use ctrlib Thread abstraction --- include/mgba-util/platform/3ds/threading.h | 25 +++------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/include/mgba-util/platform/3ds/threading.h b/include/mgba-util/platform/3ds/threading.h index 0eda3dd43..769039615 100644 --- a/include/mgba-util/platform/3ds/threading.h +++ b/include/mgba-util/platform/3ds/threading.h @@ -11,18 +11,9 @@ #include <3ds.h> #include -#ifdef _3DS -// ctrulib already has a type called Thread -#define Thread CustomThread -#endif - #define THREAD_ENTRY void typedef ThreadFunc ThreadEntry; -typedef struct { - Handle handle; - u8* stack; -} Thread; typedef LightLock Mutex; typedef struct { Mutex mutex; @@ -103,22 +94,12 @@ static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) if (!entry || !thread) { return 1; } - thread->stack = memalign(8, 0x8000); - if (!thread->stack) { - return 1; - } - bool isNew3DS; - APT_CheckNew3DS(&isNew3DS); - if (isNew3DS && svcCreateThread(&thread->handle, entry, (u32) context, (u32*) &thread->stack[0x8000], 0x18, 2) == 0) { - return 0; - } - return svcCreateThread(&thread->handle, entry, (u32) context, (u32*) &thread->stack[0x8000], 0x18, -1); + *thread = threadCreate(entry, context, 0x8000, 0x18, 2, true); + return !*thread; } static inline int ThreadJoin(Thread thread) { - svcWaitSynchronization(thread.handle, U64_MAX); - free(thread.stack); - return 0; + return threadJoin(thread, U64_MAX); } static inline void ThreadSetName(const char* name) { From e40cba5c8ba76b00aa87d5e97ac57b12d11c2d16 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 10 Jan 2018 00:40:41 -0800 Subject: [PATCH 122/152] GUI: Move running check into frontend --- src/feature/gui/gui-runner.c | 16 ++++++---------- src/feature/gui/gui-runner.h | 1 + src/platform/3ds/main.c | 7 ++++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 3ba09a415..ca4168d9f 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -18,10 +18,6 @@ #include #include -#ifdef _3DS -#include <3ds.h> -#endif - #include mLOG_DECLARE_CATEGORY(GUI_RUNNER); @@ -334,13 +330,13 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { gettimeofday(&tv, 0); runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec; - while (true) { -#ifdef _3DS - running = aptMainLoop(); - if (!running) { - break; + while (running) { + if (runner->running) { + running = runner->running(runner); + if (!running) { + break; + } } -#endif uint32_t guiKeys; uint32_t heldKeys; GUIPollInput(&runner->params, &guiKeys, &heldKeys); diff --git a/src/feature/gui/gui-runner.h b/src/feature/gui/gui-runner.h index 0a288951f..daa3c6690 100644 --- a/src/feature/gui/gui-runner.h +++ b/src/feature/gui/gui-runner.h @@ -70,6 +70,7 @@ struct mGUIRunner { void (*incrementScreenMode)(struct mGUIRunner*); void (*setFrameLimiter)(struct mGUIRunner*, bool limit); uint16_t (*pollGameInput)(struct mGUIRunner*); + bool (*running)(struct mGUIRunner*); }; void mGUIInit(struct mGUIRunner*, const char* port); diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 3377b3be9..b04cf5a27 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -678,6 +678,10 @@ static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) { tickCounter = svcGetSystemTick(); } +static bool _running(struct mGUIRunner* runner) { + return aptMainLoop(); +} + static uint32_t _pollInput(const struct mInputMap* map) { hidScanInput(); int activeKeys = hidKeysHeld(); @@ -1020,7 +1024,8 @@ int main() { .unpaused = _gameLoaded, .incrementScreenMode = _incrementScreenMode, .setFrameLimiter = _setFrameLimiter, - .pollGameInput = _pollGameInput + .pollGameInput = _pollGameInput, + .running = _running }; mGUIInit(&runner, "3ds"); From 6f5ec7d5e46a22524fa0e4f1b1f46582a28b4101 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 10 Jan 2018 08:54:23 -0800 Subject: [PATCH 123/152] GUI: Ability to select GB/GBC/SGB BIOS on console ports --- CHANGES | 1 + src/feature/gui/gui-config.c | 70 +++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index d42fb845e..beb686265 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Features: - GameShark and Action Replay button support - AGBPrint support - Debugger: Conditional breakpoints and watchpoints + - Ability to select GB/GBC/SGB BIOS on console ports Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/src/feature/gui/gui-config.c b/src/feature/gui/gui-config.c index 9d4e39fb2..d6e377277 100644 --- a/src/feature/gui/gui-config.c +++ b/src/feature/gui/gui-config.c @@ -10,6 +10,9 @@ #include "feature/gui/gui-runner.h" #include "feature/gui/remap.h" #include +#ifdef M_CORE_GB +#include +#endif #include #include @@ -55,9 +58,23 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t .nStates = 2 }; *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { - .title = "Select BIOS path", - .data = "bios", + .title = "Select GBA BIOS path", + .data = "gba.bios", }; +#ifdef M_CORE_GB + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select GB BIOS path", + .data = "gb.bios", + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select GBC BIOS path", + .data = "gbc.bios", + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select SGB BIOS path", + .data = "sgb.bios", + }; +#endif size_t i; const char* mapNames[GUI_MAX_INPUTS + 1]; if (runner->keySources) { @@ -88,7 +105,12 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t .data = 0, }; enum GUIMenuExitReason reason; - char biosPath[256] = ""; + char gbaBiosPath[256] = ""; +#ifdef M_CORE_GB + char gbBiosPath[256] = ""; + char gbcBiosPath[256] = ""; + char sgbBiosPath[256] = ""; +#endif struct GUIMenuItem* item; for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { @@ -105,8 +127,17 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t break; } if (!strcmp(item->data, "*SAVE")) { - if (biosPath[0]) { - mCoreConfigSetValue(&runner->config, "bios", biosPath); + if (gbaBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "gba.bios", gbaBiosPath); + } + if (gbBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "gb.bios", gbBiosPath); + } + if (gbcBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "gbc.bios", gbcBiosPath); + } + if (sgbBiosPath[0]) { + mCoreConfigSetValue(&runner->config, "sgb.bios", sgbBiosPath); } for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { item = GUIMenuItemListGetPointer(&menu.items, i); @@ -130,13 +161,36 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t mGUIRemapKeys(&runner->params, &runner->core->inputMap, &runner->keySources[item->state]); continue; } - if (!strcmp(item->data, "bios")) { + if (!strcmp(item->data, "gba.bios")) { // TODO: show box if failed - if (!GUISelectFile(&runner->params, biosPath, sizeof(biosPath), GBAIsBIOS)) { - biosPath[0] = '\0'; + if (!GUISelectFile(&runner->params, gbaBiosPath, sizeof(gbaBiosPath), GBAIsBIOS)) { + gbaBiosPath[0] = '\0'; } continue; } +#ifdef M_CORE_GB + if (!strcmp(item->data, "gb.bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, gbBiosPath, sizeof(gbBiosPath), GBIsBIOS)) { + gbBiosPath[0] = '\0'; + } + continue; + } + if (!strcmp(item->data, "gbc.bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, gbcBiosPath, sizeof(gbcBiosPath), GBIsBIOS)) { + gbcBiosPath[0] = '\0'; + } + continue; + } + if (!strcmp(item->data, "sgb.bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, sgbBiosPath, sizeof(sgbBiosPath), GBIsBIOS)) { + sgbBiosPath[0] = '\0'; + } + continue; + } +#endif if (item->validStates) { ++item->state; if (item->state >= item->nStates) { From cee6569bde476ea94e05237baf62d3d4ce1d2cf9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 12 Jan 2018 20:52:15 -0800 Subject: [PATCH 124/152] Libretro: Add frameskip option --- CHANGES | 1 + src/platform/libretro/libretro.c | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index beb686265..e0f527536 100644 --- a/CHANGES +++ b/CHANGES @@ -54,6 +54,7 @@ Misc: - GB MBC: Remove erroneous bank 0 wrapping - GBA Cheats: Allow multiple ROM patches in the same slot - GB: Skip BIOS option now works + - Libretro: Add frameskip option 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index d411dc4df..fe39a104b 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -88,6 +88,13 @@ static void _reloadSettings(void) { } } + var.key = "mgba_frameskip"; + var.value = 0; + if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + opts.frameskip = strtol(var.value, NULL, 10); + + } + mCoreConfigLoadDefaults(&core->config, &opts); mCoreLoadConfig(core); } @@ -105,6 +112,7 @@ void retro_set_environment(retro_environment_t env) { { "mgba_use_bios", "Use BIOS file if found (requires restart); ON|OFF" }, { "mgba_skip_bios", "Skip BIOS intro (requires restart); OFF|ON" }, { "mgba_idle_optimization", "Idle loop removal; Remove Known|Detect and Remove|Don't Remove" }, + { "mgba_frameskip", "Frameskip; 0|1|2|3|4|5|6|7|8|9|10" }, { 0, 0 } }; @@ -222,16 +230,22 @@ void retro_run(void) { uint16_t keys; inputPollCallback(); - struct retro_variable var = { - .key = "mgba_allow_opposing_directions", - .value = 0 - }; - bool updated = false; if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) { + struct retro_variable var = { + .key = "mgba_allow_opposing_directions", + .value = 0 + }; if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { ((struct GBA*) core->board)->allowOpposingDirections = strcmp(var.value, "yes") == 0; } + + var.key = "mgba_frameskip"; + var.value = 0; + if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { + mCoreConfigSetUIntValue(&core->config, "frameskip", strtol(var.value, NULL, 10)); + mCoreLoadConfig(core); + } } keys = 0; From 38e3dbc0fcec4356fff4212dee2c905d494f6c69 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 14 Jan 2018 10:38:47 -0800 Subject: [PATCH 125/152] GBA Memory: Matrix Memory support --- CHANGES | 1 + include/mgba/internal/gba/matrix.h | 28 +++++++++++ include/mgba/internal/gba/memory.h | 2 + src/gba/gba.c | 30 ++++++++---- src/gba/matrix.c | 75 ++++++++++++++++++++++++++++++ src/gba/memory.c | 9 ++++ 6 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 include/mgba/internal/gba/matrix.h create mode 100644 src/gba/matrix.c diff --git a/CHANGES b/CHANGES index e0f527536..c7da89547 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,7 @@ Misc: - GBA Cheats: Allow multiple ROM patches in the same slot - GB: Skip BIOS option now works - Libretro: Add frameskip option + - GBA Memory: 64 MiB GBA Video cartridge support 0.6.1: (2017-10-01) Bugfixes: diff --git a/include/mgba/internal/gba/matrix.h b/include/mgba/internal/gba/matrix.h new file mode 100644 index 000000000..cc4191c8b --- /dev/null +++ b/include/mgba/internal/gba/matrix.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2013-2018 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_MATRIX_H +#define GBA_MATRIX_H + +#include + +CXX_GUARD_START + +struct GBAMatrix { + uint32_t cmd; + uint32_t paddr; + uint32_t vaddr; + uint32_t size; +}; + +struct GBA; +struct GBAMemory; +void GBAMatrixReset(struct GBA*); +void GBAMatrixWrite(struct GBA*, uint32_t address, uint32_t value); +void GBAMatrixWrite16(struct GBA*, uint32_t address, uint16_t value); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/internal/gba/memory.h b/include/mgba/internal/gba/memory.h index abb6525ca..f98b7e94a 100644 --- a/include/mgba/internal/gba/memory.h +++ b/include/mgba/internal/gba/memory.h @@ -17,6 +17,7 @@ CXX_GUARD_START #include #include #include +#include enum GBAMemoryRegion { REGION_BIOS = 0x0, @@ -106,6 +107,7 @@ struct GBAMemory { struct GBACartridgeHardware hw; struct GBASavedata savedata; struct GBAVFameCart vfame; + struct GBAMatrix matrix; size_t romSize; uint32_t romMask; uint16_t romID; diff --git a/src/gba/gba.c b/src/gba/gba.c index f295c483c..196aa8493 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -125,7 +125,9 @@ void GBAUnloadROM(struct GBA* gba) { if (gba->romVf) { #ifndef FIXED_ROM_BUFFER - gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->pristineRomSize); + if (gba->isPristine) { + gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->pristineRomSize); + } #endif gba->romVf->close(gba->romVf); gba->romVf = NULL; @@ -207,6 +209,9 @@ void GBAReset(struct ARMCore* cpu) { gba->debug = false; memset(gba->debugString, 0, sizeof(gba->debugString)); + if (gba->pristineRomSize > SIZE_CART0) { + GBAMatrixReset(gba); + } if (!gba->romVf && gba->memory.rom) { GBASkipBIOS(gba); @@ -355,23 +360,30 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) { gba->pristineRomSize = vf->size(vf); vf->seek(vf, 0, SEEK_SET); if (gba->pristineRomSize > SIZE_CART0) { - gba->pristineRomSize = SIZE_CART0; - } - gba->isPristine = true; + gba->isPristine = false; + gba->memory.romSize = 0x01000000; #ifdef FIXED_ROM_BUFFER - if (gba->pristineRomSize <= romBufferSize) { gba->memory.rom = romBuffer; - vf->read(vf, romBuffer, gba->pristineRomSize); - } #else - gba->memory.rom = vf->map(vf, gba->pristineRomSize, MAP_READ); + gba->memory.rom = anonymousMemoryMap(SIZE_CART0); #endif + } else { + gba->isPristine = true; +#ifdef FIXED_ROM_BUFFER + if (gba->pristineRomSize <= romBufferSize) { + gba->memory.rom = romBuffer; + vf->read(vf, romBuffer, gba->pristineRomSize); + } +#else + gba->memory.rom = vf->map(vf, gba->pristineRomSize, MAP_READ); +#endif + gba->memory.romSize = gba->pristineRomSize; + } if (!gba->memory.rom) { mLOG(GBA, WARN, "Couldn't map ROM"); return false; } gba->yankedRomSize = 0; - gba->memory.romSize = gba->pristineRomSize; gba->memory.romMask = toPow2(gba->memory.romSize) - 1; gba->memory.mirroring = false; gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize); diff --git a/src/gba/matrix.c b/src/gba/matrix.c new file mode 100644 index 000000000..5c8fef48a --- /dev/null +++ b/src/gba/matrix.c @@ -0,0 +1,75 @@ +/* Copyright (c) 2013-2018 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include + +#include +#include +#include +#include + +static void _remapMatrix(struct GBA* gba) { + gba->romVf->seek(gba->romVf, gba->memory.matrix.paddr, SEEK_SET); + gba->romVf->read(gba->romVf, &gba->memory.rom[gba->memory.matrix.vaddr >> 2], gba->memory.matrix.size); +} + +void GBAMatrixReset(struct GBA* gba) { + gba->memory.matrix.paddr = 0x200; + gba->memory.matrix.size = 0x1000; + + gba->memory.matrix.vaddr = 0; + _remapMatrix(gba); + gba->memory.matrix.vaddr = 0x1000; + _remapMatrix(gba); + + gba->memory.matrix.paddr = 0; + gba->memory.matrix.vaddr = 0; + gba->memory.matrix.size = 0x100; + _remapMatrix(gba); +} + +void GBAMatrixWrite(struct GBA* gba, uint32_t address, uint32_t value) { + switch (address) { + case 0x0: + gba->memory.matrix.cmd = value; + switch (value) { + case 0x01: + case 0x11: + _remapMatrix(gba); + break; + default: + mLOG(GBA_MEM, STUB, "Unknown Matrix command: %08X", value); + break; + } + return; + case 0x4: + gba->memory.matrix.paddr = value & 0x03FFFFFF; + return; + case 0x8: + gba->memory.matrix.vaddr = value & 0x007FFFFF; + return; + case 0xC: + gba->memory.matrix.size = value << 9; + return; + } + mLOG(GBA_MEM, STUB, "Unknown Matrix write: %08X:%04X", address, value); +} + +void GBAMatrixWrite16(struct GBA* gba, uint32_t address, uint16_t value) { + switch (address) { + case 0x0: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.cmd & 0xFFFF0000)); + break; + case 0x4: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.paddr & 0xFFFF0000)); + break; + case 0x8: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.vaddr & 0xFFFF0000)); + break; + case 0xC: + GBAMatrixWrite(gba, address, value | (gba->memory.matrix.size & 0xFFFF0000)); + break; + } +} diff --git a/src/gba/memory.c b/src/gba/memory.c index 4829ce23e..704a660e7 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -137,6 +137,7 @@ void GBAMemoryReset(struct GBA* gba) { } GBADMAReset(gba); + memset(&gba->memory.matrix, 0, sizeof(gba->memory.matrix)); } static void _analyzeForIdleLoop(struct GBA* gba, struct ARMCore* cpu, uint32_t address) { @@ -748,6 +749,10 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { #define STORE_CART \ wait += waitstatesRegion[address >> BASE_OFFSET]; \ + if (memory->matrix.size && (address & 0x01FFFF00) == 0x00800100) { \ + GBAMatrixWrite(gba, address & 0x3C, value); \ + break; \ + } \ mLOG(GBA_MEM, STUB, "Unimplemented memory Store32: 0x%08X", address); #define STORE_SRAM \ @@ -867,6 +872,10 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle GBAHardwareGPIOWrite(&memory->hw, reg, value); break; } + if (memory->matrix.size && (address & 0x01FFFF00) == 0x00800100) { + GBAMatrixWrite16(gba, address & 0x3C, value); + break; + } // Fall through case REGION_CART0_EX: if ((address & 0x00FFFFFF) >= AGB_PRINT_BASE) { From d30d89245229b18be0c4ed92323a730becb2a77d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 14 Jan 2018 19:24:09 -0800 Subject: [PATCH 126/152] Core: Fix ROM patches not being unloaded when disabled (fixes #962) --- CHANGES | 1 + src/core/cheats.c | 2 +- src/gb/cheats.c | 6 +++++- src/gba/cheats.c | 6 +++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index c7da89547..486d327db 100644 --- a/CHANGES +++ b/CHANGES @@ -39,6 +39,7 @@ Bugfixes: - 3DS: Fix opening files in directory names with trailing slashes - GB MBC: Fix MBC2 saves (fixes mgba.io/i/954) - GBA Memory: Fix copy-on-write memory leak + - Core: Fix ROM patches not being unloaded when disabled (fixes mgba.io/i/962) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/core/cheats.c b/src/core/cheats.c index 4f5b38695..30b802bca 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -266,10 +266,10 @@ void mCheatAutosave(struct mCheatDevice* device) { #endif void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { + cheats->refresh(cheats, device); if (!cheats->enabled) { return; } - cheats->refresh(cheats, device); size_t elseLoc = 0; size_t endLoc = 0; diff --git a/src/gb/cheats.c b/src/gb/cheats.c index 118ddde20..5c202782c 100644 --- a/src/gb/cheats.c +++ b/src/gb/cheats.c @@ -244,7 +244,11 @@ bool GBCheatAddLine(struct mCheatSet* set, const char* line, int type) { static void GBCheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) { struct GBCheatSet* gbset = (struct GBCheatSet*) cheats; - _patchROM(device, gbset); + if (cheats->enabled) { + _patchROM(device, gbset); + } else { + _unpatchROM(device, gbset); + } } static void GBCheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) { diff --git a/src/gba/cheats.c b/src/gba/cheats.c index b17244c76..ec32924f3 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -274,7 +274,11 @@ bool GBACheatAddLine(struct mCheatSet* set, const char* line, int type) { static void GBACheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) { struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats; - _patchROM(device, gbaset); + if (cheats->enabled) { + _patchROM(device, gbaset); + } else { + _unpatchROM(device, gbaset); + } } static void GBACheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) { From 199e3ef4ada7a49b400c5d613ad0522c6c8e427f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 14 Jan 2018 19:24:24 -0800 Subject: [PATCH 127/152] Core: Fix crash if cheat autosave fails --- src/core/cheats.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/cheats.c b/src/core/cheats.c index 30b802bca..d0ed72508 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -260,6 +260,9 @@ void mCheatAutosave(struct mCheatDevice* device) { return; } struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return; + } mCheatSaveFile(device, vf); vf->close(vf); } From 779296058787683b46550bd44538c5293eb1eb1e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 14 Jan 2018 19:30:27 -0800 Subject: [PATCH 128/152] CMake: Enforce -pthread for C++ (fixes #909) --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b707c8b8..872be422b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,6 +213,7 @@ elseif(UNIX) endif() if(NOT APPLE AND NOT HAIKU) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) From 67e03bda91d1ffd0162a545d91f819fd56195923 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 14 Jan 2018 21:03:10 -0800 Subject: [PATCH 129/152] GBA I/O: Fix writing to DISPCNT CGB flag (fixes #902) --- CHANGES | 1 + src/gba/extra/proxy.c | 7 ++++++- src/gba/renderers/video-software.c | 1 + src/gba/video.c | 3 +++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 486d327db..a45d8413b 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,7 @@ Bugfixes: - GB MBC: Fix MBC2 saves (fixes mgba.io/i/954) - GBA Memory: Fix copy-on-write memory leak - Core: Fix ROM patches not being unloaded when disabled (fixes mgba.io/i/962) + - GBA I/O: Fix writing to DISPCNT CGB flag (fixes mgba.io/i/902) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/extra/proxy.c b/src/gba/extra/proxy.c index da38540a3..2cb003e25 100644 --- a/src/gba/extra/proxy.c +++ b/src/gba/extra/proxy.c @@ -171,11 +171,16 @@ static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address) { uint16_t GBAVideoProxyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; switch (address) { + case REG_DISPCNT: + value &= 0xFFF7; + break; case REG_BG0CNT: case REG_BG1CNT: + value &= 0xDFFF; + break; case REG_BG2CNT: case REG_BG3CNT: - value &= 0xFFCF; + value &= 0xFFFF; break; case REG_BG0HOFS: case REG_BG0VOFS: diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index 14d6ba0f5..a4609c01b 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -157,6 +157,7 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender switch (address) { case REG_DISPCNT: + value &= 0xFFF7; softwareRenderer->dispcnt = value; GBAVideoSoftwareRendererUpdateDISPCNT(softwareRenderer); break; diff --git a/src/gba/video.c b/src/gba/video.c index 9715ccff0..069e79555 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -217,6 +217,9 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* GBAVideoCacheWriteVideoRegister(renderer->cache, address, value); } switch (address) { + case REG_DISPCNT: + value &= 0xFFF7; + break; case REG_BG0CNT: case REG_BG1CNT: value &= 0xDFFF; From 9fac945e1c46f332acad6dcab3ff31dd8ba7216d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 15 Jan 2018 05:48:19 -0800 Subject: [PATCH 130/152] 3DS: Scale font based on glyph heights (fixes #961) --- CHANGES | 1 + src/platform/3ds/gui-font.c | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index a45d8413b..09eb2956d 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,7 @@ Misc: - GB: Skip BIOS option now works - Libretro: Add frameskip option - GBA Memory: 64 MiB GBA Video cartridge support + - 3DS: Scale font based on glyph heights (fixes mgba.io/i/961) 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 3c1abc0a0..67d44f4c9 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -11,13 +11,12 @@ #include "ctr-gpu.h" -#define CELL_HEIGHT 16 -#define CELL_WIDTH 16 -#define FONT_SIZE 0.52f +#define FONT_SIZE 15.6f struct GUIFont { C3D_Tex* sheets; C3D_Tex icons; + float size; }; struct GUIFont* GUIFontCreate(void) { @@ -29,6 +28,7 @@ struct GUIFont* GUIFontCreate(void) { C3D_Tex* tex; TGLP_s* glyphInfo = fontGetGlyphInfo(); + guiFont->size = FONT_SIZE / glyphInfo->cellHeight; guiFont->sheets = malloc(sizeof(*guiFont->sheets) * glyphInfo->nSheets); int i; @@ -59,16 +59,14 @@ void GUIFontDestroy(struct GUIFont* font) { } unsigned GUIFontHeight(const struct GUIFont* font) { - UNUSED(font); - return fontGetInfo()->lineFeed * FONT_SIZE; + return fontGetInfo()->lineFeed * font->size; } unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { - UNUSED(font); int index = fontGlyphIndexFromCodePoint(glyph); charWidthInfo_s* info = fontGetCharWidthInfo(index); if (info) { - return info->charWidth * FONT_SIZE; + return info->charWidth * font->size; } return 0; } @@ -108,7 +106,7 @@ void GUIFontDrawGlyph(const struct GUIFont* font, int glyph_x, int glyph_y, uint u16 v = tex->height * data.texcoord.bottom; ctrAddRectEx(color, x, y, - tex->width * width * FONT_SIZE, tex->height * height * -FONT_SIZE, + tex->width * width * font->size, tex->height * height * -font->size, u, v, tex->width * width, tex->height * height, 0); } From c76b1b7a0155679667a89dd11fb43b5b96b0ee9e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 17 Jan 2018 22:52:45 -0800 Subject: [PATCH 131/152] CMake: Fix lzma include dir --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 872be422b..1d38e3a07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -588,7 +588,7 @@ elseif(USE_ZLIB) endif() if (USE_LZMA) - include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/third-party/lzma) + include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/lzma) add_definitions(-D_7ZIP_PPMD_SUPPPORT) list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-lzma.c) set(LZMA_SRC From bf7247ad4c6e30b428d444607f14242f8f4f4a2d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 17 Jan 2018 22:53:26 -0800 Subject: [PATCH 132/152] FFmpeg: Fix build with newer lavc (fixes #966) --- src/feature/ffmpeg/ffmpeg-encoder.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index a7a206788..ca8633ee6 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -291,7 +291,11 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { encoder->video->gop_size = 60; encoder->video->max_b_frames = 3; if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { +#ifdef AV_CODEC_FLAG_GLOBAL_HEADER + encoder->video->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif } if (strcmp(vcodec->name, "libx264") == 0) { // Try to adaptively figure out when you can use a slower encoder From abf1af30b1e02f21c03c8f6ff01e68b23999f244 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 17 Jan 2018 23:22:54 -0800 Subject: [PATCH 133/152] GBA Memory: Partially revert fec4c0644 (fixes #840) --- CHANGES | 1 + src/gba/memory.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 09eb2956d..72e59f0d8 100644 --- a/CHANGES +++ b/CHANGES @@ -41,6 +41,7 @@ Bugfixes: - GBA Memory: Fix copy-on-write memory leak - Core: Fix ROM patches not being unloaded when disabled (fixes mgba.io/i/962) - GBA I/O: Fix writing to DISPCNT CGB flag (fixes mgba.io/i/902) + - GBA Memory: Partially revert prefetch changes (fixes mgba.io/i/840) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/gba/memory.c b/src/gba/memory.c index 704a660e7..54e8ef8fd 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1589,8 +1589,8 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { maxLoads -= previousLoads; } - int32_t s = cpu->memory.activeSeqCycles16; - int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16; + int32_t s = cpu->memory.activeSeqCycles16 + 1; + int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1; // Figure out how many sequential loads we can jam in int32_t stall = s; @@ -1611,7 +1611,7 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1); // The next |loads|S waitstates disappear entirely, so long as they're all in a row - cpu->cycles -= stall; + cpu->cycles -= (s - 1) * loads; return wait; } From 6dd18fd86e8b37d068ef8b3ba36739fb1597f985 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 17 Jan 2018 23:27:41 -0800 Subject: [PATCH 134/152] FFmpeg: Fix build with newer lavc (fixes #966) --- src/feature/ffmpeg/ffmpeg-encoder.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index ca8633ee6..2da3754f8 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -229,7 +229,11 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { AVDictionary* opts = 0; av_dict_set(&opts, "strict", "-2", 0); if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) { +#ifdef AV_CODEC_FLAG_GLOBAL_HEADER + encoder->audio->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif } avcodec_open2(encoder->audio, acodec, &opts); av_dict_free(&opts); From f6cc37850f5f60ce1a14ae07768c766da3584cee Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 19 Jan 2018 17:20:35 -0800 Subject: [PATCH 135/152] 3DS: Change takeover title to AR Games (fixes #965) --- src/platform/3ds/hbl.xml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/platform/3ds/hbl.xml b/src/platform/3ds/hbl.xml index b96036950..a004677ce 100644 --- a/src/platform/3ds/hbl.xml +++ b/src/platform/3ds/hbl.xml @@ -1,14 +1,8 @@ - 0004001000020d00 - 0004001000021d00 - 0004001000022d00 - 0004001000026d00 - 0004001000027d00 - 0004001000028d00 - 0004001020020d00 - 0004001020021d00 - 0004001020022d00 - 0004001020026d00 - 0004001020027d00 - 0004001020028d00 + 0004001000020e00 + 0004001000021e00 + 0004001000022e00 + 0004001000026e00 + 0004001000027e00 + 0004001000028e00 From 789a84d2e25b2a6e7df0bb86351ec81726313eed Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 21 Jan 2018 00:45:15 -0800 Subject: [PATCH 136/152] PSP2: Use system enter key by default --- CHANGES | 1 + src/platform/psp2/CMakeLists.txt | 16 +++++++++++++++- src/platform/psp2/main.c | 21 +++++++++++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 72e59f0d8..eaee274ad 100644 --- a/CHANGES +++ b/CHANGES @@ -60,6 +60,7 @@ Misc: - Libretro: Add frameskip option - GBA Memory: 64 MiB GBA Video cartridge support - 3DS: Scale font based on glyph heights (fixes mgba.io/i/961) + - PSP2: Use system enter key by default 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index eb8f31d75..34ea754e7 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -13,7 +13,21 @@ source_group("PS Vita-specific code" FILES ${OS_SRC}) list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(CORE_VFS_SRC ${CORE_VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceAppMgr_stub -lSceCtrl_stub -lSceAudio_stub -lSceCamera_stub -lSceCommonDialog_stub -lSceDisplay_stub -lSceGxm_stub -lSceMotion_stub -lScePgf_stub -lScePhotoExport_stub -lScePower_stub -lSceSysmodule_stub -lSceTouch_stub -l${M_LIBRARY}) +set(OS_LIB -lvita2d -l${M_LIBRARY} + -lSceAppMgr_stub + -lSceAppUtil_stub + -lSceAudio_stub + -lSceCamera_stub + -lSceCommonDialog_stub + -lSceCtrl_stub + -lSceDisplay_stub + -lSceGxm_stub + -lSceMotion_stub + -lScePgf_stub + -lScePhotoExport_stub + -lScePower_stub + -lSceSysmodule_stub + -lSceTouch_stub) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 40068e700..3d70b7cdc 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -12,12 +12,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -163,11 +165,26 @@ int main() { sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START); sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG_WIDE); sceSysmoduleLoadModule(SCE_SYSMODULE_PHOTO_EXPORT); + sceSysmoduleLoadModule(SCE_SYSMODULE_APPUTIL); mGUIInit(&runner, "psvita"); - mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CROSS, GUI_INPUT_SELECT); - mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CIRCLE, GUI_INPUT_BACK); + int enterButton; + SceAppUtilInitParam initParam; + SceAppUtilBootParam bootParam; + memset(&initParam, 0, sizeof(SceAppUtilInitParam)); + memset(&bootParam, 0, sizeof(SceAppUtilBootParam)); + sceAppUtilInit(&initParam, &bootParam); + sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_ENTER_BUTTON, &enterButton); + sceAppUtilShutdown(); + + if (enterButton == SCE_SYSTEM_PARAM_ENTER_BUTTON_CIRCLE) { + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CROSS, GUI_INPUT_BACK); + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CIRCLE, GUI_INPUT_SELECT); + } else { + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CROSS, GUI_INPUT_SELECT); + mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_CIRCLE, GUI_INPUT_BACK); + } mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_TRIANGLE, GUI_INPUT_CANCEL); mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_UP, GUI_INPUT_UP); mPSP2MapKey(&runner.params.keyMap, SCE_CTRL_DOWN, GUI_INPUT_DOWN); From 0e9ba00dbfd3ed4586fbe79b64ebb319db5221a5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 10 Jan 2018 00:42:09 -0800 Subject: [PATCH 137/152] 3DS: Add experimental autosave --- src/feature/gui/gui-runner.c | 16 ++++++++ src/platform/3ds/main.c | 75 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index ca4168d9f..6e68168fb 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -317,6 +317,13 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { mLOG(GUI_RUNNER, DEBUG, "Reseting..."); runner->core->reset(runner->core); mLOG(GUI_RUNNER, DEBUG, "Reset!"); + + if (mCoreLoadState(runner->core, 0, SAVESTATE_SCREENSHOT | SAVESTATE_RTC)) { + struct VFile* autosave = mCoreGetState(runner->core, 0, true); + autosave->truncate(autosave, 0); + autosave->close(autosave); + } + bool running = true; if (runner->gameLoaded) { runner->gameLoaded(runner); @@ -469,6 +476,14 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { mappedMemoryFree(drawState.screenshot, w * h * 4); } + struct VFile* autosave = mCoreGetState(runner->core, 0, false); + if (autosave) { + autosave->close(autosave); + autosave = mCoreGetState(runner->core, 0, true); + autosave->truncate(autosave, 0); + autosave->close(autosave); + } + if (runner->config.port) { mLOG(GUI_RUNNER, DEBUG, "Saving key sources..."); if (runner->keySources) { @@ -483,6 +498,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { mInputMapDeinit(&runner->core->inputMap); mLOG(GUI_RUNNER, DEBUG, "Deinitializing core..."); runner->core->deinit(runner->core); + runner->core = NULL; GUIMenuItemListDeinit(&pauseMenu.items); GUIMenuItemListDeinit(&stateSaveMenu.items); diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index b04cf5a27..7ce131219 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -6,6 +6,7 @@ #include #include +#include #ifdef M_CORE_GBA #include #include @@ -22,6 +23,7 @@ #include #include +#include #include "ctr-gpu.h" #include <3ds.h> @@ -104,6 +106,12 @@ static C3D_Tex upscaleBufferTex; static aptHookCookie cookie; +static Thread autosave; +static struct VFile* autosaveBuffer = NULL; +static Mutex autosaveMutex; +static Condition autosaveCond; +static struct mCore* autosaveCore = NULL; + extern bool allocateRomBuffer(void); static bool _initGpu(void) { @@ -193,6 +201,33 @@ static void _aptHook(APT_HookType hook, void* user) { } } +static void _autosaveThread(void* context) { + bool* running = context; + MutexLock(&autosaveMutex); + while (*running) { + ConditionWait(&autosaveCond, &autosaveMutex); + if (*running && autosaveCore) { + struct VFile* vf = mCoreGetState(autosaveCore, 0, true); + void* mem = autosaveBuffer->map(autosaveBuffer, autosaveBuffer->size(autosaveBuffer), MAP_READ); + vf->write(vf, mem, autosaveBuffer->size(autosaveBuffer)); + autosaveBuffer->unmap(autosaveBuffer, mem, autosaveBuffer->size(autosaveBuffer)); + vf->close(vf); + } + } + MutexUnlock(&autosaveMutex); +} + +static void _tryAutosave(struct mCore* core) { + if (!autosaveBuffer) { + autosaveBuffer = VFileMemChunk(NULL, 0); + } + MutexLock(&autosaveMutex); + autosaveCore = core; + mCoreSaveStateNamed(core, autosaveBuffer, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); + ConditionWake(&autosaveCond); + MutexUnlock(&autosaveMutex); +} + static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) { mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key); } @@ -425,6 +460,10 @@ static void _gameLoaded(struct mGUIRunner* runner) { } } } + + MutexLock(&autosaveMutex); + autosaveCore = runner->core; + MutexUnlock(&autosaveMutex); } static void _gameUnloaded(struct mGUIRunner* runner) { @@ -458,6 +497,10 @@ static void _gameUnloaded(struct mGUIRunner* runner) { default: break; } + + MutexLock(&autosaveMutex); + autosaveCore = NULL; + MutexUnlock(&autosaveMutex); } static void _drawTex(struct mCore* core, bool faded) { @@ -679,6 +722,16 @@ static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) { } static bool _running(struct mGUIRunner* runner) { + static int frame = 0; + if (autosaveCore) { + ++frame; + if (frame == 300) { + _tryAutosave(autosaveCore); + frame = 0; + } + } else { + frame = 0; + } return aptMainLoop(); } @@ -1041,7 +1094,29 @@ int main() { _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS); _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS); + bool autosaveActive = true; + MutexInit(&autosaveMutex); + ConditionInit(&autosaveCond); + + APT_SetAppCpuTimeLimit(20); + autosave = threadCreate(_autosaveThread, &autosaveActive, 0x4000, 0x1F, 1, true); + mGUIRunloop(&runner); + + MutexLock(&autosaveMutex); + autosaveActive = false; + ConditionWake(&autosaveCond); + MutexUnlock(&autosaveMutex); + + threadJoin(autosave, U64_MAX); + + if (autosaveBuffer) { + autosaveBuffer->close(autosaveBuffer); + } + + ConditionDeinit(&autosaveCond); + MutexDeinit(&autosaveMutex); + mGUIDeinit(&runner); _cleanup(); From 45c2fdf7ed224b11b4175d816451e645e4a45245 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 20 Jan 2018 15:00:45 -0800 Subject: [PATCH 138/152] GUI: Make autosave portable --- src/feature/gui/gui-runner.c | 125 +++++++++++++++++++++++++++-------- src/feature/gui/gui-runner.h | 20 ++++++ src/platform/3ds/main.c | 81 +++-------------------- 3 files changed, 124 insertions(+), 102 deletions(-) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 6e68168fb..e4a589861 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -23,6 +23,7 @@ mLOG_DECLARE_CATEGORY(GUI_RUNNER); mLOG_DEFINE_CATEGORY(GUI_RUNNER, "GUI Runner", "gui.runner"); +#define AUTOSAVE_GRANULARITY 600 #define FPS_GRANULARITY 120 #define FPS_BUFFER_SIZE 3 @@ -34,19 +35,11 @@ enum { RUNNER_SCREENSHOT, RUNNER_CONFIG, RUNNER_RESET, - RUNNER_COMMAND_MASK = 0xFFFF, - - RUNNER_STATE_1 = 0x10000, - RUNNER_STATE_2 = 0x20000, - RUNNER_STATE_3 = 0x30000, - RUNNER_STATE_4 = 0x40000, - RUNNER_STATE_5 = 0x50000, - RUNNER_STATE_6 = 0x60000, - RUNNER_STATE_7 = 0x70000, - RUNNER_STATE_8 = 0x80000, - RUNNER_STATE_9 = 0x90000, + RUNNER_COMMAND_MASK = 0xFFFF }; +#define RUNNER_STATE(X) ((X) << 16) + static const struct mInputPlatformInfo _mGUIKeyInfo = { .platformName = "gui", .keyId = (const char*[GUI_INPUT_MAX]) { @@ -141,6 +134,21 @@ static uint8_t _readLux(struct GBALuminanceSource* lux) { return 0xFF - value; } +static void _tryAutosave(struct mGUIRunner* runner) { +#ifdef DISABLE_THREADING + mCoreSaveState(runner->core, 0, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); +#else + if (!runner->autosave.buffer) { + runner->autosave.buffer = VFileMemChunk(NULL, 0); + } + MutexLock(&runner->autosave.mutex); + runner->autosave.core = runner->core; + mCoreSaveStateNamed(runner->core, runner->autosave.buffer, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); + ConditionWake(&runner->autosave.cond); + MutexUnlock(&runner->autosave.mutex); +#endif +} + void mGUIInit(struct mGUIRunner* runner, const char* port) { GUIInit(&runner->params); runner->port = port; @@ -174,9 +182,34 @@ void mGUIInit(struct mGUIRunner* runner, const char* port) { strncpy(runner->params.currentPath, lastPath, PATH_MAX - 1); runner->params.currentPath[PATH_MAX - 1] = '\0'; } + +#ifndef DISABLE_THREADING + if (!runner->autosave.running) { + runner->autosave.running = true; + MutexInit(&runner->autosave.mutex); + ConditionInit(&runner->autosave.cond); + ThreadCreate(&runner->autosave.thread, mGUIAutosaveThread, &runner->autosave); + } +#endif } void mGUIDeinit(struct mGUIRunner* runner) { +#ifndef DISABLE_THREADING + MutexLock(&runner->autosave.mutex); + runner->autosave.running = false; + ConditionWake(&runner->autosave.cond); + MutexUnlock(&runner->autosave.mutex); + + ThreadJoin(runner->autosave.thread); + + ConditionDeinit(&runner->autosave.cond); + MutexDeinit(&runner->autosave.mutex); + + if (runner->autosave.buffer) { + runner->autosave.buffer->close(runner->autosave.buffer); + } +#endif + if (runner->teardown) { runner->teardown(runner); } @@ -239,25 +272,26 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Save state", .submenu = &stateSaveMenu }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Load state", .submenu = &stateLoadMenu }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_1) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_2) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_3) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_4) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_5) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_6) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_7) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_8) }; - *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE_9) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(1)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(2)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(3)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(4)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(5)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(6)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(7)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(8)) }; + *GUIMenuItemListAppend(&stateSaveMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_SAVE_STATE | RUNNER_STATE(9)) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_1) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_2) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_3) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_4) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_5) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_6) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) }; - *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "Autosave", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(0)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 1", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(1)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 2", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(2)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 3", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(3)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 4", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(4)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 5", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(5)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 6", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(6)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(7)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(8)) }; + *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE(9)) }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG }; @@ -325,6 +359,13 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { } bool running = true; + +#ifndef DISABLE_THREADING + MutexLock(&runner->autosave.mutex); + runner->autosave.core = runner->core; + MutexUnlock(&runner->autosave.mutex); +#endif + if (runner->gameLoaded) { runner->gameLoaded(runner); } @@ -415,6 +456,9 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t)); } } + if (runner->core->frameCounter(runner->core) % AUTOSAVE_GRANULARITY == 0) { + _tryAutosave(runner); + } } } @@ -467,6 +511,11 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { if (runner->gameUnloaded) { runner->gameUnloaded(runner); } +#ifndef DISABLE_THREADING + MutexLock(&runner->autosave.mutex); + runner->autosave.core = NULL; + MutexUnlock(&runner->autosave.mutex); +#endif mLOG(GUI_RUNNER, DEBUG, "Unloading game..."); runner->core->unloadROM(runner->core); drawState.screenshotId = 0; @@ -524,3 +573,21 @@ void mGUIRunloop(struct mGUIRunner* runner) { mGUIRun(runner, path); } } + +#ifndef DISABLE_THREADING +THREAD_ENTRY mGUIAutosaveThread(void* context) { + struct mGUIAutosaveContext* autosave = context; + MutexLock(&autosave->mutex); + while (autosave->running) { + ConditionWait(&autosave->cond, &autosave->mutex); + if (autosave->running && autosave->core) { + struct VFile* vf = mCoreGetState(autosave->core, 0, true); + void* mem = autosave->buffer->map(autosave->buffer, autosave->buffer->size(autosave->buffer), MAP_READ); + vf->write(vf, mem, autosave->buffer->size(autosave->buffer)); + autosave->buffer->unmap(autosave->buffer, mem, autosave->buffer->size(autosave->buffer)); + vf->close(vf); + } + } + MutexUnlock(&autosave->mutex); +} +#endif diff --git a/src/feature/gui/gui-runner.h b/src/feature/gui/gui-runner.h index daa3c6690..6c91d1347 100644 --- a/src/feature/gui/gui-runner.h +++ b/src/feature/gui/gui-runner.h @@ -15,6 +15,7 @@ CXX_GUARD_START #include #include #include +#include enum mGUIInput { mGUI_INPUT_INCREASE_BRIGHTNESS = GUI_INPUT_USER_START, @@ -38,12 +39,27 @@ struct mGUIRunnerLux { int luxLevel; }; +#ifndef DISABLE_THREADING +struct VFile; +struct mGUIAutosaveContext { + struct VFile* buffer; + struct mCore* core; + Thread thread; + Mutex mutex; + Condition cond; + bool running; +}; +#endif + struct mGUIRunner { struct mCore* core; struct GUIParams params; struct mGUIBackground background; struct mGUIRunnerLux luminanceSource; +#ifndef DISABLE_THREADING + struct mGUIAutosaveContext autosave; +#endif struct mInputMap guiKeys; struct mCoreConfig config; @@ -78,6 +94,10 @@ void mGUIDeinit(struct mGUIRunner*); void mGUIRun(struct mGUIRunner*, const char* path); void mGUIRunloop(struct mGUIRunner*); +#ifndef DISABLE_THREADING +THREAD_ENTRY mGUIAutosaveThread(void* context); +#endif + CXX_GUARD_END #endif diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 7ce131219..1bc030369 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -106,12 +106,6 @@ static C3D_Tex upscaleBufferTex; static aptHookCookie cookie; -static Thread autosave; -static struct VFile* autosaveBuffer = NULL; -static Mutex autosaveMutex; -static Condition autosaveCond; -static struct mCore* autosaveCore = NULL; - extern bool allocateRomBuffer(void); static bool _initGpu(void) { @@ -201,33 +195,6 @@ static void _aptHook(APT_HookType hook, void* user) { } } -static void _autosaveThread(void* context) { - bool* running = context; - MutexLock(&autosaveMutex); - while (*running) { - ConditionWait(&autosaveCond, &autosaveMutex); - if (*running && autosaveCore) { - struct VFile* vf = mCoreGetState(autosaveCore, 0, true); - void* mem = autosaveBuffer->map(autosaveBuffer, autosaveBuffer->size(autosaveBuffer), MAP_READ); - vf->write(vf, mem, autosaveBuffer->size(autosaveBuffer)); - autosaveBuffer->unmap(autosaveBuffer, mem, autosaveBuffer->size(autosaveBuffer)); - vf->close(vf); - } - } - MutexUnlock(&autosaveMutex); -} - -static void _tryAutosave(struct mCore* core) { - if (!autosaveBuffer) { - autosaveBuffer = VFileMemChunk(NULL, 0); - } - MutexLock(&autosaveMutex); - autosaveCore = core; - mCoreSaveStateNamed(core, autosaveBuffer, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); - ConditionWake(&autosaveCond); - MutexUnlock(&autosaveMutex); -} - static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) { mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key); } @@ -460,10 +427,6 @@ static void _gameLoaded(struct mGUIRunner* runner) { } } } - - MutexLock(&autosaveMutex); - autosaveCore = runner->core; - MutexUnlock(&autosaveMutex); } static void _gameUnloaded(struct mGUIRunner* runner) { @@ -497,10 +460,6 @@ static void _gameUnloaded(struct mGUIRunner* runner) { default: break; } - - MutexLock(&autosaveMutex); - autosaveCore = NULL; - MutexUnlock(&autosaveMutex); } static void _drawTex(struct mCore* core, bool faded) { @@ -722,16 +681,7 @@ static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) { } static bool _running(struct mGUIRunner* runner) { - static int frame = 0; - if (autosaveCore) { - ++frame; - if (frame == 300) { - _tryAutosave(autosaveCore); - frame = 0; - } - } else { - frame = 0; - } + UNUSED(runner); return aptMainLoop(); } @@ -1081,6 +1031,13 @@ int main() { .running = _running }; + runner.autosave.running = true; + MutexInit(&runner.autosave.mutex); + ConditionInit(&runner.autosave.cond); + + APT_SetAppCpuTimeLimit(20); + runner.autosave.thread = threadCreate(mGUIAutosaveThread, &runner.autosave, 0x4000, 0x1F, 1, true); + mGUIInit(&runner, "3ds"); _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL); @@ -1094,29 +1051,7 @@ int main() { _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS); _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS); - bool autosaveActive = true; - MutexInit(&autosaveMutex); - ConditionInit(&autosaveCond); - - APT_SetAppCpuTimeLimit(20); - autosave = threadCreate(_autosaveThread, &autosaveActive, 0x4000, 0x1F, 1, true); - mGUIRunloop(&runner); - - MutexLock(&autosaveMutex); - autosaveActive = false; - ConditionWake(&autosaveCond); - MutexUnlock(&autosaveMutex); - - threadJoin(autosave, U64_MAX); - - if (autosaveBuffer) { - autosaveBuffer->close(autosaveBuffer); - } - - ConditionDeinit(&autosaveCond); - MutexDeinit(&autosaveMutex); - mGUIDeinit(&runner); _cleanup(); From 50cbf732b51ada4e6ce2a8f425540e701f7721dd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 20 Jan 2018 15:48:57 -0800 Subject: [PATCH 139/152] GUI: Make autosave configurable --- src/feature/gui/gui-config.c | 20 ++++++++++++++++++++ src/feature/gui/gui-runner.c | 36 ++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/feature/gui/gui-config.c b/src/feature/gui/gui-config.c index d6e377277..a0572903a 100644 --- a/src/feature/gui/gui-config.c +++ b/src/feature/gui/gui-config.c @@ -47,6 +47,26 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t }, .nStates = 2 }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Autosave state", + .data = "autosave", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On" + }, + .nStates = 2 + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Autoload state", + .data = "autoload", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On" + }, + .nStates = 2 + }; *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { .title = "Use BIOS if found", .data = "useBios", diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index e4a589861..f92a33816 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -135,6 +135,12 @@ static uint8_t _readLux(struct GBALuminanceSource* lux) { } static void _tryAutosave(struct mGUIRunner* runner) { + int autosave = false; + mCoreConfigGetIntValue(&runner->config, "autosave", &autosave); + if (!autosave) { + return; + } + #ifdef DISABLE_THREADING mCoreSaveState(runner->core, 0, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); #else @@ -168,6 +174,12 @@ void mGUIInit(struct mGUIRunner* runner, const char* port) { // TODO: Do we need to load more defaults? mCoreConfigSetDefaultIntValue(&runner->config, "volume", 0x100); mCoreConfigSetDefaultValue(&runner->config, "idleOptimization", "detect"); + mCoreConfigSetDefaultIntValue(&runner->config, "autoload", true); +#ifdef DISABLE_THREADING + mCoreConfigSetDefaultIntValue(&runner->config, "autosave", false); +#else + mCoreConfigSetDefaultIntValue(&runner->config, "autosave", true); +#endif mCoreConfigLoad(&runner->config); mCoreConfigGetIntValue(&runner->config, "logLevel", &logger.logLevel); @@ -352,10 +364,11 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->core->reset(runner->core); mLOG(GUI_RUNNER, DEBUG, "Reset!"); - if (mCoreLoadState(runner->core, 0, SAVESTATE_SCREENSHOT | SAVESTATE_RTC)) { - struct VFile* autosave = mCoreGetState(runner->core, 0, true); - autosave->truncate(autosave, 0); - autosave->close(autosave); + + int autoload = false; + mCoreConfigGetIntValue(&runner->config, "autoload", &autoload); + if (autoload) { + mCoreLoadState(runner->core, 0, SAVESTATE_SCREENSHOT | SAVESTATE_RTC); } bool running = true; @@ -516,6 +529,13 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->autosave.core = NULL; MutexUnlock(&runner->autosave.mutex); #endif + + int autosave = false; + mCoreConfigGetIntValue(&runner->config, "autosave", &autosave); + if (autosave) { + mCoreSaveState(runner->core, 0, SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); + } + mLOG(GUI_RUNNER, DEBUG, "Unloading game..."); runner->core->unloadROM(runner->core); drawState.screenshotId = 0; @@ -525,14 +545,6 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { mappedMemoryFree(drawState.screenshot, w * h * 4); } - struct VFile* autosave = mCoreGetState(runner->core, 0, false); - if (autosave) { - autosave->close(autosave); - autosave = mCoreGetState(runner->core, 0, true); - autosave->truncate(autosave, 0); - autosave->close(autosave); - } - if (runner->config.port) { mLOG(GUI_RUNNER, DEBUG, "Saving key sources..."); if (runner->keySources) { From 5973433aa06cbe46e0262161aaefbc3df9ca1c63 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 20 Jan 2018 23:20:44 -0800 Subject: [PATCH 140/152] GUI: Align autosave counter with frames run, not core frames --- src/feature/gui/gui-runner.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index f92a33816..e0ad1a155 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -391,6 +391,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { gettimeofday(&tv, 0); runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec; + int frame = 0; while (running) { if (runner->running) { running = runner->running(runner); @@ -469,9 +470,11 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t)); } } - if (runner->core->frameCounter(runner->core) % AUTOSAVE_GRANULARITY == 0) { + if (frame == AUTOSAVE_GRANULARITY) { + frame = 0; _tryAutosave(runner); } + ++frame; } } From 8ec934c58d12e70d2e5748ca7285be6c0d380630 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 23 Jan 2018 20:43:55 -0800 Subject: [PATCH 141/152] Qt: Port autosave to Qt interface --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 42 +++++++++++++++++++++++------- src/platform/qt/CoreController.h | 4 +++ src/platform/qt/SettingsView.cpp | 4 +++ src/platform/qt/SettingsView.ui | 31 ++++++++++++++++++++-- src/platform/qt/Window.cpp | 2 +- 6 files changed, 71 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index eaee274ad..0338d6e9a 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Features: - AGBPrint support - Debugger: Conditional breakpoints and watchpoints - Ability to select GB/GBC/SGB BIOS on console ports + - Optional automatic state saving/loading Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 8cf21da00..a6d60b99c 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -28,8 +28,9 @@ #include #include -using namespace QGBA; +#define AUTOSAVE_GRANULARITY 600 +using namespace QGBA; CoreController::CoreController(mCore* core, QObject* parent) : QObject(parent) @@ -48,6 +49,12 @@ CoreController::CoreController(mCore* core, QObject* parent) m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer->data()), size.width()); + m_resetActions.append([this]() { + if (m_autoload) { + mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags); + } + }); + m_threadContext.startCallback = [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); @@ -61,6 +68,8 @@ CoreController::CoreController(mCore* core, QObject* parent) break; } + controller->updateFastForward(); + if (controller->m_multiplayer) { controller->m_multiplayer->attachGame(controller); } @@ -79,10 +88,6 @@ CoreController::CoreController(mCore* core, QObject* parent) controller->m_override->apply(context->core); } - if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) { - mCoreDeleteState(context->core, 0); - } - controller->m_resetActions.clear(); QSize size = controller->screenDimensions(); @@ -100,12 +105,24 @@ CoreController::CoreController(mCore* core, QObject* parent) m_threadContext.frameCallback = [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); + if (controller->m_autosaveCounter == AUTOSAVE_GRANULARITY) { + if (controller->m_autosave) { + mCoreSaveState(context->core, 0, controller->m_saveStateFlags); + } + controller->m_autosaveCounter = 0; + } + ++controller->m_autosaveCounter; + controller->finishFrame(); }; m_threadContext.cleanCallback = [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); + if (controller->m_autosave) { + mCoreSaveState(context->core, 0, controller->m_saveStateFlags); + } + controller->clearMultiplayerController(); QMetaObject::invokeMethod(controller, "stopping"); }; @@ -126,7 +143,8 @@ CoreController::CoreController(mCore* core, QObject* parent) mThreadLogger* logContext = reinterpret_cast(logger); mCoreThread* context = logContext->p; - static const char* savestateMessage = "State %i loaded"; + static const char* savestateMessage = "State %i saved"; + static const char* loadstateMessage = "State %i loaded"; static const char* savestateFailedMessage = "State %i failed to load"; static int biosCat = -1; static int statusCat = -1; @@ -152,7 +170,7 @@ CoreController::CoreController(mCore* core, QObject* parent) #endif if (category == statusCat) { // Slot 0 is reserved for suspend points - if (strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { + if (strncmp(loadstateMessage, format, strlen(loadstateMessage)) == 0) { va_list argc; va_copy(argc, args); int slot = va_arg(argc, int); @@ -160,7 +178,7 @@ CoreController::CoreController(mCore* core, QObject* parent) if (slot == 0) { format = "Loaded suspend state"; } - } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0) { + } else if (strncmp(savestateFailedMessage, format, strlen(savestateFailedMessage)) == 0 || strncmp(savestateMessage, format, strlen(savestateMessage)) == 0) { va_list argc; va_copy(argc, args); int slot = va_arg(argc, int); @@ -232,10 +250,14 @@ void CoreController::loadConfig(ConfigController* config) { m_videoSync = config->getOption("videoSync", m_videoSync).toInt(); m_audioSync = config->getOption("audioSync", m_audioSync).toInt(); m_fpsTarget = config->getOption("fpsTarget").toFloat(); + m_autosave = config->getOption("autosave", false).toInt(); + m_autoload = config->getOption("autoload", true).toInt(); m_autofireThreshold = config->getOption("autofireThreshold", m_autofireThreshold).toInt(); - updateFastForward(); mCoreLoadForeignConfig(m_threadContext.core, config->config()); - mCoreThreadRewindParamsChanged(&m_threadContext); + if (hasStarted()) { + updateFastForward(); + mCoreThreadRewindParamsChanged(&m_threadContext); + } } #ifdef USE_DEBUGGERS diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index c2fb9a9e8..9022d6812 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -195,6 +195,10 @@ private: bool m_audioSync = AUDIO_SYNC; bool m_videoSync = VIDEO_SYNC; + bool m_autosave; + bool m_autoload; + int m_autosaveCounter; + int m_fastForward = false; int m_fastForwardForced = false; float m_fastForwardRatio = -1.f; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 8e44dc025..a36b8b324 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -357,6 +357,8 @@ void SettingsView::updateConfig() { saveSetting("showFps", m_ui.showFps); saveSetting("cheatAutoload", m_ui.cheatAutoload); saveSetting("cheatAutosave", m_ui.cheatAutosave); + saveSetting("autoload", m_ui.autoload); + saveSetting("autosave", m_ui.autosave); if (m_ui.fastForwardUnbounded->isChecked()) { saveSetting("fastForwardRatio", "-1"); @@ -478,6 +480,8 @@ void SettingsView::reloadConfig() { loadSetting("showFps", m_ui.showFps, true); loadSetting("cheatAutoload", m_ui.cheatAutoload, true); loadSetting("cheatAutosave", m_ui.cheatAutosave, true); + loadSetting("autoload", m_ui.autoload, true); + loadSetting("autosave", m_ui.autosave, false); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 26172ad3e..81a572011 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -512,7 +512,14 @@ - + + + + Qt::Horizontal + + + + Automatically save cheats @@ -522,7 +529,7 @@ - + Automatically load cheats @@ -532,6 +539,26 @@ + + + + Automatically save state + + + true + + + + + + + Automatically load state + + + true + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index b79f0d2ed..c6aab3531 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1858,8 +1858,8 @@ void Window::setController(CoreController* controller, const QString& fname) { m_pendingPatch = QString(); } - m_controller->start(); m_controller->loadConfig(m_config); + m_controller->start(); } WindowBackground::WindowBackground(QWidget* parent) From ed21eeb1591d52218f3af01592e0555b495eb92f Mon Sep 17 00:00:00 2001 From: rootfather Date: Wed, 24 Jan 2018 19:31:25 +0100 Subject: [PATCH 142/152] Qt: Add German translation for savestate autosave feature --- src/platform/qt/ts/mgba-de.ts | 180 ++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 85 deletions(-) diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index 2b8bd6bab..e8a8cdf3c 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -83,44 +83,44 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + 0 0 - + Palette # Palette # - + Address Adresse - + 0x06000000 0x06000000 - + Red Rot - + Green Grün - + Blue Blau - - - + + + 0x00 (00) 0x00 (00) @@ -1183,22 +1183,22 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. QGBA::CoreController - + Failed to open save file: %1 Fehler beim Öffnen der Speicherdatei: %1 - + Failed to open game file: %1 Fehler beim Öffnen der Spieldatei: %1 - + Failed to open snapshot file for reading: %1 Konnte Snapshot-Datei %1 nicht zum Lesen öffnen - + Failed to open snapshot file for writing: %1 Konnte Snapshot-Datei %1 nicht zum Schreiben öffnen @@ -4163,7 +4163,7 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd. - + frames Bild(er) @@ -4229,92 +4229,102 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Bildwiederholrate in der Titelleiste anzeigen - + Automatically save cheats Cheats automatisch speichern - + Automatically load cheats Cheats automatisch laden - + + Automatically save state + Zustand (Savestate) automatisch speichern + + + + Automatically load state + Zustand (Savestate) automatisch laden + + + Cheats Cheats - + Game Boy model Game Boy-Modell - - - + + + Autodetect Automatisch erkennen - - - + + + Game Boy (DMG) Game Boy (DMG) - - - + + + Super Game Boy (SGB) Super Game Boy (SGB) - - - + + + Game Boy Color (CGB) Game Boy Color (CGB) - - - + + + Game Boy Advance (AGB) Game Boy Advance (AGB) - + Super Game Boy model Super Game Boy-Modell - + Game Boy Color model Game Boy Color-Modell - + Default BG colors: Standard-Hintergrundfarben: - + Default sprite colors 1: Standard-Sprite-Farben 1: - + Default sprite colors 2: Standard-Sprite-Farben 2: - + Super Game Boy borders Super Game Boy-Rahmen - + Camera driver: Kamera-Treiber: @@ -4334,53 +4344,53 @@ Game Boy Advance ist ein eingetragenes Warenzeichen von Nintendo Co., Ltd.Cache leeren - + Fast forward speed: Vorlauf-Geschwindigkeit: - + Rewind affects save data Rücklauf beeinflusst Speicherdaten - + Preload entire ROM into memory ROM-Datei vollständig in Arbeitsspeicher vorladen - - - - - - - - - + + + + + + + + + Browse Durchsuchen - + Use BIOS file if found BIOS-Datei verwenden, wenn vorhanden - + Skip BIOS intro BIOS-Intro überspringen - + × × - + Unbounded unbegrenzt @@ -4400,17 +4410,17 @@ wenn vorhanden Pause, wenn inaktiv - + Run all Alle ausführen - + Remove known Bekannte entfernen - + Detect and remove Erkennen und entfernen @@ -4420,25 +4430,25 @@ wenn vorhanden Gegensätzliche Eingaberichtungen erlauben - - + + Screenshot Screenshot - - + + Save data Speicherdaten - - + + Cheat codes Cheat-Codes - + Enable rewind Rücklauf aktivieren @@ -4448,76 +4458,76 @@ wenn vorhanden Bilineare Filterung - + Rewind history: Rücklauf-Verlauf: - + Idle loops: Leerlaufprozesse: - + Savestate extra data: Zusätzliche Savestate-Daten: - + Load extra data: Lade zusätzliche Daten: - + Autofire interval: Autofeuer-Intervall: - + GB BIOS file: Datei mit GB-BIOS: - + GBA BIOS file: Datei mit GBA-BIOS: - + GBC BIOS file: Datei mit GBC-BIOS: - + SGB BIOS file: Datei mit SGB-BIOS: - + Save games Spielstände - - - - - + + + + + Same directory as the ROM Verzeichnis der ROM-Datei - + Save states Savestates - + Screenshots Screenshots - + Patches Patches From 3a9d77d9e0fe59d488a538474381a564b1c061d3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 25 Jan 2018 18:39:24 -0800 Subject: [PATCH 143/152] PSP2: Access to ur0 and uma0 partitions --- CHANGES | 1 + include/mgba-util/vfs.h | 2 +- src/platform/psp2/main.c | 3 +- src/platform/psp2/sce-vfs.c | 107 ++++++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 0338d6e9a..3997ce2da 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ Features: - Debugger: Conditional breakpoints and watchpoints - Ability to select GB/GBC/SGB BIOS on console ports - Optional automatic state saving/loading + - Access to ur0 and uma0 partitions on the Vita Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba-util/vfs.h b/include/mgba-util/vfs.h index 562fa52b5..f10f3c577 100644 --- a/include/mgba-util/vfs.h +++ b/include/mgba-util/vfs.h @@ -87,7 +87,7 @@ struct VDir* VDirOpenZip(const char* path, int flags); struct VDir* VDirOpen7z(const char* path, int flags); #endif -#if defined(__wii__) || defined(_3DS) +#if defined(__wii__) || defined(_3DS) || defined(PSP2) struct VDir* VDeviceList(void); #endif diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 3d70b7cdc..8ad9f5c7e 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -89,7 +89,8 @@ int main() { struct mGUIRunner runner = { .params = { PSP2_HORIZONTAL_PIXELS, PSP2_VERTICAL_PIXELS, - font, "ux0:data", _drawStart, _drawEnd, + font, "", + _drawStart, _drawEnd, _pollInput, _pollCursor, _batteryState, 0, 0, diff --git a/src/platform/psp2/sce-vfs.c b/src/platform/psp2/sce-vfs.c index fad952d78..383847291 100644 --- a/src/platform/psp2/sce-vfs.c +++ b/src/platform/psp2/sce-vfs.c @@ -52,6 +52,16 @@ static bool _vdsceDeleteFile(struct VDir* vd, const char* path); static const char* _vdesceName(struct VDirEntry* vde); static enum VFSType _vdesceType(struct VDirEntry* vde); +static bool _vdlsceClose(struct VDir* vd); +static void _vdlsceRewind(struct VDir* vd); +static struct VDirEntry* _vdlsceListNext(struct VDir* vd); +static struct VFile* _vdlsceOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdlsceOpenDir(struct VDir* vd, const char* path); +static bool _vdlsceDeleteFile(struct VDir* vd, const char* path); + +static const char* _vdlesceName(struct VDirEntry* vde); +static enum VFSType _vdlesceType(struct VDirEntry* vde); + struct VFile* VFileOpenSce(const char* path, int flags, SceMode mode) { struct VFileSce* vfsce = malloc(sizeof(struct VFileSce)); if (!vfsce) { @@ -148,6 +158,10 @@ bool _vfsceSync(struct VFile* vf, const void* buffer, size_t size) { } struct VDir* VDirOpen(const char* path) { + if (!path || !path[0]) { + return VDeviceList(); + } + SceUID dir = sceIoDopen(path); if (dir < 0) { return 0; @@ -250,3 +264,96 @@ static enum VFSType _vdesceType(struct VDirEntry* vde) { } return VFS_FILE; } + +struct VDirEntrySceDevList { + struct VDirEntry d; + ssize_t index; + const char* name; +}; + +struct VDirSceDevList { + struct VDir d; + struct VDirEntrySceDevList vde; +}; + +static const char* _devs[] = { + "ux0:", + "ur0:", + "uma0:" +}; + +struct VDir* VDeviceList() { + struct VDirSceDevList* vd = malloc(sizeof(struct VDirSceDevList)); + if (!vd) { + return 0; + } + + vd->d.close = _vdlsceClose; + vd->d.rewind = _vdlsceRewind; + vd->d.listNext = _vdlsceListNext; + vd->d.openFile = _vdlsceOpenFile; + vd->d.openDir = _vdlsceOpenDir; + vd->d.deleteFile = _vdlsceDeleteFile; + + vd->vde.d.name = _vdlesceName; + vd->vde.d.type = _vdlesceType; + vd->vde.index = -1; + vd->vde.name = 0; + + return &vd->d; +} + +static bool _vdlsceClose(struct VDir* vd) { + struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd; + free(vdl); + return true; +} + +static void _vdlsceRewind(struct VDir* vd) { + struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd; + vdl->vde.name = NULL; + vdl->vde.index = -1; +} + +static struct VDirEntry* _vdlsceListNext(struct VDir* vd) { + struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd; + while (vdl->vde.index < 3) { + ++vdl->vde.index; + vdl->vde.name = _devs[vdl->vde.index]; + SceUID dir = sceIoDopen(vdl->vde.name); + if (dir < 0) { + continue; + } + sceIoDclose(dir); + return &vdl->vde.d; + } + return 0; +} + +static struct VFile* _vdlsceOpenFile(struct VDir* vd, const char* path, int mode) { + UNUSED(vd); + UNUSED(path); + UNUSED(mode); + return NULL; +} + +static struct VDir* _vdlsceOpenDir(struct VDir* vd, const char* path) { + UNUSED(vd); + return VDirOpen(path); +} + +static bool _vdlsceDeleteFile(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + return false; +} + +static const char* _vdlesceName(struct VDirEntry* vde) { + struct VDirEntrySceDevList* vdle = (struct VDirEntrySceDevList*) vde; + return vdle->name; +} + +static enum VFSType _vdlesceType(struct VDirEntry* vde) { + UNUSED(vde); + return VFS_DIRECTORY; +} From 8aaa6105020f2ecae1b0cef29dbdb90c0a53cd57 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 25 Jan 2018 18:40:22 -0800 Subject: [PATCH 144/152] Example: Attempt 32/16-bit colors based on response --- src/platform/example/client-server/client.c | 37 +++++++++++---------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/platform/example/client-server/client.c b/src/platform/example/client-server/client.c index 86d8fdc6f..3c20144ff 100644 --- a/src/platform/example/client-server/client.c +++ b/src/platform/example/client-server/client.c @@ -29,19 +29,19 @@ int main() { SocketRecv(server, &bpp, sizeof(bpp)); width = ntohl(width); height = ntohl(height); - if (ntohl(bpp) != BYTES_PER_PIXEL) { + bpp = ntohl(bpp); + ssize_t bufferSize = width * height * bpp; + +#if !SDL_VERSION_ATLEAST(2, 0, 0) + if (bpp == 2) { + SDL_SetVideoMode(width, height, 16, SDL_DOUBLEBUF | SDL_HWSURFACE); + } else if (bpp == 4) { + SDL_SetVideoMode(width, height, 32, SDL_DOUBLEBUF | SDL_HWSURFACE); + } else { SocketClose(server); SocketSubsystemDeinit(); return 1; } - ssize_t bufferSize = width * height * BYTES_PER_PIXEL; - -#if !SDL_VERSION_ATLEAST(2, 0, 0) -#ifdef COLOR_16_BIT - SDL_SetVideoMode(width, height, 16, SDL_DOUBLEBUF | SDL_HWSURFACE); -#else - SDL_SetVideoMode(width, height, 32, SDL_DOUBLEBUF | SDL_HWSURFACE); -#endif #endif #if SDL_VERSION_ATLEAST(2, 0, 0) @@ -49,15 +49,16 @@ int main() { SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); Uint32 pixfmt; -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - pixfmt = SDL_PIXELFORMAT_RGB565; -#else - pixfmt = SDL_PIXELFORMAT_ABGR1555; -#endif -#else - pixfmt = SDL_PIXELFORMAT_ABGR8888; -#endif + if (bpp == 2) { + pixfmt = SDL_PIXELFORMAT_RGB565; + } else if (bpp == 4) { + pixfmt = SDL_PIXELFORMAT_ABGR8888; + } else { + SocketClose(server); + SocketSubsystemDeinit(); + return 1; + } + SDL_Texture* sdlTex = SDL_CreateTexture(renderer, pixfmt, SDL_TEXTUREACCESS_STREAMING, width, height); #endif From d133cabd33cad4e8896046ba7af98debb3d003a8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 25 Jan 2018 18:46:10 -0800 Subject: [PATCH 145/152] Windows: Package script --- CMakeLists.txt | 14 +++++++++++++- README.md | 6 +++--- src/platform/qt/CMakeLists.txt | 15 +++++++++++---- src/platform/sdl/CMakeLists.txt | 4 +++- tools/deploy-win.sh | 27 +++++++++++++++++++++++++++ tools/dlls.gdb | 3 +++ 6 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 tools/deploy-win.sh create mode 100644 tools/dlls.gdb diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d38e3a07..57dee4b56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,10 @@ set(BUILD_GL ON CACHE STRING "Build with OpenGL") set(BUILD_GLES2 OFF CACHE STRING "Build with OpenGL|ES 2") set(USE_EPOXY ON CACHE STRING "Build with libepoxy") set(DISABLE_DEPS OFF CACHE BOOL "Build without dependencies") +if(WIN32) + set(WIN32_UNIX_PATHS OFF CACHE BOOL "Use Unix-like paths") + mark_as_advanced(WIN32_UNIX_PATHS) +endif() file(GLOB ARM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/*.c) file(GLOB ARM_TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/arm/test/*.c) file(GLOB LR35902_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/lr35902/*.c) @@ -80,7 +84,15 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE) endif() -include(GNUInstallDirs) +if(NOT WIN32 OR WIN32_UNIX_PATHS) + include(GNUInstallDirs) +else() + set(CMAKE_INSTALL_LIBDIR ".") + set(CMAKE_INSTALL_BINDIR ".") + set(CMAKE_INSTALL_DATADIR ".") + set(CMAKE_INSTALL_DOCDIR ".") + set(CMAKE_INSTALL_INCLUDEDIR "include") +endif() set(LIBDIR "${CMAKE_INSTALL_LIBDIR}" CACHE PATH "Installed library directory") mark_as_advanced(LIBDIR) diff --git a/README.md b/README.md index c456d3f50..f1f6fa411 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,11 @@ To build on Windows for development, using MSYS2 is recommended. Follow the inst For x86 (32 bit) builds: - pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntdll-git} For x86_64 (64 bit) builds: - pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntdll-git} Check out the source code by running this command: @@ -128,7 +128,7 @@ Then finally build it by running these commands: cmake .. -G "MSYS Makefiles" make -Please note that this build of mGBA for Windows is not suitable for distribution, due to the scattering of DLLs it needs to run, but is perfect for development. However, if distributing such a build is desired (e.g. for testing on machines that don't have the MSYS2 environment installed), a tool called "[Dependency Walker](http://dependencywalker.com)" can be used to see which additional DLL files need to be shipped with the mGBA executable. +Please note that this build of mGBA for Windows is not suitable for distribution, due to the scattering of DLLs it needs to run, but is perfect for development. However, if distributing such a build is desired (e.g. for testing on machines that don't have the MSYS2 environment installed), running `cpack -G ZIP` will prepare a zip file with all of the necessary DLLs. ### Dependencies diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 340a823cd..8ddf92ab1 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -222,6 +222,8 @@ endif() if(NOT DEFINED DATADIR) if(APPLE) set(DATADIR Applications/${PROJECT_NAME}.app/Contents/Resources) + elseif(WIN32 AND NOT WIN32_UNIX_PATHS) + set(DATADIR ".") else() set(DATADIR ${CMAKE_INSTALL_DATADIR}/${BINARY_NAME}) endif() @@ -319,8 +321,13 @@ if(APPLE) endif() install(CODE "execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/tools/deploy-mac.py\" -v ${DEPLOY_OPTIONS} \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/Applications/${PROJECT_NAME}.app\")") endif() -endif() -if(WIN32 AND CMAKE_MAJOR_VERSION GREATER 2 AND CMAKE_MINOR_VERSION GREATER 7) - # Work around CMake issue #16907 - set_target_properties(${BINARY_NAME}-qt PROPERTIES AUTORCC ON SKIP_AUTORCC ON) +elseif(WIN32) + if(CMAKE_MAJOR_VERSION EQUAL 3 AND CMAKE_MINOR_VERSION EQUAL 8) + # Work around CMake issue #16907 + set_target_properties(${BINARY_NAME}-qt PROPERTIES AUTORCC ON SKIP_AUTORCC ON) + endif() + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + find_program(BASH bash) + install(CODE "execute_process(COMMAND \"${BASH}\" \"${CMAKE_SOURCE_DIR}/tools/deploy-win.sh\" \"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe\" \"\${CMAKE_INSTALL_PREFIX}\" \"\$ENV{PWD}\" WORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")" COMPONENT ${BINARY_NAME}-qt) + endif() endif() diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index 8633a4fb2..11372b0e3 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -95,7 +95,9 @@ endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) -set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) +if(NOT WIN32) + set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) +endif() install(TARGETS ${BINARY_NAME}-sdl DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl) if(UNIX) install(FILES ${CMAKE_SOURCE_DIR}/doc/mgba.6 DESTINATION ${MANDIR}/man6 COMPONENT ${BINARY_NAME}-sdl) diff --git a/tools/deploy-win.sh b/tools/deploy-win.sh new file mode 100644 index 000000000..d5a3d3296 --- /dev/null +++ b/tools/deploy-win.sh @@ -0,0 +1,27 @@ +#!/bin/bash +BINARY=$1 +INSTALLPATH="$2" +WORKDIR="$3" + + +if [ -z "$DESTDIR" ]; then + OUTDIR="$INSTALLPATH" +else + if [ -n $(echo ${INSTALLPATH} | grep "^[A-Z]:") ]; then + INSTALLPATH="${INSTALLPATH:3}" + fi + OUTDIR="$WORKDIR/$DESTDIR/$INSTALLPATH" +fi + +IFS=$'\n' +if [ -n $(which ntldd 2>&1 | grep /ntldd) ]; then + DLLS=$(ntldd -R "$BINARY" | grep -i mingw | cut -d">" -f2 | sed -e 's/(0x[0-9a-f]\+)//' -e 's/^ \+//' -e 's/ \+$//' -e 's,\\,/,g') +elif [ -n $(which gdb 2>&1 | grep /gdb) ]; then + DLLS=$(gdb "$BINARY" --command=$(dirname $0)/dlls.gdb | grep -i mingw | cut -d" " -f7- | sed -e 's/^ \+//' -e 's/ \+$//' -e 's,\\,/,g') +else + echo "Please install gdb or ntldd for deploying DLLs" +fi +cp -vu $DLLS "$OUTDIR" +if [ -n $(which windeployqt 2>&1 | grep /windeployqt) ]; then + windeployqt --no-opengl-sw --no-svg --release --dir "$OUTDIR" "$BINARY" +fi diff --git a/tools/dlls.gdb b/tools/dlls.gdb new file mode 100644 index 000000000..017b39ac5 --- /dev/null +++ b/tools/dlls.gdb @@ -0,0 +1,3 @@ +start +info sharedlibrary +q From adcb2de81414c901bdac72481bda5df2ab2bd248 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 25 Jan 2018 19:48:05 -0800 Subject: [PATCH 146/152] GBA Memory: Only copy-on-write if ROM buffer is not fixed --- src/gba/memory.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 54e8ef8fd..c7cd86d26 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1629,6 +1629,7 @@ void _pristineCow(struct GBA* gba) { if (!gba->isPristine) { return; } +#ifndef FIXED_ROM_BUFFER void* newRom = anonymousMemoryMap(SIZE_CART0); memcpy(newRom, gba->memory.rom, gba->memory.romSize); memset(((uint8_t*) newRom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize); @@ -1636,14 +1637,13 @@ void _pristineCow(struct GBA* gba) { gba->cpu->memory.activeRegion = newRom; } if (gba->romVf) { -#ifndef FIXED_ROM_BUFFER gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize); -#endif gba->romVf->close(gba->romVf); gba->romVf = NULL; } gba->memory.rom = newRom; gba->memory.hw.gpioBase = &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]; +#endif gba->isPristine = false; } From 60f44b46dc5acc9ffacb4e961e667001910da61d Mon Sep 17 00:00:00 2001 From: rootfather Date: Sat, 27 Jan 2018 18:03:50 +0100 Subject: [PATCH 147/152] Doc: Fix package name for ntldd-git --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f1f6fa411..81fb5d93a 100644 --- a/README.md +++ b/README.md @@ -110,11 +110,11 @@ To build on Windows for development, using MSYS2 is recommended. Follow the inst For x86 (32 bit) builds: - pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntdll-git} + pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} For x86_64 (64 bit) builds: - pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntdll-git} + pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} Check out the source code by running this command: From c0115cfc43ed33f969928a4e2ccb8caef39ff51b Mon Sep 17 00:00:00 2001 From: rootfather Date: Sat, 27 Jan 2018 18:06:25 +0100 Subject: [PATCH 148/152] Doc: Add translation for cpack related instructions --- README_DE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README_DE.md b/README_DE.md index fab766dba..dd02c90a3 100644 --- a/README_DE.md +++ b/README_DE.md @@ -110,11 +110,11 @@ Um mGBA auf Windows zu kompilieren, wird MSYS2 empfohlen. Befolge die Installati Für x86 (32 Bit): - pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + pacman -Sy mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} Für x86_64 (64 Bit): - pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2} + pacman -Sy mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libzip,pkg-config,qt5,SDL2,ntldd-git} Lade den aktuellen mGBA-Quellcode mithilfe des folgenden Kommandos herunter: @@ -128,7 +128,7 @@ Abschließend wird mGBA über folgende Kommandos kompiliert: cmake .. -G "MSYS Makefiles" make -Bitte beachte, dass mGBA für Windows aufgrund der Vielzahl an benötigten DLLs nicht für die weitere Verteilung geeignet ist, wenn es auf diese Weise gebaut wurde. Es ist jedoch perfekt für Entwickler geeignet. Soll mGBA dennoch weiter verteilt werden (beispielsweise zu Testzwecken auf Systemen, auf denen keine MSYS2-Umgebung installier ist), kann ein Tool namens "[Dependency Walker](http://dependencywalker.com)" verwendet werden, um zu sehen, welche DLLs zusammen mit der mGBA-Programmdatei ausgeliefert werden müssen. +Bitte beachte, dass mGBA für Windows aufgrund der Vielzahl an benötigten DLLs nicht für die weitere Verteilung geeignet ist, wenn es auf diese Weise gebaut wurde. Es ist jedoch perfekt für Entwickler geeignet. Soll mGBA dennoch weiter verteilt werden (beispielsweise zu Testzwecken auf Systemen, auf denen keine MSYS2-Umgebung installiert ist), kann mithilfe des Befehls 'cpack -G ZIP' ein ZIP-Archiv mit allen benötigten DLLs erstellt werden. ### Abhängigkeiten From 932b8117a4d83632534536a8b87b8dafcafefbe0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 Jan 2018 08:44:55 -0800 Subject: [PATCH 149/152] SDL: Fix keyrepeat causing thread interruptions --- src/platform/sdl/sdl-events.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 32f6aff52..cfd5ae2b6 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -410,7 +410,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer* if (!event->keysym.mod) { key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym); } - if (key != -1) { + if (key != -1 && !event->repeat) { mCoreThreadInterrupt(context); if (event->type == SDL_KEYDOWN) { context->core->addKeys(context->core, 1 << key); From 72e5aa078281a797854e4bd4620489e956fce866 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 Jan 2018 14:11:48 -0800 Subject: [PATCH 150/152] Qt: Add ELF loading if enabled --- src/platform/qt/Window.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index c6aab3531..5bcc5da8d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -246,6 +246,9 @@ QString Window::getFilters() const { #endif #ifdef USE_LZMA "*.7z", +#endif +#ifdef USE_ELF + "*.elf", #endif "*.agb", "*.mb", From 4a3c9423321a9d05a506b124c0af1772cb4a395e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 Jan 2018 15:31:32 -0800 Subject: [PATCH 151/152] 3DS: Remove CSND, add some size optimizations --- CHANGES | 1 + src/platform/3ds/CMakeToolchain.txt | 4 +- src/platform/3ds/cia.rsf.in | 2 - src/platform/3ds/main.c | 85 ++--------------------------- 4 files changed, 7 insertions(+), 85 deletions(-) diff --git a/CHANGES b/CHANGES index 3997ce2da..b909beeba 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,7 @@ Misc: - GBA Memory: 64 MiB GBA Video cartridge support - 3DS: Scale font based on glyph heights (fixes mgba.io/i/961) - PSP2: Use system enter key by default + - 3DS: Remove deprecated CSND interface 0.6.1: (2017-10-01) Bugfixes: diff --git a/src/platform/3ds/CMakeToolchain.txt b/src/platform/3ds/CMakeToolchain.txt index 9b67d3013..32ef76c98 100644 --- a/src/platform/3ds/CMakeToolchain.txt +++ b/src/platform/3ds/CMakeToolchain.txt @@ -23,9 +23,9 @@ endif() set(CMAKE_PROGRAM_PATH ${DEVKITARM}/bin) set(cross_prefix arm-none-eabi-) -set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=hard") +set(arch_flags "-march=armv6k -mtune=mpcore -mfloat-abi=hard -ffunction-sections") set(inc_flags "-I${CTRULIB}/include ${arch_flags} -mword-relocations") -set(link_flags "-L${CTRULIB}/lib -lctru -specs=3dsx.specs ${arch_flags}") +set(link_flags "-L${CTRULIB}/lib -lctru -specs=3dsx.specs ${arch_flags} -Wl,--gc-sections") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR arm CACHE INTERNAL "processor") diff --git a/src/platform/3ds/cia.rsf.in b/src/platform/3ds/cia.rsf.in index a5dda0b83..507da46e0 100644 --- a/src/platform/3ds/cia.rsf.in +++ b/src/platform/3ds/cia.rsf.in @@ -174,7 +174,6 @@ AccessControlInfo: - ldr:ro - ir:USER - ir:u - - csnd:SND SystemControlInfo: @@ -195,7 +194,6 @@ SystemControlInfo: cecd: 0x0004013000002602L cfg: 0x0004013000001702L codec: 0x0004013000001802L - csnd: 0x0004013000002702L dlp: 0x0004013000002802L dsp: 0x0004013000001a02L friends: 0x0004013000003202L diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 1bc030369..385639053 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -80,8 +80,7 @@ static struct m3DSImageSource { static enum { NO_SOUND, - DSP_SUPPORTED, - CSND_SUPPORTED + DSP_SUPPORTED } hasSound; // TODO: Move into context @@ -156,37 +155,19 @@ static void _cleanup(void) { linearFree(audioLeft); } - if (hasSound == CSND_SUPPORTED) { - linearFree(audioRight); - csndExit(); - } - if (hasSound == DSP_SUPPORTED) { ndspExit(); } camExit(); - csndExit(); + ndspExit(); ptmuExit(); } static void _aptHook(APT_HookType hook, void* user) { UNUSED(user); switch (hook) { - case APTHOOK_ONSUSPEND: - case APTHOOK_ONSLEEP: - if (hasSound == CSND_SUPPORTED) { - CSND_SetPlayState(8, 0); - CSND_SetPlayState(9, 0); - csndExecCmds(false); - } - break; case APTHOOK_ONEXIT: - if (hasSound == CSND_SUPPORTED) { - CSND_SetPlayState(8, 0); - CSND_SetPlayState(9, 0); - csndExecCmds(false); - } _cleanup(); exit(0); break; @@ -199,33 +180,6 @@ static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) { mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key); } -static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) { - u32 pleft = 0, pright = 0; - - int loopMode = (flags >> 10) & 3; - if (!loopMode) { - flags |= SOUND_ONE_SHOT; - } - - pleft = osConvertVirtToPhys(left); - pright = osConvertVirtToPhys(right); - - u32 timer = CSND_TIMER(sampleRate); - if (timer < 0x0042) { - timer = 0x0042; - } - else if (timer > 0xFFFF) { - timer = 0xFFFF; - } - flags &= ~0xFFFF001F; - flags |= SOUND_ENABLE | (timer << 16); - - u32 volumes = CSND_VOL(vol, -1.0); - CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes); - volumes = CSND_VOL(vol, 1.0); - CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes); -} - static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right); static void _drawStart(void) { @@ -383,12 +337,7 @@ static void _gameLoaded(struct mGUIRunner* runner) { if (hasSound != NO_SOUND) { audioPos = 0; } - if (hasSound == CSND_SUPPORTED) { - memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - csndExecCmds(false); - } else if (hasSound == DSP_SUPPORTED) { + if (hasSound == DSP_SUPPORTED) { memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t)); } unsigned mode; @@ -430,11 +379,6 @@ static void _gameLoaded(struct mGUIRunner* runner) { } static void _gameUnloaded(struct mGUIRunner* runner) { - if (hasSound == CSND_SUPPORTED) { - CSND_SetPlayState(8, 0); - CSND_SetPlayState(9, 0); - csndExecCmds(false); - } osSetSpeedupEnable(false); frameLimiter = true; @@ -796,22 +740,7 @@ static void _requestImage(struct mImageSource* source, const void** buffer, size static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) { UNUSED(stream); - if (hasSound == CSND_SUPPORTED) { - blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false); - blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false); - GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); - GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t)); - audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER; - if (audioPos == AUDIO_SAMPLES * 3) { - u8 playing = 0; - csndIsPlaying(0x8, &playing); - if (!playing) { - CSND_SetPlayState(0x8, 1); - CSND_SetPlayState(0x9, 1); - csndExecCmds(false); - } - } - } else if (hasSound == DSP_SUPPORTED) { + if (hasSound == DSP_SUPPORTED) { int startId = bufferId; while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) { bufferId = (bufferId + 1) & (DSP_BUFFERS - 1); @@ -878,12 +807,6 @@ int main() { } } - if (hasSound == NO_SOUND && !csndInit()) { - hasSound = CSND_SUPPORTED; - audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); - audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); - } - gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true); if (!_initGpu()) { From c657255009d8a3143e1a2ecd390f9f895374f60c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 Jan 2018 17:30:59 -0800 Subject: [PATCH 152/152] PSP2: Fix RingFIFO misuse causing bad audio --- CHANGES | 1 + src/platform/psp2/main.c | 2 +- src/platform/psp2/psp2-context.c | 97 ++++++++++++++++++-------------- 3 files changed, 56 insertions(+), 44 deletions(-) diff --git a/CHANGES b/CHANGES index b909beeba..f713742ee 100644 --- a/CHANGES +++ b/CHANGES @@ -44,6 +44,7 @@ Bugfixes: - Core: Fix ROM patches not being unloaded when disabled (fixes mgba.io/i/962) - GBA I/O: Fix writing to DISPCNT CGB flag (fixes mgba.io/i/902) - GBA Memory: Partially revert prefetch changes (fixes mgba.io/i/840) + - PSP2: Fix issues causing poor audio Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722) diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 8ad9f5c7e..c33c7228c 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -153,7 +153,7 @@ int main() { .teardown = mPSP2Teardown, .gameLoaded = mPSP2LoadROM, .gameUnloaded = mPSP2UnloadROM, - .prepareForFrame = mPSP2PrepareForFrame, + .prepareForFrame = NULL, .drawFrame = mPSP2Draw, .drawScreenshot = mPSP2DrawScreenshot, .paused = mPSP2Paused, diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index 278c7419d..93d0d98e3 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -71,16 +70,20 @@ static struct mSceImageSource { size_t bufferOffset; } camera; +static struct mAVStream stream; + bool frameLimiter = true; extern const uint8_t _binary_backdrop_png_start[]; static vita2d_texture* backdrop = 0; -#define PSP2_SAMPLES 256 -#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 20) +#define PSP2_SAMPLES 512 +#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 16) static struct mPSP2AudioContext { - struct RingFIFO buffer; + struct GBAStereoSample buffer[PSP2_AUDIO_BUFFER_SIZE]; + size_t writeOffset; + size_t readOffset; size_t samples; Mutex mutex; Condition cond; @@ -93,26 +96,25 @@ void mPSP2MapKey(struct mInputMap* map, int pspKey, int key) { static THREAD_ENTRY _audioThread(void* context) { struct mPSP2AudioContext* audio = (struct mPSP2AudioContext*) context; + uint32_t zeroBuffer[PSP2_SAMPLES] = {0}; int audioPort = sceAudioOutOpenPort(SCE_AUDIO_OUT_PORT_TYPE_MAIN, PSP2_SAMPLES, 48000, SCE_AUDIO_OUT_MODE_STEREO); while (audio->running) { MutexLock(&audio->mutex); - int len = audio->samples; - if (len > PSP2_SAMPLES) { - len = PSP2_SAMPLES; + void* buffer; + if (audio->samples >= PSP2_SAMPLES) { + buffer = &audio->buffer[audio->readOffset]; + audio->samples -= PSP2_SAMPLES; + audio->readOffset += PSP2_SAMPLES; + if (audio->readOffset >= PSP2_AUDIO_BUFFER_SIZE) { + audio->readOffset = 0; + } + ConditionWake(&audio->cond); + } else { + buffer = zeroBuffer; } - struct GBAStereoSample* buffer = audio->buffer.readPtr; - RingFIFORead(&audio->buffer, NULL, len * 4); - audio->samples -= len; - ConditionWake(&audio->cond); - MutexUnlock(&audio->mutex); + sceAudioOutOutput(audioPort, buffer); - MutexLock(&audio->mutex); - - if (audio->samples < PSP2_SAMPLES) { - ConditionWait(&audio->cond, &audio->mutex); - } - MutexUnlock(&audio->mutex); } sceAudioOutReleasePort(audioPort); return 0; @@ -229,6 +231,29 @@ static void _requestImage(struct mImageSource* source, const void** buffer, size sceCameraRead(imageSource->cam - 1, &read); } +static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) { + UNUSED(stream); + MutexLock(&audioContext.mutex); + struct GBAStereoSample* samples = &audioContext.buffer[audioContext.writeOffset]; + while (audioContext.samples == PSP2_AUDIO_BUFFER_SIZE) { + if (!frameLimiter) { + blip_clear(left); + blip_clear(right); + MutexUnlock(&audioContext.mutex); + return; + } + ConditionWait(&audioContext.cond, &audioContext.mutex); + } + blip_read_samples(left, &samples[0].left, PSP2_SAMPLES, true); + blip_read_samples(right, &samples[0].right, PSP2_SAMPLES, true); + audioContext.samples += PSP2_SAMPLES; + audioContext.writeOffset += PSP2_SAMPLES; + if (audioContext.writeOffset >= PSP2_AUDIO_BUFFER_SIZE) { + audioContext.writeOffset = 0; + } + MutexUnlock(&audioContext.mutex); +} + uint16_t mPSP2PollInput(struct mGUIRunner* runner) { SceCtrlData pad; sceCtrlPeekBufferPositive(0, &pad, 1); @@ -285,6 +310,7 @@ void mPSP2Setup(struct mGUIRunner* runner) { outputBuffer = vita2d_texture_get_datap(tex); runner->core->setVideoBuffer(runner->core, outputBuffer, 256); + runner->core->setAudioBufferSize(runner->core, PSP2_SAMPLES); rotation.d.sample = _sampleRotation; rotation.d.readTiltX = _readTiltX; @@ -303,6 +329,13 @@ void mPSP2Setup(struct mGUIRunner* runner) { camera.cam = 1; runner->core->setPeripheral(runner->core, mPERIPH_IMAGE_SOURCE, &camera.d); + + stream.videoDimensionsChanged = NULL; + stream.postAudioFrame = NULL; + stream.postAudioBuffer = _postAudioBuffer; + stream.postVideoFrame = NULL; + runner->core->setAVStream(runner->core, &stream); + frameLimiter = true; backdrop = vita2d_load_PNG_buffer(_binary_backdrop_png_start); @@ -341,37 +374,15 @@ void mPSP2LoadROM(struct mGUIRunner* runner) { break; } - RingFIFOInit(&audioContext.buffer, PSP2_AUDIO_BUFFER_SIZE * sizeof(struct GBAStereoSample)); MutexInit(&audioContext.mutex); ConditionInit(&audioContext.cond); + memset(audioContext.buffer, 0, sizeof(audioContext.buffer)); + audioContext.readOffset = 0; + audioContext.writeOffset = 0; audioContext.running = true; ThreadCreate(&audioThread, _audioThread, &audioContext); } -void mPSP2PrepareForFrame(struct mGUIRunner* runner) { - int nSamples = 0; - while (blip_samples_avail(runner->core->getAudioChannel(runner->core, 0)) >= PSP2_SAMPLES) { - struct GBAStereoSample* samples = audioContext.buffer.writePtr; - if (nSamples > (PSP2_AUDIO_BUFFER_SIZE >> 2) + (PSP2_AUDIO_BUFFER_SIZE >> 1)) { // * 0.75 - if (!frameLimiter) { - blip_clear(runner->core->getAudioChannel(runner->core, 0)); - blip_clear(runner->core->getAudioChannel(runner->core, 1)); - break; - } - } - blip_read_samples(runner->core->getAudioChannel(runner->core, 0), &samples[0].left, PSP2_SAMPLES, true); - blip_read_samples(runner->core->getAudioChannel(runner->core, 1), &samples[0].right, PSP2_SAMPLES, true); - while (!RingFIFOWrite(&audioContext.buffer, NULL, PSP2_SAMPLES * 4)) { - ConditionWake(&audioContext.cond); - // Spinloooooooop! - } - MutexLock(&audioContext.mutex); - audioContext.samples += PSP2_SAMPLES; - nSamples = audioContext.samples; - ConditionWake(&audioContext.cond); - MutexUnlock(&audioContext.mutex); - } -} void mPSP2UnloadROM(struct mGUIRunner* runner) { switch (runner->core->platform(runner->core)) {