diff --git a/CMakeLists.txt b/CMakeLists.txt index 598b3bdb..97dfc5bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.16) cmake_policy(VERSION 3.15) if (POLICY CMP0076) @@ -9,6 +9,11 @@ set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +option(USE_VCPKG "Use vcpkg for dependency packages" OFF) +if (USE_VCPKG) + include(ConfigureVcpkg) +endif() + project(melonDS VERSION 0.9.5 DESCRIPTION "DS emulator, sorta" @@ -73,8 +78,11 @@ if (ENABLE_LTO) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +endif() + string(REPLACE "-O2" "-O3" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") string(REPLACE "-O2" "-O3" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake new file mode 100644 index 00000000..95cbcdb9 --- /dev/null +++ b/cmake/ConfigureVcpkg.cmake @@ -0,0 +1,90 @@ +include(FetchContent) + +set(_DEFAULT_VCPKG_ROOT "${CMAKE_SOURCE_DIR}/vcpkg") +set(VCPKG_ROOT "${_DEFAULT_VCPKG_ROOT}" CACHE STRING "The path to the vcpkg repository") + +if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") + FetchContent_Declare(vcpkg + GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" + GIT_TAG 2023.12.12 + SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") + FetchContent_MakeAvailable(vcpkg) +endif() + +set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/cmake/overlay-triplets") + +option(USE_RECOMMENDED_TRIPLETS "Use the recommended triplets that are used for official builds" ON) + +if (CMAKE_OSX_ARCHITECTURES MATCHES ";") + message(FATAL_ERROR "macOS universal builds are not supported. Build them individually and combine afterwards instead.") +endif() + +if (USE_RECOMMENDED_TRIPLETS) + execute_process( + COMMAND uname -m + OUTPUT_VARIABLE _HOST_PROCESSOR + OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(_CAN_TARGET_AS_HOST OFF) + + if (APPLE) + if (NOT CMAKE_OSX_ARCHITECTURES) + if (_HOST_PROCESSOR STREQUAL arm64) + set(CMAKE_OSX_ARCHITECTURES arm64) + else() + set(CMAKE_OSX_ARCHITECTURES x86_64) + endif() + endif() + + if (CMAKE_OSX_ARCHITECTURES STREQUAL arm64) + set(_WANTED_TRIPLET arm64-osx-11-release) + set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0) + else() + set(_WANTED_TRIPLET x64-osx-1015-release) + set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) + endif() + elseif(WIN32) + # TODO Windows arm64 if possible + set(_CAN_TARGET_AS_HOST ON) + set(_WANTED_TRIPLET x64-mingw-static) + endif() + + # Don't override it if the user set something else + if (NOT VCPKG_TARGET_TRIPLET) + set(VCPKG_TARGET_TRIPLET "${_WANTED_TRIPLET}") + else() + set(_WANTED_TRIPLET "${VCPKG_TARGET_TRIPLET}") + endif() + + if (APPLE) + if (_HOST_PROCESSOR MATCHES arm64) + if (_WANTED_TRIPLET MATCHES "^arm64-osx-") + set(_CAN_TARGET_AS_HOST ON) + elseif (_WANTED_TRIPLET STREQUAL "x64-osx-1015-release") + # Use the default triplet for when building for arm64 + # because we're probably making a universal build + set(VCPKG_HOST_TRIPLET arm64-osx-11-release) + endif() + else() + if (_WANTED_TRIPLET MATCHES "^x64-osx-") + set(_CAN_TARGET_AS_HOST ON) + elseif (_WANTED_TRIPLET STREQUAL "arm64-osx-11-release") + set(VCPKG_HOST_TRIPLET x64-osx-1015-release) + endif() + endif() + endif() + + # If host and target triplet differ, vcpkg seems to always assume that the host can't run the target's binaries. + # In cases like cross compiling from ARM -> Intel macOS, or target being an older version of the host OS, we *can* do that so the packages built targeting the host are redundant. + if (_CAN_TARGET_AS_HOST AND NOT VCPKG_HOST_TRIPLET) + option(VCPKG_TARGET_AS_HOST "Use the target as host triplet to speed up builds" ON) + else() + option(VCPKG_TARGET_AS_HOST "Use the target as host triplet to speed up builds" OFF) + endif() + + if (VCPKG_TARGET_AS_HOST) + set(VCPKG_HOST_TRIPLET "${VCPKG_TARGET_TRIPLET}" CACHE STRING "Host triplet to use for vcpkg") + endif() +endif() + +set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") diff --git a/cmake/overlay-triplets/arm64-osx-11-release.cmake b/cmake/overlay-triplets/arm64-osx-11-release.cmake new file mode 100644 index 00000000..7c4b43ae --- /dev/null +++ b/cmake/overlay-triplets/arm64-osx-11-release.cmake @@ -0,0 +1,12 @@ +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_CMAKE_SYSTEM_VERSION 11.0) +set(VCPKG_OSX_ARCHITECTURES arm64) +set(VCPKG_BUILD_TYPE release) +set(VCPKG_OSX_DEPLOYMENT_TARGET 11.0) + +set(VCPKG_C_FLAGS -mmacosx-version-min=11.0) +set(VCPKG_CXX_FLAGS -mmacosx-version-min=11.0) diff --git a/cmake/overlay-triplets/x64-osx-1015-release.cmake b/cmake/overlay-triplets/x64-osx-1015-release.cmake new file mode 100644 index 00000000..fcb67a7a --- /dev/null +++ b/cmake/overlay-triplets/x64-osx-1015-release.cmake @@ -0,0 +1,12 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_CMAKE_SYSTEM_VERSION 10.15) +set(VCPKG_OSX_ARCHITECTURES x86_64) +set(VCPKG_BUILD_TYPE release) +set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15) + +set(VCPKG_C_FLAGS -mmacosx-version-min=10.15) +set(VCPKG_CXX_FLAGS -mmacosx-version-min=10.15) diff --git a/freebios/bios_common.S b/freebios/bios_common.S index 56d349ef..308d67c4 100755 --- a/freebios/bios_common.S +++ b/freebios/bios_common.S @@ -517,6 +517,9 @@ swi_get_crc16: mov const_0x1E, #0x1E adr crc_table_ptr, crc_table + bic crc_value, crc_value, #0xFF000000 + bic crc_value, crc_value, #0x00FF0000 + movs crc_length, crc_length, lsr #1 beq 1f diff --git a/freebios/drastic_bios_arm7.bin b/freebios/drastic_bios_arm7.bin index e586eb9f..adffb53a 100755 Binary files a/freebios/drastic_bios_arm7.bin and b/freebios/drastic_bios_arm7.bin differ diff --git a/freebios/drastic_bios_arm9.bin b/freebios/drastic_bios_arm9.bin index 51a82829..95c330d0 100755 Binary files a/freebios/drastic_bios_arm9.bin and b/freebios/drastic_bios_arm9.bin differ diff --git a/src/AREngine.cpp b/src/AREngine.cpp index 4e13a39a..c7d49fe6 100644 --- a/src/AREngine.cpp +++ b/src/AREngine.cpp @@ -40,16 +40,16 @@ AREngine::AREngine(melonDS::NDS& nds) : NDS(nds) case ((x)+0x08): case ((x)+0x09): case ((x)+0x0A): case ((x)+0x0B): \ case ((x)+0x0C): case ((x)+0x0D): case ((x)+0x0E): case ((x)+0x0F) -void AREngine::RunCheat(ARCode& arcode) +void AREngine::RunCheat(const ARCode& arcode) { - u32* code = &arcode.Code[0]; + const u32* code = &arcode.Code[0]; u32 offset = 0; u32 datareg = 0; u32 cond = 1; u32 condstack = 0; - u32* loopstart = code; + const u32* loopstart = code; u32 loopcount = 0; u32 loopcond = 1; u32 loopcondstack = 0; diff --git a/src/AREngine.h b/src/AREngine.h index 1f2ee186..21044676 100644 --- a/src/AREngine.h +++ b/src/AREngine.h @@ -33,7 +33,7 @@ public: void SetCodeFile(ARCodeFile* file) { CodeFile = file; } void RunCheats(); - void RunCheat(ARCode& arcode); + void RunCheat(const ARCode& arcode); private: melonDS::NDS& NDS; ARCodeFile* CodeFile; // AR code file - frontend is responsible for managing this diff --git a/src/ARM.cpp b/src/ARM.cpp index 659d303b..c2f6a6c2 100644 --- a/src/ARM.cpp +++ b/src/ARM.cpp @@ -17,6 +17,7 @@ */ #include +#include #include #include "NDS.h" #include "DSi.h" @@ -106,17 +107,17 @@ const u32 ARM::ConditionTable[16] = 0x0000 // NE }; -ARM::ARM(u32 num, melonDS::NDS& nds) : +ARM::ARM(u32 num, bool jit, std::optional gdb, melonDS::NDS& nds) : #ifdef GDBSTUB_ENABLED - GdbStub(this, Platform::GetConfigInt(num ? Platform::GdbPortARM7 : Platform::GdbPortARM9)), + GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0), #endif Num(num), // well uh NDS(nds) { #ifdef GDBSTUB_ENABLED - if (Platform::GetConfigBool(Platform::GdbEnabled) + if (gdb #ifdef JIT_ENABLED - && !Platform::GetConfigBool(Platform::JIT_Enable) + && !jit // TODO: Should we support toggling the GdbStub without destroying the ARM? #endif ) GdbStub.Init(); @@ -129,14 +130,14 @@ ARM::~ARM() // dorp } -ARMv5::ARMv5(melonDS::NDS& nds) : ARM(0, nds) +ARMv5::ARMv5(melonDS::NDS& nds, std::optional gdb, bool jit) : ARM(0, jit, gdb, nds) { DTCM = NDS.JIT.Memory.GetARM9DTCM(); PU_Map = PU_PrivMap; } -ARMv4::ARMv4(melonDS::NDS& nds) : ARM(1, nds) +ARMv4::ARMv4(melonDS::NDS& nds, std::optional gdb, bool jit) : ARM(1, jit, gdb, nds) { // } @@ -187,8 +188,6 @@ void ARM::Reset() #ifdef GDBSTUB_ENABLED IsSingleStep = false; BreakReq = false; - BreakOnStartup = Platform::GetConfigBool( - Num ? Platform::GdbARM7BreakOnStartup : Platform::GdbARM9BreakOnStartup); #endif // zorp @@ -224,7 +223,7 @@ void ARM::DoSavestate(Savestate* file) file->VarArray(R_UND, 3*sizeof(u32)); file->Var32(&CurInstr); #ifdef JIT_ENABLED - if (file->Saving && NDS.EnableJIT) + if (file->Saving && NDS.IsJITEnabled()) { // hack, the JIT doesn't really pipeline // but we still want JIT save states to be diff --git a/src/ARM.h b/src/ARM.h index 4becff02..1e0b71b8 100644 --- a/src/ARM.h +++ b/src/ARM.h @@ -20,9 +20,11 @@ #define ARM_H #include +#include #include "types.h" #include "MemRegion.h" +#include "MemConstants.h" #ifdef GDBSTUB_ENABLED #include "debug/GdbStub.h" @@ -41,10 +43,7 @@ enum RWFlags_ForceUser = (1<<21), }; -const u32 ITCMPhysicalSize = 0x8000; -const u32 DTCMPhysicalSize = 0x4000; - - +struct GDBArgs; class ARMJIT; class GPU; class ARMJIT_Memory; @@ -57,7 +56,7 @@ class ARM #endif { public: - ARM(u32 num, NDS& nds); + ARM(u32 num, bool jit, std::optional gdb, NDS& nds); virtual ~ARM(); // destroy shit virtual void Reset(); @@ -81,7 +80,7 @@ public: virtual void ExecuteJIT() = 0; #endif - bool CheckCondition(u32 code) + bool CheckCondition(u32 code) const { if (code == 0xE) return true; if (ConditionTable[code] & (1 << (CPSR>>28))) return true; @@ -110,7 +109,7 @@ public: if (v) CPSR |= 0x10000000; } - inline bool ModeIs(u32 mode) + inline bool ModeIs(u32 mode) const { u32 cm = CPSR & 0x1f; mode &= 0x1f; @@ -202,6 +201,7 @@ protected: bool IsSingleStep; bool BreakReq; bool BreakOnStartup; + u16 Port; public: int GetCPU() const override { return Num ? 7 : 9; } @@ -225,7 +225,7 @@ protected: class ARMv5 : public ARM { public: - ARMv5(melonDS::NDS& nds); + ARMv5(melonDS::NDS& nds, std::optional gdb, bool jit); ~ARMv5(); void Reset() override; @@ -315,7 +315,7 @@ public: void ICacheInvalidateAll(); void CP15Write(u32 id, u32 val); - u32 CP15Read(u32 id); + u32 CP15Read(u32 id) const; u32 CP15Control; @@ -377,7 +377,7 @@ protected: class ARMv4 : public ARM { public: - ARMv4(melonDS::NDS& nds); + ARMv4(melonDS::NDS& nds, std::optional gdb, bool jit); void FillPipeline() override; diff --git a/src/ARMJIT.cpp b/src/ARMJIT.cpp index b938dfb8..c3fcba26 100644 --- a/src/ARMJIT.cpp +++ b/src/ARMJIT.cpp @@ -63,12 +63,12 @@ const u32 CodeRegionSizes[ARMJIT_Memory::memregions_Count] = 0, ITCMPhysicalSize, 0, - sizeof(NDS::ARM9BIOS), + ARM9BIOSSize, MainRAMMaxSize, SharedWRAMSize, 0, 0x100000, - sizeof(NDS::ARM7BIOS), + ARM7BIOSSize, ARM7WRAMSize, 0, 0, @@ -237,16 +237,6 @@ ARMJIT::~ARMJIT() noexcept void ARMJIT::Reset() noexcept { - MaxBlockSize = Platform::GetConfigInt(Platform::JIT_MaxBlockSize); - LiteralOptimizations = Platform::GetConfigBool(Platform::JIT_LiteralOptimizations); - BranchOptimizations = Platform::GetConfigBool(Platform::JIT_BranchOptimizations); - FastMemory = Platform::GetConfigBool(Platform::JIT_FastMemory); - - if (MaxBlockSize < 1) - MaxBlockSize = 1; - if (MaxBlockSize > 32) - MaxBlockSize = 32; - JitEnableWrite(); ResetBlockCache(); @@ -491,6 +481,56 @@ void ARMJIT::RetireJitBlock(JitBlock* block) noexcept } } +void ARMJIT::SetJITArgs(JITArgs args) noexcept +{ + args.MaxBlockSize = std::clamp(args.MaxBlockSize, 1u, 32u); + + if (MaxBlockSize != args.MaxBlockSize + || LiteralOptimizations != args.LiteralOptimizations + || BranchOptimizations != args.BranchOptimizations + || FastMemory != args.FastMemory) + ResetBlockCache(); + + MaxBlockSize = args.MaxBlockSize; + LiteralOptimizations = args.LiteralOptimizations; + BranchOptimizations = args.BranchOptimizations; + FastMemory = args.FastMemory; +} + +void ARMJIT::SetMaxBlockSize(int size) noexcept +{ + size = std::clamp(size, 1, 32); + + if (size != MaxBlockSize) + ResetBlockCache(); + + MaxBlockSize = size; +} + +void ARMJIT::SetLiteralOptimizations(bool enabled) noexcept +{ + if (LiteralOptimizations != enabled) + ResetBlockCache(); + + LiteralOptimizations = enabled; +} + +void ARMJIT::SetBranchOptimizations(bool enabled) noexcept +{ + if (BranchOptimizations != enabled) + ResetBlockCache(); + + BranchOptimizations = enabled; +} + +void ARMJIT::SetFastMemory(bool enabled) noexcept +{ + if (FastMemory != enabled) + ResetBlockCache(); + + FastMemory = enabled; +} + void ARMJIT::CompileBlock(ARM* cpu) noexcept { bool thumb = cpu->CPSR & 0x20; diff --git a/src/ARMJIT.h b/src/ARMJIT.h index 9e1ca074..7619f234 100644 --- a/src/ARMJIT.h +++ b/src/ARMJIT.h @@ -19,10 +19,15 @@ #ifndef ARMJIT_H #define ARMJIT_H +#include +#include #include #include "types.h" - +#include "MemConstants.h" +#include "Args.h" #include "ARMJIT_Memory.h" + +#ifdef JIT_ENABLED #include "JitBlock.h" #if defined(__APPLE__) && defined(__aarch64__) @@ -30,7 +35,6 @@ #endif #include "ARMJIT_Compiler.h" -#include "MemConstants.h" namespace melonDS { @@ -40,18 +44,25 @@ class JitBlock; class ARMJIT { public: - ARMJIT(melonDS::NDS& nds) noexcept : NDS(nds), Memory(nds), JITCompiler(nds) {}; - ~ARMJIT() noexcept NOOP_IF_NO_JIT; - void InvalidateByAddr(u32) noexcept NOOP_IF_NO_JIT; - void CheckAndInvalidateWVRAM(int) noexcept NOOP_IF_NO_JIT; - void CheckAndInvalidateITCM() noexcept NOOP_IF_NO_JIT; - void Reset() noexcept NOOP_IF_NO_JIT; - void JitEnableWrite() noexcept NOOP_IF_NO_JIT; - void JitEnableExecute() noexcept NOOP_IF_NO_JIT; - void CompileBlock(ARM* cpu) noexcept NOOP_IF_NO_JIT; - void ResetBlockCache() noexcept NOOP_IF_NO_JIT; + ARMJIT(melonDS::NDS& nds, std::optional jit) noexcept : + NDS(nds), + Memory(nds), + JITCompiler(nds), + MaxBlockSize(jit.has_value() ? std::clamp(jit->MaxBlockSize, 1u, 32u) : 32), + LiteralOptimizations(jit.has_value() ? jit->LiteralOptimizations : false), + BranchOptimizations(jit.has_value() ? jit->BranchOptimizations : false), + FastMemory(jit.has_value() ? jit->FastMemory : false) + {} + ~ARMJIT() noexcept; + void InvalidateByAddr(u32) noexcept; + void CheckAndInvalidateWVRAM(int) noexcept; + void CheckAndInvalidateITCM() noexcept; + void Reset() noexcept; + void JitEnableWrite() noexcept; + void JitEnableExecute() noexcept; + void CompileBlock(ARM* cpu) noexcept; + void ResetBlockCache() noexcept; -#ifdef JIT_ENABLED template void CheckAndInvalidate(u32 addr) noexcept { @@ -62,23 +73,31 @@ public: JitBlockEntry LookUpBlock(u32 num, u64* entries, u32 offset, u32 addr) noexcept; bool SetupExecutableRegion(u32 num, u32 blockAddr, u64*& entry, u32& start, u32& size) noexcept; u32 LocaliseCodeAddress(u32 num, u32 addr) const noexcept; -#else - template - void CheckAndInvalidate(u32) noexcept {} -#endif ARMJIT_Memory Memory; +private: int MaxBlockSize {}; bool LiteralOptimizations = false; bool BranchOptimizations = false; bool FastMemory = false; - +public: melonDS::NDS& NDS; TinyVector InvalidLiterals {}; friend class ARMJIT_Memory; void blockSanityCheck(u32 num, u32 blockAddr, JitBlockEntry entry) noexcept; void RetireJitBlock(JitBlock* block) noexcept; + int GetMaxBlockSize() const noexcept { return MaxBlockSize; } + bool LiteralOptimizationsEnabled() const noexcept { return LiteralOptimizations; } + bool BranchOptimizationsEnabled() const noexcept { return BranchOptimizations; } + bool FastMemoryEnabled() const noexcept { return FastMemory; } + + void SetJITArgs(JITArgs args) noexcept; + void SetMaxBlockSize(int size) noexcept; + void SetLiteralOptimizations(bool enabled) noexcept; + void SetBranchOptimizations(bool enabled) noexcept; + void SetFastMemory(bool enabled) noexcept; + Compiler JITCompiler; std::unordered_map JitBlocks9 {}; std::unordered_map JitBlocks7 {}; @@ -162,5 +181,33 @@ public: // Defined in assembly extern "C" void ARM_Dispatch(melonDS::ARM* cpu, melonDS::JitBlockEntry entry); +#else +namespace melonDS +{ +class ARM; + +// This version is a stub; the methods all do nothing, +// but there's still a Memory member. +class ARMJIT +{ +public: + ARMJIT(melonDS::NDS& nds, std::optional) noexcept : Memory(nds) {} + ~ARMJIT() noexcept {} + void InvalidateByAddr(u32) noexcept {} + void CheckAndInvalidateWVRAM(int) noexcept {} + void CheckAndInvalidateITCM() noexcept {} + void Reset() noexcept {} + void JitEnableWrite() noexcept {} + void JitEnableExecute() noexcept {} + void CompileBlock(ARM*) noexcept {} + void ResetBlockCache() noexcept {} + template + void CheckAndInvalidate(u32 addr) noexcept {} + + ARMJIT_Memory Memory; +}; +} +#endif // JIT_ENABLED + +#endif // ARMJIT_H -#endif diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.cpp b/src/ARMJIT_A64/ARMJIT_Compiler.cpp index c306dd84..7981ed67 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_A64/ARMJIT_Compiler.cpp @@ -276,7 +276,7 @@ Compiler::Compiler(melonDS::NDS& nds) : Arm64Gen::ARM64XEmitter(), NDS(nds) VirtualProtect(pageAligned, alignedSize, PAGE_EXECUTE_READWRITE, &dummy); #elif defined(__APPLE__) pageAligned = (u8*)mmap(NULL, 1024*1024*16, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT,-1, 0); - JitEnableWrite(); + nds.JIT.JitEnableWrite(); #else mprotect(pageAligned, alignedSize, PROT_EXEC | PROT_READ | PROT_WRITE); #endif diff --git a/src/ARMJIT_A64/ARMJIT_Compiler.h b/src/ARMJIT_A64/ARMJIT_Compiler.h index 72dd7bcb..04f12e85 100644 --- a/src/ARMJIT_A64/ARMJIT_Compiler.h +++ b/src/ARMJIT_A64/ARMJIT_Compiler.h @@ -19,6 +19,8 @@ #ifndef ARMJIT_A64_COMPILER_H #define ARMJIT_A64_COMPILER_H +#if defined(JIT_ENABLED) && defined(__aarch64__) + #include "../ARM.h" #include "../dolphin/Arm64Emitter.h" @@ -96,12 +98,8 @@ class Compiler : public Arm64Gen::ARM64XEmitter public: typedef void (Compiler::*CompileFunc)(); -#ifdef JIT_ENABLED explicit Compiler(melonDS::NDS& nds); -#else - explicit Compiler(melonDS::NDS& nds) : XEmitter(), NDS(nds) {} -#endif - ~Compiler(); + ~Compiler() override; void PushRegs(bool saveHiRegs, bool saveRegsToBeChanged, bool allowUnload = true); void PopRegs(bool saveHiRegs, bool saveRegsToBeChanged); @@ -116,7 +114,7 @@ public: bool CanCompile(bool thumb, u16 kind); - bool FlagsNZNeeded() + bool FlagsNZNeeded() const { return CurInstr.SetFlags & 0xC; } @@ -236,7 +234,7 @@ public: return (u8*)entry - GetRXBase(); } - bool IsJITFault(u8* pc); + bool IsJITFault(const u8* pc); u8* RewriteMemAccess(u8* pc); void SwapCodeRegion() @@ -291,3 +289,5 @@ public: } #endif + +#endif diff --git a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp index 22a410ae..e108b7b4 100644 --- a/src/ARMJIT_A64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_A64/ARMJIT_LoadStore.cpp @@ -28,7 +28,7 @@ using namespace Arm64Gen; namespace melonDS { -bool Compiler::IsJITFault(u8* pc) +bool Compiler::IsJITFault(const u8* pc) { return (u64)pc >= (u64)GetRXBase() && (u64)pc - (u64)GetRXBase() < (JitMemMainSize + JitMemSecondarySize); } @@ -112,7 +112,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, Op2 offset, int size, int flags) if (size == 16) addressMask = ~1; - if (NDS.JIT.LiteralOptimizations && rn == 15 && rd != 15 && offset.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) + if (NDS.JIT.LiteralOptimizationsEnabled() && rn == 15 && rd != 15 && offset.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) { u32 addr = R15 + offset.Imm * ((flags & memop_SubtractOffset) ? -1 : 1); @@ -147,7 +147,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, Op2 offset, int size, int flags) MOV(W0, rnMapped); } - bool addrIsStatic = NDS.JIT.LiteralOptimizations + bool addrIsStatic = NDS.JIT.LiteralOptimizationsEnabled() && RegCache.IsLiteral(rn) && offset.IsImm && !(flags & (memop_Writeback|memop_Post)); u32 staticAddress; if (addrIsStatic) @@ -189,7 +189,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, Op2 offset, int size, int flags) ? NDS.JIT.Memory.ClassifyAddress9(addrIsStatic ? staticAddress : CurInstr.DataRegion) : NDS.JIT.Memory.ClassifyAddress7(addrIsStatic ? staticAddress : CurInstr.DataRegion); - if (NDS.JIT.FastMemory && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) + if (NDS.JIT.FastMemoryEnabled() && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) { ptrdiff_t memopStart = GetCodeOffset(); LoadStorePatch patch; @@ -453,7 +453,7 @@ void Compiler::T_Comp_LoadPCRel() u32 offset = ((CurInstr.Instr & 0xFF) << 2); u32 addr = (R15 & ~0x2) + offset; - if (!NDS.JIT.LiteralOptimizations || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) + if (!NDS.JIT.LiteralOptimizationsEnabled() || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) Comp_MemAccess(CurInstr.T_Reg(8), 15, Op2(offset), 32, 0); } @@ -498,7 +498,7 @@ s32 Compiler::Comp_MemAccessBlock(int rn, BitSet16 regs, bool store, bool preinc ? NDS.JIT.Memory.ClassifyAddress9(CurInstr.DataRegion) : NDS.JIT.Memory.ClassifyAddress7(CurInstr.DataRegion); - bool compileFastPath = NDS.JIT.FastMemory + bool compileFastPath = NDS.JIT.FastMemoryEnabled() && store && !usermode && (CurInstr.Cond() < 0xE || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget)); { diff --git a/src/ARMJIT_Compiler.h b/src/ARMJIT_Compiler.h index 4ece834f..ff4f8ff7 100644 --- a/src/ARMJIT_Compiler.h +++ b/src/ARMJIT_Compiler.h @@ -20,10 +20,6 @@ #define ARMJIT_COMPILER_H #ifdef JIT_ENABLED -#define NOOP_IF_NO_JIT -#else -#define NOOP_IF_NO_JIT {} -#endif #if defined(__x86_64__) #include "ARMJIT_x64/ARMJIT_Compiler.h" @@ -34,3 +30,5 @@ #endif #endif + +#endif diff --git a/src/ARMJIT_Internal.h b/src/ARMJIT_Internal.h index 72d40a5f..8429bade 100644 --- a/src/ARMJIT_Internal.h +++ b/src/ARMJIT_Internal.h @@ -85,7 +85,7 @@ typedef void (*InterpreterFunc)(ARM* cpu); extern InterpreterFunc InterpretARM[]; extern InterpreterFunc InterpretTHUMB[]; -inline bool PageContainsCode(AddressRange* range) +inline bool PageContainsCode(const AddressRange* range) { for (int i = 0; i < 8; i++) { diff --git a/src/ARMJIT_Memory.cpp b/src/ARMJIT_Memory.cpp index bf1fb063..c8969aee 100644 --- a/src/ARMJIT_Memory.cpp +++ b/src/ARMJIT_Memory.cpp @@ -473,8 +473,10 @@ void ARMJIT_Memory::RemapDTCM(u32 newBase, u32 newSize) noexcept void ARMJIT_Memory::RemapNWRAM(int num) noexcept { - auto* dsi = dynamic_cast(&NDS); - assert(dsi != nullptr); + if (NDS.ConsoleType == 0) + return; + + auto* dsi = static_cast(&NDS); for (int i = 0; i < Mappings[memregion_SharedWRAM].Length;) { Mapping& mapping = Mappings[memregion_SharedWRAM][i]; @@ -1071,15 +1073,19 @@ int ARMJIT_Memory::ClassifyAddress9(u32 addr) const noexcept } else { - auto& dsi = static_cast(NDS); // ONLY use this if ConsoleType == 1! - if (NDS.ConsoleType == 1 && addr >= 0xFFFF0000 && !(dsi.SCFG_BIOS & (1<<1))) + if (NDS.ConsoleType == 1) { - if ((addr >= 0xFFFF8000) && (dsi.SCFG_BIOS & (1<<0))) - return memregion_Other; + auto& dsi = static_cast(NDS); + if (addr >= 0xFFFF0000 && !(dsi.SCFG_BIOS & (1<<1))) + { + if ((addr >= 0xFFFF8000) && (dsi.SCFG_BIOS & (1<<0))) + return memregion_Other; - return memregion_BIOS9DSi; + return memregion_BIOS9DSi; + } } - else if ((addr & 0xFFFFF000) == 0xFFFF0000) + + if ((addr & 0xFFFFF000) == 0xFFFF0000) { return memregion_BIOS9; } @@ -1091,6 +1097,7 @@ int ARMJIT_Memory::ClassifyAddress9(u32 addr) const noexcept case 0x03000000: if (NDS.ConsoleType == 1) { + auto& dsi = static_cast(NDS); if (addr >= dsi.NWRAMStart[0][0] && addr < dsi.NWRAMEnd[0][0]) return memregion_NewSharedWRAM_A; if (addr >= dsi.NWRAMStart[0][1] && addr < dsi.NWRAMEnd[0][1]) @@ -1116,15 +1123,19 @@ int ARMJIT_Memory::ClassifyAddress9(u32 addr) const noexcept int ARMJIT_Memory::ClassifyAddress7(u32 addr) const noexcept { - auto& dsi = static_cast(NDS); - if (NDS.ConsoleType == 1 && addr < 0x00010000 && !(dsi.SCFG_BIOS & (1<<9))) + if (NDS.ConsoleType == 1) { - if (addr >= 0x00008000 && dsi.SCFG_BIOS & (1<<8)) - return memregion_Other; + auto& dsi = static_cast(NDS); + if (addr < 0x00010000 && !(dsi.SCFG_BIOS & (1<<9))) + { + if (addr >= 0x00008000 && dsi.SCFG_BIOS & (1<<8)) + return memregion_Other; - return memregion_BIOS7DSi; + return memregion_BIOS7DSi; + } } - else if (addr < 0x00004000) + + if (addr < 0x00004000) { return memregion_BIOS7; } @@ -1138,6 +1149,7 @@ int ARMJIT_Memory::ClassifyAddress7(u32 addr) const noexcept case 0x03000000: if (NDS.ConsoleType == 1) { + auto& dsi = static_cast(NDS); if (addr >= dsi.NWRAMStart[1][0] && addr < dsi.NWRAMEnd[1][0]) return memregion_NewSharedWRAM_A; if (addr >= dsi.NWRAMStart[1][1] && addr < dsi.NWRAMEnd[1][1]) diff --git a/src/ARMJIT_Memory.h b/src/ARMJIT_Memory.h index 487005b2..d36f6032 100644 --- a/src/ARMJIT_Memory.h +++ b/src/ARMJIT_Memory.h @@ -20,33 +20,33 @@ #define ARMJIT_MEMORY #include "types.h" -#include "TinyVector.h" - -#include "ARM.h" #include "MemConstants.h" -#if defined(__SWITCH__) -#include -#elif defined(_WIN32) +#ifdef JIT_ENABLED +# include "TinyVector.h" +# include "ARM.h" +# if defined(__SWITCH__) +# include +# elif defined(_WIN32) #include +# else +# include +# include +# include +# include +# include +# endif #else -#include -#include -#include -#include -#include -#endif - -#ifndef JIT_ENABLED -#include -#include "NDS.h" +# include #endif namespace melonDS { +#ifdef JIT_ENABLED namespace Platform { struct DynamicLibrary; } class Compiler; class ARMJIT; +#endif constexpr u32 RoundUp(u32 size) noexcept { diff --git a/src/ARMJIT_RegisterCache.h b/src/ARMJIT_RegisterCache.h index 3cb0f79f..e5f28dd6 100644 --- a/src/ARMJIT_RegisterCache.h +++ b/src/ARMJIT_RegisterCache.h @@ -99,7 +99,7 @@ public: LiteralsLoaded &= ~(1 << reg); } - bool IsLiteral(int reg) + bool IsLiteral(int reg) const { return LiteralsLoaded & (1 << reg); } diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.cpp b/src/ARMJIT_x64/ARMJIT_Compiler.cpp index eec4d7d1..b18837f3 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.cpp +++ b/src/ARMJIT_x64/ARMJIT_Compiler.cpp @@ -651,7 +651,7 @@ const Compiler::CompileFunc T_Comp[ARMInstrInfo::tk_Count] = { }; #undef F -bool Compiler::CanCompile(bool thumb, u16 kind) +bool Compiler::CanCompile(bool thumb, u16 kind) const { return (thumb ? T_Comp[kind] : A_Comp[kind]) != NULL; } @@ -667,7 +667,7 @@ void Compiler::Reset() LoadStorePatches.clear(); } -bool Compiler::IsJITFault(u8* addr) +bool Compiler::IsJITFault(const u8* addr) { return (u64)addr >= (u64)ResetStart && (u64)addr < (u64)ResetStart + CodeMemSize; } diff --git a/src/ARMJIT_x64/ARMJIT_Compiler.h b/src/ARMJIT_x64/ARMJIT_Compiler.h index fa6d78a4..941d8924 100644 --- a/src/ARMJIT_x64/ARMJIT_Compiler.h +++ b/src/ARMJIT_x64/ARMJIT_Compiler.h @@ -19,6 +19,8 @@ #ifndef ARMJIT_X64_COMPILER_H #define ARMJIT_X64_COMPILER_H +#if defined(JIT_ENABLED) && defined(__x86_64__) + #include "../dolphin/x64Emitter.h" #include "../ARMJIT_Internal.h" @@ -81,11 +83,7 @@ struct Op2 class Compiler : public Gen::XEmitter { public: -#ifdef JIT_ENABLED explicit Compiler(melonDS::NDS& nds); -#else - explicit Compiler(melonDS::NDS& nds) : XEmitter(), NDS(nds) {} -#endif void Reset(); @@ -94,7 +92,7 @@ public: void LoadReg(int reg, Gen::X64Reg nativeReg); void SaveReg(int reg, Gen::X64Reg nativeReg); - bool CanCompile(bool thumb, u16 kind); + bool CanCompile(bool thumb, u16 kind) const; typedef void (Compiler::*CompileFunc)(); @@ -236,7 +234,7 @@ public: SetCodePtr(FarCode); } - bool IsJITFault(u8* addr); + bool IsJITFault(const u8* addr); u8* RewriteMemAccess(u8* pc); @@ -284,5 +282,6 @@ public: }; } +#endif #endif diff --git a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp index 72a073db..8520bebc 100644 --- a/src/ARMJIT_x64/ARMJIT_LoadStore.cpp +++ b/src/ARMJIT_x64/ARMJIT_LoadStore.cpp @@ -119,7 +119,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag if (size == 16) addressMask = ~1; - if (NDS.JIT.LiteralOptimizations && rn == 15 && rd != 15 && op2.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) + if (NDS.JIT.LiteralOptimizationsEnabled() && rn == 15 && rd != 15 && op2.IsImm && !(flags & (memop_Post|memop_Store|memop_Writeback))) { u32 addr = R15 + op2.Imm * ((flags & memop_SubtractOffset) ? -1 : 1); @@ -136,7 +136,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag Comp_AddCycles_CDI(); } - bool addrIsStatic = NDS.JIT.LiteralOptimizations + bool addrIsStatic = NDS.JIT.LiteralOptimizationsEnabled() && RegCache.IsLiteral(rn) && op2.IsImm && !(flags & (memop_Writeback|memop_Post)); u32 staticAddress; if (addrIsStatic) @@ -200,7 +200,7 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag ? NDS.JIT.Memory.ClassifyAddress9(CurInstr.DataRegion) : NDS.JIT.Memory.ClassifyAddress7(CurInstr.DataRegion); - if (NDS.JIT.FastMemory && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) + if (NDS.JIT.FastMemoryEnabled() && ((!Thumb && CurInstr.Cond() != 0xE) || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget))) { if (rdMapped.IsImm()) { @@ -431,7 +431,7 @@ s32 Compiler::Comp_MemAccessBlock(int rn, BitSet16 regs, bool store, bool preinc else Comp_AddCycles_CD(); - bool compileFastPath = NDS.JIT.FastMemory + bool compileFastPath = NDS.JIT.FastMemoryEnabled() && !usermode && (CurInstr.Cond() < 0xE || NDS.JIT.Memory.IsFastmemCompatible(expectedTarget)); // we need to make sure that the stack stays aligned to 16 bytes @@ -809,7 +809,7 @@ void Compiler::T_Comp_LoadPCRel() { u32 offset = (CurInstr.Instr & 0xFF) << 2; u32 addr = (R15 & ~0x2) + offset; - if (!NDS.JIT.LiteralOptimizations || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) + if (!NDS.JIT.LiteralOptimizationsEnabled() || !Comp_MemLoadLiteral(32, false, CurInstr.T_Reg(8), addr)) Comp_MemAccess(CurInstr.T_Reg(8), 15, Op2(offset), 32, 0); } diff --git a/src/Args.h b/src/Args.h new file mode 100644 index 00000000..d836b643 --- /dev/null +++ b/src/Args.h @@ -0,0 +1,148 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef MELONDS_ARGS_H +#define MELONDS_ARGS_H + +#include +#include +#include + +#include "NDSCart.h" +#include "GBACart.h" +#include "types.h" +#include "MemConstants.h" +#include "DSi_NAND.h" +#include "FATStorage.h" +#include "FreeBIOS.h" +#include "GPU3D_Soft.h" +#include "SPI_Firmware.h" +#include "SPU.h" + +namespace melonDS +{ +namespace NDSCart { class CartCommon; } +namespace GBACart { class CartCommon; } + +template +constexpr std::array BrokenBIOS = []() constexpr { + std::array broken {}; + + for (int i = 0; i < 16; i++) + { + broken[i*4+0] = 0xE7; + broken[i*4+1] = 0xFF; + broken[i*4+2] = 0xDE; + broken[i*4+3] = 0xFF; + } + + return broken; +}(); + +/// Arguments that configure the JIT. +/// Ignored in builds that don't have the JIT included. +struct JITArgs +{ + unsigned MaxBlockSize = 32; + bool LiteralOptimizations = true; + bool BranchOptimizations = true; + + /// Ignored in builds that have fast memory excluded + /// (even if the JIT is otherwise available). + /// Enabled by default, but frontends should disable this when debugging + /// so the constants segfaults don't hinder debugging. + bool FastMemory = true; +}; + +struct GDBArgs +{ + u16 PortARM7 = 0; + u16 PortARM9 = 0; + bool ARM7BreakOnStartup = false; + bool ARM9BreakOnStartup = false; +}; + +/// Arguments to pass into the NDS constructor. +/// New fields here should have default values if possible. +struct NDSArgs +{ + /// NDS ROM to install. + /// Defaults to nullptr, which means no cart. + /// Should be populated with the desired save data beforehand, + /// including an SD card if applicable. + std::unique_ptr NDSROM = nullptr; + + /// GBA ROM to install. + /// Defaults to nullptr, which means no cart. + /// Should be populated with the desired save data beforehand. + /// Ignored in DSi mode. + std::unique_ptr GBAROM = nullptr; + + /// NDS ARM9 BIOS to install. + /// Defaults to FreeBIOS, which is not compatible with DSi mode. + std::array ARM9BIOS = bios_arm9_bin; + + /// NDS ARM7 BIOS to install. + /// Defaults to FreeBIOS, which is not compatible with DSi mode. + std::array ARM7BIOS = bios_arm7_bin; + + /// Firmware image to install. + /// Defaults to generated NDS firmware. + /// Generated firmware is not compatible with DSi mode. + melonDS::Firmware Firmware {0}; + + /// How the JIT should be configured when initializing. + /// Defaults to enabled, with default settings. + /// To disable the JIT, set this to std::nullopt. + /// Ignored in builds that don't have the JIT included. + std::optional JIT = JITArgs(); + + AudioBitDepth BitDepth = AudioBitDepth::Auto; + AudioInterpolation Interpolation = AudioInterpolation::None; + + /// How the GDB stub should be handled. + /// Defaults to disabled. + /// Ignored in builds that don't have the GDB stub included. + std::optional GDB = std::nullopt; + + /// The 3D renderer to initialize the DS with. + /// Defaults to the software renderer. + /// Can be changed later at any time. + std::unique_ptr Renderer3D = std::make_unique(); +}; + +/// Arguments to pass into the DSi constructor. +/// New fields here should have default values if possible. +/// Contains no virtual methods, so there's no vtable. +struct DSiArgs final : public NDSArgs +{ + std::array ARM9iBIOS = BrokenBIOS; + std::array ARM7iBIOS = BrokenBIOS; + + /// NAND image to install. + /// Required, there is no default value. + DSi_NAND::NANDImage NANDImage; + + /// SD card to install. + /// Defaults to std::nullopt, which means no SD card. + std::optional DSiSDCard; + + bool FullBIOSBoot = false; +}; +} +#endif //MELONDS_ARGS_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9fe93ae5..b1ae4c47 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ add_library(core STATIC melonDLDI.h NDS.cpp NDSCart.cpp + NDSCartR4.cpp Platform.h ROMList.h ROMList.cpp @@ -49,6 +50,8 @@ add_library(core STATIC SPI_Firmware.cpp SPU.cpp types.h + Utils.cpp + Utils.h version.h Wifi.cpp WifiAP.cpp diff --git a/src/CP15.cpp b/src/CP15.cpp index 7cea845d..58137fdd 100644 --- a/src/CP15.cpp +++ b/src/CP15.cpp @@ -668,7 +668,7 @@ void ARMv5::CP15Write(u32 id, u32 val) Log(LogLevel::Debug, "unknown CP15 write op %03X %08X\n", id, val); } -u32 ARMv5::CP15Read(u32 id) +u32 ARMv5::CP15Read(u32 id) const { //printf("CP15 read op %03X %08X\n", id, NDS::ARM9->R[15]); diff --git a/src/DSi.cpp b/src/DSi.cpp index f2781e48..c929c6d2 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -17,8 +17,10 @@ */ #include +#include #include #include +#include "Args.h" #include "NDS.h" #include "DSi.h" #include "ARM.h" @@ -68,8 +70,8 @@ const u32 NDMAModes[] = 0xFF, // wifi / GBA cart slot (TODO) }; -DSi::DSi() noexcept : - NDS(1), +DSi::DSi(DSiArgs&& args) noexcept : + NDS(std::move(args), 1), NDMAs { DSi_NDMA(0, 0, *this), DSi_NDMA(0, 1, *this), @@ -80,9 +82,11 @@ DSi::DSi() noexcept : DSi_NDMA(1, 2, *this), DSi_NDMA(1, 3, *this), }, + ARM7iBIOS(args.ARM7iBIOS), + ARM9iBIOS(args.ARM9iBIOS), DSP(*this), - SDMMC(*this, 0), - SDIO(*this, 1), + SDMMC(*this, std::move(args.NANDImage), std::move(args.DSiSDCard)), + SDIO(*this), I2C(*this), CamModule(*this), AES(*this) @@ -90,7 +94,7 @@ DSi::DSi() noexcept : // Memory is owned by ARMJIT_Memory, don't free it NWRAM_A = JIT.Memory.GetNWRAM_A(); NWRAM_B = JIT.Memory.GetNWRAM_B(); - NWRAM_C = NDS::JIT.Memory.GetNWRAM_C(); + NWRAM_C = JIT.Memory.GetNWRAM_C(); } DSi::~DSi() noexcept @@ -108,6 +112,8 @@ void DSi::Reset() //ARM9.CP15Write(0x100, ARM9.CP15Read(0x100) | 0x00050000); NDS::Reset(); + // The SOUNDBIAS register does nothing on DSi + SPU.SetApplyBias(false); KeyInput &= ~(1 << (16+6)); MapSharedWRAM(3); @@ -118,9 +124,6 @@ void DSi::Reset() CamModule.Reset(); DSP.Reset(); - SDMMC.CloseHandles(); - SDIO.CloseHandles(); - LoadNAND(); SDMMC.Reset(); @@ -128,7 +131,7 @@ void DSi::Reset() AES.Reset(); - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { SCFG_BIOS = 0x0000; } @@ -162,25 +165,23 @@ void DSi::Stop(Platform::StopReason reason) CamModule.Stop(); } -bool DSi::LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) +void DSi::SetNDSCart(std::unique_ptr&& cart) { - if (NDS::LoadCart(romdata, romlen, savedata, savelen)) - { - SetCartInserted(true); - return true; - } - - return false; + NDS::SetNDSCart(std::move(cart)); + SetCartInserted(NDSCartSlot.GetCart() != nullptr); } -void DSi::EjectCart() +std::unique_ptr DSi::EjectCart() { - NDS::EjectCart(); + auto oldcart = NDS::EjectCart(); SetCartInserted(false); + + return oldcart; } -void DSi::CamInputFrame(int cam, u32* data, int width, int height, bool rgb) + +void DSi::CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) { switch (cam) { @@ -276,7 +277,7 @@ void DSi::SetCartInserted(bool inserted) SCFG_MC |= 1; } -void DSi::DecryptModcryptArea(u32 offset, u32 size, u8* iv) +void DSi::DecryptModcryptArea(u32 offset, u32 size, const u8* iv) { AES_ctx ctx; u8 key[16]; @@ -509,9 +510,9 @@ void DSi::SetupDirectBoot() ARM9Write32(0x02FFE000+i, tmp); } - if (NANDImage && *NANDImage) + if (DSi_NAND::NANDImage* image = SDMMC.GetNAND(); image && *image) { // If a NAND image is installed, and it's valid... - if (DSi_NAND::NANDMount nand = DSi_NAND::NANDMount(*NANDImage)) + if (DSi_NAND::NANDMount nand = DSi_NAND::NANDMount(*image)) { DSi_NAND::DSiFirmwareSystemSettings userdata {}; nand.ReadUserData(userdata); @@ -531,7 +532,7 @@ void DSi::SetupDirectBoot() } } - Firmware::WifiBoard nwifiver = SPI.GetFirmware()->GetHeader().WifiBoard; + Firmware::WifiBoard nwifiver = SPI.GetFirmware().GetHeader().WifiBoard; ARM9Write8(0x020005E0, static_cast(nwifiver)); // TODO: these should be taken from the wifi firmware in NAND @@ -674,9 +675,6 @@ void DSi::SoftReset() // the DSP most likely gets reset DSP.Reset(); - SDMMC.CloseHandles(); - SDIO.CloseHandles(); - LoadNAND(); SDMMC.Reset(); @@ -684,7 +682,7 @@ void DSi::SoftReset() AES.Reset(); - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { SCFG_BIOS = 0x0000; } @@ -709,21 +707,22 @@ void DSi::SoftReset() bool DSi::LoadNAND() { - if (!NANDImage) + DSi_NAND::NANDImage* image = SDMMC.GetNAND(); + if (!(image && *image)) { Log(LogLevel::Error, "No NAND image loaded\n"); return false; } Log(LogLevel::Info, "Loading DSi NAND\n"); - DSi_NAND::NANDMount nandmount(*NANDImage); + DSi_NAND::NANDMount nandmount(*SDMMC.GetNAND()); if (!nandmount) { Log(LogLevel::Error, "Failed to load DSi NAND\n"); return false; } - FileHandle* nand = NANDImage->GetFile(); + FileHandle* nand = image->GetFile(); // Make sure NWRAM is accessible. // The Bits are set to the startup values in Reset() and we might @@ -745,7 +744,7 @@ bool DSi::LoadNAND() memset(NWRAMMask, 0, sizeof(NWRAMMask)); u32 bootparams[8]; - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { // TODO: figure out default MBK mapping // MBK1..5: disable mappings @@ -879,11 +878,11 @@ bool DSi::LoadNAND() } } - const DSi_NAND::DSiKey& emmccid = NANDImage->GetEMMCID(); + const DSi_NAND::DSiKey& emmccid = image->GetEMMCID(); Log(LogLevel::Debug, "eMMC CID: %08llX%08llX\n", *(const u64*)&emmccid[0], *(const u64*)&emmccid[8]); - Log(LogLevel::Debug, "Console ID: %" PRIx64 "\n", NANDImage->GetConsoleID()); + Log(LogLevel::Debug, "Console ID: %" PRIx64 "\n", image->GetConsoleID()); - if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot)) + if (FullBIOSBoot) { // point CPUs to boot ROM reset vectors ARM9.JumpTo(0xFFFF0000); @@ -958,21 +957,21 @@ void DSi::StallNDMAs() } -bool DSi::DMAsInMode(u32 cpu, u32 mode) +bool DSi::DMAsInMode(u32 cpu, u32 mode) const { if (NDS::DMAsInMode(cpu, mode)) return true; return NDMAsInMode(cpu, NDMAModes[mode]); } -bool DSi::DMAsRunning(u32 cpu) +bool DSi::DMAsRunning(u32 cpu) const { if (NDS::DMAsRunning(cpu)) return true; return NDMAsRunning(cpu); } -bool DSi::NDMAsInMode(u32 cpu, u32 mode) +bool DSi::NDMAsInMode(u32 cpu, u32 mode) const { cpu <<= 2; if (NDMAs[cpu+0].IsInMode(mode)) return true; @@ -982,7 +981,7 @@ bool DSi::NDMAsInMode(u32 cpu, u32 mode) return false; } -bool DSi::NDMAsRunning(u32 cpu) +bool DSi::NDMAsRunning(u32 cpu) const { cpu <<= 2; if (NDMAs[cpu+0].IsRunning()) return true; @@ -1728,12 +1727,12 @@ bool DSi::ARM9GetMemRegion(u32 addr, bool write, MemRegion* region) return false; } - region->Mem = ARM9BIOS; + region->Mem = &ARM9BIOS[0]; region->Mask = 0xFFF; } else { - region->Mem = ARM9iBIOS; + region->Mem = &ARM9iBIOS[0]; region->Mask = 0xFFFF; } return true; @@ -2678,14 +2677,14 @@ u8 DSi::ARM7IORead8(u32 addr) case 0x04004500: return I2C.ReadData(); case 0x04004501: return I2C.ReadCnt(); - case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFF; - case 0x04004D01: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 8) & 0xFF; - case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 16) & 0xFF; - case 0x04004D03: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 24) & 0xFF; - case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 32) & 0xFF; - case 0x04004D05: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 40) & 0xFF; - case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 48) & 0xFF; - case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 56; + case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFF; + case 0x04004D01: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 8) & 0xFF; + case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 16) & 0xFF; + case 0x04004D03: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 24) & 0xFF; + case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 32) & 0xFF; + case 0x04004D05: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 40) & 0xFF; + case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 48) & 0xFF; + case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 56; case 0x04004D08: return 0; case 0x4004700: return DSP.ReadSNDExCnt() & 0xFF; @@ -2726,10 +2725,10 @@ u16 DSi::ARM7IORead16(u32 addr) CASE_READ16_32BIT(0x0400405C, MBK[1][7]) CASE_READ16_32BIT(0x04004060, MBK[1][8]) - case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFFFF; - case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 16) & 0xFFFF; - case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 32) & 0xFFFF; - case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 48; + case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFFFF; + case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 16) & 0xFFFF; + case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 32) & 0xFFFF; + case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 48; case 0x04004D08: return 0; case 0x4004700: return DSP.ReadSNDExCnt(); @@ -2806,8 +2805,8 @@ u32 DSi::ARM7IORead32(u32 addr) case 0x04004400: return AES.ReadCnt(); case 0x0400440C: return AES.ReadOutputFIFO(); - case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFFFFFFFF; - case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 32; + case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFFFFFFFF; + case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 32; case 0x04004D08: return 0; case 0x4004700: diff --git a/src/DSi.h b/src/DSi.h index a221b5d7..1d010e0f 100644 --- a/src/DSi.h +++ b/src/DSi.h @@ -33,6 +33,7 @@ class DSi_I2CHost; class DSi_CamModule; class DSi_AES; class DSi_DSP; +class DSiArgs; namespace DSi_NAND { @@ -48,9 +49,8 @@ public: u16 SCFG_Clock9; u32 SCFG_EXT[2]; - u8 ARM9iBIOS[0x10000]; - u8 ARM7iBIOS[0x10000]; - std::unique_ptr NANDImage; + std::array ARM9iBIOS; + std::array ARM7iBIOS; DSi_SDHost SDMMC; DSi_SDHost SDIO; @@ -87,8 +87,8 @@ public: void RunNDMAs(u32 cpu); void StallNDMAs(); - bool NDMAsInMode(u32 cpu, u32 mode); - bool NDMAsRunning(u32 cpu); + bool NDMAsInMode(u32 cpu, u32 mode) const; + bool NDMAsRunning(u32 cpu) const; void CheckNDMAs(u32 cpu, u32 mode); void StopNDMAs(u32 cpu, u32 mode); @@ -130,22 +130,32 @@ public: void ARM7IOWrite32(u32 addr, u32 val) override; public: - DSi() noexcept; + DSi(DSiArgs&& args) noexcept; ~DSi() noexcept override; DSi(const DSi&) = delete; DSi& operator=(const DSi&) = delete; DSi(DSi&&) = delete; DSi& operator=(DSi&&) = delete; - bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) override; - void EjectCart() override; - bool NeedsDirectBoot() override + void SetNDSCart(std::unique_ptr&& cart) override; + std::unique_ptr EjectCart() override; + bool NeedsDirectBoot() const override { // for now, DSi mode requires original BIOS/NAND return false; } - void CamInputFrame(int cam, u32* data, int width, int height, bool rgb) override; - bool DMAsInMode(u32 cpu, u32 mode) override; - bool DMAsRunning(u32 cpu) override; + + [[nodiscard]] const DSi_NAND::NANDImage& GetNAND() const noexcept { return *SDMMC.GetNAND(); } + [[nodiscard]] DSi_NAND::NANDImage& GetNAND() noexcept { return *SDMMC.GetNAND(); } + void SetNAND(DSi_NAND::NANDImage&& nand) noexcept { SDMMC.SetNAND(std::move(nand)); } + u64 GetConsoleID() const noexcept { return SDMMC.GetNAND()->GetConsoleID(); } + + [[nodiscard]] const FATStorage* GetSDCard() const noexcept { return SDMMC.GetSDCard(); } + void SetSDCard(FATStorage&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); } + void SetSDCard(std::optional&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); } + + void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) override; + bool DMAsInMode(u32 cpu, u32 mode) const override; + bool DMAsRunning(u32 cpu) const override; void StopDMAs(u32 cpu, u32 mode) override; void CheckDMAs(u32 cpu, u32 mode) override; u16 SCFG_Clock7; @@ -162,10 +172,13 @@ public: u8 GPIO_IE; u8 GPIO_WiFi; + bool GetFullBIOSBoot() const noexcept { return FullBIOSBoot; } + void SetFullBIOSBoot(bool full) noexcept { FullBIOSBoot = full; } private: + bool FullBIOSBoot; void Set_SCFG_Clock9(u16 val); void Set_SCFG_MC(u32 val); - void DecryptModcryptArea(u32 offset, u32 size, u8* iv); + void DecryptModcryptArea(u32 offset, u32 size, const u8* iv); void ApplyNewRAMSize(u32 size); }; diff --git a/src/DSi_AES.cpp b/src/DSi_AES.cpp index 8e04586a..379dea13 100644 --- a/src/DSi_AES.cpp +++ b/src/DSi_AES.cpp @@ -78,7 +78,7 @@ void DSi_AES::Reset() OutputMACDue = false; // initialize keys - u64 consoleid = DSi.NANDImage->GetConsoleID(); + u64 consoleid = DSi.SDMMC.GetNAND()->GetConsoleID(); // slot 0: modcrypt *(u32*)&KeyX[0][0] = 0x746E694E; @@ -235,7 +235,7 @@ void DSi_AES::ProcessBlock_CTR() } -u32 DSi_AES::ReadCnt() +u32 DSi_AES::ReadCnt() const { u32 ret = Cnt; diff --git a/src/DSi_AES.h b/src/DSi_AES.h index 4df82695..d83c870e 100644 --- a/src/DSi_AES.h +++ b/src/DSi_AES.h @@ -54,7 +54,7 @@ public: void Reset(); void DoSavestate(Savestate* file); - u32 ReadCnt(); + u32 ReadCnt() const; void WriteCnt(u32 val); void WriteBlkCnt(u32 val); diff --git a/src/DSi_Camera.cpp b/src/DSi_Camera.cpp index 225bea8b..a1cdbe0a 100644 --- a/src/DSi_Camera.cpp +++ b/src/DSi_Camera.cpp @@ -438,7 +438,7 @@ void DSi_Camera::Stop() Platform::Camera_Stop(Num); } -bool DSi_Camera::IsActivated() +bool DSi_Camera::IsActivated() const { if (StandbyCnt & (1<<14)) return false; // standby if (!(MiscCnt & (1<<9))) return false; // data transfer not enabled @@ -477,7 +477,7 @@ void DSi_Camera::StartTransfer() Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true); } -bool DSi_Camera::TransferDone() +bool DSi_Camera::TransferDone() const { return TransferY >= FrameHeight; } @@ -590,7 +590,7 @@ void DSi_Camera::Write(u8 val, bool last) else DataPos++; } -u16 DSi_Camera::I2C_ReadReg(u16 addr) +u16 DSi_Camera::I2C_ReadReg(u16 addr) const { switch (addr) { @@ -695,7 +695,7 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) // TODO: not sure at all what is the accessible range // or if there is any overlap in the address range -u8 DSi_Camera::MCU_Read(u16 addr) +u8 DSi_Camera::MCU_Read(u16 addr) const { addr &= 0x7FFF; @@ -724,7 +724,7 @@ void DSi_Camera::MCU_Write(u16 addr, u8 val) } -void DSi_Camera::InputFrame(u32* data, int width, int height, bool rgb) +void DSi_Camera::InputFrame(const u32* data, int width, int height, bool rgb) { // TODO: double-buffering? diff --git a/src/DSi_Camera.h b/src/DSi_Camera.h index ec409223..363cea43 100644 --- a/src/DSi_Camera.h +++ b/src/DSi_Camera.h @@ -38,10 +38,10 @@ public: void Reset() override; void Stop(); - bool IsActivated(); + bool IsActivated() const; void StartTransfer(); - bool TransferDone(); + bool TransferDone() const; // lengths in words int TransferScanline(u32* buffer, int maxlen); @@ -50,7 +50,7 @@ public: u8 Read(bool last) override; void Write(u8 val, bool last) override; - void InputFrame(u32* data, int width, int height, bool rgb); + void InputFrame(const u32* data, int width, int height, bool rgb); u32 Num; @@ -59,7 +59,7 @@ private: u32 RegAddr; u16 RegData; - u16 I2C_ReadReg(u16 addr); + u16 I2C_ReadReg(u16 addr) const; void I2C_WriteReg(u16 addr, u16 val); u16 PLLDiv; @@ -72,7 +72,7 @@ private: u16 MCUAddr; u8 MCURegs[0x8000]; - u8 MCU_Read(u16 addr); + u8 MCU_Read(u16 addr) const; void MCU_Write(u16 addr, u8 val); u16 FrameWidth, FrameHeight; @@ -91,7 +91,9 @@ public: void Stop(); void DoSavestate(Savestate* file); + const DSi_Camera* GetOuterCamera() const { return Camera0; } DSi_Camera* GetOuterCamera() { return Camera0; } + const DSi_Camera* GetInnerCamera() const { return Camera1; } DSi_Camera* GetInnerCamera() { return Camera1; } void IRQ(u32 param); diff --git a/src/DSi_DSP.cpp b/src/DSi_DSP.cpp index 088943a9..25abd474 100644 --- a/src/DSi_DSP.cpp +++ b/src/DSi_DSP.cpp @@ -34,7 +34,7 @@ const u32 DSi_DSP::DataMemoryOffset = 0x20000; // from Teakra memory_interface.h // NOTE: ^ IS IN DSP WORDS, NOT IN BYTES! -u16 DSi_DSP::GetPSTS() +u16 DSi_DSP::GetPSTS() const { u16 r = DSP_PSTS & (1<<9); // this is the only sticky bit //r &= ~((1<<2)|(1<<7)); // we support instant resets and wrfifo xfers @@ -182,7 +182,7 @@ void DSi_DSP::Reset() SNDExCnt = 0; } -bool DSi_DSP::IsRstReleased() +bool DSi_DSP::IsRstReleased() const { return SCFG_RST; } @@ -193,12 +193,12 @@ void DSi_DSP::SetRstLine(bool release) DSPTimestamp = DSi.ARM9Timestamp; // only start now! } -inline bool DSi_DSP::IsDSPCoreEnabled() +inline bool DSi_DSP::IsDSPCoreEnabled() const { return (DSi.SCFG_Clock9 & (1<<1)) && SCFG_RST && (!(DSP_PCFG & (1<<0))); } -inline bool DSi_DSP::IsDSPIOEnabled() +inline bool DSi_DSP::IsDSPIOEnabled() const { return (DSi.SCFG_Clock9 & (1<<1)) && SCFG_RST; } diff --git a/src/DSi_DSP.h b/src/DSi_DSP.h index a18dabf1..f76b4202 100644 --- a/src/DSi_DSP.h +++ b/src/DSi_DSP.h @@ -41,7 +41,7 @@ public: void DSPCatchUpU32(u32 _); // SCFG_RST bit0 - bool IsRstReleased(); + bool IsRstReleased() const; void SetRstLine(bool release); // DSP_* regs (0x040043xx) (NOTE: checks SCFG_EXT) @@ -54,7 +54,7 @@ public: u32 Read32(u32 addr); void Write32(u32 addr, u32 val); - u16 ReadSNDExCnt() { return SNDExCnt; } + u16 ReadSNDExCnt() const { return SNDExCnt; } void WriteSNDExCnt(u16 val, u16 mask); // NOTE: checks SCFG_CLK9 @@ -93,10 +93,10 @@ private: static const u32 DataMemoryOffset; - u16 GetPSTS(); + u16 GetPSTS() const; - inline bool IsDSPCoreEnabled(); - inline bool IsDSPIOEnabled(); + inline bool IsDSPCoreEnabled() const; + inline bool IsDSPIOEnabled() const; bool DSPCatchUp(); diff --git a/src/DSi_I2C.cpp b/src/DSi_I2C.cpp index d5ea60c6..28f98dc8 100644 --- a/src/DSi_I2C.cpp +++ b/src/DSi_I2C.cpp @@ -117,20 +117,20 @@ void DSi_BPTWL::DoSavestate(Savestate* file) } // TODO: Needs more investigation on the other bits -inline bool DSi_BPTWL::GetIRQMode() +inline bool DSi_BPTWL::GetIRQMode() const { return Registers[0x12] & 0x01; } -u8 DSi_BPTWL::GetBootFlag() { return Registers[0x70]; } +u8 DSi_BPTWL::GetBootFlag() const { return Registers[0x70]; } -bool DSi_BPTWL::GetBatteryCharging() { return Registers[0x20] >> 7; } +bool DSi_BPTWL::GetBatteryCharging() const { return Registers[0x20] >> 7; } void DSi_BPTWL::SetBatteryCharging(bool charging) { Registers[0x20] = (((charging ? 0x8 : 0x0) << 4) | (Registers[0x20] & 0x0F)); } -u8 DSi_BPTWL::GetBatteryLevel() { return Registers[0x20] & 0xF; } +u8 DSi_BPTWL::GetBatteryLevel() const { return Registers[0x20] & 0xF; } void DSi_BPTWL::SetBatteryLevel(u8 batteryLevel) { Registers[0x20] = ((Registers[0x20] & 0xF0) | (batteryLevel & 0x0F)); @@ -143,13 +143,13 @@ void DSi_BPTWL::SetBatteryLevel(u8 batteryLevel) } -u8 DSi_BPTWL::GetVolumeLevel() { return Registers[0x40]; } +u8 DSi_BPTWL::GetVolumeLevel() const { return Registers[0x40]; } void DSi_BPTWL::SetVolumeLevel(u8 volume) { Registers[0x40] = volume & 0x1F; } -u8 DSi_BPTWL::GetBacklightLevel() { return Registers[0x41]; } +u8 DSi_BPTWL::GetBacklightLevel() const { return Registers[0x41]; } void DSi_BPTWL::SetBacklightLevel(u8 backlight) { Registers[0x41] = backlight > 4 ? 4 : backlight; @@ -246,7 +246,7 @@ void DSi_BPTWL::SetVolumeSwitchReleased(u32 key) VolumeSwitchRepeatTime = 0.0; } -inline bool DSi_BPTWL::CheckVolumeSwitchKeysValid() +inline bool DSi_BPTWL::CheckVolumeSwitchKeysValid() const { bool up = VolumeSwitchKeysDown & (1 << volumeKey_Up); bool down = VolumeSwitchKeysDown & (1 << volumeKey_Down); diff --git a/src/DSi_I2C.h b/src/DSi_I2C.h index 51fe78e6..5dfeebd0 100644 --- a/src/DSi_I2C.h +++ b/src/DSi_I2C.h @@ -86,20 +86,20 @@ public: void Reset() override; void DoSavestate(Savestate* file) override; - u8 GetBootFlag(); + u8 GetBootFlag() const; - bool GetBatteryCharging(); + bool GetBatteryCharging() const; void SetBatteryCharging(bool charging); - u8 GetBatteryLevel(); + u8 GetBatteryLevel() const; void SetBatteryLevel(u8 batteryLevel); // 0-31 - u8 GetVolumeLevel(); + u8 GetVolumeLevel() const; void SetVolumeLevel(u8 volume); // 0-4 - u8 GetBacklightLevel(); + u8 GetBacklightLevel() const; void SetBacklightLevel(u8 backlight); void DoHardwareReset(bool direct); @@ -144,10 +144,10 @@ private: u8 Registers[0x100]; u32 CurPos; - bool GetIRQMode(); + bool GetIRQMode() const; void ResetButtonState(); - bool CheckVolumeSwitchKeysValid(); + bool CheckVolumeSwitchKeysValid() const; }; diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp index 59f582fd..8da02540 100644 --- a/src/DSi_NAND.cpp +++ b/src/DSi_NAND.cpp @@ -122,7 +122,8 @@ NANDImage::NANDImage(NANDImage&& other) noexcept : ConsoleID(other.ConsoleID), FATIV(other.FATIV), FATKey(other.FATKey), - ESKey(other.ESKey) + ESKey(other.ESKey), + Length(other.Length) { other.CurFile = nullptr; } @@ -131,12 +132,16 @@ NANDImage& NANDImage::operator=(NANDImage&& other) noexcept { if (this != &other) { + if (CurFile) + CloseFile(CurFile); + CurFile = other.CurFile; eMMC_CID = other.eMMC_CID; ConsoleID = other.ConsoleID; FATIV = other.FATIV; FATKey = other.FATKey; ESKey = other.ESKey; + Length = other.Length; other.CurFile = nullptr; } @@ -362,7 +367,7 @@ bool NANDImage::ESEncrypt(u8* data, u32 len) const return true; } -bool NANDImage::ESDecrypt(u8* data, u32 len) +bool NANDImage::ESDecrypt(u8* data, u32 len) const { AES_ctx ctx; u8 iv[16]; diff --git a/src/DSi_NAND.h b/src/DSi_NAND.h index 699397bd..104845d5 100644 --- a/src/DSi_NAND.h +++ b/src/DSi_NAND.h @@ -71,7 +71,7 @@ private: u32 ReadFATBlock(u64 addr, u32 len, u8* buf); u32 WriteFATBlock(u64 addr, u32 len, const u8* buf); bool ESEncrypt(u8* data, u32 len) const; - bool ESDecrypt(u8* data, u32 len); + bool ESDecrypt(u8* data, u32 len) const; Platform::FileHandle* CurFile = nullptr; DSiKey eMMC_CID; u64 ConsoleID; diff --git a/src/DSi_NDMA.h b/src/DSi_NDMA.h index 7e87da7b..fb34dbdf 100644 --- a/src/DSi_NDMA.h +++ b/src/DSi_NDMA.h @@ -44,12 +44,12 @@ public: void Run9(); void Run7(); - bool IsInMode(u32 mode) + bool IsInMode(u32 mode) const { return ((mode == StartMode) && (Cnt & 0x80000000)); } - bool IsRunning() { return Running!=0; } + bool IsRunning() const { return Running!=0; } void StartIfNeeded(u32 mode) { diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp index 9e006e2c..a6177dec 100644 --- a/src/DSi_NWifi.cpp +++ b/src/DSi_NWifi.cpp @@ -165,13 +165,13 @@ void DSi_NWifi::Reset() for (int i = 0; i < 9; i++) Mailbox[i].Clear(); - const Firmware* fw = DSi.SPI.GetFirmware(); + const Firmware& fw = DSi.SPI.GetFirmware(); - MacAddress mac = fw->GetHeader().MacAddr; + MacAddress mac = fw.GetHeader().MacAddr; Log(LogLevel::Info, "NWifi MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - Firmware::WifiBoard type = fw->GetHeader().WifiBoard; + Firmware::WifiBoard type = fw.GetHeader().WifiBoard; switch (type) { case Firmware::WifiBoard::W015: // AR6002 diff --git a/src/DSi_SD.cpp b/src/DSi_SD.cpp index 4cbf595d..72fe3756 100644 --- a/src/DSi_SD.cpp +++ b/src/DSi_SD.cpp @@ -18,6 +18,7 @@ #include #include +#include "Args.h" #include "DSi.h" #include "DSi_SD.h" #include "DSi_NAND.h" @@ -26,6 +27,10 @@ namespace melonDS { +using std::holds_alternative; +using std::unique_ptr; +using std::get_if; +using std::get; using namespace Platform; // observed IRQ behavior during transfers @@ -57,36 +62,38 @@ enum }; -DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, u32 num) : DSi(dsi) +DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional&& sdcard) noexcept : DSi(dsi), Num(0) { - Num = num; - - DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, + DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX)); - DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, + DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); - Ports[0] = nullptr; + Ports[0] = sdcard ? std::make_unique(DSi, this, std::move(*sdcard)) : nullptr; + sdcard = std::nullopt; // to ensure that sdcard isn't left with a moved-from object + Ports[1] = std::make_unique(DSi, this, std::move(nand)); +} + +// Creates an SDIO host +DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi) noexcept : DSi(dsi), Num(1) +{ + DSi.RegisterEventFunc(Event_DSi_SDIOTransfer , + Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX)); + DSi.RegisterEventFunc(Event_DSi_SDIOTransfer, + Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); + + Ports[0] = std::make_unique(DSi, this); Ports[1] = nullptr; } DSi_SDHost::~DSi_SDHost() { - if (Ports[0]) delete Ports[0]; - if (Ports[1]) delete Ports[1]; - DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, Transfer_TX); DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, Transfer_RX); -} -void DSi_SDHost::CloseHandles() -{ - if (Ports[0]) delete Ports[0]; - if (Ports[1]) delete Ports[1]; - Ports[0] = nullptr; - Ports[1] = nullptr; + // unique_ptr's destructor will clean up the ports } void DSi_SDHost::Reset() @@ -129,48 +136,70 @@ void DSi_SDHost::Reset() TXReq = false; - CloseHandles(); + if (Ports[0]) Ports[0]->Reset(); + if (Ports[1]) Ports[1]->Reset(); +} - if (Num == 0) +FATStorage* DSi_SDHost::GetSDCard() noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[0].get())->GetSDCard(); +} + +const FATStorage* DSi_SDHost::GetSDCard() const noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[0].get())->GetSDCard(); +} + +DSi_NAND::NANDImage* DSi_SDHost::GetNAND() noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[1].get())->GetNAND(); +} + +const DSi_NAND::NANDImage* DSi_SDHost::GetNAND() const noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[1].get())->GetNAND(); +} + +void DSi_SDHost::SetSDCard(FATStorage&& sdcard) noexcept +{ + if (Num != 0) return; + + static_cast(Ports[0].get())->SetSDCard(std::move(sdcard)); +} + +void DSi_SDHost::SetSDCard(std::optional&& sdcard) noexcept +{ + if (Num != 0) return; + + if (sdcard) { - DSi_MMCStorage* sd; - DSi_MMCStorage* mmc; - - if (Platform::GetConfigBool(Platform::DSiSD_Enable)) + if (!Ports[0]) { - std::string folderpath; - if (Platform::GetConfigBool(Platform::DSiSD_FolderSync)) - folderpath = Platform::GetConfigString(Platform::DSiSD_FolderPath); - else - folderpath = ""; - - sd = new DSi_MMCStorage(this, - false, - Platform::GetConfigString(Platform::DSiSD_ImagePath), - (u64)Platform::GetConfigInt(Platform::DSiSD_ImageSize) * 1024 * 1024, - Platform::GetConfigBool(Platform::DSiSD_ReadOnly), - folderpath); - u8 sd_cid[16] = {0xBD, 0x12, 0x34, 0x56, 0x78, 0x03, 0x4D, 0x30, 0x30, 0x46, 0x50, 0x41, 0x00, 0x00, 0x15, 0x00}; - sd->SetCID(sd_cid); + Ports[0] = std::make_unique(DSi, this, std::move(*sdcard)); } else - sd = nullptr; - - mmc = new DSi_MMCStorage(this, *DSi.NANDImage); - mmc->SetCID(DSi.NANDImage->GetEMMCID().data()); - - Ports[0] = sd; - Ports[1] = mmc; + { + static_cast(Ports[0].get())->SetSDCard(std::move(*sdcard)); + } } else { - DSi_NWifi* nwifi = new DSi_NWifi(DSi, this); - - Ports[0] = nwifi; + Ports[0] = nullptr; } - if (Ports[0]) Ports[0]->Reset(); - if (Ports[1]) Ports[1]->Reset(); + sdcard = std::nullopt; + // a moved-from optional isn't empty, it contains a moved-from object +} + +void DSi_SDHost::SetNAND(DSi_NAND::NANDImage&& nand) noexcept +{ + if (Num != 0) return; + + static_cast(Ports[1].get())->SetNAND(std::move(nand)); } void DSi_SDHost::DoSavestate(Savestate* file) @@ -261,7 +290,7 @@ void DSi_SDHost::SetCardIRQ() if (!(CardIRQCtl & (1<<0))) return; u16 oldflags = CardIRQStatus & ~CardIRQMask; - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev->IRQ) CardIRQStatus |= (1<<0); else CardIRQStatus &= ~(1<<0); @@ -307,7 +336,7 @@ void DSi_SDHost::FinishRX(u32 param) SetIRQ(24); } -u32 DSi_SDHost::DataRX(u8* data, u32 len) +u32 DSi_SDHost::DataRX(const u8* data, u32 len) { if (len != BlockLen16) { Log(LogLevel::Warn, "!! BAD BLOCKLEN\n"); len = BlockLen16; } @@ -332,7 +361,7 @@ u32 DSi_SDHost::DataRX(u8* data, u32 len) void DSi_SDHost::FinishTX(u32 param) { - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (BlockCountInternal == 0) { @@ -411,7 +440,7 @@ u32 DSi_SDHost::DataTX(u8* data, u32 len) return len; } -u32 DSi_SDHost::GetTransferrableLen(u32 len) +u32 DSi_SDHost::GetTransferrableLen(u32 len) const { if (len > BlockLen16) len = BlockLen16; // checkme return len; @@ -419,7 +448,7 @@ u32 DSi_SDHost::GetTransferrableLen(u32 len) void DSi_SDHost::CheckRX() { - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); CheckSwapFIFO(); @@ -459,7 +488,7 @@ void DSi_SDHost::CheckTX() return; } - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev) dev->ContinueTransfer(); } @@ -550,7 +579,6 @@ u16 DSi_SDHost::ReadFIFO16() return 0; } - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; u16 ret = DataFIFO[f].Read(); if (DataFIFO[f].IsEmpty()) @@ -571,7 +599,6 @@ u32 DSi_SDHost::ReadFIFO32() return 0; } - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; u32 ret = DataFIFO32.Read(); if (DataFIFO32.IsEmpty()) @@ -593,7 +620,7 @@ void DSi_SDHost::Write(u32 addr, u16 val) Command = val; u8 cmd = Command & 0x3F; - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; + DSi_SDDevice* dev = Ports[PortSelect & 0x1].get(); if (dev) { // CHECKME @@ -707,7 +734,6 @@ void DSi_SDHost::Write(u32 addr, u16 val) void DSi_SDHost::WriteFIFO16(u16 val) { - DSi_SDDevice* dev = Ports[PortSelect & 0x1]; u32 f = CurFIFO; if (DataFIFO[f].IsFull()) { @@ -780,34 +806,23 @@ void DSi_SDHost::CheckSwapFIFO() #define MMC_DESC (Internal?"NAND":"SDcard") -DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, DSi_NAND::NANDImage& nand) - : DSi_SDDevice(host), Internal(true), NAND(&nand), SD(nullptr) +DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, DSi_NAND::NANDImage&& nand) noexcept + : DSi_SDDevice(host), DSi(dsi), Storage(std::move(nand)) { ReadOnly = false; + SetCID(get(Storage).GetEMMCID().data()); } -DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, bool internal, const std::string& filename, u64 size, bool readonly, const std::string& sourcedir) - : DSi_SDDevice(host) +DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, FATStorage&& sdcard) noexcept + : DSi_SDDevice(host), DSi(dsi), Storage(std::move(sdcard)) { - Internal = internal; - NAND = nullptr; - - SD = new FATStorage(filename, size, readonly, sourcedir); - SD->Open(); - - ReadOnly = readonly; + ReadOnly = get(Storage).IsReadOnly(); + SetCID(DSiSDCardCID); } -DSi_MMCStorage::~DSi_MMCStorage() -{ - if (SD) - { - SD->Close(); - delete SD; - } - - // Do not close the NANDImage, it's not owned by this object -} +// The FATStorage or NANDImage is owned by this object; +// std::variant's destructor will clean it up. +DSi_MMCStorage::~DSi_MMCStorage() = default; void DSi_MMCStorage::Reset() { @@ -836,7 +851,7 @@ void DSi_MMCStorage::Reset() void DSi_MMCStorage::DoSavestate(Savestate* file) { - file->Section(Internal ? "NAND" : "SDCR"); + file->Section(holds_alternative(Storage) ? "NAND" : "SDCR"); file->VarArray(CID, 16); file->VarArray(CSD, 16); @@ -871,7 +886,7 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) case 1: // SEND_OP_COND // CHECKME!! // also TODO: it's different for the SD card - if (Internal) + if (std::holds_alternative(Storage)) { param &= ~(1<<30); OCR &= 0xBF000000; @@ -895,7 +910,7 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) return; case 3: // get/set RCA - if (Internal) + if (holds_alternative(Storage)) { RCA = param >> 16; Host->SendResponse(CSR|0x10000, true); // huh?? @@ -930,7 +945,8 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param) case 12: // stop operation SetState(0x04); - if (NAND) FileFlush(NAND->GetFile()); + if (auto* nand = get_if(&Storage)) + FileFlush(nand->GetFile()); RWCommand = 0; Host->SendResponse(CSR, true); return; @@ -1011,7 +1027,7 @@ void DSi_MMCStorage::SendACMD(u8 cmd, u32 param) // DSi boot2 sets this to 0x40100000 (hardcoded) // then has two codepaths depending on whether bit30 did get set // is it settable at all on the MMC? probably not. - if (Internal) param &= ~(1<<30); + if (holds_alternative(Storage)) param &= ~(1<<30); OCR &= 0xBF000000; OCR |= (param & 0x40FFFFFF); Host->SendResponse(OCR, true); @@ -1057,14 +1073,14 @@ u32 DSi_MMCStorage::ReadBlock(u64 addr) len = Host->GetTransferrableLen(len); u8 data[0x200]; - if (SD) + if (auto* sd = std::get_if(&Storage)) { - SD->ReadSectors((u32)(addr >> 9), 1, data); + sd->ReadSectors((u32)(addr >> 9), 1, data); } - else if (NAND) + else if (auto* nand = std::get_if(&Storage)) { - FileSeek(NAND->GetFile(), addr, FileSeekOrigin::Start); - FileRead(&data[addr & 0x1FF], 1, len, NAND->GetFile()); + FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start); + FileRead(&data[addr & 0x1FF], 1, len, nand->GetFile()); } return Host->DataRX(&data[addr & 0x1FF], len); @@ -1078,23 +1094,23 @@ u32 DSi_MMCStorage::WriteBlock(u64 addr) u8 data[0x200]; if (len < 0x200) { - if (SD) + if (auto* sd = get_if(&Storage)) { - SD->ReadSectors((u32)(addr >> 9), 1, data); + sd->ReadSectors((u32)(addr >> 9), 1, data); } } if ((len = Host->DataTX(&data[addr & 0x1FF], len))) { if (!ReadOnly) { - if (SD) + if (auto* sd = get_if(&Storage)) { - SD->WriteSectors((u32)(addr >> 9), 1, data); + sd->WriteSectors((u32)(addr >> 9), 1, data); } - else if (NAND) + else if (auto* nand = get_if(&Storage)) { - FileSeek(NAND->GetFile(), addr, FileSeekOrigin::Start); - FileWrite(&data[addr & 0x1FF], 1, len, NAND->GetFile()); + FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start); + FileWrite(&data[addr & 0x1FF], 1, len, nand->GetFile()); } } } diff --git a/src/DSi_SD.h b/src/DSi_SD.h index 17ba8d30..29620dc5 100644 --- a/src/DSi_SD.h +++ b/src/DSi_SD.h @@ -20,28 +20,30 @@ #define DSI_SD_H #include +#include #include "FIFO.h" #include "FATStorage.h" +#include "DSi_NAND.h" #include "Savestate.h" namespace melonDS { -namespace DSi_NAND -{ - class NANDImage; -} - class DSi_SDDevice; class DSi; +using Nothing = std::monostate; +using DSiStorage = std::variant; class DSi_SDHost { public: - DSi_SDHost(melonDS::DSi& dsi, u32 num); + /// Creates an SDMMC host. + DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional&& sdcard = std::nullopt) noexcept; + + /// Creates an SDIO host + explicit DSi_SDHost(melonDS::DSi& dsi) noexcept; ~DSi_SDHost(); - void CloseHandles(); void Reset(); void DoSavestate(Savestate* file); @@ -49,9 +51,9 @@ public: void FinishRX(u32 param); void FinishTX(u32 param); void SendResponse(u32 val, bool last); - u32 DataRX(u8* data, u32 len); + u32 DataRX(const u8* data, u32 len); u32 DataTX(u8* data, u32 len); - u32 GetTransferrableLen(u32 len); + u32 GetTransferrableLen(u32 len) const; void CheckRX(); void CheckTX(); @@ -59,6 +61,15 @@ public: void SetCardIRQ(); + [[nodiscard]] FATStorage* GetSDCard() noexcept; + [[nodiscard]] const FATStorage* GetSDCard() const noexcept; + [[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept; + [[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept; + + void SetSDCard(FATStorage&& sdcard) noexcept; + void SetSDCard(std::optional&& sdcard) noexcept; + void SetNAND(DSi_NAND::NANDImage&& nand) noexcept; + u16 Read(u32 addr); void Write(u32 addr, u16 val); u16 ReadFIFO16(); @@ -96,7 +107,7 @@ private: u32 Param; u16 ResponseBuffer[8]; - DSi_SDDevice* Ports[2]; + std::array, 2> Ports {}; u32 CurFIFO; // FIFO accessible for read/write FIFO DataFIFO[2]; @@ -134,25 +145,53 @@ protected: class DSi_MMCStorage : public DSi_SDDevice { public: - DSi_MMCStorage(DSi_SDHost* host, DSi_NAND::NANDImage& nand); - DSi_MMCStorage(DSi_SDHost* host, bool internal, const std::string& filename, u64 size, bool readonly, const std::string& sourcedir); - ~DSi_MMCStorage(); + DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, DSi_NAND::NANDImage&& nand) noexcept; + DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, FATStorage&& sdcard) noexcept; + ~DSi_MMCStorage() override; - void Reset(); + [[nodiscard]] FATStorage* GetSDCard() noexcept { return std::get_if(&Storage); } + [[nodiscard]] const FATStorage* GetSDCard() const noexcept { return std::get_if(&Storage); } + [[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept { return std::get_if(&Storage); } + [[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept { return std::get_if(&Storage); } - void DoSavestate(Savestate* file); + void SetNAND(DSi_NAND::NANDImage&& nand) noexcept { Storage = std::move(nand); } + void SetSDCard(FATStorage&& sdcard) noexcept { Storage = std::move(sdcard); } + void SetSDCard(std::optional&& sdcard) noexcept + { + if (sdcard) + { // If we're setting a new SD card... + Storage = std::move(*sdcard); + sdcard = std::nullopt; + } + else + { + Storage = Nothing(); + } + } + + void SetStorage(DSiStorage&& storage) noexcept + { + Storage = std::move(storage); + storage = Nothing(); + // not sure if a moved-from variant is empty or contains a moved-from object; + // better to be safe than sorry + } + + void Reset() override; + + void DoSavestate(Savestate* file) override; void SetCID(const u8* cid) { memcpy(CID, cid, sizeof(CID)); } - void SendCMD(u8 cmd, u32 param); + void SendCMD(u8 cmd, u32 param) override; void SendACMD(u8 cmd, u32 param); - void ContinueTransfer(); + void ContinueTransfer() override; private: - bool Internal; - DSi_NAND::NANDImage* NAND; - FATStorage* SD; + static constexpr u8 DSiSDCardCID[16] = {0xBD, 0x12, 0x34, 0x56, 0x78, 0x03, 0x4D, 0x30, 0x30, 0x46, 0x50, 0x41, 0x00, 0x00, 0x15, 0x00}; + melonDS::DSi& DSi; + DSiStorage Storage; u8 CID[16]; u8 CSD[16]; diff --git a/src/DSi_SPI_TSC.cpp b/src/DSi_SPI_TSC.cpp index 6c7a15c7..d515db9f 100644 --- a/src/DSi_SPI_TSC.cpp +++ b/src/DSi_SPI_TSC.cpp @@ -121,7 +121,7 @@ void DSi_TSC::SetTouchCoords(u16 x, u16 y) } } -void DSi_TSC::MicInputFrame(s16* data, int samples) +void DSi_TSC::MicInputFrame(const s16* data, int samples) { if (TSCMode == 0x00) return TSC::MicInputFrame(data, samples); diff --git a/src/DSi_SPI_TSC.h b/src/DSi_SPI_TSC.h index 47777da5..d1a71063 100644 --- a/src/DSi_SPI_TSC.h +++ b/src/DSi_SPI_TSC.h @@ -40,7 +40,7 @@ public: void SetMode(u8 mode); void SetTouchCoords(u16 x, u16 y) override; - void MicInputFrame(s16* data, int samples) override; + void MicInputFrame(const s16* data, int samples) override; void Write(u8 val) override; void Release() override; diff --git a/src/FATStorage.cpp b/src/FATStorage.cpp index cd0a03c8..9a1a9ad4 100644 --- a/src/FATStorage.cpp +++ b/src/FATStorage.cpp @@ -29,39 +29,73 @@ namespace melonDS { namespace fs = std::filesystem; using namespace Platform; +using std::string; -FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::string& sourcedir) +FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional& sourcedir) : + FATStorage(FATStorageArgs { filename, size, readonly, sourcedir }) { - ReadOnly = readonly; - Load(filename, size, sourcedir); +} - File = nullptr; +FATStorage::FATStorage(const FATStorageArgs& args) noexcept : + FATStorage(args.Filename, args.Size, args.ReadOnly, args.SourceDir) +{ +} + +FATStorage::FATStorage(FATStorageArgs&& args) noexcept : + FilePath(std::move(args.Filename)), + FileSize(args.Size), + ReadOnly(args.ReadOnly), + SourceDir(std::move(args.SourceDir)) +{ + Load(FilePath, FileSize, SourceDir); + + File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); +} + +FATStorage::FATStorage(FATStorage&& other) noexcept +{ + FilePath = std::move(other.FilePath); + IndexPath = std::move(other.IndexPath); + SourceDir = std::move(other.SourceDir); + ReadOnly = other.ReadOnly; + File = other.File; + FileSize = other.FileSize; + DirIndex = std::move(other.DirIndex); + FileIndex = std::move(other.FileIndex); + + other.File = nullptr; +} + +FATStorage& FATStorage::operator=(FATStorage&& other) noexcept +{ + if (this != &other) + { + if (File) + CloseFile(File); + + FilePath = std::move(other.FilePath); + IndexPath = std::move(other.IndexPath); + SourceDir = std::move(other.SourceDir); + ReadOnly = other.ReadOnly; + File = other.File; + FileSize = other.FileSize; + DirIndex = std::move(other.DirIndex); + FileIndex = std::move(other.FileIndex); + + other.File = nullptr; + } + + return *this; } FATStorage::~FATStorage() { if (!ReadOnly) Save(); -} - -bool FATStorage::Open() -{ - File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); - if (!File) - { - return false; - } - - return true; -} - -void FATStorage::Close() -{ if (File) CloseFile(File); File = nullptr; } - bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) { if (!File) return false; @@ -104,18 +138,65 @@ bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len) return nwrite==len; } +u32 FATStorage::ReadFile(const std::string& path, u32 start, u32 len, u8* data) +{ + if (!File) return false; + if (FF_File) return false; -u32 FATStorage::ReadSectors(u32 start, u32 num, u8* data) + FF_File = File; + FF_FileSize = FileSize; + ff_disk_open(FF_ReadStorage, FF_WriteStorage, (LBA_t)(FileSize>>9)); + + FRESULT res; + FATFS fs; + + res = f_mount(&fs, "0:", 1); + if (res != FR_OK) + { + ff_disk_close(); + FF_File = nullptr; + return false; + } + + std::string prefixedPath("0:/"); + prefixedPath += path; + FF_FIL file; + res = f_open(&file, prefixedPath.c_str(), FA_READ); + if (res != FR_OK) + { + f_unmount("0:"); + ff_disk_close(); + FF_File = nullptr; + return false; + } + + u32 nread; + f_lseek(&file, start); + f_read(&file, data, len, &nread); + f_close(&file); + + f_unmount("0:"); + ff_disk_close(); + FF_File = nullptr; + return nread; +} + +u32 FATStorage::ReadSectors(u32 start, u32 num, u8* data) const { return ReadSectorsInternal(File, FileSize, start, num, data); } -u32 FATStorage::WriteSectors(u32 start, u32 num, u8* data) +u32 FATStorage::WriteSectors(u32 start, u32 num, const u8* data) { if (ReadOnly) return 0; return WriteSectorsInternal(File, FileSize, start, num, data); } +u64 FATStorage::GetSectorCount() const +{ + return FileSize / 0x200; +} + FileHandle* FATStorage::FF_File; u64 FATStorage::FF_FileSize; @@ -907,7 +988,7 @@ bool FATStorage::ImportDirectory(const std::string& sourcedir) return true; } -u64 FATStorage::GetDirectorySize(fs::path sourcedir) +u64 FATStorage::GetDirectorySize(fs::path sourcedir) const { u64 ret = 0; u32 csize = 0x1000; // this is an estimate @@ -930,19 +1011,15 @@ u64 FATStorage::GetDirectorySize(fs::path sourcedir) return ret; } -bool FATStorage::Load(const std::string& filename, u64 size, const std::string& sourcedir) +bool FATStorage::Load(const std::string& filename, u64 size, const std::optional& sourcedir) { - FilePath = filename; - FileSize = size; - SourceDir = sourcedir; - - bool hasdir = !sourcedir.empty(); - if (hasdir) + bool hasdir = sourcedir && !sourcedir->empty(); + if (sourcedir) { - if (!fs::is_directory(fs::u8path(sourcedir))) + if (!fs::is_directory(fs::u8path(*sourcedir))) { hasdir = false; - SourceDir = ""; + SourceDir = std::nullopt; } } @@ -1005,7 +1082,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& { if (hasdir) { - FileSize = GetDirectorySize(fs::u8path(sourcedir)); + FileSize = GetDirectorySize(fs::u8path(*sourcedir)); FileSize += 0x8000000ULL; // 128MB leeway // make it a power of two @@ -1054,7 +1131,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& if (res == FR_OK) { if (hasdir) - ImportDirectory(sourcedir); + ImportDirectory(*sourcedir); } f_unmount("0:"); @@ -1068,9 +1145,9 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string& bool FATStorage::Save() { - if (SourceDir.empty()) - { - return true; + if (!SourceDir) + { // If we're not syncing the SD card image to a host directory... + return true; // Not an error. } FF_File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); @@ -1094,7 +1171,7 @@ bool FATStorage::Save() return false; } - ExportChanges(SourceDir); + ExportChanges(*SourceDir); SaveIndex(); diff --git a/src/FATStorage.h b/src/FATStorage.h index 2bdafad8..48a411b0 100644 --- a/src/FATStorage.h +++ b/src/FATStorage.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "Platform.h" @@ -30,24 +31,44 @@ namespace melonDS { +/// Contains information necessary to load an SD card image. +/// The intended use case is for loading homebrew NDS ROMs; +/// you won't know that a ROM is homebrew until you parse it, +/// so if you load the SD card before the ROM +/// then you might end up discarding it. +struct FATStorageArgs +{ + std::string Filename; + u64 Size; + bool ReadOnly; + std::optional SourceDir; +}; + class FATStorage { public: - FATStorage(const std::string& filename, u64 size, bool readonly, const std::string& sourcedir); + FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional& sourcedir = std::nullopt); + explicit FATStorage(const FATStorageArgs& args) noexcept; + explicit FATStorage(FATStorageArgs&& args) noexcept; + FATStorage(FATStorage&& other) noexcept; + FATStorage(const FATStorage& other) = delete; + FATStorage& operator=(const FATStorage& other) = delete; + FATStorage& operator=(FATStorage&& other) noexcept; ~FATStorage(); - bool Open(); - void Close(); - bool InjectFile(const std::string& path, u8* data, u32 len); + u32 ReadFile(const std::string& path, u32 start, u32 len, u8* data); - u32 ReadSectors(u32 start, u32 num, u8* data); - u32 WriteSectors(u32 start, u32 num, u8* data); + u32 ReadSectors(u32 start, u32 num, u8* data) const; + u32 WriteSectors(u32 start, u32 num, const u8* data); + + [[nodiscard]] bool IsReadOnly() const noexcept { return ReadOnly; } + u64 GetSectorCount() const; private: std::string FilePath; std::string IndexPath; - std::string SourceDir; + std::optional SourceDir; bool ReadOnly; Platform::FileHandle* File; @@ -74,9 +95,9 @@ private: void CleanupDirectory(const std::string& sourcedir, const std::string& path, int level); bool ImportFile(const std::string& path, std::filesystem::path in); bool ImportDirectory(const std::string& sourcedir); - u64 GetDirectorySize(std::filesystem::path sourcedir); + u64 GetDirectorySize(std::filesystem::path sourcedir) const; - bool Load(const std::string& filename, u64 size, const std::string& sourcedir); + bool Load(const std::string& filename, u64 size, const std::optional& sourcedir); bool Save(); typedef struct diff --git a/src/FIFO.h b/src/FIFO.h index cbff4ab9..026c2c7f 100644 --- a/src/FIFO.h +++ b/src/FIFO.h @@ -74,12 +74,12 @@ public: return ret; } - T Peek() + T Peek() const { return Entries[ReadPos]; } - T Peek(u32 offset) + T Peek(u32 offset) const { u32 pos = ReadPos + offset; if (pos >= NumEntries) @@ -88,11 +88,11 @@ public: return Entries[pos]; } - u32 Level() { return NumOccupied; } - bool IsEmpty() { return NumOccupied == 0; } - bool IsFull() { return NumOccupied >= NumEntries; } + u32 Level() const { return NumOccupied; } + bool IsEmpty() const { return NumOccupied == 0; } + bool IsFull() const { return NumOccupied >= NumEntries; } - bool CanFit(u32 num) { return ((NumOccupied + num) <= NumEntries); } + bool CanFit(u32 num) const { return ((NumOccupied + num) <= NumEntries); } private: T Entries[NumEntries] = {0}; @@ -164,12 +164,12 @@ public: return ret; } - T Peek() + T Peek() const { return Entries[ReadPos]; } - T Peek(u32 offset) + T Peek(u32 offset) const { u32 pos = ReadPos + offset; if (pos >= NumEntries) @@ -178,11 +178,11 @@ public: return Entries[pos]; } - u32 Level() { return NumOccupied; } - bool IsEmpty() { return NumOccupied == 0; } - bool IsFull() { return NumOccupied >= NumEntries; } + u32 Level() const { return NumOccupied; } + bool IsEmpty() const { return NumOccupied == 0; } + bool IsFull() const { return NumOccupied >= NumEntries; } - bool CanFit(u32 num) { return ((NumOccupied + num) <= NumEntries); } + bool CanFit(u32 num) const { return ((NumOccupied + num) <= NumEntries); } private: u32 NumEntries; diff --git a/src/FreeBIOS.cpp b/src/FreeBIOS.cpp index 7eef18b7..f5d8f366 100644 --- a/src/FreeBIOS.cpp +++ b/src/FreeBIOS.cpp @@ -28,10 +28,10 @@ namespace melonDS { -unsigned char bios_arm7_bin[] = { +std::array bios_arm7_bin = { 0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea, 0x1a, 0x04, 0x00, 0xea, 0x19, 0x04, 0x00, 0xea, 0x18, 0x04, 0x00, 0xea, - 0xe3, 0x07, 0x00, 0xea, 0x16, 0x04, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, + 0xe5, 0x07, 0x00, 0xea, 0x16, 0x04, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -385,17 +385,17 @@ unsigned char bios_arm7_bin[] = { 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, - 0xd4, 0x1f, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, + 0xdc, 0x1f, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x5c, 0x11, 0x00, 0x00, 0x90, 0x11, 0x00, 0x00, 0x88, 0x11, 0x00, 0x00, 0x4c, 0x11, 0x00, 0x00, 0xbc, 0x11, 0x00, 0x00, 0xcc, 0x11, 0x00, 0x00, 0xe8, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x44, 0x12, 0x00, 0x00, 0xc4, 0x12, 0x00, 0x00, 0x04, 0x13, 0x00, 0x00, 0x50, 0x13, 0x00, 0x00, - 0xe8, 0x13, 0x00, 0x00, 0xf0, 0x13, 0x00, 0x00, 0x84, 0x14, 0x00, 0x00, - 0x00, 0x15, 0x00, 0x00, 0xac, 0x15, 0x00, 0x00, 0xb0, 0x15, 0x00, 0x00, - 0xb0, 0x15, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, - 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x88, 0x16, 0x00, 0x00, - 0x9c, 0x1c, 0x00, 0x00, 0xc4, 0x1f, 0x00, 0x00, 0x84, 0x1f, 0x00, 0x00, - 0xa0, 0x1f, 0x00, 0x00, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, + 0xf0, 0x13, 0x00, 0x00, 0xf8, 0x13, 0x00, 0x00, 0x8c, 0x14, 0x00, 0x00, + 0x08, 0x15, 0x00, 0x00, 0xb4, 0x15, 0x00, 0x00, 0xb8, 0x15, 0x00, 0x00, + 0xb8, 0x15, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, + 0x2c, 0x11, 0x00, 0x00, 0x2c, 0x11, 0x00, 0x00, 0x90, 0x16, 0x00, 0x00, + 0xa4, 0x1c, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x8c, 0x1f, 0x00, 0x00, + 0xa8, 0x1f, 0x00, 0x00, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x20, 0xa0, 0xe3, 0x01, 0x23, 0xc0, 0xe5, @@ -442,205 +442,206 @@ unsigned char bios_arm7_bin[] = { 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, - 0xa2, 0x20, 0xb0, 0xe1, 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, + 0xff, 0x04, 0xc0, 0xe3, 0xff, 0x08, 0xc0, 0xe3, 0xa2, 0x20, 0xb0, 0xe1, + 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, 0x80, 0x50, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, + 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, + 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x53, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0xa3, 0x53, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, - 0x20, 0x00, 0xbd, 0xe8, 0x50, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, - 0x4e, 0xff, 0xff, 0xea, 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, - 0x02, 0xc0, 0xd2, 0xe5, 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, - 0xa4, 0x2f, 0xa0, 0xe1, 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, - 0x15, 0x5c, 0xa0, 0xe1, 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, - 0x00, 0x70, 0xa0, 0xe3, 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, - 0x83, 0x31, 0xa0, 0x01, 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, - 0x04, 0x00, 0x5c, 0xe3, 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, - 0x01, 0x60, 0xd0, 0x04, 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, - 0x36, 0x6c, 0xa0, 0xe1, 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, - 0x04, 0x90, 0x89, 0x10, 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, - 0x20, 0x00, 0x58, 0xe3, 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, - 0x00, 0x80, 0xa0, 0x03, 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, - 0xe0, 0x03, 0xbd, 0xe8, 0x29, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xb0, 0xe1, 0x26, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x16, 0xff, 0xff, 0x0a, - 0x01, 0xe0, 0x5e, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, - 0xed, 0xff, 0xff, 0x5a, 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x0d, 0xff, 0xff, 0x0a, - 0x83, 0x30, 0xb0, 0xe1, 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, - 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, 0x07, 0xff, 0xff, 0x0a, - 0x20, 0x00, 0x2d, 0xe9, 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, - 0x01, 0x50, 0xdc, 0x04, 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, - 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, - 0x20, 0x00, 0xbd, 0x08, 0xf0, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, - 0xf4, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, - 0xe5, 0xff, 0xff, 0xea, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, - 0x01, 0x40, 0xd0, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, + 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, 0x20, 0x00, 0xbd, 0xe8, + 0x4e, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, 0x4c, 0xff, 0xff, 0xea, + 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, 0x02, 0xc0, 0xd2, 0xe5, + 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, 0xa4, 0x2f, 0xa0, 0xe1, + 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, 0x15, 0x5c, 0xa0, 0xe1, + 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, 0x00, 0x70, 0xa0, 0xe3, + 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, 0x83, 0x31, 0xa0, 0x01, + 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, 0x04, 0x00, 0x5c, 0xe3, + 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, 0x01, 0x60, 0xd0, 0x04, + 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, 0x36, 0x6c, 0xa0, 0xe1, + 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, 0x04, 0x90, 0x89, 0x10, + 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, 0x20, 0x00, 0x58, 0xe3, + 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, 0x00, 0x80, 0xa0, 0x03, + 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, 0xe0, 0x03, 0xbd, 0xe8, + 0x27, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, + 0x24, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x14, 0xff, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xed, 0xff, 0xff, 0x5a, + 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x0b, 0xff, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, + 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x23, 0x24, 0xb0, 0xe1, 0x05, 0xff, 0xff, 0x0a, 0x20, 0x00, 0x2d, 0xe9, + 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xdc, 0x04, + 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, - 0xe2, 0xfe, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, - 0xd9, 0xff, 0xff, 0xea, 0xde, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xa0, 0xe1, 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, - 0x7f, 0x30, 0x03, 0xe2, 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, - 0x03, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, - 0xd3, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, - 0xf3, 0xff, 0xff, 0xea, 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0xcb, 0xfe, 0xff, 0x0a, - 0x01, 0x30, 0x53, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, - 0x00, 0x00, 0x24, 0x03, 0x48, 0x06, 0x6a, 0x09, 0x8c, 0x0c, 0xab, 0x0f, - 0xc8, 0x12, 0xe2, 0x15, 0xf9, 0x18, 0x0b, 0x1c, 0x1a, 0x1f, 0x23, 0x22, - 0x28, 0x25, 0x26, 0x28, 0x1f, 0x2b, 0x11, 0x2e, 0xfb, 0x30, 0xdf, 0x33, - 0xba, 0x36, 0x8c, 0x39, 0x56, 0x3c, 0x17, 0x3f, 0xce, 0x41, 0x7a, 0x44, - 0x1c, 0x47, 0xb4, 0x49, 0x3f, 0x4c, 0xbf, 0x4e, 0x33, 0x51, 0x9b, 0x53, - 0xf5, 0x55, 0x42, 0x58, 0x82, 0x5a, 0xb3, 0x5c, 0xd7, 0x5e, 0xeb, 0x60, - 0xf1, 0x62, 0xe8, 0x64, 0xcf, 0x66, 0xa6, 0x68, 0x6d, 0x6a, 0x23, 0x6c, - 0xc9, 0x6d, 0x5e, 0x6f, 0xe2, 0x70, 0x54, 0x72, 0xb5, 0x73, 0x04, 0x75, - 0x41, 0x76, 0x6b, 0x77, 0x84, 0x78, 0x89, 0x79, 0x7c, 0x7a, 0x5c, 0x7b, - 0x29, 0x7c, 0xe3, 0x7c, 0x89, 0x7d, 0x1d, 0x7e, 0x9c, 0x7e, 0x09, 0x7f, - 0x61, 0x7f, 0xa6, 0x7f, 0xd8, 0x7f, 0xf5, 0x7f, 0x00, 0x00, 0x80, 0xe0, - 0x8c, 0x10, 0x4f, 0xe2, 0xb0, 0x00, 0x91, 0xe1, 0xa4, 0xfe, 0xff, 0xea, - 0x00, 0x00, 0x3b, 0x00, 0x76, 0x00, 0xb2, 0x00, 0xed, 0x00, 0x28, 0x01, - 0x64, 0x01, 0x9f, 0x01, 0xdb, 0x01, 0x17, 0x02, 0x52, 0x02, 0x8e, 0x02, - 0xca, 0x02, 0x05, 0x03, 0x41, 0x03, 0x7d, 0x03, 0xb9, 0x03, 0xf5, 0x03, - 0x31, 0x04, 0x6e, 0x04, 0xaa, 0x04, 0xe6, 0x04, 0x22, 0x05, 0x5f, 0x05, - 0x9b, 0x05, 0xd8, 0x05, 0x14, 0x06, 0x51, 0x06, 0x8d, 0x06, 0xca, 0x06, - 0x07, 0x07, 0x43, 0x07, 0x80, 0x07, 0xbd, 0x07, 0xfa, 0x07, 0x37, 0x08, - 0x74, 0x08, 0xb1, 0x08, 0xef, 0x08, 0x2c, 0x09, 0x69, 0x09, 0xa7, 0x09, - 0xe4, 0x09, 0x21, 0x0a, 0x5f, 0x0a, 0x9c, 0x0a, 0xda, 0x0a, 0x18, 0x0b, - 0x56, 0x0b, 0x93, 0x0b, 0xd1, 0x0b, 0x0f, 0x0c, 0x4d, 0x0c, 0x8b, 0x0c, - 0xc9, 0x0c, 0x07, 0x0d, 0x45, 0x0d, 0x84, 0x0d, 0xc2, 0x0d, 0x00, 0x0e, - 0x3f, 0x0e, 0x7d, 0x0e, 0xbc, 0x0e, 0xfa, 0x0e, 0x39, 0x0f, 0x78, 0x0f, - 0xb6, 0x0f, 0xf5, 0x0f, 0x34, 0x10, 0x73, 0x10, 0xb2, 0x10, 0xf1, 0x10, - 0x30, 0x11, 0x6f, 0x11, 0xae, 0x11, 0xee, 0x11, 0x2d, 0x12, 0x6c, 0x12, - 0xac, 0x12, 0xeb, 0x12, 0x2b, 0x13, 0x6b, 0x13, 0xaa, 0x13, 0xea, 0x13, - 0x2a, 0x14, 0x6a, 0x14, 0xa9, 0x14, 0xe9, 0x14, 0x29, 0x15, 0x69, 0x15, - 0xaa, 0x15, 0xea, 0x15, 0x2a, 0x16, 0x6a, 0x16, 0xab, 0x16, 0xeb, 0x16, - 0x2c, 0x17, 0x6c, 0x17, 0xad, 0x17, 0xed, 0x17, 0x2e, 0x18, 0x6f, 0x18, - 0xb0, 0x18, 0xf0, 0x18, 0x31, 0x19, 0x72, 0x19, 0xb3, 0x19, 0xf5, 0x19, - 0x36, 0x1a, 0x77, 0x1a, 0xb8, 0x1a, 0xfa, 0x1a, 0x3b, 0x1b, 0x7d, 0x1b, - 0xbe, 0x1b, 0x00, 0x1c, 0x41, 0x1c, 0x83, 0x1c, 0xc5, 0x1c, 0x07, 0x1d, - 0x48, 0x1d, 0x8a, 0x1d, 0xcc, 0x1d, 0x0e, 0x1e, 0x51, 0x1e, 0x93, 0x1e, - 0xd5, 0x1e, 0x17, 0x1f, 0x5a, 0x1f, 0x9c, 0x1f, 0xdf, 0x1f, 0x21, 0x20, - 0x64, 0x20, 0xa6, 0x20, 0xe9, 0x20, 0x2c, 0x21, 0x6f, 0x21, 0xb2, 0x21, - 0xf5, 0x21, 0x38, 0x22, 0x7b, 0x22, 0xbe, 0x22, 0x01, 0x23, 0x44, 0x23, - 0x88, 0x23, 0xcb, 0x23, 0x0e, 0x24, 0x52, 0x24, 0x96, 0x24, 0xd9, 0x24, - 0x1d, 0x25, 0x61, 0x25, 0xa4, 0x25, 0xe8, 0x25, 0x2c, 0x26, 0x70, 0x26, - 0xb4, 0x26, 0xf8, 0x26, 0x3d, 0x27, 0x81, 0x27, 0xc5, 0x27, 0x0a, 0x28, - 0x4e, 0x28, 0x92, 0x28, 0xd7, 0x28, 0x1c, 0x29, 0x60, 0x29, 0xa5, 0x29, - 0xea, 0x29, 0x2f, 0x2a, 0x74, 0x2a, 0xb9, 0x2a, 0xfe, 0x2a, 0x43, 0x2b, - 0x88, 0x2b, 0xcd, 0x2b, 0x13, 0x2c, 0x58, 0x2c, 0x9d, 0x2c, 0xe3, 0x2c, - 0x28, 0x2d, 0x6e, 0x2d, 0xb4, 0x2d, 0xf9, 0x2d, 0x3f, 0x2e, 0x85, 0x2e, - 0xcb, 0x2e, 0x11, 0x2f, 0x57, 0x2f, 0x9d, 0x2f, 0xe3, 0x2f, 0x2a, 0x30, - 0x70, 0x30, 0xb6, 0x30, 0xfd, 0x30, 0x43, 0x31, 0x8a, 0x31, 0xd0, 0x31, - 0x17, 0x32, 0x5e, 0x32, 0xa5, 0x32, 0xec, 0x32, 0x32, 0x33, 0x79, 0x33, - 0xc1, 0x33, 0x08, 0x34, 0x4f, 0x34, 0x96, 0x34, 0xdd, 0x34, 0x25, 0x35, - 0x6c, 0x35, 0xb4, 0x35, 0xfb, 0x35, 0x43, 0x36, 0x8b, 0x36, 0xd3, 0x36, - 0x1a, 0x37, 0x62, 0x37, 0xaa, 0x37, 0xf2, 0x37, 0x3a, 0x38, 0x83, 0x38, - 0xcb, 0x38, 0x13, 0x39, 0x5c, 0x39, 0xa4, 0x39, 0xed, 0x39, 0x35, 0x3a, - 0x7e, 0x3a, 0xc6, 0x3a, 0x0f, 0x3b, 0x58, 0x3b, 0xa1, 0x3b, 0xea, 0x3b, - 0x33, 0x3c, 0x7c, 0x3c, 0xc5, 0x3c, 0x0e, 0x3d, 0x58, 0x3d, 0xa1, 0x3d, - 0xea, 0x3d, 0x34, 0x3e, 0x7d, 0x3e, 0xc7, 0x3e, 0x11, 0x3f, 0x5a, 0x3f, - 0xa4, 0x3f, 0xee, 0x3f, 0x38, 0x40, 0x82, 0x40, 0xcc, 0x40, 0x16, 0x41, - 0x61, 0x41, 0xab, 0x41, 0xf5, 0x41, 0x40, 0x42, 0x8a, 0x42, 0xd5, 0x42, - 0x1f, 0x43, 0x6a, 0x43, 0xb5, 0x43, 0x00, 0x44, 0x4b, 0x44, 0x95, 0x44, - 0xe1, 0x44, 0x2c, 0x45, 0x77, 0x45, 0xc2, 0x45, 0x0d, 0x46, 0x59, 0x46, - 0xa4, 0x46, 0xf0, 0x46, 0x3b, 0x47, 0x87, 0x47, 0xd3, 0x47, 0x1e, 0x48, - 0x6a, 0x48, 0xb6, 0x48, 0x02, 0x49, 0x4e, 0x49, 0x9a, 0x49, 0xe6, 0x49, - 0x33, 0x4a, 0x7f, 0x4a, 0xcb, 0x4a, 0x18, 0x4b, 0x64, 0x4b, 0xb1, 0x4b, - 0xfe, 0x4b, 0x4a, 0x4c, 0x97, 0x4c, 0xe4, 0x4c, 0x31, 0x4d, 0x7e, 0x4d, - 0xcb, 0x4d, 0x18, 0x4e, 0x66, 0x4e, 0xb3, 0x4e, 0x00, 0x4f, 0x4e, 0x4f, - 0x9b, 0x4f, 0xe9, 0x4f, 0x36, 0x50, 0x84, 0x50, 0xd2, 0x50, 0x20, 0x51, - 0x6e, 0x51, 0xbc, 0x51, 0x0a, 0x52, 0x58, 0x52, 0xa6, 0x52, 0xf4, 0x52, - 0x43, 0x53, 0x91, 0x53, 0xe0, 0x53, 0x2e, 0x54, 0x7d, 0x54, 0xcc, 0x54, - 0x1a, 0x55, 0x69, 0x55, 0xb8, 0x55, 0x07, 0x56, 0x56, 0x56, 0xa5, 0x56, - 0xf4, 0x56, 0x44, 0x57, 0x93, 0x57, 0xe2, 0x57, 0x32, 0x58, 0x82, 0x58, - 0xd1, 0x58, 0x21, 0x59, 0x71, 0x59, 0xc1, 0x59, 0x10, 0x5a, 0x60, 0x5a, - 0xb0, 0x5a, 0x01, 0x5b, 0x51, 0x5b, 0xa1, 0x5b, 0xf1, 0x5b, 0x42, 0x5c, - 0x92, 0x5c, 0xe3, 0x5c, 0x34, 0x5d, 0x84, 0x5d, 0xd5, 0x5d, 0x26, 0x5e, - 0x77, 0x5e, 0xc8, 0x5e, 0x19, 0x5f, 0x6a, 0x5f, 0xbb, 0x5f, 0x0d, 0x60, - 0x5e, 0x60, 0xb0, 0x60, 0x01, 0x61, 0x53, 0x61, 0xa4, 0x61, 0xf6, 0x61, - 0x48, 0x62, 0x9a, 0x62, 0xec, 0x62, 0x3e, 0x63, 0x90, 0x63, 0xe2, 0x63, - 0x34, 0x64, 0x87, 0x64, 0xd9, 0x64, 0x2c, 0x65, 0x7e, 0x65, 0xd1, 0x65, - 0x24, 0x66, 0x76, 0x66, 0xc9, 0x66, 0x1c, 0x67, 0x6f, 0x67, 0xc2, 0x67, - 0x15, 0x68, 0x69, 0x68, 0xbc, 0x68, 0x0f, 0x69, 0x63, 0x69, 0xb6, 0x69, - 0x0a, 0x6a, 0x5e, 0x6a, 0xb1, 0x6a, 0x05, 0x6b, 0x59, 0x6b, 0xad, 0x6b, - 0x01, 0x6c, 0x55, 0x6c, 0xaa, 0x6c, 0xfe, 0x6c, 0x52, 0x6d, 0xa7, 0x6d, - 0xfb, 0x6d, 0x50, 0x6e, 0xa4, 0x6e, 0xf9, 0x6e, 0x4e, 0x6f, 0xa3, 0x6f, - 0xf8, 0x6f, 0x4d, 0x70, 0xa2, 0x70, 0xf7, 0x70, 0x4d, 0x71, 0xa2, 0x71, - 0xf7, 0x71, 0x4d, 0x72, 0xa2, 0x72, 0xf8, 0x72, 0x4e, 0x73, 0xa4, 0x73, - 0xfa, 0x73, 0x50, 0x74, 0xa6, 0x74, 0xfc, 0x74, 0x52, 0x75, 0xa8, 0x75, - 0xff, 0x75, 0x55, 0x76, 0xac, 0x76, 0x02, 0x77, 0x59, 0x77, 0xb0, 0x77, - 0x07, 0x78, 0x5e, 0x78, 0xb4, 0x78, 0x0c, 0x79, 0x63, 0x79, 0xba, 0x79, - 0x11, 0x7a, 0x69, 0x7a, 0xc0, 0x7a, 0x18, 0x7b, 0x6f, 0x7b, 0xc7, 0x7b, - 0x1f, 0x7c, 0x77, 0x7c, 0xcf, 0x7c, 0x27, 0x7d, 0x7f, 0x7d, 0xd7, 0x7d, - 0x2f, 0x7e, 0x88, 0x7e, 0xe0, 0x7e, 0x38, 0x7f, 0x91, 0x7f, 0xea, 0x7f, - 0x42, 0x80, 0x9b, 0x80, 0xf4, 0x80, 0x4d, 0x81, 0xa6, 0x81, 0xff, 0x81, - 0x59, 0x82, 0xb2, 0x82, 0x0b, 0x83, 0x65, 0x83, 0xbe, 0x83, 0x18, 0x84, - 0x72, 0x84, 0xcb, 0x84, 0x25, 0x85, 0x7f, 0x85, 0xd9, 0x85, 0x33, 0x86, - 0x8e, 0x86, 0xe8, 0x86, 0x42, 0x87, 0x9d, 0x87, 0xf7, 0x87, 0x52, 0x88, - 0xac, 0x88, 0x07, 0x89, 0x62, 0x89, 0xbd, 0x89, 0x18, 0x8a, 0x73, 0x8a, - 0xce, 0x8a, 0x2a, 0x8b, 0x85, 0x8b, 0xe0, 0x8b, 0x3c, 0x8c, 0x97, 0x8c, - 0xf3, 0x8c, 0x4f, 0x8d, 0xab, 0x8d, 0x07, 0x8e, 0x63, 0x8e, 0xbf, 0x8e, - 0x1b, 0x8f, 0x77, 0x8f, 0xd4, 0x8f, 0x30, 0x90, 0x8c, 0x90, 0xe9, 0x90, - 0x46, 0x91, 0xa2, 0x91, 0xff, 0x91, 0x5c, 0x92, 0xb9, 0x92, 0x16, 0x93, - 0x73, 0x93, 0xd1, 0x93, 0x2e, 0x94, 0x8c, 0x94, 0xe9, 0x94, 0x47, 0x95, - 0xa4, 0x95, 0x02, 0x96, 0x60, 0x96, 0xbe, 0x96, 0x1c, 0x97, 0x7a, 0x97, - 0xd8, 0x97, 0x36, 0x98, 0x95, 0x98, 0xf3, 0x98, 0x52, 0x99, 0xb0, 0x99, - 0x0f, 0x9a, 0x6e, 0x9a, 0xcd, 0x9a, 0x2c, 0x9b, 0x8b, 0x9b, 0xea, 0x9b, - 0x49, 0x9c, 0xa8, 0x9c, 0x08, 0x9d, 0x67, 0x9d, 0xc7, 0x9d, 0x26, 0x9e, - 0x86, 0x9e, 0xe6, 0x9e, 0x46, 0x9f, 0xa6, 0x9f, 0x06, 0xa0, 0x66, 0xa0, - 0xc6, 0xa0, 0x27, 0xa1, 0x87, 0xa1, 0xe8, 0xa1, 0x48, 0xa2, 0xa9, 0xa2, - 0x0a, 0xa3, 0x6b, 0xa3, 0xcc, 0xa3, 0x2d, 0xa4, 0x8e, 0xa4, 0xef, 0xa4, - 0x50, 0xa5, 0xb2, 0xa5, 0x13, 0xa6, 0x75, 0xa6, 0xd6, 0xa6, 0x38, 0xa7, - 0x9a, 0xa7, 0xfc, 0xa7, 0x5e, 0xa8, 0xc0, 0xa8, 0x22, 0xa9, 0x84, 0xa9, - 0xe7, 0xa9, 0x49, 0xaa, 0xac, 0xaa, 0x0e, 0xab, 0x71, 0xab, 0xd4, 0xab, - 0x37, 0xac, 0x9a, 0xac, 0xfd, 0xac, 0x60, 0xad, 0xc3, 0xad, 0x27, 0xae, - 0x8a, 0xae, 0xed, 0xae, 0x51, 0xaf, 0xb5, 0xaf, 0x19, 0xb0, 0x7c, 0xb0, - 0xe0, 0xb0, 0x45, 0xb1, 0xa9, 0xb1, 0x0d, 0xb2, 0x71, 0xb2, 0xd6, 0xb2, - 0x3a, 0xb3, 0x9f, 0xb3, 0x03, 0xb4, 0x68, 0xb4, 0xcd, 0xb4, 0x32, 0xb5, - 0x97, 0xb5, 0xfc, 0xb5, 0x62, 0xb6, 0xc7, 0xb6, 0x2c, 0xb7, 0x92, 0xb7, - 0xf7, 0xb7, 0x5d, 0xb8, 0xc3, 0xb8, 0x29, 0xb9, 0x8f, 0xb9, 0xf5, 0xb9, - 0x5b, 0xba, 0xc1, 0xba, 0x28, 0xbb, 0x8e, 0xbb, 0xf5, 0xbb, 0x5b, 0xbc, - 0xc2, 0xbc, 0x29, 0xbd, 0x90, 0xbd, 0xf7, 0xbd, 0x5e, 0xbe, 0xc5, 0xbe, - 0x2c, 0xbf, 0x94, 0xbf, 0xfb, 0xbf, 0x63, 0xc0, 0xca, 0xc0, 0x32, 0xc1, - 0x9a, 0xc1, 0x02, 0xc2, 0x6a, 0xc2, 0xd2, 0xc2, 0x3a, 0xc3, 0xa2, 0xc3, - 0x0b, 0xc4, 0x73, 0xc4, 0xdc, 0xc4, 0x44, 0xc5, 0xad, 0xc5, 0x16, 0xc6, - 0x7f, 0xc6, 0xe8, 0xc6, 0x51, 0xc7, 0xbb, 0xc7, 0x24, 0xc8, 0x8d, 0xc8, - 0xf7, 0xc8, 0x60, 0xc9, 0xca, 0xc9, 0x34, 0xca, 0x9e, 0xca, 0x08, 0xcb, - 0x72, 0xcb, 0xdc, 0xcb, 0x47, 0xcc, 0xb1, 0xcc, 0x1b, 0xcd, 0x86, 0xcd, - 0xf1, 0xcd, 0x5b, 0xce, 0xc6, 0xce, 0x31, 0xcf, 0x9c, 0xcf, 0x08, 0xd0, - 0x73, 0xd0, 0xde, 0xd0, 0x4a, 0xd1, 0xb5, 0xd1, 0x21, 0xd2, 0x8d, 0xd2, - 0xf8, 0xd2, 0x64, 0xd3, 0xd0, 0xd3, 0x3d, 0xd4, 0xa9, 0xd4, 0x15, 0xd5, - 0x82, 0xd5, 0xee, 0xd5, 0x5b, 0xd6, 0xc7, 0xd6, 0x34, 0xd7, 0xa1, 0xd7, - 0x0e, 0xd8, 0x7b, 0xd8, 0xe9, 0xd8, 0x56, 0xd9, 0xc3, 0xd9, 0x31, 0xda, - 0x9e, 0xda, 0x0c, 0xdb, 0x7a, 0xdb, 0xe8, 0xdb, 0x56, 0xdc, 0xc4, 0xdc, - 0x32, 0xdd, 0xa0, 0xdd, 0x0f, 0xde, 0x7d, 0xde, 0xec, 0xde, 0x5b, 0xdf, - 0xc9, 0xdf, 0x38, 0xe0, 0xa7, 0xe0, 0x16, 0xe1, 0x86, 0xe1, 0xf5, 0xe1, - 0x64, 0xe2, 0xd4, 0xe2, 0x43, 0xe3, 0xb3, 0xe3, 0x23, 0xe4, 0x93, 0xe4, - 0x03, 0xe5, 0x73, 0xe5, 0xe3, 0xe5, 0x54, 0xe6, 0xc4, 0xe6, 0x35, 0xe7, - 0xa5, 0xe7, 0x16, 0xe8, 0x87, 0xe8, 0xf8, 0xe8, 0x69, 0xe9, 0xda, 0xe9, - 0x4b, 0xea, 0xbc, 0xea, 0x2e, 0xeb, 0x9f, 0xeb, 0x11, 0xec, 0x83, 0xec, - 0xf5, 0xec, 0x66, 0xed, 0xd9, 0xed, 0x4b, 0xee, 0xbd, 0xee, 0x2f, 0xef, - 0xa2, 0xef, 0x14, 0xf0, 0x87, 0xf0, 0xfa, 0xf0, 0x6d, 0xf1, 0xe0, 0xf1, - 0x53, 0xf2, 0xc6, 0xf2, 0x39, 0xf3, 0xad, 0xf3, 0x20, 0xf4, 0x94, 0xf4, - 0x07, 0xf5, 0x7b, 0xf5, 0xef, 0xf5, 0x63, 0xf6, 0xd7, 0xf6, 0x4c, 0xf7, - 0xc0, 0xf7, 0x34, 0xf8, 0xa9, 0xf8, 0x1e, 0xf9, 0x92, 0xf9, 0x07, 0xfa, - 0x7c, 0xfa, 0xf1, 0xfa, 0x66, 0xfb, 0xdc, 0xfb, 0x51, 0xfc, 0xc7, 0xfc, - 0x3c, 0xfd, 0xb2, 0xfd, 0x28, 0xfe, 0x9e, 0xfe, 0x14, 0xff, 0x8a, 0xff, - 0x98, 0x16, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe0, 0x10, 0x10, 0x1f, 0xe5, - 0xb0, 0x00, 0x91, 0xe1, 0x1f, 0xfd, 0xff, 0xea, 0x00, 0x01, 0x01, 0x01, + 0xee, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, 0xf4, 0xff, 0xff, 0x1a, + 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, 0xe5, 0xff, 0xff, 0xea, + 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, 0x01, 0x40, 0xd0, 0x14, + 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, + 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, 0xe0, 0xfe, 0xff, 0x0a, + 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, 0xd9, 0xff, 0xff, 0xea, + 0xdc, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xa0, 0xe1, + 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, 0x7f, 0x30, 0x03, 0xe2, + 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, 0x03, 0x30, 0x83, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0xd1, 0xfe, 0xff, 0x0a, + 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, 0xf3, 0xff, 0xff, 0xea, + 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0xc9, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, 0x00, 0x00, 0x24, 0x03, + 0x48, 0x06, 0x6a, 0x09, 0x8c, 0x0c, 0xab, 0x0f, 0xc8, 0x12, 0xe2, 0x15, + 0xf9, 0x18, 0x0b, 0x1c, 0x1a, 0x1f, 0x23, 0x22, 0x28, 0x25, 0x26, 0x28, + 0x1f, 0x2b, 0x11, 0x2e, 0xfb, 0x30, 0xdf, 0x33, 0xba, 0x36, 0x8c, 0x39, + 0x56, 0x3c, 0x17, 0x3f, 0xce, 0x41, 0x7a, 0x44, 0x1c, 0x47, 0xb4, 0x49, + 0x3f, 0x4c, 0xbf, 0x4e, 0x33, 0x51, 0x9b, 0x53, 0xf5, 0x55, 0x42, 0x58, + 0x82, 0x5a, 0xb3, 0x5c, 0xd7, 0x5e, 0xeb, 0x60, 0xf1, 0x62, 0xe8, 0x64, + 0xcf, 0x66, 0xa6, 0x68, 0x6d, 0x6a, 0x23, 0x6c, 0xc9, 0x6d, 0x5e, 0x6f, + 0xe2, 0x70, 0x54, 0x72, 0xb5, 0x73, 0x04, 0x75, 0x41, 0x76, 0x6b, 0x77, + 0x84, 0x78, 0x89, 0x79, 0x7c, 0x7a, 0x5c, 0x7b, 0x29, 0x7c, 0xe3, 0x7c, + 0x89, 0x7d, 0x1d, 0x7e, 0x9c, 0x7e, 0x09, 0x7f, 0x61, 0x7f, 0xa6, 0x7f, + 0xd8, 0x7f, 0xf5, 0x7f, 0x00, 0x00, 0x80, 0xe0, 0x8c, 0x10, 0x4f, 0xe2, + 0xb0, 0x00, 0x91, 0xe1, 0xa2, 0xfe, 0xff, 0xea, 0x00, 0x00, 0x3b, 0x00, + 0x76, 0x00, 0xb2, 0x00, 0xed, 0x00, 0x28, 0x01, 0x64, 0x01, 0x9f, 0x01, + 0xdb, 0x01, 0x17, 0x02, 0x52, 0x02, 0x8e, 0x02, 0xca, 0x02, 0x05, 0x03, + 0x41, 0x03, 0x7d, 0x03, 0xb9, 0x03, 0xf5, 0x03, 0x31, 0x04, 0x6e, 0x04, + 0xaa, 0x04, 0xe6, 0x04, 0x22, 0x05, 0x5f, 0x05, 0x9b, 0x05, 0xd8, 0x05, + 0x14, 0x06, 0x51, 0x06, 0x8d, 0x06, 0xca, 0x06, 0x07, 0x07, 0x43, 0x07, + 0x80, 0x07, 0xbd, 0x07, 0xfa, 0x07, 0x37, 0x08, 0x74, 0x08, 0xb1, 0x08, + 0xef, 0x08, 0x2c, 0x09, 0x69, 0x09, 0xa7, 0x09, 0xe4, 0x09, 0x21, 0x0a, + 0x5f, 0x0a, 0x9c, 0x0a, 0xda, 0x0a, 0x18, 0x0b, 0x56, 0x0b, 0x93, 0x0b, + 0xd1, 0x0b, 0x0f, 0x0c, 0x4d, 0x0c, 0x8b, 0x0c, 0xc9, 0x0c, 0x07, 0x0d, + 0x45, 0x0d, 0x84, 0x0d, 0xc2, 0x0d, 0x00, 0x0e, 0x3f, 0x0e, 0x7d, 0x0e, + 0xbc, 0x0e, 0xfa, 0x0e, 0x39, 0x0f, 0x78, 0x0f, 0xb6, 0x0f, 0xf5, 0x0f, + 0x34, 0x10, 0x73, 0x10, 0xb2, 0x10, 0xf1, 0x10, 0x30, 0x11, 0x6f, 0x11, + 0xae, 0x11, 0xee, 0x11, 0x2d, 0x12, 0x6c, 0x12, 0xac, 0x12, 0xeb, 0x12, + 0x2b, 0x13, 0x6b, 0x13, 0xaa, 0x13, 0xea, 0x13, 0x2a, 0x14, 0x6a, 0x14, + 0xa9, 0x14, 0xe9, 0x14, 0x29, 0x15, 0x69, 0x15, 0xaa, 0x15, 0xea, 0x15, + 0x2a, 0x16, 0x6a, 0x16, 0xab, 0x16, 0xeb, 0x16, 0x2c, 0x17, 0x6c, 0x17, + 0xad, 0x17, 0xed, 0x17, 0x2e, 0x18, 0x6f, 0x18, 0xb0, 0x18, 0xf0, 0x18, + 0x31, 0x19, 0x72, 0x19, 0xb3, 0x19, 0xf5, 0x19, 0x36, 0x1a, 0x77, 0x1a, + 0xb8, 0x1a, 0xfa, 0x1a, 0x3b, 0x1b, 0x7d, 0x1b, 0xbe, 0x1b, 0x00, 0x1c, + 0x41, 0x1c, 0x83, 0x1c, 0xc5, 0x1c, 0x07, 0x1d, 0x48, 0x1d, 0x8a, 0x1d, + 0xcc, 0x1d, 0x0e, 0x1e, 0x51, 0x1e, 0x93, 0x1e, 0xd5, 0x1e, 0x17, 0x1f, + 0x5a, 0x1f, 0x9c, 0x1f, 0xdf, 0x1f, 0x21, 0x20, 0x64, 0x20, 0xa6, 0x20, + 0xe9, 0x20, 0x2c, 0x21, 0x6f, 0x21, 0xb2, 0x21, 0xf5, 0x21, 0x38, 0x22, + 0x7b, 0x22, 0xbe, 0x22, 0x01, 0x23, 0x44, 0x23, 0x88, 0x23, 0xcb, 0x23, + 0x0e, 0x24, 0x52, 0x24, 0x96, 0x24, 0xd9, 0x24, 0x1d, 0x25, 0x61, 0x25, + 0xa4, 0x25, 0xe8, 0x25, 0x2c, 0x26, 0x70, 0x26, 0xb4, 0x26, 0xf8, 0x26, + 0x3d, 0x27, 0x81, 0x27, 0xc5, 0x27, 0x0a, 0x28, 0x4e, 0x28, 0x92, 0x28, + 0xd7, 0x28, 0x1c, 0x29, 0x60, 0x29, 0xa5, 0x29, 0xea, 0x29, 0x2f, 0x2a, + 0x74, 0x2a, 0xb9, 0x2a, 0xfe, 0x2a, 0x43, 0x2b, 0x88, 0x2b, 0xcd, 0x2b, + 0x13, 0x2c, 0x58, 0x2c, 0x9d, 0x2c, 0xe3, 0x2c, 0x28, 0x2d, 0x6e, 0x2d, + 0xb4, 0x2d, 0xf9, 0x2d, 0x3f, 0x2e, 0x85, 0x2e, 0xcb, 0x2e, 0x11, 0x2f, + 0x57, 0x2f, 0x9d, 0x2f, 0xe3, 0x2f, 0x2a, 0x30, 0x70, 0x30, 0xb6, 0x30, + 0xfd, 0x30, 0x43, 0x31, 0x8a, 0x31, 0xd0, 0x31, 0x17, 0x32, 0x5e, 0x32, + 0xa5, 0x32, 0xec, 0x32, 0x32, 0x33, 0x79, 0x33, 0xc1, 0x33, 0x08, 0x34, + 0x4f, 0x34, 0x96, 0x34, 0xdd, 0x34, 0x25, 0x35, 0x6c, 0x35, 0xb4, 0x35, + 0xfb, 0x35, 0x43, 0x36, 0x8b, 0x36, 0xd3, 0x36, 0x1a, 0x37, 0x62, 0x37, + 0xaa, 0x37, 0xf2, 0x37, 0x3a, 0x38, 0x83, 0x38, 0xcb, 0x38, 0x13, 0x39, + 0x5c, 0x39, 0xa4, 0x39, 0xed, 0x39, 0x35, 0x3a, 0x7e, 0x3a, 0xc6, 0x3a, + 0x0f, 0x3b, 0x58, 0x3b, 0xa1, 0x3b, 0xea, 0x3b, 0x33, 0x3c, 0x7c, 0x3c, + 0xc5, 0x3c, 0x0e, 0x3d, 0x58, 0x3d, 0xa1, 0x3d, 0xea, 0x3d, 0x34, 0x3e, + 0x7d, 0x3e, 0xc7, 0x3e, 0x11, 0x3f, 0x5a, 0x3f, 0xa4, 0x3f, 0xee, 0x3f, + 0x38, 0x40, 0x82, 0x40, 0xcc, 0x40, 0x16, 0x41, 0x61, 0x41, 0xab, 0x41, + 0xf5, 0x41, 0x40, 0x42, 0x8a, 0x42, 0xd5, 0x42, 0x1f, 0x43, 0x6a, 0x43, + 0xb5, 0x43, 0x00, 0x44, 0x4b, 0x44, 0x95, 0x44, 0xe1, 0x44, 0x2c, 0x45, + 0x77, 0x45, 0xc2, 0x45, 0x0d, 0x46, 0x59, 0x46, 0xa4, 0x46, 0xf0, 0x46, + 0x3b, 0x47, 0x87, 0x47, 0xd3, 0x47, 0x1e, 0x48, 0x6a, 0x48, 0xb6, 0x48, + 0x02, 0x49, 0x4e, 0x49, 0x9a, 0x49, 0xe6, 0x49, 0x33, 0x4a, 0x7f, 0x4a, + 0xcb, 0x4a, 0x18, 0x4b, 0x64, 0x4b, 0xb1, 0x4b, 0xfe, 0x4b, 0x4a, 0x4c, + 0x97, 0x4c, 0xe4, 0x4c, 0x31, 0x4d, 0x7e, 0x4d, 0xcb, 0x4d, 0x18, 0x4e, + 0x66, 0x4e, 0xb3, 0x4e, 0x00, 0x4f, 0x4e, 0x4f, 0x9b, 0x4f, 0xe9, 0x4f, + 0x36, 0x50, 0x84, 0x50, 0xd2, 0x50, 0x20, 0x51, 0x6e, 0x51, 0xbc, 0x51, + 0x0a, 0x52, 0x58, 0x52, 0xa6, 0x52, 0xf4, 0x52, 0x43, 0x53, 0x91, 0x53, + 0xe0, 0x53, 0x2e, 0x54, 0x7d, 0x54, 0xcc, 0x54, 0x1a, 0x55, 0x69, 0x55, + 0xb8, 0x55, 0x07, 0x56, 0x56, 0x56, 0xa5, 0x56, 0xf4, 0x56, 0x44, 0x57, + 0x93, 0x57, 0xe2, 0x57, 0x32, 0x58, 0x82, 0x58, 0xd1, 0x58, 0x21, 0x59, + 0x71, 0x59, 0xc1, 0x59, 0x10, 0x5a, 0x60, 0x5a, 0xb0, 0x5a, 0x01, 0x5b, + 0x51, 0x5b, 0xa1, 0x5b, 0xf1, 0x5b, 0x42, 0x5c, 0x92, 0x5c, 0xe3, 0x5c, + 0x34, 0x5d, 0x84, 0x5d, 0xd5, 0x5d, 0x26, 0x5e, 0x77, 0x5e, 0xc8, 0x5e, + 0x19, 0x5f, 0x6a, 0x5f, 0xbb, 0x5f, 0x0d, 0x60, 0x5e, 0x60, 0xb0, 0x60, + 0x01, 0x61, 0x53, 0x61, 0xa4, 0x61, 0xf6, 0x61, 0x48, 0x62, 0x9a, 0x62, + 0xec, 0x62, 0x3e, 0x63, 0x90, 0x63, 0xe2, 0x63, 0x34, 0x64, 0x87, 0x64, + 0xd9, 0x64, 0x2c, 0x65, 0x7e, 0x65, 0xd1, 0x65, 0x24, 0x66, 0x76, 0x66, + 0xc9, 0x66, 0x1c, 0x67, 0x6f, 0x67, 0xc2, 0x67, 0x15, 0x68, 0x69, 0x68, + 0xbc, 0x68, 0x0f, 0x69, 0x63, 0x69, 0xb6, 0x69, 0x0a, 0x6a, 0x5e, 0x6a, + 0xb1, 0x6a, 0x05, 0x6b, 0x59, 0x6b, 0xad, 0x6b, 0x01, 0x6c, 0x55, 0x6c, + 0xaa, 0x6c, 0xfe, 0x6c, 0x52, 0x6d, 0xa7, 0x6d, 0xfb, 0x6d, 0x50, 0x6e, + 0xa4, 0x6e, 0xf9, 0x6e, 0x4e, 0x6f, 0xa3, 0x6f, 0xf8, 0x6f, 0x4d, 0x70, + 0xa2, 0x70, 0xf7, 0x70, 0x4d, 0x71, 0xa2, 0x71, 0xf7, 0x71, 0x4d, 0x72, + 0xa2, 0x72, 0xf8, 0x72, 0x4e, 0x73, 0xa4, 0x73, 0xfa, 0x73, 0x50, 0x74, + 0xa6, 0x74, 0xfc, 0x74, 0x52, 0x75, 0xa8, 0x75, 0xff, 0x75, 0x55, 0x76, + 0xac, 0x76, 0x02, 0x77, 0x59, 0x77, 0xb0, 0x77, 0x07, 0x78, 0x5e, 0x78, + 0xb4, 0x78, 0x0c, 0x79, 0x63, 0x79, 0xba, 0x79, 0x11, 0x7a, 0x69, 0x7a, + 0xc0, 0x7a, 0x18, 0x7b, 0x6f, 0x7b, 0xc7, 0x7b, 0x1f, 0x7c, 0x77, 0x7c, + 0xcf, 0x7c, 0x27, 0x7d, 0x7f, 0x7d, 0xd7, 0x7d, 0x2f, 0x7e, 0x88, 0x7e, + 0xe0, 0x7e, 0x38, 0x7f, 0x91, 0x7f, 0xea, 0x7f, 0x42, 0x80, 0x9b, 0x80, + 0xf4, 0x80, 0x4d, 0x81, 0xa6, 0x81, 0xff, 0x81, 0x59, 0x82, 0xb2, 0x82, + 0x0b, 0x83, 0x65, 0x83, 0xbe, 0x83, 0x18, 0x84, 0x72, 0x84, 0xcb, 0x84, + 0x25, 0x85, 0x7f, 0x85, 0xd9, 0x85, 0x33, 0x86, 0x8e, 0x86, 0xe8, 0x86, + 0x42, 0x87, 0x9d, 0x87, 0xf7, 0x87, 0x52, 0x88, 0xac, 0x88, 0x07, 0x89, + 0x62, 0x89, 0xbd, 0x89, 0x18, 0x8a, 0x73, 0x8a, 0xce, 0x8a, 0x2a, 0x8b, + 0x85, 0x8b, 0xe0, 0x8b, 0x3c, 0x8c, 0x97, 0x8c, 0xf3, 0x8c, 0x4f, 0x8d, + 0xab, 0x8d, 0x07, 0x8e, 0x63, 0x8e, 0xbf, 0x8e, 0x1b, 0x8f, 0x77, 0x8f, + 0xd4, 0x8f, 0x30, 0x90, 0x8c, 0x90, 0xe9, 0x90, 0x46, 0x91, 0xa2, 0x91, + 0xff, 0x91, 0x5c, 0x92, 0xb9, 0x92, 0x16, 0x93, 0x73, 0x93, 0xd1, 0x93, + 0x2e, 0x94, 0x8c, 0x94, 0xe9, 0x94, 0x47, 0x95, 0xa4, 0x95, 0x02, 0x96, + 0x60, 0x96, 0xbe, 0x96, 0x1c, 0x97, 0x7a, 0x97, 0xd8, 0x97, 0x36, 0x98, + 0x95, 0x98, 0xf3, 0x98, 0x52, 0x99, 0xb0, 0x99, 0x0f, 0x9a, 0x6e, 0x9a, + 0xcd, 0x9a, 0x2c, 0x9b, 0x8b, 0x9b, 0xea, 0x9b, 0x49, 0x9c, 0xa8, 0x9c, + 0x08, 0x9d, 0x67, 0x9d, 0xc7, 0x9d, 0x26, 0x9e, 0x86, 0x9e, 0xe6, 0x9e, + 0x46, 0x9f, 0xa6, 0x9f, 0x06, 0xa0, 0x66, 0xa0, 0xc6, 0xa0, 0x27, 0xa1, + 0x87, 0xa1, 0xe8, 0xa1, 0x48, 0xa2, 0xa9, 0xa2, 0x0a, 0xa3, 0x6b, 0xa3, + 0xcc, 0xa3, 0x2d, 0xa4, 0x8e, 0xa4, 0xef, 0xa4, 0x50, 0xa5, 0xb2, 0xa5, + 0x13, 0xa6, 0x75, 0xa6, 0xd6, 0xa6, 0x38, 0xa7, 0x9a, 0xa7, 0xfc, 0xa7, + 0x5e, 0xa8, 0xc0, 0xa8, 0x22, 0xa9, 0x84, 0xa9, 0xe7, 0xa9, 0x49, 0xaa, + 0xac, 0xaa, 0x0e, 0xab, 0x71, 0xab, 0xd4, 0xab, 0x37, 0xac, 0x9a, 0xac, + 0xfd, 0xac, 0x60, 0xad, 0xc3, 0xad, 0x27, 0xae, 0x8a, 0xae, 0xed, 0xae, + 0x51, 0xaf, 0xb5, 0xaf, 0x19, 0xb0, 0x7c, 0xb0, 0xe0, 0xb0, 0x45, 0xb1, + 0xa9, 0xb1, 0x0d, 0xb2, 0x71, 0xb2, 0xd6, 0xb2, 0x3a, 0xb3, 0x9f, 0xb3, + 0x03, 0xb4, 0x68, 0xb4, 0xcd, 0xb4, 0x32, 0xb5, 0x97, 0xb5, 0xfc, 0xb5, + 0x62, 0xb6, 0xc7, 0xb6, 0x2c, 0xb7, 0x92, 0xb7, 0xf7, 0xb7, 0x5d, 0xb8, + 0xc3, 0xb8, 0x29, 0xb9, 0x8f, 0xb9, 0xf5, 0xb9, 0x5b, 0xba, 0xc1, 0xba, + 0x28, 0xbb, 0x8e, 0xbb, 0xf5, 0xbb, 0x5b, 0xbc, 0xc2, 0xbc, 0x29, 0xbd, + 0x90, 0xbd, 0xf7, 0xbd, 0x5e, 0xbe, 0xc5, 0xbe, 0x2c, 0xbf, 0x94, 0xbf, + 0xfb, 0xbf, 0x63, 0xc0, 0xca, 0xc0, 0x32, 0xc1, 0x9a, 0xc1, 0x02, 0xc2, + 0x6a, 0xc2, 0xd2, 0xc2, 0x3a, 0xc3, 0xa2, 0xc3, 0x0b, 0xc4, 0x73, 0xc4, + 0xdc, 0xc4, 0x44, 0xc5, 0xad, 0xc5, 0x16, 0xc6, 0x7f, 0xc6, 0xe8, 0xc6, + 0x51, 0xc7, 0xbb, 0xc7, 0x24, 0xc8, 0x8d, 0xc8, 0xf7, 0xc8, 0x60, 0xc9, + 0xca, 0xc9, 0x34, 0xca, 0x9e, 0xca, 0x08, 0xcb, 0x72, 0xcb, 0xdc, 0xcb, + 0x47, 0xcc, 0xb1, 0xcc, 0x1b, 0xcd, 0x86, 0xcd, 0xf1, 0xcd, 0x5b, 0xce, + 0xc6, 0xce, 0x31, 0xcf, 0x9c, 0xcf, 0x08, 0xd0, 0x73, 0xd0, 0xde, 0xd0, + 0x4a, 0xd1, 0xb5, 0xd1, 0x21, 0xd2, 0x8d, 0xd2, 0xf8, 0xd2, 0x64, 0xd3, + 0xd0, 0xd3, 0x3d, 0xd4, 0xa9, 0xd4, 0x15, 0xd5, 0x82, 0xd5, 0xee, 0xd5, + 0x5b, 0xd6, 0xc7, 0xd6, 0x34, 0xd7, 0xa1, 0xd7, 0x0e, 0xd8, 0x7b, 0xd8, + 0xe9, 0xd8, 0x56, 0xd9, 0xc3, 0xd9, 0x31, 0xda, 0x9e, 0xda, 0x0c, 0xdb, + 0x7a, 0xdb, 0xe8, 0xdb, 0x56, 0xdc, 0xc4, 0xdc, 0x32, 0xdd, 0xa0, 0xdd, + 0x0f, 0xde, 0x7d, 0xde, 0xec, 0xde, 0x5b, 0xdf, 0xc9, 0xdf, 0x38, 0xe0, + 0xa7, 0xe0, 0x16, 0xe1, 0x86, 0xe1, 0xf5, 0xe1, 0x64, 0xe2, 0xd4, 0xe2, + 0x43, 0xe3, 0xb3, 0xe3, 0x23, 0xe4, 0x93, 0xe4, 0x03, 0xe5, 0x73, 0xe5, + 0xe3, 0xe5, 0x54, 0xe6, 0xc4, 0xe6, 0x35, 0xe7, 0xa5, 0xe7, 0x16, 0xe8, + 0x87, 0xe8, 0xf8, 0xe8, 0x69, 0xe9, 0xda, 0xe9, 0x4b, 0xea, 0xbc, 0xea, + 0x2e, 0xeb, 0x9f, 0xeb, 0x11, 0xec, 0x83, 0xec, 0xf5, 0xec, 0x66, 0xed, + 0xd9, 0xed, 0x4b, 0xee, 0xbd, 0xee, 0x2f, 0xef, 0xa2, 0xef, 0x14, 0xf0, + 0x87, 0xf0, 0xfa, 0xf0, 0x6d, 0xf1, 0xe0, 0xf1, 0x53, 0xf2, 0xc6, 0xf2, + 0x39, 0xf3, 0xad, 0xf3, 0x20, 0xf4, 0x94, 0xf4, 0x07, 0xf5, 0x7b, 0xf5, + 0xef, 0xf5, 0x63, 0xf6, 0xd7, 0xf6, 0x4c, 0xf7, 0xc0, 0xf7, 0x34, 0xf8, + 0xa9, 0xf8, 0x1e, 0xf9, 0x92, 0xf9, 0x07, 0xfa, 0x7c, 0xfa, 0xf1, 0xfa, + 0x66, 0xfb, 0xdc, 0xfb, 0x51, 0xfc, 0xc7, 0xfc, 0x3c, 0xfd, 0xb2, 0xfd, + 0x28, 0xfe, 0x9e, 0xfe, 0x14, 0xff, 0x8a, 0xff, 0xa0, 0x16, 0x00, 0x00, + 0x00, 0x00, 0x80, 0xe0, 0x10, 0x10, 0x1f, 0xe5, 0xb0, 0x00, 0x91, 0xe1, + 0x1d, 0xfd, 0xff, 0xea, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, @@ -648,905 +649,66 @@ unsigned char bios_arm7_bin[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, - 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, - 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, - 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, - 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, - 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, - 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, - 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, - 0x21, 0x21, 0x22, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, - 0x26, 0x26, 0x27, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, - 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x31, - 0x32, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, - 0x39, 0x3a, 0x3a, 0x3b, 0x3c, 0x3c, 0x3d, 0x3e, 0x3f, 0x3f, 0x40, 0x41, - 0x42, 0x42, 0x43, 0x44, 0x45, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4a, - 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, - 0x63, 0x64, 0x65, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x71, - 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7e, 0x7f, 0x20, - 0x21, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, - 0x26, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, - 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x30, 0x31, - 0x31, 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, - 0x39, 0x39, 0x3a, 0x3b, 0x3b, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x40, 0x40, - 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, - 0x4b, 0x4c, 0x4d, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x62, - 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6f, 0x70, - 0x71, 0x73, 0x74, 0x75, 0x77, 0x78, 0x79, 0x7b, 0x7c, 0x7e, 0x7e, 0x40, - 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, - 0x4b, 0x4c, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, - 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x70, - 0x71, 0x72, 0x74, 0x75, 0x76, 0x78, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x40, - 0x41, 0x42, 0x42, 0x43, 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, 0x49, 0x4a, - 0x4b, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, - 0x62, 0x63, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x6f, - 0x71, 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7e, 0x7f, - 0x00, 0x00, 0x00, 0x00, 0x2e, 0x00, 0xa0, 0xe3, 0x3c, 0x10, 0xa0, 0xe3, - 0xff, 0x20, 0xa0, 0xe3, 0x0a, 0x0c, 0x80, 0xe3, 0x0b, 0x1b, 0x81, 0xe3, - 0x05, 0x2c, 0x82, 0xe3, 0x62, 0xfc, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, - 0x01, 0x03, 0xc1, 0xe5, 0x5f, 0xfc, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, - 0x01, 0x03, 0xa0, 0xe3, 0x0f, 0xe0, 0xa0, 0xe1, 0x04, 0xf0, 0x10, 0xe5, - 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x32, 0x1e, 0x4f, 0xe2, - 0x00, 0x00, 0xd1, 0xe7, 0x56, 0xfc, 0xff, 0xea, 0xdc, 0xff, 0x80, 0x03, - 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, 0x00, 0x20, 0xa0, 0xe3, - 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, 0xfc, 0xff, 0xff, 0x1a, - 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, - 0x01, 0xd0, 0xa0, 0xe1, 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, - 0xd2, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0x2c, 0xd0, 0x41, 0xe2, - 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, 0x5f, 0x30, 0xa0, 0xe3, - 0x03, 0xf0, 0x2f, 0xe1, 0xdc, 0xd0, 0x41, 0xe2, 0xff, 0x1f, 0x90, 0xe8, - 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 -}; - -unsigned char bios_arm9_bin[] = { - 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, - 0x3c, 0x00, 0x00, 0xea, 0x3b, 0x00, 0x00, 0xea, 0x3a, 0x00, 0x00, 0xea, - 0xad, 0x01, 0x00, 0xea, 0x38, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, - 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x10, 0x00, 0x2d, 0xe9, - 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, - 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, - 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, - 0xf8, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0xe0, 0x01, 0xff, 0xff, 0x20, 0x02, 0xff, 0xff, 0x18, 0x02, 0xff, 0xff, - 0xd4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0x4c, 0x02, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xa8, 0x02, 0xff, 0xff, - 0x28, 0x03, 0xff, 0xff, 0x68, 0x03, 0xff, 0xff, 0xb4, 0x03, 0xff, 0xff, - 0x4c, 0x04, 0xff, 0xff, 0x54, 0x04, 0xff, 0xff, 0xe8, 0x04, 0xff, 0xff, - 0x64, 0x05, 0xff, 0xff, 0x10, 0x06, 0xff, 0xff, 0x14, 0x06, 0xff, 0xff, - 0x14, 0x06, 0xff, 0xff, 0x6c, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0x98, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, - 0xc8, 0x06, 0xff, 0xff, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, - 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, - 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, 0xf4, 0xff, 0xff, 0xea, - 0x01, 0x00, 0x50, 0xe2, 0xfd, 0xff, 0xff, 0xca, 0xf1, 0xff, 0xff, 0xea, - 0x11, 0x2f, 0x19, 0xee, 0xff, 0x20, 0xc2, 0xe3, 0x01, 0x29, 0x82, 0xe2, - 0x08, 0x00, 0x12, 0xe5, 0x08, 0x32, 0x83, 0xe5, 0x00, 0x00, 0x11, 0xe1, - 0x01, 0x00, 0xc0, 0xe1, 0x08, 0x00, 0x02, 0xe5, 0x01, 0xc0, 0xa0, 0xe3, - 0x08, 0xc2, 0x83, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0xa0, 0xe3, - 0x01, 0x10, 0xa0, 0xe3, 0x01, 0x33, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, - 0xef, 0xff, 0xff, 0x1b, 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, - 0xec, 0xff, 0xff, 0xeb, 0xfb, 0xff, 0xff, 0x0a, 0xdc, 0xff, 0xff, 0xea, - 0xe9, 0xff, 0xff, 0xeb, 0xda, 0xff, 0xff, 0x1a, 0xf7, 0xff, 0xff, 0xea, - 0x02, 0xc1, 0x10, 0xe2, 0x00, 0x00, 0x60, 0x42, 0x02, 0x31, 0x11, 0xe2, - 0x00, 0x10, 0x61, 0x42, 0x03, 0xc0, 0x2c, 0xe0, 0x00, 0x20, 0xa0, 0xe3, - 0x01, 0x30, 0xa0, 0xe3, 0x00, 0x00, 0x51, 0xe1, 0x81, 0x10, 0xa0, 0x91, - 0x83, 0x30, 0xa0, 0x91, 0xfb, 0xff, 0xff, 0x9a, 0x01, 0x00, 0x50, 0xe1, - 0x01, 0x00, 0x40, 0x20, 0x03, 0x20, 0x82, 0x21, 0xa3, 0x30, 0xb0, 0xe1, - 0xa1, 0x10, 0xa0, 0x31, 0xf9, 0xff, 0xff, 0x3a, 0x00, 0x10, 0xa0, 0xe1, - 0x02, 0x30, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe1, 0x02, 0x01, 0x1c, 0xe3, - 0x00, 0x00, 0x60, 0x42, 0xc2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, - 0x0e, 0xc6, 0xcc, 0xe3, 0x01, 0x03, 0x12, 0xe3, 0x0d, 0x00, 0x00, 0x1a, - 0x01, 0x04, 0x12, 0xe3, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, - 0x04, 0x00, 0x00, 0x1a, 0xb2, 0x30, 0xd0, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, - 0xb2, 0x30, 0xc1, 0xe0, 0xfb, 0xff, 0xff, 0x1a, 0xb5, 0xff, 0xff, 0xea, - 0xb0, 0x30, 0xd0, 0xe1, 0xb2, 0x30, 0xc1, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, - 0xfc, 0xff, 0xff, 0x1a, 0xb0, 0xff, 0xff, 0xea, 0x01, 0x04, 0x12, 0xe3, - 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, 0x04, 0x00, 0x00, 0x1a, - 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0x04, 0x30, 0x81, 0xe4, - 0xfb, 0xff, 0xff, 0x1a, 0xa7, 0xff, 0xff, 0xea, 0x00, 0x30, 0x90, 0xe5, - 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0xfc, 0xff, 0xff, 0x1a, - 0xa2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, 0x0e, 0xc6, 0xcc, 0xe3, - 0x01, 0x04, 0x12, 0xe3, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, - 0x04, 0x00, 0x00, 0x1a, 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, - 0x04, 0x30, 0x81, 0xe4, 0xfb, 0xff, 0xff, 0x1a, 0x97, 0xff, 0xff, 0xea, - 0x00, 0x30, 0x90, 0xe5, 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, - 0xfc, 0xff, 0xff, 0x1a, 0x92, 0xff, 0xff, 0xea, 0x01, 0x11, 0xa0, 0xe3, - 0x00, 0x20, 0xa0, 0xe3, 0x02, 0x30, 0x81, 0xe1, 0x03, 0x00, 0x50, 0xe1, - 0x03, 0x00, 0x40, 0xa0, 0xa2, 0x20, 0xa0, 0xe1, 0x01, 0x20, 0x82, 0xa1, - 0x21, 0x11, 0xb0, 0xe1, 0xf8, 0xff, 0xff, 0x1a, 0x02, 0x00, 0xa0, 0xe1, - 0x87, 0xff, 0xff, 0xea, 0x00, 0x00, 0x01, 0xcc, 0x01, 0xd8, 0x00, 0x14, - 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, - 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, - 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, - 0xa2, 0x20, 0xb0, 0xe1, 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, - 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, - 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, - 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0xa3, 0x53, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, - 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, - 0x0c, 0x00, 0x20, 0xe0, 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, - 0x20, 0x00, 0xbd, 0xe8, 0x59, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, - 0x57, 0xff, 0xff, 0xea, 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, - 0x02, 0xc0, 0xd2, 0xe5, 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, - 0xa4, 0x2f, 0xa0, 0xe1, 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, - 0x15, 0x5c, 0xa0, 0xe1, 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, - 0x00, 0x70, 0xa0, 0xe3, 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, - 0x83, 0x31, 0xa0, 0x01, 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, - 0x04, 0x00, 0x5c, 0xe3, 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, - 0x01, 0x60, 0xd0, 0x04, 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, - 0x36, 0x6c, 0xa0, 0xe1, 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, - 0x04, 0x90, 0x89, 0x10, 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, - 0x20, 0x00, 0x58, 0xe3, 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, - 0x00, 0x80, 0xa0, 0x03, 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, - 0xe0, 0x03, 0xbd, 0xe8, 0x32, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xb0, 0xe1, 0x2f, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x1f, 0xff, 0xff, 0x0a, - 0x01, 0xe0, 0x5e, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, - 0xed, 0xff, 0xff, 0x5a, 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0x40, 0xc1, 0xe4, 0x16, 0xff, 0xff, 0x0a, - 0x83, 0x30, 0xb0, 0xe1, 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, - 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, 0x10, 0xff, 0xff, 0x0a, - 0x20, 0x00, 0x2d, 0xe9, 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, - 0x02, 0x35, 0x83, 0xe3, 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, - 0x01, 0x40, 0xd0, 0xe4, 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, - 0x24, 0xe6, 0xa0, 0xe1, 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, - 0x0c, 0xc0, 0x41, 0xe0, 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, - 0x01, 0x50, 0xdc, 0x04, 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, - 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, - 0x20, 0x00, 0xbd, 0x08, 0xf9, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, - 0xf4, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, - 0xe5, 0xff, 0xff, 0xea, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, - 0x01, 0x40, 0xd0, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, - 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, - 0xeb, 0xfe, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, - 0xd9, 0xff, 0xff, 0xea, 0xe7, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0x23, 0x24, 0xa0, 0xe1, 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, - 0x7f, 0x30, 0x03, 0xe2, 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, - 0x03, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, - 0xdc, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, - 0xf3, 0xff, 0xff, 0xea, 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x01, 0xc0, 0xc1, 0xe4, 0xd4, 0xfe, 0xff, 0x0a, - 0x01, 0x30, 0x53, 0xe2, 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, - 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0xd0, 0xe4, 0x23, 0x24, 0xa0, 0xe1, - 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x42, 0xe2, 0x01, 0x30, 0xd0, 0xe4, - 0x01, 0x20, 0x52, 0xe2, 0x03, 0xc0, 0x8c, 0xe0, 0x01, 0xc0, 0xc1, 0xe4, - 0xfa, 0xff, 0xff, 0x1a, 0xc6, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, - 0xb2, 0xc0, 0xd0, 0xe0, 0x23, 0x24, 0xa0, 0xe1, 0xb2, 0xc0, 0xc1, 0xe0, - 0x01, 0x20, 0xc2, 0xe3, 0x02, 0x20, 0x42, 0xe2, 0xb2, 0x30, 0xd0, 0xe0, - 0x02, 0x20, 0x52, 0xe2, 0x03, 0xc0, 0x8c, 0xe0, 0xb2, 0xc0, 0xc1, 0xe0, - 0xfa, 0xff, 0xff, 0x1a, 0xba, 0xfe, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, - 0x00, 0x03, 0xc1, 0xe5, 0xb7, 0xfe, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, - 0x11, 0x0f, 0x19, 0xee, 0xff, 0x00, 0xc0, 0xe3, 0x01, 0x09, 0x80, 0xe2, + 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, + 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x1a, + 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1e, + 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, + 0x22, 0x23, 0x23, 0x24, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, + 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, 0x2b, 0x2c, 0x2c, 0x2d, + 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, + 0x34, 0x35, 0x35, 0x36, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3a, 0x3a, 0x3b, + 0x3c, 0x3c, 0x3d, 0x3e, 0x3f, 0x3f, 0x40, 0x41, 0x42, 0x42, 0x43, 0x44, + 0x45, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, + 0x4f, 0x50, 0x51, 0x52, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6e, 0x6f, 0x71, 0x72, 0x73, 0x75, 0x76, + 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7e, 0x7f, 0x20, 0x21, 0x21, 0x21, 0x22, + 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, + 0x27, 0x28, 0x28, 0x29, 0x29, 0x2a, 0x2a, 0x2b, 0x2b, 0x2c, 0x2c, 0x2d, + 0x2d, 0x2e, 0x2e, 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31, 0x32, 0x33, 0x33, + 0x34, 0x34, 0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x39, 0x39, 0x3a, 0x3b, + 0x3b, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x40, 0x40, 0x41, 0x42, 0x43, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6f, 0x70, 0x71, 0x73, 0x74, 0x75, + 0x77, 0x78, 0x79, 0x7b, 0x7c, 0x7e, 0x7e, 0x40, 0x41, 0x42, 0x43, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x70, 0x71, 0x72, 0x74, 0x75, + 0x76, 0x78, 0x79, 0x7b, 0x7c, 0x7d, 0x7e, 0x40, 0x41, 0x42, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6a, 0x6c, 0x6d, 0x6e, 0x6f, 0x71, 0x72, 0x73, 0x75, + 0x76, 0x77, 0x79, 0x7a, 0x7c, 0x7d, 0x7e, 0x7f, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0xa0, 0xe3, 0x3c, 0x10, 0xa0, 0xe3, 0xff, 0x20, 0xa0, 0xe3, + 0x0a, 0x0c, 0x80, 0xe3, 0x0b, 0x1b, 0x81, 0xe3, 0x05, 0x2c, 0x82, 0xe3, + 0x60, 0xfc, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, 0x01, 0x03, 0xc1, 0xe5, + 0x5d, 0xfc, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3, 0x0f, 0xe0, 0xa0, 0xe1, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, - 0x04, 0xf0, 0x5e, 0xe2, 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, + 0x04, 0xf0, 0x5e, 0xe2, 0x32, 0x1e, 0x4f, 0xe2, 0x00, 0x00, 0xd1, 0xe7, + 0x54, 0xfc, 0xff, 0xea, 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, 0x00, 0x20, 0xa0, 0xe3, 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, 0xfc, 0xff, 0xff, 0x1a, 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0x01, 0xd0, 0xa0, 0xe1, @@ -1739,6 +901,844 @@ unsigned char bios_arm9_bin[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +std::array bios_arm9_bin = { + 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, + 0x3c, 0x00, 0x00, 0xea, 0x3b, 0x00, 0x00, 0xea, 0x3a, 0x00, 0x00, 0xea, + 0xaf, 0x01, 0x00, 0xea, 0x38, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, + 0x10, 0x50, 0x2d, 0xe9, 0x00, 0x40, 0x4f, 0xe1, 0x10, 0x00, 0x2d, 0xe9, + 0x80, 0x40, 0x04, 0xe2, 0x1f, 0x40, 0x84, 0xe3, 0x02, 0xc0, 0x5e, 0xe5, + 0x04, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x20, 0x00, 0x5c, 0xe3, + 0x01, 0xc0, 0xa0, 0xa3, 0x0c, 0xf1, 0x9f, 0xe7, 0x00, 0x00, 0xa0, 0xe1, + 0x00, 0x07, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xe0, 0x01, 0xff, 0xff, 0x20, 0x02, 0xff, 0xff, 0x18, 0x02, 0xff, 0xff, + 0xd4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0x4c, 0x02, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xa8, 0x02, 0xff, 0xff, + 0x28, 0x03, 0xff, 0xff, 0x68, 0x03, 0xff, 0xff, 0xb4, 0x03, 0xff, 0xff, + 0x54, 0x04, 0xff, 0xff, 0x5c, 0x04, 0xff, 0xff, 0xf0, 0x04, 0xff, 0xff, + 0x6c, 0x05, 0xff, 0xff, 0x18, 0x06, 0xff, 0xff, 0x1c, 0x06, 0xff, 0xff, + 0x1c, 0x06, 0xff, 0xff, 0x74, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xa0, 0x06, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, 0xb4, 0x01, 0xff, 0xff, + 0xd0, 0x06, 0xff, 0xff, 0x00, 0x40, 0xbd, 0xe8, 0xd3, 0x40, 0xa0, 0xe3, + 0x04, 0xf0, 0x29, 0xe1, 0x10, 0x00, 0xbd, 0xe8, 0x04, 0xf0, 0x69, 0xe1, + 0x10, 0x50, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, 0xf4, 0xff, 0xff, 0xea, + 0x01, 0x00, 0x50, 0xe2, 0xfd, 0xff, 0xff, 0xca, 0xf1, 0xff, 0xff, 0xea, + 0x11, 0x2f, 0x19, 0xee, 0xff, 0x20, 0xc2, 0xe3, 0x01, 0x29, 0x82, 0xe2, + 0x08, 0x00, 0x12, 0xe5, 0x08, 0x32, 0x83, 0xe5, 0x00, 0x00, 0x11, 0xe1, + 0x01, 0x00, 0xc0, 0xe1, 0x08, 0x00, 0x02, 0xe5, 0x01, 0xc0, 0xa0, 0xe3, + 0x08, 0xc2, 0x83, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0xa0, 0xe3, + 0x01, 0x10, 0xa0, 0xe3, 0x01, 0x33, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, + 0xef, 0xff, 0xff, 0x1b, 0x00, 0x00, 0xa0, 0xe3, 0x90, 0x0f, 0x07, 0xee, + 0xec, 0xff, 0xff, 0xeb, 0xfb, 0xff, 0xff, 0x0a, 0xdc, 0xff, 0xff, 0xea, + 0xe9, 0xff, 0xff, 0xeb, 0xda, 0xff, 0xff, 0x1a, 0xf7, 0xff, 0xff, 0xea, + 0x02, 0xc1, 0x10, 0xe2, 0x00, 0x00, 0x60, 0x42, 0x02, 0x31, 0x11, 0xe2, + 0x00, 0x10, 0x61, 0x42, 0x03, 0xc0, 0x2c, 0xe0, 0x00, 0x20, 0xa0, 0xe3, + 0x01, 0x30, 0xa0, 0xe3, 0x00, 0x00, 0x51, 0xe1, 0x81, 0x10, 0xa0, 0x91, + 0x83, 0x30, 0xa0, 0x91, 0xfb, 0xff, 0xff, 0x9a, 0x01, 0x00, 0x50, 0xe1, + 0x01, 0x00, 0x40, 0x20, 0x03, 0x20, 0x82, 0x21, 0xa3, 0x30, 0xb0, 0xe1, + 0xa1, 0x10, 0xa0, 0x31, 0xf9, 0xff, 0xff, 0x3a, 0x00, 0x10, 0xa0, 0xe1, + 0x02, 0x30, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe1, 0x02, 0x01, 0x1c, 0xe3, + 0x00, 0x00, 0x60, 0x42, 0xc2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, + 0x0e, 0xc6, 0xcc, 0xe3, 0x01, 0x03, 0x12, 0xe3, 0x0d, 0x00, 0x00, 0x1a, + 0x01, 0x04, 0x12, 0xe3, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, + 0x04, 0x00, 0x00, 0x1a, 0xb2, 0x30, 0xd0, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, + 0xb2, 0x30, 0xc1, 0xe0, 0xfb, 0xff, 0xff, 0x1a, 0xb5, 0xff, 0xff, 0xea, + 0xb0, 0x30, 0xd0, 0xe1, 0xb2, 0x30, 0xc1, 0xe0, 0x01, 0xc0, 0x5c, 0xe2, + 0xfc, 0xff, 0xff, 0x1a, 0xb0, 0xff, 0xff, 0xea, 0x01, 0x04, 0x12, 0xe3, + 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, 0x04, 0x00, 0x00, 0x1a, + 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0x04, 0x30, 0x81, 0xe4, + 0xfb, 0xff, 0xff, 0x1a, 0xa7, 0xff, 0xff, 0xea, 0x00, 0x30, 0x90, 0xe5, + 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, 0xfc, 0xff, 0xff, 0x1a, + 0xa2, 0xff, 0xff, 0xea, 0xff, 0xc4, 0xc2, 0xe3, 0x0e, 0xc6, 0xcc, 0xe3, + 0x01, 0x04, 0x12, 0xe3, 0x03, 0x00, 0xc0, 0xe3, 0x03, 0x10, 0xc1, 0xe3, + 0x04, 0x00, 0x00, 0x1a, 0x04, 0x30, 0x90, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, + 0x04, 0x30, 0x81, 0xe4, 0xfb, 0xff, 0xff, 0x1a, 0x97, 0xff, 0xff, 0xea, + 0x00, 0x30, 0x90, 0xe5, 0x04, 0x30, 0x81, 0xe4, 0x01, 0xc0, 0x5c, 0xe2, + 0xfc, 0xff, 0xff, 0x1a, 0x92, 0xff, 0xff, 0xea, 0x01, 0x11, 0xa0, 0xe3, + 0x00, 0x20, 0xa0, 0xe3, 0x02, 0x30, 0x81, 0xe1, 0x03, 0x00, 0x50, 0xe1, + 0x03, 0x00, 0x40, 0xa0, 0xa2, 0x20, 0xa0, 0xe1, 0x01, 0x20, 0x82, 0xa1, + 0x21, 0x11, 0xb0, 0xe1, 0xf8, 0xff, 0xff, 0x1a, 0x02, 0x00, 0xa0, 0xe1, + 0x87, 0xff, 0xff, 0xea, 0x00, 0x00, 0x01, 0xcc, 0x01, 0xd8, 0x00, 0x14, + 0x01, 0xf0, 0x00, 0x3c, 0x00, 0x28, 0x01, 0xe4, 0x01, 0xa0, 0x00, 0x6c, + 0x00, 0x78, 0x01, 0xb4, 0x00, 0x50, 0x01, 0x9c, 0x01, 0x88, 0x00, 0x44, + 0x20, 0x00, 0x2d, 0xe9, 0x1e, 0x40, 0xa0, 0xe3, 0x30, 0xe0, 0x4f, 0xe2, + 0xff, 0x04, 0xc0, 0xe3, 0xff, 0x08, 0xc0, 0xe3, 0xa2, 0x20, 0xb0, 0xe1, + 0x1e, 0x00, 0x00, 0x0a, 0xb2, 0x30, 0xd1, 0xe0, 0x80, 0x50, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x83, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, + 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x51, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, + 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, + 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0xa3, 0x53, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, 0x80, 0x50, 0x04, 0xe0, + 0xb5, 0xc0, 0x9e, 0xe1, 0x20, 0x02, 0xa0, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0xa3, 0x55, 0x04, 0xe0, 0xb5, 0xc0, 0x9e, 0xe1, 0x0c, 0x00, 0x20, 0xe0, + 0x01, 0x20, 0x52, 0xe2, 0xe0, 0xff, 0xff, 0x1a, 0x20, 0x00, 0xbd, 0xe8, + 0x57, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe3, 0x55, 0xff, 0xff, 0xea, + 0xe0, 0x03, 0x2d, 0xe9, 0xb0, 0x30, 0xd2, 0xe1, 0x02, 0xc0, 0xd2, 0xe5, + 0x03, 0xe0, 0xd2, 0xe5, 0x04, 0x40, 0x92, 0xe5, 0xa4, 0x2f, 0xa0, 0xe1, + 0x02, 0x41, 0xc4, 0xe3, 0x01, 0x50, 0xa0, 0xe3, 0x15, 0x5c, 0xa0, 0xe1, + 0x01, 0x50, 0x45, 0xe2, 0x01, 0x60, 0xa0, 0xe3, 0x00, 0x70, 0xa0, 0xe3, + 0x00, 0x80, 0xa0, 0xe3, 0x01, 0x00, 0x5c, 0xe3, 0x83, 0x31, 0xa0, 0x01, + 0x02, 0x00, 0x5c, 0xe3, 0x03, 0x31, 0xa0, 0x01, 0x04, 0x00, 0x5c, 0xe3, + 0x83, 0x30, 0xa0, 0x01, 0x01, 0x00, 0x56, 0xe3, 0x01, 0x60, 0xd0, 0x04, + 0x01, 0x6c, 0x86, 0x03, 0x05, 0x90, 0x06, 0xe0, 0x36, 0x6c, 0xa0, 0xe1, + 0x00, 0x00, 0x59, 0xe3, 0x01, 0x00, 0x12, 0x03, 0x04, 0x90, 0x89, 0x10, + 0x19, 0x78, 0x87, 0xe1, 0x0e, 0x80, 0x88, 0xe0, 0x20, 0x00, 0x58, 0xe3, + 0x04, 0x70, 0x81, 0x04, 0x00, 0x70, 0xa0, 0x03, 0x00, 0x80, 0xa0, 0x03, + 0x01, 0x30, 0x53, 0xe2, 0xef, 0xff, 0xff, 0x1a, 0xe0, 0x03, 0xbd, 0xe8, + 0x30, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xb0, 0xe1, + 0x2d, 0xff, 0xff, 0x0a, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x10, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x40, 0xdc, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x1d, 0xff, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0x83, 0x30, 0xb0, 0xe1, 0xed, 0xff, 0xff, 0x5a, + 0xea, 0xff, 0xff, 0xea, 0x01, 0x40, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0x40, 0xc1, 0xe4, 0x14, 0xff, 0xff, 0x0a, 0x83, 0x30, 0xb0, 0xe1, + 0xe6, 0xff, 0xff, 0x5a, 0xe3, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x23, 0x24, 0xb0, 0xe1, 0x0e, 0xff, 0xff, 0x0a, 0x20, 0x00, 0x2d, 0xe9, + 0x00, 0x50, 0xa0, 0xe3, 0x01, 0x30, 0xd0, 0xe4, 0x02, 0x35, 0x83, 0xe3, + 0x80, 0x00, 0x13, 0xe3, 0x15, 0x00, 0x00, 0x0a, 0x01, 0x40, 0xd0, 0xe4, + 0x01, 0xe0, 0xd0, 0xe4, 0x04, 0x44, 0x8e, 0xe1, 0x24, 0xe6, 0xa0, 0xe1, + 0x0f, 0xca, 0xc4, 0xe3, 0x03, 0xe0, 0x8e, 0xe2, 0x0c, 0xc0, 0x41, 0xe0, + 0x01, 0xc0, 0x4c, 0xe2, 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xdc, 0x04, + 0x01, 0x40, 0xdc, 0x14, 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, + 0x01, 0x10, 0x81, 0xe2, 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, + 0xf7, 0xfe, 0xff, 0x0a, 0x01, 0xe0, 0x5e, 0xe2, 0xf4, 0xff, 0xff, 0x1a, + 0x83, 0x30, 0xb0, 0xe1, 0xe8, 0xff, 0xff, 0x5a, 0xe5, 0xff, 0xff, 0xea, + 0x01, 0x00, 0x11, 0xe3, 0x01, 0x50, 0xd0, 0x04, 0x01, 0x40, 0xd0, 0x14, + 0x04, 0x44, 0x85, 0x11, 0xb1, 0x40, 0x41, 0x11, 0x01, 0x10, 0x81, 0xe2, + 0x01, 0x20, 0x52, 0xe2, 0x20, 0x00, 0xbd, 0x08, 0xe9, 0xfe, 0xff, 0x0a, + 0x83, 0x30, 0xb0, 0xe1, 0xdc, 0xff, 0xff, 0x5a, 0xd9, 0xff, 0xff, 0xea, + 0xe5, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0x23, 0x24, 0xa0, 0xe1, + 0x01, 0x30, 0xd0, 0xe4, 0x80, 0x00, 0x13, 0xe3, 0x7f, 0x30, 0x03, 0xe2, + 0x07, 0x00, 0x00, 0x0a, 0x01, 0xc0, 0xd0, 0xe4, 0x03, 0x30, 0x83, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0x01, 0x20, 0x52, 0xe2, 0xda, 0xfe, 0xff, 0x0a, + 0x01, 0x30, 0x53, 0xe2, 0xfa, 0xff, 0xff, 0x1a, 0xf3, 0xff, 0xff, 0xea, + 0x01, 0x30, 0x83, 0xe2, 0x01, 0xc0, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x01, 0xc0, 0xc1, 0xe4, 0xd2, 0xfe, 0xff, 0x0a, 0x01, 0x30, 0x53, 0xe2, + 0xf9, 0xff, 0xff, 0x1a, 0xeb, 0xff, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, + 0x01, 0xc0, 0xd0, 0xe4, 0x23, 0x24, 0xa0, 0xe1, 0x01, 0xc0, 0xc1, 0xe4, + 0x01, 0x20, 0x42, 0xe2, 0x01, 0x30, 0xd0, 0xe4, 0x01, 0x20, 0x52, 0xe2, + 0x03, 0xc0, 0x8c, 0xe0, 0x01, 0xc0, 0xc1, 0xe4, 0xfa, 0xff, 0xff, 0x1a, + 0xc4, 0xfe, 0xff, 0xea, 0x04, 0x30, 0x90, 0xe4, 0xb2, 0xc0, 0xd0, 0xe0, + 0x23, 0x24, 0xa0, 0xe1, 0xb2, 0xc0, 0xc1, 0xe0, 0x01, 0x20, 0xc2, 0xe3, + 0x02, 0x20, 0x42, 0xe2, 0xb2, 0x30, 0xd0, 0xe0, 0x02, 0x20, 0x52, 0xe2, + 0x03, 0xc0, 0x8c, 0xe0, 0xb2, 0xc0, 0xc1, 0xe0, 0xfa, 0xff, 0xff, 0x1a, + 0xb8, 0xfe, 0xff, 0xea, 0x01, 0x13, 0xa0, 0xe3, 0x00, 0x03, 0xc1, 0xe5, + 0xb5, 0xfe, 0xff, 0xea, 0x0f, 0x50, 0x2d, 0xe9, 0x11, 0x0f, 0x19, 0xee, + 0xff, 0x00, 0xc0, 0xe3, 0x01, 0x09, 0x80, 0xe2, 0x0f, 0xe0, 0xa0, 0xe1, + 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, + 0xdc, 0xff, 0x80, 0x03, 0x01, 0x03, 0xa0, 0xe3, 0x80, 0x10, 0xa0, 0xe3, + 0x00, 0x20, 0xa0, 0xe3, 0x04, 0x20, 0x20, 0xe5, 0x01, 0x10, 0x51, 0xe2, + 0xfc, 0xff, 0xff, 0x1a, 0x24, 0x10, 0x1f, 0xe5, 0xd3, 0x30, 0xa0, 0xe3, + 0x03, 0xf0, 0x2f, 0xe1, 0x01, 0xd0, 0xa0, 0xe1, 0x02, 0xe0, 0xa0, 0xe1, + 0x02, 0xf0, 0x6f, 0xe1, 0xd2, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, + 0x2c, 0xd0, 0x41, 0xe2, 0x02, 0xe0, 0xa0, 0xe1, 0x02, 0xf0, 0x6f, 0xe1, + 0x5f, 0x30, 0xa0, 0xe3, 0x03, 0xf0, 0x2f, 0xe1, 0xdc, 0xd0, 0x41, 0xe2, + 0xff, 0x1f, 0x90, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; } \ No newline at end of file diff --git a/src/FreeBIOS.h b/src/FreeBIOS.h index ce90725d..a60c1f88 100644 --- a/src/FreeBIOS.h +++ b/src/FreeBIOS.h @@ -28,10 +28,13 @@ #ifndef FREEBIOS_H #define FREEBIOS_H +#include +#include "MemConstants.h" + namespace melonDS { -extern unsigned char bios_arm7_bin[16384]; -extern unsigned char bios_arm9_bin[4096]; +extern std::array bios_arm7_bin; +extern std::array bios_arm9_bin; } #endif // FREEBIOS_H diff --git a/src/GBACart.cpp b/src/GBACart.cpp index f5e320f9..1be50e75 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -1,903 +1,878 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#include -#include -#include -#include "NDS.h" -#include "GBACart.h" -#include "CRC32.h" -#include "Platform.h" - -namespace melonDS -{ -using Platform::Log; -using Platform::LogLevel; - -namespace GBACart -{ - -const char SOLAR_SENSOR_GAMECODES[10][5] = -{ - "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan) - "U3IE", // Boktai - The Sun Is in Your Hand (USA) - "U3IP", // Boktai - The Sun Is in Your Hand (Europe) - "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan) - "U32E", // Boktai 2 - Solar Boy Django (USA) - "U32P", // Boktai 2 - Solar Boy Django (Europe) - "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan) - "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample) -}; - -CartCommon::CartCommon() -{ -} - -CartCommon::~CartCommon() -{ -} - -void CartCommon::Reset() -{ -} - -void CartCommon::DoSavestate(Savestate* file) -{ - file->Section("GBCS"); -} - -void CartCommon::SetupSave(u32 type) -{ -} - -void CartCommon::LoadSave(const u8* savedata, u32 savelen) -{ -} - -int CartCommon::SetInput(int num, bool pressed) -{ - return -1; -} - -u16 CartCommon::ROMRead(u32 addr) const -{ - return 0; -} - -void CartCommon::ROMWrite(u32 addr, u16 val) -{ -} - -u8 CartCommon::SRAMRead(u32 addr) -{ - return 0; -} - -void CartCommon::SRAMWrite(u32 addr, u8 val) -{ -} - -u8* CartCommon::GetSaveMemory() const -{ - return nullptr; -} - -u32 CartCommon::GetSaveMemoryLength() const -{ - return 0; -} - -CartGame::CartGame(u8* rom, u32 len) : CartCommon() -{ - ROM = rom; - ROMLength = len; - - SRAM = nullptr; - SRAMLength = 0; - SRAMType = S_NULL; - SRAMFlashState = {}; -} - -CartGame::~CartGame() -{ - if (SRAM) delete[] SRAM; - delete[] ROM; -} - -u32 CartGame::Checksum() const -{ - u32 crc = CRC32(ROM, 0xC0, 0); - - // TODO: hash more contents? - - return crc; -} - -void CartGame::Reset() -{ - memset(&GPIO, 0, sizeof(GPIO)); -} - -void CartGame::DoSavestate(Savestate* file) -{ - CartCommon::DoSavestate(file); - - file->Var16(&GPIO.control); - file->Var16(&GPIO.data); - file->Var16(&GPIO.direction); - - u32 oldlen = SRAMLength; - - file->Var32(&SRAMLength); - - if (SRAMLength != oldlen) - { - // reallocate save memory - if (oldlen) delete[] SRAM; - SRAM = nullptr; - if (SRAMLength) SRAM = new u8[SRAMLength]; - } - if (SRAMLength) - { - // fill save memory if data is present - file->VarArray(SRAM, SRAMLength); - } - else - { - // no save data, clear the current state - SRAMType = SaveType::S_NULL; - SRAM = nullptr; - return; - } - - // persist some extra state info - file->Var8(&SRAMFlashState.bank); - file->Var8(&SRAMFlashState.cmd); - file->Var8(&SRAMFlashState.device); - file->Var8(&SRAMFlashState.manufacturer); - file->Var8(&SRAMFlashState.state); - - file->Var8((u8*)&SRAMType); - - if ((!file->Saving) && SRAM) - Platform::WriteGBASave(SRAM, SRAMLength, 0, SRAMLength); -} - -void CartGame::SetupSave(u32 type) -{ - if (SRAM) delete[] SRAM; - SRAM = nullptr; - - // TODO: have type be determined from some list, like in NDSCart - // and not this gross hack!! - SRAMLength = type; - - if (SRAMLength) - { - SRAM = new u8[SRAMLength]; - memset(SRAM, 0xFF, SRAMLength); - } - - switch (SRAMLength) - { - case 512: - SRAMType = S_EEPROM4K; - break; - case 8192: - SRAMType = S_EEPROM64K; - break; - case 32768: - SRAMType = S_SRAM256K; - break; - case 65536: - SRAMType = S_FLASH512K; - break; - case 128*1024: - SRAMType = S_FLASH1M; - break; - case 0: - SRAMType = S_NULL; - break; - default: - Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength); - } - - if (SRAMType == S_FLASH512K) - { - // Panasonic 64K chip - SRAMFlashState.device = 0x1B; - SRAMFlashState.manufacturer = 0x32; - } - else if (SRAMType == S_FLASH1M) - { - // Sanyo 128K chip - SRAMFlashState.device = 0x13; - SRAMFlashState.manufacturer = 0x62; - } -} - -void CartGame::LoadSave(const u8* savedata, u32 savelen) -{ - if (!SRAM) return; - - u32 len = std::min(savelen, SRAMLength); - memcpy(SRAM, savedata, len); - Platform::WriteGBASave(savedata, len, 0, len); -} - -u16 CartGame::ROMRead(u32 addr) const -{ - addr &= 0x01FFFFFF; - - if (addr >= 0xC4 && addr < 0xCA) - { - if (GPIO.control & 0x1) - { - switch (addr) - { - case 0xC4: return GPIO.data; - case 0xC6: return GPIO.direction; - case 0xC8: return GPIO.control; - } - } - else - return 0; - } - - // CHECKME: does ROM mirror? - if (addr < ROMLength) - return *(u16*)&ROM[addr]; - - return 0; -} - -void CartGame::ROMWrite(u32 addr, u16 val) -{ - addr &= 0x01FFFFFF; - - switch (addr) - { - case 0xC4: - GPIO.data &= ~GPIO.direction; - GPIO.data |= val & GPIO.direction; - ProcessGPIO(); - break; - - case 0xC6: - GPIO.direction = val; - break; - - case 0xC8: - GPIO.control = val; - break; - - default: - Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr); - break; - } -} - -u8 CartGame::SRAMRead(u32 addr) -{ - addr &= 0xFFFF; - - switch (SRAMType) - { - case S_EEPROM4K: - case S_EEPROM64K: - return SRAMRead_EEPROM(addr); - - case S_FLASH512K: - case S_FLASH1M: - return SRAMRead_FLASH(addr); - - case S_SRAM256K: - return SRAMRead_SRAM(addr); - default: - break; - } - - return 0xFF; -} - -void CartGame::SRAMWrite(u32 addr, u8 val) -{ - addr &= 0xFFFF; - - switch (SRAMType) - { - case S_EEPROM4K: - case S_EEPROM64K: - return SRAMWrite_EEPROM(addr, val); - - case S_FLASH512K: - case S_FLASH1M: - return SRAMWrite_FLASH(addr, val); - - case S_SRAM256K: - return SRAMWrite_SRAM(addr, val); - default: - break; - } -} - -u8* CartGame::GetSaveMemory() const -{ - return SRAM; -} - -u32 CartGame::GetSaveMemoryLength() const -{ - return SRAMLength; -} - -void CartGame::ProcessGPIO() -{ -} - -u8 CartGame::SRAMRead_EEPROM(u32 addr) -{ - return 0; -} - -void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val) -{ - // TODO: could be used in homebrew? -} - -// mostly ported from DeSmuME -u8 CartGame::SRAMRead_FLASH(u32 addr) -{ - if (SRAMFlashState.cmd == 0) // no cmd - { - return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank]; - } - - switch (SRAMFlashState.cmd) - { - case 0x90: // chip ID - if (addr == 0x0000) return SRAMFlashState.manufacturer; - if (addr == 0x0001) return SRAMFlashState.device; - break; - case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?) - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - break; - case 0xA0: // write command - break; // ignore here, handled in Write_Flash() - case 0xB0: // bank switching (128K only) - break; // ignore here, handled in Write_Flash() - default: - Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr); - break; - } - - return 0xFF; -} - -// mostly ported from DeSmuME -void CartGame::SRAMWrite_FLASH(u32 addr, u8 val) -{ - switch (SRAMFlashState.state) - { - case 0x00: - if (addr == 0x5555) - { - if (val == 0xF0) - { - // reset - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - } - else if (val == 0xAA) - { - SRAMFlashState.state = 1; - return; - } - } - if (addr == 0x0000) - { - if (SRAMFlashState.cmd == 0xB0) - { - // bank switching - SRAMFlashState.bank = val; - SRAMFlashState.cmd = 0; - return; - } - } - break; - case 0x01: - if (addr == 0x2AAA && val == 0x55) - { - SRAMFlashState.state = 2; - return; - } - SRAMFlashState.state = 0; - break; - case 0x02: - if (addr == 0x5555) - { - // send command - switch (val) - { - case 0x80: // erase - SRAMFlashState.state = 0x80; - break; - case 0x90: // chip ID - SRAMFlashState.state = 0x90; - break; - case 0xA0: // write - SRAMFlashState.state = 0; - break; - default: - SRAMFlashState.state = 0; - break; - } - - SRAMFlashState.cmd = val; - return; - } - SRAMFlashState.state = 0; - break; - // erase - case 0x80: - if (addr == 0x5555 && val == 0xAA) - { - SRAMFlashState.state = 0x81; - return; - } - SRAMFlashState.state = 0; - break; - case 0x81: - if (addr == 0x2AAA && val == 0x55) - { - SRAMFlashState.state = 0x82; - return; - } - SRAMFlashState.state = 0; - break; - case 0x82: - if (val == 0x30) - { - u32 start_addr = addr + 0x10000 * SRAMFlashState.bank; - memset((u8*)&SRAM[start_addr], 0xFF, 0x1000); - - Platform::WriteGBASave(SRAM, SRAMLength, start_addr, 0x1000); - } - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - // chip ID - case 0x90: - if (addr == 0x5555 && val == 0xAA) - { - SRAMFlashState.state = 0x91; - return; - } - SRAMFlashState.state = 0; - break; - case 0x91: - if (addr == 0x2AAA && val == 0x55) - { - SRAMFlashState.state = 0x92; - return; - } - SRAMFlashState.state = 0; - break; - case 0x92: - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - default: - break; - } - - if (SRAMFlashState.cmd == 0xA0) // write - { - SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val); - SRAMFlashState.state = 0; - SRAMFlashState.cmd = 0; - return; - } - - Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n", - val, addr, SRAMFlashState.state); -} - -u8 CartGame::SRAMRead_SRAM(u32 addr) -{ - if (addr >= SRAMLength) return 0xFF; - - return SRAM[addr]; -} - -void CartGame::SRAMWrite_SRAM(u32 addr, u8 val) -{ - if (addr >= SRAMLength) return; - - u8 prev = *(u8*)&SRAM[addr]; - if (prev != val) - { - *(u8*)&SRAM[addr] = val; - - // TODO: optimize this!! - Platform::WriteGBASave(SRAM, SRAMLength, addr, 1); - } -} - - -const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183}; - -CartGameSolarSensor::CartGameSolarSensor(u8* rom, u32 len) : CartGame(rom, len) -{ -} - -CartGameSolarSensor::~CartGameSolarSensor() -{ -} - -void CartGameSolarSensor::Reset() -{ - LightEdge = false; - LightCounter = 0; - LightSample = 0xFF; - LightLevel = 0; -} - -void CartGameSolarSensor::DoSavestate(Savestate* file) -{ - CartGame::DoSavestate(file); - - file->Var8((u8*)&LightEdge); - file->Var8(&LightCounter); - file->Var8(&LightSample); - file->Var8(&LightLevel); -} - -int CartGameSolarSensor::SetInput(int num, bool pressed) -{ - if (!pressed) return -1; - - if (num == Input_SolarSensorDown) - { - if (LightLevel > 0) - LightLevel--; - - return LightLevel; - } - else if (num == Input_SolarSensorUp) - { - if (LightLevel < 10) - LightLevel++; - - return LightLevel; - } - - return -1; -} - -void CartGameSolarSensor::ProcessGPIO() -{ - if (GPIO.data & 4) return; // Boktai chip select - if (GPIO.data & 2) // Reset - { - u8 prev = LightSample; - LightCounter = 0; - LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel])); - Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample); - } - if (GPIO.data & 1 && LightEdge) LightCounter++; - - LightEdge = !(GPIO.data & 1); - - bool sendBit = LightCounter >= LightSample; - if (GPIO.control & 1) - { - GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF); - } -} - - -CartRAMExpansion::CartRAMExpansion() : CartCommon() -{ -} - -CartRAMExpansion::~CartRAMExpansion() -{ -} - -void CartRAMExpansion::Reset() -{ - memset(RAM, 0xFF, sizeof(RAM)); - RAMEnable = 1; -} - -void CartRAMExpansion::DoSavestate(Savestate* file) -{ - CartCommon::DoSavestate(file); - - file->VarArray(RAM, sizeof(RAM)); - file->Var16(&RAMEnable); -} - -u16 CartRAMExpansion::ROMRead(u32 addr) const -{ - addr &= 0x01FFFFFF; - - if (addr < 0x01000000) - { - switch (addr) - { - case 0xB0: return 0xFFFF; - case 0xB2: return 0x0000; - case 0xB4: return 0x2400; - case 0xB6: return 0x2424; - case 0xB8: return 0xFFFF; - case 0xBA: return 0xFFFF; - case 0xBC: return 0xFFFF; - case 0xBE: return 0x7FFF; - - case 0x1FFFC: return 0xFFFF; - case 0x1FFFE: return 0x7FFF; - - case 0x240000: return RAMEnable; - case 0x240002: return 0x0000; - } - - return 0xFFFF; - } - else if (addr < 0x01800000) - { - if (!RAMEnable) return 0xFFFF; - - return *(u16*)&RAM[addr & 0x7FFFFF]; - } - - return 0xFFFF; -} - -void CartRAMExpansion::ROMWrite(u32 addr, u16 val) -{ - addr &= 0x01FFFFFF; - - if (addr < 0x01000000) - { - switch (addr) - { - case 0x240000: - RAMEnable = val & 0x0001; - return; - } - } - else if (addr < 0x01800000) - { - if (!RAMEnable) return; - - *(u16*)&RAM[addr & 0x7FFFFF] = val; - } -} - -void GBACartSlot::Reset() noexcept -{ - if (Cart) Cart->Reset(); -} - -void GBACartSlot::DoSavestate(Savestate* file) noexcept -{ - file->Section("GBAC"); // Game Boy Advance Cartridge - - // little state here - // no need to save OpenBusDecay, it will be set later - - u32 carttype = 0; - u32 cartchk = 0; - if (Cart) - { - carttype = Cart->Type(); - cartchk = Cart->Checksum(); - } - - if (file->Saving) - { - file->Var32(&carttype); - file->Var32(&cartchk); - } - else - { - u32 savetype; - file->Var32(&savetype); - if (savetype != carttype) return; - - u32 savechk; - file->Var32(&savechk); - if (savechk != cartchk) return; - } - - if (Cart) Cart->DoSavestate(file); -} - - -std::unique_ptr ParseROM(const u8* romdata, u32 romlen) -{ - if (romdata == nullptr) - { - Log(LogLevel::Error, "GBACart: romdata is null\n"); - return nullptr; - } - - if (romlen == 0) - { - Log(LogLevel::Error, "GBACart: romlen is zero\n"); - return nullptr; - } - - u32 cartromsize = 0x200; - while (cartromsize < romlen) - cartromsize <<= 1; - - u8* cartrom = nullptr; - try - { - cartrom = new u8[cartromsize]; - } - catch (const std::bad_alloc& e) - { - Log(LogLevel::Error, "GBACart: failed to allocate memory for ROM (%d bytes)\n", cartromsize); - - return nullptr; - } - - memset(cartrom, 0, cartromsize); - memcpy(cartrom, romdata, romlen); - - char gamecode[5] = { '\0' }; - memcpy(&gamecode, cartrom + 0xAC, 4); - - bool solarsensor = false; - for (const char* i : SOLAR_SENSOR_GAMECODES) - { - if (strcmp(gamecode, i) == 0) - solarsensor = true; - } - - if (solarsensor) - { - Log(LogLevel::Info, "GBA solar sensor support detected!\n"); - } - - std::unique_ptr cart; - if (solarsensor) - cart = std::make_unique(cartrom, cartromsize); - else - cart = std::make_unique(cartrom, cartromsize); - - cart->Reset(); - - // TODO: setup cart save here! from a list or something - - // save - //printf("GBA save file: %s\n", sram); - - // TODO: have a list of sorts like in NDSCart? to determine the savemem type - //if (Cart) Cart->LoadSave(sram, 0); - - return cart; -} - -bool GBACartSlot::InsertROM(std::unique_ptr&& cart) noexcept -{ - if (!cart) { - Log(LogLevel::Error, "Failed to insert invalid GBA cart; existing cart (if any) was not ejected.\n"); - return false; - } - - if (Cart != nullptr) - EjectCart(); - - Cart = std::move(cart); - - const u8* cartrom = Cart->GetROM(); - - if (cartrom) - { - char gamecode[5] = { '\0' }; - memcpy(&gamecode, Cart->GetROM() + 0xAC, 4); - Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode); - } - else - { - Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n"); - } - - return true; -} - -bool GBACartSlot::LoadROM(const u8* romdata, u32 romlen) noexcept -{ - std::unique_ptr data = ParseROM(romdata, romlen); - - return InsertROM(std::move(data)); -} - -void GBACartSlot::LoadSave(const u8* savedata, u32 savelen) noexcept -{ - if (Cart) - { - // gross hack - Cart->SetupSave(savelen); - - Cart->LoadSave(savedata, savelen); - } -} - -void GBACartSlot::LoadAddon(int type) noexcept -{ - switch (type) - { - case GBAAddon_RAMExpansion: - Cart = std::make_unique(); - break; - - default: - Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); - return; - } -} - -void GBACartSlot::EjectCart() noexcept -{ - Cart = nullptr; -} - - -int GBACartSlot::SetInput(int num, bool pressed) noexcept -{ - if (Cart) return Cart->SetInput(num, pressed); - - return -1; -} - - -u16 GBACartSlot::ROMRead(u32 addr) const noexcept -{ - if (Cart) return Cart->ROMRead(addr); - - return ((addr >> 1) & 0xFFFF) | OpenBusDecay; -} - -void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept -{ - if (Cart) Cart->ROMWrite(addr, val); -} - -u8 GBACartSlot::SRAMRead(u32 addr) noexcept -{ - if (Cart) return Cart->SRAMRead(addr); - - return 0xFF; -} - -void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept -{ - if (Cart) Cart->SRAMWrite(addr, val); -} - -} - +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include +#include "NDS.h" +#include "GBACart.h" +#include "CRC32.h" +#include "Platform.h" +#include "Utils.h" + +namespace melonDS +{ +using Platform::Log; +using Platform::LogLevel; + +namespace GBACart +{ + +const char SOLAR_SENSOR_GAMECODES[10][5] = +{ + "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan) + "U3IE", // Boktai - The Sun Is in Your Hand (USA) + "U3IP", // Boktai - The Sun Is in Your Hand (Europe) + "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan) + "U32E", // Boktai 2 - Solar Boy Django (USA) + "U32P", // Boktai 2 - Solar Boy Django (Europe) + "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan) + "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample) +}; + +CartCommon::CartCommon(GBACart::CartType type) : CartType(type) +{ +} + +void CartCommon::Reset() +{ +} + +void CartCommon::DoSavestate(Savestate* file) +{ + file->Section("GBCS"); +} + +void CartCommon::SetSaveMemory(const u8* savedata, u32 savelen) +{ +} + +int CartCommon::SetInput(int num, bool pressed) +{ + return -1; +} + +u16 CartCommon::ROMRead(u32 addr) const +{ + return 0; +} + +void CartCommon::ROMWrite(u32 addr, u16 val) +{ +} + +u8 CartCommon::SRAMRead(u32 addr) +{ + return 0; +} + +void CartCommon::SRAMWrite(u32 addr, u8 val) +{ +} + +u8* CartCommon::GetSaveMemory() const +{ + return nullptr; +} + +u32 CartCommon::GetSaveMemoryLength() const +{ + return 0; +} + +CartGame::CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type) : + CartGame(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, type) +{ +} + +CartGame::CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, GBACart::CartType type) : + CartCommon(type), + ROM(std::move(rom)), + ROMLength(len), + SRAM(std::move(sram)), + SRAMLength(sramlen) +{ + if (SRAM && SRAMLength) + { + SetupSave(sramlen); + } +} + +CartGame::~CartGame() = default; +// unique_ptr cleans up the allocated memory + +u32 CartGame::Checksum() const +{ + u32 crc = CRC32(ROM.get(), 0xC0, 0); + + // TODO: hash more contents? + + return crc; +} + +void CartGame::Reset() +{ + memset(&GPIO, 0, sizeof(GPIO)); +} + +void CartGame::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + + file->Var16(&GPIO.control); + file->Var16(&GPIO.data); + file->Var16(&GPIO.direction); + + u32 oldlen = SRAMLength; + + file->Var32(&SRAMLength); + + if (SRAMLength != oldlen) + { + // reallocate save memory + SRAM = SRAMLength ? std::make_unique(SRAMLength) : nullptr; + } + if (SRAMLength) + { + // fill save memory if data is present + file->VarArray(SRAM.get(), SRAMLength); + } + else + { + // no save data, clear the current state + SRAMType = SaveType::S_NULL; + SRAM = nullptr; + return; + } + + // persist some extra state info + file->Var8(&SRAMFlashState.bank); + file->Var8(&SRAMFlashState.cmd); + file->Var8(&SRAMFlashState.device); + file->Var8(&SRAMFlashState.manufacturer); + file->Var8(&SRAMFlashState.state); + + file->Var8((u8*)&SRAMType); + + if ((!file->Saving) && SRAM) + Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength); +} + +void CartGame::SetupSave(u32 type) +{ + // TODO: have type be determined from some list, like in NDSCart + // and not this gross hack!! + SRAMLength = type; + switch (SRAMLength) + { + case 512: + SRAMType = S_EEPROM4K; + break; + case 8192: + SRAMType = S_EEPROM64K; + break; + case 32768: + SRAMType = S_SRAM256K; + break; + case 65536: + SRAMType = S_FLASH512K; + break; + case 128*1024: + case (128*1024 + 0x10): // .sav file with appended real time clock data (ex: emulator mGBA) + SRAMType = S_FLASH1M; + break; + case 0: + SRAMType = S_NULL; + break; + default: + Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength); + } + + if (SRAMType == S_FLASH512K) + { + // Panasonic 64K chip + SRAMFlashState.device = 0x1B; + SRAMFlashState.manufacturer = 0x32; + } + else if (SRAMType == S_FLASH1M) + { + // Sanyo 128K chip + SRAMFlashState.device = 0x13; + SRAMFlashState.manufacturer = 0x62; + } +} + +void CartGame::SetSaveMemory(const u8* savedata, u32 savelen) +{ + SetupSave(savelen); + + u32 len = std::min(savelen, SRAMLength); + memcpy(SRAM.get(), savedata, len); + Platform::WriteGBASave(savedata, len, 0, len); +} + +u16 CartGame::ROMRead(u32 addr) const +{ + addr &= 0x01FFFFFF; + + if (addr >= 0xC4 && addr < 0xCA) + { + if (GPIO.control & 0x1) + { + switch (addr) + { + case 0xC4: return GPIO.data; + case 0xC6: return GPIO.direction; + case 0xC8: return GPIO.control; + } + } + else + return 0; + } + + // CHECKME: does ROM mirror? + if (addr < ROMLength) + return *(u16*)&ROM[addr]; + + return 0; +} + +void CartGame::ROMWrite(u32 addr, u16 val) +{ + addr &= 0x01FFFFFF; + + switch (addr) + { + case 0xC4: + GPIO.data &= ~GPIO.direction; + GPIO.data |= val & GPIO.direction; + ProcessGPIO(); + break; + + case 0xC6: + GPIO.direction = val; + break; + + case 0xC8: + GPIO.control = val; + break; + + default: + Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr); + break; + } +} + +u8 CartGame::SRAMRead(u32 addr) +{ + addr &= 0xFFFF; + + switch (SRAMType) + { + case S_EEPROM4K: + case S_EEPROM64K: + return SRAMRead_EEPROM(addr); + + case S_FLASH512K: + case S_FLASH1M: + return SRAMRead_FLASH(addr); + + case S_SRAM256K: + return SRAMRead_SRAM(addr); + default: + break; + } + + return 0xFF; +} + +void CartGame::SRAMWrite(u32 addr, u8 val) +{ + addr &= 0xFFFF; + + switch (SRAMType) + { + case S_EEPROM4K: + case S_EEPROM64K: + return SRAMWrite_EEPROM(addr, val); + + case S_FLASH512K: + case S_FLASH1M: + return SRAMWrite_FLASH(addr, val); + + case S_SRAM256K: + return SRAMWrite_SRAM(addr, val); + default: + break; + } +} + +u8* CartGame::GetSaveMemory() const +{ + return SRAM.get(); +} + +u32 CartGame::GetSaveMemoryLength() const +{ + return SRAMLength; +} + +void CartGame::ProcessGPIO() +{ +} + +u8 CartGame::SRAMRead_EEPROM(u32 addr) +{ + return 0; +} + +void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val) +{ + // TODO: could be used in homebrew? +} + +// mostly ported from DeSmuME +u8 CartGame::SRAMRead_FLASH(u32 addr) +{ + if (SRAMFlashState.cmd == 0) // no cmd + { + return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank]; + } + + switch (SRAMFlashState.cmd) + { + case 0x90: // chip ID + if (addr == 0x0000) return SRAMFlashState.manufacturer; + if (addr == 0x0001) return SRAMFlashState.device; + break; + case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?) + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + break; + case 0xA0: // write command + break; // ignore here, handled in Write_Flash() + case 0xB0: // bank switching (128K only) + break; // ignore here, handled in Write_Flash() + default: + Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr); + break; + } + + return 0xFF; +} + +// mostly ported from DeSmuME +void CartGame::SRAMWrite_FLASH(u32 addr, u8 val) +{ + switch (SRAMFlashState.state) + { + case 0x00: + if (addr == 0x5555) + { + if (val == 0xF0) + { + // reset + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + } + else if (val == 0xAA) + { + SRAMFlashState.state = 1; + return; + } + } + if (addr == 0x0000) + { + if (SRAMFlashState.cmd == 0xB0) + { + // bank switching + SRAMFlashState.bank = val; + SRAMFlashState.cmd = 0; + return; + } + } + break; + case 0x01: + if (addr == 0x2AAA && val == 0x55) + { + SRAMFlashState.state = 2; + return; + } + SRAMFlashState.state = 0; + break; + case 0x02: + if (addr == 0x5555) + { + // send command + switch (val) + { + case 0x80: // erase + SRAMFlashState.state = 0x80; + break; + case 0x90: // chip ID + SRAMFlashState.state = 0x90; + break; + case 0xA0: // write + SRAMFlashState.state = 0; + break; + default: + SRAMFlashState.state = 0; + break; + } + + SRAMFlashState.cmd = val; + return; + } + SRAMFlashState.state = 0; + break; + // erase + case 0x80: + if (addr == 0x5555 && val == 0xAA) + { + SRAMFlashState.state = 0x81; + return; + } + SRAMFlashState.state = 0; + break; + case 0x81: + if (addr == 0x2AAA && val == 0x55) + { + SRAMFlashState.state = 0x82; + return; + } + SRAMFlashState.state = 0; + break; + case 0x82: + if (val == 0x30) + { + u32 start_addr = addr + 0x10000 * SRAMFlashState.bank; + memset((u8*)&SRAM[start_addr], 0xFF, 0x1000); + + Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000); + } + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + // chip ID + case 0x90: + if (addr == 0x5555 && val == 0xAA) + { + SRAMFlashState.state = 0x91; + return; + } + SRAMFlashState.state = 0; + break; + case 0x91: + if (addr == 0x2AAA && val == 0x55) + { + SRAMFlashState.state = 0x92; + return; + } + SRAMFlashState.state = 0; + break; + case 0x92: + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + default: + break; + } + + if (SRAMFlashState.cmd == 0xA0) // write + { + SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val); + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + } + + Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n", + val, addr, SRAMFlashState.state); +} + +u8 CartGame::SRAMRead_SRAM(u32 addr) +{ + if (addr >= SRAMLength) return 0xFF; + + return SRAM[addr]; +} + +void CartGame::SRAMWrite_SRAM(u32 addr, u8 val) +{ + if (addr >= SRAMLength) return; + + u8 prev = *(u8*)&SRAM[addr]; + if (prev != val) + { + *(u8*)&SRAM[addr] = val; + + // TODO: optimize this!! + Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1); + } +} + + +CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen) : + CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen) +{ +} + +CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen) : + CartGame(std::move(rom), len, std::move(sram), sramlen, CartType::GameSolarSensor) +{ +} + +const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183}; + +void CartGameSolarSensor::Reset() +{ + CartGame::Reset(); + LightEdge = false; + LightCounter = 0; + LightSample = 0xFF; + LightLevel = 0; +} + +void CartGameSolarSensor::DoSavestate(Savestate* file) +{ + CartGame::DoSavestate(file); + + file->Var8((u8*)&LightEdge); + file->Var8(&LightCounter); + file->Var8(&LightSample); + file->Var8(&LightLevel); +} + +int CartGameSolarSensor::SetInput(int num, bool pressed) +{ + if (!pressed) return -1; + + if (num == Input_SolarSensorDown) + { + if (LightLevel > 0) + LightLevel--; + + return LightLevel; + } + else if (num == Input_SolarSensorUp) + { + if (LightLevel < 10) + LightLevel++; + + return LightLevel; + } + + return -1; +} + +void CartGameSolarSensor::ProcessGPIO() +{ + if (GPIO.data & 4) return; // Boktai chip select + if (GPIO.data & 2) // Reset + { + u8 prev = LightSample; + LightCounter = 0; + LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel])); + Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample); + } + if (GPIO.data & 1 && LightEdge) LightCounter++; + + LightEdge = !(GPIO.data & 1); + + bool sendBit = LightCounter >= LightSample; + if (GPIO.control & 1) + { + GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF); + } +} + + +CartRAMExpansion::CartRAMExpansion() : CartCommon(RAMExpansion) +{ +} + +CartRAMExpansion::~CartRAMExpansion() = default; + +void CartRAMExpansion::Reset() +{ + memset(RAM, 0xFF, sizeof(RAM)); + RAMEnable = 1; +} + +void CartRAMExpansion::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + + file->VarArray(RAM, sizeof(RAM)); + file->Var16(&RAMEnable); +} + +u16 CartRAMExpansion::ROMRead(u32 addr) const +{ + addr &= 0x01FFFFFF; + + if (addr < 0x01000000) + { + switch (addr) + { + case 0xB0: return 0xFFFF; + case 0xB2: return 0x0000; + case 0xB4: return 0x2400; + case 0xB6: return 0x2424; + case 0xB8: return 0xFFFF; + case 0xBA: return 0xFFFF; + case 0xBC: return 0xFFFF; + case 0xBE: return 0x7FFF; + + case 0x1FFFC: return 0xFFFF; + case 0x1FFFE: return 0x7FFF; + + case 0x240000: return RAMEnable; + case 0x240002: return 0x0000; + } + + return 0xFFFF; + } + else if (addr < 0x01800000) + { + if (!RAMEnable) return 0xFFFF; + + return *(u16*)&RAM[addr & 0x7FFFFF]; + } + + return 0xFFFF; +} + +void CartRAMExpansion::ROMWrite(u32 addr, u16 val) +{ + addr &= 0x01FFFFFF; + + if (addr < 0x01000000) + { + switch (addr) + { + case 0x240000: + RAMEnable = val & 0x0001; + return; + } + } + else if (addr < 0x01800000) + { + if (!RAMEnable) return; + + *(u16*)&RAM[addr & 0x7FFFFF] = val; + } +} + +GBACartSlot::GBACartSlot(std::unique_ptr&& cart) noexcept : Cart(std::move(cart)) +{ +} + +void GBACartSlot::Reset() noexcept +{ + if (Cart) Cart->Reset(); +} + +void GBACartSlot::DoSavestate(Savestate* file) noexcept +{ + file->Section("GBAC"); // Game Boy Advance Cartridge + + // little state here + // no need to save OpenBusDecay, it will be set later + + u32 carttype = 0; + u32 cartchk = 0; + if (Cart) + { + carttype = Cart->Type(); + cartchk = Cart->Checksum(); + } + + if (file->Saving) + { + file->Var32(&carttype); + file->Var32(&cartchk); + } + else + { + u32 savetype; + file->Var32(&savetype); + if (savetype != carttype) return; + + u32 savechk; + file->Var32(&savechk); + if (savechk != cartchk) return; + } + + if (Cart) Cart->DoSavestate(file); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen) +{ + return ParseROM(std::move(romdata), romlen, nullptr, 0); +} + +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen) +{ + auto [romcopy, romcopylen] = PadToPowerOf2(romdata, romlen); + + return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen); +} + +std::unique_ptr ParseROM(const u8* romdata, u32 romlen) +{ + return ParseROM(romdata, romlen, nullptr, 0); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen) +{ + if (romdata == nullptr) + { + Log(LogLevel::Error, "GBACart: romdata is null\n"); + return nullptr; + } + + if (romlen == 0) + { + Log(LogLevel::Error, "GBACart: romlen is zero\n"); + return nullptr; + } + + auto [cartrom, cartromsize] = PadToPowerOf2(std::move(romdata), romlen); + + char gamecode[5] = { '\0' }; + memcpy(&gamecode, cartrom.get() + 0xAC, 4); + + bool solarsensor = false; + for (const char* i : SOLAR_SENSOR_GAMECODES) + { + if (strcmp(gamecode, i) == 0) + solarsensor = true; + } + + if (solarsensor) + { + Log(LogLevel::Info, "GBA solar sensor support detected!\n"); + } + + std::unique_ptr cart; + if (solarsensor) + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + else + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + + cart->Reset(); + + // save + //printf("GBA save file: %s\n", sram); + + // TODO: have a list of sorts like in NDSCart? to determine the savemem type + //if (Cart) Cart->LoadSave(sram, 0); + + return cart; +} + +void GBACartSlot::SetCart(std::unique_ptr&& cart) noexcept +{ + Cart = std::move(cart); + + if (!Cart) + { + Log(LogLevel::Info, "Ejected GBA cart"); + return; + } + + const u8* cartrom = Cart->GetROM(); + + if (cartrom) + { + char gamecode[5] = { '\0' }; + memcpy(&gamecode, Cart->GetROM() + 0xAC, 4); + Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode); + } + else + { + Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n"); + } +} + +void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept +{ + if (Cart) + { + Cart->SetSaveMemory(savedata, savelen); + } +} + +void GBACartSlot::LoadAddon(int type) noexcept +{ + switch (type) + { + case GBAAddon_RAMExpansion: + Cart = std::make_unique(); + break; + + default: + Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); + return; + } +} + +std::unique_ptr GBACartSlot::EjectCart() noexcept +{ + return std::move(Cart); + // Cart will be nullptr after this function returns, due to the move +} + + +int GBACartSlot::SetInput(int num, bool pressed) noexcept +{ + if (Cart) return Cart->SetInput(num, pressed); + + return -1; +} + + +u16 GBACartSlot::ROMRead(u32 addr) const noexcept +{ + if (Cart) return Cart->ROMRead(addr); + + return ((addr >> 1) & 0xFFFF) | OpenBusDecay; +} + +void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept +{ + if (Cart) Cart->ROMWrite(addr, val); +} + +u8 GBACartSlot::SRAMRead(u32 addr) noexcept +{ + if (Cart) return Cart->SRAMRead(addr); + + return 0xFF; +} + +void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept +{ + if (Cart) Cart->SRAMWrite(addr, val); +} + +} + } \ No newline at end of file diff --git a/src/GBACart.h b/src/GBACart.h index a5570897..493bf6b8 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -1,257 +1,289 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#ifndef GBACART_H -#define GBACART_H - -#include -#include "types.h" -#include "Savestate.h" - -namespace melonDS::GBACart -{ - -enum CartType -{ - Default = 0x001, - Game = 0x101, - GameSolarSensor = 0x102, - RAMExpansion = 0x201, -}; - -// CartCommon -- base code shared by all cart types -class CartCommon -{ -public: - CartCommon(); - virtual ~CartCommon(); - - virtual u32 Type() const = 0; - virtual u32 Checksum() const { return 0; } - - virtual void Reset(); - - virtual void DoSavestate(Savestate* file); - - virtual void SetupSave(u32 type); - virtual void LoadSave(const u8* savedata, u32 savelen); - - virtual int SetInput(int num, bool pressed); - - virtual u16 ROMRead(u32 addr) const; - virtual void ROMWrite(u32 addr, u16 val); - - virtual u8 SRAMRead(u32 addr); - virtual void SRAMWrite(u32 addr, u8 val); - - [[nodiscard]] virtual const u8* GetROM() const { return nullptr; } - [[nodiscard]] virtual u32 GetROMLength() const { return 0; } - - virtual u8* GetSaveMemory() const; - virtual u32 GetSaveMemoryLength() const; -}; - -// CartGame -- regular retail game cart (ROM, SRAM) -class CartGame : public CartCommon -{ -public: - CartGame(u8* rom, u32 len); - virtual ~CartGame() override; - - virtual u32 Type() const override { return CartType::Game; } - virtual u32 Checksum() const override; - - virtual void Reset() override; - - virtual void DoSavestate(Savestate* file) override; - - virtual void SetupSave(u32 type) override; - virtual void LoadSave(const u8* savedata, u32 savelen) override; - - virtual u16 ROMRead(u32 addr) const override; - virtual void ROMWrite(u32 addr, u16 val) override; - - virtual u8 SRAMRead(u32 addr) override; - virtual void SRAMWrite(u32 addr, u8 val) override; - - [[nodiscard]] const u8* GetROM() const override { return ROM; } - [[nodiscard]] u32 GetROMLength() const override { return ROMLength; } - - virtual u8* GetSaveMemory() const override; - virtual u32 GetSaveMemoryLength() const override; -protected: - virtual void ProcessGPIO(); - - u8 SRAMRead_EEPROM(u32 addr); - void SRAMWrite_EEPROM(u32 addr, u8 val); - u8 SRAMRead_FLASH(u32 addr); - void SRAMWrite_FLASH(u32 addr, u8 val); - u8 SRAMRead_SRAM(u32 addr); - void SRAMWrite_SRAM(u32 addr, u8 val); - - u8* ROM; - u32 ROMLength; - - struct - { - u16 data; - u16 direction; - u16 control; - - } GPIO; - - enum SaveType - { - S_NULL, - S_EEPROM4K, - S_EEPROM64K, - S_SRAM256K, - S_FLASH512K, - S_FLASH1M - }; - - // from DeSmuME - struct - { - u8 state; - u8 cmd; - u8 device; - u8 manufacturer; - u8 bank; - - } SRAMFlashState; - - u8* SRAM; - u32 SRAMLength; - SaveType SRAMType; -}; - -// CartGameSolarSensor -- Boktai game cart -class CartGameSolarSensor : public CartGame -{ -public: - CartGameSolarSensor(u8* rom, u32 len); - virtual ~CartGameSolarSensor() override; - - virtual u32 Type() const override { return CartType::GameSolarSensor; } - - virtual void Reset() override; - - virtual void DoSavestate(Savestate* file) override; - - virtual int SetInput(int num, bool pressed) override; - -private: - virtual void ProcessGPIO() override; - - static const int kLuxLevels[11]; - - bool LightEdge; - u8 LightCounter; - u8 LightSample; - u8 LightLevel; -}; - -// CartRAMExpansion -- RAM expansion cart (DS browser, ...) -class CartRAMExpansion : public CartCommon -{ -public: - CartRAMExpansion(); - ~CartRAMExpansion() override; - - virtual u32 Type() const override { return CartType::RAMExpansion; } - - void Reset() override; - - void DoSavestate(Savestate* file) override; - - u16 ROMRead(u32 addr) const override; - void ROMWrite(u32 addr, u16 val) override; - -private: - u8 RAM[0x800000]; - u16 RAMEnable; -}; - -// possible inputs for GBA carts that might accept user input -enum -{ - Input_SolarSensorDown = 0, - Input_SolarSensorUp, -}; - -class GBACartSlot -{ -public: - GBACartSlot() noexcept = default; - ~GBACartSlot() noexcept = default; - void Reset() noexcept; - void DoSavestate(Savestate* file) noexcept; - /// Applies the GBACartData to the emulator state and unloads an existing ROM if any. - /// Upon successful insertion, \c cart will be nullptr and the global GBACart state - /// (\c CartROM, CartInserted, etc.) will be updated. - bool InsertROM(std::unique_ptr&& cart) noexcept; - bool LoadROM(const u8* romdata, u32 romlen) noexcept; - void LoadSave(const u8* savedata, u32 savelen) noexcept; - - void LoadAddon(int type) noexcept; - - void EjectCart() noexcept; - - // TODO: make more flexible, support nonbinary inputs - int SetInput(int num, bool pressed) noexcept; - - void SetOpenBusDecay(u16 val) noexcept { OpenBusDecay = val; } - - u16 ROMRead(u32 addr) const noexcept; - void ROMWrite(u32 addr, u16 val) noexcept; - - u8 SRAMRead(u32 addr) noexcept; - void SRAMWrite(u32 addr, u8 val) noexcept; - - /// This function is intended to allow frontends to save and load SRAM - /// without using melonDS APIs. - /// Modifying the emulated SRAM for any other reason is strongly discouraged. - /// The returned pointer may be invalidated if the emulator is reset, - /// or when a new game is loaded. - /// Consequently, don't store the returned pointer for any longer than necessary. - /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr. - [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } - [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } - - /// @returns The length of the buffer returned by ::GetSaveMemory() - /// if a cart is loaded and supports SRAM, otherwise zero. - [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } -private: - std::unique_ptr Cart = nullptr; - u16 OpenBusDecay = 0; -}; - -/// Parses the given ROM data and constructs a \c GBACart::CartCommon subclass -/// that can be inserted into the emulator or used to extract information about the cart beforehand. -/// @param romdata The ROM data to parse. -/// The returned cartridge will contain a copy of this data, -/// so the caller may deallocate \c romdata after this function returns. -/// @param romlen The length of the ROM data in bytes. -/// @returns A \c GBACart::CartCommon object representing the parsed ROM, -/// or \c nullptr if the ROM data couldn't be parsed. -std::unique_ptr ParseROM(const u8* romdata, u32 romlen); - -} - -#endif // GBACART_H +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef GBACART_H +#define GBACART_H + +#include +#include "types.h" +#include "Savestate.h" + +namespace melonDS::GBACart +{ + +enum CartType +{ + Default = 0x001, + Game = 0x101, + GameSolarSensor = 0x102, + RAMExpansion = 0x201, +}; + +// CartCommon -- base code shared by all cart types +class CartCommon +{ +public: + virtual ~CartCommon() = default; + + [[nodiscard]] u32 Type() const { return CartType; } + virtual u32 Checksum() const { return 0; } + + virtual void Reset(); + + virtual void DoSavestate(Savestate* file); + + virtual int SetInput(int num, bool pressed); + + virtual u16 ROMRead(u32 addr) const; + virtual void ROMWrite(u32 addr, u16 val); + + virtual u8 SRAMRead(u32 addr); + virtual void SRAMWrite(u32 addr, u8 val); + + [[nodiscard]] virtual const u8* GetROM() const { return nullptr; } + [[nodiscard]] virtual u32 GetROMLength() const { return 0; } + + virtual u8* GetSaveMemory() const; + virtual u32 GetSaveMemoryLength() const; + virtual void SetSaveMemory(const u8* savedata, u32 savelen); +protected: + CartCommon(GBACart::CartType type); + friend class GBACartSlot; +private: + GBACart::CartType CartType; +}; + +// CartGame -- regular retail game cart (ROM, SRAM) +class CartGame : public CartCommon +{ +public: + CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game); + CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game); + ~CartGame() override; + + u32 Checksum() const override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + void ROMWrite(u32 addr, u16 val) override; + + u8 SRAMRead(u32 addr) override; + void SRAMWrite(u32 addr, u8 val) override; + + [[nodiscard]] const u8* GetROM() const override { return ROM.get(); } + [[nodiscard]] u32 GetROMLength() const override { return ROMLength; } + + u8* GetSaveMemory() const override; + u32 GetSaveMemoryLength() const override; + void SetSaveMemory(const u8* savedata, u32 savelen) override; +protected: + virtual void ProcessGPIO(); + + u8 SRAMRead_EEPROM(u32 addr); + void SRAMWrite_EEPROM(u32 addr, u8 val); + u8 SRAMRead_FLASH(u32 addr); + void SRAMWrite_FLASH(u32 addr, u8 val); + u8 SRAMRead_SRAM(u32 addr); + void SRAMWrite_SRAM(u32 addr, u8 val); + + std::unique_ptr ROM; + u32 ROMLength; + + struct + { + u16 data; + u16 direction; + u16 control; + + } GPIO {}; + + enum SaveType + { + S_NULL, + S_EEPROM4K, + S_EEPROM64K, + S_SRAM256K, + S_FLASH512K, + S_FLASH1M + }; + + // from DeSmuME + struct + { + u8 state; + u8 cmd; + u8 device; + u8 manufacturer; + u8 bank; + + } SRAMFlashState {}; + + std::unique_ptr SRAM = nullptr; + u32 SRAMLength = 0; + SaveType SRAMType = S_NULL; +private: + void SetupSave(u32 type); +}; + +// CartGameSolarSensor -- Boktai game cart +class CartGameSolarSensor : public CartGame +{ +public: + CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen); + CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen); + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + int SetInput(int num, bool pressed) override; + +protected: + void ProcessGPIO() override; + +private: + static const int kLuxLevels[11]; + + bool LightEdge = false; + u8 LightCounter = 0; + u8 LightSample = 0; + u8 LightLevel = 0; +}; + +// CartRAMExpansion -- RAM expansion cart (DS browser, ...) +class CartRAMExpansion : public CartCommon +{ +public: + CartRAMExpansion(); + ~CartRAMExpansion() override; + + void Reset() override; + + void DoSavestate(Savestate* file) override; + + u16 ROMRead(u32 addr) const override; + void ROMWrite(u32 addr, u16 val) override; + +private: + u8 RAM[0x800000] {}; + u16 RAMEnable = 0; +}; + +// possible inputs for GBA carts that might accept user input +enum +{ + Input_SolarSensorDown = 0, + Input_SolarSensorUp, +}; + +class GBACartSlot +{ +public: + GBACartSlot(std::unique_ptr&& cart = nullptr) noexcept; + ~GBACartSlot() noexcept = default; + void Reset() noexcept; + void DoSavestate(Savestate* file) noexcept; + + /// Ejects the cart in the GBA slot (if any) + /// and inserts the given one. + /// + /// To insert a cart that does not require ROM data + /// (such as the RAM expansion pack), + /// create it manually with std::make_unique and pass it here. + /// + /// @param cart Movable \c unique_ptr to the GBA cart object. + /// May be \c nullptr, in which case the cart slot remains empty. + /// @post \c cart is \c nullptr and the underlying object + /// is moved into the cart slot. + void SetCart(std::unique_ptr&& cart) noexcept; + [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } + [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } + + void LoadAddon(int type) noexcept; + + /// @return The cart that was in the cart slot if any, + /// or \c nullptr if the cart slot was empty. + std::unique_ptr EjectCart() noexcept; + + // TODO: make more flexible, support nonbinary inputs + int SetInput(int num, bool pressed) noexcept; + + void SetOpenBusDecay(u16 val) noexcept { OpenBusDecay = val; } + + u16 ROMRead(u32 addr) const noexcept; + void ROMWrite(u32 addr, u16 val) noexcept; + + u8 SRAMRead(u32 addr) noexcept; + void SRAMWrite(u32 addr, u8 val) noexcept; + + /// This function is intended to allow frontends to save and load SRAM + /// without using melonDS APIs. + /// Modifying the emulated SRAM for any other reason is strongly discouraged. + /// The returned pointer may be invalidated if the emulator is reset, + /// or when a new game is loaded. + /// Consequently, don't store the returned pointer for any longer than necessary. + /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr. + [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } + [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } + + /// Sets the loaded cart's SRAM. + /// Does nothing if no cart is inserted + /// or the inserted cart doesn't support SRAM. + /// + /// @param savedata Buffer containing the raw contents of the SRAM. + /// The contents of this buffer are copied into the cart slot, + /// so the caller may dispose of it after this method returns. + /// @param savelen The length of the buffer in \c savedata, in bytes. + void SetSaveMemory(const u8* savedata, u32 savelen) noexcept; + + /// @returns The length of the buffer returned by ::GetSaveMemory() + /// if a cart is loaded and supports SRAM, otherwise zero. + [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } +private: + std::unique_ptr Cart = nullptr; + u16 OpenBusDecay = 0; +}; + +/// Parses the given ROM data and constructs a \c GBACart::CartCommon subclass +/// that can be inserted into the emulator or used to extract information about the cart beforehand. +/// @param romdata The ROM data to parse. +/// The returned cartridge will contain a copy of this data, +/// so the caller may deallocate \c romdata after this function returns. +/// @param romlen The length of the ROM data in bytes. +/// @returns A \c GBACart::CartCommon object representing the parsed ROM, +/// or \c nullptr if the ROM data couldn't be parsed. +std::unique_ptr ParseROM(const u8* romdata, u32 romlen); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen); + +/// @param romdata The ROM data to parse. Will be moved-from. +/// @param romlen Length of romdata in bytes. +/// @param sramdata The save data to add to the cart. +/// May be \c nullptr, in which case the cart will have no save data. +/// @param sramlen Length of sramdata in bytes. +/// May be zero, in which case the cart will have no save data. +/// @return Unique pointer to the parsed GBA cart, +/// or \c nullptr if there was an error. +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen); + +} + +#endif // GBACART_H diff --git a/src/GPU.cpp b/src/GPU.cpp index c80e3119..c226ccbe 100644 --- a/src/GPU.cpp +++ b/src/GPU.cpp @@ -67,7 +67,7 @@ GPU::GPU(melonDS::NDS& nds, std::unique_ptr&& renderer3d, std::uniqu NDS(nds), GPU2D_A(0, *this), GPU2D_B(1, *this), - GPU3D(nds, renderer3d ? std::move(renderer3d) : std::make_unique(*this)), + GPU3D(nds, renderer3d ? std::move(renderer3d) : std::make_unique()), GPU2D_Renderer(renderer2d ? std::move(renderer2d) : std::make_unique(*this)) { NDS.RegisterEventFunc(Event_LCD, LCD_StartHBlank, MemberEventFunc(GPU, StartHBlank)); @@ -75,7 +75,7 @@ GPU::GPU(melonDS::NDS& nds, std::unique_ptr&& renderer3d, std::uniqu NDS.RegisterEventFunc(Event_LCD, LCD_FinishFrame, MemberEventFunc(GPU, FinishFrame)); NDS.RegisterEventFunc(Event_DisplayFIFO, 0, MemberEventFunc(GPU, DisplayFIFO)); - FrontBuffer = 0; + InitFramebuffers(); } GPU::~GPU() noexcept @@ -209,7 +209,7 @@ void GPU::Stop() noexcept memset(Framebuffer[1][0].get(), 0, fbsize*4); memset(Framebuffer[1][1].get(), 0, fbsize*4); - GPU3D.Stop(); + GPU3D.Stop(*this); } void GPU::DoSavestate(Savestate* file) noexcept @@ -294,10 +294,15 @@ void GPU::AssignFramebuffers() noexcept void GPU::SetRenderer3D(std::unique_ptr&& renderer) noexcept { if (renderer == nullptr) - GPU3D.SetCurrentRenderer(std::make_unique(*this)); + GPU3D.SetCurrentRenderer(std::make_unique()); else GPU3D.SetCurrentRenderer(std::move(renderer)); + InitFramebuffers(); +} + +void GPU::InitFramebuffers() noexcept +{ int fbsize; if (GPU3D.IsRendererAccelerated()) fbsize = (256*3 + 1) * 192; @@ -894,7 +899,7 @@ void GPU::StartHBlank(u32 line) noexcept } else if (VCount == 215) { - GPU3D.VCount215(); + GPU3D.VCount215(*this); } else if (VCount == 262) { @@ -920,7 +925,7 @@ void GPU::FinishFrame(u32 lines) noexcept if (GPU3D.AbortFrame) { - GPU3D.RestartFrame(); + GPU3D.RestartFrame(*this); GPU3D.AbortFrame = false; } } @@ -1013,7 +1018,7 @@ void GPU::StartScanline(u32 line) noexcept // texture memory anyway and only update it before the start //of the next frame. // So we can give the rasteriser a bit more headroom - GPU3D.VCount144(); + GPU3D.VCount144(*this); // VBlank DispStat[0] |= (1<<0); @@ -1033,7 +1038,7 @@ void GPU::StartScanline(u32 line) noexcept // Need a better way to identify the openGL renderer in particular if (GPU3D.IsRendererAccelerated()) - GPU3D.Blit(); + GPU3D.Blit(*this); } } @@ -1063,7 +1068,7 @@ void GPU::SetVCount(u16 val) noexcept } template -NonStupidBitField VRAMTrackingSet::DeriveState(u32* currentMappings, GPU& gpu) +NonStupidBitField VRAMTrackingSet::DeriveState(const u32* currentMappings, GPU& gpu) { NonStupidBitField result; u16 banksToBeZeroed = 0; @@ -1126,12 +1131,12 @@ NonStupidBitField VRAMTrackingSet VRAMTrackingSet<32*1024, 8*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<8*1024/VRAMDirtyGranularity> VRAMTrackingSet<8*1024, 8*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 128*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<128*1024/VRAMDirtyGranularity> VRAMTrackingSet<128*1024, 16*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<256*1024/VRAMDirtyGranularity> VRAMTrackingSet<256*1024, 16*1024>::DeriveState(u32*, GPU& gpu); -template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 16*1024>::DeriveState(u32*, GPU& gpu); +template NonStupidBitField<32*1024/VRAMDirtyGranularity> VRAMTrackingSet<32*1024, 8*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<8*1024/VRAMDirtyGranularity> VRAMTrackingSet<8*1024, 8*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 128*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<128*1024/VRAMDirtyGranularity> VRAMTrackingSet<128*1024, 16*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<256*1024/VRAMDirtyGranularity> VRAMTrackingSet<256*1024, 16*1024>::DeriveState(const u32*, GPU& gpu); +template NonStupidBitField<512*1024/VRAMDirtyGranularity> VRAMTrackingSet<512*1024, 16*1024>::DeriveState(const u32*, GPU& gpu); diff --git a/src/GPU.h b/src/GPU.h index 21a05d58..e070db78 100644 --- a/src/GPU.h +++ b/src/GPU.h @@ -49,7 +49,7 @@ struct VRAMTrackingSet Mapping[i] = 0x8000; } } - NonStupidBitField DeriveState(u32* currentMappings, GPU& gpu); + NonStupidBitField DeriveState(const u32* currentMappings, GPU& gpu); }; class GPU @@ -607,6 +607,7 @@ public: private: void ResetVRAMCache() noexcept; void AssignFramebuffers() noexcept; + void InitFramebuffers() noexcept; template T ReadVRAM_ABGExtPal(u32 addr) const noexcept { diff --git a/src/GPU2D.cpp b/src/GPU2D.cpp index e7fb9a29..e0aa630d 100644 --- a/src/GPU2D.cpp +++ b/src/GPU2D.cpp @@ -648,7 +648,7 @@ void Unit::CheckWindows(u32 line) else if (line == Win1Coords[2]) Win1Active |= 0x1; } -void Unit::CalculateWindowMask(u32 line, u8* windowMask, u8* objWindow) +void Unit::CalculateWindowMask(u32 line, u8* windowMask, const u8* objWindow) { for (u32 i = 0; i < 256; i++) windowMask[i] = WinCnt[2]; // window outside @@ -694,7 +694,7 @@ void Unit::CalculateWindowMask(u32 line, u8* windowMask, u8* objWindow) } } -void Unit::GetBGVRAM(u8*& data, u32& mask) +void Unit::GetBGVRAM(u8*& data, u32& mask) const { if (Num == 0) { @@ -708,7 +708,7 @@ void Unit::GetBGVRAM(u8*& data, u32& mask) } } -void Unit::GetOBJVRAM(u8*& data, u32& mask) +void Unit::GetOBJVRAM(u8*& data, u32& mask) const { if (Num == 0) { diff --git a/src/GPU2D.h b/src/GPU2D.h index 7367d07a..e87167cb 100644 --- a/src/GPU2D.h +++ b/src/GPU2D.h @@ -52,7 +52,7 @@ public: void Write16(u32 addr, u16 val); void Write32(u32 addr, u32 val); - bool UsesFIFO() + bool UsesFIFO() const { if (((DispCnt >> 16) & 0x3) == 3) return true; @@ -72,11 +72,11 @@ public: u16* GetBGExtPal(u32 slot, u32 pal); u16* GetOBJExtPal(); - void GetBGVRAM(u8*& data, u32& mask); - void GetOBJVRAM(u8*& data, u32& mask); + void GetBGVRAM(u8*& data, u32& mask) const; + void GetOBJVRAM(u8*& data, u32& mask) const; void UpdateMosaicCounters(u32 line); - void CalculateWindowMask(u32 line, u8* windowMask, u8* objWindow); + void CalculateWindowMask(u32 line, u8* windowMask, const u8* objWindow); u32 Num; bool Enabled; diff --git a/src/GPU2D_Soft.cpp b/src/GPU2D_Soft.cpp index 6d0252c3..e01d3665 100644 --- a/src/GPU2D_Soft.cpp +++ b/src/GPU2D_Soft.cpp @@ -30,7 +30,7 @@ SoftRenderer::SoftRenderer(melonDS::GPU& gpu) // mosaic table is initialized at compile-time } -u32 SoftRenderer::ColorComposite(int i, u32 val1, u32 val2) +u32 SoftRenderer::ColorComposite(int i, u32 val1, u32 val2) const { u32 coloreffect = 0; u32 eva, evb; diff --git a/src/GPU2D_Soft.h b/src/GPU2D_Soft.h index ca242a51..befb67f6 100644 --- a/src/GPU2D_Soft.h +++ b/src/GPU2D_Soft.h @@ -117,7 +117,7 @@ private: return rb | g | 0xFF000000; } - u32 ColorComposite(int i, u32 val1, u32 val2); + u32 ColorComposite(int i, u32 val1, u32 val2) const; template void DrawScanlineBGMode(u32 line); void DrawScanlineBGMode6(u32 line); diff --git a/src/GPU3D.cpp b/src/GPU3D.cpp index 049ae589..47877021 100644 --- a/src/GPU3D.cpp +++ b/src/GPU3D.cpp @@ -142,10 +142,16 @@ void MatrixLoadIdentity(s32* m); GPU3D::GPU3D(melonDS::NDS& nds, std::unique_ptr&& renderer) noexcept : NDS(nds), - CurrentRenderer(renderer ? std::move(renderer) : std::make_unique(nds.GPU)) + CurrentRenderer(renderer ? std::move(renderer) : std::make_unique()) { } +void GPU3D::SetCurrentRenderer(std::unique_ptr&& renderer) noexcept +{ + CurrentRenderer = std::move(renderer); + CurrentRenderer->Reset(NDS.GPU); +} + void GPU3D::ResetRenderingState() noexcept { RenderNumPolygons = 0; @@ -172,17 +178,6 @@ void GPU3D::Reset() noexcept CmdStallQueue.Clear(); - NumCommands = 0; - CurCommand = 0; - ParamCount = 0; - TotalParams = 0; - - NumPushPopCommands = 0; - NumTestCommands = 0; - - DispCnt = 0; - AlphaRef = 0; - ZeroDotWLimit = 0; // CHECKME GXStat = 0; @@ -190,7 +185,6 @@ void GPU3D::Reset() noexcept memset(ExecParams, 0, 32*4); ExecParamCount = 0; - Timestamp = 0; CycleCount = 0; VertexPipeline = 0; NormalPipeline = 0; @@ -198,6 +192,8 @@ void GPU3D::Reset() noexcept VertexSlotCounter = 0; VertexSlotsFree = 1; + NumPushPopCommands = 0; + NumTestCommands = 0; MatrixMode = 0; @@ -215,35 +211,86 @@ void GPU3D::Reset() noexcept memset(PosMatrixStack, 0, 31 * 16*4); memset(VecMatrixStack, 0, 31 * 16*4); memset(TexMatrixStack, 0, 16*4); + ProjMatrixStackPointer = 0; PosMatrixStackPointer = 0; TexMatrixStackPointer = 0; + NumCommands = 0; + CurCommand = 0; + ParamCount = 0; + TotalParams = 0; + + GeometryEnabled = false; + RenderingEnabled = false; + + DispCnt = 0; + AlphaRefVal = 0; + AlphaRef = 0; + + memset(ToonTable, 0, sizeof(ToonTable)); + memset(EdgeTable, 0, sizeof(EdgeTable)); + + // TODO: confirm initial polyid/color/fog values + FogOffset = 0; + FogColor = 0; + memset(FogDensityTable, 0, sizeof(FogDensityTable)); + + ClearAttr1 = 0x3F000000; + ClearAttr2 = 0x00007FFF; + + ResetRenderingState(); + + AbortFrame = false; + + Timestamp = 0; + + PolygonMode = 0; + memset(CurVertex, 0, sizeof(CurVertex)); + memset(VertexColor, 0, sizeof(VertexColor)); + memset(TexCoords, 0, sizeof(TexCoords)); + memset(RawTexCoords, 0, sizeof(RawTexCoords)); + memset(Normal, 0, sizeof(Normal)); + + memset(LightDirection, 0, sizeof(LightDirection)); + memset(LightColor, 0, sizeof(LightColor)); + memset(MatDiffuse, 0, sizeof(MatDiffuse)); + memset(MatAmbient, 0, sizeof(MatAmbient)); + memset(MatSpecular, 0, sizeof(MatSpecular)); + memset(MatEmission, 0, sizeof(MatSpecular)); + + UseShininessTable = false; + memset(ShininessTable, 0, sizeof(ShininessTable)); + + PolygonAttr = 0; + CurPolygonAttr = 0; + + TexParam = 0; + TexPalette = 0; + memset(PosTestResult, 0, 4*4); memset(VecTestResult, 0, 2*3); + memset(TempVertexBuffer, 0, sizeof(TempVertexBuffer)); VertexNum = 0; VertexNumInPoly = 0; + NumConsecutivePolygons = 0; + LastStripPolygon = nullptr; + NumOpaquePolygons = 0; - CurRAMBank = 0; CurVertexRAM = &VertexRAM[0]; CurPolygonRAM = &PolygonRAM[0]; NumVertices = 0; NumPolygons = 0; - NumOpaquePolygons = 0; - - // TODO: confirm initial polyid/color/fog values - ClearAttr1 = 0x3F000000; - ClearAttr2 = 0x00007FFF; + CurRAMBank = 0; FlushRequest = 0; FlushAttributes = 0; - ResetRenderingState(); - RenderXPos = 0; - AbortFrame = false; + if (CurrentRenderer) + CurrentRenderer->Reset(NDS.GPU); } void GPU3D::DoSavestate(Savestate* file) noexcept @@ -1455,7 +1502,7 @@ void GPU3D::CalculateLighting() noexcept } -void GPU3D::BoxTest(u32* params) noexcept +void GPU3D::BoxTest(const u32* params) noexcept { Vertex cube[8]; Vertex face[10]; @@ -1588,7 +1635,7 @@ void GPU3D::VecTest(u32 param) noexcept -void GPU3D::CmdFIFOWrite(CmdFIFOEntry& entry) noexcept +void GPU3D::CmdFIFOWrite(const CmdFIFOEntry& entry) noexcept { if (CmdFIFO.IsEmpty() && !CmdPIPE.IsFull()) { @@ -2329,20 +2376,20 @@ void GPU3D::CheckFIFODMA() noexcept NDS.CheckDMAs(0, 0x07); } -void GPU3D::VCount144() noexcept +void GPU3D::VCount144(GPU& gpu) noexcept { - CurrentRenderer->VCount144(); + CurrentRenderer->VCount144(gpu); } -void GPU3D::RestartFrame() noexcept +void GPU3D::RestartFrame(GPU& gpu) noexcept { - CurrentRenderer->RestartFrame(); + CurrentRenderer->RestartFrame(gpu); } -void GPU3D::Stop() noexcept +void GPU3D::Stop(const GPU& gpu) noexcept { if (CurrentRenderer) - CurrentRenderer->Stop(); + CurrentRenderer->Stop(gpu); } @@ -2435,9 +2482,9 @@ void GPU3D::VBlank() noexcept } } -void GPU3D::VCount215() noexcept +void GPU3D::VCount215(GPU& gpu) noexcept { - CurrentRenderer->RenderFrame(); + CurrentRenderer->RenderFrame(gpu); } void GPU3D::SetRenderXPos(u16 xpos) noexcept @@ -2897,10 +2944,10 @@ void GPU3D::Write32(u32 addr, u32 val) noexcept Log(LogLevel::Debug, "unknown GPU3D write32 %08X %08X\n", addr, val); } -void GPU3D::Blit() noexcept +void GPU3D::Blit(const GPU& gpu) noexcept { if (CurrentRenderer) - CurrentRenderer->Blit(); + CurrentRenderer->Blit(gpu); } Renderer3D::Renderer3D(bool Accelerated) diff --git a/src/GPU3D.h b/src/GPU3D.h index 8e743fac..7c42eeb5 100644 --- a/src/GPU3D.h +++ b/src/GPU3D.h @@ -101,12 +101,12 @@ public: void CheckFIFOIRQ() noexcept; void CheckFIFODMA() noexcept; - void VCount144() noexcept; + void VCount144(GPU& gpu) noexcept; void VBlank() noexcept; - void VCount215() noexcept; + void VCount215(GPU& gpu) noexcept; - void RestartFrame() noexcept; - void Stop() noexcept; + void RestartFrame(GPU& gpu) noexcept; + void Stop(const GPU& gpu) noexcept; void SetRenderXPos(u16 xpos) noexcept; [[nodiscard]] u16 GetRenderXPos() const noexcept { return RenderXPos; } @@ -117,7 +117,7 @@ public: [[nodiscard]] bool IsRendererAccelerated() const noexcept; [[nodiscard]] Renderer3D& GetCurrentRenderer() noexcept { return *CurrentRenderer; } [[nodiscard]] const Renderer3D& GetCurrentRenderer() const noexcept { return *CurrentRenderer; } - void SetCurrentRenderer(std::unique_ptr&& renderer) noexcept { CurrentRenderer = std::move(renderer); } + void SetCurrentRenderer(std::unique_ptr&& renderer) noexcept; u8 Read8(u32 addr) noexcept; u16 Read16(u32 addr) noexcept; @@ -125,7 +125,7 @@ public: void Write8(u32 addr, u8 val) noexcept; void Write16(u32 addr, u16 val) noexcept; void Write32(u32 addr, u32 val) noexcept; - void Blit() noexcept; + void Blit(const GPU& gpu) noexcept; private: melonDS::NDS& NDS; typedef union @@ -147,10 +147,10 @@ private: void SubmitPolygon() noexcept; void SubmitVertex() noexcept; void CalculateLighting() noexcept; - void BoxTest(u32* params) noexcept; + void BoxTest(const u32* params) noexcept; void PosTest() noexcept; void VecTest(u32 param) noexcept; - void CmdFIFOWrite(CmdFIFOEntry& entry) noexcept; + void CmdFIFOWrite(const CmdFIFOEntry& entry) noexcept; CmdFIFOEntry CmdFIFORead() noexcept; void FinishWork(s32 cycles) noexcept; void VertexPipelineSubmitCmd() noexcept @@ -254,6 +254,7 @@ public: u32 ClearAttr1 = 0; u32 ClearAttr2 = 0; + u32 RenderDispCnt = 0; u8 RenderAlphaRef = 0; @@ -333,19 +334,19 @@ public: Renderer3D(const Renderer3D&) = delete; Renderer3D& operator=(const Renderer3D&) = delete; - virtual void Reset() = 0; + virtual void Reset(GPU& gpu) = 0; // This "Accelerated" flag currently communicates if the framebuffer should // be allocated differently and other little misc handlers. Ideally there // are more detailed "traits" that we can ask of the Renderer3D type const bool Accelerated; - virtual void VCount144() {}; - virtual void Stop() {} - virtual void RenderFrame() = 0; - virtual void RestartFrame() {}; + virtual void VCount144(GPU& gpu) {}; + virtual void Stop(const GPU& gpu) {} + virtual void RenderFrame(GPU& gpu) = 0; + virtual void RestartFrame(GPU& gpu) {}; virtual u32* GetLine(int line) = 0; - virtual void Blit() {}; + virtual void Blit(const GPU& gpu) {}; virtual void PrepareCaptureFrame() {} protected: Renderer3D(bool Accelerated); diff --git a/src/GPU3D_OpenGL.cpp b/src/GPU3D_OpenGL.cpp index bee04305..27711a89 100644 --- a/src/GPU3D_OpenGL.cpp +++ b/src/GPU3D_OpenGL.cpp @@ -97,9 +97,8 @@ void SetupDefaultTexParams(GLuint tex) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } -GLRenderer::GLRenderer(GLCompositor&& compositor, melonDS::GPU& gpu) noexcept : +GLRenderer::GLRenderer(GLCompositor&& compositor) noexcept : Renderer3D(true), - GPU(gpu), CurGLCompositor(std::move(compositor)) { // GLRenderer::New() will be used to actually initialize the renderer; @@ -107,7 +106,7 @@ GLRenderer::GLRenderer(GLCompositor&& compositor, melonDS::GPU& gpu) noexcept : // so we can just let the destructor clean up a half-initialized renderer. } -std::unique_ptr GLRenderer::New(melonDS::GPU& gpu) noexcept +std::unique_ptr GLRenderer::New() noexcept { assert(glEnable != nullptr); @@ -117,7 +116,7 @@ std::unique_ptr GLRenderer::New(melonDS::GPU& gpu) noexcept // Will be returned if the initialization succeeds, // or cleaned up via RAII if it fails. - std::unique_ptr result = std::unique_ptr(new GLRenderer(std::move(*compositor), gpu)); + std::unique_ptr result = std::unique_ptr(new GLRenderer(std::move(*compositor))); compositor = std::nullopt; glEnable(GL_DEPTH_TEST); @@ -333,7 +332,7 @@ GLRenderer::~GLRenderer() } } -void GLRenderer::Reset() +void GLRenderer::Reset(GPU& gpu) { // This is where the compositor's Reset() method would be called, // except there's no such method right now. @@ -406,7 +405,7 @@ void GLRenderer::SetRenderSettings(bool betterpolygons, int scale) noexcept } -void GLRenderer::SetupPolygon(GLRenderer::RendererPolygon* rp, Polygon* polygon) +void GLRenderer::SetupPolygon(GLRenderer::RendererPolygon* rp, Polygon* polygon) const { rp->PolyData = polygon; @@ -452,7 +451,7 @@ void GLRenderer::SetupPolygon(GLRenderer::RendererPolygon* rp, Polygon* polygon) } } -u32* GLRenderer::SetupVertex(Polygon* poly, int vid, Vertex* vtx, u32 vtxattr, u32* vptr) +u32* GLRenderer::SetupVertex(const Polygon* poly, int vid, const Vertex* vtx, u32 vtxattr, u32* vptr) const { u32 z = poly->FinalZ[vid]; u32 w = poly->FinalW[vid]; @@ -735,18 +734,18 @@ void GLRenderer::BuildPolygons(GLRenderer::RendererPolygon* polygons, int npolys NumEdgeIndices = eidx - EdgeIndicesOffset; } -int GLRenderer::RenderSinglePolygon(int i) +int GLRenderer::RenderSinglePolygon(int i) const { - RendererPolygon* rp = &PolygonList[i]; + const RendererPolygon* rp = &PolygonList[i]; glDrawElements(rp->PrimType, rp->NumIndices, GL_UNSIGNED_SHORT, (void*)(uintptr_t)(rp->IndicesOffset * 2)); return 1; } -int GLRenderer::RenderPolygonBatch(int i) +int GLRenderer::RenderPolygonBatch(int i) const { - RendererPolygon* rp = &PolygonList[i]; + const RendererPolygon* rp = &PolygonList[i]; GLuint primtype = rp->PrimType; u32 key = rp->RenderKey; int numpolys = 0; @@ -754,7 +753,7 @@ int GLRenderer::RenderPolygonBatch(int i) for (int iend = i; iend < NumFinalPolys; iend++) { - RendererPolygon* cur_rp = &PolygonList[iend]; + const RendererPolygon* cur_rp = &PolygonList[iend]; if (cur_rp->PrimType != primtype) break; if (cur_rp->RenderKey != key) break; @@ -766,16 +765,16 @@ int GLRenderer::RenderPolygonBatch(int i) return numpolys; } -int GLRenderer::RenderPolygonEdgeBatch(int i) +int GLRenderer::RenderPolygonEdgeBatch(int i) const { - RendererPolygon* rp = &PolygonList[i]; + const RendererPolygon* rp = &PolygonList[i]; u32 key = rp->RenderKey; int numpolys = 0; u32 numindices = 0; for (int iend = i; iend < NumFinalPolys; iend++) { - RendererPolygon* cur_rp = &PolygonList[iend]; + const RendererPolygon* cur_rp = &PolygonList[iend]; if (cur_rp->RenderKey != key) break; numpolys++; @@ -786,14 +785,14 @@ int GLRenderer::RenderPolygonEdgeBatch(int i) return numpolys; } -void GLRenderer::RenderSceneChunk(int y, int h) +void GLRenderer::RenderSceneChunk(const GPU3D& gpu3d, int y, int h) { u32 flags = 0; - if (GPU.GPU3D.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; + if (gpu3d.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; if (h != 192) glScissor(0, y<PolyData->IsShadow) { // shadow against clear-plane will only pass if its polyID matches that of the clear plane - u32 clrpolyid = (GPU.GPU3D.RenderClearAttr1 >> 24) & 0x3F; + u32 clrpolyid = (gpu3d.RenderClearAttr1 >> 24) & 0x3F; if (polyid != clrpolyid) { i++; continue; } glEnable(GL_BLEND); @@ -1089,7 +1088,7 @@ void GLRenderer::RenderSceneChunk(int y, int h) } } - if (GPU.GPU3D.RenderDispCnt & 0x00A0) // fog/edge enabled + if (gpu3d.RenderDispCnt & 0x00A0) // fog/edge enabled { glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glColorMaski(1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); @@ -1111,7 +1110,7 @@ void GLRenderer::RenderSceneChunk(int y, int h) glBindBuffer(GL_ARRAY_BUFFER, ClearVertexBufferID); glBindVertexArray(ClearVertexArrayID); - if (GPU.GPU3D.RenderDispCnt & (1<<5)) + if (gpu3d.RenderDispCnt & (1<<5)) { // edge marking // TODO: depth/polyid values at screen edges @@ -1123,19 +1122,19 @@ void GLRenderer::RenderSceneChunk(int y, int h) glDrawArrays(GL_TRIANGLES, 0, 2*3); } - if (GPU.GPU3D.RenderDispCnt & (1<<7)) + if (gpu3d.RenderDispCnt & (1<<7)) { // fog glUseProgram(FinalPassFogShader[2]); - if (GPU.GPU3D.RenderDispCnt & (1<<6)) + if (gpu3d.RenderDispCnt & (1<<6)) glBlendFuncSeparate(GL_ZERO, GL_ONE, GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA); else glBlendFuncSeparate(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA); { - u32 c = GPU.GPU3D.RenderFogColor; + u32 c = gpu3d.RenderFogColor; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1150,7 +1149,7 @@ void GLRenderer::RenderSceneChunk(int y, int h) } -void GLRenderer::RenderFrame() +void GLRenderer::RenderFrame(GPU& gpu) { CurShaderID = -1; @@ -1159,11 +1158,11 @@ void GLRenderer::RenderFrame() ShaderConfig.uScreenSize[0] = ScreenW; ShaderConfig.uScreenSize[1] = ScreenH; - ShaderConfig.uDispCnt = GPU.GPU3D.RenderDispCnt; + ShaderConfig.uDispCnt = gpu.GPU3D.RenderDispCnt; for (int i = 0; i < 32; i++) { - u16 c = GPU.GPU3D.RenderToonTable[i]; + u16 c = gpu.GPU3D.RenderToonTable[i]; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1175,7 +1174,7 @@ void GLRenderer::RenderFrame() for (int i = 0; i < 8; i++) { - u16 c = GPU.GPU3D.RenderEdgeTable[i]; + u16 c = gpu.GPU3D.RenderEdgeTable[i]; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1186,7 +1185,7 @@ void GLRenderer::RenderFrame() } { - u32 c = GPU.GPU3D.RenderFogColor; + u32 c = gpu.GPU3D.RenderFogColor; u32 r = c & 0x1F; u32 g = (c >> 5) & 0x1F; u32 b = (c >> 10) & 0x1F; @@ -1200,12 +1199,12 @@ void GLRenderer::RenderFrame() for (int i = 0; i < 34; i++) { - u8 d = GPU.GPU3D.RenderFogDensityTable[i]; + u8 d = gpu.GPU3D.RenderFogDensityTable[i]; ShaderConfig.uFogDensity[i][0] = (float)d / 127.0; } - ShaderConfig.uFogOffset = GPU.GPU3D.RenderFogOffset; - ShaderConfig.uFogShift = GPU.GPU3D.RenderFogShift; + ShaderConfig.uFogOffset = gpu.GPU3D.RenderFogOffset; + ShaderConfig.uFogShift = gpu.GPU3D.RenderFogShift; glBindBuffer(GL_UNIFORM_BUFFER, ShaderConfigUBO); void* unibuf = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY); @@ -1218,13 +1217,13 @@ void GLRenderer::RenderFrame() glBindTexture(GL_TEXTURE_2D, TexMemID); for (int i = 0; i < 4; i++) { - u32 mask = GPU.VRAMMap_Texture[i]; + u32 mask = gpu.VRAMMap_Texture[i]; u8* vram; if (!mask) continue; - else if (mask & (1<<0)) vram = GPU.VRAM_A; - else if (mask & (1<<1)) vram = GPU.VRAM_B; - else if (mask & (1<<2)) vram = GPU.VRAM_C; - else if (mask & (1<<3)) vram = GPU.VRAM_D; + else if (mask & (1<<0)) vram = gpu.VRAM_A; + else if (mask & (1<<1)) vram = gpu.VRAM_B; + else if (mask & (1<<2)) vram = gpu.VRAM_C; + else if (mask & (1<<3)) vram = gpu.VRAM_D; glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i*128, 1024, 128, GL_RED_INTEGER, GL_UNSIGNED_BYTE, vram); } @@ -1234,12 +1233,12 @@ void GLRenderer::RenderFrame() for (int i = 0; i < 6; i++) { // 6 x 16K chunks - u32 mask = GPU.VRAMMap_TexPal[i]; + u32 mask = gpu.VRAMMap_TexPal[i]; u8* vram; if (!mask) continue; - else if (mask & (1<<4)) vram = &GPU.VRAM_E[(i&3)*0x4000]; - else if (mask & (1<<5)) vram = GPU.VRAM_F; - else if (mask & (1<<6)) vram = GPU.VRAM_G; + else if (mask & (1<<4)) vram = &gpu.VRAM_E[(i&3)*0x4000]; + else if (mask & (1<<5)) vram = gpu.VRAM_F; + else if (mask & (1<<6)) vram = gpu.VRAM_G; glTexSubImage2D(GL_TEXTURE_2D, 0, 0, i*8, 1024, 8, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, vram); } @@ -1264,13 +1263,13 @@ void GLRenderer::RenderFrame() glUseProgram(ClearShaderPlain[2]); glDepthFunc(GL_ALWAYS); - u32 r = GPU.GPU3D.RenderClearAttr1 & 0x1F; - u32 g = (GPU.GPU3D.RenderClearAttr1 >> 5) & 0x1F; - u32 b = (GPU.GPU3D.RenderClearAttr1 >> 10) & 0x1F; - u32 fog = (GPU.GPU3D.RenderClearAttr1 >> 15) & 0x1; - u32 a = (GPU.GPU3D.RenderClearAttr1 >> 16) & 0x1F; - u32 polyid = (GPU.GPU3D.RenderClearAttr1 >> 24) & 0x3F; - u32 z = ((GPU.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; + u32 r = gpu.GPU3D.RenderClearAttr1 & 0x1F; + u32 g = (gpu.GPU3D.RenderClearAttr1 >> 5) & 0x1F; + u32 b = (gpu.GPU3D.RenderClearAttr1 >> 10) & 0x1F; + u32 fog = (gpu.GPU3D.RenderClearAttr1 >> 15) & 0x1; + u32 a = (gpu.GPU3D.RenderClearAttr1 >> 16) & 0x1F; + u32 polyid = (gpu.GPU3D.RenderClearAttr1 >> 24) & 0x3F; + u32 z = ((gpu.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); @@ -1289,20 +1288,20 @@ void GLRenderer::RenderFrame() glDrawArrays(GL_TRIANGLES, 0, 2*3); } - if (GPU.GPU3D.RenderNumPolygons) + if (gpu.GPU3D.RenderNumPolygons) { // render shit here u32 flags = 0; - if (GPU.GPU3D.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; + if (gpu.GPU3D.RenderPolygonRAM[0]->WBuffer) flags |= RenderFlag_WBuffer; int npolys = 0; int firsttrans = -1; - for (u32 i = 0; i < GPU.GPU3D.RenderNumPolygons; i++) + for (u32 i = 0; i < gpu.GPU3D.RenderNumPolygons; i++) { - if (GPU.GPU3D.RenderPolygonRAM[i]->Degenerate) continue; + if (gpu.GPU3D.RenderPolygonRAM[i]->Degenerate) continue; - SetupPolygon(&PolygonList[npolys], GPU.GPU3D.RenderPolygonRAM[i]); - if (firsttrans < 0 && GPU.GPU3D.RenderPolygonRAM[i]->Translucent) + SetupPolygon(&PolygonList[npolys], gpu.GPU3D.RenderPolygonRAM[i]); + if (firsttrans < 0 && gpu.GPU3D.RenderPolygonRAM[i]->Translucent) firsttrans = npolys; npolys++; @@ -1319,15 +1318,15 @@ void GLRenderer::RenderFrame() glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, NumIndices * 2, IndexBuffer); glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, EdgeIndicesOffset * 2, NumEdgeIndices * 2, IndexBuffer + EdgeIndicesOffset); - RenderSceneChunk(0, 192); + RenderSceneChunk(gpu.GPU3D, 0, 192); } FrontBuffer = FrontBuffer ? 0 : 1; } -void GLRenderer::Stop() +void GLRenderer::Stop(const GPU& gpu) { - CurGLCompositor.Stop(GPU); + CurGLCompositor.Stop(gpu); } void GLRenderer::PrepareCaptureFrame() @@ -1345,9 +1344,9 @@ void GLRenderer::PrepareCaptureFrame() glReadPixels(0, 0, 256, 192, GL_BGRA, GL_UNSIGNED_BYTE, NULL); } -void GLRenderer::Blit() +void GLRenderer::Blit(const GPU& gpu) { - CurGLCompositor.RenderFrame(GPU, *this); + CurGLCompositor.RenderFrame(gpu, *this); } u32* GLRenderer::GetLine(int line) diff --git a/src/GPU3D_OpenGL.h b/src/GPU3D_OpenGL.h index 63ee8de2..c30232ca 100644 --- a/src/GPU3D_OpenGL.h +++ b/src/GPU3D_OpenGL.h @@ -31,7 +31,7 @@ class GLRenderer : public Renderer3D { public: ~GLRenderer() override; - void Reset() override; + void Reset(GPU& gpu) override; void SetRenderSettings(bool betterpolygons, int scale) noexcept; void SetBetterPolygons(bool betterpolygons) noexcept; @@ -39,22 +39,22 @@ public: [[nodiscard]] bool GetBetterPolygons() const noexcept { return BetterPolygons; } [[nodiscard]] int GetScaleFactor() const noexcept { return ScaleFactor; } - void VCount144() override {}; - void RenderFrame() override; - void Stop() override; + void VCount144(GPU& gpu) override {}; + void RenderFrame(GPU& gpu) override; + void Stop(const GPU& gpu) override; u32* GetLine(int line) override; void SetupAccelFrame(); void PrepareCaptureFrame() override; - void Blit() override; + void Blit(const GPU& gpu) override; [[nodiscard]] const GLCompositor& GetCompositor() const noexcept { return CurGLCompositor; } GLCompositor& GetCompositor() noexcept { return CurGLCompositor; } - static std::unique_ptr New(melonDS::GPU& gpu) noexcept; + static std::unique_ptr New() noexcept; private: // Used by New() - GLRenderer(GLCompositor&& compositor, GPU& gpu) noexcept; + GLRenderer(GLCompositor&& compositor) noexcept; // GL version requirements // * texelFetch: 3.0 (GLSL 1.30) (3.2/1.50 for MS) @@ -74,19 +74,18 @@ private: u32 RenderKey; }; - melonDS::GPU& GPU; GLCompositor CurGLCompositor; RendererPolygon PolygonList[2048] {}; bool BuildRenderShader(u32 flags, const char* vs, const char* fs); void UseRenderShader(u32 flags); - void SetupPolygon(RendererPolygon* rp, Polygon* polygon); - u32* SetupVertex(Polygon* poly, int vid, Vertex* vtx, u32 vtxattr, u32* vptr); + void SetupPolygon(RendererPolygon* rp, Polygon* polygon) const; + u32* SetupVertex(const Polygon* poly, int vid, const Vertex* vtx, u32 vtxattr, u32* vptr) const; void BuildPolygons(RendererPolygon* polygons, int npolys); - int RenderSinglePolygon(int i); - int RenderPolygonBatch(int i); - int RenderPolygonEdgeBatch(int i); - void RenderSceneChunk(int y, int h); + int RenderSinglePolygon(int i) const; + int RenderPolygonBatch(int i) const; + int RenderPolygonEdgeBatch(int i) const; + void RenderSceneChunk(const GPU3D& gpu3d, int y, int h); enum { diff --git a/src/GPU3D_Soft.cpp b/src/GPU3D_Soft.cpp index e0840a65..458a4877 100644 --- a/src/GPU3D_Soft.cpp +++ b/src/GPU3D_Soft.cpp @@ -42,14 +42,16 @@ void SoftRenderer::StopRenderThread() } } -void SoftRenderer::SetupRenderThread() +void SoftRenderer::SetupRenderThread(GPU& gpu) { if (Threaded) { if (!RenderThreadRunning.load(std::memory_order_relaxed)) { RenderThreadRunning = true; - RenderThread = Platform::Thread_Create(std::bind(&SoftRenderer::RenderThreadFunc, this)); + RenderThread = Platform::Thread_Create([this, &gpu]() { + RenderThreadFunc(gpu); + }); } // otherwise more than one frame can be queued up at once @@ -71,8 +73,8 @@ void SoftRenderer::SetupRenderThread() } -SoftRenderer::SoftRenderer(melonDS::GPU& gpu, bool threaded) noexcept - : Renderer3D(false), GPU(gpu), Threaded(threaded) +SoftRenderer::SoftRenderer(bool threaded) noexcept + : Renderer3D(false), Threaded(threaded) { Sema_RenderStart = Platform::Semaphore_Create(); Sema_RenderDone = Platform::Semaphore_Create(); @@ -92,7 +94,7 @@ SoftRenderer::~SoftRenderer() Platform::Semaphore_Free(Sema_ScanlineCount); } -void SoftRenderer::Reset() +void SoftRenderer::Reset(GPU& gpu) { memset(ColorBuffer, 0, BufferSize * 2 * 4); memset(DepthBuffer, 0, BufferSize * 2 * 4); @@ -100,19 +102,19 @@ void SoftRenderer::Reset() PrevIsShadowMask = false; - SetupRenderThread(); + SetupRenderThread(gpu); } -void SoftRenderer::SetThreaded(bool threaded) noexcept +void SoftRenderer::SetThreaded(bool threaded, GPU& gpu) noexcept { if (Threaded != threaded) { Threaded = threaded; - SetupRenderThread(); + SetupRenderThread(gpu); } } -void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) +void SoftRenderer::TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) const { u32 vramaddr = (texparam & 0xFFFF) << 3; @@ -167,10 +169,10 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 1: // A3I5 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x1F)<<1)); + *color = ReadVRAM_TexPal(texpal + ((pixel&0x1F)<<1), gpu); *alpha = ((pixel >> 3) & 0x1C) + (pixel >> 6); } break; @@ -178,12 +180,12 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 2: // 4-color { vramaddr += (((t * width) + s) >> 2); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); pixel >>= ((s & 0x3) << 1); pixel &= 0x3; texpal <<= 3; - *color = ReadVRAM_TexPal(texpal + (pixel<<1)); + *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -191,12 +193,12 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 3: // 16-color { vramaddr += (((t * width) + s) >> 1); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); if (s & 0x1) pixel >>= 4; else pixel &= 0xF; texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1)); + *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -204,10 +206,10 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 4: // 256-color { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + (pixel<<1)); + *color = ReadVRAM_TexPal(texpal + (pixel<<1), gpu); *alpha = (pixel==0) ? alpha0 : 31; } break; @@ -221,30 +223,30 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co if (vramaddr >= 0x40000) slot1addr += 0x10000; - u8 val = ReadVRAM_Texture(vramaddr); + u8 val = ReadVRAM_Texture(vramaddr, gpu); val >>= (2 * (s & 0x3)); - u16 palinfo = ReadVRAM_Texture(slot1addr); + u16 palinfo = ReadVRAM_Texture(slot1addr, gpu); u32 paloffset = (palinfo & 0x3FFF) << 2; texpal <<= 4; switch (val & 0x3) { case 0: - *color = ReadVRAM_TexPal(texpal + paloffset); + *color = ReadVRAM_TexPal(texpal + paloffset, gpu); *alpha = 31; break; case 1: - *color = ReadVRAM_TexPal(texpal + paloffset + 2); + *color = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); *alpha = 31; break; case 2: if ((palinfo >> 14) == 1) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2); + u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); + u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -261,8 +263,8 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2); + u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); + u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -278,20 +280,20 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co *color = r | g | b; } else - *color = ReadVRAM_TexPal(texpal + paloffset + 4); + *color = ReadVRAM_TexPal(texpal + paloffset + 4, gpu); *alpha = 31; break; case 3: if ((palinfo >> 14) == 2) { - *color = ReadVRAM_TexPal(texpal + paloffset + 6); + *color = ReadVRAM_TexPal(texpal + paloffset + 6, gpu); *alpha = 31; } else if ((palinfo >> 14) == 3) { - u16 color0 = ReadVRAM_TexPal(texpal + paloffset); - u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2); + u16 color0 = ReadVRAM_TexPal(texpal + paloffset, gpu); + u16 color1 = ReadVRAM_TexPal(texpal + paloffset + 2, gpu); u32 r0 = color0 & 0x001F; u32 g0 = color0 & 0x03E0; @@ -320,10 +322,10 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 6: // A5I3 { vramaddr += ((t * width) + s); - u8 pixel = ReadVRAM_Texture(vramaddr); + u8 pixel = ReadVRAM_Texture(vramaddr, gpu); texpal <<= 4; - *color = ReadVRAM_TexPal(texpal + ((pixel&0x7)<<1)); + *color = ReadVRAM_TexPal(texpal + ((pixel&0x7)<<1), gpu); *alpha = (pixel >> 3); } break; @@ -331,7 +333,7 @@ void SoftRenderer::TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* co case 7: // direct color { vramaddr += (((t * width) + s) << 1); - *color = ReadVRAM_Texture(vramaddr); + *color = ReadVRAM_Texture(vramaddr, gpu); *alpha = (*color & 0x8000) ? 31 : 0; } break; @@ -388,7 +390,7 @@ bool DepthTest_LessThan_FrontFacing(s32 dstz, s32 z, u32 dstattr) return false; } -u32 SoftRenderer::AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept +u32 SoftRenderer::AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept { u32 dstalpha = dstcolor >> 24; @@ -399,7 +401,7 @@ u32 SoftRenderer::AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept u32 srcG = (srccolor >> 8) & 0x3F; u32 srcB = (srccolor >> 16) & 0x3F; - if (GPU.GPU3D.RenderDispCnt & (1<<3)) + if (gpu3d.RenderDispCnt & (1<<3)) { u32 dstR = dstcolor & 0x3F; u32 dstG = (dstcolor >> 8) & 0x3F; @@ -418,7 +420,7 @@ u32 SoftRenderer::AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept return srcR | (srcG << 8) | (srcB << 16) | (dstalpha << 24); } -u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t) +u32 SoftRenderer::RenderPixel(const GPU& gpu, const Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t) const { u8 r, g, b, a; @@ -428,7 +430,7 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 if (blendmode == 2) { - if (GPU.GPU3D.RenderDispCnt & (1<<1)) + if (gpu.GPU3D.RenderDispCnt & (1<<1)) { // highlight mode: color is calculated normally // except all vertex color components are set @@ -442,7 +444,7 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 { // toon mode: vertex color is replaced by toon color - u16 tooncolor = GPU.GPU3D.RenderToonTable[vr >> 1]; + u16 tooncolor = gpu.GPU3D.RenderToonTable[vr >> 1]; vr = (tooncolor << 1) & 0x3E; if (vr) vr++; vg = (tooncolor >> 4) & 0x3E; if (vg) vg++; @@ -450,12 +452,12 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 } } - if ((GPU.GPU3D.RenderDispCnt & (1<<0)) && (((polygon->TexParam >> 26) & 0x7) != 0)) + if ((gpu.GPU3D.RenderDispCnt & (1<<0)) && (((polygon->TexParam >> 26) & 0x7) != 0)) { u8 tr, tg, tb; u16 tcolor; u8 talpha; - TextureLookup(polygon->TexParam, polygon->TexPalette, s, t, &tcolor, &talpha); + TextureLookup(gpu, polygon->TexParam, polygon->TexPalette, s, t, &tcolor, &talpha); tr = (tcolor << 1) & 0x3E; if (tr) tr++; tg = (tcolor >> 4) & 0x3E; if (tg) tg++; @@ -503,9 +505,9 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 a = polyalpha; } - if ((blendmode == 2) && (GPU.GPU3D.RenderDispCnt & (1<<1))) + if ((blendmode == 2) && (gpu.GPU3D.RenderDispCnt & (1<<1))) { - u16 tooncolor = GPU.GPU3D.RenderToonTable[vr >> 1]; + u16 tooncolor = gpu.GPU3D.RenderToonTable[vr >> 1]; vr = (tooncolor << 1) & 0x3E; if (vr) vr++; vg = (tooncolor >> 4) & 0x3E; if (vg) vg++; @@ -526,7 +528,7 @@ u32 SoftRenderer::RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 return r | (g << 8) | (b << 16) | (a << 24); } -void SoftRenderer::PlotTranslucentPixel(u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow) +void SoftRenderer::PlotTranslucentPixel(const GPU3D& gpu3d, u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow) { u32 dstattr = AttrBuffer[pixeladdr]; u32 attr = (polyattr & 0xE0F0) | ((polyattr >> 8) & 0xFF0000) | (1<<22) | (dstattr & 0xFF001F0F); @@ -556,7 +558,7 @@ void SoftRenderer::PlotTranslucentPixel(u32 pixeladdr, u32 color, u32 z, u32 pol if (!(dstattr & (1<<15))) attr &= ~(1<<15); - color = AlphaBlend(color, ColorBuffer[pixeladdr], color>>24); + color = AlphaBlend(gpu3d, color, ColorBuffer[pixeladdr], color>>24); if (z != -1) DepthBuffer[pixeladdr] = z; @@ -579,7 +581,7 @@ void SoftRenderer::CheckForLine(RendererPolygon* rp) rp->Line = false; } -void SoftRenderer::SetupPolygonLeftEdge(SoftRenderer::RendererPolygon* rp, s32 y) +void SoftRenderer::SetupPolygonLeftEdge(SoftRenderer::RendererPolygon* rp, s32 y) const { Polygon* polygon = rp->PolyData; @@ -606,7 +608,7 @@ void SoftRenderer::SetupPolygonLeftEdge(SoftRenderer::RendererPolygon* rp, s32 y polygon->FinalW[rp->CurVL], polygon->FinalW[rp->NextVL], y); } -void SoftRenderer::SetupPolygonRightEdge(SoftRenderer::RendererPolygon* rp, s32 y) +void SoftRenderer::SetupPolygonRightEdge(SoftRenderer::RendererPolygon* rp, s32 y) const { Polygon* polygon = rp->PolyData; @@ -633,7 +635,7 @@ void SoftRenderer::SetupPolygonRightEdge(SoftRenderer::RendererPolygon* rp, s32 polygon->FinalW[rp->CurVR], polygon->FinalW[rp->NextVR], y); } -void SoftRenderer::SetupPolygon(SoftRenderer::RendererPolygon* rp, Polygon* polygon) +void SoftRenderer::SetupPolygon(SoftRenderer::RendererPolygon* rp, Polygon* polygon) const { u32 nverts = polygon->NumVertices; @@ -687,7 +689,7 @@ void SoftRenderer::SetupPolygon(SoftRenderer::RendererPolygon* rp, Polygon* poly } } -void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) +void SoftRenderer::RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y) { Polygon* polygon = rp->PolyData; @@ -772,7 +774,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) std::swap(zl, zr); // CHECKME: edge fill rules for swapped opaque shadow mask polygons - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu3d.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu3d.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -800,7 +802,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) rp->SlopeR.EdgeParams(&r_edgelen, &r_edgecov); // CHECKME: edge fill rules for unswapped opaque shadow mask polygons - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu3d.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu3d.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -821,7 +823,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) // similarly, we can perform alpha test early (checkme) if (wireframe) polyalpha = 31; - if (polyalpha <= GPU.GPU3D.RenderAlphaRef) return; + if (polyalpha <= gpu3d.RenderAlphaRef) return; // in wireframe mode, there are special rules for equal Z (TODO) @@ -923,7 +925,7 @@ void SoftRenderer::RenderShadowMaskScanline(RendererPolygon* rp, s32 y) rp->XR = rp->SlopeR.Step(); } -void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) +void SoftRenderer::RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y) { Polygon* polygon = rp->PolyData; @@ -1015,7 +1017,7 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) // edges are always filled if antialiasing/edgemarking are enabled, // if the pixels are translucent and alpha blending is enabled, or if the polygon is wireframe // checkme: do swapped line polygons exist? if they do then they likely should also be filled - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu.GPU3D.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -1050,7 +1052,7 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) // * edges are filled if both sides are identical and fully overlapping // edges are always filled if antialiasing/edgemarking are enabled, // if the pixels are translucent and alpha blending is enabled, or if the polygon is wireframe - if ((GPU.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (GPU.GPU3D.RenderDispCnt & (1<<3))) || wireframe) + if ((gpu.GPU3D.RenderDispCnt & ((1<<4)|(1<<5))) || ((polyalpha < 31) && (gpu.GPU3D.RenderDispCnt & (1<<3))) || wireframe) { l_filledge = true; r_filledge = true; @@ -1149,17 +1151,17 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) s16 s = interpX.Interpolate(sl, sr); s16 t = interpX.Interpolate(tl, tr); - u32 color = RenderPixel(polygon, vr>>3, vg>>3, vb>>3, s, t); + u32 color = RenderPixel(gpu, polygon, vr>>3, vg>>3, vb>>3, s, t); u8 alpha = color >> 24; // alpha test - if (alpha <= GPU.GPU3D.RenderAlphaRef) continue; + if (alpha <= gpu.GPU3D.RenderAlphaRef) continue; if (alpha == 31) { u32 attr = polyattr | edge; - if (GPU.GPU3D.RenderDispCnt & (1<<4)) + if (gpu.GPU3D.RenderDispCnt & (1<<4)) { // anti-aliasing: all edges are rendered @@ -1189,11 +1191,11 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) else { if (!(polygon->Attr & (1<<11))) z = -1; - PlotTranslucentPixel(pixeladdr, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr, color, z, polyattr, polygon->IsShadow); // blend with bottom pixel too, if needed if ((dstattr & 0xF) && (pixeladdr < BufferSize)) - PlotTranslucentPixel(pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); } } @@ -1245,17 +1247,17 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) s16 s = interpX.Interpolate(sl, sr); s16 t = interpX.Interpolate(tl, tr); - u32 color = RenderPixel(polygon, vr>>3, vg>>3, vb>>3, s, t); + u32 color = RenderPixel(gpu, polygon, vr>>3, vg>>3, vb>>3, s, t); u8 alpha = color >> 24; // alpha test - if (alpha <= GPU.GPU3D.RenderAlphaRef) continue; + if (alpha <= gpu.GPU3D.RenderAlphaRef) continue; if (alpha == 31) { u32 attr = polyattr | edge; - if ((GPU.GPU3D.RenderDispCnt & (1<<4)) && (attr & 0xF)) + if ((gpu.GPU3D.RenderDispCnt & (1<<4)) && (attr & 0xF)) { // anti-aliasing: all edges are rendered @@ -1278,11 +1280,11 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) else { if (!(polygon->Attr & (1<<11))) z = -1; - PlotTranslucentPixel(pixeladdr, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr, color, z, polyattr, polygon->IsShadow); // blend with bottom pixel too, if needed if ((dstattr & 0xF) && (pixeladdr < BufferSize)) - PlotTranslucentPixel(pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); } } @@ -1337,17 +1339,17 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) s16 s = interpX.Interpolate(sl, sr); s16 t = interpX.Interpolate(tl, tr); - u32 color = RenderPixel(polygon, vr>>3, vg>>3, vb>>3, s, t); + u32 color = RenderPixel(gpu, polygon, vr>>3, vg>>3, vb>>3, s, t); u8 alpha = color >> 24; // alpha test - if (alpha <= GPU.GPU3D.RenderAlphaRef) continue; + if (alpha <= gpu.GPU3D.RenderAlphaRef) continue; if (alpha == 31) { u32 attr = polyattr | edge; - if (GPU.GPU3D.RenderDispCnt & (1<<4)) + if (gpu.GPU3D.RenderDispCnt & (1<<4)) { // anti-aliasing: all edges are rendered @@ -1377,11 +1379,11 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) else { if (!(polygon->Attr & (1<<11))) z = -1; - PlotTranslucentPixel(pixeladdr, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr, color, z, polyattr, polygon->IsShadow); // blend with bottom pixel too, if needed if ((dstattr & 0xF) && (pixeladdr < BufferSize)) - PlotTranslucentPixel(pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); + PlotTranslucentPixel(gpu.GPU3D, pixeladdr+BufferSize, color, z, polyattr, polygon->IsShadow); } } @@ -1389,7 +1391,7 @@ void SoftRenderer::RenderPolygonScanline(RendererPolygon* rp, s32 y) rp->XR = rp->SlopeR.Step(); } -void SoftRenderer::RenderScanline(s32 y, int npolys) +void SoftRenderer::RenderScanline(const GPU& gpu, s32 y, int npolys) { for (int i = 0; i < npolys; i++) { @@ -1399,19 +1401,19 @@ void SoftRenderer::RenderScanline(s32 y, int npolys) if (y >= polygon->YTop && (y < polygon->YBottom || (y == polygon->YTop && polygon->YBottom == polygon->YTop))) { if (polygon->IsShadowMask) - RenderShadowMaskScanline(rp, y); + RenderShadowMaskScanline(gpu.GPU3D, rp, y); else - RenderPolygonScanline(rp, y); + RenderPolygonScanline(gpu, rp, y); } } } -u32 SoftRenderer::CalculateFogDensity(u32 pixeladdr) +u32 SoftRenderer::CalculateFogDensity(const GPU3D& gpu3d, u32 pixeladdr) const { u32 z = DepthBuffer[pixeladdr]; u32 densityid, densityfrac; - if (z < GPU.GPU3D.RenderFogOffset) + if (z < gpu3d.RenderFogOffset) { densityid = 0; densityfrac = 0; @@ -1423,8 +1425,8 @@ u32 SoftRenderer::CalculateFogDensity(u32 pixeladdr) // on hardware, the final value can overflow the 32-bit range with a shift big enough, // causing fog to 'wrap around' and accidentally apply to larger Z ranges - z -= GPU.GPU3D.RenderFogOffset; - z = (z >> 2) << GPU.GPU3D.RenderFogShift; + z -= gpu3d.RenderFogOffset; + z = (z >> 2) << gpu3d.RenderFogShift; densityid = z >> 17; if (densityid >= 32) @@ -1438,20 +1440,20 @@ u32 SoftRenderer::CalculateFogDensity(u32 pixeladdr) // checkme (may be too precise?) u32 density = - ((GPU.GPU3D.RenderFogDensityTable[densityid] * (0x20000-densityfrac)) + - (GPU.GPU3D.RenderFogDensityTable[densityid+1] * densityfrac)) >> 17; + ((gpu3d.RenderFogDensityTable[densityid] * (0x20000-densityfrac)) + + (gpu3d.RenderFogDensityTable[densityid+1] * densityfrac)) >> 17; if (density >= 127) density = 128; return density; } -void SoftRenderer::ScanlineFinalPass(s32 y) +void SoftRenderer::ScanlineFinalPass(const GPU3D& gpu3d, s32 y) { // to consider: // clearing all polygon fog flags if the master flag isn't set? // merging all final pass loops into one? - if (GPU.GPU3D.RenderDispCnt & (1<<5)) + if (gpu3d.RenderDispCnt & (1<<5)) { // edge marking // only applied to topmost pixels @@ -1471,7 +1473,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) ((polyid != (AttrBuffer[pixeladdr-ScanlineWidth] >> 24)) && (z < DepthBuffer[pixeladdr-ScanlineWidth])) || ((polyid != (AttrBuffer[pixeladdr+ScanlineWidth] >> 24)) && (z < DepthBuffer[pixeladdr+ScanlineWidth]))) { - u16 edgecolor = GPU.GPU3D.RenderEdgeTable[polyid >> 3]; + u16 edgecolor = gpu3d.RenderEdgeTable[polyid >> 3]; u32 edgeR = (edgecolor << 1) & 0x3E; if (edgeR) edgeR++; u32 edgeG = (edgecolor >> 4) & 0x3E; if (edgeG) edgeG++; u32 edgeB = (edgecolor >> 9) & 0x3E; if (edgeB) edgeB++; @@ -1484,7 +1486,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) } } - if (GPU.GPU3D.RenderDispCnt & (1<<7)) + if (gpu3d.RenderDispCnt & (1<<7)) { // fog @@ -1497,12 +1499,12 @@ void SoftRenderer::ScanlineFinalPass(s32 y) // TODO: check the 'fog alpha glitch with small Z' GBAtek talks about - bool fogcolor = !(GPU.GPU3D.RenderDispCnt & (1<<6)); + bool fogcolor = !(gpu3d.RenderDispCnt & (1<<6)); - u32 fogR = (GPU.GPU3D.RenderFogColor << 1) & 0x3E; if (fogR) fogR++; - u32 fogG = (GPU.GPU3D.RenderFogColor >> 4) & 0x3E; if (fogG) fogG++; - u32 fogB = (GPU.GPU3D.RenderFogColor >> 9) & 0x3E; if (fogB) fogB++; - u32 fogA = (GPU.GPU3D.RenderFogColor >> 16) & 0x1F; + u32 fogR = (gpu3d.RenderFogColor << 1) & 0x3E; if (fogR) fogR++; + u32 fogG = (gpu3d.RenderFogColor >> 4) & 0x3E; if (fogG) fogG++; + u32 fogB = (gpu3d.RenderFogColor >> 9) & 0x3E; if (fogB) fogB++; + u32 fogA = (gpu3d.RenderFogColor >> 16) & 0x1F; for (int x = 0; x < 256; x++) { @@ -1512,7 +1514,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) u32 attr = AttrBuffer[pixeladdr]; if (attr & (1<<15)) { - density = CalculateFogDensity(pixeladdr); + density = CalculateFogDensity(gpu3d, pixeladdr); srccolor = ColorBuffer[pixeladdr]; srcR = srccolor & 0x3F; @@ -1541,7 +1543,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) attr = AttrBuffer[pixeladdr]; if (!(attr & (1<<15))) continue; - density = CalculateFogDensity(pixeladdr); + density = CalculateFogDensity(gpu3d, pixeladdr); srccolor = ColorBuffer[pixeladdr]; srcR = srccolor & 0x3F; @@ -1562,7 +1564,7 @@ void SoftRenderer::ScanlineFinalPass(s32 y) } } - if (GPU.GPU3D.RenderDispCnt & (1<<4)) + if (gpu3d.RenderDispCnt & (1<<4)) { // anti-aliasing @@ -1615,10 +1617,10 @@ void SoftRenderer::ScanlineFinalPass(s32 y) } } -void SoftRenderer::ClearBuffers() +void SoftRenderer::ClearBuffers(const GPU& gpu) { - u32 clearz = ((GPU.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; - u32 polyid = GPU.GPU3D.RenderClearAttr1 & 0x3F000000; // this sets the opaque polygonID + u32 clearz = ((gpu.GPU3D.RenderClearAttr2 & 0x7FFF) * 0x200) + 0x1FF; + u32 polyid = gpu.GPU3D.RenderClearAttr1 & 0x3F000000; // this sets the opaque polygonID // fill screen borders for edge marking @@ -1648,17 +1650,17 @@ void SoftRenderer::ClearBuffers() // clear the screen - if (GPU.GPU3D.RenderDispCnt & (1<<14)) + if (gpu.GPU3D.RenderDispCnt & (1<<14)) { - u8 xoff = (GPU.GPU3D.RenderClearAttr2 >> 16) & 0xFF; - u8 yoff = (GPU.GPU3D.RenderClearAttr2 >> 24) & 0xFF; + u8 xoff = (gpu.GPU3D.RenderClearAttr2 >> 16) & 0xFF; + u8 yoff = (gpu.GPU3D.RenderClearAttr2 >> 24) & 0xFF; for (int y = 0; y < ScanlineWidth*192; y+=ScanlineWidth) { for (int x = 0; x < 256; x++) { - u16 val2 = ReadVRAM_Texture(0x40000 + (yoff << 9) + (xoff << 1)); - u16 val3 = ReadVRAM_Texture(0x60000 + (yoff << 9) + (xoff << 1)); + u16 val2 = ReadVRAM_Texture(0x40000 + (yoff << 9) + (xoff << 1), gpu); + u16 val3 = ReadVRAM_Texture(0x60000 + (yoff << 9) + (xoff << 1), gpu); // TODO: confirm color conversion u32 r = (val2 << 1) & 0x3E; if (r) r++; @@ -1683,13 +1685,13 @@ void SoftRenderer::ClearBuffers() else { // TODO: confirm color conversion - u32 r = (GPU.GPU3D.RenderClearAttr1 << 1) & 0x3E; if (r) r++; - u32 g = (GPU.GPU3D.RenderClearAttr1 >> 4) & 0x3E; if (g) g++; - u32 b = (GPU.GPU3D.RenderClearAttr1 >> 9) & 0x3E; if (b) b++; - u32 a = (GPU.GPU3D.RenderClearAttr1 >> 16) & 0x1F; + u32 r = (gpu.GPU3D.RenderClearAttr1 << 1) & 0x3E; if (r) r++; + u32 g = (gpu.GPU3D.RenderClearAttr1 >> 4) & 0x3E; if (g) g++; + u32 b = (gpu.GPU3D.RenderClearAttr1 >> 9) & 0x3E; if (b) b++; + u32 a = (gpu.GPU3D.RenderClearAttr1 >> 16) & 0x1F; u32 color = r | (g << 8) | (b << 16) | (a << 24); - polyid |= (GPU.GPU3D.RenderClearAttr1 & 0x8000); + polyid |= (gpu.GPU3D.RenderClearAttr1 & 0x8000); for (int y = 0; y < ScanlineWidth*192; y+=ScanlineWidth) { @@ -1704,7 +1706,7 @@ void SoftRenderer::ClearBuffers() } } -void SoftRenderer::RenderPolygons(bool threaded, Polygon** polygons, int npolys) +void SoftRenderer::RenderPolygons(const GPU& gpu, bool threaded, Polygon** polygons, int npolys) { int j = 0; for (int i = 0; i < npolys; i++) @@ -1713,38 +1715,38 @@ void SoftRenderer::RenderPolygons(bool threaded, Polygon** polygons, int npolys) SetupPolygon(&PolygonList[j++], polygons[i]); } - RenderScanline(0, j); + RenderScanline(gpu, 0, j); for (s32 y = 1; y < 192; y++) { - RenderScanline(y, j); - ScanlineFinalPass(y-1); + RenderScanline(gpu, y, j); + ScanlineFinalPass(gpu.GPU3D, y-1); if (threaded) Platform::Semaphore_Post(Sema_ScanlineCount); } - ScanlineFinalPass(191); + ScanlineFinalPass(gpu.GPU3D, 191); if (threaded) Platform::Semaphore_Post(Sema_ScanlineCount); } -void SoftRenderer::VCount144() +void SoftRenderer::VCount144(GPU& gpu) { - if (RenderThreadRunning.load(std::memory_order_relaxed) && !GPU.GPU3D.AbortFrame) + if (RenderThreadRunning.load(std::memory_order_relaxed) && !gpu.GPU3D.AbortFrame) Platform::Semaphore_Wait(Sema_RenderDone); } -void SoftRenderer::RenderFrame() +void SoftRenderer::RenderFrame(GPU& gpu) { - auto textureDirty = GPU.VRAMDirty_Texture.DeriveState(GPU.VRAMMap_Texture, GPU); - auto texPalDirty = GPU.VRAMDirty_TexPal.DeriveState(GPU.VRAMMap_TexPal, GPU); + auto textureDirty = gpu.VRAMDirty_Texture.DeriveState(gpu.VRAMMap_Texture, gpu); + auto texPalDirty = gpu.VRAMDirty_TexPal.DeriveState(gpu.VRAMMap_TexPal, gpu); - bool textureChanged = GPU.MakeVRAMFlat_TextureCoherent(textureDirty); - bool texPalChanged = GPU.MakeVRAMFlat_TexPalCoherent(texPalDirty); + bool textureChanged = gpu.MakeVRAMFlat_TextureCoherent(textureDirty); + bool texPalChanged = gpu.MakeVRAMFlat_TexPalCoherent(texPalDirty); - FrameIdentical = !(textureChanged || texPalChanged) && GPU.GPU3D.RenderFrameIdentical; + FrameIdentical = !(textureChanged || texPalChanged) && gpu.GPU3D.RenderFrameIdentical; if (RenderThreadRunning.load(std::memory_order_relaxed)) { @@ -1752,17 +1754,17 @@ void SoftRenderer::RenderFrame() } else if (!FrameIdentical) { - ClearBuffers(); - RenderPolygons(false, &GPU.GPU3D.RenderPolygonRAM[0], GPU.GPU3D.RenderNumPolygons); + ClearBuffers(gpu); + RenderPolygons(gpu, false, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons); } } -void SoftRenderer::RestartFrame() +void SoftRenderer::RestartFrame(GPU& gpu) { - SetupRenderThread(); + SetupRenderThread(gpu); } -void SoftRenderer::RenderThreadFunc() +void SoftRenderer::RenderThreadFunc(GPU& gpu) { for (;;) { @@ -1776,8 +1778,8 @@ void SoftRenderer::RenderThreadFunc() } else { - ClearBuffers(); - RenderPolygons(true, &GPU.GPU3D.RenderPolygonRAM[0], GPU.GPU3D.RenderNumPolygons); + ClearBuffers(gpu); + RenderPolygons(gpu, true, &gpu.GPU3D.RenderPolygonRAM[0], gpu.GPU3D.RenderNumPolygons); } Platform::Semaphore_Post(Sema_RenderDone); diff --git a/src/GPU3D_Soft.h b/src/GPU3D_Soft.h index d1d7f89c..1c4d7735 100644 --- a/src/GPU3D_Soft.h +++ b/src/GPU3D_Soft.h @@ -29,19 +29,19 @@ namespace melonDS class SoftRenderer : public Renderer3D { public: - SoftRenderer(melonDS::GPU& gpu, bool threaded = false) noexcept; + SoftRenderer(bool threaded = false) noexcept; ~SoftRenderer() override; - void Reset() override; + void Reset(GPU& gpu) override; - void SetThreaded(bool threaded) noexcept; + void SetThreaded(bool threaded, GPU& gpu) noexcept; [[nodiscard]] bool IsThreaded() const noexcept { return Threaded; } - void VCount144() override; - void RenderFrame() override; - void RestartFrame() override; + void VCount144(GPU& gpu) override; + void RenderFrame(GPU& gpu) override; + void RestartFrame(GPU& gpu) override; u32* GetLine(int line) override; - void SetupRenderThread(); + void SetupRenderThread(GPU& gpu); void StopRenderThread(); private: // Notes on the interpolator: @@ -429,16 +429,16 @@ private: }; template - inline T ReadVRAM_Texture(u32 addr) + inline T ReadVRAM_Texture(u32 addr, const GPU& gpu) const { - return *(T*)&GPU.VRAMFlat_Texture[addr & 0x7FFFF]; + return *(T*)&gpu.VRAMFlat_Texture[addr & 0x7FFFF]; } template - inline T ReadVRAM_TexPal(u32 addr) + inline T ReadVRAM_TexPal(u32 addr, const GPU& gpu) const { - return *(T*)&GPU.VRAMFlat_TexPal[addr & 0x1FFFF]; + return *(T*)&gpu.VRAMFlat_TexPal[addr & 0x1FFFF]; } - u32 AlphaBlend(u32 srccolor, u32 dstcolor, u32 alpha) noexcept; + u32 AlphaBlend(const GPU3D& gpu3d, u32 srccolor, u32 dstcolor, u32 alpha) const noexcept; struct RendererPolygon { @@ -453,24 +453,23 @@ private: }; - melonDS::GPU& GPU; RendererPolygon PolygonList[2048]; - void TextureLookup(u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha); - u32 RenderPixel(Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t); - void PlotTranslucentPixel(u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow); + void TextureLookup(const GPU& gpu, u32 texparam, u32 texpal, s16 s, s16 t, u16* color, u8* alpha) const; + u32 RenderPixel(const GPU& gpu, const Polygon* polygon, u8 vr, u8 vg, u8 vb, s16 s, s16 t) const; + void PlotTranslucentPixel(const GPU3D& gpu3d, u32 pixeladdr, u32 color, u32 z, u32 polyattr, u32 shadow); void CheckForLine(RendererPolygon* rp); - void SetupPolygonLeftEdge(RendererPolygon* rp, s32 y); - void SetupPolygonRightEdge(RendererPolygon* rp, s32 y); - void SetupPolygon(RendererPolygon* rp, Polygon* polygon); - void RenderShadowMaskScanline(RendererPolygon* rp, s32 y); - void RenderPolygonScanline(RendererPolygon* rp, s32 y); - void RenderScanline(s32 y, int npolys); - u32 CalculateFogDensity(u32 pixeladdr); - void ScanlineFinalPass(s32 y); - void ClearBuffers(); - void RenderPolygons(bool threaded, Polygon** polygons, int npolys); + void SetupPolygonLeftEdge(RendererPolygon* rp, s32 y) const; + void SetupPolygonRightEdge(RendererPolygon* rp, s32 y) const; + void SetupPolygon(RendererPolygon* rp, Polygon* polygon) const; + void RenderShadowMaskScanline(const GPU3D& gpu3d, RendererPolygon* rp, s32 y); + void RenderPolygonScanline(const GPU& gpu, RendererPolygon* rp, s32 y); + void RenderScanline(const GPU& gpu, s32 y, int npolys); + u32 CalculateFogDensity(const GPU3D& gpu3d, u32 pixeladdr) const; + void ScanlineFinalPass(const GPU3D& gpu3d, s32 y); + void ClearBuffers(const GPU& gpu); + void RenderPolygons(const GPU& gpu, bool threaded, Polygon** polygons, int npolys); - void RenderThreadFunc(); + void RenderThreadFunc(GPU& gpu); // buffer dimensions are 258x194 to add a offscreen 1px border // which simplifies edge marking tests diff --git a/src/JitBlock.h b/src/JitBlock.h index 6a187b27..9b31d6d7 100644 --- a/src/JitBlock.h +++ b/src/JitBlock.h @@ -46,12 +46,12 @@ public: JitBlockEntry EntryPoint; - u32* AddressRanges() - { return &Data[0]; } - u32* AddressMasks() - { return &Data[NumAddresses]; } - u32* Literals() - { return &Data[NumAddresses * 2]; } + const u32* AddressRanges() const { return &Data[0]; } + u32* AddressRanges() { return &Data[0]; } + const u32* AddressMasks() const { return &Data[NumAddresses]; } + u32* AddressMasks() { return &Data[NumAddresses]; } + const u32* Literals() const { return &Data[NumAddresses * 2]; } + u32* Literals() { return &Data[NumAddresses * 2]; } private: TinyVector Data; diff --git a/src/MemConstants.h b/src/MemConstants.h index ab80faba..e9aa6b2b 100644 --- a/src/MemConstants.h +++ b/src/MemConstants.h @@ -30,6 +30,10 @@ constexpr u32 NWRAMSize = 0x40000; constexpr u32 ARM9BIOSSize = 0x1000; constexpr u32 ARM7BIOSSize = 0x4000; constexpr u32 DSiBIOSSize = 0x10000; +constexpr u32 ITCMPhysicalSize = 0x8000; +constexpr u32 DTCMPhysicalSize = 0x4000; +constexpr u32 ARM7BIOSCRC32 = 0x1280f0d5; +constexpr u32 ARM9BIOSCRC32 = 0x2ab23573; } #endif // MELONDS_MEMCONSTANTS_H \ No newline at end of file diff --git a/src/NDS.cpp b/src/NDS.cpp index 3aa4a52a..7c176fae 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -34,6 +34,7 @@ #include "AREngine.h" #include "Platform.h" #include "FreeBIOS.h" +#include "Args.h" #include "DSi.h" #include "DSi_SPI_TSC.h" @@ -74,19 +75,39 @@ const s32 kIterationCycleMargin = 8; NDS* NDS::Current = nullptr; -NDS::NDS(int type) noexcept : +NDS::NDS() noexcept : + NDS( + NDSArgs { + nullptr, + nullptr, + bios_arm9_bin, + bios_arm7_bin, + Firmware(0), + } + ) +{ +} + +NDS::NDS(NDSArgs&& args, int type) noexcept : ConsoleType(type), - JIT(*this), - SPU(*this), - GPU(*this), - SPI(*this), + ARM7BIOS(args.ARM7BIOS), + ARM9BIOS(args.ARM9BIOS), + ARM7BIOSNative(CRC32(ARM7BIOS.data(), ARM7BIOS.size()) == ARM7BIOSCRC32), + ARM9BIOSNative(CRC32(ARM9BIOS.data(), ARM9BIOS.size()) == ARM9BIOSCRC32), + JIT(*this, args.JIT), + SPU(*this, args.BitDepth, args.Interpolation), + GPU(*this, std::move(args.Renderer3D)), + SPI(*this, std::move(args.Firmware)), RTC(*this), Wifi(*this), - NDSCartSlot(*this), - GBACartSlot(), + NDSCartSlot(*this, std::move(args.NDSROM)), + GBACartSlot(type == 1 ? nullptr : std::move(args.GBAROM)), AREngine(*this), - ARM9(*this), - ARM7(*this), + ARM9(*this, args.GDB, args.JIT.has_value()), + ARM7(*this, args.GDB, args.JIT.has_value()), +#ifdef JIT_ENABLED + EnableJIT(args.JIT.has_value()), +#endif DMAs { DMA(0, 0, *this), DMA(0, 1, *this), @@ -187,6 +208,22 @@ void NDS::SetARM7RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswi } } +#ifdef JIT_ENABLED +void NDS::SetJITArgs(std::optional args) noexcept +{ + if (args) + { // If we want to turn the JIT on... + JIT.SetJITArgs(*args); + } + else if (args.has_value() != EnableJIT) + { // Else if we want to turn the JIT off, and it wasn't already off... + JIT.ResetBlockCache(); + } + + EnableJIT = args.has_value(); +} +#endif + void NDS::InitTimings() { // TODO, eventually: @@ -224,7 +261,7 @@ void NDS::InitTimings() // handled later: GBA slot, wifi } -bool NDS::NeedsDirectBoot() +bool NDS::NeedsDirectBoot() const { if (ConsoleType == 1) { @@ -233,12 +270,12 @@ bool NDS::NeedsDirectBoot() } else { - // internal BIOS does not support direct boot - if (!Platform::GetConfigBool(Platform::ExternalBIOSEnable)) + // DSi/3DS firmwares aren't bootable, neither is the generated firmware + if (!SPI.GetFirmware().IsBootable()) return true; - // DSi/3DS firmwares aren't bootable - if (!SPI.GetFirmware()->IsBootable()) + // FreeBIOS requires direct boot (it can't boot firmware) + if (!IsLoadedARM9BIOSKnownNative() || !IsLoadedARM7BIOSKnownNative()) return true; return false; @@ -252,6 +289,13 @@ void NDS::SetupDirectBoot() const u8* cartrom = NDSCartSlot.GetCart()->GetROM(); MapSharedWRAM(3); + // Copy the Nintendo logo from the NDS ROM header to the ARM9 BIOS if using FreeBIOS + // Games need this for DS<->GBA comm to work + if (!IsLoadedARM9BIOSKnownNative()) + { + memcpy(ARM9BIOS.data() + 0x20, header.NintendoLogo, 0x9C); + } + // setup main RAM data for (u32 i = 0; i < 0x170; i+=4) @@ -378,10 +422,6 @@ void NDS::Reset() Platform::FileHandle* f; u32 i; -#ifdef JIT_ENABLED - EnableJIT = Platform::GetConfigBool(Platform::JIT_Enable); -#endif - RunningGame = false; LastSysClockCycles = 0; @@ -489,28 +529,6 @@ void NDS::Reset() SPI.Reset(); RTC.Reset(); Wifi.Reset(); - - // TODO: move the SOUNDBIAS/degrade logic to SPU? - - // The SOUNDBIAS register does nothing on DSi - SPU.SetApplyBias(ConsoleType == 0); - - bool degradeAudio = true; - - if (ConsoleType == 1) - { - //DSi::Reset(); - KeyInput &= ~(1 << (16+6)); - degradeAudio = false; - } - - int bitDepth = Platform::GetConfigInt(Platform::AudioBitDepth); - if (bitDepth == 1) // Always 10-bit - degradeAudio = true; - else if (bitDepth == 2) // Always 16-bit - degradeAudio = false; - - SPU.SetDegrade10Bit(degradeAudio); } void NDS::Start() @@ -710,42 +728,27 @@ bool NDS::DoSavestate(Savestate* file) return true; } -bool NDS::LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) +void NDS::SetNDSCart(std::unique_ptr&& cart) { - if (!NDSCartSlot.LoadROM(romdata, romlen)) - return false; - - if (savedata && savelen) - NDSCartSlot.LoadSave(savedata, savelen); - - return true; + NDSCartSlot.SetCart(std::move(cart)); + // The existing cart will always be ejected; + // if cart is null, then that's equivalent to ejecting a cart + // without inserting a new one. } -void NDS::LoadSave(const u8* savedata, u32 savelen) +void NDS::SetNDSSave(const u8* savedata, u32 savelen) { if (savedata && savelen) - NDSCartSlot.LoadSave(savedata, savelen); + NDSCartSlot.SetSaveMemory(savedata, savelen); } -void NDS::EjectCart() +void NDS::SetGBASave(const u8* savedata, u32 savelen) { - NDSCartSlot.EjectCart(); -} + if (ConsoleType == 0 && savedata && savelen) + { + GBACartSlot.SetSaveMemory(savedata, savelen); + } -bool NDS::CartInserted() -{ - return NDSCartSlot.GetCart() != nullptr; -} - -bool NDS::LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) -{ - if (!GBACartSlot.LoadROM(romdata, romlen)) - return false; - - if (savedata && savelen) - GBACartSlot.LoadSave(savedata, savelen); - - return true; } void NDS::LoadGBAAddon(int type) @@ -753,24 +756,21 @@ void NDS::LoadGBAAddon(int type) GBACartSlot.LoadAddon(type); } -void NDS::EjectGBACart() -{ - GBACartSlot.EjectCart(); -} - void NDS::LoadBIOS() { Reset(); } -bool NDS::IsLoadedARM9BIOSBuiltIn() +void NDS::SetARM7BIOS(const std::array& bios) noexcept { - return memcmp(ARM9BIOS, bios_arm9_bin, sizeof(NDS::ARM9BIOS)) == 0; + ARM7BIOS = bios; + ARM7BIOSNative = CRC32(ARM7BIOS.data(), ARM7BIOS.size()) == ARM7BIOSCRC32; } -bool NDS::IsLoadedARM7BIOSBuiltIn() +void NDS::SetARM9BIOS(const std::array& bios) noexcept { - return memcmp(ARM7BIOS, bios_arm7_bin, sizeof(NDS::ARM7BIOS)) == 0; + ARM9BIOS = bios; + ARM9BIOSNative = CRC32(ARM9BIOS.data(), ARM9BIOS.size()) == ARM9BIOSCRC32; } u64 NDS::NextTarget() @@ -1169,7 +1169,7 @@ void NDS::SetKeyMask(u32 mask) CheckKeyIRQ(1, oldkey, KeyInput); } -bool NDS::IsLidClosed() +bool NDS::IsLidClosed() const { if (KeyInput & (1<<23)) return true; return false; @@ -1339,7 +1339,7 @@ void NDS::SetIRQ(u32 cpu, u32 irq) { CPUStop &= ~CPUStop_Sleep; CPUStop |= CPUStop_Wakeup; - GPU.GPU3D.RestartFrame(); + GPU.GPU3D.RestartFrame(GPU); } } } @@ -1362,7 +1362,7 @@ void NDS::ClearIRQ2(u32 irq) UpdateIRQ(1); } -bool NDS::HaltInterrupted(u32 cpu) +bool NDS::HaltInterrupted(u32 cpu) const { if (cpu == 0) { @@ -1433,7 +1433,7 @@ void NDS::EnterSleepMode() ARM7.Halt(2); } -u32 NDS::GetPC(u32 cpu) +u32 NDS::GetPC(u32 cpu) const { return cpu ? ARM7.R[15] : ARM9.R[15]; } @@ -1661,7 +1661,7 @@ void NDS::TimerStart(u32 id, u16 cnt) -bool NDS::DMAsInMode(u32 cpu, u32 mode) +bool NDS::DMAsInMode(u32 cpu, u32 mode) const { cpu <<= 2; if (DMAs[cpu+0].IsInMode(mode)) return true; @@ -1672,7 +1672,7 @@ bool NDS::DMAsInMode(u32 cpu, u32 mode) return false; } -bool NDS::DMAsRunning(u32 cpu) +bool NDS::DMAsRunning(u32 cpu) const { cpu <<= 2; if (DMAs[cpu+0].IsRunning()) return true; @@ -2252,7 +2252,7 @@ bool NDS::ARM9GetMemRegion(u32 addr, bool write, MemRegion* region) if ((addr & 0xFFFFF000) == 0xFFFF0000 && !write) { - region->Mem = ARM9BIOS; + region->Mem = &ARM9BIOS[0]; region->Mask = 0xFFF; return true; } @@ -2700,7 +2700,7 @@ bool NDS::ARM7GetMemRegion(u32 addr, bool write, MemRegion* region) { if (ARM7.R[15] < 0x4000 && (addr >= ARM7BIOSProt || ARM7.R[15] < ARM7BIOSProt)) { - region->Mem = ARM7BIOS; + region->Mem = &ARM7BIOS[0]; region->Mask = 0x3FFF; return true; } @@ -4216,4 +4216,4 @@ void NDS::ARM7IOWrite32(u32 addr, u32 val) Log(LogLevel::Debug, "unknown ARM7 IO write32 %08X %08X %08X\n", addr, val, ARM7.R[15]); } -} \ No newline at end of file +} diff --git a/src/NDS.h b/src/NDS.h index 0979b2fa..23b1f888 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include "Platform.h" @@ -36,7 +36,12 @@ #include "AREngine.h" #include "GPU.h" #include "ARMJIT.h" +#include "MemRegion.h" +#include "ARMJIT_Memory.h" +#include "ARM.h" +#include "CRC32.h" #include "DMA.h" +#include "FreeBIOS.h" // when touching the main loop/timing code, pls test a lot of shit // with this enabled, to make sure it doesn't desync @@ -44,7 +49,8 @@ namespace melonDS { - +struct NDSArgs; +class Firmware; enum { Event_LCD = 0, @@ -217,11 +223,12 @@ class ARMJIT; class NDS { -public: - +private: #ifdef JIT_ENABLED bool EnableJIT; #endif + +public: // TODO: Encapsulate the rest of these members int ConsoleType; int CurCPU; @@ -255,8 +262,14 @@ public: u8 ROMSeed0[2*8]; u8 ROMSeed1[2*8]; - u8 ARM9BIOS[0x1000]; - u8 ARM7BIOS[0x4000]; +protected: + // These BIOS arrays should be declared *before* the component objects (JIT, SPI, etc.) + // so that they're initialized before the component objects' constructors run. + std::array ARM9BIOS; + std::array ARM7BIOS; + bool ARM9BIOSNative; + bool ARM7BIOSNative; +public: // TODO: Encapsulate the rest of these members u16 ARM7BIOSProt; u8* MainRAM; @@ -303,25 +316,70 @@ public: void SetARM9RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth, int nonseq, int seq); void SetARM7RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth, int nonseq, int seq); - // 0=DS 1=DSi - void SetConsoleType(int type); - void LoadBIOS(); - bool IsLoadedARM9BIOSBuiltIn(); - bool IsLoadedARM7BIOSBuiltIn(); - virtual bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen); - void LoadSave(const u8* savedata, u32 savelen); - virtual void EjectCart(); - bool CartInserted(); + /// @return \c true if the loaded ARM9 BIOS image is a known dump + /// of a native DS-compatible ARM9 BIOS. + [[nodiscard]] bool IsLoadedARM9BIOSKnownNative() const noexcept { return ARM9BIOSNative; } + [[nodiscard]] const std::array& GetARM9BIOS() const noexcept { return ARM9BIOS; } + void SetARM9BIOS(const std::array& bios) noexcept; - virtual bool NeedsDirectBoot(); + [[nodiscard]] const std::array& GetARM7BIOS() const noexcept { return ARM7BIOS; } + void SetARM7BIOS(const std::array& bios) noexcept; + + /// @return \c true if the loaded ARM7 BIOS image is a known dump + /// of a native DS-compatible ARM9 BIOS. + [[nodiscard]] bool IsLoadedARM7BIOSKnownNative() const noexcept { return ARM7BIOSNative; } + + [[nodiscard]] NDSCart::CartCommon* GetNDSCart() { return NDSCartSlot.GetCart(); } + [[nodiscard]] const NDSCart::CartCommon* GetNDSCart() const { return NDSCartSlot.GetCart(); } + virtual void SetNDSCart(std::unique_ptr&& cart); + [[nodiscard]] bool CartInserted() const noexcept { return NDSCartSlot.GetCart() != nullptr; } + virtual std::unique_ptr EjectCart() { return NDSCartSlot.EjectCart(); } + + [[nodiscard]] u8* GetNDSSave() { return NDSCartSlot.GetSaveMemory(); } + [[nodiscard]] const u8* GetNDSSave() const { return NDSCartSlot.GetSaveMemory(); } + [[nodiscard]] u32 GetNDSSaveLength() const { return NDSCartSlot.GetSaveMemoryLength(); } + void SetNDSSave(const u8* savedata, u32 savelen); + + const Firmware& GetFirmware() const { return SPI.GetFirmwareMem()->GetFirmware(); } + Firmware& GetFirmware() { return SPI.GetFirmwareMem()->GetFirmware(); } + void SetFirmware(Firmware&& firmware) { SPI.GetFirmwareMem()->SetFirmware(std::move(firmware)); } + + const Renderer3D& GetRenderer3D() const noexcept { return GPU.GetRenderer3D(); } + Renderer3D& GetRenderer3D() noexcept { return GPU.GetRenderer3D(); } + void SetRenderer3D(std::unique_ptr&& renderer) noexcept + { + if (renderer != nullptr) + GPU.SetRenderer3D(std::move(renderer)); + } + + virtual bool NeedsDirectBoot() const; void SetupDirectBoot(const std::string& romname); virtual void SetupDirectBoot(); - bool LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen); + [[nodiscard]] GBACart::CartCommon* GetGBACart() { return (ConsoleType == 1) ? nullptr : GBACartSlot.GetCart(); } + [[nodiscard]] const GBACart::CartCommon* GetGBACart() const { return (ConsoleType == 1) ? nullptr : GBACartSlot.GetCart(); } + + /// Inserts a GBA cart into the emulated console's Slot-2. + /// + /// @param cart The GBA cart, most likely (but not necessarily) returned from GBACart::ParseROM. + /// To insert an accessory that doesn't use a ROM image + /// (e.g. the Expansion Pak), create it manually and pass it here. + /// If \c nullptr, the existing cart is ejected. + /// If this is a DSi, this method does nothing. + /// + /// @post \c cart is \c nullptr and this NDS takes ownership + /// of the cart object it held, if any. + void SetGBACart(std::unique_ptr&& cart) { if (ConsoleType == 0) GBACartSlot.SetCart(std::move(cart)); } + + u8* GetGBASave() { return GBACartSlot.GetSaveMemory(); } + const u8* GetGBASave() const { return GBACartSlot.GetSaveMemory(); } + u32 GetGBASaveLength() const { return GBACartSlot.GetSaveMemoryLength(); } + void SetGBASave(const u8* savedata, u32 savelen); + void LoadGBAAddon(int type); - void EjectGBACart(); + std::unique_ptr EjectGBACart() { return GBACartSlot.EjectCart(); } u32 RunFrame(); @@ -332,10 +390,10 @@ public: void SetKeyMask(u32 mask); - bool IsLidClosed(); + bool IsLidClosed() const; void SetLidClosed(bool closed); - virtual void CamInputFrame(int cam, u32* data, int width, int height, bool rgb) {} + virtual void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) {} void MicInputFrame(s16* data, int samples); void RegisterEventFunc(u32 id, u32 funcid, EventFunc func); @@ -354,20 +412,20 @@ public: void ClearIRQ(u32 cpu, u32 irq); void SetIRQ2(u32 irq); void ClearIRQ2(u32 irq); - bool HaltInterrupted(u32 cpu); + bool HaltInterrupted(u32 cpu) const; void StopCPU(u32 cpu, u32 mask); void ResumeCPU(u32 cpu, u32 mask); void GXFIFOStall(); void GXFIFOUnstall(); - u32 GetPC(u32 cpu); + u32 GetPC(u32 cpu) const; u64 GetSysClockCycles(int num); void NocashPrint(u32 cpu, u32 addr); void MonitorARM9Jump(u32 addr); - virtual bool DMAsInMode(u32 cpu, u32 mode); - virtual bool DMAsRunning(u32 cpu); + virtual bool DMAsInMode(u32 cpu, u32 mode) const; + virtual bool DMAsRunning(u32 cpu) const; virtual void CheckDMAs(u32 cpu, u32 mode); virtual void StopDMAs(u32 cpu, u32 mode); @@ -405,6 +463,14 @@ public: virtual void ARM7IOWrite16(u32 addr, u16 val); virtual void ARM7IOWrite32(u32 addr, u32 val); +#ifdef JIT_ENABLED + [[nodiscard]] bool IsJITEnabled() const noexcept { return EnableJIT; } + void SetJITArgs(std::optional args) noexcept; +#else + [[nodiscard]] bool IsJITEnabled() const noexcept { return false; } + void SetJITArgs(std::optional args) noexcept {} +#endif + private: void InitTimings(); u32 SchedListMask; @@ -456,7 +522,8 @@ private: template u32 RunFrame(); public: - NDS() noexcept : NDS(0) {} + NDS(NDSArgs&& args) noexcept : NDS(std::move(args), 0) {} + NDS() noexcept; virtual ~NDS() noexcept; NDS(const NDS&) = delete; NDS& operator=(const NDS&) = delete; @@ -465,7 +532,7 @@ public: // The frontend should set and unset this manually after creating and destroying the NDS object. [[deprecated("Temporary workaround until JIT code generation is revised to accommodate multiple NDS objects.")]] static NDS* Current; protected: - explicit NDS(int type) noexcept; + explicit NDS(NDSArgs&& args, int type) noexcept; virtual void DoSavestateExtra(Savestate* file) {} }; diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index 95306fc5..a64d8a27 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -20,12 +20,12 @@ #include "NDS.h" #include "DSi.h" #include "NDSCart.h" -#include "ARM.h" #include "CRC32.h" #include "Platform.h" #include "ROMList.h" #include "melonDLDI.h" -#include "xxhash/xxhash.h" +#include "FATStorage.h" +#include "Utils.h" namespace melonDS { @@ -43,12 +43,12 @@ enum // SRAM TODO: emulate write delays??? -u32 ByteSwap(u32 val) +constexpr u32 ByteSwap(u32 val) { return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24); } -void NDSCartSlot::Key1_Encrypt(u32* data) noexcept +void NDSCartSlot::Key1_Encrypt(u32* data) const noexcept { u32 y = data[0]; u32 x = data[1]; @@ -69,7 +69,7 @@ void NDSCartSlot::Key1_Encrypt(u32* data) noexcept data[1] = y ^ Key1_KeyBuf[0x11]; } -void NDSCartSlot::Key1_Decrypt(u32* data) noexcept +void NDSCartSlot::Key1_Decrypt(u32* data) const noexcept { u32 y = data[0]; u32 x = data[1]; @@ -109,9 +109,9 @@ void NDSCartSlot::Key1_ApplyKeycode(u32* keycode, u32 mod) noexcept } } -void NDSCartSlot::Key1_LoadKeyBuf(bool dsi, bool externalBios, u8 *bios, u32 biosLength) noexcept +void NDSCartSlot::Key1_LoadKeyBuf(bool dsi, const u8 *bios, u32 biosLength) noexcept { - if (externalBios) + if (NDS.IsLoadedARM7BIOSKnownNative()) { u32 expected_bios_length = dsi ? 0x10000 : 0x4000; if (biosLength != expected_bios_length) @@ -136,9 +136,9 @@ void NDSCartSlot::Key1_LoadKeyBuf(bool dsi, bool externalBios, u8 *bios, u32 bio } } -void NDSCartSlot::Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, u8 *bios, u32 biosLength) noexcept +void NDSCartSlot::Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, const u8 *bios, u32 biosLength) noexcept { - Key1_LoadKeyBuf(dsi, Platform::GetConfigBool(Platform::ExternalBIOSEnable), bios, biosLength); + Key1_LoadKeyBuf(dsi, bios, biosLength); u32 keycode[3] = {idcode, idcode>>1, idcode<<1}; if (level >= 1) Key1_ApplyKeycode(keycode, mod); @@ -152,7 +152,7 @@ void NDSCartSlot::Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, u8 } -void NDSCartSlot::Key2_Encrypt(u8* data, u32 len) noexcept +void NDSCartSlot::Key2_Encrypt(const u8* data, u32 len) noexcept { for (u32 i = 0; i < len; i++) { @@ -173,27 +173,29 @@ void NDSCartSlot::Key2_Encrypt(u8* data, u32 len) noexcept } -CartCommon::CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams) +CartCommon::CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) : + CartCommon(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, type) { - ROM = rom; - ROMLength = len; - ChipID = chipid; - ROMParams = romparams; +} - memcpy(&Header, rom, sizeof(Header)); +CartCommon::CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) : + ROM(std::move(rom)), + ROMLength(len), + ChipID(chipid), + ROMParams(romparams), + CartType(type) +{ + memcpy(&Header, ROM.get(), sizeof(Header)); IsDSi = Header.IsDSi() && !badDSiDump; DSiBase = Header.DSiRegionStart << 19; } -CartCommon::~CartCommon() -{ - delete[] ROM; -} +CartCommon::~CartCommon() = default; u32 CartCommon::Checksum() const { const NDSHeader& header = GetHeader(); - u32 crc = CRC32(ROM, 0x40); + u32 crc = CRC32(ROM.get(), 0x40); crc = CRC32(&ROM[header.ARM9ROMOffset], header.ARM9Size, crc); crc = CRC32(&ROM[header.ARM7ROMOffset], header.ARM7Size, crc); @@ -230,15 +232,7 @@ void CartCommon::DoSavestate(Savestate* file) file->Bool32(&DSiMode); } -void CartCommon::SetupSave(u32 type) -{ -} - -void CartCommon::LoadSave(const u8* savedata, u32 savelen) -{ -} - -int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) +int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) { if (CmdEncMode == 0) { @@ -267,7 +261,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da case 0x3C: CmdEncMode = 1; - cartslot.Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2, nds.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + cartslot.Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2, nds.GetARM7BIOS().data(), ARM7BIOSSize); DSiMode = false; return 0; @@ -276,7 +270,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da { auto& dsi = static_cast(nds); CmdEncMode = 1; - cartslot.Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2, dsi.ARM7iBIOS, sizeof(DSi::ARM7iBIOS)); + cartslot.Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2, &dsi.ARM7iBIOS[0], sizeof(DSi::ARM7iBIOS)); DSiMode = true; } return 0; @@ -351,7 +345,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da return 0; } -void CartCommon::ROMCommandFinish(u8* cmd, u8* data, u32 len) +void CartCommon::ROMCommandFinish(const u8* cmd, u8* data, u32 len) { } @@ -360,23 +354,13 @@ u8 CartCommon::SPIWrite(u8 val, u32 pos, bool last) return 0xFF; } -u8 *CartCommon::GetSaveMemory() const -{ - return nullptr; -} - -u32 CartCommon::GetSaveMemoryLength() const -{ - return 0; -} - -void CartCommon::ReadROM(u32 addr, u32 len, u8* data, u32 offset) +void CartCommon::ReadROM(u32 addr, u32 len, u8* data, u32 offset) const { if (addr >= ROMLength) return; if ((addr+len) > ROMLength) len = ROMLength - addr; - memcpy(data+offset, ROM+addr, len); + memcpy(data+offset, ROM.get()+addr, len); } const NDSBanner* CartCommon::Banner() const @@ -385,22 +369,68 @@ const NDSBanner* CartCommon::Banner() const size_t bannersize = header.IsDSi() ? 0x23C0 : 0xA40; if (header.BannerOffset >= 0x200 && header.BannerOffset < (ROMLength - bannersize)) { - return reinterpret_cast(ROM + header.BannerOffset); + return reinterpret_cast(ROM.get() + header.BannerOffset); } return nullptr; } -CartRetail::CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams) : CartCommon(rom, len, chipid, badDSiDump, romparams) +CartRetail::CartRetail(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, melonDS::NDSCart::CartType type) : + CartRetail(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, type) { - SRAM = nullptr; } -CartRetail::~CartRetail() +CartRetail::CartRetail(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, melonDS::NDSCart::CartType type) : + CartCommon(std::move(rom), len, chipid, badDSiDump, romparams, type) { - if (SRAM) delete[] SRAM; + u32 savememtype = ROMParams.SaveMemType <= 10 ? ROMParams.SaveMemType : 0; + constexpr int sramlengths[] = + { + 0, + 512, + 8192, 65536, 128*1024, + 256*1024, 512*1024, 1024*1024, + 8192*1024, 16384*1024, 65536*1024 + }; + SRAMLength = sramlengths[savememtype]; + + if (SRAMLength) + { // If this cart should have any save data... + if (sram && sramlen == SRAMLength) + { // If we were given save data that already has the correct length... + SRAM = std::move(sram); + } + else + { // Copy in what we can, truncate the rest. + SRAM = std::make_unique(SRAMLength); + memset(SRAM.get(), 0xFF, SRAMLength); + + if (sram) + { // If we have anything to copy, that is. + memcpy(SRAM.get(), sram.get(), std::min(sramlen, SRAMLength)); + } + } + } + + switch (savememtype) + { + case 1: SRAMType = 1; break; // EEPROM, small + case 2: + case 3: + case 4: SRAMType = 2; break; // EEPROM, regular + case 5: + case 6: + case 7: SRAMType = 3; break; // FLASH + case 8: + case 9: + case 10: SRAMType = 4; break; // NAND + default: SRAMType = 0; break; // ...whatever else + } } +CartRetail::~CartRetail() = default; +// std::unique_ptr cleans up the SRAM and ROM + void CartRetail::Reset() { CartCommon::Reset(); @@ -425,13 +455,11 @@ void CartRetail::DoSavestate(Savestate* file) Log(LogLevel::Warn, "savestate: VERY BAD!!!! SRAM LENGTH DIFFERENT. %d -> %d\n", oldlen, SRAMLength); Log(LogLevel::Warn, "oh well. loading it anyway. adsfgdsf\n"); - if (oldlen) delete[] SRAM; - SRAM = nullptr; - if (SRAMLength) SRAM = new u8[SRAMLength]; + SRAM = SRAMLength ? std::make_unique(SRAMLength) : nullptr; } if (SRAMLength) { - file->VarArray(SRAM, SRAMLength); + file->VarArray(SRAM.get(), SRAMLength); } // SPI status shito @@ -441,57 +469,19 @@ void CartRetail::DoSavestate(Savestate* file) file->Var8(&SRAMStatus); if ((!file->Saving) && SRAM) - Platform::WriteNDSSave(SRAM, SRAMLength, 0, SRAMLength); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, 0, SRAMLength); } -void CartRetail::SetupSave(u32 type) -{ - if (SRAM) delete[] SRAM; - SRAM = nullptr; - - if (type > 10) type = 0; - int sramlen[] = - { - 0, - 512, - 8192, 65536, 128*1024, - 256*1024, 512*1024, 1024*1024, - 8192*1024, 16384*1024, 65536*1024 - }; - SRAMLength = sramlen[type]; - - if (SRAMLength) - { - SRAM = new u8[SRAMLength]; - memset(SRAM, 0xFF, SRAMLength); - } - - switch (type) - { - case 1: SRAMType = 1; break; // EEPROM, small - case 2: - case 3: - case 4: SRAMType = 2; break; // EEPROM, regular - case 5: - case 6: - case 7: SRAMType = 3; break; // FLASH - case 8: - case 9: - case 10: SRAMType = 4; break; // NAND - default: SRAMType = 0; break; // ...whatever else - } -} - -void CartRetail::LoadSave(const u8* savedata, u32 savelen) +void CartRetail::SetSaveMemory(const u8* savedata, u32 savelen) { if (!SRAM) return; u32 len = std::min(savelen, SRAMLength); - memcpy(SRAM, savedata, len); + memcpy(SRAM.get(), savedata, len); Platform::WriteNDSSave(savedata, len, 0, len); } -int CartRetail::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) +int CartRetail::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) { if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); @@ -551,17 +541,7 @@ u8 CartRetail::SPIWrite(u8 val, u32 pos, bool last) } } -u8 *CartRetail::GetSaveMemory() const -{ - return SRAM; -} - -u32 CartRetail::GetSaveMemoryLength() const -{ - return SRAMLength; -} - -void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) +void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const { addr &= (ROMLength-1); @@ -578,7 +558,7 @@ void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) addr = 0x8000 + (addr & 0x1FF); } - memcpy(data+offset, ROM+addr, len); + memcpy(data+offset, ROM.get()+addr, len); } u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) @@ -613,7 +593,7 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, (SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr); } return 0; @@ -677,7 +657,7 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -734,7 +714,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -771,7 +751,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -817,7 +797,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -840,7 +820,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) if (last) { SRAMStatus &= ~(1<<1); - Platform::WriteNDSSave(SRAM, SRAMLength, + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); } return 0; @@ -852,15 +832,19 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) } } - -CartRetailNAND::CartRetailNAND(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams) +CartRetailNAND::CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : + CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) { } -CartRetailNAND::~CartRetailNAND() +CartRetailNAND::CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : + CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailNAND) { + BuildSRAMID(); } +CartRetailNAND::~CartRetailNAND() = default; + void CartRetailNAND::Reset() { CartRetail::Reset(); @@ -889,13 +873,13 @@ void CartRetailNAND::DoSavestate(Savestate* file) BuildSRAMID(); } -void CartRetailNAND::LoadSave(const u8* savedata, u32 savelen) +void CartRetailNAND::SetSaveMemory(const u8* savedata, u32 savelen) { - CartRetail::LoadSave(savedata, savelen); + CartRetail::SetSaveMemory(savedata, savelen); BuildSRAMID(); } -int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) +int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) { if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); @@ -924,7 +908,7 @@ int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8 if (SRAMLength && SRAMAddr < (SRAMBase+SRAMLength-0x20000)) { memcpy(&SRAM[SRAMAddr - SRAMBase], SRAMWriteBuffer, 0x800); - Platform::WriteNDSSave(SRAM, SRAMLength, SRAMAddr - SRAMBase, 0x800); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMAddr - SRAMBase, 0x800); } SRAMAddr = 0; @@ -1031,7 +1015,7 @@ int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8 } } -void CartRetailNAND::ROMCommandFinish(u8* cmd, u8* data, u32 len) +void CartRetailNAND::ROMCommandFinish(const u8* cmd, u8* data, u32 len) { if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); @@ -1080,15 +1064,28 @@ void CartRetailNAND::BuildSRAMID() } -CartRetailIR::CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams) : CartRetail(rom, len, chipid, badDSiDump, romparams) +CartRetailIR::CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : + CartRetailIR(CopyToUnique(rom, len), len, chipid, irversion, badDSiDump, romparams, std::move(sram), sramlen) { - IRVersion = irversion; } -CartRetailIR::~CartRetailIR() +CartRetailIR::CartRetailIR( + std::unique_ptr&& rom, + u32 len, + u32 chipid, + u32 irversion, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& sram, + u32 sramlen +) : + CartRetail(std::move(rom), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, CartType::RetailIR), + IRVersion(irversion) { } +CartRetailIR::~CartRetailIR() = default; + void CartRetailIR::Reset() { CartRetail::Reset(); @@ -1125,25 +1122,18 @@ u8 CartRetailIR::SPIWrite(u8 val, u32 pos, bool last) return 0; } +CartRetailBT::CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : + CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) +{ +} -CartRetailBT::CartRetailBT(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams) +CartRetailBT::CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : + CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailBT) { Log(LogLevel::Info,"POKETYPE CART\n"); } -CartRetailBT::~CartRetailBT() -{ -} - -void CartRetailBT::Reset() -{ - CartRetail::Reset(); -} - -void CartRetailBT::DoSavestate(Savestate* file) -{ - CartRetail::DoSavestate(file); -} +CartRetailBT::~CartRetailBT() = default; u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) { @@ -1160,149 +1150,23 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) } -CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartCommon(rom, len, chipid, false, romparams) +CartSD::CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : + CartSD(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard)) +{} + +CartSD::CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : + CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew), + SD(std::move(sdcard)) { - SD = nullptr; + sdcard = std::nullopt; + // std::move on optionals usually results in an optional with a moved-from object } -CartHomebrew::~CartHomebrew() -{ - if (SD) - { - SD->Close(); - delete SD; - } -} +CartSD::~CartSD() = default; +// The SD card is destroyed by the optional's destructor -void CartHomebrew::Reset() -{ - CartCommon::Reset(); - ReadOnly = Platform::GetConfigBool(Platform::DLDI_ReadOnly); - - if (SD) - { - SD->Close(); - delete SD; - } - - if (Platform::GetConfigBool(Platform::DLDI_Enable)) - { - std::string folderpath; - if (Platform::GetConfigBool(Platform::DLDI_FolderSync)) - folderpath = Platform::GetConfigString(Platform::DLDI_FolderPath); - else - folderpath = ""; - - ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), ReadOnly); - SD = new FATStorage(Platform::GetConfigString(Platform::DLDI_ImagePath), - (u64)Platform::GetConfigInt(Platform::DLDI_ImageSize) * 1024 * 1024, - ReadOnly, - folderpath); - SD->Open(); - } - else - SD = nullptr; -} - -void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds) -{ - CartCommon::SetupDirectBoot(romname, nds); - - if (SD) - { - // add the ROM to the SD volume - - if (!SD->InjectFile(romname, ROM, ROMLength)) - return; - - // setup argv command line - - char argv[512] = {0}; - int argvlen; - - strncpy(argv, "fat:/", 511); - strncat(argv, romname.c_str(), 511); - argvlen = strlen(argv); - - const NDSHeader& header = GetHeader(); - - u32 argvbase = header.ARM9RAMAddress + header.ARM9Size; - argvbase = (argvbase + 0xF) & ~0xF; - - for (u32 i = 0; i <= argvlen; i+=4) - nds.ARM9Write32(argvbase+i, *(u32*)&argv[i]); - - nds.ARM9Write32(0x02FFFE70, 0x5F617267); - nds.ARM9Write32(0x02FFFE74, argvbase); - nds.ARM9Write32(0x02FFFE78, argvlen+1); - // The DSi version of ARM9Write32 will be called if nds is really a DSi - } -} - -void CartHomebrew::DoSavestate(Savestate* file) -{ - CartCommon::DoSavestate(file); -} - -int CartHomebrew::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) -{ - if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); - - switch (cmd[0]) - { - case 0xB7: - { - u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; - memset(data, 0, len); - - if (((addr + len - 1) >> 12) != (addr >> 12)) - { - u32 len1 = 0x1000 - (addr & 0xFFF); - ReadROM_B7(addr, len1, data, 0); - ReadROM_B7(addr+len1, len-len1, data, len1); - } - else - ReadROM_B7(addr, len, data, 0); - } - return 0; - - case 0xC0: // SD read - { - u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; - if (SD) SD->ReadSectors(sector, len>>9, data); - } - return 0; - - case 0xC1: // SD write - return 1; - - default: - return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); - } -} - -void CartHomebrew::ROMCommandFinish(u8* cmd, u8* data, u32 len) -{ - if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); - - // TODO: delayed SD writing? like we have for SRAM - - switch (cmd[0]) - { - case 0xC1: - { - u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; - if (SD && (!ReadOnly)) SD->WriteSectors(sector, len>>9, data); - } - break; - - default: - return CartCommon::ROMCommandFinish(cmd, data, len); - } -} - -void CartHomebrew::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) +void CartSD::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const { if (patch[0x0D] > binary[dldioffset+0x0F]) { @@ -1403,7 +1267,7 @@ void CartHomebrew::ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, Log(LogLevel::Debug, "applied DLDI patch at %08X\n", dldioffset); } -void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) +void CartSD::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) { if (*(u32*)&patch[0] != 0xBF8DA5ED || *(u32*)&patch[4] != 0x69684320 || @@ -1433,23 +1297,134 @@ void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly) } } -void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) +void CartSD::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const { // TODO: how strict should this be for homebrew? addr &= (ROMLength-1); - memcpy(data+offset, ROM+addr, len); + memcpy(data+offset, ROM.get()+addr, len); } +CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : + CartSD(rom, len, chipid, romparams, std::move(sdcard)) +{} +CartHomebrew::CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : + CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) +{} -NDSCartSlot::NDSCartSlot(melonDS::NDS& nds) noexcept : NDS(nds) +CartHomebrew::~CartHomebrew() = default; + +void CartHomebrew::Reset() +{ + CartSD::Reset(); + + if (SD) + ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), SD->IsReadOnly()); +} + +void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds) +{ + CartCommon::SetupDirectBoot(romname, nds); + + if (SD) + { + // add the ROM to the SD volume + + if (!SD->InjectFile(romname, ROM.get(), ROMLength)) + return; + + // setup argv command line + + char argv[512] = {0}; + int argvlen; + + strncpy(argv, "fat:/", 511); + strncat(argv, romname.c_str(), 511); + argvlen = strlen(argv); + + const NDSHeader& header = GetHeader(); + + u32 argvbase = header.ARM9RAMAddress + header.ARM9Size; + argvbase = (argvbase + 0xF) & ~0xF; + + for (u32 i = 0; i <= argvlen; i+=4) + nds.ARM9Write32(argvbase+i, *(u32*)&argv[i]); + + nds.ARM9Write32(0x02FFFE70, 0x5F617267); + nds.ARM9Write32(0x02FFFE74, argvbase); + nds.ARM9Write32(0x02FFFE78, argvlen+1); + // The DSi version of ARM9Write32 will be called if nds is really a DSi + } +} + +int CartHomebrew::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); + + switch (cmd[0]) + { + case 0xB7: + { + u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + memset(data, 0, len); + + if (((addr + len - 1) >> 12) != (addr >> 12)) + { + u32 len1 = 0x1000 - (addr & 0xFFF); + ReadROM_B7(addr, len1, data, 0); + ReadROM_B7(addr+len1, len-len1, data, len1); + } + else + ReadROM_B7(addr, len, data, 0); + } + return 0; + + case 0xC0: // SD read + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (SD) SD->ReadSectors(sector, len>>9, data); + } + return 0; + + case 0xC1: // SD write + return 1; + + default: + return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); + } +} + +void CartHomebrew::ROMCommandFinish(const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); + + // TODO: delayed SD writing? like we have for SRAM + + switch (cmd[0]) + { + case 0xC1: + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (SD && !SD->IsReadOnly()) SD->WriteSectors(sector, len>>9, data); + } + break; + + default: + return CartCommon::ROMCommandFinish(cmd, data, len); + } +} + +NDSCartSlot::NDSCartSlot(melonDS::NDS& nds, std::unique_ptr&& rom) noexcept : NDS(nds) { NDS.RegisterEventFunc(Event_ROMTransfer, ROMTransfer_PrepareData, MemberEventFunc(NDSCartSlot, ROMPrepareData)); NDS.RegisterEventFunc(Event_ROMTransfer, ROMTransfer_End, MemberEventFunc(NDSCartSlot, ROMEndTransfer)); NDS.RegisterEventFunc(Event_ROMSPITransfer, 0, MemberEventFunc(NDSCartSlot, SPITransferDone)); // All fields are default-constructed because they're listed as such in the class declaration + + if (rom) + SetCart(std::move(rom)); } NDSCartSlot::~NDSCartSlot() noexcept @@ -1569,16 +1544,13 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept memcpy(out, &cartrom[arm9base], 0x800); - Key1_InitKeycode(false, gamecode, 2, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, gamecode, 2, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); Key1_Decrypt((u32*)&out[0]); - Key1_InitKeycode(false, gamecode, 3, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, gamecode, 3, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); for (u32 i = 0; i < 0x800; i += 8) Key1_Decrypt((u32*)&out[i]); - XXH64_hash_t hash = XXH64(out, 0x800, 0); - Log(LogLevel::Debug, "Secure area post-decryption xxh64 hash: %zx\n", hash); - if (!strncmp((const char*)out, "encryObj", 8)) { Log(LogLevel::Info, "Secure area decryption OK\n"); @@ -1593,7 +1565,12 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept } } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args) +{ + return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args)); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args) { if (romdata == nullptr) { @@ -1607,28 +1584,10 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen) return nullptr; } - u32 cartromsize = 0x200; - while (cartromsize < romlen) - cartromsize <<= 1; // ROM size must be a power of 2 - - u8* cartrom = nullptr; - try - { - cartrom = new u8[cartromsize]; - } - catch (const std::bad_alloc& e) - { - Log(LogLevel::Error, "NDSCart: failed to allocate memory for ROM (%d bytes)\n", cartromsize); - - return nullptr; - } - - // copy romdata into cartrom then zero out the remaining space - memcpy(cartrom, romdata, romlen); - memset(cartrom + romlen, 0, cartromsize - romlen); + auto [cartrom, cartromsize] = PadToPowerOf2(std::move(romdata), romlen); NDSHeader header {}; - memcpy(&header, cartrom, sizeof(header)); + memcpy(&header, cartrom.get(), sizeof(header)); bool dsi = header.IsDSi(); bool badDSiDump = false; @@ -1640,6 +1599,7 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen) dsi = false; } + const char *gametitle = header.GameTitle; u32 gamecode = header.GameCodeAsU32(); u32 arm9base = header.ARM9ROMOffset; @@ -1694,30 +1654,33 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen) } std::unique_ptr cart; + std::unique_ptr sram = args ? std::move(args->SRAM) : nullptr; + u32 sramlen = args ? args->SRAMLength : 0; if (homebrew) - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + { + std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sdcard)); + } + else if (gametitle[0] == 0 && !strncmp("SD/TF-NDS", gametitle + 1, 9) && gamecode == 0x414D5341) + { + std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, std::move(sdcard)); + } else if (cartid & 0x08000000) - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); else if (irversion != 0) - cart = std::make_unique(cartrom, cartromsize, cartid, irversion, badDSiDump, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen); else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); else - cart = std::make_unique(cartrom, cartromsize, cartid, badDSiDump, romparams); - - if (romparams.SaveMemType > 0) - cart->SetupSave(romparams.SaveMemType); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen); + args = std::nullopt; return cart; } -bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept +void NDSCartSlot::SetCart(std::unique_ptr&& cart) noexcept { - if (!cart) { - Log(LogLevel::Error, "Failed to insert invalid cart; existing cart (if any) was not ejected.\n"); - return false; - } - if (Cart) EjectCart(); @@ -1725,6 +1688,10 @@ bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept // and cloning polymorphic objects without knowing the underlying type is annoying. Cart = std::move(cart); + if (!Cart) + // If we're ejecting an existing cart without inserting a new one... + return; + Cart->Reset(); const NDSHeader& header = Cart->GetHeader(); @@ -1739,11 +1706,11 @@ bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept strncpy((char*)&cartrom[header.ARM9ROMOffset], "encryObj", 8); - Key1_InitKeycode(false, romparams.GameCode, 3, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, romparams.GameCode, 3, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); for (u32 i = 0; i < 0x800; i += 8) Key1_Encrypt((u32*)&cartrom[header.ARM9ROMOffset + i]); - Key1_InitKeycode(false, romparams.GameCode, 2, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_InitKeycode(false, romparams.GameCode, 2, 2, NDS.GetARM7BIOS().data(), ARM7BIOSSize); Key1_Encrypt((u32*)&cartrom[header.ARM9ROMOffset]); Log(LogLevel::Debug, "Re-encrypted cart secure area\n"); @@ -1757,21 +1724,12 @@ bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept Log(LogLevel::Info, "Inserted cart with game code: %.4s\n", header.GameCode); Log(LogLevel::Info, "Inserted cart with ID: %08X\n", Cart->ID()); Log(LogLevel::Info, "ROM entry: %08X %08X\n", romparams.ROMSize, romparams.SaveMemType); - - return true; } -bool NDSCartSlot::LoadROM(const u8* romdata, u32 romlen) noexcept -{ - std::unique_ptr cart = ParseROM(romdata, romlen); - - return InsertROM(std::move(cart)); -} - -void NDSCartSlot::LoadSave(const u8* savedata, u32 savelen) noexcept +void NDSCartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept { if (Cart) - Cart->LoadSave(savedata, savelen); + Cart->SetSaveMemory(savedata, savelen); } void NDSCartSlot::SetupDirectBoot(const std::string& romname) noexcept @@ -1780,15 +1738,15 @@ void NDSCartSlot::SetupDirectBoot(const std::string& romname) noexcept Cart->SetupDirectBoot(romname, NDS); } -void NDSCartSlot::EjectCart() noexcept +std::unique_ptr NDSCartSlot::EjectCart() noexcept { - if (!Cart) return; + if (!Cart) return nullptr; // ejecting the cart triggers the gamecard IRQ NDS.SetIRQ(0, IRQ_CartIREQMC); NDS.SetIRQ(1, IRQ_CartIREQMC); - Cart = nullptr; + return std::move(Cart); // CHECKME: does an eject imply anything for the ROM/SPI transfer registers? } diff --git a/src/NDSCart.h b/src/NDSCart.h index 24f9f9ee..78439a2c 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include "types.h" #include "Savestate.h" @@ -45,18 +45,42 @@ enum CartType RetailIR = 0x103, RetailBT = 0x104, Homebrew = 0x201, + UnlicensedR4 = 0x301 }; class NDSCartSlot; +/// Arguments used to create and populate an NDS cart of unknown type. +/// Different carts take different subsets of these arguments, +/// but we won't know which ones to use +/// until we parse the header at runtime. +struct NDSCartArgs +{ + /// The arguments used to load a homebrew SD card image. + /// If \c nullopt, then the cart will not have an SD card. + /// Ignored for retail ROMs. + std::optional SDCard = std::nullopt; + + /// Save RAM to load into the cartridge. + /// If \c nullptr, then the cart's SRAM buffer will be empty. + /// Ignored for homebrew ROMs. + std::unique_ptr SRAM = nullptr; + + /// The length of the buffer in SRAM. + /// If 0, then the cart's SRAM buffer will be empty. + /// Ignored for homebrew ROMs. + u32 SRAMLength = 0; +}; + // CartCommon -- base code shared by all cart types class CartCommon { public: - CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams); + CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type); + CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type); virtual ~CartCommon(); - virtual u32 Type() const = 0; + [[nodiscard]] u32 Type() const { return CartType; }; [[nodiscard]] u32 Checksum() const; virtual void Reset(); @@ -64,16 +88,16 @@ public: virtual void DoSavestate(Savestate* file); - virtual void SetupSave(u32 type); - virtual void LoadSave(const u8* savedata, u32 savelen); - virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len); - virtual void ROMCommandFinish(u8* cmd, u8* data, u32 len); + virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len); + virtual void ROMCommandFinish(const u8* cmd, u8* data, u32 len); virtual u8 SPIWrite(u8 val, u32 pos, bool last); - virtual u8* GetSaveMemory() const; - virtual u32 GetSaveMemoryLength() const; + virtual u8* GetSaveMemory() { return nullptr; } + virtual const u8* GetSaveMemory() const { return nullptr; } + virtual u32 GetSaveMemoryLength() const { return 0; } + virtual void SetSaveMemory(const u8* savedata, u32 savelen) {}; [[nodiscard]] const NDSHeader& GetHeader() const { return Header; } [[nodiscard]] NDSHeader& GetHeader() { return Header; } @@ -82,105 +106,120 @@ public: [[nodiscard]] const NDSBanner* Banner() const; [[nodiscard]] const ROMListEntry& GetROMParams() const { return ROMParams; }; [[nodiscard]] u32 ID() const { return ChipID; } - [[nodiscard]] const u8* GetROM() const { return ROM; } + [[nodiscard]] const u8* GetROM() const { return ROM.get(); } [[nodiscard]] u32 GetROMLength() const { return ROMLength; } protected: - void ReadROM(u32 addr, u32 len, u8* data, u32 offset); + void ReadROM(u32 addr, u32 len, u8* data, u32 offset) const; - u8* ROM; - u32 ROMLength; - u32 ChipID; - bool IsDSi; - bool DSiMode; - u32 DSiBase; + std::unique_ptr ROM = nullptr; + u32 ROMLength = 0; + u32 ChipID = 0; + bool IsDSi = false; + bool DSiMode = false; + u32 DSiBase = 0; - u32 CmdEncMode; - u32 DataEncMode; + u32 CmdEncMode = 0; + u32 DataEncMode = 0; // Kept separate from the ROM data so we can decrypt the modcrypt area // without touching the overall ROM data - NDSHeader Header; - ROMListEntry ROMParams; + NDSHeader Header {}; + ROMListEntry ROMParams {}; + const melonDS::NDSCart::CartType CartType = Default; }; // CartRetail -- regular retail cart (ROM, SPI SRAM) class CartRetail : public CartCommon { public: - CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams); - virtual ~CartRetail() override; + CartRetail( + const u8* rom, + u32 len, + u32 chipid, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& sram, + u32 sramlen, + melonDS::NDSCart::CartType type = CartType::Retail + ); + CartRetail( + std::unique_ptr&& rom, + u32 len, u32 chipid, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& sram, + u32 sramlen, + melonDS::NDSCart::CartType type = CartType::Retail + ); + ~CartRetail() override; - virtual u32 Type() const override { return CartType::Retail; } + void Reset() override; - virtual void Reset() override; + void DoSavestate(Savestate* file) override; - virtual void DoSavestate(Savestate* file) override; + void SetSaveMemory(const u8* savedata, u32 savelen) override; - virtual void SetupSave(u32 type) override; - virtual void LoadSave(const u8* savedata, u32 savelen) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; - virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; + u8 SPIWrite(u8 val, u32 pos, bool last) override; - virtual u8 SPIWrite(u8 val, u32 pos, bool last) override; - - virtual u8* GetSaveMemory() const override; - virtual u32 GetSaveMemoryLength() const override; + u8* GetSaveMemory() override { return SRAM.get(); } + const u8* GetSaveMemory() const override { return SRAM.get(); } + u32 GetSaveMemoryLength() const override { return SRAMLength; } protected: - void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset); + void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const; u8 SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last); u8 SRAMWrite_EEPROM(u8 val, u32 pos, bool last); u8 SRAMWrite_FLASH(u8 val, u32 pos, bool last); - u8* SRAM; - u32 SRAMLength; - u32 SRAMType; + std::unique_ptr SRAM = nullptr; + u32 SRAMLength = 0; + u32 SRAMType = 0; - u8 SRAMCmd; - u32 SRAMAddr; - u32 SRAMFirstAddr; - u8 SRAMStatus; + u8 SRAMCmd = 0; + u32 SRAMAddr = 0; + u32 SRAMFirstAddr = 0; + u8 SRAMStatus = 0; }; // CartRetailNAND -- retail cart with NAND SRAM (WarioWare DIY, Jam with the Band, ...) class CartRetailNAND : public CartRetail { public: - CartRetailNAND(u8* rom, u32 len, u32 chipid, ROMListEntry romparams); + CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); ~CartRetailNAND() override; - virtual u32 Type() const override { return CartType::RetailNAND; } - void Reset() override; void DoSavestate(Savestate* file) override; - void LoadSave(const u8* savedata, u32 savelen) override; + void SetSaveMemory(const u8* savedata, u32 savelen) override; - int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; - void ROMCommandFinish(u8* cmd, u8* data, u32 len) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; + void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override; u8 SPIWrite(u8 val, u32 pos, bool last) override; private: void BuildSRAMID(); - u32 SRAMBase; - u32 SRAMWindow; + u32 SRAMBase = 0; + u32 SRAMWindow = 0; - u8 SRAMWriteBuffer[0x800]; - u32 SRAMWritePos; + u8 SRAMWriteBuffer[0x800] {}; + u32 SRAMWritePos = 0; }; // CartRetailIR -- SPI IR device and SRAM class CartRetailIR : public CartRetail { public: - CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams); + CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailIR(std::unique_ptr&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); ~CartRetailIR() override; - virtual u32 Type() const override { return CartType::RetailIR; } - void Reset() override; void DoSavestate(Savestate* file) override; @@ -188,56 +227,119 @@ public: u8 SPIWrite(u8 val, u32 pos, bool last) override; private: - u32 IRVersion; - u8 IRCmd; + u32 IRVersion = 0; + u8 IRCmd = 0; }; // CartRetailBT - Pok�mon Typing Adventure (SPI BT controller) class CartRetailBT : public CartRetail { public: - CartRetailBT(u8* rom, u32 len, u32 chipid, ROMListEntry romparams); + CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); ~CartRetailBT() override; - virtual u32 Type() const override { return CartType::RetailBT; } - - void Reset() override; - - void DoSavestate(Savestate* file) override; - u8 SPIWrite(u8 val, u32 pos, bool last) override; }; -// CartHomebrew -- homebrew 'cart' (no SRAM, DLDI) -class CartHomebrew : public CartCommon +// CartSD -- any 'cart' with an SD card slot +class CartSD : public CartCommon { public: - CartHomebrew(u8* rom, u32 len, u32 chipid, ROMListEntry romparams); - ~CartHomebrew() override; + CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + ~CartSD() override; - virtual u32 Type() const override { return CartType::Homebrew; } + [[nodiscard]] const std::optional& GetSDCard() const noexcept { return SD; } + void SetSDCard(FATStorage&& sdcard) noexcept { SD = std::move(sdcard); } + void SetSDCard(std::optional&& sdcard) noexcept + { + SD = std::move(sdcard); + sdcard = std::nullopt; + // moving from an optional doesn't set it to nullopt, + // it just leaves behind an optional with a moved-from value + } + +protected: + void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly) const; + void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly); + void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const; + + std::optional SD {}; +}; + +// CartHomebrew -- homebrew 'cart' (no SRAM, DLDI) +class CartHomebrew : public CartSD +{ +public: + CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + ~CartHomebrew() override; void Reset() override; void SetupDirectBoot(const std::string& romname, NDS& nds) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; + void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override; +}; + +// CartR4 -- unlicensed R4 'cart' (NDSCartR4.cpp) +enum CartR4Type +{ + /* non-SDHC carts */ + CartR4TypeM3Simply = 0, + CartR4TypeR4 = 1, + /* SDHC carts */ + CartR4TypeAce3DS = 2 +}; + +enum CartR4Language +{ + CartR4LanguageJapanese = (7 << 3) | 1, + CartR4LanguageEnglish = (7 << 3) | 2, + CartR4LanguageFrench = (2 << 3) | 2, + CartR4LanguageKorean = (4 << 3) | 2, + CartR4LanguageSimplifiedChinese = (6 << 3) | 3, + CartR4LanguageTraditionalChinese = (7 << 3) | 3 +}; + +class CartR4 : public CartSD +{ +public: + CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, + std::optional&& sdcard = std::nullopt); + ~CartR4() override; + + void Reset() override; + void DoSavestate(Savestate* file) override; - int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; - void ROMCommandFinish(u8* cmd, u8* data, u32 len) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) override; + void ROMCommandFinish(const u8* cmd, u8* data, u32 len) override; private: - void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly); - void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly); - void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset); + inline u32 GetAdjustedSector(u32 sector) const + { + return R4CartType >= CartR4TypeAce3DS ? sector : sector >> 9; + } - FATStorage* SD; - bool ReadOnly; + u16 GetEncryptionKey(u16 sector); + void ReadSDToBuffer(u32 sector, bool rom); + u64 SDFATEntrySectorGet(u32 entry, u32 addr); + + s32 EncryptionKey; + u32 FATEntryOffset[2]; + u8 Buffer[512]; + u8 InitStatus; + CartR4Type R4CartType; + CartR4Language CartLanguage; + bool BufferInitialized; }; class NDSCartSlot { public: - NDSCartSlot(melonDS::NDS& nds) noexcept; + explicit NDSCartSlot(melonDS::NDS& nds, std::unique_ptr&& rom = nullptr) noexcept; ~NDSCartSlot() noexcept; void Reset() noexcept; void ResetCart() noexcept; @@ -252,25 +354,16 @@ public: /// If the provided cart is not valid, /// then the currently-loaded ROM will not be ejected. /// - /// @param cart Movable reference to the cart. - /// @returns \c true if the cart was successfully loaded, - /// \c false otherwise. + /// @param cart Movable reference to the cart, + /// or \c nullptr to eject the cart. /// @post If the cart was successfully loaded, /// then \c cart will be \c nullptr /// and \c Cart will contain the object that \c cart previously pointed to. /// Otherwise, \c cart and \c Cart will be both be unchanged. - bool InsertROM(std::unique_ptr&& cart) noexcept; + void SetCart(std::unique_ptr&& cart) noexcept; + [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } + [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - /// Parses a ROM image and loads it into the emulator. - /// This function is equivalent to calling ::ParseROM() and ::InsertROM() in sequence. - /// @param romdata Pointer to the ROM image. - /// The cart emulator maintains its own copy of this data, - /// so the caller is free to discard this data after calling this function. - /// @param romlen The length of the ROM image, in bytes. - /// @returns \c true if the ROM image was successfully loaded, - /// \c false if not. - bool LoadROM(const u8* romdata, u32 romlen) noexcept; - void LoadSave(const u8* savedata, u32 savelen) noexcept; void SetupDirectBoot(const std::string& romname) noexcept; /// This function is intended to allow frontends to save and load SRAM @@ -282,11 +375,15 @@ public: /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr. [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; } + void SetSaveMemory(const u8* savedata, u32 savelen) noexcept; /// @returns The length of the buffer returned by ::GetSaveMemory() /// if a cart is loaded and supports SRAM, otherwise zero. [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } - void EjectCart() noexcept; + + /// @return The cart that was in the slot before it was ejected, + /// or \c nullptr if the slot was already empty. + std::unique_ptr EjectCart() noexcept; u32 ReadROMData() noexcept; void WriteROMData(u32 val) noexcept; void WriteSPICnt(u16 val) noexcept; @@ -294,9 +391,6 @@ public: [[nodiscard]] u8 ReadSPIData() const noexcept; void WriteSPIData(u8 val) noexcept; - [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } - [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - [[nodiscard]] u8 GetROMCommand(u8 index) const noexcept { return ROMCommand[index]; } void SetROMCommand(u8 index, u8 val) noexcept { ROMCommand[index] = val; } @@ -306,34 +400,34 @@ public: private: friend class CartCommon; melonDS::NDS& NDS; - u16 SPICnt {}; - u32 ROMCnt {}; + u16 SPICnt = 0; + u32 ROMCnt = 0; std::array ROMCommand {}; - u8 SPIData; - u32 SPIDataPos; - bool SPIHold; + u8 SPIData = 0; + u32 SPIDataPos = 0; + bool SPIHold = false; - u32 ROMData; + u32 ROMData = 0; - std::array TransferData; - u32 TransferPos; - u32 TransferLen; - u32 TransferDir; - std::array TransferCmd; + std::array TransferData {}; + u32 TransferPos = 0; + u32 TransferLen = 0; + u32 TransferDir = 0; + std::array TransferCmd {}; - std::unique_ptr Cart; + std::unique_ptr Cart = nullptr; - std::array Key1_KeyBuf; + std::array Key1_KeyBuf {}; - u64 Key2_X; - u64 Key2_Y; + u64 Key2_X = 0; + u64 Key2_Y = 0; - void Key1_Encrypt(u32* data) noexcept; - void Key1_Decrypt(u32* data) noexcept; + void Key1_Encrypt(u32* data) const noexcept; + void Key1_Decrypt(u32* data) const noexcept; void Key1_ApplyKeycode(u32* keycode, u32 mod) noexcept; - void Key1_LoadKeyBuf(bool dsi, bool externalBios, u8 *bios, u32 biosLength) noexcept; - void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, u8 *bios, u32 biosLength) noexcept; - void Key2_Encrypt(u8* data, u32 len) noexcept; + void Key1_LoadKeyBuf(bool dsi, const u8 *bios, u32 biosLength) noexcept; + void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, const u8 *bios, u32 biosLength) noexcept; + void Key2_Encrypt(const u8* data, u32 len) noexcept; void ROMEndTransfer(u32 param) noexcept; void ROMPrepareData(u32 param) noexcept; void AdvanceROMTransfer() noexcept; @@ -346,9 +440,13 @@ private: /// The returned cartridge will contain a copy of this data, /// so the caller may deallocate \c romdata after this function returns. /// @param romlen The length of the ROM data in bytes. +/// @param sdcard The arguments to use for initializing the SD card. +/// Ignored if the parsed ROM is not homebrew. +/// If not given, the cart will not have an SD card. /// @returns A \c NDSCart::CartCommon object representing the parsed ROM, /// or \c nullptr if the ROM data couldn't be parsed. -std::unique_ptr ParseROM(const u8* romdata, u32 romlen); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args = std::nullopt); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args = std::nullopt); } #endif diff --git a/src/NDSCartR4.cpp b/src/NDSCartR4.cpp new file mode 100644 index 00000000..8497f556 --- /dev/null +++ b/src/NDSCartR4.cpp @@ -0,0 +1,371 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include "NDS.h" +#include "DSi.h" +#include "NDSCart.h" +#include "Platform.h" + +namespace melonDS +{ +using Platform::Log; +using Platform::LogLevel; + +namespace NDSCart +{ + +/* + Original algorithm discovered by yasu, 2007 + + http://hp.vector.co.jp/authors/VA013928/ + http://www.usay.jp/ + http://www.yasu.nu/ +*/ +static void DecryptR4Sector(u8* dest, u8* src, u16 key1) +{ + for (int i = 0; i < 512; i++) + { + // Derive key2 from key1. + u8 key2 = ((key1 >> 7) & 0x80) + | ((key1 >> 6) & 0x60) + | ((key1 >> 5) & 0x10) + | ((key1 >> 4) & 0x0C) + | (key1 & 0x03); + + // Decrypt byte. + dest[i] = src[i] ^ key2; + + // Derive next key1 from key2. + u16 tmp = ((src[i] << 8) ^ key1); + u16 tmpXor = 0; + for (int ii = 0; ii < 16; ii++) + tmpXor ^= (tmp >> ii); + + u16 newKey1 = 0; + newKey1 |= ((tmpXor & 0x80) | (tmp & 0x7C)) << 8; + newKey1 |= ((tmp ^ (tmpXor >> 14)) << 8) & 0x0300; + newKey1 |= (((tmp >> 1) ^ tmp) >> 6) & 0xFC; + newKey1 |= ((tmp ^ (tmpXor >> 1)) >> 8) & 0x03; + + key1 = newKey1; + } +} + +CartR4::CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, + std::optional&& sdcard) + : CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) +{ + InitStatus = 0; + R4CartType = ctype; + CartLanguage = clanguage; +} + +CartR4::~CartR4() +{ +} + +void CartR4::Reset() +{ + CartSD::Reset(); + + BufferInitialized = false; + EncryptionKey = -1; + FATEntryOffset[0] = 0xFFFFFFFF; + FATEntryOffset[1] = 0xFFFFFFFF; + + if (!SD) + InitStatus = 1; + else + { + u8 buffer[512]; + if (!SD->ReadFile("_DS_MENU.DAT", 0, 512, buffer)) + InitStatus = 3; + else + InitStatus = 4; + } +} + +void CartR4::DoSavestate(Savestate* file) +{ + CartCommon::DoSavestate(file); + + file->Var32(&FATEntryOffset[0]); + file->Var32(&FATEntryOffset[1]); + file->VarArray(Buffer, 512); +} + +// FIXME: Ace3DS/clone behavior is only partially verified. +int CartR4::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) + return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len); + + switch (cmd[0]) + { + case 0xB0: /* Get card information */ + { + u32 info = 0x75A00000 | (((R4CartType >= 1 ? 4 : 0) | CartLanguage) << 3) | InitStatus; + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = info; + return 0; + } + case 0xB4: /* FAT entry */ + { + u8 entryBuffer[512]; + u32 sector = ((cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]) & (~0x1F); + // set FAT entry offset to the starting cluster, to gain a bit of speed + SD->ReadSectors(sector >> 9, 1, entryBuffer); + u16 fileEntryOffset = sector & 0x1FF; + u32 clusterStart = (entryBuffer[fileEntryOffset + 27] << 8) + | entryBuffer[fileEntryOffset + 26] + | (entryBuffer[fileEntryOffset + 21] << 24) + | (entryBuffer[fileEntryOffset + 20] << 16); + FATEntryOffset[cmd[4] & 0x01] = clusterStart; + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xB8: /* ? Get chip ID ? */ + { + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = ChipID; + return 0; + } + case 0xB2: /* Save read request */ + case 0xB6: /* ROM read request */ + { + u32 sector = ((cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]); + ReadSDToBuffer(sector, cmd[0] == 0xB6); + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xB9: /* SD read request */ + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (SD) + SD->ReadSectors(GetAdjustedSector(sector), 1, Buffer); + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xBB: /* SD write start */ + case 0xBD: /* Save write start */ + return 1; + case 0xBC: /* SD write status */ + case 0xBE: /* Save write status */ + { + if (R4CartType == CartR4TypeAce3DS && cmd[0] == 0xBC) + { + uint8_t checksum = 0; + for (int i = 0; i < 7; i++) + checksum ^= cmd[i]; + if (checksum != cmd[7]) + Log(LogLevel::Warn, "R4: invalid 0xBC command checksum (%d != %d)", cmd[7], checksum); + } + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } + case 0xB7: /* ROM read data */ + { + /* If the buffer has not been initialized yet, emulate ROM. */ + /* TODO: When does the R4 do this exactly? */ + if (!BufferInitialized) + { + u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + memcpy(data, &ROM[addr & (ROMLength-1)], len); + return 0; + } + /* Otherwise, fall through. */ + } + case 0xB3: /* Save read data */ + case 0xBA: /* SD read data */ + { + // TODO: Do these use separate buffers? + for (u32 pos = 0; pos < len; pos++) + data[pos] = Buffer[pos & 0x1FF]; + return 0; + } + case 0xBF: /* ROM read decrypted data */ + { + // TODO: Is decryption done using the sector from 0xBF or 0xB6? + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + if (len >= 512) + DecryptR4Sector(data, Buffer, GetEncryptionKey(sector >> 9)); + return 0; + } + default: + Log(LogLevel::Warn, "R4: unknown command %02X %02X %02X %02X %02X %02X %02X %02X (%d)\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], len); + for (u32 pos = 0; pos < len; pos += 4) + *(u32*)&data[pos] = 0; + return 0; + } +} + +void CartR4::ROMCommandFinish(const u8* cmd, u8* data, u32 len) +{ + if (CmdEncMode != 2) return CartCommon::ROMCommandFinish(cmd, data, len); + + switch (cmd[0]) + { + case 0xBB: /* SD write start */ + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + + // The official R4 firmware sends a superfluous write to card + // (sector 0, byte 1) on boot. TODO: Is this correct? + if (GetAdjustedSector(sector) != sector && (sector & 0x1FF)) break; + + if (SD && !SD->IsReadOnly()) + SD->WriteSectors(GetAdjustedSector(sector), 1, data); + break; + } + case 0xBD: /* Save write start */ + { + u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + + if (sector & 0x1FF) break; + + if (SD && !SD->IsReadOnly()) + SD->WriteSectors( + SDFATEntrySectorGet(FATEntryOffset[1], sector) >> 9, + 1, data + ); + break; + } + } +} + +u16 CartR4::GetEncryptionKey(u16 sector) +{ + if (EncryptionKey == -1) + { + u8 encryptedBuffer[512]; + u8 decryptedBuffer[512]; + SD->ReadFile("_DS_MENU.DAT", 0, 512, encryptedBuffer); + for (u32 key = 0; key < 0x10000; key++) + { + DecryptR4Sector(decryptedBuffer, encryptedBuffer, key); + if (decryptedBuffer[12] == '#' && decryptedBuffer[13] == '#' + && decryptedBuffer[14] == '#' && decryptedBuffer[15] == '#') + { + EncryptionKey = key; + break; + } + } + + if (EncryptionKey != -1) + { + Log(LogLevel::Warn, "R4: found cartridge key = %04X\n", EncryptionKey); + } + else + { + EncryptionKey = -2; + Log(LogLevel::Warn, "R4: could not find cartridge key\n"); + } + } + return EncryptionKey ^ sector; +} + +void CartR4::ReadSDToBuffer(u32 sector, bool rom) +{ + if (SD) + { + if (rom && FATEntryOffset[0] == 0xFFFFFFFF) + { + // On first boot, read from _DS_MENU.DAT. + SD->ReadFile("_DS_MENU.DAT", sector & ~0x1FF, 512, Buffer); + } + else + { + // Default mode. + SD->ReadSectors( + SDFATEntrySectorGet(FATEntryOffset[rom ? 0 : 1], sector) >> 9, + 1, Buffer + ); + } + BufferInitialized = true; + } +} + +u64 CartR4::SDFATEntrySectorGet(u32 entry, u32 addr) +{ + u8 buffer[512]; + u32 bufferSector = 0xFFFFFFFF; + + // Parse FAT header. + SD->ReadSectors(0, 1, buffer); + u16 bytesPerSector = (buffer[12] << 8) | buffer[11]; + u8 sectorsPerCluster = buffer[13]; + u16 firstFatSector = (buffer[15] << 8) | buffer[14]; + u8 fatTableCount = buffer[16]; + u32 clustersTotal = SD->GetSectorCount() / sectorsPerCluster; + bool isFat32 = clustersTotal >= 65526; + + u32 fatTableSize = (buffer[23] << 8) | buffer[22]; + if (fatTableSize == 0 && isFat32) + fatTableSize = (buffer[39] << 24) | (buffer[38] << 16) | (buffer[37] << 8) | buffer[36]; + u32 bytesPerCluster = bytesPerSector * sectorsPerCluster; + u32 rootDirSectors = 0; + if (!isFat32) { + u32 rootDirEntries = (buffer[18] << 8) | buffer[17]; + rootDirSectors = ((rootDirEntries * 32) + (bytesPerSector - 1)) / bytesPerSector; + } + u32 firstDataSector = firstFatSector + fatTableCount * fatTableSize + rootDirSectors; + + // Parse file entry (done when processing command 0xB4). + u32 clusterStart = entry; + + // Parse cluster table. + u32 currentCluster = clusterStart; + while (true) + { + currentCluster &= isFat32 ? 0x0FFFFFFF : 0xFFFF; + if (addr < bytesPerCluster) + { + // Read from this cluster. + return (u64) (firstDataSector + ((currentCluster - 2) * sectorsPerCluster)) * bytesPerSector + addr; + } + else if (currentCluster >= 2 && currentCluster <= (isFat32 ? 0x0FFFFFF6 : 0xFFF6)) + { + // Follow into next cluster. + u32 nextClusterOffset = firstFatSector * bytesPerSector + currentCluster * (isFat32 ? 4 : 2); + u32 nextClusterTableSector = nextClusterOffset >> 9; + if (bufferSector != nextClusterTableSector) + { + SD->ReadSectors(nextClusterTableSector, 1, buffer); + bufferSector = nextClusterTableSector; + } + nextClusterOffset &= 0x1FF; + currentCluster = (buffer[nextClusterOffset + 1] << 8) | buffer[nextClusterOffset]; + if (isFat32) + currentCluster |= (buffer[nextClusterOffset + 3] << 24) | (buffer[nextClusterOffset + 2] << 16); + addr -= bytesPerCluster; + } + else + { + // End of cluster table. + return 0; + } + } +} + +} +} diff --git a/src/NonStupidBitfield.h b/src/NonStupidBitfield.h index eb5e1f2b..4a5550f1 100644 --- a/src/NonStupidBitfield.h +++ b/src/NonStupidBitfield.h @@ -42,7 +42,7 @@ struct NonStupidBitField NonStupidBitField& BitField; u32 Idx; - operator bool() + operator bool() const { return BitField.Data[Idx >> 6] & (1ULL << (Idx & 0x3F)); } @@ -62,13 +62,13 @@ struct NonStupidBitField u32 BitIdx; u64 RemainingBits; - u32 operator*() { return DataIdx * 64 + BitIdx; } + u32 operator*() const { return DataIdx * 64 + BitIdx; } - bool operator==(const Iterator& other) + bool operator==(const Iterator& other) const { return other.DataIdx == DataIdx; } - bool operator!=(const Iterator& other) + bool operator!=(const Iterator& other) const { return other.DataIdx != DataIdx; } diff --git a/src/Platform.h b/src/Platform.h index f8b0453c..21b3d465 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -92,56 +92,6 @@ int InstanceID(); */ std::string InstanceFileSuffix(); -// configuration values - -enum ConfigEntry -{ -#ifdef JIT_ENABLED - JIT_Enable, - JIT_MaxBlockSize, - JIT_LiteralOptimizations, - JIT_BranchOptimizations, - JIT_FastMemory, -#endif - - ExternalBIOSEnable, - - DLDI_Enable, - DLDI_ImagePath, - DLDI_ImageSize, - DLDI_ReadOnly, - DLDI_FolderSync, - DLDI_FolderPath, - - DSiSD_Enable, - DSiSD_ImagePath, - DSiSD_ImageSize, - DSiSD_ReadOnly, - DSiSD_FolderSync, - DSiSD_FolderPath, - - Firm_MAC, - - WifiSettingsPath, - - AudioBitDepth, - - DSi_FullBIOSBoot, - -#ifdef GDBSTUB_ENABLED - GdbEnabled, - GdbPortARM7, - GdbPortARM9, - GdbARM7BreakOnStartup, - GdbARM9BreakOnStartup, -#endif -}; - -int GetConfigInt(ConfigEntry entry); -bool GetConfigBool(ConfigEntry entry); -std::string GetConfigString(ConfigEntry entry); -bool GetConfigArray(ConfigEntry entry, void* data); - /** * Denotes how a file will be opened and accessed. * Flags may or may not correspond to the operating system's file API. diff --git a/src/RTC.cpp b/src/RTC.cpp index b5e497a2..d8219df1 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -86,17 +86,17 @@ void RTC::DoSavestate(Savestate* file) } -u8 RTC::BCD(u8 val) +u8 RTC::BCD(u8 val) const { return (val % 10) | ((val / 10) << 4); } -u8 RTC::FromBCD(u8 val) +u8 RTC::FromBCD(u8 val) const { return (val & 0xF) + ((val >> 4) * 10); } -u8 RTC::BCDIncrement(u8 val) +u8 RTC::BCDIncrement(u8 val) const { val++; if ((val & 0x0F) >= 0x0A) @@ -106,7 +106,7 @@ u8 RTC::BCDIncrement(u8 val) return val; } -u8 RTC::BCDSanitize(u8 val, u8 vmin, u8 vmax) +u8 RTC::BCDSanitize(u8 val, u8 vmin, u8 vmax) const { if (val < vmin || val > vmax) val = vmin; @@ -119,12 +119,12 @@ u8 RTC::BCDSanitize(u8 val, u8 vmin, u8 vmax) } -void RTC::GetState(StateData& state) +void RTC::GetState(StateData& state) const { memcpy(&state, &State, sizeof(State)); } -void RTC::SetState(StateData& state) +void RTC::SetState(const StateData& state) { memcpy(&State, &state, sizeof(State)); @@ -134,7 +134,7 @@ void RTC::SetState(StateData& state) WriteDateTime(i+1, State.DateTime[i]); } -void RTC::GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second) +void RTC::GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second) const { year = FromBCD(State.DateTime[0]); year += 2000; @@ -374,7 +374,7 @@ void RTC::ProcessIRQ(int type) // 0=minute carry 1=periodic 2=status reg write } -u8 RTC::DaysInMonth() +u8 RTC::DaysInMonth() const { u8 numdays; diff --git a/src/RTC.h b/src/RTC.h index 0caf5ee5..1477e0eb 100644 --- a/src/RTC.h +++ b/src/RTC.h @@ -55,9 +55,9 @@ public: void DoSavestate(Savestate* file); - void GetState(StateData& state); - void SetState(StateData& state); - void GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second); + void GetState(StateData& state) const; + void SetState(const StateData& state); + void GetDateTime(int& year, int& month, int& day, int& hour, int& minute, int& second) const; void SetDateTime(int year, int month, int day, int hour, int minute, int second); void ClockTimer(u32 param); @@ -87,16 +87,16 @@ private: void ResetState(); void ScheduleTimer(bool first); - u8 BCD(u8 val); - u8 FromBCD(u8 val); - u8 BCDIncrement(u8 val); - u8 BCDSanitize(u8 val, u8 vmin, u8 vmax); + u8 BCD(u8 val) const; + u8 FromBCD(u8 val) const; + u8 BCDIncrement(u8 val) const; + u8 BCDSanitize(u8 val, u8 vmin, u8 vmax) const; void SetIRQ(u8 irq); void ClearIRQ(u8 irq); void ProcessIRQ(int type); - u8 DaysInMonth(); + u8 DaysInMonth() const; void CountYear(); void CountMonth(); void CheckEndOfMonth(); diff --git a/src/SPI.cpp b/src/SPI.cpp index b3e5b4e1..2aa915c6 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -57,33 +57,24 @@ u16 CRC16(const u8* data, u32 len, u32 start) -bool FirmwareMem::VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset) +bool FirmwareMem::VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset) const { - u16 crc_stored = *(u16*)&Firmware->Buffer()[crcoffset]; - u16 crc_calced = CRC16(&Firmware->Buffer()[offset], len, start); + u16 crc_stored = *(u16*)&FirmwareData.Buffer()[crcoffset]; + u16 crc_calced = CRC16(&FirmwareData.Buffer()[offset], len, start); return (crc_stored == crc_calced); } -FirmwareMem::FirmwareMem(melonDS::NDS& nds) : SPIDevice(nds) +FirmwareMem::FirmwareMem(melonDS::NDS& nds, melonDS::Firmware&& firmware) : SPIDevice(nds), FirmwareData(std::move(firmware)) { } -FirmwareMem::~FirmwareMem() -{ - RemoveFirmware(); -} +FirmwareMem::~FirmwareMem() = default; void FirmwareMem::Reset() { - if (!Firmware) - { - Log(LogLevel::Warn, "SPI firmware: no firmware loaded! Using default\n"); - Firmware = std::make_unique(NDS.ConsoleType); - } - // fix touchscreen coords - for (auto& u : Firmware->GetUserData()) + for (auto& u : FirmwareData.GetUserData()) { u.TouchCalibrationADC1[0] = 0; u.TouchCalibrationADC1[1] = 0; @@ -95,17 +86,17 @@ void FirmwareMem::Reset() u.TouchCalibrationPixel2[1] = 191; } - Firmware->UpdateChecksums(); + FirmwareData.UpdateChecksums(); // disable autoboot //Firmware[userdata+0x64] &= 0xBF; - MacAddress mac = Firmware->GetHeader().MacAddr; + MacAddress mac = FirmwareData.GetHeader().MacAddr; Log(LogLevel::Info, "MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); // verify shit - u32 mask = Firmware->Mask(); - Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&Firmware->Buffer()[0x2C], 0x2A)?"GOOD":"BAD"); + u32 mask = FirmwareData.Mask(); + Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&FirmwareData.Buffer()[0x2C], 0x2A)?"GOOD":"BAD"); Log(LogLevel::Debug, "FW: AP1 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FA00&mask, 0xFE, 0x7FAFE&mask)?"GOOD":"BAD"); Log(LogLevel::Debug, "FW: AP2 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FB00&mask, 0xFE, 0x7FBFE&mask)?"GOOD":"BAD"); Log(LogLevel::Debug, "FW: AP3 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FC00&mask, 0xFE, 0x7FCFE&mask)?"GOOD":"BAD"); @@ -136,8 +127,8 @@ void FirmwareMem::DoSavestate(Savestate* file) void FirmwareMem::SetupDirectBoot() { - const auto& header = Firmware->GetHeader(); - const auto& userdata = Firmware->GetEffectiveUserData(); + const auto& header = FirmwareData.GetHeader(); + const auto& userdata = FirmwareData.GetEffectiveUserData(); if (NDS.ConsoleType == 1) { // The ARMWrite methods are virtual, they'll delegate to DSi if necessary @@ -163,58 +154,9 @@ void FirmwareMem::SetupDirectBoot() } } -const class Firmware* FirmwareMem::GetFirmware() +bool FirmwareMem::IsLoadedFirmwareBuiltIn() const { - return Firmware.get(); -} - -bool FirmwareMem::IsLoadedFirmwareBuiltIn() -{ - return Firmware->GetHeader().Identifier == GENERATED_FIRMWARE_IDENTIFIER; -} - -bool FirmwareMem::InstallFirmware(class Firmware&& firmware) -{ - if (!firmware.Buffer()) - { - Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n"); - return false; - } - - Firmware = std::make_unique(std::move(firmware)); - - FirmwareIdentifier id = Firmware->GetHeader().Identifier; - Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]); - - return true; -} - -bool FirmwareMem::InstallFirmware(std::unique_ptr&& firmware) -{ - if (!firmware) - { - Log(LogLevel::Error, "SPI firmware: firmware is null!\n"); - return false; - } - - if (!firmware->Buffer()) - { - Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n"); - return false; - } - - Firmware = std::move(firmware); - - FirmwareIdentifier id = Firmware->GetHeader().Identifier; - Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]); - - return true; -} - -void FirmwareMem::RemoveFirmware() -{ - Firmware.reset(); - Log(LogLevel::Debug, "Removed installed firmware (if any)\n"); + return FirmwareData.GetHeader().Identifier == GENERATED_FIRMWARE_IDENTIFIER; } void FirmwareMem::Write(u8 val) @@ -256,7 +198,7 @@ void FirmwareMem::Write(u8 val) } else { - Data = Firmware->Buffer()[Addr & Firmware->Mask()]; + Data = FirmwareData.Buffer()[Addr & FirmwareData.Mask()]; Addr++; } @@ -279,7 +221,7 @@ void FirmwareMem::Write(u8 val) } else { - Firmware->Buffer()[Addr & Firmware->Mask()] = val; + FirmwareData.Buffer()[Addr & FirmwareData.Mask()] = val; Data = val; Addr++; } @@ -314,11 +256,11 @@ void FirmwareMem::Release() { // If the SPI firmware chip just finished a write... // We only notify the frontend of changes to the Wi-fi/userdata settings region // (although it might still decide to flush the whole thing) - u32 wifioffset = Firmware->GetWifiAccessPointOffset(); + u32 wifioffset = FirmwareData.GetWifiAccessPointOffset(); // Request that the start of the Wi-fi/userdata settings region // through the end of the firmware blob be flushed to disk - Platform::WriteFirmware(*Firmware, wifioffset, Firmware->Length() - wifioffset); + Platform::WriteFirmware(FirmwareData, wifioffset, FirmwareData.Length() - wifioffset); } SPIDevice::Release(); @@ -366,7 +308,7 @@ void PowerMan::DoSavestate(Savestate* file) file->VarArray(RegMasks, 8); // is that needed?? } -bool PowerMan::GetBatteryLevelOkay() { return !Registers[1]; } +bool PowerMan::GetBatteryLevelOkay() const { return !Registers[1]; } void PowerMan::SetBatteryLevelOkay(bool okay) { Registers[1] = okay ? 0x00 : 0x01; } void PowerMan::Write(u8 val) @@ -462,7 +404,7 @@ void TSC::SetTouchCoords(u16 x, u16 y) NDS.KeyInput &= ~(1 << (16+6)); } -void TSC::MicInputFrame(s16* data, int samples) +void TSC::MicInputFrame(const s16* data, int samples) { if (!data) { @@ -530,11 +472,11 @@ void TSC::Write(u8 val) -SPIHost::SPIHost(melonDS::NDS& nds) : NDS(nds) +SPIHost::SPIHost(melonDS::NDS& nds, Firmware&& firmware) : NDS(nds) { NDS.RegisterEventFunc(Event_SPITransfer, 0, MemberEventFunc(SPIHost, TransferDone)); - Devices[SPIDevice_FirmwareMem] = new FirmwareMem(NDS); + Devices[SPIDevice_FirmwareMem] = new FirmwareMem(NDS, std::move(firmware)); Devices[SPIDevice_PowerMan] = new PowerMan(NDS); if (NDS.ConsoleType == 1) @@ -607,7 +549,7 @@ void SPIHost::TransferDone(u32 param) NDS.SetIRQ(1, IRQ_SPI); } -u8 SPIHost::ReadData() +u8 SPIHost::ReadData() const { if (!(Cnt & (1<<15))) return 0; if (Cnt & (1<<7)) return 0; // checkme diff --git a/src/SPI.h b/src/SPI.h index f27f0c3e..7ed889a4 100644 --- a/src/SPI.h +++ b/src/SPI.h @@ -51,7 +51,7 @@ public: virtual void Reset() = 0; virtual void DoSavestate(Savestate* file) = 0; - virtual u8 Read() { return Data; } + virtual u8 Read() const { return Data; } virtual void Write(u8 val) = 0; virtual void Release() { Hold = false; DataPos = 0; } @@ -66,31 +66,30 @@ protected: class FirmwareMem : public SPIDevice { public: - FirmwareMem(melonDS::NDS& nds); + FirmwareMem(melonDS::NDS& nds, melonDS::Firmware&& firmware); ~FirmwareMem() override; void Reset() override; void DoSavestate(Savestate* file) override; void SetupDirectBoot(); - const class Firmware* GetFirmware(); - bool IsLoadedFirmwareBuiltIn(); - bool InstallFirmware(class Firmware&& firmware); - bool InstallFirmware(std::unique_ptr&& firmware); - void RemoveFirmware(); + Firmware& GetFirmware() noexcept { return FirmwareData; } + [[nodiscard]] const Firmware& GetFirmware() const noexcept { return FirmwareData; } + void SetFirmware(Firmware&& firmware) { FirmwareData = std::move(firmware); } + bool IsLoadedFirmwareBuiltIn() const; void Write(u8 val) override; void Release() override; private: - std::unique_ptr Firmware; + Firmware FirmwareData; u8 CurCmd; u8 StatusReg; u32 Addr; - bool VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset); + bool VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset) const; }; class PowerMan : public SPIDevice @@ -101,7 +100,7 @@ public: void Reset() override; void DoSavestate(Savestate* file) override; - bool GetBatteryLevelOkay(); + bool GetBatteryLevelOkay() const; void SetBatteryLevelOkay(bool okay); void Write(u8 val) override; @@ -122,7 +121,7 @@ public: virtual void DoSavestate(Savestate* file) override; virtual void SetTouchCoords(u16 x, u16 y); - virtual void MicInputFrame(s16* data, int samples); + virtual void MicInputFrame(const s16* data, int samples); virtual void Write(u8 val) override; @@ -141,21 +140,26 @@ protected: class SPIHost { public: - SPIHost(melonDS::NDS& nds); + SPIHost(melonDS::NDS& nds, Firmware&& firmware); ~SPIHost(); void Reset(); void DoSavestate(Savestate* file); FirmwareMem* GetFirmwareMem() { return (FirmwareMem*)Devices[SPIDevice_FirmwareMem]; } + const FirmwareMem* GetFirmwareMem() const { return (FirmwareMem*)Devices[SPIDevice_FirmwareMem]; } PowerMan* GetPowerMan() { return (PowerMan*)Devices[SPIDevice_PowerMan]; } + const PowerMan* GetPowerMan() const { return (PowerMan*)Devices[SPIDevice_PowerMan]; } TSC* GetTSC() { return (TSC*)Devices[SPIDevice_TSC]; } + const TSC* GetTSC() const { return (TSC*)Devices[SPIDevice_TSC]; } - const Firmware* GetFirmware() { return GetFirmwareMem()->GetFirmware(); } + const Firmware& GetFirmware() const { return GetFirmwareMem()->GetFirmware(); } + Firmware& GetFirmware() { return GetFirmwareMem()->GetFirmware(); } + void SetFirmware(Firmware&& firmware) { GetFirmwareMem()->SetFirmware(std::move(firmware)); } - u16 ReadCnt() { return Cnt; } + u16 ReadCnt() const { return Cnt; } void WriteCnt(u16 val); - u8 ReadData(); + u8 ReadData() const; void WriteData(u8 val); void TransferDone(u32 param); diff --git a/src/SPU.cpp b/src/SPU.cpp index 00db3691..f0d59464 100644 --- a/src/SPU.cpp +++ b/src/SPU.cpp @@ -65,23 +65,110 @@ const s16 SPUChannel::PSGTable[8][8] = {-0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF} }; -s16 SPUChannel::InterpCos[0x100]; -s16 SPUChannel::InterpCubic[0x100][4]; -bool SPUChannel::InterpInited = false; +template +constexpr T ipow(T num, unsigned int pow) +{ + T product = 1; + for (int i = 0; i < pow; ++i) + { + product *= num; + } + return product; +} -SPU::SPU(melonDS::NDS& nds) : NDS(nds) +template +constexpr T factorial(T num) +{ + T product = 1; + for (T i = 1; i <= num; ++i) + { + product *= i; + } + + return product; +} + +// We can't use std::cos in constexpr functions until C++26, +// so we need to compute the cosine ourselves with the Taylor series. +// Code adapted from https://prosepoetrycode.potterpcs.net/2015/07/a-simple-constexpr-power-function-c/ +template +constexpr double cosine (double theta) +{ + return (ipow(-1, Iterations) * ipow(theta, 2 * Iterations)) / + static_cast(factorial(2ull * Iterations)) + + cosine(theta); +} + +template <> +constexpr double cosine<0> (double theta) +{ + return 1.0; +} + +// generate interpolation tables +// values are 1:1:14 fixed-point +constexpr std::array InterpCos = []() constexpr { + std::array interp {}; + + for (int i = 0; i < 0x100; i++) + { + float ratio = (i * M_PI) / 255.0f; + ratio = 1.0f - cosine(ratio); + + interp[i] = (s16)(ratio * 0x2000); + } + + return interp; +}(); + +constexpr array2d InterpCubic = []() constexpr { + array2d interp {}; + + for (int i = 0; i < 0x100; i++) + { + s32 i1 = i << 6; + s32 i2 = (i * i) >> 2; + s32 i3 = (i * i * i) >> 10; + + interp[i][0] = -i3 + 2*i2 - i1; + interp[i][1] = i3 - 2*i2 + 0x4000; + interp[i][2] = -i3 + i2 + i1; + interp[i][3] = i3 - i2; + } + + return interp; +}(); + +SPU::SPU(melonDS::NDS& nds, AudioBitDepth bitdepth, AudioInterpolation interpolation) : + NDS(nds), + Channels { + SPUChannel(0, nds, interpolation), + SPUChannel(1, nds, interpolation), + SPUChannel(2, nds, interpolation), + SPUChannel(3, nds, interpolation), + SPUChannel(4, nds, interpolation), + SPUChannel(5, nds, interpolation), + SPUChannel(6, nds, interpolation), + SPUChannel(7, nds, interpolation), + SPUChannel(8, nds, interpolation), + SPUChannel(9, nds, interpolation), + SPUChannel(10, nds, interpolation), + SPUChannel(11, nds, interpolation), + SPUChannel(12, nds, interpolation), + SPUChannel(13, nds, interpolation), + SPUChannel(14, nds, interpolation), + SPUChannel(15, nds, interpolation), + }, + Capture { + SPUCaptureUnit(0, nds), + SPUCaptureUnit(1, nds), + }, + AudioLock(Platform::Mutex_Create()), + Degrade10Bit(bitdepth == AudioBitDepth::_10Bit || (nds.ConsoleType == 1 && bitdepth == AudioBitDepth::Auto)) { NDS.RegisterEventFunc(Event_SPU, 0, MemberEventFunc(SPU, Mix)); - for (int i = 0; i < 16; i++) - Channels[i] = new SPUChannel(i, NDS); - - Capture[0] = new SPUCaptureUnit(0, NDS); - Capture[1] = new SPUCaptureUnit(1, NDS); - - AudioLock = Platform::Mutex_Create(); - ApplyBias = true; Degrade10Bit = false; @@ -90,50 +177,10 @@ SPU::SPU(melonDS::NDS& nds) : NDS(nds) OutputBackbufferWritePosition = 0; OutputFrontBufferReadPosition = 0; OutputFrontBufferWritePosition = 0; - - if (!SPUChannel::InterpInited) - { - // generate interpolation tables - // values are 1:1:14 fixed-point - - float m_pi = std::acos(-1.0f); - for (int i = 0; i < 0x100; i++) - { - float ratio = (i * m_pi) / 255.0f; - ratio = 1.0f - std::cos(ratio); - - SPUChannel::InterpCos[i] = (s16)(ratio * 0x2000); - } - - for (int i = 0; i < 0x100; i++) - { - s32 i1 = i << 6; - s32 i2 = (i * i) >> 2; - s32 i3 = (i * i * i) >> 10; - - SPUChannel::InterpCubic[i][0] = -i3 + 2*i2 - i1; - SPUChannel::InterpCubic[i][1] = i3 - 2*i2 + 0x4000; - SPUChannel::InterpCubic[i][2] = -i3 + i2 + i1; - SPUChannel::InterpCubic[i][3] = i3 - i2; - } - - SPUChannel::InterpInited = true; - } } SPU::~SPU() { - for (int i = 0; i < 16; i++) - { - delete Channels[i]; - Channels[i] = nullptr; - } - - delete Capture[0]; - delete Capture[1]; - Capture[0] = nullptr; - Capture[1] = nullptr; - Platform::Mutex_Free(AudioLock); AudioLock = nullptr; @@ -149,10 +196,10 @@ void SPU::Reset() Bias = 0; for (int i = 0; i < 16; i++) - Channels[i]->Reset(); + Channels[i].Reset(); - Capture[0]->Reset(); - Capture[1]->Reset(); + Capture[0].Reset(); + Capture[1].Reset(); NDS.ScheduleEvent(Event_SPU, false, 1024, 0, 0); } @@ -176,11 +223,11 @@ void SPU::DoSavestate(Savestate* file) file->Var8(&MasterVolume); file->Var16(&Bias); - for (int i = 0; i < 16; i++) - Channels[i]->DoSavestate(file); + for (SPUChannel& channel : Channels) + channel.DoSavestate(file); - Capture[0]->DoSavestate(file); - Capture[1]->DoSavestate(file); + for (SPUCaptureUnit& capture : Capture) + capture.DoSavestate(file); } @@ -190,10 +237,10 @@ void SPU::SetPowerCnt(u32 val) } -void SPU::SetInterpolation(int type) +void SPU::SetInterpolation(AudioInterpolation type) { - for (int i = 0; i < 16; i++) - Channels[i]->InterpType = type; + for (SPUChannel& channel : Channels) + channel.InterpType = type; } void SPU::SetBias(u16 bias) @@ -211,15 +258,26 @@ void SPU::SetDegrade10Bit(bool enable) Degrade10Bit = enable; } - -SPUChannel::SPUChannel(u32 num, melonDS::NDS& nds) : NDS(nds) +void SPU::SetDegrade10Bit(AudioBitDepth depth) { - Num = num; - - InterpType = 0; + switch (depth) + { + case AudioBitDepth::Auto: + Degrade10Bit = (NDS.ConsoleType == 0); + break; + case AudioBitDepth::_10Bit: + Degrade10Bit = true; + break; + case AudioBitDepth::_16Bit: + Degrade10Bit = false; + break; + } } -SPUChannel::~SPUChannel() +SPUChannel::SPUChannel(u32 num, melonDS::NDS& nds, AudioInterpolation interpolation) : + NDS(nds), + Num(num), + InterpType(interpolation) { } @@ -520,7 +578,7 @@ s32 SPUChannel::Run() // for optional interpolation: save previous samples // the interpolated audio will be delayed by a couple samples, // but it's easier to deal with this way - if ((type < 3) && (InterpType != 0)) + if ((type < 3) && (InterpType != AudioInterpolation::None)) { PrevSample[2] = PrevSample[1]; PrevSample[1] = PrevSample[0]; @@ -540,24 +598,24 @@ s32 SPUChannel::Run() s32 val = (s32)CurSample; // interpolation (emulation improvement, not a hardware feature) - if ((type < 3) && (InterpType != 0)) + if ((type < 3) && (InterpType != AudioInterpolation::None)) { s32 samplepos = ((Timer - TimerReload) * 0x100) / (0x10000 - TimerReload); if (samplepos > 0xFF) samplepos = 0xFF; switch (InterpType) { - case 1: // linear + case AudioInterpolation::Linear: val = ((val * samplepos) + (PrevSample[0] * (0xFF-samplepos))) >> 8; break; - case 2: // cosine + case AudioInterpolation::Cosine: val = ((val * InterpCos[samplepos]) + (PrevSample[0] * InterpCos[0xFF-samplepos])) >> 14; break; - case 3: // cubic + case AudioInterpolation::Cubic: val = ((PrevSample[2] * InterpCubic[samplepos][0]) + (PrevSample[1] * InterpCubic[samplepos][1]) + (PrevSample[0] * InterpCubic[samplepos][2]) + @@ -579,11 +637,6 @@ void SPUChannel::PanOutput(s32 in, s32& left, s32& right) SPUCaptureUnit::SPUCaptureUnit(u32 num, melonDS::NDS& nds) : NDS(nds), Num(num) -{ - Num = num; -} - -SPUCaptureUnit::~SPUCaptureUnit() { } @@ -713,21 +766,21 @@ void SPU::Mix(u32 dummy) if ((Cnt & (1<<15)) && (!dummy)) { - s32 ch0 = Channels[0]->DoRun(); - s32 ch1 = Channels[1]->DoRun(); - s32 ch2 = Channels[2]->DoRun(); - s32 ch3 = Channels[3]->DoRun(); + s32 ch0 = Channels[0].DoRun(); + s32 ch1 = Channels[1].DoRun(); + s32 ch2 = Channels[2].DoRun(); + s32 ch3 = Channels[3].DoRun(); // TODO: addition from capture registers - Channels[0]->PanOutput(ch0, left, right); - Channels[2]->PanOutput(ch2, left, right); + Channels[0].PanOutput(ch0, left, right); + Channels[2].PanOutput(ch2, left, right); - if (!(Cnt & (1<<12))) Channels[1]->PanOutput(ch1, left, right); - if (!(Cnt & (1<<13))) Channels[3]->PanOutput(ch3, left, right); + if (!(Cnt & (1<<12))) Channels[1].PanOutput(ch1, left, right); + if (!(Cnt & (1<<13))) Channels[3].PanOutput(ch3, left, right); for (int i = 4; i < 16; i++) { - SPUChannel* chan = Channels[i]; + SPUChannel* chan = &Channels[i]; s32 channel = chan->DoRun(); chan->PanOutput(channel, left, right); @@ -736,7 +789,7 @@ void SPU::Mix(u32 dummy) // sound capture // TODO: other sound capture sources, along with their bugs - if (Capture[0]->Cnt & (1<<7)) + if (Capture[0].Cnt & (1<<7)) { s32 val = left; @@ -744,10 +797,10 @@ void SPU::Mix(u32 dummy) if (val < -0x8000) val = -0x8000; else if (val > 0x7FFF) val = 0x7FFF; - Capture[0]->Run(val); + Capture[0].Run(val); } - if (Capture[1]->Cnt & (1<<7)) + if (Capture[1].Cnt & (1<<7)) { s32 val = right; @@ -755,7 +808,7 @@ void SPU::Mix(u32 dummy) if (val < -0x8000) val = -0x8000; else if (val > 0x7FFF) val = 0x7FFF; - Capture[1]->Run(val); + Capture[1].Run(val); } // final output @@ -767,20 +820,20 @@ void SPU::Mix(u32 dummy) break; case 0x0100: // channel 1 { - s32 pan = 128 - Channels[1]->Pan; + s32 pan = 128 - Channels[1].Pan; leftoutput = ((s64)ch1 * pan) >> 10; } break; case 0x0200: // channel 3 { - s32 pan = 128 - Channels[3]->Pan; + s32 pan = 128 - Channels[3].Pan; leftoutput = ((s64)ch3 * pan) >> 10; } break; case 0x0300: // channel 1+3 { - s32 pan1 = 128 - Channels[1]->Pan; - s32 pan3 = 128 - Channels[3]->Pan; + s32 pan1 = 128 - Channels[1].Pan; + s32 pan3 = 128 - Channels[3].Pan; leftoutput = (((s64)ch1 * pan1) >> 10) + (((s64)ch3 * pan3) >> 10); } break; @@ -793,20 +846,20 @@ void SPU::Mix(u32 dummy) break; case 0x0400: // channel 1 { - s32 pan = Channels[1]->Pan; + s32 pan = Channels[1].Pan; rightoutput = ((s64)ch1 * pan) >> 10; } break; case 0x0800: // channel 3 { - s32 pan = Channels[3]->Pan; + s32 pan = Channels[3].Pan; rightoutput = ((s64)ch3 * pan) >> 10; } break; case 0x0C00: // channel 1+3 { - s32 pan1 = Channels[1]->Pan; - s32 pan3 = Channels[3]->Pan; + s32 pan1 = Channels[1].Pan; + s32 pan3 = Channels[3].Pan; rightoutput = (((s64)ch1 * pan1) >> 10) + (((s64)ch3 * pan3) >> 10); } break; @@ -903,7 +956,7 @@ void SPU::InitOutput() Platform::Mutex_Unlock(AudioLock); } -int SPU::GetOutputSize() +int SPU::GetOutputSize() const { Platform::Mutex_Lock(AudioLock); @@ -982,7 +1035,7 @@ u8 SPU::Read8(u32 addr) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -999,8 +1052,8 @@ u8 SPU::Read8(u32 addr) case 0x04000500: return Cnt & 0x7F; case 0x04000501: return Cnt >> 8; - case 0x04000508: return Capture[0]->Cnt; - case 0x04000509: return Capture[1]->Cnt; + case 0x04000508: return Capture[0].Cnt; + case 0x04000509: return Capture[1].Cnt; } } @@ -1012,7 +1065,7 @@ u16 SPU::Read16(u32 addr) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1027,7 +1080,7 @@ u16 SPU::Read16(u32 addr) case 0x04000500: return Cnt; case 0x04000504: return Bias; - case 0x04000508: return Capture[0]->Cnt | (Capture[1]->Cnt << 8); + case 0x04000508: return Capture[0].Cnt | (Capture[1].Cnt << 8); } } @@ -1039,7 +1092,7 @@ u32 SPU::Read32(u32 addr) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1053,10 +1106,10 @@ u32 SPU::Read32(u32 addr) case 0x04000500: return Cnt; case 0x04000504: return Bias; - case 0x04000508: return Capture[0]->Cnt | (Capture[1]->Cnt << 8); + case 0x04000508: return Capture[0].Cnt | (Capture[1].Cnt << 8); - case 0x04000510: return Capture[0]->DstAddr; - case 0x04000518: return Capture[1]->DstAddr; + case 0x04000510: return Capture[0].DstAddr; + case 0x04000518: return Capture[1].DstAddr; } } @@ -1068,7 +1121,7 @@ void SPU::Write8(u32 addr, u8 val) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1092,11 +1145,11 @@ void SPU::Write8(u32 addr, u8 val) return; case 0x04000508: - Capture[0]->SetCnt(val); + Capture[0].SetCnt(val); if (val & 0x03) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %02X\n", val); return; case 0x04000509: - Capture[1]->SetCnt(val); + Capture[1].SetCnt(val); if (val & 0x03) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %02X\n", val); return; } @@ -1109,7 +1162,7 @@ void SPU::Write16(u32 addr, u16 val) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1117,8 +1170,8 @@ void SPU::Write16(u32 addr, u16 val) case 0x2: chan->SetCnt((chan->Cnt & 0x0000FFFF) | (val << 16)); return; case 0x8: chan->SetTimerReload(val); - if ((addr & 0xF0) == 0x10) Capture[0]->SetTimerReload(val); - else if ((addr & 0xF0) == 0x30) Capture[1]->SetTimerReload(val); + if ((addr & 0xF0) == 0x10) Capture[0].SetTimerReload(val); + else if ((addr & 0xF0) == 0x30) Capture[1].SetTimerReload(val); return; case 0xA: chan->SetLoopPos(val); return; @@ -1141,13 +1194,13 @@ void SPU::Write16(u32 addr, u16 val) return; case 0x04000508: - Capture[0]->SetCnt(val & 0xFF); - Capture[1]->SetCnt(val >> 8); + Capture[0].SetCnt(val & 0xFF); + Capture[1].SetCnt(val >> 8); if (val & 0x0303) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %04X\n", val); return; - case 0x04000514: Capture[0]->SetLength(val); return; - case 0x0400051C: Capture[1]->SetLength(val); return; + case 0x04000514: Capture[0].SetLength(val); return; + case 0x0400051C: Capture[1].SetLength(val); return; } } @@ -1158,7 +1211,7 @@ void SPU::Write32(u32 addr, u32 val) { if (addr < 0x04000500) { - SPUChannel* chan = Channels[(addr >> 4) & 0xF]; + SPUChannel* chan = &Channels[(addr >> 4) & 0xF]; switch (addr & 0xF) { @@ -1168,8 +1221,8 @@ void SPU::Write32(u32 addr, u32 val) chan->SetLoopPos(val >> 16); val &= 0xFFFF; chan->SetTimerReload(val); - if ((addr & 0xF0) == 0x10) Capture[0]->SetTimerReload(val); - else if ((addr & 0xF0) == 0x30) Capture[1]->SetTimerReload(val); + if ((addr & 0xF0) == 0x10) Capture[0].SetTimerReload(val); + else if ((addr & 0xF0) == 0x30) Capture[1].SetTimerReload(val); return; case 0xC: chan->SetLength(val); return; } @@ -1189,15 +1242,15 @@ void SPU::Write32(u32 addr, u32 val) return; case 0x04000508: - Capture[0]->SetCnt(val & 0xFF); - Capture[1]->SetCnt(val >> 8); + Capture[0].SetCnt(val & 0xFF); + Capture[1].SetCnt(val >> 8); if (val & 0x0303) Log(LogLevel::Warn, "!! UNSUPPORTED SPU CAPTURE MODE %04X\n", val); return; - case 0x04000510: Capture[0]->SetDstAddr(val); return; - case 0x04000514: Capture[0]->SetLength(val & 0xFFFF); return; - case 0x04000518: Capture[1]->SetDstAddr(val); return; - case 0x0400051C: Capture[1]->SetLength(val & 0xFFFF); return; + case 0x04000510: Capture[0].SetDstAddr(val); return; + case 0x04000514: Capture[0].SetLength(val & 0xFFFF); return; + case 0x04000518: Capture[1].SetDstAddr(val); return; + case 0x0400051C: Capture[1].SetLength(val & 0xFFFF); return; } } } diff --git a/src/SPU.h b/src/SPU.h index bf0c658b..b2b05ac7 100644 --- a/src/SPU.h +++ b/src/SPU.h @@ -27,11 +27,25 @@ namespace melonDS class NDS; class SPU; +enum class AudioBitDepth +{ + Auto, + _10Bit, + _16Bit, +}; + +enum class AudioInterpolation +{ + None, + Linear, + Cosine, + Cubic, +}; + class SPUChannel { public: - SPUChannel(u32 num, melonDS::NDS& nds); - ~SPUChannel(); + SPUChannel(u32 num, melonDS::NDS& nds, AudioInterpolation interpolation); void Reset(); void DoSavestate(Savestate* file); @@ -39,44 +53,40 @@ public: static const u16 ADPCMTable[89]; static const s16 PSGTable[8][8]; - static s16 InterpCos[0x100]; - static s16 InterpCubic[0x100][4]; - static bool InterpInited; - // audio interpolation is an improvement upon the original hardware // (which performs no interpolation) - int InterpType; + AudioInterpolation InterpType = AudioInterpolation::None; - u32 Num; + const u32 Num; - u32 Cnt; - u32 SrcAddr; - u16 TimerReload; - u32 LoopPos; - u32 Length; + u32 Cnt = 0; + u32 SrcAddr = 0; + u16 TimerReload = 0; + u32 LoopPos = 0; + u32 Length = 0; - u8 Volume; - u8 VolumeShift; - u8 Pan; + u8 Volume = 0; + u8 VolumeShift = 0; + u8 Pan = 0; - bool KeyOn; - u32 Timer; - s32 Pos; - s16 PrevSample[3]; - s16 CurSample; - u16 NoiseVal; + bool KeyOn = false; + u32 Timer = 0; + s32 Pos = 0; + s16 PrevSample[3] {}; + s16 CurSample = 0; + u16 NoiseVal = 0; - s32 ADPCMVal; - s32 ADPCMIndex; - s32 ADPCMValLoop; - s32 ADPCMIndexLoop; - u8 ADPCMCurByte; + s32 ADPCMVal = 0; + s32 ADPCMIndex = 0; + s32 ADPCMValLoop = 0; + s32 ADPCMIndexLoop = 0; + u8 ADPCMCurByte = 0; - u32 FIFO[8]; - u32 FIFOReadPos; - u32 FIFOWritePos; - u32 FIFOReadOffset; - u32 FIFOLevel; + u32 FIFO[8] {}; + u32 FIFOReadPos = 0; + u32 FIFOWritePos = 0; + u32 FIFOReadOffset = 0; + u32 FIFOLevel = 0; void FIFO_BufferData(); template T FIFO_ReadData(); @@ -150,25 +160,24 @@ class SPUCaptureUnit { public: SPUCaptureUnit(u32 num, melonDS::NDS&); - ~SPUCaptureUnit(); void Reset(); void DoSavestate(Savestate* file); - u32 Num; + const u32 Num; - u8 Cnt; - u32 DstAddr; - u16 TimerReload; - u32 Length; + u8 Cnt = 0; + u32 DstAddr = 0; + u16 TimerReload = 0; + u32 Length = 0; - u32 Timer; - s32 Pos; + u32 Timer = 0; + s32 Pos = 0; - u32 FIFO[4]; - u32 FIFOReadPos; - u32 FIFOWritePos; - u32 FIFOWriteOffset; - u32 FIFOLevel; + u32 FIFO[4] {}; + u32 FIFOReadPos = 0; + u32 FIFOWritePos = 0; + u32 FIFOWriteOffset = 0; + u32 FIFOLevel = 0; void FIFO_FlushData(); template void FIFO_WriteData(T val); @@ -206,7 +215,7 @@ private: class SPU { public: - SPU(melonDS::NDS& nds); + explicit SPU(melonDS::NDS& nds, AudioBitDepth bitdepth, AudioInterpolation interpolation); ~SPU(); void Reset(); void DoSavestate(Savestate* file); @@ -216,10 +225,11 @@ public: void SetPowerCnt(u32 val); // 0=none 1=linear 2=cosine 3=cubic - void SetInterpolation(int type); + void SetInterpolation(AudioInterpolation type); void SetBias(u16 bias); void SetDegrade10Bit(bool enable); + void SetDegrade10Bit(AudioBitDepth depth); void SetApplyBias(bool enable); void Mix(u32 dummy); @@ -227,7 +237,7 @@ public: void TrimOutput(); void DrainOutput(); void InitOutput(); - int GetOutputSize(); + int GetOutputSize() const; void Sync(bool wait); int ReadOutput(s16* data, int samples); void TransferOutput(); @@ -242,23 +252,23 @@ public: private: static const u32 OutputBufferSize = 2*2048; melonDS::NDS& NDS; - s16 OutputBackbuffer[2 * OutputBufferSize]; - u32 OutputBackbufferWritePosition; + s16 OutputBackbuffer[2 * OutputBufferSize] {}; + u32 OutputBackbufferWritePosition = 0; - s16 OutputFrontBuffer[2 * OutputBufferSize]; - u32 OutputFrontBufferWritePosition; - u32 OutputFrontBufferReadPosition; + s16 OutputFrontBuffer[2 * OutputBufferSize] {}; + u32 OutputFrontBufferWritePosition = 0; + u32 OutputFrontBufferReadPosition = 0; Platform::Mutex* AudioLock; - u16 Cnt; - u8 MasterVolume; - u16 Bias; - bool ApplyBias; - bool Degrade10Bit; + u16 Cnt = 0; + u8 MasterVolume = 0; + u16 Bias = 0; + bool ApplyBias = true; + bool Degrade10Bit = false; - SPUChannel* Channels[16]; - SPUCaptureUnit* Capture[2]; + std::array Channels; + std::array Capture; }; } diff --git a/src/TinyVector.h b/src/TinyVector.h index 1904f2ad..5a30ff65 100644 --- a/src/TinyVector.h +++ b/src/TinyVector.h @@ -97,7 +97,7 @@ struct __attribute__((packed)) TinyVector Data[i] = Data[i + 1];*/ } - int Find(T needle) + int Find(T needle) const { for (int i = 0; i < Length; i++) { @@ -125,6 +125,12 @@ struct __attribute__((packed)) TinyVector assert(index >= 0 && index < Length); return Data[index]; } + + const T& operator[](int index) const + { + assert(index >= 0 && index < Length); + return Data[index]; + } }; } diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 00000000..698cf9bd --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,66 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "Utils.h" + +#include + +namespace melonDS +{ +std::pair, u32> PadToPowerOf2(std::unique_ptr&& data, u32 len) noexcept +{ + if (data == nullptr || len == 0) + return {nullptr, 0}; + + if ((len & (len - 1)) == 0) + return {std::move(data), len}; + + u32 newlen = 1; + while (newlen < len) + newlen <<= 1; + + auto newdata = std::make_unique(newlen); + memcpy(newdata.get(), data.get(), len); + data = nullptr; + return {std::move(newdata), newlen}; +} + +std::pair, u32> PadToPowerOf2(const u8* data, u32 len) noexcept +{ + if (len == 0) + return {nullptr, 0}; + + u32 newlen = 1; + while (newlen < len) + newlen <<= 1; + + auto newdata = std::make_unique(newlen); + memcpy(newdata.get(), data, len); + return {std::move(newdata), newlen}; +} + +std::unique_ptr CopyToUnique(const u8* data, u32 len) noexcept +{ + if (data == nullptr || len == 0) + return nullptr; + + auto newdata = std::make_unique(len); + memcpy(newdata.get(), data, len); + return newdata; +} +} \ No newline at end of file diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 00000000..63be217b --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,43 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef MELONDS_UTILS_H +#define MELONDS_UTILS_H + +#include +#include "types.h" +#include + +namespace melonDS +{ +/// Ensures that the given array is a power of 2 in length. +/// +/// @return If \c len is a power of 2, returns \c data and \c len unchanged +/// without copying anything. +/// If \c data is \c nullptr, returns {nullptr, 0}. +/// Otherwise, return a copy of \c data with zero-padding to the next power of 2. +/// @post \c data is \c nullptr, even if it doesn't need to be copied. +std::pair, u32> PadToPowerOf2(std::unique_ptr&& data, u32 len) noexcept; + +std::pair, u32> PadToPowerOf2(const u8* data, u32 len) noexcept; + +std::unique_ptr CopyToUnique(const u8* data, u32 len) noexcept; + +} + +#endif // MELONDS_UTILS_H diff --git a/src/Wifi.cpp b/src/Wifi.cpp index 50275236..4da253ef 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -78,12 +78,12 @@ const u8 Wifi::MPAckMAC[6] = {0x03, 0x09, 0xBF, 0x00, 0x00, 0x03}; // * TX errors (if applicable) -bool MACEqual(u8* a, const u8* b) +bool MACEqual(const u8* a, const u8* b) { return (*(u32*)&a[0] == *(u32*)&b[0]) && (*(u16*)&a[4] == *(u16*)&b[4]); } -bool MACIsBroadcast(u8* a) +bool MACIsBroadcast(const u8* a) { return (*(u32*)&a[0] == 0xFFFFFFFF) && (*(u16*)&a[4] == 0xFFFF); } @@ -158,12 +158,12 @@ void Wifi::Reset() } #undef BBREG_FIXED - const Firmware* fw = NDS.SPI.GetFirmware(); + const Firmware& fw = NDS.SPI.GetFirmware(); - RFVersion = fw->GetHeader().RFChipType; + RFVersion = fw.GetHeader().RFChipType; memset(RFRegs, 0, 4*0x40); - Firmware::FirmwareConsoleType console = fw->GetHeader().ConsoleType; + Firmware::FirmwareConsoleType console = fw.GetHeader().ConsoleType; if (console == Firmware::FirmwareConsoleType::DS) IOPORT(0x000) = 0x1440; else if (console == Firmware::FirmwareConsoleType::DSLite) @@ -440,14 +440,14 @@ void Wifi::PowerDown() } -int Wifi::PreambleLen(int rate) +int Wifi::PreambleLen(int rate) const { if (rate == 1) return 192; if (IOPORT(W_Preamble) & 0x0004) return 96; return 192; } -u32 Wifi::NumClients(u16 bitmask) +u32 Wifi::NumClients(u16 bitmask) const { u32 ret = 0; for (int i = 1; i < 16; i++) @@ -457,7 +457,7 @@ u32 Wifi::NumClients(u16 bitmask) return ret; } -void Wifi::IncrementTXCount(TXSlot* slot) +void Wifi::IncrementTXCount(const TXSlot* slot) { u8 cnt = RAM[slot->Addr + 0x4]; if (cnt < 0xFF) cnt++; @@ -477,7 +477,7 @@ void Wifi::ReportMPReplyErrors(u16 clientfail) } } -void Wifi::TXSendFrame(TXSlot* slot, int num) +void Wifi::TXSendFrame(const TXSlot* slot, int num) { u32 noseqno = 0; @@ -2258,12 +2258,12 @@ void Wifi::Write(u32 addr, u16 val) } -u8* Wifi::GetMAC() +const u8* Wifi::GetMAC() const { return (u8*)&IOPORT(W_MACAddr0); } -u8* Wifi::GetBSSID() +const u8* Wifi::GetBSSID() const { return (u8*)&IOPORT(W_BSSID0); } diff --git a/src/Wifi.h b/src/Wifi.h index 76fa1463..5553a6f5 100644 --- a/src/Wifi.h +++ b/src/Wifi.h @@ -169,8 +169,8 @@ public: u16 Read(u32 addr); void Write(u32 addr, u16 val); - u8* GetMAC(); - u8* GetBSSID(); + const u8* GetMAC() const; + const u8* GetBSSID() const; private: melonDS::NDS& NDS; @@ -261,12 +261,12 @@ private: void SetStatus(u32 status); void PowerDown(); - int PreambleLen(int rate); - u32 NumClients(u16 bitmask); - void IncrementTXCount(TXSlot* slot); + int PreambleLen(int rate) const; + u32 NumClients(u16 bitmask) const; + void IncrementTXCount(const TXSlot* slot); void ReportMPReplyErrors(u16 clientfail); - void TXSendFrame(TXSlot* slot, int num); + void TXSendFrame(const TXSlot* slot, int num); void StartTX_LocN(int nslot, int loc); void StartTX_Cmd(); void StartTX_Beacon(); diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index efc34a5c..4c645203 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -66,8 +66,8 @@ const u8 WifiAP::APMac[6] = {0x00, 0xF0, 0x77, 0x77, 0x77, 0x77}; #define PALIGN_4(p, base) while (PLEN(p,base) & 0x3) *p++ = 0xFF; -bool MACEqual(u8* a, const u8* b); -bool MACIsBroadcast(u8* a); +bool MACEqual(const u8* a, const u8* b); +bool MACIsBroadcast(const u8* a); WifiAP::WifiAP(Wifi* client) : Client(client) @@ -107,7 +107,7 @@ void WifiAP::MSTimer() } -int WifiAP::HandleManagementFrame(u8* data, int len) +int WifiAP::HandleManagementFrame(const u8* data, int len) { // TODO: perfect this // noting that frames sent pre-auth/assoc don't have a proper BSSID @@ -258,7 +258,7 @@ int WifiAP::HandleManagementFrame(u8* data, int len) } -int WifiAP::SendPacket(u8* data, int len) +int WifiAP::SendPacket(const u8* data, int len) { data += 12; diff --git a/src/WifiAP.h b/src/WifiAP.h index 5d966687..8f3ed111 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -38,7 +38,7 @@ public: void MSTimer(); // packet format: 12-byte TX header + original 802.11 frame - int SendPacket(u8* data, int len); + int SendPacket(const u8* data, int len); int RecvPacket(u8* data); private: @@ -60,7 +60,7 @@ private: // 0=disconnected 1=authenticated 2=associated int ClientStatus; - int HandleManagementFrame(u8* data, int len); + int HandleManagementFrame(const u8* data, int len); }; } diff --git a/src/fatfs/ff.c b/src/fatfs/ff.c index 9d212949..385da84e 100644 --- a/src/fatfs/ff.c +++ b/src/fatfs/ff.c @@ -728,13 +728,13 @@ static int dbc_2nd (BYTE c) #if FF_USE_LFN -/* Get a Unicode code point from the TCHAR string in defined API encodeing */ +/* Get a Unicode code point from the FF_TCHAR string in defined API encodeing */ static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on surrogate pair, 0xFFFFFFFF on decode error) */ - const TCHAR** str /* Pointer to pointer to TCHAR string in configured encoding */ + const FF_TCHAR** str /* Pointer to pointer to FF_TCHAR string in configured encoding */ ) { DWORD uc; - const TCHAR *p = *str; + const FF_TCHAR *p = *str; #if FF_LFN_UNICODE == 1 /* UTF-16 input */ WCHAR wc; @@ -771,7 +771,7 @@ static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on } #elif FF_LFN_UNICODE == 3 /* UTF-32 input */ - uc = (TCHAR)*p++; /* Get a unit */ + uc = (FF_TCHAR)*p++; /* Get a unit */ if (uc >= 0x110000 || IsSurrogate(uc)) return 0xFFFFFFFF; /* Wrong code? */ if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ @@ -800,7 +800,7 @@ static DWORD tchar2uni ( /* Returns a character in UTF-16 encoding (>=0x10000 on /* Store a Unicode char in defined API encoding */ static UINT put_utf ( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */ DWORD chr, /* UTF-16 encoded character (Surrogate pair if >=0x10000) */ - TCHAR* buf, /* Output buffer */ + FF_TCHAR* buf, /* Output buffer */ UINT szb /* Size of the buffer */ ) { @@ -824,20 +824,20 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over if (chr < 0x80) { /* Single byte code? */ if (szb < 1) return 0; /* Buffer overflow? */ - *buf = (TCHAR)chr; + *buf = (FF_TCHAR)chr; return 1; } if (chr < 0x800) { /* 2-byte sequence? */ if (szb < 2) return 0; /* Buffer overflow? */ - *buf++ = (TCHAR)(0xC0 | (chr >> 6 & 0x1F)); - *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + *buf++ = (FF_TCHAR)(0xC0 | (chr >> 6 & 0x1F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 0 & 0x3F)); return 2; } if (chr < 0x10000) { /* 3-byte sequence? */ if (szb < 3 || IsSurrogate(chr)) return 0; /* Buffer overflow or wrong code? */ - *buf++ = (TCHAR)(0xE0 | (chr >> 12 & 0x0F)); - *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); - *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + *buf++ = (FF_TCHAR)(0xE0 | (chr >> 12 & 0x0F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 0 & 0x3F)); return 3; } /* 4-byte sequence */ @@ -846,10 +846,10 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ chr = (hc | chr) + 0x10000; - *buf++ = (TCHAR)(0xF0 | (chr >> 18 & 0x07)); - *buf++ = (TCHAR)(0x80 | (chr >> 12 & 0x3F)); - *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); - *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + *buf++ = (FF_TCHAR)(0xF0 | (chr >> 18 & 0x07)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 12 & 0x3F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (FF_TCHAR)(0x80 | (chr >> 0 & 0x3F)); return 4; #elif FF_LFN_UNICODE == 3 /* UTF-32 output */ @@ -862,7 +862,7 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ chr = (hc | chr) + 0x10000; } - *buf++ = (TCHAR)chr; + *buf++ = (FF_TCHAR)chr; return 1; #else /* ANSI/OEM output */ @@ -872,11 +872,11 @@ static UINT put_utf ( /* Returns number of encoding units written (0:buffer over if (wc >= 0x100) { /* Is this a DBC? */ if (szb < 2) return 0; *buf++ = (char)(wc >> 8); /* Store DBC 1st byte */ - *buf++ = (TCHAR)wc; /* Store DBC 2nd byte */ + *buf++ = (FF_TCHAR)wc; /* Store DBC 2nd byte */ return 2; } if (wc == 0 || szb < 1) return 0; /* Invalid char or buffer overflow? */ - *buf++ = (TCHAR)wc; /* Store the character */ + *buf++ = (FF_TCHAR)wc; /* Store the character */ return 1; #endif } @@ -2595,7 +2595,7 @@ static void get_fileinfo ( FATFS *fs = dp->obj.fs; UINT nw; #else - TCHAR c; + FF_TCHAR c; #endif @@ -2668,7 +2668,7 @@ static void get_fileinfo ( if (nw == 0) { di = 0; break; } /* Buffer overflow? */ di += nw; #else /* ANSI/OEM output */ - fno->altname[di++] = (TCHAR)wc; /* Store it without any conversion */ + fno->altname[di++] = (FF_TCHAR)wc; /* Store it without any conversion */ #endif } fno->altname[di] = 0; /* Terminate the SFN (null string means SFN is invalid) */ @@ -2681,7 +2681,7 @@ static void get_fileinfo ( wc = (WCHAR)fno->altname[si]; if (wc == '.') lcf = NS_EXT; if (IsUpper(wc) && (dp->dir[DIR_NTres] & lcf)) wc += 0x20; - fno->fname[di] = (TCHAR)wc; + fno->fname[di] = (FF_TCHAR)wc; } } fno->fname[di] = 0; /* Terminate the LFN */ @@ -2691,7 +2691,7 @@ static void get_fileinfo ( #else /* Non-LFN configuration */ si = di = 0; while (si < 11) { /* Copy name body and extension */ - c = (TCHAR)dp->dir[si++]; + c = (FF_TCHAR)dp->dir[si++]; if (c == ' ') continue; /* Skip padding spaces */ if (c == RDDEM) c = DDEM; /* Restore replaced DDEM character */ if (si == 9) fno->fname[di++] = '.';/* Insert a . if extension is exist */ @@ -2719,7 +2719,7 @@ static void get_fileinfo ( static DWORD get_achar ( /* Get a character and advance ptr */ - const TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */ + const FF_TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */ ) { DWORD chr; @@ -2750,13 +2750,13 @@ static DWORD get_achar ( /* Get a character and advance ptr */ static int pattern_match ( /* 0:mismatched, 1:matched */ - const TCHAR* pat, /* Matching pattern */ - const TCHAR* nam, /* String to be tested */ + const FF_TCHAR* pat, /* Matching pattern */ + const FF_TCHAR* nam, /* String to be tested */ UINT skip, /* Number of pre-skip chars (number of ?s, b8:infinite (* specified)) */ UINT recur /* Recursion count */ ) { - const TCHAR *pptr, *nptr; + const FF_TCHAR *pptr, *nptr; DWORD pchr, nchr; UINT sk; @@ -2800,7 +2800,7 @@ static int pattern_match ( /* 0:mismatched, 1:matched */ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */ FF_DIR* dp, /* Pointer to the directory object */ - const TCHAR** path /* Pointer to pointer to the segment in the path string */ + const FF_TCHAR** path /* Pointer to pointer to the segment in the path string */ ) { #if FF_USE_LFN /* LFN configuration */ @@ -2808,7 +2808,7 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr WCHAR wc, *lfn; DWORD uc; UINT i, ni, si, di; - const TCHAR *p; + const FF_TCHAR *p; /* Create LFN into LFN working buffer */ @@ -3002,7 +3002,7 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ FF_DIR* dp, /* Directory object to return last directory and found object */ - const TCHAR* path /* Full-path string to find a file or directory */ + const FF_TCHAR* path /* Full-path string to find a file or directory */ ) { FRESULT res; @@ -3088,11 +3088,11 @@ static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ /*-----------------------------------------------------------------------*/ static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive number or null pointer) */ - const TCHAR** path /* Pointer to pointer to the path name */ + const FF_TCHAR** path /* Pointer to pointer to the path name */ ) { - const TCHAR *tp, *tt; - TCHAR tc; + const FF_TCHAR *tp, *tt; + FF_TCHAR tc; int i; int vol = -1; #if FF_STR_VOLUME_ID /* Find string volume ID */ @@ -3118,7 +3118,7 @@ static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive numb c = *sp++; tc = *tp++; if (IsLower(c)) c -= 0x20; if (IsLower(tc)) tc -= 0x20; - } while (c && (TCHAR)c == tc); + } while (c && (FF_TCHAR)c == tc); } while ((c || tp != tt) && ++i < FF_VOLUMES); /* Repeat for each id until pattern match */ } #endif @@ -3138,7 +3138,7 @@ static int get_ldnumber ( /* Returns logical drive number (-1:invalid drive numb c = *sp++; tc = *(++tt); if (IsLower(c)) c -= 0x20; if (IsLower(tc)) tc -= 0x20; - } while (c && (TCHAR)c == tc); + } while (c && (FF_TCHAR)c == tc); } while ((c || (tc != '/' && !IsTerminator(tc))) && ++i < FF_VOLUMES); /* Repeat for each ID until pattern match */ if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */ vol = i; /* Drive number */ @@ -3330,7 +3330,7 @@ static UINT find_volume ( /* Returns BS status found in the hosting drive */ /*-----------------------------------------------------------------------*/ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */ - const TCHAR** path, /* Pointer to pointer to the path name (drive number) */ + const FF_TCHAR** path, /* Pointer to pointer to the path name (drive number) */ FATFS** rfs, /* Pointer to pointer to the found filesystem object */ BYTE mode /* !=0: Check write protection for write access */ ) @@ -3604,14 +3604,14 @@ static FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */ FRESULT f_mount ( FATFS* fs, /* Pointer to the filesystem object to be registered (NULL:unmount)*/ - const TCHAR* path, /* Logical drive number to be mounted/unmounted */ + const FF_TCHAR* path, /* Logical drive number to be mounted/unmounted */ BYTE opt /* Mount option: 0=Do not mount (delayed mount), 1=Mount immediately */ ) { FATFS *cfs; int vol; FRESULT res; - const TCHAR *rp = path; + const FF_TCHAR *rp = path; /* Get logical drive number */ @@ -3652,7 +3652,7 @@ FRESULT f_mount ( FRESULT f_open ( FF_FIL* fp, /* Pointer to the blank file object */ - const TCHAR* path, /* Pointer to the file name */ + const FF_TCHAR* path, /* Pointer to the file name */ BYTE mode /* Access mode and open mode flags */ ) { @@ -4186,7 +4186,7 @@ FRESULT f_close ( /*-----------------------------------------------------------------------*/ FRESULT f_chdrive ( - const TCHAR* path /* Drive number to set */ + const FF_TCHAR* path /* Drive number to set */ ) { int vol; @@ -4203,7 +4203,7 @@ FRESULT f_chdrive ( FRESULT f_chdir ( - const TCHAR* path /* Pointer to the directory path */ + const FF_TCHAR* path /* Pointer to the directory path */ ) { #if FF_STR_VOLUME_ID == 2 @@ -4265,8 +4265,8 @@ FRESULT f_chdir ( #if FF_FS_RPATH >= 2 FRESULT f_getcwd ( - TCHAR* buff, /* Pointer to the directory path */ - UINT len /* Size of buff in unit of TCHAR */ + FF_TCHAR* buff, /* Pointer to the directory path */ + UINT len /* Size of buff in unit of FF_TCHAR */ ) { FRESULT res; @@ -4274,7 +4274,7 @@ FRESULT f_getcwd ( FATFS *fs; UINT i, n; DWORD ccl; - TCHAR *tp = buff; + FF_TCHAR *tp = buff; #if FF_VOLUMES >= 2 UINT vl; #if FF_STR_VOLUME_ID @@ -4287,7 +4287,7 @@ FRESULT f_getcwd ( /* Get logical drive */ buff[0] = 0; /* Set null string to get current volume */ - res = mount_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */ + res = mount_volume((const FF_TCHAR**)&buff, &fs, 0); /* Get current volume */ if (res == FR_OK) { dj.obj.fs = fs; INIT_NAMBUF(fs); @@ -4328,15 +4328,15 @@ FRESULT f_getcwd ( #if FF_STR_VOLUME_ID >= 1 /* String volume ID */ for (n = 0, vp = (const char*)VolumeStr[CurrVol]; vp[n]; n++) ; if (i >= n + 2) { - if (FF_STR_VOLUME_ID == 2) *tp++ = (TCHAR)'/'; - for (vl = 0; vl < n; *tp++ = (TCHAR)vp[vl], vl++) ; - if (FF_STR_VOLUME_ID == 1) *tp++ = (TCHAR)':'; + if (FF_STR_VOLUME_ID == 2) *tp++ = (FF_TCHAR)'/'; + for (vl = 0; vl < n; *tp++ = (FF_TCHAR)vp[vl], vl++) ; + if (FF_STR_VOLUME_ID == 1) *tp++ = (FF_TCHAR)':'; vl++; } #else /* Numeric volume ID */ if (i >= 3) { - *tp++ = (TCHAR)'0' + CurrVol; - *tp++ = (TCHAR)':'; + *tp++ = (FF_TCHAR)'0' + CurrVol; + *tp++ = (FF_TCHAR)':'; vl = 2; } #endif @@ -4530,7 +4530,7 @@ FRESULT f_lseek ( FRESULT f_opendir ( FF_DIR* dp, /* Pointer to directory object to create */ - const TCHAR* path /* Pointer to the directory path */ + const FF_TCHAR* path /* Pointer to the directory path */ ) { FRESULT res; @@ -4688,8 +4688,8 @@ FRESULT f_findnext ( FRESULT f_findfirst ( FF_DIR* dp, /* Pointer to the blank directory object */ FF_FILINFO* fno, /* Pointer to the file information structure */ - const TCHAR* path, /* Pointer to the directory to open */ - const TCHAR* pattern /* Pointer to the matching pattern */ + const FF_TCHAR* path, /* Pointer to the directory to open */ + const FF_TCHAR* pattern /* Pointer to the matching pattern */ ) { FRESULT res; @@ -4713,7 +4713,7 @@ FRESULT f_findfirst ( /*-----------------------------------------------------------------------*/ FRESULT f_stat ( - const TCHAR* path, /* Pointer to the file path */ + const FF_TCHAR* path, /* Pointer to the file path */ FF_FILINFO* fno /* Pointer to file information to return */ ) { @@ -4748,7 +4748,7 @@ FRESULT f_stat ( /*-----------------------------------------------------------------------*/ FRESULT f_getfree ( - const TCHAR* path, /* Logical drive number */ + const FF_TCHAR* path, /* Logical drive number */ DWORD* nclst, /* Pointer to a variable to return number of free clusters */ FATFS** fatfs /* Pointer to return pointer to corresponding filesystem object */ ) @@ -4890,7 +4890,7 @@ FRESULT f_truncate ( /*-----------------------------------------------------------------------*/ FRESULT f_unlink ( - const TCHAR* path /* Pointer to the file or directory path */ + const FF_TCHAR* path /* Pointer to the file or directory path */ ) { FRESULT res; @@ -4984,7 +4984,7 @@ FRESULT f_unlink ( /*-----------------------------------------------------------------------*/ FRESULT f_mkdir ( - const TCHAR* path /* Pointer to the directory path */ + const FF_TCHAR* path /* Pointer to the directory path */ ) { FRESULT res; @@ -5068,8 +5068,8 @@ FRESULT f_mkdir ( /*-----------------------------------------------------------------------*/ FRESULT f_rename ( - const TCHAR* path_old, /* Pointer to the object name to be renamed */ - const TCHAR* path_new /* Pointer to the new name */ + const FF_TCHAR* path_old, /* Pointer to the object name to be renamed */ + const FF_TCHAR* path_new /* Pointer to the new name */ ) { FRESULT res; @@ -5178,7 +5178,7 @@ FRESULT f_rename ( /*-----------------------------------------------------------------------*/ FRESULT f_chmod ( - const TCHAR* path, /* Pointer to the file path */ + const FF_TCHAR* path, /* Pointer to the file path */ BYTE attr, /* Attribute bits */ BYTE mask /* Attribute mask to change */ ) @@ -5225,7 +5225,7 @@ FRESULT f_chmod ( /*-----------------------------------------------------------------------*/ FRESULT f_utime ( - const TCHAR* path, /* Pointer to the file/directory name */ + const FF_TCHAR* path, /* Pointer to the file/directory name */ const FF_FILINFO* fno /* Pointer to the timestamp to be set */ ) { @@ -5272,8 +5272,8 @@ FRESULT f_utime ( /*-----------------------------------------------------------------------*/ FRESULT f_getlabel ( - const TCHAR* path, /* Logical drive number */ - TCHAR* label, /* Buffer to store the volume label */ + const FF_TCHAR* path, /* Logical drive number */ + FF_TCHAR* label, /* Buffer to store the volume label */ DWORD* vsn /* Variable to store the volume serial number */ ) { @@ -5322,7 +5322,7 @@ FRESULT f_getlabel ( if (wc == 0) { di = 0; break; } /* Invalid char in current code page? */ di += put_utf(wc, &label[di], 4); /* Store it in Unicode */ #else /* ANSI/OEM output */ - label[di++] = (TCHAR)wc; + label[di++] = (FF_TCHAR)wc; #endif } do { /* Truncate trailing spaces */ @@ -5369,7 +5369,7 @@ FRESULT f_getlabel ( /*-----------------------------------------------------------------------*/ FRESULT f_setlabel ( - const TCHAR* label /* Volume label to set with heading logical drive number */ + const FF_TCHAR* label /* Volume label to set with heading logical drive number */ ) { FRESULT res; @@ -5800,7 +5800,7 @@ static FRESULT create_partition ( FRESULT f_mkfs ( - const TCHAR* path, /* Logical drive number */ + const FF_TCHAR* path, /* Logical drive number */ const FF_MKFS_PARM* opt, /* Format options */ void* work, /* Pointer to working buffer (null: use heap memory) */ UINT len /* Size of working buffer [byte] */ @@ -6335,14 +6335,14 @@ FRESULT f_fdisk ( /* Get a String from the File */ /*-----------------------------------------------------------------------*/ -TCHAR* f_gets ( - TCHAR* buff, /* Pointer to the buffer to store read string */ +FF_TCHAR* f_gets ( + FF_TCHAR* buff, /* Pointer to the buffer to store read string */ int len, /* Size of string buffer (items) */ FF_FIL* fp /* Pointer to the file object */ ) { int nc = 0; - TCHAR *p = buff; + FF_TCHAR *p = buff; BYTE s[4]; UINT rc; DWORD dc; @@ -6407,32 +6407,32 @@ TCHAR* f_gets ( if (FF_USE_STRFUNC == 2 && dc == '\r') continue; /* Strip \r off if needed */ #if FF_LFN_UNICODE == 1 || FF_LFN_UNICODE == 3 /* Output it in UTF-16/32 encoding */ if (FF_LFN_UNICODE == 1 && dc >= 0x10000) { /* Out of BMP at UTF-16? */ - *p++ = (TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */ + *p++ = (FF_TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */ dc = 0xDC00 | (dc & 0x3FF); /* Make low surrogate */ } - *p++ = (TCHAR)dc; nc++; + *p++ = (FF_TCHAR)dc; nc++; if (dc == '\n') break; /* End of line? */ #elif FF_LFN_UNICODE == 2 /* Output it in UTF-8 encoding */ if (dc < 0x80) { /* Single byte? */ - *p++ = (TCHAR)dc; + *p++ = (FF_TCHAR)dc; nc++; if (dc == '\n') break; /* End of line? */ } else { if (dc < 0x800) { /* 2-byte sequence? */ - *p++ = (TCHAR)(0xC0 | (dc >> 6 & 0x1F)); - *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + *p++ = (FF_TCHAR)(0xC0 | (dc >> 6 & 0x1F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 0 & 0x3F)); nc += 2; } else { if (dc < 0x10000) { /* 3-byte sequence? */ - *p++ = (TCHAR)(0xE0 | (dc >> 12 & 0x0F)); - *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); - *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + *p++ = (FF_TCHAR)(0xE0 | (dc >> 12 & 0x0F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 0 & 0x3F)); nc += 3; } else { /* 4-byte sequence? */ - *p++ = (TCHAR)(0xF0 | (dc >> 18 & 0x07)); - *p++ = (TCHAR)(0x80 | (dc >> 12 & 0x3F)); - *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); - *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + *p++ = (FF_TCHAR)(0xF0 | (dc >> 18 & 0x07)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 12 & 0x3F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (FF_TCHAR)(0x80 | (dc >> 0 & 0x3F)); nc += 4; } } @@ -6447,7 +6447,7 @@ TCHAR* f_gets ( if (rc != 1) break; /* EOF? */ dc = s[0]; if (FF_USE_STRFUNC == 2 && dc == '\r') continue; - *p++ = (TCHAR)dc; nc++; + *p++ = (FF_TCHAR)dc; nc++; if (dc == '\n') break; } #endif @@ -6485,7 +6485,7 @@ typedef struct { /* Buffered file write with code conversion */ -static void putc_bfd (putbuff* pb, TCHAR c) +static void putc_bfd (putbuff* pb, FF_TCHAR c) { UINT n; int i, nc; @@ -6493,7 +6493,7 @@ static void putc_bfd (putbuff* pb, TCHAR c) WCHAR hs, wc; #if FF_LFN_UNICODE == 2 DWORD dc; - const TCHAR *tp; + const FF_TCHAR *tp; #endif #endif @@ -6535,7 +6535,7 @@ static void putc_bfd (putbuff* pb, TCHAR c) return; } } - tp = (const TCHAR*)pb->bs; + tp = (const FF_TCHAR*)pb->bs; dc = tchar2uni(&tp); /* UTF-8 ==> UTF-16 */ if (dc == 0xFFFFFFFF) return; /* Wrong code? */ wc = (WCHAR)dc; @@ -6638,7 +6638,7 @@ static void putc_init (putbuff* pb, FF_FIL* fp) int f_putc ( - TCHAR c, /* A character to be output */ + FF_TCHAR c, /* A character to be output */ FF_FIL* fp /* Pointer to the file object */ ) { @@ -6658,7 +6658,7 @@ int f_putc ( /*-----------------------------------------------------------------------*/ int f_puts ( - const TCHAR* str, /* Pointer to the string to be output */ + const FF_TCHAR* str, /* Pointer to the string to be output */ FF_FIL* fp /* Pointer to the file object */ ) { @@ -6727,7 +6727,7 @@ static void ftoa ( char* buf, /* Buffer to output the floating point string */ double val, /* Value to output */ int prec, /* Number of fractional digits */ - TCHAR fmt /* Notation */ + FF_TCHAR fmt /* Notation */ ) { int d; @@ -6800,7 +6800,7 @@ static void ftoa ( int f_printf ( FF_FIL* fp, /* Pointer to the file object */ - const TCHAR* fmt, /* Pointer to the format string */ + const FF_TCHAR* fmt, /* Pointer to the format string */ ... /* Optional arguments... */ ) { @@ -6813,8 +6813,8 @@ int f_printf ( #else DWORD v; #endif - TCHAR tc, pad, *tp; - TCHAR nul = 0; + FF_TCHAR tc, pad, *tp; + FF_TCHAR nul = 0; char d, str[SZ_NUM_BUF]; @@ -6879,10 +6879,10 @@ int f_printf ( case 'X': /* Unsigned hexdecimal (upper case) */ r = 16; break; case 'c': /* Character */ - putc_bfd(&pb, (TCHAR)va_arg(arp, int)); + putc_bfd(&pb, (FF_TCHAR)va_arg(arp, int)); continue; case 's': /* String */ - tp = va_arg(arp, TCHAR*); /* Get a pointer argument */ + tp = va_arg(arp, FF_TCHAR*); /* Get a pointer argument */ if (!tp) tp = &nul; /* Null ptr generates a null string */ for (j = 0; tp[j]; j++) ; /* j = tcslen(tp) */ if (prec >= 0 && j > (UINT)prec) j = prec; /* Limited length of string body */ @@ -6937,7 +6937,7 @@ int f_printf ( if (f & 1) str[i++] = '-'; /* Sign */ /* Write it */ for (j = i; !(f & 2) && j < w; j++) putc_bfd(&pb, pad); /* Left pads */ - do putc_bfd(&pb, (TCHAR)str[--i]); while (i); /* Body */ + do putc_bfd(&pb, (FF_TCHAR)str[--i]); while (i); /* Body */ while (j++ < w) putc_bfd(&pb, ' '); /* Right pads */ } diff --git a/src/fatfs/ff.h b/src/fatfs/ff.h index c2832be1..1662d836 100644 --- a/src/fatfs/ff.h +++ b/src/fatfs/ff.h @@ -85,24 +85,24 @@ typedef DWORD LBA_t; -/* Type of path name strings on FatFs API (TCHAR) */ +/* Type of path name strings on FatFs API (FF_TCHAR) */ #if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ -typedef WCHAR TCHAR; +typedef WCHAR FF_TCHAR; #define _T(x) L ## x #define _TEXT(x) L ## x #elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ -typedef char TCHAR; +typedef char FF_TCHAR; #define _T(x) u8 ## x #define _TEXT(x) u8 ## x #elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ -typedef DWORD TCHAR; +typedef DWORD FF_TCHAR; #define _T(x) U ## x #define _TEXT(x) U ## x #elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) #error Wrong FF_LFN_UNICODE setting #else /* ANSI/OEM code in SBCS/DBCS */ -typedef char TCHAR; +typedef char FF_TCHAR; #define _T(x) x #define _TEXT(x) x #endif @@ -236,7 +236,7 @@ typedef struct { DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ #endif #if FF_USE_FIND - const TCHAR* pat; /* Pointer to the name matching pattern */ + const FF_TCHAR* pat; /* Pointer to the name matching pattern */ #endif } FF_DIR; @@ -250,10 +250,10 @@ typedef struct { WORD ftime; /* Modified time */ BYTE fattrib; /* File attribute */ #if FF_USE_LFN - TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ - TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ + FF_TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ + FF_TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ #else - TCHAR fname[12 + 1]; /* File name */ + FF_TCHAR fname[12 + 1]; /* File name */ #endif } FF_FILINFO; @@ -301,40 +301,40 @@ typedef enum { /*--------------------------------------------------------------*/ /* FatFs module application interface */ -FRESULT f_open (FF_FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ +FRESULT f_open (FF_FIL* fp, const FF_TCHAR* path, BYTE mode); /* Open or create a file */ FRESULT f_close (FF_FIL* fp); /* Close an open file object */ FRESULT f_read (FF_FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ FRESULT f_write (FF_FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ FRESULT f_lseek (FF_FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ FRESULT f_truncate (FF_FIL* fp); /* Truncate the file */ FRESULT f_sync (FF_FIL* fp); /* Flush cached data of the writing file */ -FRESULT f_opendir (FF_DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_opendir (FF_DIR* dp, const FF_TCHAR* path); /* Open a directory */ FRESULT f_closedir (FF_DIR* dp); /* Close an open directory */ FRESULT f_readdir (FF_DIR* dp, FF_FILINFO* fno); /* Read a directory item */ -FRESULT f_findfirst (FF_DIR* dp, FF_FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findfirst (FF_DIR* dp, FF_FILINFO* fno, const FF_TCHAR* path, const FF_TCHAR* pattern); /* Find first file */ FRESULT f_findnext (FF_DIR* dp, FF_FILINFO* fno); /* Find next file */ -FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ -FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ -FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ -FRESULT f_stat (const TCHAR* path, FF_FILINFO* fno); /* Get file status */ -FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ -FRESULT f_utime (const TCHAR* path, const FF_FILINFO* fno); /* Change timestamp of a file/dir */ -FRESULT f_chdir (const TCHAR* path); /* Change current directory */ -FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ -FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */ -FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ -FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */ -FRESULT f_setlabel (const TCHAR* label); /* Set volume label */ +FRESULT f_mkdir (const FF_TCHAR* path); /* Create a sub directory */ +FRESULT f_unlink (const FF_TCHAR* path); /* Delete an existing file or directory */ +FRESULT f_rename (const FF_TCHAR* path_old, const FF_TCHAR* path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (const FF_TCHAR* path, FF_FILINFO* fno); /* Get file status */ +FRESULT f_chmod (const FF_TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ +FRESULT f_utime (const FF_TCHAR* path, const FF_FILINFO* fno); /* Change timestamp of a file/dir */ +FRESULT f_chdir (const FF_TCHAR* path); /* Change current directory */ +FRESULT f_chdrive (const FF_TCHAR* path); /* Change current drive */ +FRESULT f_getcwd (FF_TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (const FF_TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (const FF_TCHAR* path, FF_TCHAR* label, DWORD* vsn); /* Get volume label */ +FRESULT f_setlabel (const FF_TCHAR* label); /* Set volume label */ FRESULT f_forward (FF_FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ FRESULT f_expand (FF_FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */ -FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ -FRESULT f_mkfs (const TCHAR* path, const FF_MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ +FRESULT f_mount (FATFS* fs, const FF_TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ +FRESULT f_mkfs (const FF_TCHAR* path, const FF_MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */ FRESULT f_setcp (WORD cp); /* Set current code page */ -int f_putc (TCHAR c, FF_FIL* fp); /* Put a character to the file */ -int f_puts (const TCHAR* str, FF_FIL* cp); /* Put a string to the file */ -int f_printf (FF_FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ -TCHAR* f_gets (TCHAR* buff, int len, FF_FIL* fp); /* Get a string from the file */ +int f_putc (FF_TCHAR c, FF_FIL* fp); /* Put a character to the file */ +int f_puts (const FF_TCHAR* str, FF_FIL* cp); /* Put a string to the file */ +int f_printf (FF_FIL* fp, const FF_TCHAR* str, ...); /* Put a formatted string to the file */ +FF_TCHAR* f_gets (FF_TCHAR* buff, int len, FF_FIL* fp); /* Get a string from the file */ #define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) #define f_error(fp) ((fp)->err) diff --git a/src/fatfs/ffsystem.c b/src/fatfs/ffsystem.c index 63fedf65..ebde84a5 100644 --- a/src/fatfs/ffsystem.c +++ b/src/fatfs/ffsystem.c @@ -110,7 +110,11 @@ DWORD get_fattime(void) time_t timestamp = time(NULL); struct tm timedata; +#if defined(_MSC_VER) + localtime_s(&timedata, ×tamp); +#else localtime_r(×tamp, &timedata); +#endif DWORD ret; ret = (timedata.tm_sec >> 1); diff --git a/src/frontend/qt_sdl/ArchiveUtil.cpp b/src/frontend/qt_sdl/ArchiveUtil.cpp index 7d1eca9c..aa508e8d 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.cpp +++ b/src/frontend/qt_sdl/ArchiveUtil.cpp @@ -120,13 +120,12 @@ QVector ExtractFileFromArchive(QString path, QString wantedFile, QByteA } -s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize) +s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr& filedata, u32* filesize) { struct archive *a = archive_read_new(); struct archive_entry *entry; int r; - if (!filedata) return -1; archive_read_support_format_all(a); archive_read_support_filter_all(a); @@ -148,8 +147,8 @@ s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* size_t bytesToRead = archive_entry_size(entry); if (filesize) *filesize = bytesToRead; - *filedata = new u8[bytesToRead]; - ssize_t bytesRead = archive_read_data(a, *filedata, bytesToRead); + filedata = std::make_unique(bytesToRead); + ssize_t bytesRead = archive_read_data(a, filedata.get(), bytesToRead); archive_read_close(a); archive_read_free(a); diff --git a/src/frontend/qt_sdl/ArchiveUtil.h b/src/frontend/qt_sdl/ArchiveUtil.h index eaffb0dd..246670e7 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.h +++ b/src/frontend/qt_sdl/ArchiveUtil.h @@ -37,7 +37,7 @@ namespace Archive using namespace melonDS; QVector ListArchive(QString path); -s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize); +s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr& filedata, u32* filesize); //QVector ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer); //u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata); diff --git a/src/frontend/qt_sdl/AudioInOut.cpp b/src/frontend/qt_sdl/AudioInOut.cpp index ae5529d9..1f1ee1c5 100644 --- a/src/frontend/qt_sdl/AudioInOut.cpp +++ b/src/frontend/qt_sdl/AudioInOut.cpp @@ -369,7 +369,7 @@ void UpdateSettings(NDS& nds) { MicClose(); - nds.SPU.SetInterpolation(Config::AudioInterp); + nds.SPU.SetInterpolation(static_cast(Config::AudioInterp)); SetupMicInputData(); MicOpen(); diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 40815638..92810210 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -5,6 +5,9 @@ include(FixInterfaceIncludes) set(SOURCES_QT_SDL main.cpp main_shaders.h + Screen.cpp + Window.cpp + EmuThread.cpp CheatsDialog.cpp Config.cpp DateTimeDialog.cpp @@ -38,7 +41,7 @@ set(SOURCES_QT_SDL SaveManager.cpp CameraManager.cpp AudioInOut.cpp - + ArchiveUtil.h ArchiveUtil.cpp @@ -82,11 +85,11 @@ if (BUILD_STATIC) endif() pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) -pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp) +pkg_check_modules(Slirp REQUIRED slirp) pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive) pkg_check_modules(Zstd REQUIRED IMPORTED_TARGET libzstd) -fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive) +fix_interface_includes(PkgConfig::SDL2 PkgConfig::LibArchive) add_compile_definitions(ARCHIVE_SUPPORT_ENABLED) @@ -158,9 +161,12 @@ else() target_include_directories(melonDS PUBLIC ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() target_link_libraries(melonDS PRIVATE core) -target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive PkgConfig::Zstd) +target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::LibArchive PkgConfig::Zstd) target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS}) +target_include_directories(melonDS PRIVATE "${Slirp_INCLUDE_DIRS}") +target_link_libraries(melonDS PRIVATE "${Slirp_LINK_LIBRARIES}") + if (UNIX) option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF) elseif (WIN32) diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.ui b/src/frontend/qt_sdl/EmuSettingsDialog.ui index 74bc0865..2746e1da 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.ui +++ b/src/frontend/qt_sdl/EmuSettingsDialog.ui @@ -26,7 +26,7 @@ - 5 + 0 diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp new file mode 100644 index 00000000..f86d30b7 --- /dev/null +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -0,0 +1,758 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "main.h" +#include "Input.h" +#include "AudioInOut.h" + +#include "types.h" +#include "version.h" + +#include "FrontendUtil.h" +#include "OSD.h" + +#include "Args.h" +#include "NDS.h" +#include "NDSCart.h" +#include "GBACart.h" +#include "GPU.h" +#include "SPU.h" +#include "Wifi.h" +#include "Platform.h" +#include "LocalMP.h" +#include "Config.h" +#include "RTC.h" +#include "DSi.h" +#include "DSi_I2C.h" +#include "GPU3D_Soft.h" +#include "GPU3D_OpenGL.h" + +#include "Savestate.h" + +#include "ROMManager.h" +//#include "ArchiveUtil.h" +//#include "CameraManager.h" + +//#include "CLI.h" + +// TODO: uniform variable spelling +using namespace melonDS; + +// TEMP +extern bool RunningSomething; +extern MainWindow* mainWindow; +extern int autoScreenSizing; +extern int videoRenderer; +extern bool videoSettingsDirty; + + +EmuThread::EmuThread(QObject* parent) : QThread(parent) +{ + EmuStatus = emuStatus_Exit; + EmuRunning = emuStatus_Paused; + EmuPauseStack = EmuPauseStackRunning; + RunningSomething = false; + + connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); + connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); + connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); + connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); + connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger())); + connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); + connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); + connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); + connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged())); + connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); + connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); + connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); +} + +std::unique_ptr EmuThread::CreateConsole( + std::unique_ptr&& ndscart, + std::unique_ptr&& gbacart +) noexcept +{ + auto arm7bios = ROMManager::LoadARM7BIOS(); + if (!arm7bios) + return nullptr; + + auto arm9bios = ROMManager::LoadARM9BIOS(); + if (!arm9bios) + return nullptr; + + auto firmware = ROMManager::LoadFirmware(Config::ConsoleType); + if (!firmware) + return nullptr; + +#ifdef JIT_ENABLED + JITArgs jitargs { + static_cast(Config::JIT_MaxBlockSize), + Config::JIT_LiteralOptimisations, + Config::JIT_BranchOptimisations, + Config::JIT_FastMemory, + }; +#endif + +#ifdef GDBSTUB_ENABLED + GDBArgs gdbargs { + static_cast(Config::GdbPortARM7), + static_cast(Config::GdbPortARM9), + Config::GdbARM7BreakOnStartup, + Config::GdbARM9BreakOnStartup, + }; +#endif + + NDSArgs ndsargs { + std::move(ndscart), + std::move(gbacart), + *arm9bios, + *arm7bios, + std::move(*firmware), +#ifdef JIT_ENABLED + Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt, +#else + std::nullopt, +#endif + static_cast(Config::AudioBitDepth), + static_cast(Config::AudioInterp), +#ifdef GDBSTUB_ENABLED + Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt, +#else + std::nullopt, +#endif + }; + + if (Config::ConsoleType == 1) + { + auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); + if (!arm7ibios) + return nullptr; + + auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); + if (!arm9ibios) + return nullptr; + + auto nand = ROMManager::LoadNAND(*arm7ibios); + if (!nand) + return nullptr; + + auto sdcard = ROMManager::LoadDSiSDCard(); + DSiArgs args { + std::move(ndsargs), + *arm9ibios, + *arm7ibios, + std::move(*nand), + std::move(sdcard), + Config::DSiFullBIOSBoot, + }; + + args.GBAROM = nullptr; + + return std::make_unique(std::move(args)); + } + + return std::make_unique(std::move(ndsargs)); +} + +bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept +{ + // Let's get the cart we want to use; + // if we wnat to keep the cart, we'll eject it from the existing console first. + std::unique_ptr nextndscart; + if (std::holds_alternative(ndsargs)) + { // If we want to keep the existing cart (if any)... + nextndscart = NDS ? NDS->EjectCart() : nullptr; + ndsargs = {}; + } + else if (const auto ptr = std::get_if>(&ndsargs)) + { + nextndscart = std::move(*ptr); + ndsargs = {}; + } + + if (nextndscart && nextndscart->Type() == NDSCart::Homebrew) + { + // Load DLDISDCard will return nullopt if the SD card is disabled; + // SetSDCard will accept nullopt, which means no SD card + auto& homebrew = static_cast(*nextndscart); + homebrew.SetSDCard(ROMManager::LoadDLDISDCard()); + } + + std::unique_ptr nextgbacart; + if (std::holds_alternative(gbaargs)) + { + nextgbacart = NDS ? NDS->EjectGBACart() : nullptr; + } + else if (const auto ptr = std::get_if>(&gbaargs)) + { + nextgbacart = std::move(*ptr); + gbaargs = {}; + } + + if (!NDS || NDS->ConsoleType != Config::ConsoleType) + { // If we're switching between DS and DSi mode, or there's no console... + // To ensure the destructor is called before a new one is created, + // as the presence of global signal handlers still complicates things a bit + NDS = nullptr; + NDS::Current = nullptr; + + NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart)); + + if (NDS == nullptr) + return false; + + NDS->Reset(); + NDS::Current = NDS.get(); + + return true; + } + + auto arm9bios = ROMManager::LoadARM9BIOS(); + if (!arm9bios) + return false; + + auto arm7bios = ROMManager::LoadARM7BIOS(); + if (!arm7bios) + return false; + + auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType); + if (!firmware) + return false; + + if (NDS->ConsoleType == 1) + { // If the console we're updating is a DSi... + DSi& dsi = static_cast(*NDS); + + auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); + if (!arm9ibios) + return false; + + auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); + if (!arm7ibios) + return false; + + auto nandimage = ROMManager::LoadNAND(*arm7ibios); + if (!nandimage) + return false; + + auto dsisdcard = ROMManager::LoadDSiSDCard(); + + dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot); + dsi.ARM7iBIOS = *arm7ibios; + dsi.ARM9iBIOS = *arm9ibios; + dsi.SetNAND(std::move(*nandimage)); + dsi.SetSDCard(std::move(dsisdcard)); + // We're moving the optional, not the card + // (inserting std::nullopt here is okay, it means no card) + + dsi.EjectGBACart(); + } + + if (NDS->ConsoleType == 0) + { + NDS->SetGBACart(std::move(nextgbacart)); + } + +#ifdef JIT_ENABLED + JITArgs jitargs { + static_cast(Config::JIT_MaxBlockSize), + Config::JIT_LiteralOptimisations, + Config::JIT_BranchOptimisations, + Config::JIT_FastMemory, + }; + NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt); +#endif + NDS->SetARM7BIOS(*arm7bios); + NDS->SetARM9BIOS(*arm9bios); + NDS->SetFirmware(std::move(*firmware)); + NDS->SetNDSCart(std::move(nextndscart)); + NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + NDS->SPU.SetDegrade10Bit(static_cast(Config::AudioBitDepth)); + + NDS::Current = NDS.get(); + + return true; +} + +void EmuThread::run() +{ + u32 mainScreenPos[3]; + Platform::FileHandle* file; + + UpdateConsole(nullptr, nullptr); + // No carts are inserted when melonDS first boots + + mainScreenPos[0] = 0; + mainScreenPos[1] = 0; + mainScreenPos[2] = 0; + autoScreenSizing = 0; + + videoSettingsDirty = false; + + if (mainWindow->hasOGL) + { + screenGL = static_cast(mainWindow->panel); + screenGL->initOpenGL(); + videoRenderer = Config::_3DRenderer; + } + else + { + screenGL = nullptr; + videoRenderer = 0; + } + + if (videoRenderer == 0) + { // If we're using the software renderer... + NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); + } + else + { + auto glrenderer = melonDS::GLRenderer::New(); + glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); + NDS->GPU.SetRenderer3D(std::move(glrenderer)); + } + + Input::Init(); + + u32 nframes = 0; + double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); + double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; + double frameLimitError = 0.0; + double lastMeasureTime = lastTime; + + u32 winUpdateCount = 0, winUpdateFreq = 1; + u8 dsiVolumeLevel = 0x1F; + + file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); + if (file) + { + RTC::StateData state; + Platform::FileRead(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + NDS->RTC.SetState(state); + } + + char melontitle[100]; + + while (EmuRunning != emuStatus_Exit) + { + Input::Process(); + + if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); + + if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); + if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); + if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); + + if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); + + if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); + if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); + + if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) + { + EmuStatus = emuStatus_Running; + if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; + + if (Input::HotkeyPressed(HK_SolarSensorDecrease)) + { + int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); + if (level != -1) + { + char msg[64]; + sprintf(msg, "Solar sensor level: %d", level); + OSD::AddMessage(0, msg); + } + } + if (Input::HotkeyPressed(HK_SolarSensorIncrease)) + { + int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); + if (level != -1) + { + char msg[64]; + sprintf(msg, "Solar sensor level: %d", level); + OSD::AddMessage(0, msg); + } + } + + if (NDS->ConsoleType == 1) + { + DSi& dsi = static_cast(*NDS); + double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; + + // Handle power button + if (Input::HotkeyDown(HK_PowerButton)) + { + dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); + } + else if (Input::HotkeyReleased(HK_PowerButton)) + { + dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); + } + + // Handle volume buttons + if (Input::HotkeyDown(HK_VolumeUp)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); + } + else if (Input::HotkeyReleased(HK_VolumeUp)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); + } + + if (Input::HotkeyDown(HK_VolumeDown)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); + } + else if (Input::HotkeyReleased(HK_VolumeDown)) + { + dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); + } + + dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); + } + + // update render settings if needed + // HACK: + // once the fast forward hotkey is released, we need to update vsync + // to the old setting again + if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward)) + { + if (screenGL) + { + screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); + videoRenderer = Config::_3DRenderer; + } +#ifdef OGLRENDERER_ENABLED + else +#endif + { + videoRenderer = 0; + } + + videoRenderer = screenGL ? Config::_3DRenderer : 0; + + videoSettingsDirty = false; + + if (videoRenderer == 0) + { // If we're using the software renderer... + NDS->GPU.SetRenderer3D(std::make_unique(Config::Threaded3D != 0)); + } + else + { + auto glrenderer = melonDS::GLRenderer::New(); + glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); + NDS->GPU.SetRenderer3D(std::move(glrenderer)); + } + } + + // process input and hotkeys + NDS->SetKeyMask(Input::InputMask); + + if (Input::HotkeyPressed(HK_Lid)) + { + bool lid = !NDS->IsLidClosed(); + NDS->SetLidClosed(lid); + OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened"); + } + + // microphone input + AudioInOut::MicProcess(*NDS); + + // auto screen layout + if (Config::ScreenSizing == Frontend::screenSizing_Auto) + { + mainScreenPos[2] = mainScreenPos[1]; + mainScreenPos[1] = mainScreenPos[0]; + mainScreenPos[0] = NDS->PowerControl9 >> 15; + + int guess; + if (mainScreenPos[0] == mainScreenPos[2] && + mainScreenPos[0] != mainScreenPos[1]) + { + // constant flickering, likely displaying 3D on both screens + // TODO: when both screens are used for 2D only...??? + guess = Frontend::screenSizing_Even; + } + else + { + if (mainScreenPos[0] == 1) + guess = Frontend::screenSizing_EmphTop; + else + guess = Frontend::screenSizing_EmphBot; + } + + if (guess != autoScreenSizing) + { + autoScreenSizing = guess; + emit screenLayoutChange(); + } + } + + + // emulate + u32 nlines = NDS->RunFrame(); + + if (ROMManager::NDSSave) + ROMManager::NDSSave->CheckFlush(); + + if (ROMManager::GBASave) + ROMManager::GBASave->CheckFlush(); + + if (ROMManager::FirmwareSave) + ROMManager::FirmwareSave->CheckFlush(); + + if (!screenGL) + { + FrontBufferLock.lock(); + FrontBuffer = NDS->GPU.FrontBuffer; + FrontBufferLock.unlock(); + } + else + { + FrontBuffer = NDS->GPU.FrontBuffer; + screenGL->drawScreenGL(); + } + +#ifdef MELONCAP + MelonCap::Update(); +#endif // MELONCAP + + if (EmuRunning == emuStatus_Exit) break; + + winUpdateCount++; + if (winUpdateCount >= winUpdateFreq && !screenGL) + { + emit windowUpdate(); + winUpdateCount = 0; + } + + bool fastforward = Input::HotkeyDown(HK_FastForward); + + if (fastforward && screenGL && Config::ScreenVSync) + { + screenGL->setSwapInterval(0); + } + + if (Config::DSiVolumeSync && NDS->ConsoleType == 1) + { + DSi& dsi = static_cast(*NDS); + u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); + if (volumeLevel != dsiVolumeLevel) + { + dsiVolumeLevel = volumeLevel; + emit syncVolumeLevel(); + } + + Config::AudioVolume = volumeLevel * (256.0 / 31.0); + } + + if (Config::AudioSync && !fastforward) + AudioInOut::AudioSync(*this->NDS); + + double frametimeStep = nlines / (60.0 * 263.0); + + { + bool limitfps = Config::LimitFPS && !fastforward; + + double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0; + + double curtime = SDL_GetPerformanceCounter() * perfCountsSec; + + frameLimitError += practicalFramelimit - (curtime - lastTime); + if (frameLimitError < -practicalFramelimit) + frameLimitError = -practicalFramelimit; + if (frameLimitError > practicalFramelimit) + frameLimitError = practicalFramelimit; + + if (round(frameLimitError * 1000.0) > 0.0) + { + SDL_Delay(round(frameLimitError * 1000.0)); + double timeBeforeSleep = curtime; + curtime = SDL_GetPerformanceCounter() * perfCountsSec; + frameLimitError -= curtime - timeBeforeSleep; + } + + lastTime = curtime; + } + + nframes++; + if (nframes >= 30) + { + double time = SDL_GetPerformanceCounter() * perfCountsSec; + double dt = time - lastMeasureTime; + lastMeasureTime = time; + + u32 fps = round(nframes / dt); + nframes = 0; + + float fpstarget = 1.0/frametimeStep; + + winUpdateFreq = fps / (u32)round(fpstarget); + if (winUpdateFreq < 1) + winUpdateFreq = 1; + + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); + else + sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); + changeWindowTitle(melontitle); + } + } + else + { + // paused + nframes = 0; + lastTime = SDL_GetPerformanceCounter() * perfCountsSec; + lastMeasureTime = lastTime; + + emit windowUpdate(); + + EmuStatus = EmuRunning; + + int inst = Platform::InstanceID(); + if (inst == 0) + sprintf(melontitle, "melonDS " MELONDS_VERSION); + else + sprintf(melontitle, "melonDS (%d)", inst+1); + changeWindowTitle(melontitle); + + SDL_Delay(75); + + if (screenGL) + screenGL->drawScreenGL(); + + ContextRequestKind contextRequest = ContextRequest; + if (contextRequest == contextRequest_InitGL) + { + screenGL = static_cast(mainWindow->panel); + screenGL->initOpenGL(); + ContextRequest = contextRequest_None; + } + else if (contextRequest == contextRequest_DeInitGL) + { + screenGL->deinitOpenGL(); + screenGL = nullptr; + ContextRequest = contextRequest_None; + } + } + } + + file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); + if (file) + { + RTC::StateData state; + NDS->RTC.GetState(state); + Platform::FileWrite(&state, sizeof(state), 1, file); + Platform::CloseFile(file); + } + + EmuStatus = emuStatus_Exit; + + NDS::Current = nullptr; + // nds is out of scope, so unique_ptr cleans it up for us +} + +void EmuThread::changeWindowTitle(char* title) +{ + emit windowTitleChange(QString(title)); +} + +void EmuThread::emuRun() +{ + EmuRunning = emuStatus_Running; + EmuPauseStack = EmuPauseStackRunning; + RunningSomething = true; + + // checkme + emit windowEmuStart(); + AudioInOut::Enable(); +} + +void EmuThread::initContext() +{ + ContextRequest = contextRequest_InitGL; + while (ContextRequest != contextRequest_None); +} + +void EmuThread::deinitContext() +{ + ContextRequest = contextRequest_DeInitGL; + while (ContextRequest != contextRequest_None); +} + +void EmuThread::emuPause() +{ + EmuPauseStack++; + if (EmuPauseStack > EmuPauseStackPauseThreshold) return; + + PrevEmuStatus = EmuRunning; + EmuRunning = emuStatus_Paused; + while (EmuStatus != emuStatus_Paused); + + AudioInOut::Disable(); +} + +void EmuThread::emuUnpause() +{ + if (EmuPauseStack < EmuPauseStackPauseThreshold) return; + + EmuPauseStack--; + if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; + + EmuRunning = PrevEmuStatus; + + AudioInOut::Enable(); +} + +void EmuThread::emuStop() +{ + EmuRunning = emuStatus_Exit; + EmuPauseStack = EmuPauseStackRunning; + + AudioInOut::Disable(); +} + +void EmuThread::emuFrameStep() +{ + if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); + EmuRunning = emuStatus_FrameStep; +} + +bool EmuThread::emuIsRunning() +{ + return EmuRunning == emuStatus_Running; +} + +bool EmuThread::emuIsActive() +{ + return (RunningSomething == 1); +} diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h new file mode 100644 index 00000000..4950ebbf --- /dev/null +++ b/src/frontend/qt_sdl/EmuThread.h @@ -0,0 +1,134 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef EMUTHREAD_H +#define EMUTHREAD_H + +#include +#include + +#include +#include +#include + +#include "NDSCart.h" +#include "GBACart.h" + +using Keep = std::monostate; +using UpdateConsoleNDSArgs = std::variant>; +using UpdateConsoleGBAArgs = std::variant>; +namespace melonDS +{ +class NDS; +} + +class ScreenPanelGL; + +class EmuThread : public QThread +{ + Q_OBJECT + void run() override; + +public: + explicit EmuThread(QObject* parent = nullptr); + + void changeWindowTitle(char* title); + + // to be called from the UI thread + void emuRun(); + void emuPause(); + void emuUnpause(); + void emuStop(); + void emuFrameStep(); + + bool emuIsRunning(); + bool emuIsActive(); + + void initContext(); + void deinitContext(); + + int FrontBuffer = 0; + QMutex FrontBufferLock; + + /// Applies the config in args. + /// Creates a new NDS console if needed, + /// modifies the existing one if possible. + /// @return \c true if the console was updated. + /// If this returns \c false, then the existing NDS console is not modified. + bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept; + std::unique_ptr NDS; // TODO: Proper encapsulation and synchronization +signals: + void windowUpdate(); + void windowTitleChange(QString title); + + void windowEmuStart(); + void windowEmuStop(); + void windowEmuPause(); + void windowEmuReset(); + void windowEmuFrameStep(); + + void windowLimitFPSChange(); + + void screenLayoutChange(); + + void windowFullscreenToggle(); + + void swapScreensToggle(); + void screenEmphasisToggle(); + + void syncVolumeLevel(); + +private: + std::unique_ptr CreateConsole( + std::unique_ptr&& ndscart, + std::unique_ptr&& gbacart + ) noexcept; + + enum EmuStatusKind + { + emuStatus_Exit, + emuStatus_Running, + emuStatus_Paused, + emuStatus_FrameStep, + }; + std::atomic EmuStatus; + + EmuStatusKind PrevEmuStatus; + EmuStatusKind EmuRunning; + + constexpr static int EmuPauseStackRunning = 0; + constexpr static int EmuPauseStackPauseThreshold = 1; + int EmuPauseStack; + + enum ContextRequestKind + { + contextRequest_None = 0, + contextRequest_InitGL, + contextRequest_DeInitGL + }; + std::atomic ContextRequest = contextRequest_None; + + ScreenPanelGL* screenGL; + + int autoScreenSizing; + + int videoRenderer; + bool videoSettingsDirty; +}; + +#endif // EMUTHREAD_H diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/Input.h index 366c9c39..2dfa4a77 100644 --- a/src/frontend/qt_sdl/Input.h +++ b/src/frontend/qt_sdl/Input.h @@ -19,6 +19,8 @@ #ifndef INPUT_H #define INPUT_H +#include + #include "types.h" namespace Input diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp index a0dfdf76..7ea98686 100644 --- a/src/frontend/qt_sdl/LocalMP.cpp +++ b/src/frontend/qt_sdl/LocalMP.cpp @@ -130,7 +130,7 @@ bool SemInit(int num) char semname[64]; sprintf(semname, "Local\\melonNIFI_Sem%02d", num); - HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname); + HANDLE sem = CreateSemaphoreA(nullptr, 0, 64, semname); SemPool[num] = sem; SemInited[num] = true; return sem != INVALID_HANDLE_VALUE; @@ -248,7 +248,9 @@ bool Init() Log(LogLevel::Info, "MP sharedmem doesn't exist. creating\n"); if (!MPQueue->create(kQueueSize)) { - Log(LogLevel::Error, "MP sharedmem create failed :(\n"); + Log(LogLevel::Error, "MP sharedmem create failed :( (%d)\n", MPQueue->error()); + delete MPQueue; + MPQueue = nullptr; return false; } @@ -303,10 +305,13 @@ void DeInit() if (MPQueue) { MPQueue->lock(); - MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); - header->ConnectedBitmask &= ~(1 << InstanceID); - header->InstanceBitmask &= ~(1 << InstanceID); - header->NumInstances--; + if (MPQueue->data() != nullptr) + { + MPQueueHeader *header = (MPQueueHeader *) MPQueue->data(); + header->ConnectedBitmask &= ~(1 << InstanceID); + header->InstanceBitmask &= ~(1 << InstanceID); + header->NumInstances--; + } MPQueue->unlock(); SemPoolDeinit(); @@ -325,6 +330,7 @@ void SetRecvTimeout(int timeout) void Begin() { + if (!MPQueue) return; MPQueue->lock(); MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); PacketReadOffset = header->PacketWriteOffset; @@ -337,6 +343,7 @@ void Begin() void End() { + if (!MPQueue) return; MPQueue->lock(); MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); //SemReset(InstanceID); @@ -418,6 +425,7 @@ void FIFOWrite(int fifo, void* buf, int len) int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) { + if (!MPQueue) return 0; MPQueue->lock(); u8* data = (u8*)MPQueue->data(); MPQueueHeader* header = (MPQueueHeader*)&data[0]; @@ -473,6 +481,7 @@ int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) int RecvPacketGeneric(u8* packet, bool block, u64* timestamp) { + if (!MPQueue) return 0; for (;;) { if (!SemWait(InstanceID, block ? RecvTimeout : 0)) @@ -549,6 +558,8 @@ int SendAck(u8* packet, int len, u64 timestamp) int RecvHostPacket(u8* packet, u64* timestamp) { + if (!MPQueue) return -1; + if (LastHostID != -1) { // check if the host is still connected @@ -568,6 +579,8 @@ int RecvHostPacket(u8* packet, u64* timestamp) u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) { + if (!MPQueue) return 0; + u16 ret = 0; u16 myinstmask = (1 << InstanceID); u16 curinstmask; diff --git a/src/frontend/qt_sdl/OSD.cpp b/src/frontend/qt_sdl/OSD.cpp index 25072df7..3734b76b 100644 --- a/src/frontend/qt_sdl/OSD.cpp +++ b/src/frontend/qt_sdl/OSD.cpp @@ -418,7 +418,7 @@ void DrawNative(QPainter& painter) Rendering.unlock(); } -void DrawGL(float w, float h) +void DrawGL(float w, float h, float factor) { if (!Config::ShowOSD) return; if (!mainWindow || !mainWindow->panel) return; @@ -430,7 +430,7 @@ void DrawGL(float w, float h) glUseProgram(Shader[2]); glUniform2f(uScreenSize, w, h); - glUniform1f(uScaleFactor, mainWindow->devicePixelRatioF()); + glUniform1f(uScaleFactor, factor); glBindBuffer(GL_ARRAY_BUFFER, OSDVertexBuffer); glBindVertexArray(OSDVertexArray); diff --git a/src/frontend/qt_sdl/OSD.h b/src/frontend/qt_sdl/OSD.h index 64131d5b..c907a0bb 100644 --- a/src/frontend/qt_sdl/OSD.h +++ b/src/frontend/qt_sdl/OSD.h @@ -32,7 +32,7 @@ void AddMessage(u32 color, const char* text); void Update(); void DrawNative(QPainter& painter); -void DrawGL(float w, float h); +void DrawGL(float w, float h, float factor); } diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index c2e2f47b..6fe87ac4 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -66,12 +66,25 @@ void IPCInit() IPCBuffer = new QSharedMemory("melonIPC"); +#if !defined(Q_OS_WINDOWS) + // QSharedMemory instances can be left over from crashed processes on UNIX platforms. + // To prevent melonDS thinking there's another instance, we attach and then immediately detach from the + // shared memory. If no other process was actually using it, it'll be destroyed and we'll have a clean + // shared memory buffer after creating it again below. + if (IPCBuffer->attach()) + { + IPCBuffer->detach(); + delete IPCBuffer; + IPCBuffer = new QSharedMemory("melonIPC"); + } +#endif + if (!IPCBuffer->attach()) { Log(LogLevel::Info, "IPC sharedmem doesn't exist. creating\n"); if (!IPCBuffer->create(1024)) { - Log(LogLevel::Error, "IPC sharedmem create failed :(\n"); + Log(LogLevel::Error, "IPC sharedmem create failed: %s\n", IPCBuffer->errorString().toStdString().c_str()); delete IPCBuffer; IPCBuffer = nullptr; return; @@ -193,118 +206,6 @@ std::string InstanceFileSuffix() return suffix; } - -int GetConfigInt(ConfigEntry entry) -{ - const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; - - switch (entry) - { -#ifdef JIT_ENABLED - case JIT_MaxBlockSize: return Config::JIT_MaxBlockSize; -#endif - - case DLDI_ImageSize: return imgsizes[Config::DLDISize]; - - case DSiSD_ImageSize: return imgsizes[Config::DSiSDSize]; - - case AudioBitDepth: return Config::AudioBitDepth; - -#ifdef GDBSTUB_ENABLED - case GdbPortARM7: return Config::GdbPortARM7; - case GdbPortARM9: return Config::GdbPortARM9; -#endif - } - - return 0; -} - -bool GetConfigBool(ConfigEntry entry) -{ - switch (entry) - { -#ifdef JIT_ENABLED - case JIT_Enable: return Config::JIT_Enable != 0; - case JIT_LiteralOptimizations: return Config::JIT_LiteralOptimisations != 0; - case JIT_BranchOptimizations: return Config::JIT_BranchOptimisations != 0; - case JIT_FastMemory: return Config::JIT_FastMemory != 0; -#endif - - case ExternalBIOSEnable: return Config::ExternalBIOSEnable != 0; - - case DLDI_Enable: return Config::DLDIEnable != 0; - case DLDI_ReadOnly: return Config::DLDIReadOnly != 0; - case DLDI_FolderSync: return Config::DLDIFolderSync != 0; - - case DSiSD_Enable: return Config::DSiSDEnable != 0; - case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0; - case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0; - - case DSi_FullBIOSBoot: return Config::DSiFullBIOSBoot != 0; - -#ifdef GDBSTUB_ENABLED - case GdbEnabled: return Config::GdbEnabled; - case GdbARM7BreakOnStartup: return Config::GdbARM7BreakOnStartup; - case GdbARM9BreakOnStartup: return Config::GdbARM9BreakOnStartup; -#endif - } - - return false; -} - -std::string GetConfigString(ConfigEntry entry) -{ - switch (entry) - { - case DLDI_ImagePath: return Config::DLDISDPath; - case DLDI_FolderPath: return Config::DLDIFolderPath; - - case DSiSD_ImagePath: return Config::DSiSDPath; - case DSiSD_FolderPath: return Config::DSiSDFolderPath; - - case WifiSettingsPath: return Config::WifiSettingsPath; - } - - return ""; -} - -bool GetConfigArray(ConfigEntry entry, void* data) -{ - switch (entry) - { - case Firm_MAC: - { - std::string& mac_in = Config::FirmwareMAC; - u8* mac_out = (u8*)data; - - int o = 0; - u8 tmp = 0; - for (int i = 0; i < 18; i++) - { - char c = mac_in[i]; - if (c == '\0') break; - - int n; - if (c >= '0' && c <= '9') n = c - '0'; - else if (c >= 'a' && c <= 'f') n = c - 'a' + 10; - else if (c >= 'A' && c <= 'F') n = c - 'A' + 10; - else continue; - - if (!(o & 1)) - tmp = n; - else - mac_out[o >> 1] = n | (tmp << 4); - - o++; - if (o >= 12) return true; - } - } - return false; - } - - return false; -} - constexpr char AccessMode(FileMode mode, bool file_exists) { if (!(mode & FileMode::Write)) diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 2c0cee2c..a20af206 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -68,8 +68,8 @@ std::string BaseGBAROMDir = ""; std::string BaseGBAROMName = ""; std::string BaseGBAAssetName = ""; -SaveManager* NDSSave = nullptr; -SaveManager* GBASave = nullptr; +std::unique_ptr NDSSave = nullptr; +std::unique_ptr GBASave = nullptr; std::unique_ptr FirmwareSave = nullptr; std::unique_ptr BackupState = nullptr; @@ -303,6 +303,28 @@ QString VerifySetup() return ""; } +std::string GetEffectiveFirmwareSavePath(EmuThread* thread) +{ + if (!Config::ExternalBIOSEnable) + { + return Config::WifiSettingsPath; + } + if (thread->NDS->ConsoleType == 1) + { + return Config::DSiFirmwarePath; + } + else + { + return Config::FirmwarePath; + } +} + +// Initializes the firmware save manager with the selected firmware image's path +// OR the path to the wi-fi settings. +void InitFirmwareSaveManager(EmuThread* thread) noexcept +{ + FirmwareSave = std::make_unique(GetEffectiveFirmwareSavePath(thread)); +} std::string GetSavestateName(int slot) { @@ -482,6 +504,11 @@ void LoadCheats(NDS& nds) std::optional> LoadARM9BIOS() noexcept { + if (!Config::ExternalBIOSEnable) + { + return Config::ConsoleType == 0 ? std::make_optional(bios_arm9_bin) : std::nullopt; + } + if (FileHandle* f = OpenLocalFile(Config::BIOS9Path, Read)) { std::array bios {}; @@ -498,6 +525,11 @@ std::optional> LoadARM9BIOS() noexcept std::optional> LoadARM7BIOS() noexcept { + if (!Config::ExternalBIOSEnable) + { + return Config::ConsoleType == 0 ? std::make_optional(bios_arm7_bin) : std::nullopt; + } + if (FileHandle* f = OpenLocalFile(Config::BIOS7Path, Read)) { std::array bios {}; @@ -518,6 +550,16 @@ std::optional> LoadDSiARM9BIOS() noexcept std::array bios {}; FileRead(bios.data(), sizeof(bios), 1, f); CloseFile(f); + + if (!Config::DSiFullBIOSBoot) + { + // herp + *(u32*)&bios[0] = 0xEAFFFFFE; // overwrites the reset vector + + // TODO!!!! + // hax the upper 32K out of the goddamn DSi + // done that :) -pcy + } Log(Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str()); return bios; } @@ -533,6 +575,16 @@ std::optional> LoadDSiARM7BIOS() noexcept std::array bios {}; FileRead(bios.data(), sizeof(bios), 1, f); CloseFile(f); + + if (!Config::DSiFullBIOSBoot) + { + // herp + *(u32*)&bios[0] = 0xEAFFFFFE; // overwrites the reset vector + + // TODO!!!! + // hax the upper 32K out of the goddamn DSi + // done that :) -pcy + } Log(Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str()); return bios; } @@ -589,6 +641,16 @@ Firmware GenerateFirmware(int type) noexcept std::optional LoadFirmware(int type) noexcept { + if (!Config::ExternalBIOSEnable) + { // If we're using built-in firmware... + if (type == 1) + { + Log(Error, "DSi firmware: cannot use built-in firmware in DSi mode!\n"); + return std::nullopt; + } + + return GenerateFirmware(type); + } const string& firmwarepath = type == 1 ? Config::DSiFirmwarePath : Config::FirmwarePath; Log(Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str()); @@ -609,7 +671,10 @@ std::optional LoadFirmware(int type) noexcept return std::nullopt; } - CustomizeFirmware(firmware); + if (Config::FirmwareOverrideSettings) + { + CustomizeFirmware(firmware); + } return firmware; } @@ -694,7 +759,20 @@ std::optional LoadNAND(const std::array& a return nandImage; } -constexpr int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; +constexpr u64 imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; +std::optional GetDSiSDCardArgs() noexcept +{ + if (!Config::DSiSDEnable) + return std::nullopt; + + return FATStorageArgs { + Config::DSiSDPath, + imgsizes[Config::DSiSDSize], + Config::DSiSDReadOnly, + Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt + }; +} + std::optional LoadDSiSDCard() noexcept { if (!Config::DSiSDEnable) @@ -704,97 +782,34 @@ std::optional LoadDSiSDCard() noexcept Config::DSiSDPath, imgsizes[Config::DSiSDSize], Config::DSiSDReadOnly, - Config::DSiSDFolderSync ? Config::DSiSDFolderPath : "" + Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt ); } -void LoadBIOSFiles(NDS& nds) +std::optional GetDLDISDCardArgs() noexcept { - if (Config::ExternalBIOSEnable) - { - if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS9Path, FileMode::Read)) - { - FileRewind(f); - FileRead(nds.ARM9BIOS, sizeof(NDS::ARM9BIOS), 1, f); + if (!Config::DLDIEnable) + return std::nullopt; - Log(LogLevel::Info, "ARM9 BIOS loaded from %s\n", Config::BIOS9Path.c_str()); - Platform::CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM9 BIOS not found\n"); + return FATStorageArgs{ + Config::DLDISDPath, + imgsizes[Config::DLDISize], + Config::DLDIReadOnly, + Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt + }; +} - for (int i = 0; i < 16; i++) - ((u32*)nds.ARM9BIOS)[i] = 0xE7FFDEFF; - } +std::optional LoadDLDISDCard() noexcept +{ + if (!Config::DLDIEnable) + return std::nullopt; - if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS7Path, FileMode::Read)) - { - FileRead(nds.ARM7BIOS, sizeof(NDS::ARM7BIOS), 1, f); - - Log(LogLevel::Info, "ARM7 BIOS loaded from\n", Config::BIOS7Path.c_str()); - Platform::CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM7 BIOS not found\n"); - - for (int i = 0; i < 16; i++) - ((u32*)nds.ARM7BIOS)[i] = 0xE7FFDEFF; - } - } - else - { - Log(LogLevel::Info, "Using built-in ARM7 and ARM9 BIOSes\n"); - memcpy(nds.ARM9BIOS, bios_arm9_bin, sizeof(bios_arm9_bin)); - memcpy(nds.ARM7BIOS, bios_arm7_bin, sizeof(bios_arm7_bin)); - } - - if (Config::ConsoleType == 1) - { - DSi& dsi = static_cast(nds); - if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS9Path, FileMode::Read)) - { - FileRead(dsi.ARM9iBIOS, sizeof(DSi::ARM9iBIOS), 1, f); - - Log(LogLevel::Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str()); - Platform::CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM9i BIOS not found\n"); - - for (int i = 0; i < 16; i++) - ((u32*)dsi.ARM9iBIOS)[i] = 0xE7FFDEFF; - } - - if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read)) - { - // TODO: check if the first 32 bytes are crapoed - FileRead(dsi.ARM7iBIOS, sizeof(DSi::ARM7iBIOS), 1, f); - - Log(LogLevel::Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str()); - CloseFile(f); - } - else - { - Log(LogLevel::Warn, "ARM7i BIOS not found\n"); - - for (int i = 0; i < 16; i++) - ((u32*)dsi.ARM7iBIOS)[i] = 0xE7FFDEFF; - } - - if (!Config::DSiFullBIOSBoot) - { - // herp - *(u32*)&dsi.ARM9iBIOS[0] = 0xEAFFFFFE; - *(u32*)&dsi.ARM7iBIOS[0] = 0xEAFFFFFE; - - // TODO!!!! - // hax the upper 32K out of the goddamn DSi - // done that :) -pcy - } - } + return FATStorage( + Config::DLDISDPath, + imgsizes[Config::DLDISize], + Config::DLDIReadOnly, + Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt + ); } void EnableCheats(NDS& nds, bool enable) @@ -835,16 +850,10 @@ void SetDateTime(NDS& nds) void Reset(EmuThread* thread) { - thread->RecreateConsole(); + thread->UpdateConsole(Keep {}, Keep {}); if (Config::ConsoleType == 1) EjectGBACart(*thread->NDS); - LoadBIOSFiles(*thread->NDS); - InstallFirmware(*thread->NDS); - if (Config::ConsoleType == 1) - { - InstallNAND(static_cast(*thread->NDS)); - } thread->NDS->Reset(); SetBatteryLevels(*thread->NDS); SetDateTime(*thread->NDS); @@ -867,6 +876,7 @@ void Reset(EmuThread* thread) GBASave->SetPath(newsave, false); } + InitFirmwareSaveManager(thread); if (FirmwareSave) { std::string oldsave = FirmwareSave->GetPath(); @@ -899,36 +909,25 @@ void Reset(EmuThread* thread) } -bool LoadBIOS(EmuThread* thread) +bool BootToMenu(EmuThread* thread) { - thread->RecreateConsole(); - - LoadBIOSFiles(*thread->NDS); - - if (!InstallFirmware(*thread->NDS)) - return false; - - if (Config::ConsoleType == 1 && !InstallNAND(static_cast(*thread->NDS))) + // Keep whatever cart is in the console, if any. + if (!thread->UpdateConsole(Keep {}, Keep {})) + // Try to update the console, but keep the existing cart. If that fails... return false; + // BIOS and firmware files are loaded, patched, and installed in UpdateConsole if (thread->NDS->NeedsDirectBoot()) return false; - /*if (NDSSave) delete NDSSave; - NDSSave = nullptr; - - CartType = -1; - BaseROMDir = ""; - BaseROMName = ""; - BaseAssetName = "";*/ - + InitFirmwareSaveManager(thread); thread->NDS->Reset(); SetBatteryLevels(*thread->NDS); SetDateTime(*thread->NDS); return true; } -u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent) +u32 DecompressROM(const u8* inContent, const u32 inSize, unique_ptr& outContent) { u64 realSize = ZSTD_getFrameContentSize(inContent, inSize); const u32 maxSize = 0x40000000; @@ -940,16 +939,15 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent) if (realSize != ZSTD_CONTENTSIZE_UNKNOWN) { - u8* realContent = new u8[realSize]; - u64 decompressed = ZSTD_decompress(realContent, realSize, inContent, inSize); + outContent = make_unique(realSize); + u64 decompressed = ZSTD_decompress(outContent.get(), realSize, inContent, inSize); if (ZSTD_isError(decompressed)) { - delete[] realContent; + outContent = nullptr; return 0; } - *outContent = realContent; return realSize; } else @@ -1005,8 +1003,8 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent) } while (inBuf.pos < inBuf.size); ZSTD_freeDStream(dStream); - *outContent = new u8[outBuf.pos]; - memcpy(*outContent, outBuf.dst, outBuf.pos); + outContent = make_unique(outBuf.pos); + memcpy(outContent.get(), outBuf.dst, outBuf.pos); ZSTD_freeDStream(dStream); free(outBuf.dst); @@ -1023,42 +1021,6 @@ void ClearBackupState() } } -// We want both the firmware object and the path that was used to load it, -// since we'll need to give it to the save manager later -pair, string> LoadFirmwareFromFile() -{ - string loadedpath; - unique_ptr firmware = nullptr; - string firmwarepath = Config::ConsoleType == 0 ? Config::FirmwarePath : Config::DSiFirmwarePath; - - Log(LogLevel::Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str()); - - string firmwareinstancepath = firmwarepath + Platform::InstanceFileSuffix(); - - loadedpath = firmwareinstancepath; - FileHandle* f = Platform::OpenLocalFile(firmwareinstancepath, FileMode::Read); - if (!f) - { - loadedpath = firmwarepath; - f = Platform::OpenLocalFile(firmwarepath, FileMode::Read); - } - - if (f) - { - firmware = make_unique(f); - if (!firmware->Buffer()) - { - Log(LogLevel::Warn, "Couldn't read firmware file!\n"); - firmware = nullptr; - loadedpath = ""; - } - - CloseFile(f); - } - - return std::make_pair(std::move(firmware), loadedpath); -} - pair, string> GenerateDefaultFirmware() { // Construct the default firmware... @@ -1068,7 +1030,7 @@ pair, string> GenerateDefaultFirmware() // Try to open the instanced Wi-fi settings, falling back to the regular Wi-fi settings if they don't exist. // We don't need to save the whole firmware, just the part that may actually change. - std::string wfcsettingspath = Platform::GetConfigString(ConfigEntry::WifiSettingsPath); + std::string wfcsettingspath = Config::WifiSettingsPath; settingspath = wfcsettingspath + Platform::InstanceFileSuffix(); FileHandle* f = Platform::OpenLocalFile(settingspath, FileMode::Read); if (!f) @@ -1116,6 +1078,36 @@ pair, string> GenerateDefaultFirmware() return std::make_pair(std::move(firmware), std::move(wfcsettingspath)); } +bool ParseMacAddress(void* data) +{ + const std::string& mac_in = Config::FirmwareMAC; + u8* mac_out = (u8*)data; + + int o = 0; + u8 tmp = 0; + for (int i = 0; i < 18; i++) + { + char c = mac_in[i]; + if (c == '\0') break; + + int n; + if (c >= '0' && c <= '9') n = c - '0'; + else if (c >= 'a' && c <= 'f') n = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') n = c - 'A' + 10; + else continue; + + if (!(o & 1)) + tmp = n; + else + mac_out[o >> 1] = n | (tmp << 4); + + o++; + if (o >= 12) return true; + } + + return false; +} + void CustomizeFirmware(Firmware& firmware) noexcept { auto& currentData = firmware.GetEffectiveUserData(); @@ -1174,7 +1166,7 @@ void CustomizeFirmware(Firmware& firmware) noexcept MacAddress configuredMac; - rep = Platform::GetConfigArray(Platform::Firm_MAC, &configuredMac); + rep = ParseMacAddress(&configuredMac); rep &= (configuredMac != MacAddress()); if (rep) @@ -1201,155 +1193,12 @@ void CustomizeFirmware(Firmware& firmware) noexcept firmware.UpdateChecksums(); } -static Platform::FileHandle* OpenNANDFile() noexcept -{ - std::string nandpath = Config::DSiNANDPath; - std::string instnand = nandpath + Platform::InstanceFileSuffix(); - - FileHandle* nandfile = Platform::OpenLocalFile(instnand, FileMode::ReadWriteExisting); - if ((!nandfile) && (Platform::InstanceID() > 0)) - { - FileHandle* orig = Platform::OpenLocalFile(nandpath, FileMode::Read); - if (!orig) - { - Log(LogLevel::Error, "Failed to open DSi NAND from %s\n", nandpath.c_str()); - return nullptr; - } - - QFile::copy(QString::fromStdString(nandpath), QString::fromStdString(instnand)); - - nandfile = Platform::OpenLocalFile(instnand, FileMode::ReadWriteExisting); - } - - return nandfile; -} - -bool InstallNAND(DSi& dsi) -{ - Platform::FileHandle* nandfile = OpenNANDFile(); - if (!nandfile) - return false; - - DSi_NAND::NANDImage nandImage(nandfile, &dsi.ARM7iBIOS[0x8308]); - if (!nandImage) - { - Log(LogLevel::Error, "Failed to parse DSi NAND\n"); - return false; - } - - // scoped so that mount isn't alive when we move the NAND image to DSi::NANDImage - { - auto mount = DSi_NAND::NANDMount(nandImage); - if (!mount) - { - Log(LogLevel::Error, "Failed to mount DSi NAND\n"); - return false; - } - - DSi_NAND::DSiFirmwareSystemSettings settings {}; - if (!mount.ReadUserData(settings)) - { - Log(LogLevel::Error, "Failed to read DSi NAND user data\n"); - return false; - } - - // override user settings, if needed - if (Config::FirmwareOverrideSettings) - { - // we store relevant strings as UTF-8, so we need to convert them to UTF-16 - auto converter = wstring_convert, char16_t>{}; - - // setting up username - std::u16string username = converter.from_bytes(Config::FirmwareUsername); - size_t usernameLength = std::min(username.length(), (size_t) 10); - memset(&settings.Nickname, 0, sizeof(settings.Nickname)); - memcpy(&settings.Nickname, username.data(), usernameLength * sizeof(char16_t)); - - // setting language - settings.Language = static_cast(Config::FirmwareLanguage); - - // setting up color - settings.FavoriteColor = Config::FirmwareFavouriteColour; - - // setting up birthday - settings.BirthdayMonth = Config::FirmwareBirthdayMonth; - settings.BirthdayDay = Config::FirmwareBirthdayDay; - - // setup message - std::u16string message = converter.from_bytes(Config::FirmwareMessage); - size_t messageLength = std::min(message.length(), (size_t) 26); - memset(&settings.Message, 0, sizeof(settings.Message)); - memcpy(&settings.Message, message.data(), messageLength * sizeof(char16_t)); - - // TODO: make other items configurable? - } - - // fix touchscreen coords - settings.TouchCalibrationADC1 = {0, 0}; - settings.TouchCalibrationPixel1 = {0, 0}; - settings.TouchCalibrationADC2 = {255 << 4, 191 << 4}; - settings.TouchCalibrationPixel2 = {255, 191}; - - settings.UpdateHash(); - - if (!mount.ApplyUserData(settings)) - { - Log(LogLevel::Error, "Failed to write patched DSi NAND user data\n"); - return false; - } - } - - dsi.NANDImage = std::make_unique(std::move(nandImage)); - return true; -} - -bool InstallFirmware(NDS& nds) -{ - FirmwareSave.reset(); - unique_ptr firmware; - string firmwarepath; - bool generated = false; - - if (Config::ExternalBIOSEnable) - { // If we want to try loading a firmware dump... - - tie(firmware, firmwarepath) = LoadFirmwareFromFile(); - if (!firmware) - { // Try to load the configured firmware dump. If that fails... - Log(LogLevel::Warn, "Firmware not found! Generating default firmware.\n"); - } - } - - if (!firmware) - { // If we haven't yet loaded firmware (either because the load failed or we want to use the default...) - tie(firmware, firmwarepath) = GenerateDefaultFirmware(); - } - - if (!firmware) - return false; - - if (Config::FirmwareOverrideSettings) - { - CustomizeFirmware(*firmware); - } - - FirmwareSave = std::make_unique(firmwarepath); - - return nds.SPI.GetFirmwareMem()->InstallFirmware(std::move(firmware)); -} - -bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) +// Loads ROM data without parsing it. Works for GBA and NDS ROMs. +bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u32& filelen, string& basepath, string& romname) noexcept { if (filepath.empty()) return false; - u8* filedata = nullptr; - u32 filelen; - - std::string basepath; - std::string romname; - - int num = filepath.count(); - if (num == 1) + if (int num = filepath.count(); num == 1) { // regular file @@ -1361,38 +1210,35 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) if (len > 0x40000000) { Platform::CloseFile(f); - delete[] filedata; return false; } Platform::FileRewind(f); - filedata = new u8[len]; - size_t nread = Platform::FileRead(filedata, (size_t)len, 1, f); + filedata = make_unique(len); + size_t nread = Platform::FileRead(filedata.get(), (size_t)len, 1, f); + Platform::CloseFile(f); if (nread != 1) { - Platform::CloseFile(f); - delete[] filedata; + filedata = nullptr; return false; } - Platform::CloseFile(f); filelen = (u32)len; if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst") { - u8* outContent = nullptr; - u32 decompressed = DecompressROM(filedata, len, &outContent); + filelen = DecompressROM(filedata.get(), len, filedata); - if (decompressed > 0) + if (filelen > 0) { - delete[] filedata; - filedata = outContent; - filelen = decompressed; filename = filename.substr(0, filename.length() - 4); } else { - delete[] filedata; + filedata = nullptr; + filelen = 0; + basepath = ""; + romname = ""; return false; } } @@ -1400,19 +1246,21 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) int pos = LastSep(filename); if(pos != -1) basepath = filename.substr(0, pos); + romname = filename.substr(pos+1); + return true; } #ifdef ARCHIVE_SUPPORT_ENABLED else if (num == 2) { // file inside archive - s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen); + s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), filedata, &filelen); if (lenread < 0) return false; if (!filedata) return false; if (lenread != filelen) { - delete[] filedata; + filedata = nullptr; return false; } @@ -1421,38 +1269,31 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) std::string std_romname = filepath.at(1).toStdString(); romname = std_romname.substr(LastSep(std_romname)+1); + return true; } #endif else return false; +} + +bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) +{ + unique_ptr filedata = nullptr; + u32 filelen; + std::string basepath; + std::string romname; + + if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) + return false; - if (NDSSave) delete NDSSave; NDSSave = nullptr; BaseROMDir = basepath; BaseROMName = romname; BaseAssetName = romname.substr(0, romname.rfind('.')); - emuthread->RecreateConsole(); - if (!InstallFirmware(*emuthread->NDS)) - { - return false; - } - - if (reset) - { - emuthread->NDS->EjectCart(); - LoadBIOSFiles(*emuthread->NDS); - if (Config::ConsoleType == 1) - InstallNAND(static_cast(*emuthread->NDS)); - - emuthread->NDS->Reset(); - SetBatteryLevels(*emuthread->NDS); - SetDateTime(*emuthread->NDS); - } - u32 savelen = 0; - u8* savedata = nullptr; + std::unique_ptr savedata = nullptr; std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); std::string origsav = savname; @@ -1465,36 +1306,56 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) savelen = (u32)Platform::FileLength(sav); FileRewind(sav); - savedata = new u8[savelen]; - FileRead(savedata, savelen, 1, sav); + savedata = std::make_unique(savelen); + FileRead(savedata.get(), savelen, 1, sav); CloseFile(sav); } - bool res = emuthread->NDS->LoadCart(filedata, filelen, savedata, savelen); - if (res && reset) + NDSCart::NDSCartArgs cartargs { + // Don't load the SD card itself yet, because we don't know if + // the ROM is homebrew or not. + // So this is the card we *would* load if the ROM were homebrew. + .SDCard = GetDLDISDCardArgs(), + .SRAM = std::move(savedata), + .SRAMLength = savelen, + }; + + auto cart = NDSCart::ParseROM(std::move(filedata), filelen, std::move(cartargs)); + if (!cart) + // If we couldn't parse the ROM... + return false; + + if (reset) { + if (!emuthread->UpdateConsole(std::move(cart), Keep {})) + return false; + + InitFirmwareSaveManager(emuthread); + emuthread->NDS->Reset(); + if (Config::DirectBoot || emuthread->NDS->NeedsDirectBoot()) - { + { // If direct boot is enabled or forced... emuthread->NDS->SetupDirectBoot(romname); } - } - if (res) + SetBatteryLevels(*emuthread->NDS); + SetDateTime(*emuthread->NDS); + } + else { - CartType = 0; - NDSSave = new SaveManager(savname); - - LoadCheats(*emuthread->NDS); + assert(emuthread->NDS != nullptr); + emuthread->NDS->SetNDSCart(std::move(cart)); } - if (savedata) delete[] savedata; - delete[] filedata; - return res; + CartType = 0; + NDSSave = std::make_unique(savname); + LoadCheats(*emuthread->NDS); + + return true; } void EjectCart(NDS& nds) { - if (NDSSave) delete NDSSave; NDSSave = nullptr; UnloadCheats(nds); @@ -1529,92 +1390,16 @@ QString CartLabel() bool LoadGBAROM(NDS& nds, QStringList filepath) { - if (Config::ConsoleType == 1) return false; - if (filepath.empty()) return false; + if (nds.ConsoleType == 1) return false; // DSi doesn't have a GBA slot - u8* filedata; + unique_ptr filedata = nullptr; u32 filelen; - std::string basepath; std::string romname; - int num = filepath.count(); - if (num == 1) - { - // regular file - - std::string filename = filepath.at(0).toStdString(); - FileHandle* f = Platform::OpenFile(filename, FileMode::Read); - if (!f) return false; - - long len = FileLength(f); - if (len > 0x40000000) - { - CloseFile(f); - return false; - } - - FileRewind(f); - filedata = new u8[len]; - size_t nread = FileRead(filedata, (size_t)len, 1, f); - if (nread != 1) - { - CloseFile(f); - delete[] filedata; - return false; - } - - CloseFile(f); - filelen = (u32)len; - - if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst") - { - u8* outContent = nullptr; - u32 decompressed = DecompressROM(filedata, len, &outContent); - - if (decompressed > 0) - { - delete[] filedata; - filedata = outContent; - filelen = decompressed; - filename = filename.substr(0, filename.length() - 4); - } - else - { - delete[] filedata; - return false; - } - } - - int pos = LastSep(filename); - basepath = filename.substr(0, pos); - romname = filename.substr(pos+1); - } -#ifdef ARCHIVE_SUPPORT_ENABLED - else if (num == 2) - { - // file inside archive - - s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen); - if (lenread < 0) return false; - if (!filedata) return false; - if (lenread != filelen) - { - delete[] filedata; - return false; - } - - std::string std_archivepath = filepath.at(0).toStdString(); - basepath = std_archivepath.substr(0, LastSep(std_archivepath)); - - std::string std_romname = filepath.at(1).toStdString(); - romname = std_romname.substr(LastSep(std_romname)+1); - } -#endif - else + if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) return false; - if (GBASave) delete GBASave; GBASave = nullptr; BaseGBAROMDir = basepath; @@ -1622,7 +1407,7 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) BaseGBAAssetName = romname.substr(0, romname.rfind('.')); u32 savelen = 0; - u8* savedata = nullptr; + std::unique_ptr savedata = nullptr; std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); std::string origsav = savname; @@ -1634,30 +1419,29 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) { savelen = (u32)FileLength(sav); - FileRewind(sav); - savedata = new u8[savelen]; - FileRead(savedata, savelen, 1, sav); + if (savelen > 0) + { + FileRewind(sav); + savedata = std::make_unique(savelen); + FileRead(savedata.get(), savelen, 1, sav); + } CloseFile(sav); } - bool res = nds.LoadGBACart(filedata, filelen, savedata, savelen); + auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen); + if (!cart) + return false; - if (res) - { - GBACartType = 0; - GBASave = new SaveManager(savname); - } - - if (savedata) delete[] savedata; - delete[] filedata; - return res; + nds.SetGBACart(std::move(cart)); + GBACartType = 0; + GBASave = std::make_unique(savname); + return true; } void LoadGBAAddon(NDS& nds, int type) { if (Config::ConsoleType == 1) return; - if (GBASave) delete GBASave; GBASave = nullptr; nds.LoadGBAAddon(type); @@ -1670,7 +1454,6 @@ void LoadGBAAddon(NDS& nds, int type) void EjectGBACart(NDS& nds) { - if (GBASave) delete GBASave; GBASave = nullptr; nds.EjectGBACart(); diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h index 2163a680..0b640c84 100644 --- a/src/frontend/qt_sdl/ROMManager.h +++ b/src/frontend/qt_sdl/ROMManager.h @@ -35,34 +35,43 @@ namespace melonDS class NDS; class DSi; class FATStorage; +class FATStorageArgs; } class EmuThread; namespace ROMManager { using namespace melonDS; -extern SaveManager* NDSSave; -extern SaveManager* GBASave; +extern std::unique_ptr NDSSave; +extern std::unique_ptr GBASave; extern std::unique_ptr FirmwareSave; QString VerifySetup(); void Reset(EmuThread* thread); -bool LoadBIOS(EmuThread* thread); + +/// Boots the emulated console into its system menu without starting a game. +bool BootToMenu(EmuThread* thread); void ClearBackupState(); +/// Returns the configured ARM9 BIOS loaded from disk, +/// the FreeBIOS if external BIOS is disabled and we're in NDS mode, +/// or nullopt if loading failed. std::optional> LoadARM9BIOS() noexcept; std::optional> LoadARM7BIOS() noexcept; std::optional> LoadDSiARM9BIOS() noexcept; std::optional> LoadDSiARM7BIOS() noexcept; +std::optional GetDSiSDCardArgs() noexcept; std::optional LoadDSiSDCard() noexcept; +std::optional GetDLDISDCardArgs() noexcept; +std::optional LoadDLDISDCard() noexcept; void CustomizeFirmware(Firmware& firmware) noexcept; Firmware GenerateFirmware(int type) noexcept; /// Loads and customizes a firmware image based on the values in Config std::optional LoadFirmware(int type) noexcept; /// Loads and customizes a NAND image based on the values in Config std::optional LoadNAND(const std::array& arm7ibios) noexcept; -bool InstallFirmware(NDS& nds); -bool InstallNAND(DSi& dsi); + +/// Inserts a ROM into the emulated console. bool LoadROM(EmuThread*, QStringList filepath, bool reset); void EjectCart(NDS& nds); bool CartInserted(); diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp new file mode 100644 index 00000000..d8af6624 --- /dev/null +++ b/src/frontend/qt_sdl/Screen.cpp @@ -0,0 +1,742 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#ifndef _WIN32 +#ifndef APPLE +#include +#endif +#endif + +#include "OpenGLSupport.h" +#include "duckstation/gl/context.h" + +#include "main.h" + +#include "NDS.h" +#include "GPU.h" +#include "GPU3D_Soft.h" +#include "GPU3D_OpenGL.h" +#include "Platform.h" +#include "Config.h" + +#include "main_shaders.h" + +#include "OSD.h" + +using namespace melonDS; + + +// TEMP +extern MainWindow* mainWindow; +extern EmuThread* emuThread; +extern bool RunningSomething; +extern int autoScreenSizing; + +extern int videoRenderer; +extern bool videoSettingsDirty; + + +ScreenHandler::ScreenHandler(QWidget* widget) +{ + widget->setMouseTracking(true); + widget->setAttribute(Qt::WA_AcceptTouchEvents); + QTimer* mouseTimer = setupMouseTimer(); + widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); +} + +ScreenHandler::~ScreenHandler() +{ + mouseTimer->stop(); + delete mouseTimer; +} + +void ScreenHandler::screenSetupLayout(int w, int h) +{ + int sizing = Config::ScreenSizing; + if (sizing == 3) sizing = autoScreenSizing; + + float aspectTop, aspectBot; + + for (auto ratio : aspectRatios) + { + if (ratio.id == Config::ScreenAspectTop) + aspectTop = ratio.ratio; + if (ratio.id == Config::ScreenAspectBot) + aspectBot = ratio.ratio; + } + + if (aspectTop == 0) + aspectTop = ((float) w / h) / (4.f / 3.f); + + if (aspectBot == 0) + aspectBot = ((float) w / h) / (4.f / 3.f); + + Frontend::SetupScreenLayout(w, h, + static_cast(Config::ScreenLayout), + static_cast(Config::ScreenRotation), + static_cast(sizing), + Config::ScreenGap, + Config::IntegerScaling != 0, + Config::ScreenSwap != 0, + aspectTop, + aspectBot); + + numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); +} + +QSize ScreenHandler::screenGetMinSize(int factor = 1) +{ + bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg + || Config::ScreenRotation == Frontend::screenRot_270Deg); + int gap = Config::ScreenGap * factor; + + int w = 256 * factor; + int h = 192 * factor; + + if (Config::ScreenSizing == Frontend::screenSizing_TopOnly + || Config::ScreenSizing == Frontend::screenSizing_BotOnly) + { + return QSize(w, h); + } + + if (Config::ScreenLayout == Frontend::screenLayout_Natural) + { + if (isHori) + return QSize(h+gap+h, w); + else + return QSize(w, h+gap+h); + } + else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) + { + if (isHori) + return QSize(h, w+gap+w); + else + return QSize(w, h+gap+h); + } + else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) + { + if (isHori) + return QSize(h+gap+h, w); + else + return QSize(w+gap+w, h); + } + else // hybrid + { + if (isHori) + return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); + else + return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); + } +} + +void ScreenHandler::screenOnMousePress(QMouseEvent* event) +{ + event->accept(); + if (event->button() != Qt::LeftButton) return; + + int x = event->pos().x(); + int y = event->pos().y(); + + if (Frontend::GetTouchCoords(x, y, false)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } +} + +void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) +{ + event->accept(); + if (event->button() != Qt::LeftButton) return; + + if (touching) + { + touching = false; + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + } +} + +void ScreenHandler::screenOnMouseMove(QMouseEvent* event) +{ + event->accept(); + + showCursor(); + + if (!(event->buttons() & Qt::LeftButton)) return; + if (!touching) return; + + int x = event->pos().x(); + int y = event->pos().y(); + + if (Frontend::GetTouchCoords(x, y, true)) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } +} + +void ScreenHandler::screenHandleTablet(QTabletEvent* event) +{ + event->accept(); + + switch(event->type()) + { + case QEvent::TabletPress: + case QEvent::TabletMove: + { + int x = event->x(); + int y = event->y(); + + if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } + } + break; + case QEvent::TabletRelease: + if (touching) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + touching = false; + } + break; + default: + break; + } +} + +void ScreenHandler::screenHandleTouch(QTouchEvent* event) +{ + event->accept(); + + switch(event->type()) + { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + if (event->touchPoints().length() > 0) + { + QPointF lastPosition = event->touchPoints().first().lastPos(); + int x = (int)lastPosition.x(); + int y = (int)lastPosition.y(); + + if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) + { + touching = true; + assert(emuThread->NDS != nullptr); + emuThread->NDS->TouchScreen(x, y); + } + } + break; + case QEvent::TouchEnd: + if (touching) + { + assert(emuThread->NDS != nullptr); + emuThread->NDS->ReleaseScreen(); + touching = false; + } + break; + default: + break; + } +} + +void ScreenHandler::showCursor() +{ + mainWindow->panelWidget->setCursor(Qt::ArrowCursor); + mouseTimer->start(); +} + +QTimer* ScreenHandler::setupMouseTimer() +{ + mouseTimer = new QTimer(); + mouseTimer->setSingleShot(true); + mouseTimer->setInterval(Config::MouseHideSeconds*1000); + mouseTimer->start(); + + return mouseTimer; +} + +ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) +{ + screen[0] = QImage(256, 192, QImage::Format_RGB32); + screen[1] = QImage(256, 192, QImage::Format_RGB32); + + screenTrans[0].reset(); + screenTrans[1].reset(); + + OSD::Init(false); +} + +ScreenPanelNative::~ScreenPanelNative() +{ + OSD::DeInit(); +} + +void ScreenPanelNative::setupScreenLayout() +{ + int w = width(); + int h = height(); + + screenSetupLayout(w, h); + + for (int i = 0; i < numScreens; i++) + { + float* mtx = screenMatrix[i]; + screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, + mtx[2], mtx[3], 0.f, + mtx[4], mtx[5], 1.f); + } +} + +void ScreenPanelNative::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + // fill background + painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); + + if (emuThread->emuIsActive()) + { + assert(emuThread->NDS != nullptr); + emuThread->FrontBufferLock.lock(); + int frontbuf = emuThread->FrontBuffer; + if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + { + emuThread->FrontBufferLock.unlock(); + return; + } + + memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); + memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); + emuThread->FrontBufferLock.unlock(); + + QRect screenrc(0, 0, 256, 192); + + for (int i = 0; i < numScreens; i++) + { + painter.setTransform(screenTrans[i]); + painter.drawImage(screenrc, screen[screenKind[i]]); + } + } + + OSD::Update(); + OSD::DrawNative(painter); +} + +void ScreenPanelNative::resizeEvent(QResizeEvent* event) +{ + setupScreenLayout(); +} + +void ScreenPanelNative::mousePressEvent(QMouseEvent* event) +{ + screenOnMousePress(event); +} + +void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event) +{ + screenOnMouseRelease(event); +} + +void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event) +{ + screenOnMouseMove(event); +} + +void ScreenPanelNative::tabletEvent(QTabletEvent* event) +{ + screenHandleTablet(event); +} + +bool ScreenPanelNative::event(QEvent* event) +{ + if (event->type() == QEvent::TouchBegin + || event->type() == QEvent::TouchEnd + || event->type() == QEvent::TouchUpdate) + { + screenHandleTouch((QTouchEvent*)event); + return true; + } + return QWidget::event(event); +} + +void ScreenPanelNative::onScreenLayoutChanged() +{ + setMinimumSize(screenGetMinSize()); + setupScreenLayout(); +} + + +ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QWidget(parent), ScreenHandler(this) +{ + setAutoFillBackground(false); + setAttribute(Qt::WA_NativeWindow, true); + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_PaintOnScreen, true); + setAttribute(Qt::WA_KeyCompression, false); + setFocusPolicy(Qt::StrongFocus); + setMinimumSize(screenGetMinSize()); +} + +ScreenPanelGL::~ScreenPanelGL() +{} + +bool ScreenPanelGL::createContext() +{ + std::optional windowInfo = getWindowInfo(); + std::array versionsToTry = { + GL::Context::Version{GL::Context::Profile::Core, 4, 3}, + GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; + if (windowInfo.has_value()) + { + glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); + glContext->DoneCurrent(); + } + + return glContext != nullptr; +} + +void ScreenPanelGL::setSwapInterval(int intv) +{ + if (!glContext) return; + + glContext->SetSwapInterval(intv); +} + +void ScreenPanelGL::initOpenGL() +{ + if (!glContext) return; + + glContext->MakeCurrent(); + + OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); + GLuint pid = screenShaderProgram[2]; + glBindAttribLocation(pid, 0, "vPosition"); + glBindAttribLocation(pid, 1, "vTexcoord"); + glBindFragDataLocation(pid, 0, "oColor"); + + OpenGL::LinkShaderProgram(screenShaderProgram); + + glUseProgram(pid); + glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); + + screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); + screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); + + // to prevent bleeding between both parts of the screen + // with bilinear filtering enabled + const int paddedHeight = 192*2+2; + const float padPixels = 1.f / paddedHeight; + + const float vertices[] = + { + 0.f, 0.f, 0.f, 0.f, + 0.f, 192.f, 0.f, 0.5f - padPixels, + 256.f, 192.f, 1.f, 0.5f - padPixels, + 0.f, 0.f, 0.f, 0.f, + 256.f, 192.f, 1.f, 0.5f - padPixels, + 256.f, 0.f, 1.f, 0.f, + + 0.f, 0.f, 0.f, 0.5f + padPixels, + 0.f, 192.f, 0.f, 1.f, + 256.f, 192.f, 1.f, 1.f, + 0.f, 0.f, 0.f, 0.5f + padPixels, + 256.f, 192.f, 1.f, 1.f, + 256.f, 0.f, 1.f, 0.5f + padPixels + }; + + glGenBuffers(1, &screenVertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glGenVertexArrays(1, &screenVertexArray); + glBindVertexArray(screenVertexArray); + glEnableVertexAttribArray(0); // position + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); + glEnableVertexAttribArray(1); // texcoord + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); + + glGenTextures(1, &screenTexture); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, screenTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + // fill the padding + u8 zeroData[256*4*4]; + memset(zeroData, 0, sizeof(zeroData)); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); + + OSD::Init(true); + + glContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); + transferLayout(); +} + +void ScreenPanelGL::deinitOpenGL() +{ + if (!glContext) return; + + glDeleteTextures(1, &screenTexture); + + glDeleteVertexArrays(1, &screenVertexArray); + glDeleteBuffers(1, &screenVertexBuffer); + + OpenGL::DeleteShaderProgram(screenShaderProgram); + + OSD::DeInit(); + + glContext->DoneCurrent(); + + lastScreenWidth = lastScreenHeight = -1; +} + +void ScreenPanelGL::drawScreenGL() +{ + if (!glContext) return; + if (!emuThread->NDS) return; + + int w = windowInfo.surface_width; + int h = windowInfo.surface_height; + float factor = windowInfo.surface_scale; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDisable(GL_DEPTH_TEST); + glDepthMask(false); + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, w, h); + + glUseProgram(screenShaderProgram[2]); + glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); + + int frontbuf = emuThread->FrontBuffer; + glActiveTexture(GL_TEXTURE0); + +#ifdef OGLRENDERER_ENABLED + if (emuThread->NDS->GPU.GetRenderer3D().Accelerated) + { + // hardware-accelerated render + static_cast(emuThread->NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf); + } + else +#endif + { + // regular render + glBindTexture(GL_TEXTURE_2D, screenTexture); + + if (emuThread->NDS->GPU.Framebuffer[frontbuf][0] && emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][0].get()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, + GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][1].get()); + } + } + + screenSettingsLock.lock(); + + GLint filter = this->filter ? GL_LINEAR : GL_NEAREST; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + + glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); + glBindVertexArray(screenVertexArray); + + for (int i = 0; i < numScreens; i++) + { + glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); + glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3); + } + + screenSettingsLock.unlock(); + + OSD::Update(); + OSD::DrawGL(w, h, factor); + + glContext->SwapBuffers(); +} + +qreal ScreenPanelGL::devicePixelRatioFromScreen() const +{ + const QScreen* screen_for_ratio = window()->windowHandle()->screen(); + if (!screen_for_ratio) + screen_for_ratio = QGuiApplication::primaryScreen(); + + return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); +} + +int ScreenPanelGL::scaledWindowWidth() const +{ + return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); +} + +int ScreenPanelGL::scaledWindowHeight() const +{ + return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); +} + +std::optional ScreenPanelGL::getWindowInfo() +{ + WindowInfo wi; + + // Windows and Apple are easy here since there's no display connection. + #if defined(_WIN32) + wi.type = WindowInfo::Type::Win32; + wi.window_handle = reinterpret_cast(winId()); + #elif defined(__APPLE__) + wi.type = WindowInfo::Type::MacOS; + wi.window_handle = reinterpret_cast(winId()); + #else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + const QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("xcb")) + { + wi.type = WindowInfo::Type::X11; + wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); + wi.window_handle = reinterpret_cast(winId()); + } + else if (platform_name == QStringLiteral("wayland")) + { + wi.type = WindowInfo::Type::Wayland; + QWindow* handle = windowHandle(); + if (handle == nullptr) + return std::nullopt; + + wi.display_connection = pni->nativeResourceForWindow("display", handle); + wi.window_handle = pni->nativeResourceForWindow("surface", handle); + } + else + { + qCritical() << "Unknown PNI platform " << platform_name; + return std::nullopt; + } + #endif + + wi.surface_width = static_cast(scaledWindowWidth()); + wi.surface_height = static_cast(scaledWindowHeight()); + wi.surface_scale = static_cast(devicePixelRatioFromScreen()); + + return wi; +} + + +QPaintEngine* ScreenPanelGL::paintEngine() const +{ + return nullptr; +} + +void ScreenPanelGL::setupScreenLayout() +{ + int w = width(); + int h = height(); + + screenSetupLayout(w, h); + transferLayout(); +} + +void ScreenPanelGL::resizeEvent(QResizeEvent* event) +{ + setupScreenLayout(); + + QWidget::resizeEvent(event); +} + +void ScreenPanelGL::mousePressEvent(QMouseEvent* event) +{ + screenOnMousePress(event); +} + +void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event) +{ + screenOnMouseRelease(event); +} + +void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event) +{ + screenOnMouseMove(event); +} + +void ScreenPanelGL::tabletEvent(QTabletEvent* event) +{ + screenHandleTablet(event); +} + +bool ScreenPanelGL::event(QEvent* event) +{ + if (event->type() == QEvent::TouchBegin + || event->type() == QEvent::TouchEnd + || event->type() == QEvent::TouchUpdate) + { + screenHandleTouch((QTouchEvent*)event); + return true; + } + return QWidget::event(event); +} + +void ScreenPanelGL::transferLayout() +{ + std::optional windowInfo = getWindowInfo(); + if (windowInfo.has_value()) + { + screenSettingsLock.lock(); + + if (lastScreenWidth != windowInfo->surface_width || lastScreenHeight != windowInfo->surface_height) + { + if (glContext) + glContext->ResizeSurface(windowInfo->surface_width, windowInfo->surface_height); + lastScreenWidth = windowInfo->surface_width; + lastScreenHeight = windowInfo->surface_height; + } + + this->filter = Config::ScreenFilter; + this->windowInfo = *windowInfo; + + screenSettingsLock.unlock(); + } +} + +void ScreenPanelGL::onScreenLayoutChanged() +{ + setMinimumSize(screenGetMinSize()); + setupScreenLayout(); +} diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h new file mode 100644 index 00000000..b11df29b --- /dev/null +++ b/src/frontend/qt_sdl/Screen.h @@ -0,0 +1,172 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef SCREEN_H +#define SCREEN_H + +#include "glad/glad.h" +#include "FrontendUtil.h" +#include "duckstation/gl/context.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class EmuThread; + + +const struct { int id; float ratio; const char* label; } aspectRatios[] = +{ + { 0, 1, "4:3 (native)" }, + { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, + { 1, (16.f / 9) / (4.f / 3), "16:9" }, + { 2, (21.f / 9) / (4.f / 3), "21:9" }, + { 3, 0, "window" } +}; +constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); + + +class ScreenHandler +{ + Q_GADGET + +public: + ScreenHandler(QWidget* widget); + virtual ~ScreenHandler(); + QTimer* setupMouseTimer(); + void updateMouseTimer(); + QTimer* mouseTimer; + QSize screenGetMinSize(int factor); + +protected: + void screenSetupLayout(int w, int h); + + void screenOnMousePress(QMouseEvent* event); + void screenOnMouseRelease(QMouseEvent* event); + void screenOnMouseMove(QMouseEvent* event); + + void screenHandleTablet(QTabletEvent* event); + void screenHandleTouch(QTouchEvent* event); + + float screenMatrix[Frontend::MaxScreenTransforms][6]; + int screenKind[Frontend::MaxScreenTransforms]; + int numScreens; + + bool touching = false; + + void showCursor(); +}; + + +class ScreenPanelNative : public QWidget, public ScreenHandler +{ + Q_OBJECT + +public: + explicit ScreenPanelNative(QWidget* parent); + virtual ~ScreenPanelNative(); + +protected: + void paintEvent(QPaintEvent* event) override; + + void resizeEvent(QResizeEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + + void tabletEvent(QTabletEvent* event) override; + bool event(QEvent* event) override; +private slots: + void onScreenLayoutChanged(); + +private: + void setupScreenLayout(); + + QImage screen[2]; + QTransform screenTrans[Frontend::MaxScreenTransforms]; +}; + + +class ScreenPanelGL : public QWidget, public ScreenHandler +{ + Q_OBJECT + +public: + explicit ScreenPanelGL(QWidget* parent); + virtual ~ScreenPanelGL(); + + std::optional getWindowInfo(); + + bool createContext(); + + void setSwapInterval(int intv); + + void initOpenGL(); + void deinitOpenGL(); + void drawScreenGL(); + + GL::Context* getContext() { return glContext.get(); } + + void transferLayout(); +protected: + + qreal devicePixelRatioFromScreen() const; + int scaledWindowWidth() const; + int scaledWindowHeight() const; + + QPaintEngine* paintEngine() const override; + + void resizeEvent(QResizeEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + + void tabletEvent(QTabletEvent* event) override; + bool event(QEvent* event) override; + +private slots: + void onScreenLayoutChanged(); + +private: + void setupScreenLayout(); + + std::unique_ptr glContext; + + GLuint screenVertexBuffer, screenVertexArray; + GLuint screenTexture; + GLuint screenShaderProgram[3]; + GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; + + QMutex screenSettingsLock; + WindowInfo windowInfo; + bool filter; + + int lastScreenWidth = -1, lastScreenHeight = -1; +}; + +#endif // SCREEN_H + diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp new file mode 100644 index 00000000..7b67ec52 --- /dev/null +++ b/src/frontend/qt_sdl/Window.cpp @@ -0,0 +1,2078 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#include +#include +#ifndef APPLE +#include +#endif +#endif + +#include "main.h" +#include "Input.h" +#include "CheatsDialog.h" +#include "DateTimeDialog.h" +#include "EmuSettingsDialog.h" +#include "InputConfig/InputConfigDialog.h" +#include "VideoSettingsDialog.h" +#include "CameraSettingsDialog.h" +#include "AudioSettingsDialog.h" +#include "FirmwareSettingsDialog.h" +#include "PathSettingsDialog.h" +#include "MPSettingsDialog.h" +#include "WifiSettingsDialog.h" +#include "InterfaceSettingsDialog.h" +#include "ROMInfoDialog.h" +#include "RAMInfoDialog.h" +#include "TitleManagerDialog.h" +#include "PowerManagement/PowerManagementDialog.h" +#include "AudioInOut.h" + +#include "Platform.h" +#include "Config.h" +#include "Savestate.h" +#include "LocalMP.h" + +//#include "main_shaders.h" + +#include "ROMManager.h" +#include "ArchiveUtil.h" +#include "CameraManager.h" + +#include "OSD.h" + +using namespace melonDS; + +// TEMP +extern MainWindow* mainWindow; +extern EmuThread* emuThread; +extern bool RunningSomething; +extern QString NdsRomMimeType; +extern QStringList NdsRomExtensions; +extern QString GbaRomMimeType; +extern QStringList GbaRomExtensions; +extern QStringList ArchiveMimeTypes; +extern QStringList ArchiveExtensions; +/*static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs); +static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames); +static bool NdsRomByExtension(const QString& filename); +static bool GbaRomByExtension(const QString& filename); +static bool SupportedArchiveByExtension(const QString& filename); +static bool NdsRomByMimetype(const QMimeType& mimetype); +static bool GbaRomByMimetype(const QMimeType& mimetype); +static bool SupportedArchiveByMimetype(const QMimeType& mimetype); +static bool ZstdNdsRomByExtension(const QString& filename); +static bool ZstdGbaRomByExtension(const QString& filename); +static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive);*/ + +extern CameraManager* camManager[2]; +extern bool camStarted[2]; + +extern int videoRenderer; +extern bool videoSettingsDirty; + + +// AAAAAAA +static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) +{ + return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) { + return filename.endsWith(ext, cs); + }); +} + +static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames) +{ + return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) { + return mimetype.inherits(superTypeName); + }); +} + + +static bool NdsRomByExtension(const QString& filename) +{ + return FileExtensionInList(filename, NdsRomExtensions); +} + +static bool GbaRomByExtension(const QString& filename) +{ + return FileExtensionInList(filename, GbaRomExtensions); +} + +static bool SupportedArchiveByExtension(const QString& filename) +{ + return FileExtensionInList(filename, ArchiveExtensions); +} + + +static bool NdsRomByMimetype(const QMimeType& mimetype) +{ + return mimetype.inherits(NdsRomMimeType); +} + +static bool GbaRomByMimetype(const QMimeType& mimetype) +{ + return mimetype.inherits(GbaRomMimeType); +} + +static bool SupportedArchiveByMimetype(const QMimeType& mimetype) +{ + return MimeTypeInList(mimetype, ArchiveMimeTypes); +} + +static bool ZstdNdsRomByExtension(const QString& filename) +{ + return filename.endsWith(".zst", Qt::CaseInsensitive) && + NdsRomByExtension(filename.left(filename.size() - 4)); +} + +static bool ZstdGbaRomByExtension(const QString& filename) +{ + return filename.endsWith(".zst", Qt::CaseInsensitive) && + GbaRomByExtension(filename.left(filename.size() - 4)); +} + +static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive = false) +{ + if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename)) + return true; + + if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename)) + return true; + + const auto matchmode = insideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchmode); + return NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype) || SupportedArchiveByMimetype(mimetype); +} + + +#ifndef _WIN32 +static int signalFd[2]; +QSocketNotifier *signalSn; + +static void signalHandler(int) +{ + char a = 1; + write(signalFd[0], &a, sizeof(a)); +} +#endif + +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) +{ +#ifndef _WIN32 + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) + { + qFatal("Couldn't create socketpair"); + } + + signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); + connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); + + struct sigaction sa; + + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_flags |= SA_RESTART; + sigaction(SIGINT, &sa, 0); +#endif + + oldW = Config::WindowWidth; + oldH = Config::WindowHeight; + oldMax = Config::WindowMaximized; + + setWindowTitle("melonDS " MELONDS_VERSION); + setAttribute(Qt::WA_DeleteOnClose); + setAcceptDrops(true); + setFocusPolicy(Qt::ClickFocus); + + int inst = Platform::InstanceID(); + + QMenuBar* menubar = new QMenuBar(); + { + QMenu* menu = menubar->addMenu("File"); + + actOpenROM = menu->addAction("Open ROM..."); + connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); + actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); + + /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); + connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); + actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ + + recentMenu = menu->addMenu("Open recent"); + for (int i = 0; i < 10; ++i) + { + std::string item = Config::RecentROMList[i]; + if (!item.empty()) + recentFileList.push_back(QString::fromStdString(item)); + } + updateRecentFilesMenu(); + + //actBootFirmware = menu->addAction("Launch DS menu"); + actBootFirmware = menu->addAction("Boot firmware"); + connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); + + menu->addSeparator(); + + actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel()); + actCurrentCart->setEnabled(false); + + actInsertCart = menu->addAction("Insert cart..."); + connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); + + actEjectCart = menu->addAction("Eject cart"); + connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); + + menu->addSeparator(); + + actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel()); + actCurrentGBACart->setEnabled(false); + + actInsertGBACart = menu->addAction("Insert ROM cart..."); + connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); + + { + QMenu* submenu = menu->addMenu("Insert add-on cart"); + + actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); + actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); + connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); + } + + actEjectGBACart = menu->addAction("Eject cart"); + connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); + + menu->addSeparator(); + + actImportSavefile = menu->addAction("Import savefile"); + connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); + + menu->addSeparator(); + + { + QMenu* submenu = menu->addMenu("Save state"); + + for (int i = 1; i < 9; i++) + { + actSaveState[i] = submenu->addAction(QString("%1").arg(i)); + actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1))); + actSaveState[i]->setData(QVariant(i)); + connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); + } + + actSaveState[0] = submenu->addAction("File..."); + actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); + actSaveState[0]->setData(QVariant(0)); + connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); + } + { + QMenu* submenu = menu->addMenu("Load state"); + + for (int i = 1; i < 9; i++) + { + actLoadState[i] = submenu->addAction(QString("%1").arg(i)); + actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1)); + actLoadState[i]->setData(QVariant(i)); + connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); + } + + actLoadState[0] = submenu->addAction("File..."); + actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); + actLoadState[0]->setData(QVariant(0)); + connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); + } + + actUndoStateLoad = menu->addAction("Undo state load"); + actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); + connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); + + menu->addSeparator(); + + actQuit = menu->addAction("Quit"); + connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); + actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); + } + { + QMenu* menu = menubar->addMenu("System"); + + actPause = menu->addAction("Pause"); + actPause->setCheckable(true); + connect(actPause, &QAction::triggered, this, &MainWindow::onPause); + + actReset = menu->addAction("Reset"); + connect(actReset, &QAction::triggered, this, &MainWindow::onReset); + + actStop = menu->addAction("Stop"); + connect(actStop, &QAction::triggered, this, &MainWindow::onStop); + + actFrameStep = menu->addAction("Frame step"); + connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); + + menu->addSeparator(); + + actPowerManagement = menu->addAction("Power management"); + connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); + + actDateTime = menu->addAction("Date and time"); + connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); + + menu->addSeparator(); + + actEnableCheats = menu->addAction("Enable cheats"); + actEnableCheats->setCheckable(true); + connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); + + //if (inst == 0) + { + actSetupCheats = menu->addAction("Setup cheat codes"); + actSetupCheats->setMenuRole(QAction::NoRole); + connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); + + menu->addSeparator(); + actROMInfo = menu->addAction("ROM info"); + connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); + + actRAMInfo = menu->addAction("RAM search"); + connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); + + actTitleManager = menu->addAction("Manage DSi titles"); + connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); + } + + { + menu->addSeparator(); + QMenu* submenu = menu->addMenu("Multiplayer"); + + actMPNewInstance = submenu->addAction("Launch new instance"); + connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); + } + } + { + QMenu* menu = menubar->addMenu("Config"); + + actEmuSettings = menu->addAction("Emu settings"); + connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + +#ifdef __APPLE__ + actPreferences = menu->addAction("Preferences..."); + connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); + actPreferences->setMenuRole(QAction::PreferencesRole); +#endif + + actInputConfig = menu->addAction("Input and hotkeys"); + connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); + + actVideoSettings = menu->addAction("Video settings"); + connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); + + actCameraSettings = menu->addAction("Camera settings"); + connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); + + actAudioSettings = menu->addAction("Audio settings"); + connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); + + actMPSettings = menu->addAction("Multiplayer settings"); + connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); + + actWifiSettings = menu->addAction("Wifi settings"); + connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); + + actFirmwareSettings = menu->addAction("Firmware settings"); + connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); + + actInterfaceSettings = menu->addAction("Interface settings"); + connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); + + actPathSettings = menu->addAction("Path settings"); + connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); + + { + QMenu* submenu = menu->addMenu("Savestate settings"); + + actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); + actSavestateSRAMReloc->setCheckable(true); + connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); + } + + menu->addSeparator(); + + { + QMenu* submenu = menu->addMenu("Screen size"); + + for (int i = 0; i < 4; i++) + { + int data = i+1; + actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); + actScreenSize[i]->setData(QVariant(data)); + connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); + } + } + { + QMenu* submenu = menu->addMenu("Screen rotation"); + grpScreenRotation = new QActionGroup(submenu); + + for (int i = 0; i < Frontend::screenRot_MAX; i++) + { + int data = i*90; + actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); + actScreenRotation[i]->setActionGroup(grpScreenRotation); + actScreenRotation[i]->setData(QVariant(i)); + actScreenRotation[i]->setCheckable(true); + } + + connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); + } + { + QMenu* submenu = menu->addMenu("Screen gap"); + grpScreenGap = new QActionGroup(submenu); + + const int screengap[] = {0, 1, 8, 64, 90, 128}; + + for (int i = 0; i < 6; i++) + { + int data = screengap[i]; + actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); + actScreenGap[i]->setActionGroup(grpScreenGap); + actScreenGap[i]->setData(QVariant(data)); + actScreenGap[i]->setCheckable(true); + } + + connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); + } + { + QMenu* submenu = menu->addMenu("Screen layout"); + grpScreenLayout = new QActionGroup(submenu); + + const char* screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; + + for (int i = 0; i < Frontend::screenLayout_MAX; i++) + { + actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); + actScreenLayout[i]->setActionGroup(grpScreenLayout); + actScreenLayout[i]->setData(QVariant(i)); + actScreenLayout[i]->setCheckable(true); + } + + connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); + + submenu->addSeparator(); + + actScreenSwap = submenu->addAction("Swap screens"); + actScreenSwap->setCheckable(true); + connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); + } + { + QMenu* submenu = menu->addMenu("Screen sizing"); + grpScreenSizing = new QActionGroup(submenu); + + const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; + + for (int i = 0; i < Frontend::screenSizing_MAX; i++) + { + actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); + actScreenSizing[i]->setActionGroup(grpScreenSizing); + actScreenSizing[i]->setData(QVariant(i)); + actScreenSizing[i]->setCheckable(true); + } + + connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); + + submenu->addSeparator(); + + actIntegerScaling = submenu->addAction("Force integer scaling"); + actIntegerScaling->setCheckable(true); + connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); + } + { + QMenu* submenu = menu->addMenu("Aspect ratio"); + grpScreenAspectTop = new QActionGroup(submenu); + grpScreenAspectBot = new QActionGroup(submenu); + actScreenAspectTop = new QAction*[AspectRatiosNum]; + actScreenAspectBot = new QAction*[AspectRatiosNum]; + + for (int i = 0; i < 2; i++) + { + QActionGroup* group = grpScreenAspectTop; + QAction** actions = actScreenAspectTop; + + if (i == 1) + { + group = grpScreenAspectBot; + submenu->addSeparator(); + actions = actScreenAspectBot; + } + + for (int j = 0; j < AspectRatiosNum; j++) + { + auto ratio = aspectRatios[j]; + QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); + actions[j] = submenu->addAction(label); + actions[j]->setActionGroup(group); + actions[j]->setData(QVariant(ratio.id)); + actions[j]->setCheckable(true); + } + + connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); + } + } + + actScreenFiltering = menu->addAction("Screen filtering"); + actScreenFiltering->setCheckable(true); + connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); + + actShowOSD = menu->addAction("Show OSD"); + actShowOSD->setCheckable(true); + connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); + + menu->addSeparator(); + + actLimitFramerate = menu->addAction("Limit framerate"); + actLimitFramerate->setCheckable(true); + connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); + + actAudioSync = menu->addAction("Audio sync"); + actAudioSync->setCheckable(true); + connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); + } + setMenuBar(menubar); + + resize(Config::WindowWidth, Config::WindowHeight); + + if (Config::FirmwareUsername == "Arisotura") + actMPNewInstance->setText("Fart"); + +#ifdef Q_OS_MAC + QPoint screenCenter = screen()->availableGeometry().center(); + QRect frameGeo = frameGeometry(); + frameGeo.moveCenter(screenCenter); + move(frameGeo.topLeft()); +#endif + + if (oldMax) + showMaximized(); + else + show(); + + createScreenPanel(); + + actEjectCart->setEnabled(false); + actEjectGBACart->setEnabled(false); + + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(false); + } + + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + actImportSavefile->setEnabled(false); + + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); + + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); + + actSetupCheats->setEnabled(false); + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); + + actEnableCheats->setChecked(Config::EnableCheats); + + actROMInfo->setEnabled(false); + actRAMInfo->setEnabled(false); + + actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM); + + actScreenRotation[Config::ScreenRotation]->setChecked(true); + + for (int i = 0; i < 6; i++) + { + if (actScreenGap[i]->data().toInt() == Config::ScreenGap) + { + actScreenGap[i]->setChecked(true); + break; + } + } + + actScreenLayout[Config::ScreenLayout]->setChecked(true); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + actIntegerScaling->setChecked(Config::IntegerScaling); + + actScreenSwap->setChecked(Config::ScreenSwap); + + for (int i = 0; i < AspectRatiosNum; i++) + { + if (Config::ScreenAspectTop == aspectRatios[i].id) + actScreenAspectTop[i]->setChecked(true); + if (Config::ScreenAspectBot == aspectRatios[i].id) + actScreenAspectBot[i]->setChecked(true); + } + + actScreenFiltering->setChecked(Config::ScreenFilter); + actShowOSD->setChecked(Config::ShowOSD); + + actLimitFramerate->setChecked(Config::LimitFPS); + actAudioSync->setChecked(Config::AudioSync); + + if (inst > 0) + { + actEmuSettings->setEnabled(false); + actVideoSettings->setEnabled(false); + actMPSettings->setEnabled(false); + actWifiSettings->setEnabled(false); + actInterfaceSettings->setEnabled(false); + +#ifdef __APPLE__ + actPreferences->setEnabled(false); +#endif // __APPLE__ + } +} + +MainWindow::~MainWindow() +{ + delete[] actScreenAspectTop; + delete[] actScreenAspectBot; +} + +void MainWindow::osdAddMessage(unsigned int color, const char* fmt, ...) +{ + if (fmt == nullptr) + return; + + char msg[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(msg, 1024, fmt, args); + va_end(args); + + OSD::AddMessage(color, msg); +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + if (hasOGL) + { + // we intentionally don't unpause here + emuThread->emuPause(); + emuThread->deinitContext(); + } + + QMainWindow::closeEvent(event); +} + +void MainWindow::createScreenPanel() +{ + hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + + if (hasOGL) + { + ScreenPanelGL* panelGL = new ScreenPanelGL(this); + panelGL->show(); + + panel = panelGL; + panelWidget = panelGL; + + panelGL->createContext(); + } + + if (!hasOGL) + { + ScreenPanelNative* panelNative = new ScreenPanelNative(this); + panel = panelNative; + panelWidget = panelNative; + panelWidget->show(); + } + setCentralWidget(panelWidget); + + actScreenFiltering->setEnabled(hasOGL); + + connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); + emit screenLayoutChange(); +} + +GL::Context* MainWindow::getOGLContext() +{ + if (!hasOGL) return nullptr; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->getContext(); +} + +/*void MainWindow::initOpenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->initOpenGL(); +} + +void MainWindow::deinitOpenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->deinitOpenGL(); +} + +void MainWindow::drawScreenGL() +{ + if (!hasOGL) return; + + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->drawScreenGL(); +}*/ + +void MainWindow::resizeEvent(QResizeEvent* event) +{ + int w = event->size().width(); + int h = event->size().height(); + + if (!isFullScreen()) + { + // this is ugly + // thing is, when maximizing the window, we first receive the resizeEvent + // with a new size matching the screen, then the changeEvent telling us that + // the maximized flag was updated + oldW = Config::WindowWidth; + oldH = Config::WindowHeight; + oldMax = isMaximized(); + + Config::WindowWidth = w; + Config::WindowHeight = h; + } +} + +void MainWindow::changeEvent(QEvent* event) +{ + if (isMaximized() && !oldMax) + { + Config::WindowWidth = oldW; + Config::WindowHeight = oldH; + } + + Config::WindowMaximized = isMaximized() ? 1:0; +} + +void MainWindow::keyPressEvent(QKeyEvent* event) +{ + if (event->isAutoRepeat()) return; + + // TODO!! REMOVE ME IN RELEASE BUILDS!! + //if (event->key() == Qt::Key_F11) NDS::debug(0); + + Input::KeyPress(event); +} + +void MainWindow::keyReleaseEvent(QKeyEvent* event) +{ + if (event->isAutoRepeat()) return; + + Input::KeyRelease(event); +} + + +void MainWindow::dragEnterEvent(QDragEnterEvent* event) +{ + if (!event->mimeData()->hasUrls()) return; + + QList urls = event->mimeData()->urls(); + if (urls.count() > 1) return; // not handling more than one file at once + + QString filename = urls.at(0).toLocalFile(); + + if (FileIsSupportedFiletype(filename)) + event->acceptProposedAction(); +} + +void MainWindow::dropEvent(QDropEvent* event) +{ + if (!event->mimeData()->hasUrls()) return; + + QList urls = event->mimeData()->urls(); + if (urls.count() > 1) return; // not handling more than one file at once + + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + const QString filename = file.last(); + const bool romInsideArchive = file.size() > 1; + const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode); + + bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype); + bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype); + isNdsRom |= ZstdNdsRomByExtension(filename); + isGbaRom |= ZstdGbaRomByExtension(filename); + + if (isNdsRom) + { + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); + emuThread->emuUnpause(); + return; + } + + const QString barredFilename = file.join('|'); + recentFileList.removeAll(barredFilename); + recentFileList.prepend(barredFilename); + updateRecentFilesMenu(); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); + + updateCartInserted(false); + } + else if (isGbaRom) + { + if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(true); + } + else + { + QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM."); + emuThread->emuUnpause(); + return; + } +} + +void MainWindow::focusInEvent(QFocusEvent* event) +{ + AudioInOut::AudioMute(mainWindow); +} + +void MainWindow::focusOutEvent(QFocusEvent* event) +{ + AudioInOut::AudioMute(mainWindow); +} + +void MainWindow::onAppStateChanged(Qt::ApplicationState state) +{ + if (state == Qt::ApplicationInactive) + { + if (Config::PauseLostFocus && emuThread->emuIsRunning()) + emuThread->emuPause(); + } + else if (state == Qt::ApplicationActive) + { + if (Config::PauseLostFocus && !pausedManually) + emuThread->emuUnpause(); + } +} + +bool MainWindow::verifySetup() +{ + QString res = ROMManager::VerifySetup(); + if (!res.isEmpty()) + { + QMessageBox::critical(this, "melonDS", res); + return false; + } + + return true; +} + +bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) +{ + if (!verifySetup()) + { + return false; + } + + bool gbaloaded = false; + if (!gbafile.isEmpty()) + { + if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); + return false; + } + + gbaloaded = true; + } + + bool ndsloaded = false; + if (!file.isEmpty()) + { + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + return false; + } + recentFileList.removeAll(file.join("|")); + recentFileList.prepend(file.join("|")); + updateRecentFilesMenu(); + ndsloaded = true; + } + + if (boot) + { + if (ndsloaded) + { + emuThread->NDS->Start(); + emuThread->emuRun(); + } + else + { + onBootFirmware(); + } + } + + updateCartInserted(false); + + if (gbaloaded) + { + updateCartInserted(true); + } + + return true; +} + +QStringList MainWindow::splitArchivePath(const QString& filename, bool useMemberSyntax) +{ + if (filename.isEmpty()) return {}; + +#ifdef ARCHIVE_SUPPORT_ENABLED + if (useMemberSyntax) + { + const QStringList filenameParts = filename.split('|'); + if (filenameParts.size() > 2) + { + QMessageBox::warning(this, "melonDS", "This path contains too many '|'."); + return {}; + } + + if (filenameParts.size() == 2) + { + const QString archive = filenameParts.at(0); + if (!QFileInfo(archive).exists()) + { + QMessageBox::warning(this, "melonDS", "This archive does not exist."); + return {}; + } + + const QString subfile = filenameParts.at(1); + if (!Archive::ListArchive(archive).contains(subfile)) + { + QMessageBox::warning(this, "melonDS", "This archive does not contain the desired file."); + return {}; + } + + return filenameParts; + } + } +#endif + + if (!QFileInfo(filename).exists()) + { + QMessageBox::warning(this, "melonDS", "This ROM file does not exist."); + return {}; + } + +#ifdef ARCHIVE_SUPPORT_ENABLED + if (SupportedArchiveByExtension(filename) + || SupportedArchiveByMimetype(QMimeDatabase().mimeTypeForFile(filename))) + { + const QString subfile = pickFileFromArchive(filename); + if (subfile.isEmpty()) + return {}; + + return { filename, subfile }; + } +#endif + + return { filename }; +} + +QString MainWindow::pickFileFromArchive(QString archiveFileName) +{ + QVector archiveROMList = Archive::ListArchive(archiveFileName); + + if (archiveROMList.size() <= 1) + { + if (!archiveROMList.isEmpty() && archiveROMList.at(0) == "OK") + QMessageBox::warning(this, "melonDS", "This archive is empty."); + else + QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions."); + return QString(); + } + + archiveROMList.removeFirst(); + + const auto notSupportedRom = [&](const auto& filename){ + if (NdsRomByExtension(filename) || GbaRomByExtension(filename)) + return false; + const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension); + return !(NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype)); + }; + + archiveROMList.erase(std::remove_if(archiveROMList.begin(), archiveROMList.end(), notSupportedRom), + archiveROMList.end()); + + if (archiveROMList.isEmpty()) + { + QMessageBox::warning(this, "melonDS", "This archive does not contain any supported ROMs."); + return QString(); + } + + if (archiveROMList.size() == 1) + return archiveROMList.first(); + + bool ok; + const QString toLoad = QInputDialog::getItem( + this, "melonDS", + "This archive contains multiple files. Select which ROM you want to load.", + archiveROMList.toList(), 0, false, &ok + ); + + if (ok) return toLoad; + + // User clicked on cancel + + return QString(); +} + +QStringList MainWindow::pickROM(bool gba) +{ + const QString console = gba ? "GBA" : "DS"; + const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; + + QString rawROMs = romexts.join(" *"); + QString extraFilters = ";;" + console + " ROMs (*" + rawROMs; + QString allROMs = rawROMs; + + QString zstdROMs = "*" + romexts.join(".zst *") + ".zst"; + extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")"; + allROMs += " " + zstdROMs; + +#ifdef ARCHIVE_SUPPORT_ENABLED + QString archives = "*" + ArchiveExtensions.join(" *"); + extraFilters += ";;Archives (" + archives + ")"; + allROMs += " " + archives; +#endif + extraFilters += ";;All files (*.*)"; + + const QString filename = QFileDialog::getOpenFileName( + this, "Open " + console + " ROM", + QString::fromStdString(Config::LastROMFolder), + "All supported files (*" + allROMs + ")" + extraFilters + ); + + if (filename.isEmpty()) return {}; + + Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); + return splitArchivePath(filename, false); +} + +void MainWindow::updateCartInserted(bool gba) +{ + bool inserted; + if (gba) + { + inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + actEjectGBACart->setEnabled(inserted); + } + else + { + inserted = ROMManager::CartInserted(); + actCurrentCart->setText("DS slot: " + ROMManager::CartLabel()); + actEjectCart->setEnabled(inserted); + actImportSavefile->setEnabled(inserted); + actSetupCheats->setEnabled(inserted); + actROMInfo->setEnabled(inserted); + actRAMInfo->setEnabled(inserted); + } +} + +void MainWindow::onOpenFile() +{ + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + QStringList file = pickROM(false); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + QString filename = file.join('|'); + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); + + updateCartInserted(false); +} + +void MainWindow::onClearRecentFiles() +{ + recentFileList.clear(); + for (int i = 0; i < 10; i++) + Config::RecentROMList[i] = ""; + updateRecentFilesMenu(); +} + +void MainWindow::updateRecentFilesMenu() +{ + recentMenu->clear(); + + for (int i = 0; i < recentFileList.size(); ++i) + { + if (i >= 10) break; + + QString item_full = recentFileList.at(i); + QString item_display = item_full; + int itemlen = item_full.length(); + const int maxlen = 100; + if (itemlen > maxlen) + { + int cut_start = 0; + while (item_full[cut_start] != '/' && item_full[cut_start] != '\\' && + cut_start < itemlen) + cut_start++; + + int cut_end = itemlen-1; + while (((item_full[cut_end] != '/' && item_full[cut_end] != '\\') || + (cut_start+4+(itemlen-cut_end) < maxlen)) && + cut_end > 0) + cut_end--; + + item_display.truncate(cut_start+1); + item_display += "..."; + item_display += QString(item_full).remove(0, cut_end); + } + + QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display)); + actRecentFile_i->setData(item_full); + connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); + + Config::RecentROMList[i] = recentFileList.at(i).toStdString(); + } + + while (recentFileList.size() > 10) + recentFileList.removeLast(); + + recentMenu->addSeparator(); + + QAction *actClearRecentList = recentMenu->addAction("Clear"); + connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles); + + if (recentFileList.empty()) + actClearRecentList->setEnabled(false); + + Config::Save(); +} + +void MainWindow::onClickRecentFile() +{ + QAction *act = (QAction *)sender(); + QString filename = act->data().toString(); + + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + const QStringList file = splitArchivePath(filename, true); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(emuThread, file, true)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + recentFileList.removeAll(filename); + recentFileList.prepend(filename); + updateRecentFilesMenu(); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); + + updateCartInserted(false); +} + +void MainWindow::onBootFirmware() +{ + emuThread->emuPause(); + + if (!verifySetup()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::BootToMenu(emuThread)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); + emuThread->emuUnpause(); + return; + } + + assert(emuThread->NDS != nullptr); + emuThread->NDS->Start(); + emuThread->emuRun(); +} + +void MainWindow::onInsertCart() +{ + emuThread->emuPause(); + + QStringList file = pickROM(false); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadROM(emuThread, file, false)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onEjectCart() +{ + emuThread->emuPause(); + + ROMManager::EjectCart(*emuThread->NDS); + + emuThread->emuUnpause(); + + updateCartInserted(false); +} + +void MainWindow::onInsertGBACart() +{ + emuThread->emuPause(); + + QStringList file = pickROM(true); + if (file.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + { + // TODO: better error reporting? + QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); + emuThread->emuUnpause(); + return; + } + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onInsertGBAAddon() +{ + QAction* act = (QAction*)sender(); + int type = act->data().toInt(); + + emuThread->emuPause(); + + ROMManager::LoadGBAAddon(*emuThread->NDS, type); + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onEjectGBACart() +{ + emuThread->emuPause(); + + ROMManager::EjectGBACart(*emuThread->NDS); + + emuThread->emuUnpause(); + + updateCartInserted(true); +} + +void MainWindow::onSaveState() +{ + int slot = ((QAction*)sender())->data().toInt(); + + emuThread->emuPause(); + + std::string filename; + if (slot > 0) + { + filename = ROMManager::GetSavestateName(slot); + } + else + { + // TODO: specific 'last directory' for savestate files? + QString qfilename = QFileDialog::getSaveFileName(this, + "Save state", + QString::fromStdString(Config::LastROMFolder), + "melonDS savestates (*.mln);;Any file (*.*)"); + if (qfilename.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + filename = qfilename.toStdString(); + } + + if (ROMManager::SaveState(*emuThread->NDS, filename)) + { + if (slot > 0) osdAddMessage(0, "State saved to slot %d", slot); + else osdAddMessage(0, "State saved to file"); + + actLoadState[slot]->setEnabled(true); + } + else + { + osdAddMessage(0xFFA0A0, "State save failed"); + } + + emuThread->emuUnpause(); +} + +void MainWindow::onLoadState() +{ + int slot = ((QAction*)sender())->data().toInt(); + + emuThread->emuPause(); + + std::string filename; + if (slot > 0) + { + filename = ROMManager::GetSavestateName(slot); + } + else + { + // TODO: specific 'last directory' for savestate files? + QString qfilename = QFileDialog::getOpenFileName(this, + "Load state", + QString::fromStdString(Config::LastROMFolder), + "melonDS savestates (*.ml*);;Any file (*.*)"); + if (qfilename.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + filename = qfilename.toStdString(); + } + + if (!Platform::FileExists(filename)) + { + if (slot > 0) osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); + else osdAddMessage(0xFFA0A0, "State file does not exist"); + + emuThread->emuUnpause(); + return; + } + + if (ROMManager::LoadState(*emuThread->NDS, filename)) + { + if (slot > 0) osdAddMessage(0, "State loaded from slot %d", slot); + else osdAddMessage(0, "State loaded from file"); + + actUndoStateLoad->setEnabled(true); + } + else + { + osdAddMessage(0xFFA0A0, "State load failed"); + } + + emuThread->emuUnpause(); +} + +void MainWindow::onUndoStateLoad() +{ + emuThread->emuPause(); + ROMManager::UndoStateLoad(*emuThread->NDS); + emuThread->emuUnpause(); + + osdAddMessage(0, "State load undone"); +} + +void MainWindow::onImportSavefile() +{ + emuThread->emuPause(); + QString path = QFileDialog::getOpenFileName(this, + "Select savefile", + QString::fromStdString(Config::LastROMFolder), + "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); + + if (path.isEmpty()) + { + emuThread->emuUnpause(); + return; + } + + Platform::FileHandle* f = Platform::OpenFile(path.toStdString(), Platform::FileMode::Read); + if (!f) + { + QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); + emuThread->emuUnpause(); + return; + } + + if (RunningSomething) + { + if (QMessageBox::warning(this, + "melonDS", + "The emulation will be reset and the current savefile overwritten.", + QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) + { + emuThread->emuUnpause(); + return; + } + + ROMManager::Reset(emuThread); + } + + u32 len = FileLength(f); + + std::unique_ptr data = std::make_unique(len); + Platform::FileRewind(f); + Platform::FileRead(data.get(), len, 1, f); + + assert(emuThread->NDS != nullptr); + emuThread->NDS->SetNDSSave(data.get(), len); + + CloseFile(f); + emuThread->emuUnpause(); +} + +void MainWindow::onQuit() +{ +#ifndef _WIN32 + signalSn->setEnabled(false); +#endif + QApplication::quit(); +} + + +void MainWindow::onPause(bool checked) +{ + if (!RunningSomething) return; + + if (checked) + { + emuThread->emuPause(); + osdAddMessage(0, "Paused"); + pausedManually = true; + } + else + { + emuThread->emuUnpause(); + osdAddMessage(0, "Resumed"); + pausedManually = false; + } +} + +void MainWindow::onReset() +{ + if (!RunningSomething) return; + + emuThread->emuPause(); + + actUndoStateLoad->setEnabled(false); + + ROMManager::Reset(emuThread); + + osdAddMessage(0, "Reset"); + emuThread->emuRun(); +} + +void MainWindow::onStop() +{ + if (!RunningSomething) return; + + emuThread->emuPause(); + emuThread->NDS->Stop(); +} + +void MainWindow::onFrameStep() +{ + if (!RunningSomething) return; + + emuThread->emuFrameStep(); +} + +void MainWindow::onOpenDateTime() +{ + DateTimeDialog* dlg = DateTimeDialog::openDlg(this); +} + +void MainWindow::onOpenPowerManagement() +{ + PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); +} + +void MainWindow::onEnableCheats(bool checked) +{ + Config::EnableCheats = checked?1:0; + ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); +} + +void MainWindow::onSetupCheats() +{ + emuThread->emuPause(); + + CheatsDialog* dlg = CheatsDialog::openDlg(this); + connect(dlg, &CheatsDialog::finished, this, &MainWindow::onCheatsDialogFinished); +} + +void MainWindow::onCheatsDialogFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onROMInfo() +{ + auto cart = emuThread->NDS->NDSCartSlot.GetCart(); + if (cart) + ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); +} + +void MainWindow::onRAMInfo() +{ + RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); +} + +void MainWindow::onOpenTitleManager() +{ + TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); +} + +void MainWindow::onMPNewInstance() +{ + //QProcess::startDetached(QApplication::applicationFilePath()); + QProcess newinst; + newinst.setProgram(QApplication::applicationFilePath()); + newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); + +#ifdef __WIN32__ + newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) + { + args->flags |= CREATE_NEW_CONSOLE; + }); +#endif + + newinst.startDetached(); +} + +void MainWindow::onOpenEmuSettings() +{ + emuThread->emuPause(); + + EmuSettingsDialog* dlg = EmuSettingsDialog::openDlg(this); + connect(dlg, &EmuSettingsDialog::finished, this, &MainWindow::onEmuSettingsDialogFinished); +} + +void MainWindow::onEmuSettingsDialogFinished(int res) +{ + emuThread->emuUnpause(); + + if (Config::ConsoleType == 1) + { + actInsertGBACart->setEnabled(false); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(false); + actEjectGBACart->setEnabled(false); + } + else + { + actInsertGBACart->setEnabled(true); + for (int i = 0; i < 1; i++) + actInsertGBAAddon[i]->setEnabled(true); + actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); + } + + if (EmuSettingsDialog::needsReset) + onReset(); + + actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + + if (!RunningSomething) + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); +} + +void MainWindow::onOpenInputConfig() +{ + emuThread->emuPause(); + + InputConfigDialog* dlg = InputConfigDialog::openDlg(this); + connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished); +} + +void MainWindow::onInputConfigFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onOpenVideoSettings() +{ + VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this); + connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings); +} + +void MainWindow::onOpenCameraSettings() +{ + emuThread->emuPause(); + + camStarted[0] = camManager[0]->isStarted(); + camStarted[1] = camManager[1]->isStarted(); + if (camStarted[0]) camManager[0]->stop(); + if (camStarted[1]) camManager[1]->stop(); + + CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this); + connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished); +} + +void MainWindow::onCameraSettingsFinished(int res) +{ + if (camStarted[0]) camManager[0]->start(); + if (camStarted[1]) camManager[1]->start(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenAudioSettings() +{ + AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this, emuThread->emuIsActive(), emuThread); + connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); + connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); + connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); + connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); +} + +void MainWindow::onOpenFirmwareSettings() +{ + emuThread->emuPause(); + + FirmwareSettingsDialog* dlg = FirmwareSettingsDialog::openDlg(this); + connect(dlg, &FirmwareSettingsDialog::finished, this, &MainWindow::onFirmwareSettingsFinished); +} + +void MainWindow::onFirmwareSettingsFinished(int res) +{ + if (FirmwareSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenPathSettings() +{ + emuThread->emuPause(); + + PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this); + connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished); +} + +void MainWindow::onPathSettingsFinished(int res) +{ + if (PathSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onUpdateAudioSettings() +{ + assert(emuThread->NDS != nullptr); + emuThread->NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + + if (Config::AudioBitDepth == 0) + emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); + else + emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); +} + +void MainWindow::onAudioSettingsFinished(int res) +{ + AudioInOut::UpdateSettings(*emuThread->NDS); +} + +void MainWindow::onOpenMPSettings() +{ + emuThread->emuPause(); + + MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); + connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); +} + +void MainWindow::onMPSettingsFinished(int res) +{ + AudioInOut::AudioMute(mainWindow); + LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenWifiSettings() +{ + emuThread->emuPause(); + + WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this); + connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished); +} + +void MainWindow::onWifiSettingsFinished(int res) +{ + Platform::LAN_DeInit(); + Platform::LAN_Init(); + + if (WifiSettingsDialog::needsReset) + onReset(); + + emuThread->emuUnpause(); +} + +void MainWindow::onOpenInterfaceSettings() +{ + emuThread->emuPause(); + InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); + connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); + connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); +} + +void MainWindow::onUpdateMouseTimer() +{ + panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); +} + +void MainWindow::onInterfaceSettingsFinished(int res) +{ + emuThread->emuUnpause(); +} + +void MainWindow::onChangeSavestateSRAMReloc(bool checked) +{ + Config::SavestateRelocSRAM = checked?1:0; +} + +void MainWindow::onChangeScreenSize() +{ + int factor = ((QAction*)sender())->data().toInt(); + QSize diff = size() - panelWidget->size(); + resize(panel->screenGetMinSize(factor) + diff); +} + +void MainWindow::onChangeScreenRotation(QAction* act) +{ + int rot = act->data().toInt(); + Config::ScreenRotation = rot; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenGap(QAction* act) +{ + int gap = act->data().toInt(); + Config::ScreenGap = gap; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenLayout(QAction* act) +{ + int layout = act->data().toInt(); + Config::ScreenLayout = layout; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenSwap(bool checked) +{ + Config::ScreenSwap = checked?1:0; + + // Swap between top and bottom screen when displaying one screen. + if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) + { + // Bottom Screen. + Config::ScreenSizing = Frontend::screenSizing_BotOnly; + actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) + { + // Top Screen. + Config::ScreenSizing = Frontend::screenSizing_TopOnly; + actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); + actScreenSizing[Config::ScreenSizing]->setChecked(true); + } + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenSizing(QAction* act) +{ + int sizing = act->data().toInt(); + Config::ScreenSizing = sizing; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenAspect(QAction* act) +{ + int aspect = act->data().toInt(); + QActionGroup* group = act->actionGroup(); + + if (group == grpScreenAspectTop) + { + Config::ScreenAspectTop = aspect; + } + else + { + Config::ScreenAspectBot = aspect; + } + + emit screenLayoutChange(); +} + +void MainWindow::onChangeIntegerScaling(bool checked) +{ + Config::IntegerScaling = checked?1:0; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeScreenFiltering(bool checked) +{ + Config::ScreenFilter = checked?1:0; + + emit screenLayoutChange(); +} + +void MainWindow::onChangeShowOSD(bool checked) +{ + Config::ShowOSD = checked?1:0; +} +void MainWindow::onChangeLimitFramerate(bool checked) +{ + Config::LimitFPS = checked?1:0; +} + +void MainWindow::onChangeAudioSync(bool checked) +{ + Config::AudioSync = checked?1:0; +} + + +void MainWindow::onTitleUpdate(QString title) +{ + setWindowTitle(title); +} + +void ToggleFullscreen(MainWindow* mainWindow) +{ + if (!mainWindow->isFullScreen()) + { + mainWindow->showFullScreen(); + mainWindow->menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working + } + else + { + mainWindow->showNormal(); + int menuBarHeight = mainWindow->menuBar()->sizeHint().height(); + mainWindow->menuBar()->setFixedHeight(menuBarHeight); + } +} + +void MainWindow::onFullscreenToggled() +{ + ToggleFullscreen(this); +} + +void MainWindow::onScreenEmphasisToggled() +{ + int currentSizing = Config::ScreenSizing; + if (currentSizing == Frontend::screenSizing_EmphTop) + { + Config::ScreenSizing = Frontend::screenSizing_EmphBot; + } + else if (currentSizing == Frontend::screenSizing_EmphBot) + { + Config::ScreenSizing = Frontend::screenSizing_EmphTop; + } + + emit screenLayoutChange(); +} + +void MainWindow::onEmuStart() +{ + for (int i = 1; i < 9; i++) + { + actSaveState[i]->setEnabled(true); + actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); + } + actSaveState[0]->setEnabled(true); + actLoadState[0]->setEnabled(true); + actUndoStateLoad->setEnabled(false); + + actPause->setEnabled(true); + actPause->setChecked(false); + actReset->setEnabled(true); + actStop->setEnabled(true); + actFrameStep->setEnabled(true); + + actDateTime->setEnabled(false); + actPowerManagement->setEnabled(true); + + actTitleManager->setEnabled(false); +} + +void MainWindow::onEmuStop() +{ + emuThread->emuPause(); + + for (int i = 0; i < 9; i++) + { + actSaveState[i]->setEnabled(false); + actLoadState[i]->setEnabled(false); + } + actUndoStateLoad->setEnabled(false); + + actPause->setEnabled(false); + actReset->setEnabled(false); + actStop->setEnabled(false); + actFrameStep->setEnabled(false); + + actDateTime->setEnabled(true); + actPowerManagement->setEnabled(false); + + actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); +} + +void MainWindow::onUpdateVideoSettings(bool glchange) +{ + if (glchange) + { + emuThread->emuPause(); + if (hasOGL) emuThread->deinitContext(); + + delete panel; + createScreenPanel(); + connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); + } + + videoSettingsDirty = true; + + if (glchange) + { + if (hasOGL) emuThread->initContext(); + emuThread->emuUnpause(); + } +} diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h new file mode 100644 index 00000000..f0f66c26 --- /dev/null +++ b/src/frontend/qt_sdl/Window.h @@ -0,0 +1,301 @@ +/* + Copyright 2016-2023 melonDS team + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#ifndef WINDOW_H +#define WINDOW_H + +#include "glad/glad.h" +#include "FrontendUtil.h" +#include "duckstation/gl/context.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Screen.h" + + +class EmuThread; + +/* +class WindowBase : public QMainWindow +{ + Q_OBJECT + +public: + explicit WindowBase(QWidget* parent = nullptr); + ~WindowBase(); + + bool hasOGL; + GL::Context* getOGLContext(); + + //void onAppStateChanged(Qt::ApplicationState state); + +protected: + void resizeEvent(QResizeEvent* event) override; + void changeEvent(QEvent* event) override; + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + +signals: + void screenLayoutChange(); + +private slots: + //void onQuit(); + + //void onTitleUpdate(QString title); + + //void onEmuStart(); + //void onEmuStop(); + + //void onUpdateVideoSettings(bool glchange); + + void onFullscreenToggled(); + void onScreenEmphasisToggled(); + +private: + virtual void closeEvent(QCloseEvent* event) override; + + void createScreenPanel(); + + //bool pausedManually = false; + + int oldW, oldH; + bool oldMax; + +public: + ScreenHandler* panel; + QWidget* panelWidget; +};*/ + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent = nullptr); + ~MainWindow(); + + bool hasOGL; + GL::Context* getOGLContext(); + /*void initOpenGL(); + void deinitOpenGL(); + void drawScreenGL();*/ + + bool preloadROMs(QStringList file, QStringList gbafile, bool boot); + QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); + + void onAppStateChanged(Qt::ApplicationState state); + + void osdAddMessage(unsigned int color, const char* fmt, ...); + +protected: + void resizeEvent(QResizeEvent* event) override; + void changeEvent(QEvent* event) override; + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + void focusInEvent(QFocusEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; + +signals: + void screenLayoutChange(); + +private slots: + void onOpenFile(); + void onClickRecentFile(); + void onClearRecentFiles(); + void onBootFirmware(); + void onInsertCart(); + void onEjectCart(); + void onInsertGBACart(); + void onInsertGBAAddon(); + void onEjectGBACart(); + void onSaveState(); + void onLoadState(); + void onUndoStateLoad(); + void onImportSavefile(); + void onQuit(); + + void onPause(bool checked); + void onReset(); + void onStop(); + void onFrameStep(); + void onOpenPowerManagement(); + void onOpenDateTime(); + void onEnableCheats(bool checked); + void onSetupCheats(); + void onCheatsDialogFinished(int res); + void onROMInfo(); + void onRAMInfo(); + void onOpenTitleManager(); + void onMPNewInstance(); + + void onOpenEmuSettings(); + void onEmuSettingsDialogFinished(int res); + void onOpenInputConfig(); + void onInputConfigFinished(int res); + void onOpenVideoSettings(); + void onOpenCameraSettings(); + void onCameraSettingsFinished(int res); + void onOpenAudioSettings(); + void onUpdateAudioSettings(); + void onAudioSettingsFinished(int res); + void onOpenMPSettings(); + void onMPSettingsFinished(int res); + void onOpenWifiSettings(); + void onWifiSettingsFinished(int res); + void onOpenFirmwareSettings(); + void onFirmwareSettingsFinished(int res); + void onOpenPathSettings(); + void onPathSettingsFinished(int res); + void onOpenInterfaceSettings(); + void onInterfaceSettingsFinished(int res); + void onUpdateMouseTimer(); + void onChangeSavestateSRAMReloc(bool checked); + void onChangeScreenSize(); + void onChangeScreenRotation(QAction* act); + void onChangeScreenGap(QAction* act); + void onChangeScreenLayout(QAction* act); + void onChangeScreenSwap(bool checked); + void onChangeScreenSizing(QAction* act); + void onChangeScreenAspect(QAction* act); + void onChangeIntegerScaling(bool checked); + void onChangeScreenFiltering(bool checked); + void onChangeShowOSD(bool checked); + void onChangeLimitFramerate(bool checked); + void onChangeAudioSync(bool checked); + + void onTitleUpdate(QString title); + + void onEmuStart(); + void onEmuStop(); + + void onUpdateVideoSettings(bool glchange); + + void onFullscreenToggled(); + void onScreenEmphasisToggled(); + +private: + virtual void closeEvent(QCloseEvent* event) override; + + QStringList currentROM; + QStringList currentGBAROM; + QList recentFileList; + QMenu *recentMenu; + void updateRecentFilesMenu(); + + bool verifySetup(); + QString pickFileFromArchive(QString archiveFileName); + QStringList pickROM(bool gba); + void updateCartInserted(bool gba); + + void createScreenPanel(); + + bool pausedManually = false; + + int oldW, oldH; + bool oldMax; + +public: + ScreenHandler* panel; + QWidget* panelWidget; + + QAction* actOpenROM; + QAction* actBootFirmware; + QAction* actCurrentCart; + QAction* actInsertCart; + QAction* actEjectCart; + QAction* actCurrentGBACart; + QAction* actInsertGBACart; + QAction* actInsertGBAAddon[1]; + QAction* actEjectGBACart; + QAction* actImportSavefile; + QAction* actSaveState[9]; + QAction* actLoadState[9]; + QAction* actUndoStateLoad; + QAction* actQuit; + + QAction* actPause; + QAction* actReset; + QAction* actStop; + QAction* actFrameStep; + QAction* actPowerManagement; + QAction* actDateTime; + QAction* actEnableCheats; + QAction* actSetupCheats; + QAction* actROMInfo; + QAction* actRAMInfo; + QAction* actTitleManager; + QAction* actMPNewInstance; + + QAction* actEmuSettings; +#ifdef __APPLE__ + QAction* actPreferences; +#endif + QAction* actInputConfig; + QAction* actVideoSettings; + QAction* actCameraSettings; + QAction* actAudioSettings; + QAction* actMPSettings; + QAction* actWifiSettings; + QAction* actFirmwareSettings; + QAction* actPathSettings; + QAction* actInterfaceSettings; + QAction* actSavestateSRAMReloc; + QAction* actScreenSize[4]; + QActionGroup* grpScreenRotation; + QAction* actScreenRotation[Frontend::screenRot_MAX]; + QActionGroup* grpScreenGap; + QAction* actScreenGap[6]; + QActionGroup* grpScreenLayout; + QAction* actScreenLayout[Frontend::screenLayout_MAX]; + QAction* actScreenSwap; + QActionGroup* grpScreenSizing; + QAction* actScreenSizing[Frontend::screenSizing_MAX]; + QAction* actIntegerScaling; + QActionGroup* grpScreenAspectTop; + QAction** actScreenAspectTop; + QActionGroup* grpScreenAspectBot; + QAction** actScreenAspectBot; + QAction* actScreenFiltering; + QAction* actShowOSD; + QAction* actLimitFramerate; + QAction* actAudioSync; +}; + +void ToggleFullscreen(MainWindow* mainWindow); + +#endif // WINDOW_H diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index a0ac0860..2f08c374 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -81,6 +81,7 @@ #include "FrontendUtil.h" #include "OSD.h" +#include "Args.h" #include "NDS.h" #include "NDSCart.h" #include "GBACart.h" @@ -98,7 +99,7 @@ #include "Savestate.h" -#include "main_shaders.h" +//#include "main_shaders.h" #include "ROMManager.h" #include "ArchiveUtil.h" @@ -108,14 +109,15 @@ // TODO: uniform variable spelling using namespace melonDS; -const QString NdsRomMimeType = "application/x-nintendo-ds-rom"; -const QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; +QString NdsRomMimeType = "application/x-nintendo-ds-rom"; +QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; + +QString GbaRomMimeType = "application/x-gba-rom"; +QStringList GbaRomExtensions { ".gba", ".agb" }; -const QString GbaRomMimeType = "application/x-gba-rom"; -const QStringList GbaRomExtensions { ".gba", ".agb" }; // This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). -const QStringList ArchiveMimeTypes +QStringList ArchiveMimeTypes { #ifdef ARCHIVE_SUPPORT_ENABLED "application/zip", @@ -137,7 +139,7 @@ const QStringList ArchiveMimeTypes #endif }; -const QStringList ArchiveExtensions +QStringList ArchiveExtensions { #ifdef ARCHIVE_SUPPORT_ENABLED ".zip", ".7z", ".rar", ".tar", @@ -170,1189 +172,11 @@ bool videoSettingsDirty; CameraManager* camManager[2]; bool camStarted[2]; -const struct { int id; float ratio; const char* label; } aspectRatios[] = -{ - { 0, 1, "4:3 (native)" }, - { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"}, - { 1, (16.f / 9) / (4.f / 3), "16:9" }, - { 2, (21.f / 9) / (4.f / 3), "21:9" }, - { 3, 0, "window" } -}; -constexpr int AspectRatiosNum = sizeof(aspectRatios) / sizeof(aspectRatios[0]); +//extern int AspectRatiosNum; -EmuThread::EmuThread(QObject* parent) : QThread(parent) -{ - EmuStatus = emuStatus_Exit; - EmuRunning = emuStatus_Paused; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = false; - connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint())); - connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); - connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); - connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); - connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger())); - connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); - connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); - connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); - connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged())); - connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); - connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); - connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); - static_cast(mainWindow->panel)->transferLayout(this); -} - -std::unique_ptr EmuThread::CreateConsole() -{ - if (Config::ConsoleType == 1) - { - return std::make_unique(); - } - - return std::make_unique(); -} - -void EmuThread::RecreateConsole() -{ - if (!NDS || NDS->ConsoleType != Config::ConsoleType) - { - NDS = nullptr; // To ensure the destructor is called before a new one is created - NDS::Current = nullptr; - - NDS = CreateConsole(); - // TODO: Insert ROMs - NDS::Current = NDS.get(); - } -} - - -void EmuThread::updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix) -{ - screenSettingsLock.lock(); - - if (lastScreenWidth != windowInfo.surface_width || lastScreenHeight != windowInfo.surface_height) - { - if (oglContext) - oglContext->ResizeSurface(windowInfo.surface_width, windowInfo.surface_height); - lastScreenWidth = windowInfo.surface_width; - lastScreenHeight = windowInfo.surface_height; - } - - this->filter = filter; - this->windowInfo = windowInfo; - this->numScreens = numScreens; - memcpy(this->screenKind, screenKind, sizeof(int)*numScreens); - memcpy(this->screenMatrix, screenMatrix, sizeof(float)*numScreens*6); - - screenSettingsLock.unlock(); -} - -void EmuThread::initOpenGL() -{ - GL::Context* windowctx = mainWindow->getOGLContext(); - - oglContext = windowctx; - oglContext->MakeCurrent(); - - OpenGL::BuildShaderProgram(kScreenVS, kScreenFS, screenShaderProgram, "ScreenShader"); - GLuint pid = screenShaderProgram[2]; - glBindAttribLocation(pid, 0, "vPosition"); - glBindAttribLocation(pid, 1, "vTexcoord"); - glBindFragDataLocation(pid, 0, "oColor"); - - OpenGL::LinkShaderProgram(screenShaderProgram); - - glUseProgram(pid); - glUniform1i(glGetUniformLocation(pid, "ScreenTex"), 0); - - screenShaderScreenSizeULoc = glGetUniformLocation(pid, "uScreenSize"); - screenShaderTransformULoc = glGetUniformLocation(pid, "uTransform"); - - // to prevent bleeding between both parts of the screen - // with bilinear filtering enabled - const int paddedHeight = 192*2+2; - const float padPixels = 1.f / paddedHeight; - - const float vertices[] = - { - 0.f, 0.f, 0.f, 0.f, - 0.f, 192.f, 0.f, 0.5f - padPixels, - 256.f, 192.f, 1.f, 0.5f - padPixels, - 0.f, 0.f, 0.f, 0.f, - 256.f, 192.f, 1.f, 0.5f - padPixels, - 256.f, 0.f, 1.f, 0.f, - - 0.f, 0.f, 0.f, 0.5f + padPixels, - 0.f, 192.f, 0.f, 1.f, - 256.f, 192.f, 1.f, 1.f, - 0.f, 0.f, 0.f, 0.5f + padPixels, - 256.f, 192.f, 1.f, 1.f, - 256.f, 0.f, 1.f, 0.5f + padPixels - }; - - glGenBuffers(1, &screenVertexBuffer); - glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - glGenVertexArrays(1, &screenVertexArray); - glBindVertexArray(screenVertexArray); - glEnableVertexAttribArray(0); // position - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(0)); - glEnableVertexAttribArray(1); // texcoord - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*4, (void*)(2*4)); - - glGenTextures(1, &screenTexture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, screenTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, paddedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - // fill the padding - u8 zeroData[256*4*4]; - memset(zeroData, 0, sizeof(zeroData)); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); - - OSD::Init(true); - - oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); -} - -void EmuThread::deinitOpenGL() -{ - glDeleteTextures(1, &screenTexture); - - glDeleteVertexArrays(1, &screenVertexArray); - glDeleteBuffers(1, &screenVertexBuffer); - - OpenGL::DeleteShaderProgram(screenShaderProgram); - - OSD::DeInit(); - - oglContext->DoneCurrent(); - oglContext = nullptr; - - lastScreenWidth = lastScreenHeight = -1; -} - -void EmuThread::run() -{ - u32 mainScreenPos[3]; - Platform::FileHandle* file; - - RecreateConsole(); - - mainScreenPos[0] = 0; - mainScreenPos[1] = 0; - mainScreenPos[2] = 0; - autoScreenSizing = 0; - - videoSettingsDirty = false; - - if (mainWindow->hasOGL) - { - initOpenGL(); - videoRenderer = Config::_3DRenderer; - } - else - { - videoRenderer = 0; - } - - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(NDS->GPU, Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(NDS->GPU); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } - - NDS->SPU.SetInterpolation(Config::AudioInterp); - - Input::Init(); - - u32 nframes = 0; - double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); - double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; - double frameLimitError = 0.0; - double lastMeasureTime = lastTime; - - u32 winUpdateCount = 0, winUpdateFreq = 1; - u8 dsiVolumeLevel = 0x1F; - - file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Read); - if (file) - { - RTC::StateData state; - Platform::FileRead(&state, sizeof(state), 1, file); - Platform::CloseFile(file); - NDS->RTC.SetState(state); - } - - char melontitle[100]; - - while (EmuRunning != emuStatus_Exit) - { - Input::Process(); - - if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); - - if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); - if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); - if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); - - if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); - - if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); - if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); - - if (Input::HotkeyPressed(HK_SolarSensorDecrease)) - { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); - if (level != -1) - { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); - } - } - if (Input::HotkeyPressed(HK_SolarSensorIncrease)) - { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); - if (level != -1) - { - char msg[64]; - sprintf(msg, "Solar sensor level: %d", level); - OSD::AddMessage(0, msg); - } - } - - if (NDS->ConsoleType == 1) - { - DSi& dsi = static_cast(*NDS); - double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; - - // Handle power button - if (Input::HotkeyDown(HK_PowerButton)) - { - dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); - } - else if (Input::HotkeyReleased(HK_PowerButton)) - { - dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); - } - - // Handle volume buttons - if (Input::HotkeyDown(HK_VolumeUp)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); - } - else if (Input::HotkeyReleased(HK_VolumeUp)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); - } - - if (Input::HotkeyDown(HK_VolumeDown)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); - } - else if (Input::HotkeyReleased(HK_VolumeDown)) - { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); - } - - dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); - } - - if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) - { - EmuStatus = emuStatus_Running; - if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; - - // update render settings if needed - // HACK: - // once the fast forward hotkey is released, we need to update vsync - // to the old setting again - if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward)) - { - if (oglContext) - { - oglContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); - videoRenderer = Config::_3DRenderer; - } -#ifdef OGLRENDERER_ENABLED - else -#endif - { - videoRenderer = 0; - } - - videoRenderer = oglContext ? Config::_3DRenderer : 0; - - videoSettingsDirty = false; - - if (videoRenderer == 0) - { // If we're using the software renderer... - NDS->GPU.SetRenderer3D(std::make_unique(NDS->GPU, Config::Threaded3D != 0)); - } - else - { - auto glrenderer = melonDS::GLRenderer::New(NDS->GPU); - glrenderer->SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - NDS->GPU.SetRenderer3D(std::move(glrenderer)); - } - } - - // process input and hotkeys - NDS->SetKeyMask(Input::InputMask); - - if (Input::HotkeyPressed(HK_Lid)) - { - bool lid = !NDS->IsLidClosed(); - NDS->SetLidClosed(lid); - OSD::AddMessage(0, lid ? "Lid closed" : "Lid opened"); - } - - // microphone input - AudioInOut::MicProcess(*NDS); - - // auto screen layout - if (Config::ScreenSizing == Frontend::screenSizing_Auto) - { - mainScreenPos[2] = mainScreenPos[1]; - mainScreenPos[1] = mainScreenPos[0]; - mainScreenPos[0] = NDS->PowerControl9 >> 15; - - int guess; - if (mainScreenPos[0] == mainScreenPos[2] && - mainScreenPos[0] != mainScreenPos[1]) - { - // constant flickering, likely displaying 3D on both screens - // TODO: when both screens are used for 2D only...??? - guess = Frontend::screenSizing_Even; - } - else - { - if (mainScreenPos[0] == 1) - guess = Frontend::screenSizing_EmphTop; - else - guess = Frontend::screenSizing_EmphBot; - } - - if (guess != autoScreenSizing) - { - autoScreenSizing = guess; - emit screenLayoutChange(); - } - } - - - // emulate - u32 nlines = NDS->RunFrame(); - - if (ROMManager::NDSSave) - ROMManager::NDSSave->CheckFlush(); - - if (ROMManager::GBASave) - ROMManager::GBASave->CheckFlush(); - - if (ROMManager::FirmwareSave) - ROMManager::FirmwareSave->CheckFlush(); - - if (!oglContext) - { - FrontBufferLock.lock(); - FrontBuffer = NDS->GPU.FrontBuffer; - FrontBufferLock.unlock(); - } - else - { - FrontBuffer = NDS->GPU.FrontBuffer; - drawScreenGL(); - } - -#ifdef MELONCAP - MelonCap::Update(); -#endif // MELONCAP - - if (EmuRunning == emuStatus_Exit) break; - - winUpdateCount++; - if (winUpdateCount >= winUpdateFreq && !oglContext) - { - emit windowUpdate(); - winUpdateCount = 0; - } - - bool fastforward = Input::HotkeyDown(HK_FastForward); - - if (fastforward && oglContext && Config::ScreenVSync) - { - oglContext->SetSwapInterval(0); - } - - if (Config::DSiVolumeSync && NDS->ConsoleType == 1) - { - DSi& dsi = static_cast(*NDS); - u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); - if (volumeLevel != dsiVolumeLevel) - { - dsiVolumeLevel = volumeLevel; - emit syncVolumeLevel(); - } - - Config::AudioVolume = volumeLevel * (256.0 / 31.0); - } - - if (Config::AudioSync && !fastforward) - AudioInOut::AudioSync(*emuThread->NDS); - - double frametimeStep = nlines / (60.0 * 263.0); - - { - bool limitfps = Config::LimitFPS && !fastforward; - - double practicalFramelimit = limitfps ? frametimeStep : 1.0 / 1000.0; - - double curtime = SDL_GetPerformanceCounter() * perfCountsSec; - - frameLimitError += practicalFramelimit - (curtime - lastTime); - if (frameLimitError < -practicalFramelimit) - frameLimitError = -practicalFramelimit; - if (frameLimitError > practicalFramelimit) - frameLimitError = practicalFramelimit; - - if (round(frameLimitError * 1000.0) > 0.0) - { - SDL_Delay(round(frameLimitError * 1000.0)); - double timeBeforeSleep = curtime; - curtime = SDL_GetPerformanceCounter() * perfCountsSec; - frameLimitError -= curtime - timeBeforeSleep; - } - - lastTime = curtime; - } - - nframes++; - if (nframes >= 30) - { - double time = SDL_GetPerformanceCounter() * perfCountsSec; - double dt = time - lastMeasureTime; - lastMeasureTime = time; - - u32 fps = round(nframes / dt); - nframes = 0; - - float fpstarget = 1.0/frametimeStep; - - winUpdateFreq = fps / (u32)round(fpstarget); - if (winUpdateFreq < 1) - winUpdateFreq = 1; - - int inst = Platform::InstanceID(); - if (inst == 0) - sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); - else - sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1); - changeWindowTitle(melontitle); - } - } - else - { - // paused - nframes = 0; - lastTime = SDL_GetPerformanceCounter() * perfCountsSec; - lastMeasureTime = lastTime; - - emit windowUpdate(); - - EmuStatus = EmuRunning; - - int inst = Platform::InstanceID(); - if (inst == 0) - sprintf(melontitle, "melonDS " MELONDS_VERSION); - else - sprintf(melontitle, "melonDS (%d)", inst+1); - changeWindowTitle(melontitle); - - SDL_Delay(75); - - if (oglContext) - drawScreenGL(); - - ContextRequestKind contextRequest = ContextRequest; - if (contextRequest == contextRequest_InitGL) - { - initOpenGL(); - ContextRequest = contextRequest_None; - } - else if (contextRequest == contextRequest_DeInitGL) - { - deinitOpenGL(); - ContextRequest = contextRequest_None; - } - } - } - - file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); - if (file) - { - RTC::StateData state; - NDS->RTC.GetState(state); - Platform::FileWrite(&state, sizeof(state), 1, file); - Platform::CloseFile(file); - } - - EmuStatus = emuStatus_Exit; - - NDS::Current = nullptr; - // nds is out of scope, so unique_ptr cleans it up for us -} - -void EmuThread::changeWindowTitle(char* title) -{ - emit windowTitleChange(QString(title)); -} - -void EmuThread::emuRun() -{ - EmuRunning = emuStatus_Running; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = true; - - // checkme - emit windowEmuStart(); - AudioInOut::Enable(); -} - -void EmuThread::initContext() -{ - ContextRequest = contextRequest_InitGL; - while (ContextRequest != contextRequest_None); -} - -void EmuThread::deinitContext() -{ - ContextRequest = contextRequest_DeInitGL; - while (ContextRequest != contextRequest_None); -} - -void EmuThread::emuPause() -{ - EmuPauseStack++; - if (EmuPauseStack > EmuPauseStackPauseThreshold) return; - - PrevEmuStatus = EmuRunning; - EmuRunning = emuStatus_Paused; - while (EmuStatus != emuStatus_Paused); - - AudioInOut::Disable(); -} - -void EmuThread::emuUnpause() -{ - if (EmuPauseStack < EmuPauseStackPauseThreshold) return; - - EmuPauseStack--; - if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; - - EmuRunning = PrevEmuStatus; - - AudioInOut::Enable(); -} - -void EmuThread::emuStop() -{ - EmuRunning = emuStatus_Exit; - EmuPauseStack = EmuPauseStackRunning; - - AudioInOut::Disable(); -} - -void EmuThread::emuFrameStep() -{ - if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); - EmuRunning = emuStatus_FrameStep; -} - -bool EmuThread::emuIsRunning() -{ - return EmuRunning == emuStatus_Running; -} - -bool EmuThread::emuIsActive() -{ - return (RunningSomething == 1); -} - -void EmuThread::drawScreenGL() -{ - if (!NDS) return; - int w = windowInfo.surface_width; - int h = windowInfo.surface_height; - float factor = windowInfo.surface_scale; - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDisable(GL_DEPTH_TEST); - glDepthMask(false); - glDisable(GL_BLEND); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_STENCIL_TEST); - glClear(GL_COLOR_BUFFER_BIT); - - glViewport(0, 0, w, h); - - glUseProgram(screenShaderProgram[2]); - glUniform2f(screenShaderScreenSizeULoc, w / factor, h / factor); - - int frontbuf = FrontBuffer; - glActiveTexture(GL_TEXTURE0); - -#ifdef OGLRENDERER_ENABLED - if (NDS->GPU.GetRenderer3D().Accelerated) - { - // hardware-accelerated render - static_cast(NDS->GPU.GetRenderer3D()).GetCompositor().BindOutputTexture(frontbuf); - } - else -#endif - { - // regular render - glBindTexture(GL_TEXTURE_2D, screenTexture); - - if (NDS->GPU.Framebuffer[frontbuf][0] && NDS->GPU.Framebuffer[frontbuf][1]) - { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][0].get()); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, NDS->GPU.Framebuffer[frontbuf][1].get()); - } - } - - screenSettingsLock.lock(); - - GLint filter = this->filter ? GL_LINEAR : GL_NEAREST; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - - glBindBuffer(GL_ARRAY_BUFFER, screenVertexBuffer); - glBindVertexArray(screenVertexArray); - - for (int i = 0; i < numScreens; i++) - { - glUniformMatrix2x3fv(screenShaderTransformULoc, 1, GL_TRUE, screenMatrix[i]); - glDrawArrays(GL_TRIANGLES, screenKind[i] == 0 ? 0 : 2*3, 2*3); - } - - screenSettingsLock.unlock(); - - OSD::Update(); - OSD::DrawGL(w, h); - - oglContext->SwapBuffers(); -} - -ScreenHandler::ScreenHandler(QWidget* widget) -{ - widget->setMouseTracking(true); - widget->setAttribute(Qt::WA_AcceptTouchEvents); - QTimer* mouseTimer = setupMouseTimer(); - widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);}); -} - -ScreenHandler::~ScreenHandler() -{ - mouseTimer->stop(); - delete mouseTimer; -} - -void ScreenHandler::screenSetupLayout(int w, int h) -{ - int sizing = Config::ScreenSizing; - if (sizing == 3) sizing = autoScreenSizing; - - float aspectTop, aspectBot; - - for (auto ratio : aspectRatios) - { - if (ratio.id == Config::ScreenAspectTop) - aspectTop = ratio.ratio; - if (ratio.id == Config::ScreenAspectBot) - aspectBot = ratio.ratio; - } - - if (aspectTop == 0) - aspectTop = ((float) w / h) / (4.f / 3.f); - - if (aspectBot == 0) - aspectBot = ((float) w / h) / (4.f / 3.f); - - Frontend::SetupScreenLayout(w, h, - static_cast(Config::ScreenLayout), - static_cast(Config::ScreenRotation), - static_cast(sizing), - Config::ScreenGap, - Config::IntegerScaling != 0, - Config::ScreenSwap != 0, - aspectTop, - aspectBot); - - numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); -} - -QSize ScreenHandler::screenGetMinSize(int factor = 1) -{ - bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg - || Config::ScreenRotation == Frontend::screenRot_270Deg); - int gap = Config::ScreenGap * factor; - - int w = 256 * factor; - int h = 192 * factor; - - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly - || Config::ScreenSizing == Frontend::screenSizing_BotOnly) - { - return QSize(w, h); - } - - if (Config::ScreenLayout == Frontend::screenLayout_Natural) - { - if (isHori) - return QSize(h+gap+h, w); - else - return QSize(w, h+gap+h); - } - else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) - { - if (isHori) - return QSize(h, w+gap+w); - else - return QSize(w, h+gap+h); - } - else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) - { - if (isHori) - return QSize(h+gap+h, w); - else - return QSize(w+gap+w, h); - } - else // hybrid - { - if (isHori) - return QSize(h+gap+h, 3*w + (int)ceil((4*gap) / 3.0)); - else - return QSize(3*w + (int)ceil((4*gap) / 3.0), h+gap+h); - } -} - -void ScreenHandler::screenOnMousePress(QMouseEvent* event) -{ - event->accept(); - if (event->button() != Qt::LeftButton) return; - - int x = event->pos().x(); - int y = event->pos().y(); - - if (Frontend::GetTouchCoords(x, y, false)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } -} - -void ScreenHandler::screenOnMouseRelease(QMouseEvent* event) -{ - event->accept(); - if (event->button() != Qt::LeftButton) return; - - if (touching) - { - touching = false; - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - } -} - -void ScreenHandler::screenOnMouseMove(QMouseEvent* event) -{ - event->accept(); - - showCursor(); - - if (!(event->buttons() & Qt::LeftButton)) return; - if (!touching) return; - - int x = event->pos().x(); - int y = event->pos().y(); - - if (Frontend::GetTouchCoords(x, y, true)) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } -} - -void ScreenHandler::screenHandleTablet(QTabletEvent* event) -{ - event->accept(); - - switch(event->type()) - { - case QEvent::TabletPress: - case QEvent::TabletMove: - { - int x = event->x(); - int y = event->y(); - - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } - } - break; - case QEvent::TabletRelease: - if (touching) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - touching = false; - } - break; - default: - break; - } -} - -void ScreenHandler::screenHandleTouch(QTouchEvent* event) -{ - event->accept(); - - switch(event->type()) - { - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - if (event->touchPoints().length() > 0) - { - QPointF lastPosition = event->touchPoints().first().lastPos(); - int x = (int)lastPosition.x(); - int y = (int)lastPosition.y(); - - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) - { - touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); - } - } - break; - case QEvent::TouchEnd: - if (touching) - { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); - touching = false; - } - break; - default: - break; - } -} - -void ScreenHandler::showCursor() -{ - mainWindow->panelWidget->setCursor(Qt::ArrowCursor); - mouseTimer->start(); -} - -QTimer* ScreenHandler::setupMouseTimer() -{ - mouseTimer = new QTimer(); - mouseTimer->setSingleShot(true); - mouseTimer->setInterval(Config::MouseHideSeconds*1000); - mouseTimer->start(); - - return mouseTimer; -} - -ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this) -{ - screen[0] = QImage(256, 192, QImage::Format_RGB32); - screen[1] = QImage(256, 192, QImage::Format_RGB32); - - screenTrans[0].reset(); - screenTrans[1].reset(); - - OSD::Init(false); -} - -ScreenPanelNative::~ScreenPanelNative() -{ - OSD::DeInit(); -} - -void ScreenPanelNative::setupScreenLayout() -{ - int w = width(); - int h = height(); - - screenSetupLayout(w, h); - - for (int i = 0; i < numScreens; i++) - { - float* mtx = screenMatrix[i]; - screenTrans[i].setMatrix(mtx[0], mtx[1], 0.f, - mtx[2], mtx[3], 0.f, - mtx[4], mtx[5], 1.f); - } -} - -void ScreenPanelNative::paintEvent(QPaintEvent* event) -{ - QPainter painter(this); - - // fill background - painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); - - if (emuThread->emuIsActive()) - { - assert(emuThread->NDS != nullptr); - emuThread->FrontBufferLock.lock(); - int frontbuf = emuThread->FrontBuffer; - if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1]) - { - emuThread->FrontBufferLock.unlock(); - return; - } - - memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); - memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); - emuThread->FrontBufferLock.unlock(); - - QRect screenrc(0, 0, 256, 192); - - for (int i = 0; i < numScreens; i++) - { - painter.setTransform(screenTrans[i]); - painter.drawImage(screenrc, screen[screenKind[i]]); - } - } - - OSD::Update(); - OSD::DrawNative(painter); -} - -void ScreenPanelNative::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); -} - -void ScreenPanelNative::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelNative::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelNative::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelNative::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelNative::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - -void ScreenPanelNative::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); -} - - -ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QWidget(parent), ScreenHandler(this) -{ - setAutoFillBackground(false); - setAttribute(Qt::WA_NativeWindow, true); - setAttribute(Qt::WA_NoSystemBackground, true); - setAttribute(Qt::WA_PaintOnScreen, true); - setAttribute(Qt::WA_KeyCompression, false); - setFocusPolicy(Qt::StrongFocus); - setMinimumSize(screenGetMinSize()); -} - -ScreenPanelGL::~ScreenPanelGL() -{} - -bool ScreenPanelGL::createContext() -{ - std::optional windowInfo = getWindowInfo(); - std::array versionsToTry = { - GL::Context::Version{GL::Context::Profile::Core, 4, 3}, - GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; - if (windowInfo.has_value()) - { - glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); - glContext->DoneCurrent(); - } - - return glContext != nullptr; -} - -qreal ScreenPanelGL::devicePixelRatioFromScreen() const -{ - const QScreen* screen_for_ratio = window()->windowHandle()->screen(); - if (!screen_for_ratio) - screen_for_ratio = QGuiApplication::primaryScreen(); - - return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); -} - -int ScreenPanelGL::scaledWindowWidth() const -{ - return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())), 1); -} - -int ScreenPanelGL::scaledWindowHeight() const -{ - return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())), 1); -} - -std::optional ScreenPanelGL::getWindowInfo() -{ - WindowInfo wi; - - // Windows and Apple are easy here since there's no display connection. - #if defined(_WIN32) - wi.type = WindowInfo::Type::Win32; - wi.window_handle = reinterpret_cast(winId()); - #elif defined(__APPLE__) - wi.type = WindowInfo::Type::MacOS; - wi.window_handle = reinterpret_cast(winId()); - #else - QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); - const QString platform_name = QGuiApplication::platformName(); - if (platform_name == QStringLiteral("xcb")) - { - wi.type = WindowInfo::Type::X11; - wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); - wi.window_handle = reinterpret_cast(winId()); - } - else if (platform_name == QStringLiteral("wayland")) - { - wi.type = WindowInfo::Type::Wayland; - QWindow* handle = windowHandle(); - if (handle == nullptr) - return std::nullopt; - - wi.display_connection = pni->nativeResourceForWindow("display", handle); - wi.window_handle = pni->nativeResourceForWindow("surface", handle); - } - else - { - qCritical() << "Unknown PNI platform " << platform_name; - return std::nullopt; - } - #endif - - wi.surface_width = static_cast(scaledWindowWidth()); - wi.surface_height = static_cast(scaledWindowHeight()); - wi.surface_scale = static_cast(devicePixelRatioFromScreen()); - - return wi; -} - - -QPaintEngine* ScreenPanelGL::paintEngine() const -{ - return nullptr; -} - -void ScreenPanelGL::setupScreenLayout() -{ - int w = width(); - int h = height(); - - screenSetupLayout(w, h); - if (emuThread) - transferLayout(emuThread); -} - -void ScreenPanelGL::resizeEvent(QResizeEvent* event) -{ - setupScreenLayout(); - - QWidget::resizeEvent(event); -} - -void ScreenPanelGL::mousePressEvent(QMouseEvent* event) -{ - screenOnMousePress(event); -} - -void ScreenPanelGL::mouseReleaseEvent(QMouseEvent* event) -{ - screenOnMouseRelease(event); -} - -void ScreenPanelGL::mouseMoveEvent(QMouseEvent* event) -{ - screenOnMouseMove(event); -} - -void ScreenPanelGL::tabletEvent(QTabletEvent* event) -{ - screenHandleTablet(event); -} - -bool ScreenPanelGL::event(QEvent* event) -{ - if (event->type() == QEvent::TouchBegin - || event->type() == QEvent::TouchEnd - || event->type() == QEvent::TouchUpdate) - { - screenHandleTouch((QTouchEvent*)event); - return true; - } - return QWidget::event(event); -} - -void ScreenPanelGL::transferLayout(EmuThread* thread) -{ - std::optional windowInfo = getWindowInfo(); - if (windowInfo.has_value()) - thread->updateScreenSettings(Config::ScreenFilter, *windowInfo, numScreens, screenKind, &screenMatrix[0][0]); -} - -void ScreenPanelGL::onScreenLayoutChanged() -{ - setMinimumSize(screenGetMinSize()); - setupScreenLayout(); -} static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) @@ -1427,1864 +251,7 @@ static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive } -#ifndef _WIN32 -static int signalFd[2]; -QSocketNotifier *signalSn; -static void signalHandler(int) -{ - char a = 1; - write(signalFd[0], &a, sizeof(a)); -} -#endif - -MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) -{ -#ifndef _WIN32 - if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) - { - qFatal("Couldn't create socketpair"); - } - - signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); - connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); - - struct sigaction sa; - - sa.sa_handler = signalHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_flags |= SA_RESTART; - sigaction(SIGINT, &sa, 0); -#endif - - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = Config::WindowMaximized; - - setWindowTitle("melonDS " MELONDS_VERSION); - setAttribute(Qt::WA_DeleteOnClose); - setAcceptDrops(true); - setFocusPolicy(Qt::ClickFocus); - - int inst = Platform::InstanceID(); - - QMenuBar* menubar = new QMenuBar(); - { - QMenu* menu = menubar->addMenu("File"); - - actOpenROM = menu->addAction("Open ROM..."); - connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile); - actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open)); - - /*actOpenROMArchive = menu->addAction("Open ROM inside archive..."); - connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive); - actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ - - recentMenu = menu->addMenu("Open recent"); - for (int i = 0; i < 10; ++i) - { - std::string item = Config::RecentROMList[i]; - if (!item.empty()) - recentFileList.push_back(QString::fromStdString(item)); - } - updateRecentFilesMenu(); - - //actBootFirmware = menu->addAction("Launch DS menu"); - actBootFirmware = menu->addAction("Boot firmware"); - connect(actBootFirmware, &QAction::triggered, this, &MainWindow::onBootFirmware); - - menu->addSeparator(); - - actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel()); - actCurrentCart->setEnabled(false); - - actInsertCart = menu->addAction("Insert cart..."); - connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart); - - actEjectCart = menu->addAction("Eject cart"); - connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart); - - menu->addSeparator(); - - actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel()); - actCurrentGBACart->setEnabled(false); - - actInsertGBACart = menu->addAction("Insert ROM cart..."); - connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart); - - { - QMenu* submenu = menu->addMenu("Insert add-on cart"); - - actInsertGBAAddon[0] = submenu->addAction("Memory expansion"); - actInsertGBAAddon[0]->setData(QVariant(GBAAddon_RAMExpansion)); - connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon); - } - - actEjectGBACart = menu->addAction("Eject cart"); - connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart); - - menu->addSeparator(); - - actImportSavefile = menu->addAction("Import savefile"); - connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile); - - menu->addSeparator(); - - { - QMenu* submenu = menu->addMenu("Save state"); - - for (int i = 1; i < 9; i++) - { - actSaveState[i] = submenu->addAction(QString("%1").arg(i)); - actSaveState[i]->setShortcut(QKeySequence(Qt::ShiftModifier | (Qt::Key_F1+i-1))); - actSaveState[i]->setData(QVariant(i)); - connect(actSaveState[i], &QAction::triggered, this, &MainWindow::onSaveState); - } - - actSaveState[0] = submenu->addAction("File..."); - actSaveState[0]->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); - actSaveState[0]->setData(QVariant(0)); - connect(actSaveState[0], &QAction::triggered, this, &MainWindow::onSaveState); - } - { - QMenu* submenu = menu->addMenu("Load state"); - - for (int i = 1; i < 9; i++) - { - actLoadState[i] = submenu->addAction(QString("%1").arg(i)); - actLoadState[i]->setShortcut(QKeySequence(Qt::Key_F1+i-1)); - actLoadState[i]->setData(QVariant(i)); - connect(actLoadState[i], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actLoadState[0] = submenu->addAction("File..."); - actLoadState[0]->setShortcut(QKeySequence(Qt::Key_F9)); - actLoadState[0]->setData(QVariant(0)); - connect(actLoadState[0], &QAction::triggered, this, &MainWindow::onLoadState); - } - - actUndoStateLoad = menu->addAction("Undo state load"); - actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12)); - connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad); - - menu->addSeparator(); - - actQuit = menu->addAction("Quit"); - connect(actQuit, &QAction::triggered, this, &MainWindow::onQuit); - actQuit->setShortcut(QKeySequence(QKeySequence::StandardKey::Quit)); - } - { - QMenu* menu = menubar->addMenu("System"); - - actPause = menu->addAction("Pause"); - actPause->setCheckable(true); - connect(actPause, &QAction::triggered, this, &MainWindow::onPause); - - actReset = menu->addAction("Reset"); - connect(actReset, &QAction::triggered, this, &MainWindow::onReset); - - actStop = menu->addAction("Stop"); - connect(actStop, &QAction::triggered, this, &MainWindow::onStop); - - actFrameStep = menu->addAction("Frame step"); - connect(actFrameStep, &QAction::triggered, this, &MainWindow::onFrameStep); - - menu->addSeparator(); - - actPowerManagement = menu->addAction("Power management"); - connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement); - - actDateTime = menu->addAction("Date and time"); - connect(actDateTime, &QAction::triggered, this, &MainWindow::onOpenDateTime); - - menu->addSeparator(); - - actEnableCheats = menu->addAction("Enable cheats"); - actEnableCheats->setCheckable(true); - connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats); - - //if (inst == 0) - { - actSetupCheats = menu->addAction("Setup cheat codes"); - actSetupCheats->setMenuRole(QAction::NoRole); - connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats); - - menu->addSeparator(); - actROMInfo = menu->addAction("ROM info"); - connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo); - - actRAMInfo = menu->addAction("RAM search"); - connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo); - - actTitleManager = menu->addAction("Manage DSi titles"); - connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager); - } - - { - menu->addSeparator(); - QMenu* submenu = menu->addMenu("Multiplayer"); - - actMPNewInstance = submenu->addAction("Launch new instance"); - connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance); - } - } - { - QMenu* menu = menubar->addMenu("Config"); - - actEmuSettings = menu->addAction("Emu settings"); - connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - -#ifdef __APPLE__ - actPreferences = menu->addAction("Preferences..."); - connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings); - actPreferences->setMenuRole(QAction::PreferencesRole); -#endif - - actInputConfig = menu->addAction("Input and hotkeys"); - connect(actInputConfig, &QAction::triggered, this, &MainWindow::onOpenInputConfig); - - actVideoSettings = menu->addAction("Video settings"); - connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings); - - actCameraSettings = menu->addAction("Camera settings"); - connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings); - - actAudioSettings = menu->addAction("Audio settings"); - connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings); - - actMPSettings = menu->addAction("Multiplayer settings"); - connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings); - - actWifiSettings = menu->addAction("Wifi settings"); - connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings); - - actFirmwareSettings = menu->addAction("Firmware settings"); - connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings); - - actInterfaceSettings = menu->addAction("Interface settings"); - connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings); - - actPathSettings = menu->addAction("Path settings"); - connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings); - - { - QMenu* submenu = menu->addMenu("Savestate settings"); - - actSavestateSRAMReloc = submenu->addAction("Separate savefiles"); - actSavestateSRAMReloc->setCheckable(true); - connect(actSavestateSRAMReloc, &QAction::triggered, this, &MainWindow::onChangeSavestateSRAMReloc); - } - - menu->addSeparator(); - - { - QMenu* submenu = menu->addMenu("Screen size"); - - for (int i = 0; i < 4; i++) - { - int data = i+1; - actScreenSize[i] = submenu->addAction(QString("%1x").arg(data)); - actScreenSize[i]->setData(QVariant(data)); - connect(actScreenSize[i], &QAction::triggered, this, &MainWindow::onChangeScreenSize); - } - } - { - QMenu* submenu = menu->addMenu("Screen rotation"); - grpScreenRotation = new QActionGroup(submenu); - - for (int i = 0; i < Frontend::screenRot_MAX; i++) - { - int data = i*90; - actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); - actScreenRotation[i]->setActionGroup(grpScreenRotation); - actScreenRotation[i]->setData(QVariant(i)); - actScreenRotation[i]->setCheckable(true); - } - - connect(grpScreenRotation, &QActionGroup::triggered, this, &MainWindow::onChangeScreenRotation); - } - { - QMenu* submenu = menu->addMenu("Screen gap"); - grpScreenGap = new QActionGroup(submenu); - - const int screengap[] = {0, 1, 8, 64, 90, 128}; - - for (int i = 0; i < 6; i++) - { - int data = screengap[i]; - actScreenGap[i] = submenu->addAction(QString("%1 px").arg(data)); - actScreenGap[i]->setActionGroup(grpScreenGap); - actScreenGap[i]->setData(QVariant(data)); - actScreenGap[i]->setCheckable(true); - } - - connect(grpScreenGap, &QActionGroup::triggered, this, &MainWindow::onChangeScreenGap); - } - { - QMenu* submenu = menu->addMenu("Screen layout"); - grpScreenLayout = new QActionGroup(submenu); - - const char* screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; - - for (int i = 0; i < Frontend::screenLayout_MAX; i++) - { - actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); - actScreenLayout[i]->setActionGroup(grpScreenLayout); - actScreenLayout[i]->setData(QVariant(i)); - actScreenLayout[i]->setCheckable(true); - } - - connect(grpScreenLayout, &QActionGroup::triggered, this, &MainWindow::onChangeScreenLayout); - - submenu->addSeparator(); - - actScreenSwap = submenu->addAction("Swap screens"); - actScreenSwap->setCheckable(true); - connect(actScreenSwap, &QAction::triggered, this, &MainWindow::onChangeScreenSwap); - } - { - QMenu* submenu = menu->addMenu("Screen sizing"); - grpScreenSizing = new QActionGroup(submenu); - - const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; - - for (int i = 0; i < Frontend::screenSizing_MAX; i++) - { - actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); - actScreenSizing[i]->setActionGroup(grpScreenSizing); - actScreenSizing[i]->setData(QVariant(i)); - actScreenSizing[i]->setCheckable(true); - } - - connect(grpScreenSizing, &QActionGroup::triggered, this, &MainWindow::onChangeScreenSizing); - - submenu->addSeparator(); - - actIntegerScaling = submenu->addAction("Force integer scaling"); - actIntegerScaling->setCheckable(true); - connect(actIntegerScaling, &QAction::triggered, this, &MainWindow::onChangeIntegerScaling); - } - { - QMenu* submenu = menu->addMenu("Aspect ratio"); - grpScreenAspectTop = new QActionGroup(submenu); - grpScreenAspectBot = new QActionGroup(submenu); - actScreenAspectTop = new QAction*[AspectRatiosNum]; - actScreenAspectBot = new QAction*[AspectRatiosNum]; - - for (int i = 0; i < 2; i++) - { - QActionGroup* group = grpScreenAspectTop; - QAction** actions = actScreenAspectTop; - - if (i == 1) - { - group = grpScreenAspectBot; - submenu->addSeparator(); - actions = actScreenAspectBot; - } - - for (int j = 0; j < AspectRatiosNum; j++) - { - auto ratio = aspectRatios[j]; - QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label); - actions[j] = submenu->addAction(label); - actions[j]->setActionGroup(group); - actions[j]->setData(QVariant(ratio.id)); - actions[j]->setCheckable(true); - } - - connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect); - } - } - - actScreenFiltering = menu->addAction("Screen filtering"); - actScreenFiltering->setCheckable(true); - connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); - - actShowOSD = menu->addAction("Show OSD"); - actShowOSD->setCheckable(true); - connect(actShowOSD, &QAction::triggered, this, &MainWindow::onChangeShowOSD); - - menu->addSeparator(); - - actLimitFramerate = menu->addAction("Limit framerate"); - actLimitFramerate->setCheckable(true); - connect(actLimitFramerate, &QAction::triggered, this, &MainWindow::onChangeLimitFramerate); - - actAudioSync = menu->addAction("Audio sync"); - actAudioSync->setCheckable(true); - connect(actAudioSync, &QAction::triggered, this, &MainWindow::onChangeAudioSync); - } - setMenuBar(menubar); - - resize(Config::WindowWidth, Config::WindowHeight); - - if (Config::FirmwareUsername == "Arisotura") - actMPNewInstance->setText("Fart"); - -#ifdef Q_OS_MAC - QPoint screenCenter = screen()->availableGeometry().center(); - QRect frameGeo = frameGeometry(); - frameGeo.moveCenter(screenCenter); - move(frameGeo.topLeft()); -#endif - - if (oldMax) - showMaximized(); - else - show(); - - createScreenPanel(); - - actEjectCart->setEnabled(false); - actEjectGBACart->setEnabled(false); - - if (Config::ConsoleType == 1) - { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - } - - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - actImportSavefile->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); - - actEnableCheats->setChecked(Config::EnableCheats); - - actROMInfo->setEnabled(false); - actRAMInfo->setEnabled(false); - - actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM); - - actScreenRotation[Config::ScreenRotation]->setChecked(true); - - for (int i = 0; i < 6; i++) - { - if (actScreenGap[i]->data().toInt() == Config::ScreenGap) - { - actScreenGap[i]->setChecked(true); - break; - } - } - - actScreenLayout[Config::ScreenLayout]->setChecked(true); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - actIntegerScaling->setChecked(Config::IntegerScaling); - - actScreenSwap->setChecked(Config::ScreenSwap); - - for (int i = 0; i < AspectRatiosNum; i++) - { - if (Config::ScreenAspectTop == aspectRatios[i].id) - actScreenAspectTop[i]->setChecked(true); - if (Config::ScreenAspectBot == aspectRatios[i].id) - actScreenAspectBot[i]->setChecked(true); - } - - actScreenFiltering->setChecked(Config::ScreenFilter); - actShowOSD->setChecked(Config::ShowOSD); - - actLimitFramerate->setChecked(Config::LimitFPS); - actAudioSync->setChecked(Config::AudioSync); - - if (inst > 0) - { - actEmuSettings->setEnabled(false); - actVideoSettings->setEnabled(false); - actMPSettings->setEnabled(false); - actWifiSettings->setEnabled(false); - actInterfaceSettings->setEnabled(false); - -#ifdef __APPLE__ - actPreferences->setEnabled(false); -#endif // __APPLE__ - } -} - -MainWindow::~MainWindow() -{ - delete[] actScreenAspectTop; - delete[] actScreenAspectBot; -} - -void MainWindow::closeEvent(QCloseEvent* event) -{ - if (hasOGL) - { - // we intentionally don't unpause here - emuThread->emuPause(); - emuThread->deinitContext(); - } - - QMainWindow::closeEvent(event); -} - -void MainWindow::createScreenPanel() -{ - hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); - - if (hasOGL) - { - ScreenPanelGL* panelGL = new ScreenPanelGL(this); - panelGL->show(); - - panel = panelGL; - panelWidget = panelGL; - - panelGL->createContext(); - } - - if (!hasOGL) - { - ScreenPanelNative* panelNative = new ScreenPanelNative(this); - panel = panelNative; - panelWidget = panelNative; - panelWidget->show(); - } - setCentralWidget(panelWidget); - - actScreenFiltering->setEnabled(hasOGL); - - connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged())); - emit screenLayoutChange(); -} - -GL::Context* MainWindow::getOGLContext() -{ - if (!hasOGL) return nullptr; - - ScreenPanelGL* glpanel = static_cast(panel); - return glpanel->getContext(); -} - -void MainWindow::resizeEvent(QResizeEvent* event) -{ - int w = event->size().width(); - int h = event->size().height(); - - if (!isFullScreen()) - { - // this is ugly - // thing is, when maximizing the window, we first receive the resizeEvent - // with a new size matching the screen, then the changeEvent telling us that - // the maximized flag was updated - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = isMaximized(); - - Config::WindowWidth = w; - Config::WindowHeight = h; - } -} - -void MainWindow::changeEvent(QEvent* event) -{ - if (isMaximized() && !oldMax) - { - Config::WindowWidth = oldW; - Config::WindowHeight = oldH; - } - - Config::WindowMaximized = isMaximized() ? 1:0; -} - -void MainWindow::keyPressEvent(QKeyEvent* event) -{ - if (event->isAutoRepeat()) return; - - // TODO!! REMOVE ME IN RELEASE BUILDS!! - //if (event->key() == Qt::Key_F11) NDS::debug(0); - - Input::KeyPress(event); -} - -void MainWindow::keyReleaseEvent(QKeyEvent* event) -{ - if (event->isAutoRepeat()) return; - - Input::KeyRelease(event); -} - - -void MainWindow::dragEnterEvent(QDragEnterEvent* event) -{ - if (!event->mimeData()->hasUrls()) return; - - QList urls = event->mimeData()->urls(); - if (urls.count() > 1) return; // not handling more than one file at once - - QString filename = urls.at(0).toLocalFile(); - - if (FileIsSupportedFiletype(filename)) - event->acceptProposedAction(); -} - -void MainWindow::dropEvent(QDropEvent* event) -{ - if (!event->mimeData()->hasUrls()) return; - - QList urls = event->mimeData()->urls(); - if (urls.count() > 1) return; // not handling more than one file at once - - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - const QStringList file = splitArchivePath(urls.at(0).toLocalFile(), false); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - const QString filename = file.last(); - const bool romInsideArchive = file.size() > 1; - const auto matchMode = romInsideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchMode); - - bool isNdsRom = NdsRomByExtension(filename) || NdsRomByMimetype(mimetype); - bool isGbaRom = GbaRomByExtension(filename) || GbaRomByMimetype(mimetype); - isNdsRom |= ZstdNdsRomByExtension(filename); - isGbaRom |= ZstdGbaRomByExtension(filename); - - if (isNdsRom) - { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); - emuThread->emuUnpause(); - return; - } - - const QString barredFilename = file.join('|'); - recentFileList.removeAll(barredFilename); - recentFileList.prepend(barredFilename); - updateRecentFilesMenu(); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - - updateCartInserted(false); - } - else if (isGbaRom) - { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - - updateCartInserted(true); - } - else - { - QMessageBox::critical(this, "melonDS", "The file could not be recognized as a DS or GBA ROM."); - emuThread->emuUnpause(); - return; - } -} - -void MainWindow::focusInEvent(QFocusEvent* event) -{ - AudioInOut::AudioMute(mainWindow); -} - -void MainWindow::focusOutEvent(QFocusEvent* event) -{ - AudioInOut::AudioMute(mainWindow); -} - -void MainWindow::onAppStateChanged(Qt::ApplicationState state) -{ - if (state == Qt::ApplicationInactive) - { - if (Config::PauseLostFocus && emuThread->emuIsRunning()) - emuThread->emuPause(); - } - else if (state == Qt::ApplicationActive) - { - if (Config::PauseLostFocus && !pausedManually) - emuThread->emuUnpause(); - } -} - -bool MainWindow::verifySetup() -{ - QString res = ROMManager::VerifySetup(); - if (!res.isEmpty()) - { - QMessageBox::critical(this, "melonDS", res); - return false; - } - - return true; -} - -bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) -{ - if (!verifySetup()) - { - return false; - } - - bool gbaloaded = false; - if (!gbafile.isEmpty()) - { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); - return false; - } - - gbaloaded = true; - } - - bool ndsloaded = false; - if (!file.isEmpty()) - { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - return false; - } - recentFileList.removeAll(file.join("|")); - recentFileList.prepend(file.join("|")); - updateRecentFilesMenu(); - ndsloaded = true; - } - - if (boot) - { - if (ndsloaded) - { - emuThread->NDS->Start(); - emuThread->emuRun(); - } - else - { - onBootFirmware(); - } - } - - updateCartInserted(false); - - if (gbaloaded) - { - updateCartInserted(true); - } - - return true; -} - -QStringList MainWindow::splitArchivePath(const QString& filename, bool useMemberSyntax) -{ - if (filename.isEmpty()) return {}; - -#ifdef ARCHIVE_SUPPORT_ENABLED - if (useMemberSyntax) - { - const QStringList filenameParts = filename.split('|'); - if (filenameParts.size() > 2) - { - QMessageBox::warning(this, "melonDS", "This path contains too many '|'."); - return {}; - } - - if (filenameParts.size() == 2) - { - const QString archive = filenameParts.at(0); - if (!QFileInfo(archive).exists()) - { - QMessageBox::warning(this, "melonDS", "This archive does not exist."); - return {}; - } - - const QString subfile = filenameParts.at(1); - if (!Archive::ListArchive(archive).contains(subfile)) - { - QMessageBox::warning(this, "melonDS", "This archive does not contain the desired file."); - return {}; - } - - return filenameParts; - } - } -#endif - - if (!QFileInfo(filename).exists()) - { - QMessageBox::warning(this, "melonDS", "This ROM file does not exist."); - return {}; - } - -#ifdef ARCHIVE_SUPPORT_ENABLED - if (SupportedArchiveByExtension(filename) - || SupportedArchiveByMimetype(QMimeDatabase().mimeTypeForFile(filename))) - { - const QString subfile = pickFileFromArchive(filename); - if (subfile.isEmpty()) - return {}; - - return { filename, subfile }; - } -#endif - - return { filename }; -} - -QString MainWindow::pickFileFromArchive(QString archiveFileName) -{ - QVector archiveROMList = Archive::ListArchive(archiveFileName); - - if (archiveROMList.size() <= 1) - { - if (!archiveROMList.isEmpty() && archiveROMList.at(0) == "OK") - QMessageBox::warning(this, "melonDS", "This archive is empty."); - else - QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions."); - return QString(); - } - - archiveROMList.removeFirst(); - - const auto notSupportedRom = [&](const auto& filename){ - if (NdsRomByExtension(filename) || GbaRomByExtension(filename)) - return false; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension); - return !(NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype)); - }; - - archiveROMList.erase(std::remove_if(archiveROMList.begin(), archiveROMList.end(), notSupportedRom), - archiveROMList.end()); - - if (archiveROMList.isEmpty()) - { - QMessageBox::warning(this, "melonDS", "This archive does not contain any supported ROMs."); - return QString(); - } - - if (archiveROMList.size() == 1) - return archiveROMList.first(); - - bool ok; - const QString toLoad = QInputDialog::getItem( - this, "melonDS", - "This archive contains multiple files. Select which ROM you want to load.", - archiveROMList.toList(), 0, false, &ok - ); - - if (ok) return toLoad; - - // User clicked on cancel - - return QString(); -} - -QStringList MainWindow::pickROM(bool gba) -{ - const QString console = gba ? "GBA" : "DS"; - const QStringList& romexts = gba ? GbaRomExtensions : NdsRomExtensions; - - QString rawROMs = romexts.join(" *"); - QString extraFilters = ";;" + console + " ROMs (*" + rawROMs; - QString allROMs = rawROMs; - - QString zstdROMs = "*" + romexts.join(".zst *") + ".zst"; - extraFilters += ");;Zstandard-compressed " + console + " ROMs (" + zstdROMs + ")"; - allROMs += " " + zstdROMs; - -#ifdef ARCHIVE_SUPPORT_ENABLED - QString archives = "*" + ArchiveExtensions.join(" *"); - extraFilters += ";;Archives (" + archives + ")"; - allROMs += " " + archives; -#endif - extraFilters += ";;All files (*.*)"; - - const QString filename = QFileDialog::getOpenFileName( - this, "Open " + console + " ROM", - QString::fromStdString(Config::LastROMFolder), - "All supported files (*" + allROMs + ")" + extraFilters - ); - - if (filename.isEmpty()) return {}; - - Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); - return splitArchivePath(filename, false); -} - -void MainWindow::updateCartInserted(bool gba) -{ - bool inserted; - if (gba) - { - inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); - actEjectGBACart->setEnabled(inserted); - } - else - { - inserted = ROMManager::CartInserted(); - actCurrentCart->setText("DS slot: " + ROMManager::CartLabel()); - actEjectCart->setEnabled(inserted); - actImportSavefile->setEnabled(inserted); - actSetupCheats->setEnabled(inserted); - actROMInfo->setEnabled(inserted); - actRAMInfo->setEnabled(inserted); - } -} - -void MainWindow::onOpenFile() -{ - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - QStringList file = pickROM(false); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - QString filename = file.join('|'); - recentFileList.removeAll(filename); - recentFileList.prepend(filename); - updateRecentFilesMenu(); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - - updateCartInserted(false); -} - -void MainWindow::onClearRecentFiles() -{ - recentFileList.clear(); - for (int i = 0; i < 10; i++) - Config::RecentROMList[i] = ""; - updateRecentFilesMenu(); -} - -void MainWindow::updateRecentFilesMenu() -{ - recentMenu->clear(); - - for (int i = 0; i < recentFileList.size(); ++i) - { - if (i >= 10) break; - - QString item_full = recentFileList.at(i); - QString item_display = item_full; - int itemlen = item_full.length(); - const int maxlen = 100; - if (itemlen > maxlen) - { - int cut_start = 0; - while (item_full[cut_start] != '/' && item_full[cut_start] != '\\' && - cut_start < itemlen) - cut_start++; - - int cut_end = itemlen-1; - while (((item_full[cut_end] != '/' && item_full[cut_end] != '\\') || - (cut_start+4+(itemlen-cut_end) < maxlen)) && - cut_end > 0) - cut_end--; - - item_display.truncate(cut_start+1); - item_display += "..."; - item_display += QString(item_full).remove(0, cut_end); - } - - QAction *actRecentFile_i = recentMenu->addAction(QString("%1. %2").arg(i+1).arg(item_display)); - actRecentFile_i->setData(item_full); - connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); - - Config::RecentROMList[i] = recentFileList.at(i).toStdString(); - } - - while (recentFileList.size() > 10) - recentFileList.removeLast(); - - recentMenu->addSeparator(); - - QAction *actClearRecentList = recentMenu->addAction("Clear"); - connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles); - - if (recentFileList.empty()) - actClearRecentList->setEnabled(false); - - Config::Save(); -} - -void MainWindow::onClickRecentFile() -{ - QAction *act = (QAction *)sender(); - QString filename = act->data().toString(); - - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - const QStringList file = splitArchivePath(filename, true); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - recentFileList.removeAll(filename); - recentFileList.prepend(filename); - updateRecentFilesMenu(); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); - - updateCartInserted(false); -} - -void MainWindow::onBootFirmware() -{ - emuThread->emuPause(); - - if (!verifySetup()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadBIOS(emuThread)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); - emuThread->emuUnpause(); - return; - } - - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); - emuThread->emuRun(); -} - -void MainWindow::onInsertCart() -{ - emuThread->emuPause(); - - QStringList file = pickROM(false); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadROM(emuThread, file, false)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - - updateCartInserted(false); -} - -void MainWindow::onEjectCart() -{ - emuThread->emuPause(); - - ROMManager::EjectCart(*emuThread->NDS); - - emuThread->emuUnpause(); - - updateCartInserted(false); -} - -void MainWindow::onInsertGBACart() -{ - emuThread->emuPause(); - - QStringList file = pickROM(true); - if (file.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - emuThread->emuUnpause(); - return; - } - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onInsertGBAAddon() -{ - QAction* act = (QAction*)sender(); - int type = act->data().toInt(); - - emuThread->emuPause(); - - ROMManager::LoadGBAAddon(*emuThread->NDS, type); - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onEjectGBACart() -{ - emuThread->emuPause(); - - ROMManager::EjectGBACart(*emuThread->NDS); - - emuThread->emuUnpause(); - - updateCartInserted(true); -} - -void MainWindow::onSaveState() -{ - int slot = ((QAction*)sender())->data().toInt(); - - emuThread->emuPause(); - - std::string filename; - if (slot > 0) - { - filename = ROMManager::GetSavestateName(slot); - } - else - { - // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getSaveFileName(this, - "Save state", - QString::fromStdString(Config::LastROMFolder), - "melonDS savestates (*.mln);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - filename = qfilename.toStdString(); - } - - if (ROMManager::SaveState(*emuThread->NDS, filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State saved to slot %d", slot); - else sprintf(msg, "State saved to file"); - OSD::AddMessage(0, msg); - - actLoadState[slot]->setEnabled(true); - } - else - { - OSD::AddMessage(0xFFA0A0, "State save failed"); - } - - emuThread->emuUnpause(); -} - -void MainWindow::onLoadState() -{ - int slot = ((QAction*)sender())->data().toInt(); - - emuThread->emuPause(); - - std::string filename; - if (slot > 0) - { - filename = ROMManager::GetSavestateName(slot); - } - else - { - // TODO: specific 'last directory' for savestate files? - QString qfilename = QFileDialog::getOpenFileName(this, - "Load state", - QString::fromStdString(Config::LastROMFolder), - "melonDS savestates (*.ml*);;Any file (*.*)"); - if (qfilename.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - filename = qfilename.toStdString(); - } - - if (!Platform::FileExists(filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State slot %d is empty", slot); - else sprintf(msg, "State file does not exist"); - OSD::AddMessage(0xFFA0A0, msg); - - emuThread->emuUnpause(); - return; - } - - if (ROMManager::LoadState(*emuThread->NDS, filename)) - { - char msg[64]; - if (slot > 0) sprintf(msg, "State loaded from slot %d", slot); - else sprintf(msg, "State loaded from file"); - OSD::AddMessage(0, msg); - - actUndoStateLoad->setEnabled(true); - } - else - { - OSD::AddMessage(0xFFA0A0, "State load failed"); - } - - emuThread->emuUnpause(); -} - -void MainWindow::onUndoStateLoad() -{ - emuThread->emuPause(); - ROMManager::UndoStateLoad(*emuThread->NDS); - emuThread->emuUnpause(); - - OSD::AddMessage(0, "State load undone"); -} - -void MainWindow::onImportSavefile() -{ - emuThread->emuPause(); - QString path = QFileDialog::getOpenFileName(this, - "Select savefile", - QString::fromStdString(Config::LastROMFolder), - "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); - - if (path.isEmpty()) - { - emuThread->emuUnpause(); - return; - } - - Platform::FileHandle* f = Platform::OpenFile(path.toStdString(), Platform::FileMode::Read); - if (!f) - { - QMessageBox::critical(this, "melonDS", "Could not open the given savefile."); - emuThread->emuUnpause(); - return; - } - - if (RunningSomething) - { - if (QMessageBox::warning(this, - "melonDS", - "The emulation will be reset and the current savefile overwritten.", - QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) - { - emuThread->emuUnpause(); - return; - } - - ROMManager::Reset(emuThread); - } - - u32 len = FileLength(f); - - u8* data = new u8[len]; - Platform::FileRewind(f); - Platform::FileRead(data, len, 1, f); - - assert(emuThread->NDS != nullptr); - emuThread->NDS->LoadSave(data, len); - delete[] data; - - CloseFile(f); - emuThread->emuUnpause(); -} - -void MainWindow::onQuit() -{ -#ifndef _WIN32 - signalSn->setEnabled(false); -#endif - QApplication::quit(); -} - - -void MainWindow::onPause(bool checked) -{ - if (!RunningSomething) return; - - if (checked) - { - emuThread->emuPause(); - OSD::AddMessage(0, "Paused"); - pausedManually = true; - } - else - { - emuThread->emuUnpause(); - OSD::AddMessage(0, "Resumed"); - pausedManually = false; - } -} - -void MainWindow::onReset() -{ - if (!RunningSomething) return; - - emuThread->emuPause(); - - actUndoStateLoad->setEnabled(false); - - ROMManager::Reset(emuThread); - - OSD::AddMessage(0, "Reset"); - emuThread->emuRun(); -} - -void MainWindow::onStop() -{ - if (!RunningSomething) return; - - emuThread->emuPause(); - emuThread->NDS->Stop(); -} - -void MainWindow::onFrameStep() -{ - if (!RunningSomething) return; - - emuThread->emuFrameStep(); -} - -void MainWindow::onOpenDateTime() -{ - DateTimeDialog* dlg = DateTimeDialog::openDlg(this); -} - -void MainWindow::onOpenPowerManagement() -{ - PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); -} - -void MainWindow::onEnableCheats(bool checked) -{ - Config::EnableCheats = checked?1:0; - ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); -} - -void MainWindow::onSetupCheats() -{ - emuThread->emuPause(); - - CheatsDialog* dlg = CheatsDialog::openDlg(this); - connect(dlg, &CheatsDialog::finished, this, &MainWindow::onCheatsDialogFinished); -} - -void MainWindow::onCheatsDialogFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onROMInfo() -{ - auto cart = emuThread->NDS->NDSCartSlot.GetCart(); - if (cart) - ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); -} - -void MainWindow::onRAMInfo() -{ - RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); -} - -void MainWindow::onOpenTitleManager() -{ - TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this); -} - -void MainWindow::onMPNewInstance() -{ - //QProcess::startDetached(QApplication::applicationFilePath()); - QProcess newinst; - newinst.setProgram(QApplication::applicationFilePath()); - newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1)); - -#ifdef __WIN32__ - newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args) - { - args->flags |= CREATE_NEW_CONSOLE; - }); -#endif - - newinst.startDetached(); -} - -void MainWindow::onOpenEmuSettings() -{ - emuThread->emuPause(); - - EmuSettingsDialog* dlg = EmuSettingsDialog::openDlg(this); - connect(dlg, &EmuSettingsDialog::finished, this, &MainWindow::onEmuSettingsDialogFinished); -} - -void MainWindow::onEmuSettingsDialogFinished(int res) -{ - emuThread->emuUnpause(); - - if (Config::ConsoleType == 1) - { - actInsertGBACart->setEnabled(false); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(false); - actEjectGBACart->setEnabled(false); - } - else - { - actInsertGBACart->setEnabled(true); - for (int i = 0; i < 1; i++) - actInsertGBAAddon[i]->setEnabled(true); - actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); - } - - if (EmuSettingsDialog::needsReset) - onReset(); - - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); - - if (!RunningSomething) - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); -} - -void MainWindow::onOpenInputConfig() -{ - emuThread->emuPause(); - - InputConfigDialog* dlg = InputConfigDialog::openDlg(this); - connect(dlg, &InputConfigDialog::finished, this, &MainWindow::onInputConfigFinished); -} - -void MainWindow::onInputConfigFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onOpenVideoSettings() -{ - VideoSettingsDialog* dlg = VideoSettingsDialog::openDlg(this); - connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings); -} - -void MainWindow::onOpenCameraSettings() -{ - emuThread->emuPause(); - - camStarted[0] = camManager[0]->isStarted(); - camStarted[1] = camManager[1]->isStarted(); - if (camStarted[0]) camManager[0]->stop(); - if (camStarted[1]) camManager[1]->stop(); - - CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this); - connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished); -} - -void MainWindow::onCameraSettingsFinished(int res) -{ - if (camStarted[0]) camManager[0]->start(); - if (camStarted[1]) camManager[1]->start(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenAudioSettings() -{ - AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this, emuThread->emuIsActive(), emuThread); - connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); - connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); - connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); - connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); -} - -void MainWindow::onOpenFirmwareSettings() -{ - emuThread->emuPause(); - - FirmwareSettingsDialog* dlg = FirmwareSettingsDialog::openDlg(this); - connect(dlg, &FirmwareSettingsDialog::finished, this, &MainWindow::onFirmwareSettingsFinished); -} - -void MainWindow::onFirmwareSettingsFinished(int res) -{ - if (FirmwareSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenPathSettings() -{ - emuThread->emuPause(); - - PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this); - connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished); -} - -void MainWindow::onPathSettingsFinished(int res) -{ - if (PathSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onUpdateAudioSettings() -{ - assert(emuThread->NDS != nullptr); - emuThread->NDS->SPU.SetInterpolation(Config::AudioInterp); - - if (Config::AudioBitDepth == 0) - emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); - else - emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); -} - -void MainWindow::onAudioSettingsFinished(int res) -{ - AudioInOut::UpdateSettings(*emuThread->NDS); -} - -void MainWindow::onOpenMPSettings() -{ - emuThread->emuPause(); - - MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this); - connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished); -} - -void MainWindow::onMPSettingsFinished(int res) -{ - AudioInOut::AudioMute(mainWindow); - LocalMP::SetRecvTimeout(Config::MPRecvTimeout); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenWifiSettings() -{ - emuThread->emuPause(); - - WifiSettingsDialog* dlg = WifiSettingsDialog::openDlg(this); - connect(dlg, &WifiSettingsDialog::finished, this, &MainWindow::onWifiSettingsFinished); -} - -void MainWindow::onWifiSettingsFinished(int res) -{ - Platform::LAN_DeInit(); - Platform::LAN_Init(); - - if (WifiSettingsDialog::needsReset) - onReset(); - - emuThread->emuUnpause(); -} - -void MainWindow::onOpenInterfaceSettings() -{ - emuThread->emuPause(); - InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); - connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); - connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); -} - -void MainWindow::onUpdateMouseTimer() -{ - panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); -} - -void MainWindow::onInterfaceSettingsFinished(int res) -{ - emuThread->emuUnpause(); -} - -void MainWindow::onChangeSavestateSRAMReloc(bool checked) -{ - Config::SavestateRelocSRAM = checked?1:0; -} - -void MainWindow::onChangeScreenSize() -{ - int factor = ((QAction*)sender())->data().toInt(); - QSize diff = size() - panelWidget->size(); - resize(panel->screenGetMinSize(factor) + diff); -} - -void MainWindow::onChangeScreenRotation(QAction* act) -{ - int rot = act->data().toInt(); - Config::ScreenRotation = rot; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenGap(QAction* act) -{ - int gap = act->data().toInt(); - Config::ScreenGap = gap; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenLayout(QAction* act) -{ - int layout = act->data().toInt(); - Config::ScreenLayout = layout; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenSwap(bool checked) -{ - Config::ScreenSwap = checked?1:0; - - // Swap between top and bottom screen when displaying one screen. - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) - { - // Bottom Screen. - Config::ScreenSizing = Frontend::screenSizing_BotOnly; - actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - } - else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) - { - // Top Screen. - Config::ScreenSizing = Frontend::screenSizing_TopOnly; - actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - } - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenSizing(QAction* act) -{ - int sizing = act->data().toInt(); - Config::ScreenSizing = sizing; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenAspect(QAction* act) -{ - int aspect = act->data().toInt(); - QActionGroup* group = act->actionGroup(); - - if (group == grpScreenAspectTop) - { - Config::ScreenAspectTop = aspect; - } - else - { - Config::ScreenAspectBot = aspect; - } - - emit screenLayoutChange(); -} - -void MainWindow::onChangeIntegerScaling(bool checked) -{ - Config::IntegerScaling = checked?1:0; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeScreenFiltering(bool checked) -{ - Config::ScreenFilter = checked?1:0; - - emit screenLayoutChange(); -} - -void MainWindow::onChangeShowOSD(bool checked) -{ - Config::ShowOSD = checked?1:0; -} -void MainWindow::onChangeLimitFramerate(bool checked) -{ - Config::LimitFPS = checked?1:0; -} - -void MainWindow::onChangeAudioSync(bool checked) -{ - Config::AudioSync = checked?1:0; -} - - -void MainWindow::onTitleUpdate(QString title) -{ - setWindowTitle(title); -} - -void ToggleFullscreen(MainWindow* mainWindow) -{ - if (!mainWindow->isFullScreen()) - { - mainWindow->showFullScreen(); - mainWindow->menuBar()->setFixedHeight(0); // Don't use hide() as menubar actions stop working - } - else - { - mainWindow->showNormal(); - int menuBarHeight = mainWindow->menuBar()->sizeHint().height(); - mainWindow->menuBar()->setFixedHeight(menuBarHeight); - } -} - -void MainWindow::onFullscreenToggled() -{ - ToggleFullscreen(this); -} - -void MainWindow::onScreenEmphasisToggled() -{ - int currentSizing = Config::ScreenSizing; - if (currentSizing == Frontend::screenSizing_EmphTop) - { - Config::ScreenSizing = Frontend::screenSizing_EmphBot; - } - else if (currentSizing == Frontend::screenSizing_EmphBot) - { - Config::ScreenSizing = Frontend::screenSizing_EmphTop; - } - - emit screenLayoutChange(); -} - -void MainWindow::onEmuStart() -{ - for (int i = 1; i < 9; i++) - { - actSaveState[i]->setEnabled(true); - actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); - } - actSaveState[0]->setEnabled(true); - actLoadState[0]->setEnabled(true); - actUndoStateLoad->setEnabled(false); - - actPause->setEnabled(true); - actPause->setChecked(false); - actReset->setEnabled(true); - actStop->setEnabled(true); - actFrameStep->setEnabled(true); - - actDateTime->setEnabled(false); - actPowerManagement->setEnabled(true); - - actTitleManager->setEnabled(false); -} - -void MainWindow::onEmuStop() -{ - emuThread->emuPause(); - - for (int i = 0; i < 9; i++) - { - actSaveState[i]->setEnabled(false); - actLoadState[i]->setEnabled(false); - } - actUndoStateLoad->setEnabled(false); - - actPause->setEnabled(false); - actReset->setEnabled(false); - actStop->setEnabled(false); - actFrameStep->setEnabled(false); - - actDateTime->setEnabled(true); - actPowerManagement->setEnabled(false); - - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); -} - -void MainWindow::onUpdateVideoSettings(bool glchange) -{ - if (glchange) - { - emuThread->emuPause(); - if (hasOGL) emuThread->deinitContext(); - - delete panel; - createScreenPanel(); - connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint())); - } - - videoSettingsDirty = true; - - if (glchange) - { - if (hasOGL) emuThread->initContext(); - emuThread->emuUnpause(); - } -} void emuStop() @@ -3297,8 +264,11 @@ void emuStop() MelonApplication::MelonApplication(int& argc, char** argv) : QApplication(argc, argv) { -#ifndef __APPLE__ +#if !defined(Q_OS_APPLE) setWindowIcon(QIcon(":/melon-icon")); + #if defined(Q_OS_UNIX) + setDesktopFileName(QString("net.kuribo64.melonDS")); + #endif #endif } diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 72ebfb19..51157c6c 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -22,228 +22,18 @@ #include "glad/glad.h" #include -#include #include #include #include #include #include #include -#include #include #include -#include - -#include - +#include "Window.h" +#include "EmuThread.h" #include "FrontendUtil.h" -#include "duckstation/gl/context.h" - -namespace melonDS -{ -class NDS; -} - -class EmuThread : public QThread -{ - Q_OBJECT - void run() override; - -public: - explicit EmuThread(QObject* parent = nullptr); - - void changeWindowTitle(char* title); - - // to be called from the UI thread - void emuRun(); - void emuPause(); - void emuUnpause(); - void emuStop(); - void emuFrameStep(); - - bool emuIsRunning(); - bool emuIsActive(); - - void initContext(); - void deinitContext(); - - int FrontBuffer = 0; - QMutex FrontBufferLock; - - void updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix); - void RecreateConsole(); - std::unique_ptr NDS; // TODO: Proper encapsulation and synchronization -signals: - void windowUpdate(); - void windowTitleChange(QString title); - - void windowEmuStart(); - void windowEmuStop(); - void windowEmuPause(); - void windowEmuReset(); - void windowEmuFrameStep(); - - void windowLimitFPSChange(); - - void screenLayoutChange(); - - void windowFullscreenToggle(); - - void swapScreensToggle(); - void screenEmphasisToggle(); - - void syncVolumeLevel(); - -private: - std::unique_ptr CreateConsole(); - void drawScreenGL(); - void initOpenGL(); - void deinitOpenGL(); - - enum EmuStatusKind - { - emuStatus_Exit, - emuStatus_Running, - emuStatus_Paused, - emuStatus_FrameStep, - }; - std::atomic EmuStatus; - - EmuStatusKind PrevEmuStatus; - EmuStatusKind EmuRunning; - - constexpr static int EmuPauseStackRunning = 0; - constexpr static int EmuPauseStackPauseThreshold = 1; - int EmuPauseStack; - - enum ContextRequestKind - { - contextRequest_None = 0, - contextRequest_InitGL, - contextRequest_DeInitGL - }; - std::atomic ContextRequest = contextRequest_None; - - GL::Context* oglContext = nullptr; - GLuint screenVertexBuffer, screenVertexArray; - GLuint screenTexture; - GLuint screenShaderProgram[3]; - GLuint screenShaderTransformULoc, screenShaderScreenSizeULoc; - - QMutex screenSettingsLock; - WindowInfo windowInfo; - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - bool filter; - - int lastScreenWidth = -1, lastScreenHeight = -1; -}; - - -class ScreenHandler -{ - Q_GADGET - -public: - ScreenHandler(QWidget* widget); - virtual ~ScreenHandler(); - QTimer* setupMouseTimer(); - void updateMouseTimer(); - QTimer* mouseTimer; - QSize screenGetMinSize(int factor); - -protected: - void screenSetupLayout(int w, int h); - - void screenOnMousePress(QMouseEvent* event); - void screenOnMouseRelease(QMouseEvent* event); - void screenOnMouseMove(QMouseEvent* event); - - void screenHandleTablet(QTabletEvent* event); - void screenHandleTouch(QTouchEvent* event); - - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - - bool touching = false; - - void showCursor(); -}; - - -class ScreenPanelNative : public QWidget, public ScreenHandler -{ - Q_OBJECT - -public: - explicit ScreenPanelNative(QWidget* parent); - virtual ~ScreenPanelNative(); - -protected: - void paintEvent(QPaintEvent* event) override; - - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; -private slots: - void onScreenLayoutChanged(); - -private: - void setupScreenLayout(); - - QImage screen[2]; - QTransform screenTrans[Frontend::MaxScreenTransforms]; -}; - - -class ScreenPanelGL : public QWidget, public ScreenHandler -{ - Q_OBJECT - -public: - explicit ScreenPanelGL(QWidget* parent); - virtual ~ScreenPanelGL(); - - std::optional getWindowInfo(); - - bool createContext(); - - GL::Context* getContext() { return glContext.get(); } - - void transferLayout(EmuThread* thread); -protected: - - qreal devicePixelRatioFromScreen() const; - int scaledWindowWidth() const; - int scaledWindowHeight() const; - - QPaintEngine* paintEngine() const override; - - void resizeEvent(QResizeEvent* event) override; - - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - - void tabletEvent(QTabletEvent* event) override; - bool event(QEvent* event) override; - -private slots: - void onScreenLayoutChanged(); - -private: - void setupScreenLayout(); - - std::unique_ptr glContext; -}; class MelonApplication : public QApplication { @@ -254,199 +44,4 @@ public: bool event(QEvent* event) override; }; -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget* parent = nullptr); - ~MainWindow(); - - bool hasOGL; - GL::Context* getOGLContext(); - - bool preloadROMs(QStringList file, QStringList gbafile, bool boot); - QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); - - void onAppStateChanged(Qt::ApplicationState state); - -protected: - void resizeEvent(QResizeEvent* event) override; - void changeEvent(QEvent* event) override; - - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - - void dragEnterEvent(QDragEnterEvent* event) override; - void dropEvent(QDropEvent* event) override; - - void focusInEvent(QFocusEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - -signals: - void screenLayoutChange(); - -private slots: - void onOpenFile(); - void onClickRecentFile(); - void onClearRecentFiles(); - void onBootFirmware(); - void onInsertCart(); - void onEjectCart(); - void onInsertGBACart(); - void onInsertGBAAddon(); - void onEjectGBACart(); - void onSaveState(); - void onLoadState(); - void onUndoStateLoad(); - void onImportSavefile(); - void onQuit(); - - void onPause(bool checked); - void onReset(); - void onStop(); - void onFrameStep(); - void onOpenPowerManagement(); - void onOpenDateTime(); - void onEnableCheats(bool checked); - void onSetupCheats(); - void onCheatsDialogFinished(int res); - void onROMInfo(); - void onRAMInfo(); - void onOpenTitleManager(); - void onMPNewInstance(); - - void onOpenEmuSettings(); - void onEmuSettingsDialogFinished(int res); - void onOpenInputConfig(); - void onInputConfigFinished(int res); - void onOpenVideoSettings(); - void onOpenCameraSettings(); - void onCameraSettingsFinished(int res); - void onOpenAudioSettings(); - void onUpdateAudioSettings(); - void onAudioSettingsFinished(int res); - void onOpenMPSettings(); - void onMPSettingsFinished(int res); - void onOpenWifiSettings(); - void onWifiSettingsFinished(int res); - void onOpenFirmwareSettings(); - void onFirmwareSettingsFinished(int res); - void onOpenPathSettings(); - void onPathSettingsFinished(int res); - void onOpenInterfaceSettings(); - void onInterfaceSettingsFinished(int res); - void onUpdateMouseTimer(); - void onChangeSavestateSRAMReloc(bool checked); - void onChangeScreenSize(); - void onChangeScreenRotation(QAction* act); - void onChangeScreenGap(QAction* act); - void onChangeScreenLayout(QAction* act); - void onChangeScreenSwap(bool checked); - void onChangeScreenSizing(QAction* act); - void onChangeScreenAspect(QAction* act); - void onChangeIntegerScaling(bool checked); - void onChangeScreenFiltering(bool checked); - void onChangeShowOSD(bool checked); - void onChangeLimitFramerate(bool checked); - void onChangeAudioSync(bool checked); - - void onTitleUpdate(QString title); - - void onEmuStart(); - void onEmuStop(); - - void onUpdateVideoSettings(bool glchange); - - void onFullscreenToggled(); - void onScreenEmphasisToggled(); - -private: - virtual void closeEvent(QCloseEvent* event) override; - - QStringList currentROM; - QStringList currentGBAROM; - QList recentFileList; - QMenu *recentMenu; - void updateRecentFilesMenu(); - - bool verifySetup(); - QString pickFileFromArchive(QString archiveFileName); - QStringList pickROM(bool gba); - void updateCartInserted(bool gba); - - void createScreenPanel(); - - bool pausedManually = false; - - int oldW, oldH; - bool oldMax; - -public: - ScreenHandler* panel; - QWidget* panelWidget; - - QAction* actOpenROM; - QAction* actBootFirmware; - QAction* actCurrentCart; - QAction* actInsertCart; - QAction* actEjectCart; - QAction* actCurrentGBACart; - QAction* actInsertGBACart; - QAction* actInsertGBAAddon[1]; - QAction* actEjectGBACart; - QAction* actImportSavefile; - QAction* actSaveState[9]; - QAction* actLoadState[9]; - QAction* actUndoStateLoad; - QAction* actQuit; - - QAction* actPause; - QAction* actReset; - QAction* actStop; - QAction* actFrameStep; - QAction* actPowerManagement; - QAction* actDateTime; - QAction* actEnableCheats; - QAction* actSetupCheats; - QAction* actROMInfo; - QAction* actRAMInfo; - QAction* actTitleManager; - QAction* actMPNewInstance; - - QAction* actEmuSettings; -#ifdef __APPLE__ - QAction* actPreferences; -#endif - QAction* actInputConfig; - QAction* actVideoSettings; - QAction* actCameraSettings; - QAction* actAudioSettings; - QAction* actMPSettings; - QAction* actWifiSettings; - QAction* actFirmwareSettings; - QAction* actPathSettings; - QAction* actInterfaceSettings; - QAction* actSavestateSRAMReloc; - QAction* actScreenSize[4]; - QActionGroup* grpScreenRotation; - QAction* actScreenRotation[Frontend::screenRot_MAX]; - QActionGroup* grpScreenGap; - QAction* actScreenGap[6]; - QActionGroup* grpScreenLayout; - QAction* actScreenLayout[Frontend::screenLayout_MAX]; - QAction* actScreenSwap; - QActionGroup* grpScreenSizing; - QAction* actScreenSizing[Frontend::screenSizing_MAX]; - QAction* actIntegerScaling; - QActionGroup* grpScreenAspectTop; - QAction** actScreenAspectTop; - QActionGroup* grpScreenAspectBot; - QAction** actScreenAspectBot; - QAction* actScreenFiltering; - QAction* actShowOSD; - QAction* actLimitFramerate; - QAction* actAudioSync; -}; - #endif // MAIN_H diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..2ff0c549 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,18 @@ +{ + "dependencies": [ + "sdl2", + "libarchive", + "libslirp", + "zstd", + { + "name": "qtbase", + "default-features": false, + "features": ["gui", "png", "thread", "widgets", "opengl", "zstd"] + }, + { + "name": "qtmultimedia", + "default-features": false + }, + "qtsvg" + ] +}