From 7b9012671abdbe1d3b831099ea052f5682ba82e2 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:01:26 +0200 Subject: [PATCH 01/50] Add libuv --- .gitmodules | 6 ++++++ CMakeLists.txt | 11 ++++++++++- src/lua.cpp | 7 +++++++ third_party/libuv | 1 + third_party/luv | 1 + 5 files changed, 25 insertions(+), 1 deletion(-) create mode 160000 third_party/libuv create mode 160000 third_party/luv diff --git a/.gitmodules b/.gitmodules index 8a6cac49..93bb8f9d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,9 @@ [submodule "third_party/hydra_core"] path = third_party/hydra_core url = https://github.com/hydra-emu/core +[submodule "third_party/luv"] + path = third_party/luv + url = https://github.com/luvit/luv +[submodule "third_party/libuv"] + path = third_party/libuv + url = https://github.com/libuv/libuv diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a718fbb..52ddb495 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -245,6 +245,15 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp third_party/xxhash/xxhash.c ) +if(ENABLE_LUAJIT) + # Build luv and libuv for TCP Lua server usage + include_directories(third_party/luv/src) + include_directories(third_party/luv/deps/lua-compat-5.3/c-api) + include_directories(third_party/libuv/include) + set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/luv/src/luv.c) + + add_subdirectory(third_party/libuv) +endif() if(ENABLE_QT_GUI) include_directories(third_party/duckstation) @@ -399,7 +408,7 @@ endif() if(ENABLE_LUAJIT) target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_LUA=1") - target_link_libraries(Alber PRIVATE libluajit) + target_link_libraries(Alber PRIVATE libluajit uv) endif() if(ENABLE_OPENGL) diff --git a/src/lua.cpp b/src/lua.cpp index 729b6581..f7e0b719 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -1,6 +1,10 @@ #ifdef PANDA3DS_ENABLE_LUA #include "lua_manager.hpp" +extern "C" { + #include "luv.h" +} + void LuaManager::initialize() { L = luaL_newstate(); // Open Lua @@ -11,6 +15,9 @@ void LuaManager::initialize() { } luaL_openlibs(L); + lua_pushstring(L, "luv"); + luaopen_luv(L); + lua_settable(L, LUA_GLOBALSINDEX); initializeThunks(); initialized = true; diff --git a/third_party/libuv b/third_party/libuv new file mode 160000 index 00000000..b8368a14 --- /dev/null +++ b/third_party/libuv @@ -0,0 +1 @@ +Subproject commit b8368a1441fd4ebdaaae70b67136c80b1a98be32 diff --git a/third_party/luv b/third_party/luv new file mode 160000 index 00000000..3e55ac43 --- /dev/null +++ b/third_party/luv @@ -0,0 +1 @@ +Subproject commit 3e55ac4331d06aa5f43016a142aa2aaa23264105 From 93042b928348d615505b4af4cd622f04c4154894 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:37:59 +0200 Subject: [PATCH 02/50] Disable libuv on Android --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52ddb495..82484596 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,11 @@ option(ENABLE_LUAJIT "Enable scripting with the Lua programming language" ON) option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) option(BUILD_HYDRA_CORE "Build a Hydra core" OFF) +if(ENABLE_LUAJIT AND ANDROID) + message(STATUS "Enabled LuaJIT on Android build. Automatically disabling it until it works properly") + set(ENABLE_LUAJIT OFF) +endif() + include_directories(${PROJECT_SOURCE_DIR}/include/) include_directories(${PROJECT_SOURCE_DIR}/include/kernel) include_directories (${FMT_INCLUDE_DIR}) From 3bb4af5e13bd0ea125cf1d22daf9a694061f34fb Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:47:39 +0200 Subject: [PATCH 03/50] Fix libuv linkage --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 82484596..0d8278cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -413,7 +413,7 @@ endif() if(ENABLE_LUAJIT) target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_LUA=1") - target_link_libraries(Alber PRIVATE libluajit uv) + target_link_libraries(Alber PRIVATE libluajit uv_a) endif() if(ENABLE_OPENGL) From 7343497f36820ac914f57c60fb98fcb19c93fa88 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:17:35 +0200 Subject: [PATCH 04/50] Add basic scheduler structure --- CMakeLists.txt | 2 +- include/cpu_dynarmic.hpp | 16 ++++++----- include/scheduler.hpp | 50 +++++++++++++++++++++++++++++++++++ src/core/CPU/cpu_dynarmic.cpp | 45 +++++++++++++++++-------------- 4 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 include/scheduler.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b4b1503a..e557c5c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,7 +244,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp - include/PICA/dynapica/shader_rec_emitter_arm64.hpp + include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/scheduler.hpp ) cmrc_add_resource_library( diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 8f1e277b..e45705de 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -9,6 +9,7 @@ #include "helpers.hpp" #include "kernel.hpp" #include "memory.hpp" +#include "scheduler.hpp" class CPU; @@ -112,15 +113,16 @@ public: }; class CPU { - std::unique_ptr jit; - std::shared_ptr cp15; + std::unique_ptr jit; + std::shared_ptr cp15; - // Make exclusive monitor with only 1 CPU core - Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; - MyEnvironment env; - Memory& mem; + // Make exclusive monitor with only 1 CPU core + Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; + MyEnvironment env; + Memory& mem; + Scheduler scheduler; -public: + public: static constexpr u64 ticksPerSec = 268111856; CPU(Memory& mem, Kernel& kernel); diff --git a/include/scheduler.hpp b/include/scheduler.hpp new file mode 100644 index 00000000..e941259c --- /dev/null +++ b/include/scheduler.hpp @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include + +#include "helpers.hpp" + +struct Scheduler { + enum class EventType { + VBlank = 0, // End of frame event + Panic = 1, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) + TotalNumberOfEvents // How many event types do we have in total? + }; + static constexpr usize totalNumberOfEvents = static_cast(EventType::TotalNumberOfEvents); + + template + using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; + + EventMap events; + u64 currentTimestamp = 0; + u64 nextTimestamp = 0; + + // Set nextTimestamp to the timestamp of the next event + void updateNextTimestamp() { nextTimestamp = events.cbegin()->first; } + void addCycles(u64 cycles) { currentTimestamp += cycles; } + + void addEvent(EventType type, u64 timestamp) { + events.emplace(timestamp, type); + updateNextTimestamp(); + } + + void removeEvent(EventType type) { + auto it = std::ranges::find_if(events, [type](decltype(events)::const_reference pair) { return pair.second == type; }); + + if (it != events.end()) { + events.erase(it); + updateNextTimestamp(); + } + }; + + void reset() { + currentTimestamp = 0; + + // Clear any pending events + events.clear(); + // Add a dummy event to always keep the scheduler non-empty + addEvent(EventType::Panic, std::numeric_limits::max()); + } +}; \ No newline at end of file diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index 29ca49d1..62e484be 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -1,32 +1,37 @@ #ifdef CPU_DYNARMIC #include "cpu_dynarmic.hpp" + #include "arm_defs.hpp" CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel) { - cp15 = std::make_shared(); + cp15 = std::make_shared(); - Dynarmic::A32::UserConfig config; - config.arch_version = Dynarmic::A32::ArchVersion::v6K; - config.callbacks = &env; - config.coprocessors[15] = cp15; - config.define_unpredictable_behaviour = true; - config.global_monitor = &exclusiveMonitor; - config.processor_id = 0; - - jit = std::make_unique(config); + Dynarmic::A32::UserConfig config; + config.arch_version = Dynarmic::A32::ArchVersion::v6K; + config.callbacks = &env; + config.coprocessors[15] = cp15; + config.define_unpredictable_behaviour = true; + config.global_monitor = &exclusiveMonitor; + config.processor_id = 0; + + jit = std::make_unique(config); } void CPU::reset() { - setCPSR(CPSR::UserMode); - setFPSCR(FPSCR::MainThreadDefault); - env.totalTicks = 0; + setCPSR(CPSR::UserMode); + setFPSCR(FPSCR::MainThreadDefault); + env.totalTicks = 0; - cp15->reset(); - cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage - jit->Reset(); - jit->ClearCache(); - jit->Regs().fill(0); - jit->ExtRegs().fill(0); + cp15->reset(); + cp15->setTLSBase(VirtualAddrs::TLSBase); // Set cp15 TLS pointer to the main thread's thread-local storage + jit->Reset(); + jit->ClearCache(); + jit->Regs().fill(0); + jit->ExtRegs().fill(0); + + // Reset scheduler and add a VBlank event + scheduler.reset(); + scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60); } -#endif // CPU_DYNARMIC \ No newline at end of file +#endif // CPU_DYNARMIC \ No newline at end of file From 75663d0601e4920b8164e810f76679d27e894867 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 21 Jan 2024 22:27:04 +0200 Subject: [PATCH 05/50] More app folder utilities --- include/emulator.hpp | 2 ++ src/emulator.cpp | 45 ++++++++++++++++++++---------------- src/panda_qt/main_window.cpp | 11 +++++++-- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index f4537425..da12c1bd 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -128,6 +128,8 @@ class Emulator { std::filesystem::path getConfigPath(); std::filesystem::path getAndroidAppPath(); + // Get the root path for the emulator's app data + std::filesystem::path getAppDataRoot(); std::span getSMDH(); }; diff --git a/src/emulator.cpp b/src/emulator.cpp index e94170a2..673e0ebc 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -117,6 +117,30 @@ void Emulator::runFrame() { } } +// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplicationName on Linux, etc) +// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in +// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart. +// If the portable build setting is enabled, then those saves go in the executable directory instead +std::filesystem::path Emulator::getAppDataRoot() { + std::filesystem::path appDataPath; + +#ifdef __ANDROID__ + appDataPath = getAndroidAppPath(); +#else + char* appData; + if (!config.usePortableBuild) { + appData = SDL_GetPrefPath(nullptr, "Alber"); + appDataPath = std::filesystem::path(appData); + } else { + appData = SDL_GetBasePath(); + appDataPath = std::filesystem::path(appData) / "Emulator Files"; + } + SDL_free(appData); +#endif + + return appDataPath; +} + bool Emulator::loadROM(const std::filesystem::path& path) { // Reset the emulator if we've already loaded a ROM if (romType != ROMType::None) { @@ -127,26 +151,7 @@ bool Emulator::loadROM(const std::filesystem::path& path) { memory.loadedCXI = std::nullopt; memory.loaded3DSX = std::nullopt; - // Get path for saving files (AppData on Windows, /home/user/.local/share/ApplicationName on Linux, etc) - // Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in - // %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart. - // If the portable build setting is enabled, then those saves go in the executable directory instead - std::filesystem::path appDataPath; - - #ifdef __ANDROID__ - appDataPath = getAndroidAppPath(); - #else - char* appData; - if (!config.usePortableBuild) { - appData = SDL_GetPrefPath(nullptr, "Alber"); - appDataPath = std::filesystem::path(appData); - } else { - appData = SDL_GetBasePath(); - appDataPath = std::filesystem::path(appData) / "Emulator Files"; - } - SDL_free(appData); - #endif - + const std::filesystem::path appDataPath = getAppDataRoot(); const std::filesystem::path dataPath = appDataPath / path.filename().stem(); const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt"; IOFile::setAppDataDir(dataPath); diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index e390aa44..5c661119 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -1,6 +1,8 @@ #include "panda_qt/main_window.hpp" +#include #include +#include #include #include @@ -26,8 +28,14 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) // Create and bind actions for them auto loadGameAction = fileMenu->addAction(tr("Load game")); auto loadLuaAction = fileMenu->addAction(tr("Load Lua script")); + auto openAppFolderAction = fileMenu->addAction(tr("Open Panda3DS folder")); + connect(loadGameAction, &QAction::triggered, this, &MainWindow::selectROM); connect(loadLuaAction, &QAction::triggered, this, &MainWindow::selectLuaFile); + connect(openAppFolderAction, &QAction::triggered, this, [this]() { + QString path = QString::fromStdU16String(emu->getAppDataRoot().u16string()); + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + }); auto pauseAction = emulationMenu->addAction(tr("Pause")); auto resumeAction = emulationMenu->addAction(tr("Resume")); @@ -194,8 +202,7 @@ void MainWindow::dumpRomFS() { return; } std::filesystem::path path(folder.toStdU16String()); - - // TODO: This might break if the game accesses RomFS while we're dumping, we should move it to the emulator thread when we've got a message queue going + messageQueueMutex.lock(); RomFS::DumpingResult res = emu->dumpRomFS(path); messageQueueMutex.unlock(); From 97f97dc6e27d5e0ec673d7bad624787510a865bd Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:38:11 +0200 Subject: [PATCH 06/50] Use CPU for counting ticks instead of scheduler --- include/cpu_dynarmic.hpp | 64 ++++++++++++++++++----------------- include/scheduler.hpp | 4 --- src/core/CPU/cpu_dynarmic.cpp | 4 ++- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index e45705de..a3fb3378 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -11,14 +11,16 @@ #include "memory.hpp" #include "scheduler.hpp" +class Emulator; class CPU; class MyEnvironment final : public Dynarmic::A32::UserCallbacks { -public: - u64 ticksLeft = 0; - u64 totalTicks = 0; - Memory& mem; - Kernel& kernel; + public: + u64 ticksLeft = 0; + u64 totalTicks = 0; + Memory& mem; + Kernel& kernel; + Scheduler& scheduler; u64 getCyclesForInstruction(bool isThumb, u32 instruction); @@ -77,39 +79,39 @@ public: std::terminate(); } - void CallSVC(u32 swi) override { - kernel.serviceSVC(swi); - } + void CallSVC(u32 swi) override { + kernel.serviceSVC(swi); + } - void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { - switch (exception) { - case Dynarmic::A32::Exception::UnpredictableInstruction: - Helpers::panic("Unpredictable instruction at pc = %08X", pc); - break; + void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { + switch (exception) { + case Dynarmic::A32::Exception::UnpredictableInstruction: + Helpers::panic("Unpredictable instruction at pc = %08X", pc); + break; - default: Helpers::panic("Fired exception oops"); - } - } + default: Helpers::panic("Fired exception oops"); + } + } - void AddTicks(u64 ticks) override { - totalTicks += ticks; + void AddTicks(u64 ticks) override { + totalTicks += ticks; - if (ticks > ticksLeft) { - ticksLeft = 0; - return; - } - ticksLeft -= ticks; - } + if (ticks > ticksLeft) { + ticksLeft = 0; + return; + } + ticksLeft -= ticks; + } - u64 GetTicksRemaining() override { - return ticksLeft; - } + u64 GetTicksRemaining() override { + return ticksLeft; + } - u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override { - return getCyclesForInstruction(isThumb, instruction); - } + u64 GetTicksForCode(bool isThumb, u32 vaddr, u32 instruction) override { + return getCyclesForInstruction(isThumb, instruction); + } - MyEnvironment(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} + MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} }; class CPU { diff --git a/include/scheduler.hpp b/include/scheduler.hpp index e941259c..3d2f582c 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -18,12 +18,10 @@ struct Scheduler { using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; EventMap events; - u64 currentTimestamp = 0; u64 nextTimestamp = 0; // Set nextTimestamp to the timestamp of the next event void updateNextTimestamp() { nextTimestamp = events.cbegin()->first; } - void addCycles(u64 cycles) { currentTimestamp += cycles; } void addEvent(EventType type, u64 timestamp) { events.emplace(timestamp, type); @@ -40,8 +38,6 @@ struct Scheduler { }; void reset() { - currentTimestamp = 0; - // Clear any pending events events.clear(); // Add a dummy event to always keep the scheduler non-empty diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index 62e484be..caf8a66e 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -3,7 +3,7 @@ #include "arm_defs.hpp" -CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel) { +CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, scheduler) { cp15 = std::make_shared(); Dynarmic::A32::UserConfig config; @@ -32,6 +32,8 @@ void CPU::reset() { // Reset scheduler and add a VBlank event scheduler.reset(); scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60); + + printf("%lld\n", scheduler.nextTimestamp); } #endif // CPU_DYNARMIC \ No newline at end of file From fa82dad38d412b7710ea3d9187cf267555b85171 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 02:29:14 +0200 Subject: [PATCH 07/50] First stuff running with scheduler --- include/cpu_dynarmic.hpp | 30 ++++++-------------- include/emulator.hpp | 7 +++++ include/scheduler.hpp | 3 ++ src/core/CPU/cpu_dynarmic.cpp | 30 ++++++++++++++++---- src/emulator.cpp | 52 +++++++++++++++++++++++++++++------ 5 files changed, 86 insertions(+), 36 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index a3fb3378..1ec50d9e 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -94,7 +94,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks { } void AddTicks(u64 ticks) override { - totalTicks += ticks; + scheduler.currentTimestamp += ticks; if (ticks > ticksLeft) { ticksLeft = 0; @@ -111,7 +111,7 @@ class MyEnvironment final : public Dynarmic::A32::UserCallbacks { return getCyclesForInstruction(isThumb, instruction); } - MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} + MyEnvironment(Memory& mem, Kernel& kernel, Scheduler& scheduler) : mem(mem), kernel(kernel), scheduler(scheduler) {} }; class CPU { @@ -122,12 +122,13 @@ class CPU { Dynarmic::ExclusiveMonitor exclusiveMonitor{1}; MyEnvironment env; Memory& mem; - Scheduler scheduler; + Scheduler& scheduler; + Emulator& emu; public: static constexpr u64 ticksPerSec = 268111856; - CPU(Memory& mem, Kernel& kernel); + CPU(Memory& mem, Kernel& kernel, Emulator& emu); void reset(); void setReg(int index, u32 value) { @@ -166,29 +167,14 @@ class CPU { } u64 getTicks() { - return env.totalTicks; + return scheduler.currentTimestamp; } // Get reference to tick count. Memory needs access to this u64& getTicksRef() { - return env.totalTicks; + return scheduler.currentTimestamp; } void clearCache() { jit->ClearCache(); } - - void runFrame() { - env.ticksLeft = ticksPerSec / 60; - execute: - const auto exitReason = jit->Run(); - - if (static_cast(exitReason) != 0) [[unlikely]] { - // Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing - // The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump - if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) { - goto execute; - } else { - Helpers::panic("Exit reason: %d\nPC: %08X", static_cast(exitReason), getReg(15)); - } - } - } + void runFrame(); }; \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index f4537425..55ed8499 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -15,6 +15,7 @@ #include "io_file.hpp" #include "lua_manager.hpp" #include "memory.hpp" +#include "scheduler.hpp" #ifdef PANDA3DS_ENABLE_HTTP_SERVER #include "http_server.hpp" @@ -42,6 +43,7 @@ class Emulator { Kernel kernel; Crypto::AESEngine aesEngine; Cheats cheats; + Scheduler scheduler; // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state @@ -85,6 +87,8 @@ class Emulator { // change ROMs. If Reload is selected, the emulator will reload its selected ROM. This is useful for eg a "reset" button that keeps the current // ROM and just resets the emu enum class ReloadOption { NoReload, Reload }; + // Used in CPU::runFrame + bool frameDone = false; Emulator(); ~Emulator(); @@ -94,6 +98,8 @@ class Emulator { void reset(ReloadOption reload); void run(void* frontend = nullptr); void runFrame(); + // Poll the scheduler for events + void pollScheduler(); void resume(); // Resume the emulator void pause(); // Pause the emulator @@ -121,6 +127,7 @@ class Emulator { Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } LuaManager& getLua() { return lua; } + Scheduler& getScheduler() { return scheduler; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/include/scheduler.hpp b/include/scheduler.hpp index 3d2f582c..fb097618 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -18,6 +18,7 @@ struct Scheduler { using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; EventMap events; + u64 currentTimestamp = 0; u64 nextTimestamp = 0; // Set nextTimestamp to the timestamp of the next event @@ -38,6 +39,8 @@ struct Scheduler { }; void reset() { + currentTimestamp = 0; + // Clear any pending events events.clear(); // Add a dummy event to always keep the scheduler non-empty diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index caf8a66e..f373dc06 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -2,8 +2,9 @@ #include "cpu_dynarmic.hpp" #include "arm_defs.hpp" +#include "emulator.hpp" -CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, scheduler) { +CPU::CPU(Memory& mem, Kernel& kernel, Emulator& emu) : mem(mem), emu(emu), scheduler(emu.getScheduler()), env(mem, kernel, emu.getScheduler()) { cp15 = std::make_shared(); Dynarmic::A32::UserConfig config; @@ -28,12 +29,31 @@ void CPU::reset() { jit->ClearCache(); jit->Regs().fill(0); jit->ExtRegs().fill(0); +} - // Reset scheduler and add a VBlank event - scheduler.reset(); - scheduler.addEvent(Scheduler::EventType::VBlank, ticksPerSec / 60); +void CPU::runFrame() { + emu.frameDone = false; - printf("%lld\n", scheduler.nextTimestamp); + while (!emu.frameDone) { + // Run CPU until the next scheduler event + env.ticksLeft = scheduler.nextTimestamp; + + execute: + const auto exitReason = jit->Run(); + + // Handle any scheduler events that need handling. + emu.pollScheduler(); + + if (static_cast(exitReason) != 0) [[unlikely]] { + // Cache invalidation needs to exit the JIT so it returns a CacheInvalidation HaltReason. In our case, we just go back to executing + // The goto might be terrible but it does guarantee that this does not recursively call run and crash, instead getting optimized to a jump + if (Dynarmic::Has(exitReason, Dynarmic::HaltReason::CacheInvalidation)) { + goto execute; + } else { + Helpers::panic("Exit reason: %d\nPC: %08X", static_cast(exitReason), getReg(15)); + } + } + } } #endif // CPU_DYNARMIC \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index e94170a2..46efd209 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -17,10 +17,11 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; #endif Emulator::Emulator() - : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config), - memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false) + : config(getConfigPath()), kernel(cpu, memory, gpu, config), cpu(memory, kernel, *this), gpu(memory, config), memory(cpu.getTicksRef(), config), + cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false) #ifdef PANDA3DS_ENABLE_HTTP_SERVER - , httpServer(this) + , + httpServer(this) #endif { #ifdef PANDA3DS_ENABLE_DISCORD_RPC @@ -45,6 +46,10 @@ void Emulator::reset(ReloadOption reload) { cpu.reset(); gpu.reset(); memory.reset(); + // Reset scheduler and add a VBlank event + scheduler.reset(); + scheduler.addEvent(Scheduler::EventType::VBlank, CPU::ticksPerSec / 60); + // Kernel must be reset last because it depends on CPU/Memory state kernel.reset(); @@ -99,12 +104,6 @@ void Emulator::runFrame() { if (running) { cpu.runFrame(); // Run 1 frame of instructions gpu.display(); // Display graphics - lua.signalEvent(LuaEvent::Frame); - - // Send VBlank interrupts - ServiceManager& srv = kernel.getServiceManager(); - srv.sendGPUInterrupt(GPUInterrupt::VBlank0); - srv.sendGPUInterrupt(GPUInterrupt::VBlank1); // Run cheats if any are loaded if (cheats.haveCheats()) [[unlikely]] { @@ -117,6 +116,41 @@ void Emulator::runFrame() { } } +void Emulator::pollScheduler() { + auto& events = scheduler.events; + + // Pop events until there's none pending anymore + while (scheduler.currentTimestamp > scheduler.nextTimestamp) { + // Read event timestamp and type, pop it from the scheduler and handle it + auto [time, eventType] = std::move(*events.begin()); + events.erase(events.begin()); + + scheduler.updateNextTimestamp(); + + switch (eventType) { + case Scheduler::EventType::VBlank: { + // Signal that we've reached the end of a frame + frameDone = true; + lua.signalEvent(LuaEvent::Frame); + + // Send VBlank interrupts + ServiceManager& srv = kernel.getServiceManager(); + srv.sendGPUInterrupt(GPUInterrupt::VBlank0); + srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + + // Queue next VBlank event + scheduler.addEvent(Scheduler::EventType::VBlank, time + CPU::ticksPerSec / 60); + break; + } + + default: { + Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast(eventType)); + break; + } + } + } +} + bool Emulator::loadROM(const std::filesystem::path& path) { // Reset the emulator if we've already loaded a ROM if (romType != ROMType::None) { From 0be099d1eae87f15c1ae0b73f6bbd6c279310752 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 04:04:05 +0200 Subject: [PATCH 08/50] Hook up KTimer to scheduler --- include/cpu_dynarmic.hpp | 6 +++- include/kernel/kernel.hpp | 2 +- include/kernel/kernel_types.hpp | 5 ++- include/scheduler.hpp | 38 +++++++++++++++++++- src/core/CPU/cpu_dynarmic.cpp | 4 ++- src/core/kernel/timers.cpp | 63 +++++++++++++++++++++++---------- src/emulator.cpp | 5 ++- 7 files changed, 96 insertions(+), 27 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 1ec50d9e..97acddd0 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -126,7 +126,7 @@ class CPU { Emulator& emu; public: - static constexpr u64 ticksPerSec = 268111856; + static constexpr u64 ticksPerSec = Scheduler::arm11Clock; CPU(Memory& mem, Kernel& kernel, Emulator& emu); void reset(); @@ -175,6 +175,10 @@ class CPU { return scheduler.currentTimestamp; } + Scheduler& getScheduler() { + return scheduler; + } + void clearCache() { jit->ClearCache(); } void runFrame(); }; \ No newline at end of file diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index 3f09bf12..217dd148 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -70,6 +70,7 @@ public: Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port Handle makeTimer(ResetType resetType); + void pollTimers(); // Signals an event, returns true on success or false if the event does not exist bool signalEvent(Handle e); @@ -94,7 +95,6 @@ public: void releaseMutex(Mutex* moo); void cancelTimer(Timer* timer); void signalTimer(Handle timerHandle, Timer* timer); - void updateTimer(Handle timerHandle, Timer* timer); // Wake up the thread with the highest priority out of all threads in the waitlist // Returns the index of the woken up thread diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 53c60774..79684e17 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -177,13 +177,12 @@ struct Timer { u64 waitlist; // Refer to the getWaitlist function below for documentation ResetType resetType = ResetType::OneShot; - u64 startTick; // CPU tick the timer started - u64 currentDelay; // Number of ns until the timer fires next time + u64 fireTick; // CPU tick the timer will be fired u64 interval; // Number of ns until the timer fires for the second and future times bool fired; // Has this timer been signalled? bool running; // Is this timer running or stopped? - Timer(ResetType type) : resetType(type), startTick(0), currentDelay(0), interval(0), waitlist(0), fired(false), running(false) {} + Timer(ResetType type) : resetType(type), fireTick(0), interval(0), waitlist(0), fired(false), running(false) {} }; struct MemoryBlock { diff --git a/include/scheduler.hpp b/include/scheduler.hpp index fb097618..69c91f06 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -5,14 +5,17 @@ #include #include "helpers.hpp" +#include "logger.hpp" struct Scheduler { enum class EventType { VBlank = 0, // End of frame event - Panic = 1, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) + UpdateTimers = 1, // Update kernel timer objects + Panic = 2, // Dummy event that is always pending and should never be triggered (Timestamp = UINT64_MAX) TotalNumberOfEvents // How many event types do we have in total? }; static constexpr usize totalNumberOfEvents = static_cast(EventType::TotalNumberOfEvents); + static constexpr u64 arm11Clock = 268111856; template using EventMap = boost::container::flat_multimap, boost::container::static_vector, size>>; @@ -46,4 +49,37 @@ struct Scheduler { // Add a dummy event to always keep the scheduler non-empty addEvent(EventType::Panic, std::numeric_limits::max()); } + + private: + static constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits::max() / arm11Clock; + + public: + // Function for converting time units to cycles for various kernel functions + // Thank you Citra + static constexpr s64 nsToCycles(float ns) { return s64(arm11Clock * (0.000000001f) * ns); } + static constexpr s64 nsToCycles(int ns) { return arm11Clock * s64(ns) / 1000000000; } + + static constexpr s64 nsToCycles(s64 ns) { + if (ns / 1000000000 > static_cast(MAX_VALUE_TO_MULTIPLY)) { + return std::numeric_limits::max(); + } + + if (ns > static_cast(MAX_VALUE_TO_MULTIPLY)) { + return arm11Clock * (ns / 1000000000); + } + + return (arm11Clock * ns) / 1000000000; + } + + static constexpr s64 nsToCycles(u64 ns) { + if (ns / 1000000000 > MAX_VALUE_TO_MULTIPLY) { + return std::numeric_limits::max(); + } + + if (ns > MAX_VALUE_TO_MULTIPLY) { + return arm11Clock * (s64(ns) / 1000000000); + } + + return (arm11Clock * s64(ns)) / 1000000000; + } }; \ No newline at end of file diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index f373dc06..94ce335e 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -36,7 +36,7 @@ void CPU::runFrame() { while (!emu.frameDone) { // Run CPU until the next scheduler event - env.ticksLeft = scheduler.nextTimestamp; + env.ticksLeft = scheduler.nextTimestamp - scheduler.currentTimestamp; execute: const auto exitReason = jit->Run(); @@ -54,6 +54,8 @@ void CPU::runFrame() { } } } + + printf("CPU END!\n"); } #endif // CPU_DYNARMIC \ No newline at end of file diff --git a/src/core/kernel/timers.cpp b/src/core/kernel/timers.cpp index a9c95292..f245f9dc 100644 --- a/src/core/kernel/timers.cpp +++ b/src/core/kernel/timers.cpp @@ -1,5 +1,8 @@ -#include "kernel.hpp" +#include + #include "cpu.hpp" +#include "kernel.hpp" +#include "scheduler.hpp" Handle Kernel::makeTimer(ResetType type) { Handle ret = makeObject(KernelObjectType::Timer); @@ -13,30 +16,48 @@ Handle Kernel::makeTimer(ResetType type) { return ret; } -void Kernel::updateTimer(Handle handle, Timer* timer) { - if (timer->running) { - const u64 currentTicks = cpu.getTicks(); - u64 elapsedTicks = currentTicks - timer->startTick; +void Kernel::pollTimers() { + u64 currentTick = cpu.getTicks(); - constexpr double ticksPerSec = double(CPU::ticksPerSec); - constexpr double nsPerTick = ticksPerSec / 1000000000.0; - const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick); + // Find the next timestamp we'll poll KTimers on. To do this, we find the minimum tick one of our timers will fire + u64 nextTimestamp = std::numeric_limits::max(); + // Do we have any active timers anymore? If not, then we won't need to schedule a new timer poll event + bool haveActiveTimers = false; - // Timer has fired - if (elapsedNs >= timer->currentDelay) { - timer->startTick = currentTicks; - timer->currentDelay = timer->interval; - signalTimer(handle, timer); + for (auto handle : timerHandles) { + KernelObject* object = getObject(handle, KernelObjectType::Timer); + if (object != nullptr) { + Timer* timer = object->getData(); + + if (timer->running) { + // If timer has fired, signal it and set the tick it will next time + if (currentTick >= timer->fireTick) { + signalTimer(handle, timer); + } + + // Update our next timer fire timestamp and mark that we should schedule a new event to poll timers + // We recheck timer->running because signalling a timer stops it if interval == 0 + if (timer->running) { + nextTimestamp = std::min(nextTimestamp, timer->fireTick); + haveActiveTimers = true; + } + } } } + + // If we still have active timers, schedule next poll event + if (haveActiveTimers) { + Scheduler& scheduler = cpu.getScheduler(); + scheduler.addEvent(Scheduler::EventType::UpdateTimers, nextTimestamp); + } } void Kernel::cancelTimer(Timer* timer) { timer->running = false; - // TODO: When we have a scheduler this should properly cancel timer events in the scheduler } void Kernel::signalTimer(Handle timerHandle, Timer* timer) { + printf("DEEPFRIED\n"); timer->fired = true; requireReschedule(); @@ -54,6 +75,8 @@ void Kernel::signalTimer(Handle timerHandle, Timer* timer) { if (timer->interval == 0) { cancelTimer(timer); + } else { + timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(timer->interval); } } @@ -87,18 +110,20 @@ void Kernel::svcSetTimer() { Timer* timer = object->getData(); cancelTimer(timer); - timer->currentDelay = initial; timer->interval = interval; timer->running = true; - timer->startTick = cpu.getTicks(); + timer->fireTick = cpu.getTicks() + Scheduler::nsToCycles(initial); + + Scheduler& scheduler = cpu.getScheduler(); + // Signal an event to poll timers as soon as possible + scheduler.removeEvent(Scheduler::EventType::UpdateTimers); + scheduler.addEvent(Scheduler::EventType::UpdateTimers, cpu.getTicks() + 1); // If the initial delay is 0 then instantly signal the timer if (initial == 0) { signalTimer(handle, timer); - } else { - // This should schedule an event in the scheduler when we have one } - + regs[0] = Result::Success; } diff --git a/src/emulator.cpp b/src/emulator.cpp index 46efd209..058fe868 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -120,7 +120,7 @@ void Emulator::pollScheduler() { auto& events = scheduler.events; // Pop events until there's none pending anymore - while (scheduler.currentTimestamp > scheduler.nextTimestamp) { + while (scheduler.currentTimestamp >= scheduler.nextTimestamp) { // Read event timestamp and type, pop it from the scheduler and handle it auto [time, eventType] = std::move(*events.begin()); events.erase(events.begin()); @@ -129,6 +129,7 @@ void Emulator::pollScheduler() { switch (eventType) { case Scheduler::EventType::VBlank: { + printf("VBLANK!!!!!!\n"); // Signal that we've reached the end of a frame frameDone = true; lua.signalEvent(LuaEvent::Frame); @@ -143,6 +144,8 @@ void Emulator::pollScheduler() { break; } + case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break; + default: { Helpers::panic("Scheduler: Unimplemented event type received: %d\n", static_cast(eventType)); break; From af996c55bae29d53cd02d5e973d08c03c6029cc0 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 04:10:47 +0200 Subject: [PATCH 09/50] Remove debug printfs --- src/core/CPU/cpu_dynarmic.cpp | 2 -- src/core/kernel/timers.cpp | 1 - src/emulator.cpp | 1 - 3 files changed, 4 deletions(-) diff --git a/src/core/CPU/cpu_dynarmic.cpp b/src/core/CPU/cpu_dynarmic.cpp index 94ce335e..da5270b4 100644 --- a/src/core/CPU/cpu_dynarmic.cpp +++ b/src/core/CPU/cpu_dynarmic.cpp @@ -54,8 +54,6 @@ void CPU::runFrame() { } } } - - printf("CPU END!\n"); } #endif // CPU_DYNARMIC \ No newline at end of file diff --git a/src/core/kernel/timers.cpp b/src/core/kernel/timers.cpp index f245f9dc..35fc57a4 100644 --- a/src/core/kernel/timers.cpp +++ b/src/core/kernel/timers.cpp @@ -57,7 +57,6 @@ void Kernel::cancelTimer(Timer* timer) { } void Kernel::signalTimer(Handle timerHandle, Timer* timer) { - printf("DEEPFRIED\n"); timer->fired = true; requireReschedule(); diff --git a/src/emulator.cpp b/src/emulator.cpp index 058fe868..098aba8c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -129,7 +129,6 @@ void Emulator::pollScheduler() { switch (eventType) { case Scheduler::EventType::VBlank: { - printf("VBLANK!!!!!!\n"); // Signal that we've reached the end of a frame frameDone = true; lua.signalEvent(LuaEvent::Frame); From 88dc2cc8647901e9af036df3f260225c65fbd47e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:57:02 +0200 Subject: [PATCH 10/50] Remove std::ranges usage --- include/scheduler.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/include/scheduler.hpp b/include/scheduler.hpp index 69c91f06..92328878 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "helpers.hpp" #include "logger.hpp" @@ -33,11 +32,14 @@ struct Scheduler { } void removeEvent(EventType type) { - auto it = std::ranges::find_if(events, [type](decltype(events)::const_reference pair) { return pair.second == type; }); - - if (it != events.end()) { - events.erase(it); - updateNextTimestamp(); + for (auto it = events.begin(); it != events.end(); it++) { + // Find first event of type "type" and remove it. + // Our scheduler shouldn't have duplicate events, so it's safe to exit when an event is found + if (it->second == type) { + events.erase(it); + updateNextTimestamp(); + break; + } } }; From 3c06098ead6c913117f014fe9e06b58c4096d815 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:07:52 +0200 Subject: [PATCH 11/50] Mark VBlank event likely --- src/emulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulator.cpp b/src/emulator.cpp index f0f24c59..1ac8d5b2 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -128,7 +128,7 @@ void Emulator::pollScheduler() { scheduler.updateNextTimestamp(); switch (eventType) { - case Scheduler::EventType::VBlank: { + case Scheduler::EventType::VBlank: [[likely]] { // Signal that we've reached the end of a frame frameDone = true; lua.signalEvent(LuaEvent::Frame); From f78acb8049edc03cead982ec6482894c9c00802d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:32:21 +0200 Subject: [PATCH 12/50] Add more CAM definitions --- include/services/cam.hpp | 6 ++++++ src/core/services/cam.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/include/services/cam.hpp b/include/services/cam.hpp index 611a3b6d..9c217082 100644 --- a/include/services/cam.hpp +++ b/include/services/cam.hpp @@ -23,11 +23,17 @@ class CAMService { // Service commands void driverInitialize(u32 messagePointer); + void driverFinalize(u32 messagePointer); void getMaxLines(u32 messagePointer); void getBufferErrorInterruptEvent(u32 messagePointer); + void getSuitableY2RCoefficients(u32 messagePointer); + void getTransferBytes(u32 messagePointer); void setContrast(u32 messagePointer); void setFrameRate(u32 messagePointer); + void setSize(u32 messagePointer); void setTransferLines(u32 messagePointer); + void setTrimming(u32 messagePointer); + void setTrimminsParamsCenter(u32 messagePointer); public: CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} diff --git a/src/core/services/cam.cpp b/src/core/services/cam.cpp index a0206077..56a2c380 100644 --- a/src/core/services/cam.cpp +++ b/src/core/services/cam.cpp @@ -6,10 +6,16 @@ namespace CAMCommands { enum : u32 { GetBufferErrorInterruptEvent = 0x00060040, DriverInitialize = 0x00390000, + DriverFinalize = 0x003A0000, SetTransferLines = 0x00090100, GetMaxLines = 0x000A0080, + GetTransferBytes = 0x000C0040, + SetTrimming = 0x000E0080, + SetTrimmingParamsCenter = 0x00120140, + SetSize = 0x001F00C0, // Set size has different headers between cam:u and New3DS QTM module SetFrameRate = 0x00200080, SetContrast = 0x00230080, + GetSuitableY2rStandardCoefficient = 0x00360000, }; } From 84b8bc8c48a9922cae3c13ff504e12ee8dbe2ca1 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:29:05 +0200 Subject: [PATCH 13/50] Properly implement camera ports, add more cam commands --- include/services/cam.hpp | 19 ++++- src/core/services/cam.cpp | 150 +++++++++++++++++++++++++++++++++++--- 2 files changed, 153 insertions(+), 16 deletions(-) diff --git a/include/services/cam.hpp b/include/services/cam.hpp index 9c217082..57cb7e13 100644 --- a/include/services/cam.hpp +++ b/include/services/cam.hpp @@ -12,14 +12,25 @@ class Kernel; class CAMService { + using Event = std::optional; + + struct Port { + Event bufferErrorInterruptevent = std::nullopt; + u16 transferBytes; + + void reset() { + bufferErrorInterruptevent = std::nullopt; + transferBytes = 256; + } + }; + Handle handle = KernelHandles::CAM; Memory& mem; Kernel& kernel; MAKE_LOG_FUNCTION(log, camLogger) - using Event = std::optional; - static constexpr size_t portCount = 4; // PORT_NONE, PORT_CAM1, PORT_CAM2, PORT_BOTH - std::array bufferErrorInterruptEvents; + static constexpr size_t portCount = 2; + std::array ports; // Service commands void driverInitialize(u32 messagePointer); @@ -33,7 +44,7 @@ class CAMService { void setSize(u32 messagePointer); void setTransferLines(u32 messagePointer); void setTrimming(u32 messagePointer); - void setTrimminsParamsCenter(u32 messagePointer); + void setTrimmingParamsCenter(u32 messagePointer); public: CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} diff --git a/src/core/services/cam.cpp b/src/core/services/cam.cpp index 56a2c380..38f3d6d7 100644 --- a/src/core/services/cam.cpp +++ b/src/core/services/cam.cpp @@ -19,17 +19,65 @@ namespace CAMCommands { }; } -void CAMService::reset() { bufferErrorInterruptEvents.fill(std::nullopt); } +// Helper struct for working with camera ports +class PortSelect { + u32 value; + + public: + PortSelect(u32 val) : value(val) {} + bool isValid() const { return value < 4; } + + bool isSinglePort() const { + // 1 corresponds to the first camera port and 2 corresponds to the second port + return value == 1 || value == 2; + } + + bool isBothPorts() const { + // 3 corresponds to both ports + return value == 3; + } + + // Returns the index of the camera port, assuming that it's only a single port + int getSingleIndex() const { + if (!isSinglePort()) [[unlikely]] { + Helpers::panic("Camera: getSingleIndex called for port with invalid value"); + } + + return value - 1; + } + + std::vector getPortIndices() const { + switch (value) { + case 1: return {0}; // Only port 1 + case 2: return {1}; // Only port 2 + case 3: return {0, 1}; // Both port 1 and port 2 + default: return {}; // No ports or invalid ports + } + } +}; + +void CAMService::reset() { + for (auto& port : ports) { + port.reset(); + } +} void CAMService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break; + case CAMCommands::DriverFinalize: driverFinalize(messagePointer); break; case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break; case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break; + case CAMCommands::GetSuitableY2rStandardCoefficient: getSuitableY2RCoefficients(messagePointer); break; + case CAMCommands::GetTransferBytes: getTransferBytes(messagePointer); break; case CAMCommands::SetContrast: setContrast(messagePointer); break; case CAMCommands::SetFrameRate: setFrameRate(messagePointer); break; case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break; + case CAMCommands::SetTrimming: setTrimming(messagePointer); break; + case CAMCommands::SetTrimmingParamsCenter: setTrimmingParamsCenter(messagePointer); break; + case CAMCommands::SetSize: setSize(messagePointer); break; + default: Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command); break; @@ -42,6 +90,12 @@ void CAMService::driverInitialize(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void CAMService::driverFinalize(u32 messagePointer) { + log("CAM::DriverFinalize\n"); + mem.write32(messagePointer, IPC::responseHeader(0x3A, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void CAMService::setContrast(u32 messagePointer) { const u32 cameraSelect = mem.read32(messagePointer + 4); const u32 contrast = mem.read32(messagePointer + 8); @@ -53,12 +107,23 @@ void CAMService::setContrast(u32 messagePointer) { } void CAMService::setTransferLines(u32 messagePointer) { - const u32 port = mem.read32(messagePointer + 4); - const s16 lines = mem.read16(messagePointer + 8); - const s16 width = mem.read16(messagePointer + 12); - const s16 height = mem.read16(messagePointer + 16); + const u32 portIndex = mem.read32(messagePointer + 4); + const u16 lines = mem.read16(messagePointer + 8); + const u16 width = mem.read16(messagePointer + 12); + const u16 height = mem.read16(messagePointer + 16); + const PortSelect port(portIndex); - log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", port, lines, width, height); + if (port.isValid()) { + const u32 transferBytes = lines * width * 2; + + for (int i : port.getPortIndices()) { + ports[i].transferBytes = transferBytes; + } + } else { + Helpers::warn("CAM::SetTransferLines: Invalid port\n"); + } + + log("CAM::SetTransferLines (port = %d, lines = %d, width = %d, height = %d)\n", portIndex, lines, width, height); mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0)); mem.write32(messagePointer + 4, Result::Success); @@ -74,6 +139,41 @@ void CAMService::setFrameRate(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void CAMService::setSize(u32 messagePointer) { + const u32 cameraSelect = mem.read32(messagePointer + 4); + const u32 size = mem.read32(messagePointer + 8); + const u32 context = mem.read32(messagePointer + 12); + + log("CAM::SetSize (camera select = %d, size = %d, context = %d)\n", cameraSelect, size, context); + + mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +void CAMService::setTrimming(u32 messagePointer) { + const u32 port = mem.read32(messagePointer + 4); + const bool trim = mem.read8(messagePointer + 8) != 0; + + log("CAM::SetTrimming (port = %d, trimming = %s)\n", port, trim ? "enabled" : "disabled"); + + mem.write32(messagePointer, IPC::responseHeader(0x0E, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + +void CAMService::setTrimmingParamsCenter(u32 messagePointer) { + const u32 port = mem.read32(messagePointer + 4); + const s16 trimWidth = s16(mem.read16(messagePointer + 8)); + const s16 trimHeight = s16(mem.read16(messagePointer + 12)); + const s16 cameraWidth = s16(mem.read16(messagePointer + 16)); + const s16 cameraHeight = s16(mem.read16(messagePointer + 20)); + + log("CAM::SetTrimmingParamsCenter (port = %d), trim size = (%d, %d), camera size = (%d, %d)\n", port, trimWidth, trimHeight, cameraWidth, + cameraHeight); + + mem.write32(messagePointer, IPC::responseHeader(0x12, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + // Algorithm taken from Citra // https://github.com/citra-emu/citra/blob/master/src/core/hle/service/cam/cam.cpp#L465 void CAMService::getMaxLines(u32 messagePointer) { @@ -106,16 +206,40 @@ void CAMService::getMaxLines(u32 messagePointer) { } } +void CAMService::getSuitableY2RCoefficients(u32 messagePointer) { + log("CAM::GetSuitableY2RCoefficients\n"); + mem.write32(messagePointer, IPC::responseHeader(0x36, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + // Y2R standard coefficient value + mem.write32(messagePointer + 8, 0); +} + +void CAMService::getTransferBytes(u32 messagePointer) { + const u32 portIndex = mem.read32(messagePointer + 4); + const PortSelect port(portIndex); + log("CAM::GetTransferBytes (port = %d)\n", portIndex); + + mem.write32(messagePointer, IPC::responseHeader(0x0C, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + + if (port.isSinglePort()) { + mem.write32(messagePointer + 8, ports[port.getSingleIndex()].transferBytes); + } else { + // TODO: This should return the proper error code + Helpers::warn("CAM::GetTransferBytes: Invalid port index"); + mem.write32(messagePointer + 8, 0); + } +} + void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) { - const u32 port = mem.read32(messagePointer + 4); - log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", port); + const u32 portIndex = mem.read32(messagePointer + 4); + const PortSelect port(portIndex); + log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", portIndex); mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2)); - if (port >= portCount) { - Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port"); - } else { - auto& event = bufferErrorInterruptEvents[port]; + if (port.isSinglePort()) { + auto& event = ports[port.getSingleIndex()].bufferErrorInterruptevent; if (!event.has_value()) { event = kernel.makeEvent(ResetType::OneShot); } @@ -123,5 +247,7 @@ void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 0); mem.write32(messagePointer + 12, event.value()); + } else { + Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port"); } } \ No newline at end of file From a3fda72f88e61ff14330842e697013bd186040db Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:38:18 +0200 Subject: [PATCH 14/50] Add FS::SdmcArchiveResource --- include/services/fs.hpp | 1 + src/core/services/fs.cpp | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/services/fs.hpp b/include/services/fs.hpp index 2fe3ba5d..4d879e7f 100644 --- a/include/services/fs.hpp +++ b/include/services/fs.hpp @@ -61,6 +61,7 @@ class FSService { void getFreeBytes(u32 messagePointer); void getFormatInfo(u32 messagePointer); void getPriority(u32 messagePointer); + void getSdmcArchiveResource(u32 messagePointer); void getThisSaveDataSecureValue(u32 messagePointer); void theGameboyVCFunction(u32 messagePointer); void initialize(u32 messagePointer); diff --git a/src/core/services/fs.cpp b/src/core/services/fs.cpp index 1f3317fb..264abcab 100644 --- a/src/core/services/fs.cpp +++ b/src/core/services/fs.cpp @@ -27,6 +27,7 @@ namespace FSCommands { CloseArchive = 0x080E0080, FormatThisUserSaveData = 0x080F0180, GetFreeBytes = 0x08120080, + GetSdmcArchiveResource = 0x08140000, IsSdmcDetected = 0x08170000, IsSdmcWritable = 0x08180000, CardSlotIsInserted = 0x08210000, @@ -179,6 +180,7 @@ void FSService::handleSyncRequest(u32 messagePointer) { case FSCommands::GetFreeBytes: getFreeBytes(messagePointer); break; case FSCommands::GetFormatInfo: getFormatInfo(messagePointer); break; case FSCommands::GetPriority: getPriority(messagePointer); break; + case FSCommands::GetSdmcArchiveResource: getSdmcArchiveResource(messagePointer); break; case FSCommands::GetThisSaveDataSecureValue: getThisSaveDataSecureValue(messagePointer); break; case FSCommands::Initialize: initialize(messagePointer); break; case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break; @@ -764,3 +766,22 @@ void FSService::renameFile(u32 messagePointer) { const HorizonResult res = sourceArchive->archive->renameFile(sourcePath, destPath); mem.write32(messagePointer + 4, static_cast(res)); } + +void FSService::getSdmcArchiveResource(u32 messagePointer) { + log("FS::GetSdmcArchiveResource"); // For the time being, return the same stubbed archive resource for every media type + + static constexpr ArchiveResource resource = { + .sectorSize = 512, + .clusterSize = 16_KB, + .partitionCapacityInClusters = 0x80000, // 0x80000 * 16 KB = 8GB + .freeSpaceInClusters = 0x80000, // Same here + }; + + mem.write32(messagePointer, IPC::responseHeader(0x814, 5, 0)); + mem.write32(messagePointer + 4, Result::Success); + + mem.write32(messagePointer + 8, resource.sectorSize); + mem.write32(messagePointer + 12, resource.clusterSize); + mem.write32(messagePointer + 16, resource.partitionCapacityInClusters); + mem.write32(messagePointer + 20, resource.freeSpaceInClusters); +} \ No newline at end of file From ecf0416b64bf14eda770abf26eed296e593c3dca Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 17:17:23 +0200 Subject: [PATCH 15/50] clang-format and IWYU fixes --- src/core/services/cam.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/services/cam.cpp b/src/core/services/cam.cpp index 38f3d6d7..8c59321d 100644 --- a/src/core/services/cam.cpp +++ b/src/core/services/cam.cpp @@ -1,4 +1,7 @@ #include "services/cam.hpp" + +#include + #include "ipc.hpp" #include "kernel.hpp" @@ -12,7 +15,7 @@ namespace CAMCommands { GetTransferBytes = 0x000C0040, SetTrimming = 0x000E0080, SetTrimmingParamsCenter = 0x00120140, - SetSize = 0x001F00C0, // Set size has different headers between cam:u and New3DS QTM module + SetSize = 0x001F00C0, // Set size has different headers between cam:u and New3DS QTM module SetFrameRate = 0x00200080, SetContrast = 0x00230080, GetSuitableY2rStandardCoefficient = 0x00360000, @@ -78,9 +81,7 @@ void CAMService::handleSyncRequest(u32 messagePointer) { case CAMCommands::SetTrimmingParamsCenter: setTrimmingParamsCenter(messagePointer); break; case CAMCommands::SetSize: setSize(messagePointer); break; - default: - Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command); - break; + default: Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command); break; } } @@ -115,7 +116,7 @@ void CAMService::setTransferLines(u32 messagePointer) { if (port.isValid()) { const u32 transferBytes = lines * width * 2; - + for (int i : port.getPortIndices()) { ports[i].transferBytes = transferBytes; } @@ -145,7 +146,7 @@ void CAMService::setSize(u32 messagePointer) { const u32 context = mem.read32(messagePointer + 12); log("CAM::SetSize (camera select = %d, size = %d, context = %d)\n", cameraSelect, size, context); - + mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } From 442ae3a210a06fa19c6c1673682b174e025546a1 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:10:18 +0200 Subject: [PATCH 16/50] Add CAM::SetReceiving, fix ports --- include/services/cam.hpp | 7 ++++-- src/core/services/cam.cpp | 47 ++++++++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/include/services/cam.hpp b/include/services/cam.hpp index 57cb7e13..eec70208 100644 --- a/include/services/cam.hpp +++ b/include/services/cam.hpp @@ -15,11 +15,13 @@ class CAMService { using Event = std::optional; struct Port { - Event bufferErrorInterruptevent = std::nullopt; + Event bufferErrorInterruptEvent = std::nullopt; + Event receiveEvent = std::nullopt; u16 transferBytes; void reset() { - bufferErrorInterruptevent = std::nullopt; + bufferErrorInterruptEvent = std::nullopt; + receiveEvent = std::nullopt; transferBytes = 256; } }; @@ -41,6 +43,7 @@ class CAMService { void getTransferBytes(u32 messagePointer); void setContrast(u32 messagePointer); void setFrameRate(u32 messagePointer); + void setReceiving(u32 messagePointer); void setSize(u32 messagePointer); void setTransferLines(u32 messagePointer); void setTrimming(u32 messagePointer); diff --git a/src/core/services/cam.cpp b/src/core/services/cam.cpp index 8c59321d..c0335dcb 100644 --- a/src/core/services/cam.cpp +++ b/src/core/services/cam.cpp @@ -8,6 +8,7 @@ namespace CAMCommands { enum : u32 { GetBufferErrorInterruptEvent = 0x00060040, + SetReceiving = 0x00070102, DriverInitialize = 0x00390000, DriverFinalize = 0x003A0000, SetTransferLines = 0x00090100, @@ -76,12 +77,16 @@ void CAMService::handleSyncRequest(u32 messagePointer) { case CAMCommands::GetTransferBytes: getTransferBytes(messagePointer); break; case CAMCommands::SetContrast: setContrast(messagePointer); break; case CAMCommands::SetFrameRate: setFrameRate(messagePointer); break; + case CAMCommands::SetReceiving: setReceiving(messagePointer); break; + case CAMCommands::SetSize: setSize(messagePointer); break; case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break; case CAMCommands::SetTrimming: setTrimming(messagePointer); break; case CAMCommands::SetTrimmingParamsCenter: setTrimmingParamsCenter(messagePointer); break; - case CAMCommands::SetSize: setSize(messagePointer); break; - default: Helpers::panic("Unimplemented CAM service requested. Command: %08X\n", command); break; + default: + Helpers::warn("Unimplemented CAM service requested. Command: %08X\n", command); + mem.write32(messagePointer + 4, 0); + break; } } @@ -108,7 +113,7 @@ void CAMService::setContrast(u32 messagePointer) { } void CAMService::setTransferLines(u32 messagePointer) { - const u32 portIndex = mem.read32(messagePointer + 4); + const u32 portIndex = mem.read8(messagePointer + 4); const u16 lines = mem.read16(messagePointer + 8); const u16 width = mem.read16(messagePointer + 12); const u16 height = mem.read16(messagePointer + 16); @@ -152,7 +157,7 @@ void CAMService::setSize(u32 messagePointer) { } void CAMService::setTrimming(u32 messagePointer) { - const u32 port = mem.read32(messagePointer + 4); + const u32 port = mem.read8(messagePointer + 4); const bool trim = mem.read8(messagePointer + 8) != 0; log("CAM::SetTrimming (port = %d, trimming = %s)\n", port, trim ? "enabled" : "disabled"); @@ -162,7 +167,7 @@ void CAMService::setTrimming(u32 messagePointer) { } void CAMService::setTrimmingParamsCenter(u32 messagePointer) { - const u32 port = mem.read32(messagePointer + 4); + const u32 port = mem.read8(messagePointer + 4); const s16 trimWidth = s16(mem.read16(messagePointer + 8)); const s16 trimHeight = s16(mem.read16(messagePointer + 12)); const s16 cameraWidth = s16(mem.read16(messagePointer + 16)); @@ -216,7 +221,7 @@ void CAMService::getSuitableY2RCoefficients(u32 messagePointer) { } void CAMService::getTransferBytes(u32 messagePointer) { - const u32 portIndex = mem.read32(messagePointer + 4); + const u32 portIndex = mem.read8(messagePointer + 4); const PortSelect port(portIndex); log("CAM::GetTransferBytes (port = %d)\n", portIndex); @@ -233,14 +238,14 @@ void CAMService::getTransferBytes(u32 messagePointer) { } void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) { - const u32 portIndex = mem.read32(messagePointer + 4); + const u32 portIndex = mem.read8(messagePointer + 4); const PortSelect port(portIndex); log("CAM::GetBufferErrorInterruptEvent (port = %d)\n", portIndex); mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 2)); if (port.isSinglePort()) { - auto& event = ports[port.getSingleIndex()].bufferErrorInterruptevent; + auto& event = ports[port.getSingleIndex()].bufferErrorInterruptEvent; if (!event.has_value()) { event = kernel.makeEvent(ResetType::OneShot); } @@ -251,4 +256,30 @@ void CAMService::getBufferErrorInterruptEvent(u32 messagePointer) { } else { Helpers::panic("CAM::GetBufferErrorInterruptEvent: Invalid port"); } +} + +void CAMService::setReceiving(u32 messagePointer) { + const u32 destination = mem.read32(messagePointer + 4); + const u32 portIndex = mem.read8(messagePointer + 8); + const u32 size = mem.read32(messagePointer + 12); + const u16 transferUnit = mem.read16(messagePointer + 16); + const Handle process = mem.read32(messagePointer + 24); + + const PortSelect port(portIndex); + log("CAM::SetReceiving (port = %d)\n", portIndex); + + mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 2)); + + if (port.isSinglePort()) { + auto& event = ports[port.getSingleIndex()].receiveEvent; + if (!event.has_value()) { + event = kernel.makeEvent(ResetType::OneShot); + } + + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, 0); + mem.write32(messagePointer + 12, event.value()); + } else { + Helpers::panic("CAM::SetReceiving: Invalid port"); + } } \ No newline at end of file From 707b11ccd84df170f9e57e9d5caaa7148cead27f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:23:16 +0200 Subject: [PATCH 17/50] Add CAM::StartCapture --- include/services/cam.hpp | 1 + src/core/services/cam.cpp | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/include/services/cam.hpp b/include/services/cam.hpp index eec70208..5ef3edba 100644 --- a/include/services/cam.hpp +++ b/include/services/cam.hpp @@ -48,6 +48,7 @@ class CAMService { void setTransferLines(u32 messagePointer); void setTrimming(u32 messagePointer); void setTrimmingParamsCenter(u32 messagePointer); + void startCapture(u32 messagePointer); public: CAMService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} diff --git a/src/core/services/cam.cpp b/src/core/services/cam.cpp index c0335dcb..e617ef2f 100644 --- a/src/core/services/cam.cpp +++ b/src/core/services/cam.cpp @@ -7,6 +7,7 @@ namespace CAMCommands { enum : u32 { + StartCapture = 0x00010040, GetBufferErrorInterruptEvent = 0x00060040, SetReceiving = 0x00070102, DriverInitialize = 0x00390000, @@ -82,6 +83,7 @@ void CAMService::handleSyncRequest(u32 messagePointer) { case CAMCommands::SetTransferLines: setTransferLines(messagePointer); break; case CAMCommands::SetTrimming: setTrimming(messagePointer); break; case CAMCommands::SetTrimmingParamsCenter: setTrimmingParamsCenter(messagePointer); break; + case CAMCommands::StartCapture: startCapture(messagePointer); break; default: Helpers::warn("Unimplemented CAM service requested. Command: %08X\n", command); @@ -282,4 +284,26 @@ void CAMService::setReceiving(u32 messagePointer) { } else { Helpers::panic("CAM::SetReceiving: Invalid port"); } -} \ No newline at end of file +} + +void CAMService::startCapture(u32 messagePointer) { + const u32 portIndex = mem.read8(messagePointer + 4); + const PortSelect port(portIndex); + log("CAM::StartCapture (port = %d)\n", portIndex); + + mem.write32(messagePointer, IPC::responseHeader(0x01, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); + + if (port.isValid()) { + for (int i : port.getPortIndices()) { + auto& event = ports[port.getSingleIndex()].receiveEvent; + + // Until we properly implement cameras, immediately signal the receive event + if (event.has_value()) { + kernel.signalEvent(event.value()); + } + } + } else { + Helpers::warn("CAM::StartCapture: Invalid port index"); + } +} From 194f29206b9dd759701a83b3c8cb6fcb340924e2 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:31:26 +0200 Subject: [PATCH 18/50] Stub Y2R::SetSendingYUV --- include/services/y2r.hpp | 1 + src/core/services/y2r.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/include/services/y2r.hpp b/include/services/y2r.hpp index 0a1cae2f..a08c41a2 100644 --- a/include/services/y2r.hpp +++ b/include/services/y2r.hpp @@ -98,6 +98,7 @@ class Y2RService { void setSendingY(u32 messagePointer); void setSendingU(u32 messagePointer); void setSendingV(u32 messagePointer); + void setSendingYUV(u32 messagePointer); void setSpacialDithering(u32 messagePointer); void setStandardCoeff(u32 messagePointer); void setTemporalDithering(u32 messagePointer); diff --git a/src/core/services/y2r.cpp b/src/core/services/y2r.cpp index b5daf6bb..99b18418 100644 --- a/src/core/services/y2r.cpp +++ b/src/core/services/y2r.cpp @@ -18,6 +18,7 @@ namespace Y2RCommands { SetSendingY = 0x00100102, SetSendingU = 0x00110102, SetSendingV = 0x00120102, + SetSendingYUV = 0x00130102, SetReceiving = 0x00180102, SetInputLineWidth = 0x001A0040, GetInputLineWidth = 0x001B0000, @@ -82,6 +83,7 @@ void Y2RService::handleSyncRequest(u32 messagePointer) { case Y2RCommands::SetSendingY: setSendingY(messagePointer); break; case Y2RCommands::SetSendingU: setSendingU(messagePointer); break; case Y2RCommands::SetSendingV: setSendingV(messagePointer); break; + case Y2RCommands::SetSendingYUV: setSendingYUV(messagePointer); break; case Y2RCommands::SetSpacialDithering: setSpacialDithering(messagePointer); break; case Y2RCommands::SetStandardCoeff: setStandardCoeff(messagePointer); break; case Y2RCommands::SetTemporalDithering: setTemporalDithering(messagePointer); break; @@ -399,6 +401,14 @@ void Y2RService::setSendingV(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void Y2RService::setSendingYUV(u32 messagePointer) { + log("Y2R::SetSendingYUV\n"); + Helpers::warn("Unimplemented Y2R::SetSendingYUV"); + + mem.write32(messagePointer, IPC::responseHeader(0x13, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void Y2RService::setReceiving(u32 messagePointer) { log("Y2R::SetReceiving\n"); Helpers::warn("Unimplemented Y2R::setReceiving"); From 76a14b3bae409c268e76daf721962136b690aa7e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:20:56 +0200 Subject: [PATCH 19/50] Implement CAM::GetMaxBytes/SetTransferBytes --- include/services/cam.hpp | 2 ++ src/core/services/cam.cpp | 49 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/services/cam.hpp b/include/services/cam.hpp index 5ef3edba..60ede3b9 100644 --- a/include/services/cam.hpp +++ b/include/services/cam.hpp @@ -37,6 +37,7 @@ class CAMService { // Service commands void driverInitialize(u32 messagePointer); void driverFinalize(u32 messagePointer); + void getMaxBytes(u32 messagePointer); void getMaxLines(u32 messagePointer); void getBufferErrorInterruptEvent(u32 messagePointer); void getSuitableY2RCoefficients(u32 messagePointer); @@ -45,6 +46,7 @@ class CAMService { void setFrameRate(u32 messagePointer); void setReceiving(u32 messagePointer); void setSize(u32 messagePointer); + void setTransferBytes(u32 messagePointer); void setTransferLines(u32 messagePointer); void setTrimming(u32 messagePointer); void setTrimmingParamsCenter(u32 messagePointer); diff --git a/src/core/services/cam.cpp b/src/core/services/cam.cpp index e617ef2f..b3dfd1dc 100644 --- a/src/core/services/cam.cpp +++ b/src/core/services/cam.cpp @@ -14,7 +14,9 @@ namespace CAMCommands { DriverFinalize = 0x003A0000, SetTransferLines = 0x00090100, GetMaxLines = 0x000A0080, + SetTransferBytes = 0x000B0100, GetTransferBytes = 0x000C0040, + GetMaxBytes = 0x000D0080, SetTrimming = 0x000E0080, SetTrimmingParamsCenter = 0x00120140, SetSize = 0x001F00C0, // Set size has different headers between cam:u and New3DS QTM module @@ -73,6 +75,7 @@ void CAMService::handleSyncRequest(u32 messagePointer) { case CAMCommands::DriverInitialize: driverInitialize(messagePointer); break; case CAMCommands::DriverFinalize: driverFinalize(messagePointer); break; case CAMCommands::GetBufferErrorInterruptEvent: getBufferErrorInterruptEvent(messagePointer); break; + case CAMCommands::GetMaxBytes: getMaxBytes(messagePointer); break; case CAMCommands::GetMaxLines: getMaxLines(messagePointer); break; case CAMCommands::GetSuitableY2rStandardCoefficient: getSuitableY2RCoefficients(messagePointer); break; case CAMCommands::GetTransferBytes: getTransferBytes(messagePointer); break; @@ -87,7 +90,7 @@ void CAMService::handleSyncRequest(u32 messagePointer) { default: Helpers::warn("Unimplemented CAM service requested. Command: %08X\n", command); - mem.write32(messagePointer + 4, 0); + mem.write32(messagePointer + 4, Result::Success); break; } } @@ -114,6 +117,28 @@ void CAMService::setContrast(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void CAMService::setTransferBytes(u32 messagePointer) { + const u32 portIndex = mem.read8(messagePointer + 4); + const u32 bytes = mem.read16(messagePointer + 8); + // ...why do these parameters even exist? + const u16 width = mem.read16(messagePointer + 12); + const u16 height = mem.read16(messagePointer + 16); + const PortSelect port(portIndex); + + if (port.isValid()) { + for (int i : port.getPortIndices()) { + ports[i].transferBytes = bytes; + } + } else { + Helpers::warn("CAM::SetTransferBytes: Invalid port\n"); + } + + log("CAM::SetTransferBytes (port = %d, bytes = %d, width = %d, height = %d)\n", portIndex, bytes, width, height); + + mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + void CAMService::setTransferLines(u32 messagePointer) { const u32 portIndex = mem.read8(messagePointer + 4); const u16 lines = mem.read16(messagePointer + 8); @@ -214,6 +239,28 @@ void CAMService::getMaxLines(u32 messagePointer) { } } +void CAMService::getMaxBytes(u32 messagePointer) { + const u16 width = mem.read16(messagePointer + 4); + const u16 height = mem.read16(messagePointer + 8); + log("CAM::GetMaxBytes (width = %d, height = %d)\n", width, height); + + constexpr u32 MIN_TRANSFER_UNIT = 256; + constexpr u32 MAX_BUFFER_SIZE = 2560; + if (width * height * 2 % MIN_TRANSFER_UNIT != 0) { + Helpers::panic("CAM::GetMaxLines out of range"); + } else { + u32 bytes = MAX_BUFFER_SIZE; + + while (width * height * 2 % bytes != 0) { + bytes -= MIN_TRANSFER_UNIT; + } + + mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, bytes); + } +} + void CAMService::getSuitableY2RCoefficients(u32 messagePointer) { log("CAM::GetSuitableY2RCoefficients\n"); mem.write32(messagePointer, IPC::responseHeader(0x36, 2, 0)); From 313620cad9646c963289fdfcf88527b9b59783f9 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 23 Jan 2024 21:56:24 +0200 Subject: [PATCH 20/50] Implement SDMC Write-Only archive --- include/fs/archive_sdmc.hpp | 6 ++++-- include/services/fs.hpp | 7 ++++--- src/core/fs/archive_sdmc.cpp | 17 +++++++++++++++-- src/core/services/fs.cpp | 1 + 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/include/fs/archive_sdmc.hpp b/include/fs/archive_sdmc.hpp index 4aa80eab..f63731c4 100644 --- a/include/fs/archive_sdmc.hpp +++ b/include/fs/archive_sdmc.hpp @@ -5,8 +5,10 @@ using Result::HorizonResult; class SDMCArchive : public ArchiveBase { -public: - SDMCArchive(Memory& mem) : ArchiveBase(mem) {} + bool isWriteOnly = false; // There's 2 variants of the SDMC archive: Regular one (Read/Write) and write-only + + public: + SDMCArchive(Memory& mem, bool writeOnly = false) : ArchiveBase(mem), isWriteOnly(writeOnly) {} u64 getFreeBytes() override { return 1_GB; } std::string name() override { return "SDMC"; } diff --git a/include/services/fs.hpp b/include/services/fs.hpp index 4d879e7f..4a613121 100644 --- a/include/services/fs.hpp +++ b/include/services/fs.hpp @@ -26,6 +26,7 @@ class FSService { SelfNCCHArchive selfNcch; SaveDataArchive saveData; SDMCArchive sdmc; + SDMCArchive sdmcWriteOnly; NCCHArchive ncch; // UserSaveData archives @@ -82,9 +83,9 @@ class FSService { public: FSService(Memory& mem, Kernel& kernel, const EmulatorConfig& config) - : mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), selfNcch(mem), - ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config), - systemSaveData(mem) {} + : mem(mem), saveData(mem), sharedExtSaveData_nand(mem, "../SharedFiles/NAND", true), extSaveData_sdmc(mem, "SDMC"), sdmc(mem), + sdmcWriteOnly(mem, true), selfNcch(mem), ncch(mem), userSaveData1(mem, ArchiveID::UserSaveData1), + userSaveData2(mem, ArchiveID::UserSaveData2), kernel(kernel), config(config), systemSaveData(mem) {} void reset(); void handleSyncRequest(u32 messagePointer); diff --git a/src/core/fs/archive_sdmc.cpp b/src/core/fs/archive_sdmc.cpp index 232335d0..6c34de7a 100644 --- a/src/core/fs/archive_sdmc.cpp +++ b/src/core/fs/archive_sdmc.cpp @@ -45,8 +45,16 @@ HorizonResult SDMCArchive::deleteFile(const FSPath& path) { FileDescriptor SDMCArchive::openFile(const FSPath& path, const FilePerms& perms) { FilePerms realPerms = perms; - // SD card always has read permission - realPerms.raw |= (1 << 0); + + if (isWriteOnly) { + if (perms.read()) { + Helpers::warn("SDMC: Read flag is not allowed in SDMC Write-Only archive"); + return FileError; + } + } else { + // Regular SDMC archive always has read permission + realPerms.raw |= (1 << 0); + } if ((realPerms.create() && !realPerms.write())) { Helpers::panic("[SDMC] Unsupported flags for OpenFile"); @@ -130,6 +138,11 @@ HorizonResult SDMCArchive::createDirectory(const FSPath& path) { } Rust::Result SDMCArchive::openDirectory(const FSPath& path) { + if (isWriteOnly) { + Helpers::warn("SDMC: OpenDirectory is not allowed in SDMC Write-Only archive"); + return Err(Result::FS::UnexpectedFileOrDir); + } + if (path.type == PathType::UTF16) { if (!isPathSafe(path)) { Helpers::panic("Unsafe path in SaveData::OpenDirectory"); diff --git a/src/core/services/fs.cpp b/src/core/services/fs.cpp index 264abcab..2e102958 100644 --- a/src/core/services/fs.cpp +++ b/src/core/services/fs.cpp @@ -97,6 +97,7 @@ ArchiveBase* FSService::getArchiveFromID(u32 id, const FSPath& archivePath) { case ArchiveID::SystemSaveData: return &systemSaveData; case ArchiveID::SDMC: return &sdmc; + case ArchiveID::SDMCWriteOnly: return &sdmcWriteOnly; case ArchiveID::SavedataAndNcch: return &ncch; // This can only access NCCH outside of FSPXI default: Helpers::panic("Unknown archive. ID: %d\n", id); From c108da5e026b293d7efab583224fd7dd98be9bcf Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:51:44 +0200 Subject: [PATCH 21/50] Moar appletting --- include/applets/applet.hpp | 9 ++++--- include/kernel/handles.hpp | 1 + include/memory.hpp | 3 ++- include/services/gsp_gpu.hpp | 18 +++++++++++++ src/core/applets/mii_selector.cpp | 12 +++++++++ src/core/applets/software_keyboard.cpp | 3 +++ src/core/kernel/memory_management.cpp | 1 + src/core/services/apt.cpp | 4 +-- src/core/services/gsp_gpu.cpp | 36 ++++++++++++++++++++++++-- 9 files changed, 78 insertions(+), 9 deletions(-) diff --git a/include/applets/applet.hpp b/include/applets/applet.hpp index 79fba1cb..8087ab82 100644 --- a/include/applets/applet.hpp +++ b/include/applets/applet.hpp @@ -65,10 +65,11 @@ namespace Applets { }; struct Parameter { - u32 senderID; - u32 destID; - u32 signal; - std::vector data; + u32 senderID; // ID of the parameter sender + u32 destID; // ID of the app to receive parameter + u32 signal; // Signal type (eg request) + u32 object; // Some applets will also respond with shared memory handles for transferring data between the sender and called + std::vector data; // Misc data }; class AppletBase { diff --git a/include/kernel/handles.hpp b/include/kernel/handles.hpp index b038049f..fe746b65 100644 --- a/include/kernel/handles.hpp +++ b/include/kernel/handles.hpp @@ -53,6 +53,7 @@ namespace KernelHandles { GSPSharedMemHandle = MaxServiceHandle + 1, // Handle for the GSP shared memory FontSharedMemHandle, CSNDSharedMemHandle, + APTCaptureSharedMemHandle, // Shared memory for display capture info, HIDSharedMemHandle, MinSharedMemHandle = GSPSharedMemHandle, diff --git a/include/memory.hpp b/include/memory.hpp index 3ddd00cc..640ae5f0 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -112,11 +112,12 @@ class Memory { // This tracks our OS' memory allocations std::vector memoryInfo; - std::array sharedMemBlocks = { + std::array sharedMemBlocks = { SharedMemoryBlock(0, 0, KernelHandles::FontSharedMemHandle), // Shared memory for the system font (size is 0 because we read the size from the cmrc filesystem SharedMemoryBlock(0, 0x1000, KernelHandles::GSPSharedMemHandle), // GSP shared memory SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle), // HID shared memory SharedMemoryBlock(0, 0x3000, KernelHandles::CSNDSharedMemHandle), // CSND shared memory + SharedMemoryBlock(0, 0xE7000, KernelHandles::APTCaptureSharedMemHandle), // APT Capture Buffer memory }; public: diff --git a/include/services/gsp_gpu.hpp b/include/services/gsp_gpu.hpp index 68e24580..0a8f8a0a 100644 --- a/include/services/gsp_gpu.hpp +++ b/include/services/gsp_gpu.hpp @@ -60,6 +60,15 @@ class GPUService { }; static_assert(sizeof(FramebufferUpdate) == 64, "GSP::GPU::FramebufferUpdate has the wrong size"); + // Used for saving and restoring GPU state via ImportDisplayCaptureInfo + struct CaptureInfo { + u32 leftFramebuffer; // Left framebuffer VA + u32 rightFramebuffer; // Right framebuffer VA (Top screen only) + u32 format; + u32 stride; + }; + static_assert(sizeof(CaptureInfo) == 16, "GSP::GPU::CaptureInfo has the wrong size"); + // Service commands void acquireRight(u32 messagePointer); void flushDataCache(u32 messagePointer); @@ -86,6 +95,15 @@ class GPUService { void setBufferSwapImpl(u32 screen_id, const FramebufferInfo& info); + // Get the framebuffer info in shared memory for a given screen + FramebufferUpdate* getFramebufferInfo(int screen) { + // TODO: Offset depends on GSP thread being triggered + return reinterpret_cast(&sharedMem[0x200 + screen * sizeof(FramebufferUpdate)]); + } + + FramebufferUpdate* getTopFramebufferInfo() { return getFramebufferInfo(0); } + FramebufferUpdate* getBottomFramebufferInfo() { return getFramebufferInfo(1); } + public: GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu), kernel(kernel), currentPID(currentPID) {} diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index f392d846..6a06a01a 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -1,5 +1,7 @@ #include "applets/mii_selector.hpp" +#include "kernel/handles.hpp" + using namespace Applets; void MiiSelectorApplet::reset() {} @@ -7,5 +9,15 @@ Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; } Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Mii Selector: Unimplemented ReceiveParameter"); + + Applets::Parameter param = Applets::Parameter{ + .senderID = parameter.destID, + .destID = AppletIDs::Application, + .signal = static_cast(APTSignal::Response), + .object = KernelHandles::APTCaptureSharedMemHandle, + .data = {}, + }; + + nextParameter = param; return Result::Success; } \ No newline at end of file diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index be5a9e21..fd5f4afe 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -1,5 +1,7 @@ #include "applets/software_keyboard.hpp" +#include "kernel/handles.hpp" + using namespace Applets; void SoftwareKeyboardApplet::reset() {} @@ -12,6 +14,7 @@ Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Pa .senderID = parameter.destID, .destID = AppletIDs::Application, .signal = static_cast(APTSignal::Response), + .object = KernelHandles::APTCaptureSharedMemHandle, .data = {}, }; diff --git a/src/core/kernel/memory_management.cpp b/src/core/kernel/memory_management.cpp index e90a5697..7acc749b 100644 --- a/src/core/kernel/memory_management.cpp +++ b/src/core/kernel/memory_management.cpp @@ -144,6 +144,7 @@ void Kernel::mapMemoryBlock() { printf("Mapping CSND memory block\n"); break; + case KernelHandles::APTCaptureSharedMemHandle: break; default: Helpers::panic("Mapping unknown shared memory block: %X", block); } } else { diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index 830b8377..25d063c1 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -274,7 +274,7 @@ void APTService::receiveParameter(u32 messagePointer) { // Size of parameter data mem.write32(messagePointer + 16, parameter.data.size()); mem.write32(messagePointer + 20, 0x10); - mem.write32(messagePointer + 24, 0); + mem.write32(messagePointer + 24, parameter.object); mem.write32(messagePointer + 28, 0); } @@ -296,7 +296,7 @@ void APTService::glanceParameter(u32 messagePointer) { // Size of parameter data mem.write32(messagePointer + 16, parameter.data.size()); mem.write32(messagePointer + 20, 0); - mem.write32(messagePointer + 24, 0); + mem.write32(messagePointer + 24, parameter.object); mem.write32(messagePointer + 28, 0); } diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index 861dfb0a..a698f7cc 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -143,8 +143,7 @@ void GPUService::requestInterrupt(GPUInterrupt type) { // Not emulating this causes Yoshi's Wooly World, Captain Toad, Metroid 2 et al to hang if (type == GPUInterrupt::VBlank0 || type == GPUInterrupt::VBlank1) { int screen = static_cast(type) - static_cast(GPUInterrupt::VBlank0); // 0 for top screen, 1 for bottom - // TODO: Offset depends on GSP thread being triggered - FramebufferUpdate* update = reinterpret_cast(&sharedMem[0x200 + screen * sizeof(FramebufferUpdate)]); + FramebufferUpdate* update = getFramebufferInfo(screen); if (update->dirtyFlag & 1) { setBufferSwapImpl(screen, update->framebufferInfo[update->index]); @@ -488,4 +487,37 @@ void GPUService::importDisplayCaptureInfo(u32 messagePointer) { mem.write32(messagePointer, IPC::responseHeader(0x18, 9, 0)); mem.write32(messagePointer + 4, Result::Success); + + if (sharedMem == nullptr) { + Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo called without GSP module being properly initialized!"); + return; + } + + FramebufferUpdate* topScreen = getTopFramebufferInfo(); + FramebufferUpdate* bottomScreen = getBottomFramebufferInfo(); + + // Capture the relevant data for both screens and return them to the caller + CaptureInfo topScreenCapture = { + .leftFramebuffer = topScreen->framebufferInfo[topScreen->index].leftFramebufferVaddr, + .rightFramebuffer = topScreen->framebufferInfo[topScreen->index].rightFramebufferVaddr, + .format = topScreen->framebufferInfo[topScreen->index].format, + .stride = topScreen->framebufferInfo[topScreen->index].stride, + }; + + CaptureInfo bottomScreenCapture = { + .leftFramebuffer = bottomScreen->framebufferInfo[bottomScreen->index].leftFramebufferVaddr, + .rightFramebuffer = bottomScreen->framebufferInfo[bottomScreen->index].rightFramebufferVaddr, + .format = bottomScreen->framebufferInfo[bottomScreen->index].format, + .stride = bottomScreen->framebufferInfo[bottomScreen->index].stride, + }; + + mem.write32(messagePointer + 8, topScreenCapture.leftFramebuffer); + mem.write32(messagePointer + 12, topScreenCapture.rightFramebuffer); + mem.write32(messagePointer + 16, topScreenCapture.format); + mem.write32(messagePointer + 20, topScreenCapture.stride); + + mem.write32(messagePointer + 24, bottomScreenCapture.leftFramebuffer); + mem.write32(messagePointer + 28, bottomScreenCapture.rightFramebuffer); + mem.write32(messagePointer + 32, bottomScreenCapture.format); + mem.write32(messagePointer + 36, bottomScreenCapture.stride); } \ No newline at end of file From 85a2ca1bf4de5698a026a937393ffc6923979bb3 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 24 Jan 2024 19:04:55 +0200 Subject: [PATCH 22/50] Stub svcUnmapMemoryBlock --- include/kernel/kernel.hpp | 1 + src/core/kernel/kernel.cpp | 1 + src/core/kernel/memory_management.cpp | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index 217dd148..d35d9031 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -137,6 +137,7 @@ public: void duplicateHandle(); void exitThread(); void mapMemoryBlock(); + void unmapMemoryBlock(); void queryMemory(); void getCurrentProcessorNumber(); void getProcessID(); diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp index 2e4fffff..392b87fd 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -50,6 +50,7 @@ void Kernel::serviceSVC(u32 svc) { case 0x1D: svcClearTimer(); break; case 0x1E: createMemoryBlock(); break; case 0x1F: mapMemoryBlock(); break; + case 0x20: unmapMemoryBlock(); break; case 0x21: createAddressArbiter(); break; case 0x22: arbitrateAddress(); break; case 0x23: svcCloseHandle(); break; diff --git a/src/core/kernel/memory_management.cpp b/src/core/kernel/memory_management.cpp index 7acc749b..0d234be5 100644 --- a/src/core/kernel/memory_management.cpp +++ b/src/core/kernel/memory_management.cpp @@ -207,3 +207,12 @@ void Kernel::createMemoryBlock() { regs[0] = Result::Success; regs[1] = makeMemoryBlock(addr, size, myPermission, otherPermission); } + +void Kernel::unmapMemoryBlock() { + Handle block = regs[0]; + u32 addr = regs[1]; + logSVC("Unmap memory block (block handle = %X, addr = %08X)\n", block, addr); + + Helpers::warn("Stubbed svcUnmapMemoryBlock!"); + regs[0] = Result::Success; +} From 5fe1637e172771b5f870961efdc7277818427c60 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 24 Jan 2024 20:28:16 +0200 Subject: [PATCH 23/50] Add APT::StartLibraryApplet --- include/applets/applet.hpp | 15 +++++---- include/applets/mii_selector.hpp | 2 +- include/applets/software_keyboard.hpp | 2 +- src/core/applets/mii_selector.cpp | 2 +- src/core/applets/software_keyboard.cpp | 27 ++++++++++------ src/core/services/apt.cpp | 44 ++++++++++++++++++++++++-- 6 files changed, 71 insertions(+), 21 deletions(-) diff --git a/include/applets/applet.hpp b/include/applets/applet.hpp index 8087ab82..98fb6c20 100644 --- a/include/applets/applet.hpp +++ b/include/applets/applet.hpp @@ -1,6 +1,9 @@ #pragma once +#include + #include "helpers.hpp" +#include "kernel/kernel_types.hpp" #include "memory.hpp" #include "result/result.hpp" @@ -65,11 +68,11 @@ namespace Applets { }; struct Parameter { - u32 senderID; // ID of the parameter sender - u32 destID; // ID of the app to receive parameter - u32 signal; // Signal type (eg request) - u32 object; // Some applets will also respond with shared memory handles for transferring data between the sender and called - std::vector data; // Misc data + u32 senderID; // ID of the parameter sender + u32 destID; // ID of the app to receive parameter + u32 signal; // Signal type (eg request) + u32 object; // Some applets will also respond with shared memory handles for transferring data between the sender and called + std::vector data; // Misc data }; class AppletBase { @@ -81,7 +84,7 @@ namespace Applets { virtual const char* name() = 0; // Called by APT::StartLibraryApplet and similar - virtual Result::HorizonResult start() = 0; + virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters) = 0; // Transfer parameters from application -> applet virtual Result::HorizonResult receiveParameter(const Parameter& parameter) = 0; virtual void reset() = 0; diff --git a/include/applets/mii_selector.hpp b/include/applets/mii_selector.hpp index c90d4b59..8bd757dd 100644 --- a/include/applets/mii_selector.hpp +++ b/include/applets/mii_selector.hpp @@ -4,7 +4,7 @@ namespace Applets { class MiiSelectorApplet final : public AppletBase { public: virtual const char* name() override { return "Mii Selector"; } - virtual Result::HorizonResult start() override; + virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters) override; virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; diff --git a/include/applets/software_keyboard.hpp b/include/applets/software_keyboard.hpp index f4179012..785ba20a 100644 --- a/include/applets/software_keyboard.hpp +++ b/include/applets/software_keyboard.hpp @@ -4,7 +4,7 @@ namespace Applets { class SoftwareKeyboardApplet final : public AppletBase { public: virtual const char* name() override { return "Software Keyboard"; } - virtual Result::HorizonResult start() override; + virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters) override; virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index 6a06a01a..ed1c4ad2 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -5,7 +5,7 @@ using namespace Applets; void MiiSelectorApplet::reset() {} -Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; } +Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters) { return Result::Success; } Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Mii Selector: Unimplemented ReceiveParameter"); diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index fd5f4afe..07259d0d 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -5,19 +5,28 @@ using namespace Applets; void SoftwareKeyboardApplet::reset() {} -Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; } +Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters) { return Result::Success; } Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Software keyboard: Unimplemented ReceiveParameter"); - Applets::Parameter param = Applets::Parameter{ - .senderID = parameter.destID, - .destID = AppletIDs::Application, - .signal = static_cast(APTSignal::Response), - .object = KernelHandles::APTCaptureSharedMemHandle, - .data = {}, - }; + switch (parameter.signal) { + // Signal == request -> Applet is asking swkbd for a shared memory handle for backing up the framebuffer before opening the applet + case u32(APTSignal::Request): { + Applets::Parameter param = Applets::Parameter{ + .senderID = parameter.destID, + .destID = AppletIDs::Application, + .signal = static_cast(APTSignal::Response), + .object = KernelHandles::APTCaptureSharedMemHandle, + .data = {}, + }; + + nextParameter = param; + break; + } + + default: Helpers::panic("SWKBD SIGNAL %d\n", parameter.signal); + } - nextParameter = param; return Result::Success; } \ No newline at end of file diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index 25d063c1..b2f3e849 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -64,6 +64,7 @@ void APTService::handleSyncRequest(u32 messagePointer) { case APTCommands::NotifyToWait: notifyToWait(messagePointer); break; case APTCommands::PreloadLibraryApplet: preloadLibraryApplet(messagePointer); break; case APTCommands::PrepareToStartLibraryApplet: prepareToStartLibraryApplet(messagePointer); break; + case APTCommands::StartLibraryApplet: startLibraryApplet(messagePointer); break; case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break; case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break; case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break; @@ -140,6 +141,43 @@ void APTService::prepareToStartLibraryApplet(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void APTService::startLibraryApplet(u32 messagePointer) { + const u32 appID = mem.read32(messagePointer + 4); + const u32 bufferSize = mem.read32(messagePointer + 8); + const Handle parameters = mem.read32(messagePointer + 16); + const u32 buffer = mem.read32(messagePointer + 24); + log("APT::StartLibraryApplet (app ID = %X)\n", appID); + + mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); + + Applets::AppletBase* destApplet = appletManager.getApplet(appID); + if (destApplet == nullptr) { + Helpers::warn("APT::StartLibraryApplet: Unimplemented dest applet ID"); + } else { + KernelObject* sharedMemObject = kernel.getObject(parameters); + if (sharedMemObject == nullptr) { + Helpers::warn("Couldn't find shared memory block\n"); + return; + } + + const MemoryBlock* sharedMem = sharedMemObject->getData(); + std::vector data; + data.reserve(bufferSize); + + for (u32 i = 0; i < bufferSize; i++) { + data.push_back(mem.read8(buffer + i)); + } + + Helpers::warn("DONE STARTED DAT STUFF"); + destApplet->start(*sharedMem, data); + + if (resumeEvent.has_value()) { + kernel.signalEvent(resumeEvent.value()); + } + } +} + void APTService::checkNew3DS(u32 messagePointer) { log("APT::CheckNew3DS\n"); mem.write32(messagePointer, IPC::responseHeader(0x102, 2, 0)); @@ -222,7 +260,7 @@ void APTService::sendParameter(u32 messagePointer) { const u32 parameterHandle = mem.read32(messagePointer + 24); // What dis? const u32 parameterPointer = mem.read32(messagePointer + 32); - log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X) (Stubbed)", sourceAppID, destAppID, cmd, paramSize); + log("APT::SendParameter (source app = %X, dest app = %X, cmd = %X, size = %X)", sourceAppID, destAppID, cmd, paramSize); mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 0)); mem.write32(messagePointer + 4, Result::Success); @@ -260,7 +298,7 @@ void APTService::sendParameter(u32 messagePointer) { void APTService::receiveParameter(u32 messagePointer) { const u32 app = mem.read32(messagePointer + 4); const u32 size = mem.read32(messagePointer + 8); - log("APT::ReceiveParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size); + log("APT::ReceiveParameter(app ID = %X, size = %04X)\n", app, size); if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000"); auto parameter = appletManager.receiveParameter(); @@ -281,7 +319,7 @@ void APTService::receiveParameter(u32 messagePointer) { void APTService::glanceParameter(u32 messagePointer) { const u32 app = mem.read32(messagePointer + 4); const u32 size = mem.read32(messagePointer + 8); - log("APT::GlanceParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size); + log("APT::GlanceParameter(app ID = %X, size = %04X)\n", app, size); if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000"); auto parameter = appletManager.glanceParameter(); From d09254a8ae1d61160a20d3706b0815a7601f5c95 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 24 Jan 2024 21:47:18 +0200 Subject: [PATCH 24/50] Implement software keyboard kinda --- include/applets/applet.hpp | 2 +- include/applets/mii_selector.hpp | 2 +- include/applets/software_keyboard.hpp | 151 ++++++++++++++++++++++++- src/core/applets/mii_selector.cpp | 2 +- src/core/applets/software_keyboard.cpp | 44 ++++++- src/core/services/apt.cpp | 9 +- 6 files changed, 201 insertions(+), 9 deletions(-) diff --git a/include/applets/applet.hpp b/include/applets/applet.hpp index 98fb6c20..d9ba6143 100644 --- a/include/applets/applet.hpp +++ b/include/applets/applet.hpp @@ -84,7 +84,7 @@ namespace Applets { virtual const char* name() = 0; // Called by APT::StartLibraryApplet and similar - virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters) = 0; + virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) = 0; // Transfer parameters from application -> applet virtual Result::HorizonResult receiveParameter(const Parameter& parameter) = 0; virtual void reset() = 0; diff --git a/include/applets/mii_selector.hpp b/include/applets/mii_selector.hpp index 8bd757dd..3b6cf2ec 100644 --- a/include/applets/mii_selector.hpp +++ b/include/applets/mii_selector.hpp @@ -4,7 +4,7 @@ namespace Applets { class MiiSelectorApplet final : public AppletBase { public: virtual const char* name() override { return "Mii Selector"; } - virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters) override; + virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) override; virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; diff --git a/include/applets/software_keyboard.hpp b/include/applets/software_keyboard.hpp index 785ba20a..3a167ad0 100644 --- a/include/applets/software_keyboard.hpp +++ b/include/applets/software_keyboard.hpp @@ -1,13 +1,162 @@ +#include + #include "applets/applet.hpp" +#include "swap.hpp" namespace Applets { + // Software keyboard definitions adapted from libctru/Citra + // Keyboard input filtering flags. Allows the caller to specify what input is explicitly not allowed + namespace SoftwareKeyboardFilter { + enum Filter : u32 { + Digits = 1, // Disallow the use of more than a certain number of digits (0 or more) + At = 1 << 1, // Disallow the use of the @ sign. + Percent = 1 << 2, // Disallow the use of the % sign. + Backslash = 1 << 3, // Disallow the use of the \ sign. + Profanity = 1 << 4, // Disallow profanity using Nintendo's profanity filter. + Callback = 1 << 5, // Use a callback in order to check the input. + }; + } // namespace SoftwareKeyboardFilter + + // Keyboard features. + namespace SoftwareKeyboardFeature { + enum Feature { + Parental = 1, // Parental PIN mode. + DarkenTopScreen = 1 << 1, // Darken the top screen when the keyboard is shown. + PredictiveInput = 1 << 2, // Enable predictive input (necessary for Kanji input in JPN systems). + Multiline = 1 << 3, // Enable multiline input. + FixedWidth = 1 << 4, // Enable fixed-width mode. + AllowHome = 1 << 5, // Allow the usage of the HOME button. + AllowReset = 1 << 6, // Allow the usage of a software-reset combination. + AllowPower = 1 << 7, // Allow the usage of the POWER button. + DefaultQWERTY = 1 << 9, // Default to the QWERTY page when the keyboard is shown. + }; + } // namespace SoftwareKeyboardFeature + class SoftwareKeyboardApplet final : public AppletBase { public: + static constexpr int MAX_BUTTON = 3; // Maximum number of buttons that can be in the keyboard. + static constexpr int MAX_BUTTON_TEXT_LEN = 16; // Maximum button text length, in UTF-16 code units. + static constexpr int MAX_HINT_TEXT_LEN = 64; // Maximum hint text length, in UTF-16 code units. + static constexpr int MAX_CALLBACK_MSG_LEN = 256; // Maximum filter callback error message length, in UTF-16 code units. + + // Keyboard types + enum class SoftwareKeyboardType : u32 { + Normal, // Normal keyboard with several pages (QWERTY/accents/symbol/mobile) + QWERTY, // QWERTY keyboard only. + NumPad, // Number pad. + Western, // On JPN systems, a text keyboard without Japanese input capabilities, otherwise same as SWKBD_TYPE_NORMAL. + }; + + // Keyboard dialog buttons. + enum class SoftwareKeyboardButtonConfig : u32 { + SingleButton, // Ok button + DualButton, // Cancel | Ok buttons + TripleButton, // Cancel | I Forgot | Ok buttons + NoButton, // No button (returned by swkbdInputText in special cases) + }; + + // Accepted input types. + enum class SoftwareKeyboardValidInput : u32 { + Anything, // All inputs are accepted. + NotEmpty, // Empty inputs are not accepted. + NotEmptyNotBlank, // Empty or blank inputs (consisting solely of whitespace) are not accepted. + NotBlank, // Blank inputs (consisting solely of whitespace) are not accepted, but empty inputs are. + FixedLen, // The input must have a fixed length (specified by maxTextLength in swkbdInit) + }; + + // Keyboard password modes. + enum class SoftwareKeyboardPasswordMode : u32 { + None, // Characters are not concealed. + Hide, // Characters are concealed immediately. + HideDelay, // Characters are concealed a second after they've been typed. + }; + + // Keyboard filter callback return values. + enum class SoftwareKeyboardCallbackResult : u32 { + OK, // Specifies that the input is valid. + Close, // Displays an error message, then closes the keyboard. + Continue, // Displays an error message and continues displaying the keyboard. + }; + + // Keyboard return values. + enum class SoftwareKeyboardResult : s32 { + None = -1, // Dummy/unused. + InvalidInput = -2, // Invalid parameters to swkbd. + OutOfMem = -3, // Out of memory. + + D0Click = 0, // The button was clicked in 1-button dialogs. + D1Click0, // The left button was clicked in 2-button dialogs. + D1Click1, // The right button was clicked in 2-button dialogs. + D2Click0, // The left button was clicked in 3-button dialogs. + D2Click1, // The middle button was clicked in 3-button dialogs. + D2Click2, // The right button was clicked in 3-button dialogs. + + HomePressed = 10, // The HOME button was pressed. + ResetPressed, // The soft-reset key combination was pressed. + PowerPressed, // The POWER button was pressed. + + ParentalOK = 20, // The parental PIN was verified successfully. + ParentalFail, // The parental PIN was incorrect. + + BannedInput = 30, // The filter callback returned SoftwareKeyboardCallback::CLOSE. + }; + + struct SoftwareKeyboardConfig { + enum_le type; + enum_le numButtonsM1; + enum_le validInput; + enum_le passwordMode; + s32_le isParentalScreen; + s32_le darkenTopScreen; + u32_le filterFlags; + u32_le saveStateFlags; + u16_le maxTextLength; + u16_le dictWordCount; + u16_le maxDigits; + std::array, MAX_BUTTON> buttonText; + std::array numpadKeys; + std::array hintText; // Text to display when asking the user for input + bool predictiveInput; + bool multiline; + bool fixedWidth; + bool allowHome; + bool allowReset; + bool allowPower; + bool unknown; + bool defaultQwerty; + std::array buttonSubmitsText; + u16_le language; + + u32_le initialTextOffset; // Offset of the default text in the output SharedMemory + u32_le dictOffset; + u32_le initialStatusOffset; + u32_le initialLearningOffset; + u32_le sharedMemorySize; // Size of the SharedMemory + u32_le version; + + enum_le returnCode; + + u32_le statusOffset; + u32_le learningOffset; + + u32_le textOffset; // Offset in the SharedMemory where the output text starts + u16_le textLength; // Length in characters of the output text + + enum_le callbackResult; + std::array callbackMessage; + bool skipAtCheck; + std::array pad; + }; + static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software keyboard config size is wrong"); + virtual const char* name() override { return "Software Keyboard"; } - virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters) override; + virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) override; virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; SoftwareKeyboardApplet(Memory& memory, std::optional& nextParam) : AppletBase(memory, nextParam) {} + void closeKeyboard(u32 appID); + + SoftwareKeyboardConfig config; }; } // namespace Applets \ No newline at end of file diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index ed1c4ad2..ede8de28 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -5,7 +5,7 @@ using namespace Applets; void MiiSelectorApplet::reset() {} -Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters) { return Result::Success; } +Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) { return Result::Success; } Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Mii Selector: Unimplemented ReceiveParameter"); diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index 07259d0d..2f3e2e35 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -1,11 +1,12 @@ #include "applets/software_keyboard.hpp" +#include + #include "kernel/handles.hpp" using namespace Applets; void SoftwareKeyboardApplet::reset() {} -Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters) { return Result::Success; } Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Software keyboard: Unimplemented ReceiveParameter"); @@ -25,8 +26,47 @@ Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Pa break; } - default: Helpers::panic("SWKBD SIGNAL %d\n", parameter.signal); + default: Helpers::panic("Unimplemented swkbd signal %d\n", parameter.signal); } return Result::Success; +} + +Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) { + if (parameters.size() < sizeof(SoftwareKeyboardConfig)) { + Helpers::warn("SoftwareKeyboard::Start: Invalid size for keyboard configuration"); + return Result::Success; + } + + std::memcpy(&config, ¶meters[0], sizeof(config)); + + // Temporarily hardcode the pressed button to be the firs tone + switch (config.numButtonsM1) { + case SoftwareKeyboardButtonConfig::SingleButton: config.returnCode = SoftwareKeyboardResult::D0Click; break; + case SoftwareKeyboardButtonConfig::DualButton: config.returnCode = SoftwareKeyboardResult::D1Click0; break; + case SoftwareKeyboardButtonConfig::TripleButton: config.returnCode = SoftwareKeyboardResult::D2Click0; break; + case SoftwareKeyboardButtonConfig::NoButton: config.returnCode = SoftwareKeyboardResult::None; break; + } + + if (config.filterFlags & SoftwareKeyboardFilter::Callback) { + Helpers::warn("Unimplemented software keyboard profanity callback"); + } + + closeKeyboard(appID); + return Result::Success; +} + +void SoftwareKeyboardApplet::closeKeyboard(u32 appID) { + Applets::Parameter param = Applets::Parameter{ + .senderID = appID, + .destID = AppletIDs::Application, + .signal = static_cast(APTSignal::WakeupByExit), + .object = 0, + }; + + // Copy software keyboard configuration into the response parameter + param.data.resize(sizeof(config)); + std::memcpy(¶m.data[0], &config, sizeof(config)); + + nextParameter = param; } \ No newline at end of file diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index b2f3e849..f98c1bb5 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -154,6 +154,8 @@ void APTService::startLibraryApplet(u32 messagePointer) { Applets::AppletBase* destApplet = appletManager.getApplet(appID); if (destApplet == nullptr) { Helpers::warn("APT::StartLibraryApplet: Unimplemented dest applet ID"); + mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); } else { KernelObject* sharedMemObject = kernel.getObject(parameters); if (sharedMemObject == nullptr) { @@ -169,12 +171,13 @@ void APTService::startLibraryApplet(u32 messagePointer) { data.push_back(mem.read8(buffer + i)); } - Helpers::warn("DONE STARTED DAT STUFF"); - destApplet->start(*sharedMem, data); - + Result::HorizonResult result = destApplet->start(*sharedMem, data, appID); if (resumeEvent.has_value()) { kernel.signalEvent(resumeEvent.value()); } + + mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0)); + mem.write32(messagePointer + 4, result); } } From 29494efd94377121b380da6f91f9eb3a17f1996b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 24 Jan 2024 22:21:17 +0200 Subject: [PATCH 25/50] Stub GSP::GPU::RestoreVramSysArea --- include/services/gsp_gpu.hpp | 1 + src/core/applets/software_keyboard.cpp | 15 +++++++++++++++ src/core/services/gsp_gpu.cpp | 9 +++++++++ 3 files changed, 25 insertions(+) diff --git a/include/services/gsp_gpu.hpp b/include/services/gsp_gpu.hpp index 0a8f8a0a..1e48190c 100644 --- a/include/services/gsp_gpu.hpp +++ b/include/services/gsp_gpu.hpp @@ -75,6 +75,7 @@ class GPUService { void importDisplayCaptureInfo(u32 messagePointer); void registerInterruptRelayQueue(u32 messagePointer); void releaseRight(u32 messagePointer); + void restoreVramSysArea(u32 messagePointer); void saveVramSysArea(u32 messagePointer); void setAxiConfigQoSMode(u32 messagePointer); void setBufferSwap(u32 messagePointer); diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index 2f3e2e35..432d74c6 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -1,6 +1,7 @@ #include "applets/software_keyboard.hpp" #include +#include #include "kernel/handles.hpp" @@ -38,8 +39,19 @@ Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem return Result::Success; } + // Get keyboard configuration from the application std::memcpy(&config, ¶meters[0], sizeof(config)); + const std::u16string text = u"Pander"; + u32 textAddress = sharedMem.addr; + + // Copy text to shared memory the app gave us + for (u32 i = 0; i < text.size(); i++) { + mem.write16(textAddress, u16(text[i])); + textAddress += sizeof(u16); + } + mem.write16(textAddress, 0); // Write UTF-16 null terminator + // Temporarily hardcode the pressed button to be the firs tone switch (config.numButtonsM1) { case SoftwareKeyboardButtonConfig::SingleButton: config.returnCode = SoftwareKeyboardResult::D0Click; break; @@ -48,6 +60,9 @@ Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem case SoftwareKeyboardButtonConfig::NoButton: config.returnCode = SoftwareKeyboardResult::None; break; } + config.textOffset = 0; + config.textLength = static_cast(text.size()); + if (config.filterFlags & SoftwareKeyboardFilter::Callback) { Helpers::warn("Unimplemented software keyboard profanity callback"); } diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index a698f7cc..8dff6a79 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -18,6 +18,7 @@ namespace ServiceCommands { ReleaseRight = 0x00170000, ImportDisplayCaptureInfo = 0x00180000, SaveVramSysArea = 0x00190000, + RestoreVramSysArea = 0x001A0000, SetInternalPriorities = 0x001E0080, StoreDataCache = 0x001F0082 }; @@ -51,6 +52,7 @@ void GPUService::handleSyncRequest(u32 messagePointer) { case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break; case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break; case ServiceCommands::ReleaseRight: releaseRight(messagePointer); break; + case ServiceCommands::RestoreVramSysArea: restoreVramSysArea(messagePointer); break; case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break; case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break; case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break; @@ -481,6 +483,13 @@ void GPUService::saveVramSysArea(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void GPUService::restoreVramSysArea(u32 messagePointer) { + Helpers::warn("GSP::GPU::RestoreVramSysArea (stubbed)"); + + mem.write32(messagePointer, IPC::responseHeader(0x1A, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + // Used in similar fashion to the SaveVramSysArea function void GPUService::importDisplayCaptureInfo(u32 messagePointer) { Helpers::warn("GSP::GPU::ImportDisplayCaptureInfo (stubbed)"); From ef3bd0281999f77cc7abe939724ec1873d6b590b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 25 Jan 2024 02:09:14 +0200 Subject: [PATCH 26/50] Properly transfer data between apps and applets --- src/core/applets/software_keyboard.cpp | 9 ++++++--- src/core/services/apt.cpp | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index 432d74c6..dfe68b60 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -42,7 +42,7 @@ Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem // Get keyboard configuration from the application std::memcpy(&config, ¶meters[0], sizeof(config)); - const std::u16string text = u"Pander"; + const std::u16string text = u"Pand"; u32 textAddress = sharedMem.addr; // Copy text to shared memory the app gave us @@ -55,13 +55,16 @@ Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem // Temporarily hardcode the pressed button to be the firs tone switch (config.numButtonsM1) { case SoftwareKeyboardButtonConfig::SingleButton: config.returnCode = SoftwareKeyboardResult::D0Click; break; - case SoftwareKeyboardButtonConfig::DualButton: config.returnCode = SoftwareKeyboardResult::D1Click0; break; - case SoftwareKeyboardButtonConfig::TripleButton: config.returnCode = SoftwareKeyboardResult::D2Click0; break; + case SoftwareKeyboardButtonConfig::DualButton: config.returnCode = SoftwareKeyboardResult::D1Click1; break; + case SoftwareKeyboardButtonConfig::TripleButton: config.returnCode = SoftwareKeyboardResult::D2Click2; break; case SoftwareKeyboardButtonConfig::NoButton: config.returnCode = SoftwareKeyboardResult::None; break; + default: Helpers::warn("Software keyboard: Invalid button mode specification"); break; } config.textOffset = 0; config.textLength = static_cast(text.size()); + static_assert(offsetof(SoftwareKeyboardConfig, textOffset) == 324); + static_assert(offsetof(SoftwareKeyboardConfig, textLength) == 328); if (config.filterFlags & SoftwareKeyboardFilter::Callback) { Helpers::warn("Unimplemented software keyboard profanity callback"); diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index f98c1bb5..b475b89d 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -148,9 +148,6 @@ void APTService::startLibraryApplet(u32 messagePointer) { const u32 buffer = mem.read32(messagePointer + 24); log("APT::StartLibraryApplet (app ID = %X)\n", appID); - mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0)); - mem.write32(messagePointer + 4, Result::Success); - Applets::AppletBase* destApplet = appletManager.getApplet(appID); if (destApplet == nullptr) { Helpers::warn("APT::StartLibraryApplet: Unimplemented dest applet ID"); @@ -301,6 +298,8 @@ void APTService::sendParameter(u32 messagePointer) { void APTService::receiveParameter(u32 messagePointer) { const u32 app = mem.read32(messagePointer + 4); const u32 size = mem.read32(messagePointer + 8); + // Parameter data pointer is in the thread static buffer, which starts 0x100 bytes after the command buffer + const u32 buffer = mem.read32(messagePointer + 0x100 + 4); log("APT::ReceiveParameter(app ID = %X, size = %04X)\n", app, size); if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000"); @@ -317,11 +316,18 @@ void APTService::receiveParameter(u32 messagePointer) { mem.write32(messagePointer + 20, 0x10); mem.write32(messagePointer + 24, parameter.object); mem.write32(messagePointer + 28, 0); + + const u32 transferSize = std::min(size, parameter.data.size()); + for (u32 i = 0; i < transferSize; i++) { + mem.write8(buffer + i, parameter.data[i]); + } } void APTService::glanceParameter(u32 messagePointer) { const u32 app = mem.read32(messagePointer + 4); const u32 size = mem.read32(messagePointer + 8); + // Parameter data pointer is in the thread static buffer, which starts 0x100 bytes after the command buffer + const u32 buffer = mem.read32(messagePointer + 0x100 + 4); log("APT::GlanceParameter(app ID = %X, size = %04X)\n", app, size); if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000"); @@ -339,6 +345,11 @@ void APTService::glanceParameter(u32 messagePointer) { mem.write32(messagePointer + 20, 0); mem.write32(messagePointer + 24, parameter.object); mem.write32(messagePointer + 28, 0); + + const u32 transferSize = std::min(size, parameter.data.size()); + for (u32 i = 0; i < transferSize; i++) { + mem.write8(buffer + i, parameter.data[i]); + } } void APTService::replySleepQuery(u32 messagePointer) { From 8fc61769abb6377e0a87c88fc12e24f1f4e65471 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 25 Jan 2024 02:31:01 +0200 Subject: [PATCH 27/50] Implement PTM::GetPedometerState --- include/services/boss.hpp | 1 + include/services/ptm.hpp | 1 + src/core/services/boss.cpp | 9 +++++++++ src/core/services/ptm.cpp | 13 ++++++++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/include/services/boss.hpp b/include/services/boss.hpp index 57452e2d..769184e5 100644 --- a/include/services/boss.hpp +++ b/include/services/boss.hpp @@ -14,6 +14,7 @@ class BOSSService { void cancelTask(u32 messagePointer); void initializeSession(u32 messagePointer); void getErrorCode(u32 messagePointer); + void getNewArrivalFlag(u32 messagePointer); void getNsDataIdList(u32 messagePointer, u32 commandWord); void getOptoutFlag(u32 messagePointer); void getStorageEntryInfo(u32 messagePointer); // Unknown what this is, name taken from Citra diff --git a/include/services/ptm.hpp b/include/services/ptm.hpp index ae845725..f752839b 100644 --- a/include/services/ptm.hpp +++ b/include/services/ptm.hpp @@ -17,6 +17,7 @@ class PTMService { void getAdapterState(u32 messagePointer); void getBatteryChargeState(u32 messagePointer); void getBatteryLevel(u32 messagePointer); + void getPedometerState(u32 messagePointer); void getStepHistory(u32 messagePointer); void getStepHistoryAll(u32 messagePointer); void getTotalStepCount(u32 messagePointer); diff --git a/src/core/services/boss.cpp b/src/core/services/boss.cpp index 30190cd3..a8f7194b 100644 --- a/src/core/services/boss.cpp +++ b/src/core/services/boss.cpp @@ -6,6 +6,7 @@ namespace BOSSCommands { InitializeSession = 0x00010082, UnregisterStorage = 0x00030000, GetTaskStorageInfo = 0x00040000, + GetNewArrivalFlag = 0x00070000, RegisterNewArrivalEvent = 0x00080002, SetOptoutFlag = 0x00090040, GetOptoutFlag = 0x000A0000, @@ -37,6 +38,7 @@ void BOSSService::handleSyncRequest(u32 messagePointer) { switch (command) { case BOSSCommands::CancelTask: cancelTask(messagePointer); break; case BOSSCommands::GetErrorCode: getErrorCode(messagePointer); break; + case BOSSCommands::GetNewArrivalFlag: getNewArrivalFlag(messagePointer); break; case BOSSCommands::GetNsDataIdList: case BOSSCommands::GetNsDataIdList1: getNsDataIdList(messagePointer, command); break; @@ -240,4 +242,11 @@ void BOSSService::unregisterStorage(u32 messagePointer) { log("BOSS::UnregisterStorage (stubbed)\n"); mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0)); mem.write32(messagePointer + 4, Result::Success); +} + +void BOSSService::getNewArrivalFlag(u32 messagePointer) { + log("BOSS::GetNewArrivalFlag (stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write8(messagePointer + 8, 0); // Flag } \ No newline at end of file diff --git a/src/core/services/ptm.cpp b/src/core/services/ptm.cpp index 071fa012..4cd7e095 100644 --- a/src/core/services/ptm.cpp +++ b/src/core/services/ptm.cpp @@ -6,6 +6,7 @@ namespace PTMCommands { GetAdapterState = 0x00050000, GetBatteryLevel = 0x00070000, GetBatteryChargeState = 0x00080000, + GetPedometerState = 0x00090000, GetStepHistory = 0x000B00C2, GetTotalStepCount = 0x000C0000, GetStepHistoryAll = 0x000F0084, @@ -30,6 +31,7 @@ void PTMService::handleSyncRequest(u32 messagePointer, PTMService::Type type) { case PTMCommands::GetAdapterState: getAdapterState(messagePointer); break; case PTMCommands::GetBatteryChargeState: getBatteryChargeState(messagePointer); break; case PTMCommands::GetBatteryLevel: getBatteryLevel(messagePointer); break; + case PTMCommands::GetPedometerState: getPedometerState(messagePointer); break; case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break; case PTMCommands::GetStepHistoryAll: getStepHistoryAll(messagePointer); break; case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break; @@ -67,11 +69,20 @@ void PTMService::getBatteryChargeState(u32 messagePointer) { // We're only charging if the battery is not already full const bool charging = config.chargerPlugged && (config.batteryPercentage < 100); - mem.write32(messagePointer, IPC::responseHeader(0x7, 2, 0)); + mem.write32(messagePointer, IPC::responseHeader(0x8, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write8(messagePointer + 8, charging ? 1 : 0); } +void PTMService::getPedometerState(u32 messagePointer) { + log("PTM::GetPedometerState"); + const bool countingSteps = true; + + mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write8(messagePointer + 8, countingSteps ? 1 : 0); +} + void PTMService::getBatteryLevel(u32 messagePointer) { log("PTM::GetBatteryLevel"); From 6e6c84eebba714c8f21f9e5ae77a36a53f240079 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 25 Jan 2024 03:39:48 +0200 Subject: [PATCH 28/50] Add Mii Selector --- include/applets/applet.hpp | 2 +- include/applets/mii_selector.hpp | 72 +++++++++++++++++++++++++- include/applets/software_keyboard.hpp | 2 +- src/core/applets/mii_selector.cpp | 68 +++++++++++++++++++++++- src/core/applets/software_keyboard.cpp | 9 +++- src/core/services/apt.cpp | 8 +-- src/core/services/ptm.cpp | 2 +- 7 files changed, 150 insertions(+), 13 deletions(-) diff --git a/include/applets/applet.hpp b/include/applets/applet.hpp index d9ba6143..48f20b03 100644 --- a/include/applets/applet.hpp +++ b/include/applets/applet.hpp @@ -84,7 +84,7 @@ namespace Applets { virtual const char* name() = 0; // Called by APT::StartLibraryApplet and similar - virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) = 0; + virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector& parameters, u32 appID) = 0; // Transfer parameters from application -> applet virtual Result::HorizonResult receiveParameter(const Parameter& parameter) = 0; virtual void reset() = 0; diff --git a/include/applets/mii_selector.hpp b/include/applets/mii_selector.hpp index 3b6cf2ec..36f9fe79 100644 --- a/include/applets/mii_selector.hpp +++ b/include/applets/mii_selector.hpp @@ -1,13 +1,83 @@ +#include + #include "applets/applet.hpp" +#include "swap.hpp" namespace Applets { + struct MiiConfig { + u8 enableCancelButton; + u8 enableGuestMii; + u8 showOnTopScreen; + std::array pad1; + std::array title; + std::array pad2; + u8 showGuestMiis; + std::array pad3; + u32 initiallySelectedIndex; + std::array guestMiiWhitelist; + std::array userMiiWhitelist; + std::array pad4; + u32 magicValue; + }; + static_assert(sizeof(MiiConfig) == 0x104, "Mii config size is wrong"); + + // Some members of this struct are not properly aligned so we need pragma pack +#pragma pack(push, 1) + struct MiiData { + u8 version; + u8 miiOptions; + u8 miiPos; + u8 consoleID; + + u64_be systemID; + u32_be miiID; + std::array creatorMAC; + u16 padding; + + u16_be miiDetails; + std::array miiName; + u8 height; + u8 width; + + u8 faceStyle; + u8 faceDetails; + u8 hairStyle; + u8 hairDetails; + u32_be eyeDetails; + u32_be eyebrowDetails; + u16_be noseDetails; + u16_be mouthDetails; + u16_be moustacheDetails; + u16_be beardDetails; + u16_be glassesDetails; + u16_be moleDetails; + + std::array authorName; + }; +#pragma pack(pop) + static_assert(sizeof(MiiData) == 0x5C, "MiiData structure has incorrect size"); + + struct MiiResult { + u32_be returnCode; + u32_be isGuestMiiSelected; + u32_be selectedGuestMiiIndex; + MiiData selectedMiiData; + u16_be unknown1; + u16_be miiChecksum; + std::array guestMiiName; + }; + static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size"); + class MiiSelectorApplet final : public AppletBase { public: virtual const char* name() override { return "Mii Selector"; } - virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) override; + virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector& parameters, u32 appID) override; virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; + MiiResult output; + MiiConfig config; + MiiResult getDefaultMii(); MiiSelectorApplet(Memory& memory, std::optional& nextParam) : AppletBase(memory, nextParam) {} }; } // namespace Applets \ No newline at end of file diff --git a/include/applets/software_keyboard.hpp b/include/applets/software_keyboard.hpp index 3a167ad0..f753566d 100644 --- a/include/applets/software_keyboard.hpp +++ b/include/applets/software_keyboard.hpp @@ -150,7 +150,7 @@ namespace Applets { static_assert(sizeof(SoftwareKeyboardConfig) == 0x400, "Software keyboard config size is wrong"); virtual const char* name() override { return "Software Keyboard"; } - virtual Result::HorizonResult start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) override; + virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector& parameters, u32 appID) override; virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index ede8de28..5b7637e3 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -1,11 +1,38 @@ #include "applets/mii_selector.hpp" +#include +#include + #include "kernel/handles.hpp" using namespace Applets; void MiiSelectorApplet::reset() {} -Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) { return Result::Success; } +Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock* sharedMem, const std::vector& parameters, u32 appID) { + // Get mii configuration from the application + std::memcpy(&config, ¶meters[0], sizeof(config)); + + Applets::Parameter param = Applets::Parameter{ + .senderID = appID, + .destID = AppletIDs::Application, + .signal = static_cast(APTSignal::WakeupByExit), + .object = 0, + }; + + // Thanks to Citra devs as always for the default mii data and other applet help + output = getDefaultMii(); + output.returnCode = 0; // Success + // output.selectedMiiData = miiData; + output.selectedGuestMiiIndex = std::numeric_limits::max(); + output.miiChecksum = boost::crc<16, 0x1021, 0, 0, false, false>(&output.selectedMiiData, sizeof(MiiData) + sizeof(output.unknown1)); + + // Copy output into the response parameter + param.data.resize(sizeof(output)); + std::memcpy(¶m.data[0], &output, sizeof(output)); + + nextParameter = param; + return Result::Success; +} Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Mii Selector: Unimplemented ReceiveParameter"); @@ -20,4 +47,43 @@ Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Paramet nextParameter = param; return Result::Success; +} + +MiiResult MiiSelectorApplet::getDefaultMii() { + // This data was obtained from Citra + MiiData miiData; + miiData.version = 0x03; + miiData.miiOptions = 0x00; + miiData.miiPos = 0x10; + miiData.consoleID = 0x30; + miiData.systemID = 0xD285B6B300C8850A; + miiData.miiID = 0x98391EE4; + miiData.creatorMAC = {0x40, 0xF4, 0x07, 0xB7, 0x37, 0x10}; + miiData.padding = 0x0000; + miiData.miiDetails = 0xA600; + miiData.miiName = {'P', 'a', 'n', 'd', 'a', '3', 'D', 'S', 0x0, 0x0}; + miiData.height = 0x40; + miiData.width = 0x40; + miiData.faceStyle = 0x00; + miiData.faceDetails = 0x00; + miiData.hairStyle = 0x21; + miiData.hairDetails = 0x01; + miiData.eyeDetails = 0x02684418; + miiData.eyebrowDetails = 0x26344614; + miiData.noseDetails = 0x8112; + miiData.mouthDetails = 0x1768; + miiData.moustacheDetails = 0x0D00; + miiData.beardDetails = 0x0029; + miiData.glassesDetails = 0x0052; + miiData.moleDetails = 0x4850; + miiData.authorName = {u'B', u'O', u'N', u'K', u'E', u'R'}; + + MiiResult result; + result.returnCode = 0x0; + result.isGuestMiiSelected = 0x0; + result.selectedGuestMiiIndex = std::numeric_limits::max(); + result.selectedMiiData = miiData; + result.guestMiiName.fill(0x0); + + return result; } \ No newline at end of file diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index dfe68b60..520e89c1 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -33,17 +33,22 @@ Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Pa return Result::Success; } -Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock& sharedMem, const std::vector& parameters, u32 appID) { +Result::HorizonResult SoftwareKeyboardApplet::start(const MemoryBlock* sharedMem, const std::vector& parameters, u32 appID) { if (parameters.size() < sizeof(SoftwareKeyboardConfig)) { Helpers::warn("SoftwareKeyboard::Start: Invalid size for keyboard configuration"); return Result::Success; } + if (sharedMem == nullptr) { + Helpers::warn("SoftwareKeyboard: Missing shared memory"); + return Result::Success; + } + // Get keyboard configuration from the application std::memcpy(&config, ¶meters[0], sizeof(config)); const std::u16string text = u"Pand"; - u32 textAddress = sharedMem.addr; + u32 textAddress = sharedMem->addr; // Copy text to shared memory the app gave us for (u32 i = 0; i < text.size(); i++) { diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index b475b89d..404a0e59 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -155,12 +155,8 @@ void APTService::startLibraryApplet(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } else { KernelObject* sharedMemObject = kernel.getObject(parameters); - if (sharedMemObject == nullptr) { - Helpers::warn("Couldn't find shared memory block\n"); - return; - } - const MemoryBlock* sharedMem = sharedMemObject->getData(); + const MemoryBlock* sharedMem = sharedMemObject ? sharedMemObject->getData() : nullptr; std::vector data; data.reserve(bufferSize); @@ -168,7 +164,7 @@ void APTService::startLibraryApplet(u32 messagePointer) { data.push_back(mem.read8(buffer + i)); } - Result::HorizonResult result = destApplet->start(*sharedMem, data, appID); + Result::HorizonResult result = destApplet->start(sharedMem, data, appID); if (resumeEvent.has_value()) { kernel.signalEvent(resumeEvent.value()); } diff --git a/src/core/services/ptm.cpp b/src/core/services/ptm.cpp index 4cd7e095..67451cc2 100644 --- a/src/core/services/ptm.cpp +++ b/src/core/services/ptm.cpp @@ -76,7 +76,7 @@ void PTMService::getBatteryChargeState(u32 messagePointer) { void PTMService::getPedometerState(u32 messagePointer) { log("PTM::GetPedometerState"); - const bool countingSteps = true; + constexpr bool countingSteps = true; mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0)); mem.write32(messagePointer + 4, Result::Success); From f0c20d70bc82abdcd4182992fb688a44243828b2 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:31:17 +0200 Subject: [PATCH 29/50] Add error/EULA applet --- CMakeLists.txt | 6 +++-- include/applets/applet_manager.hpp | 2 ++ include/applets/error_applet.hpp | 15 ++++++++++++ src/core/applets/applet_manager.cpp | 6 ++++- src/core/applets/error_applet.cpp | 32 ++++++++++++++++++++++++++ src/core/applets/mii_selector.cpp | 2 -- src/core/applets/software_keyboard.cpp | 2 -- 7 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 include/applets/error_applet.hpp create mode 100644 src/core/applets/error_applet.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e557c5c8..baa37466 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,7 +188,9 @@ set(FS_SOURCE_FILES src/core/fs/archive_self_ncch.cpp src/core/fs/archive_save_d src/core/fs/ivfc.cpp src/core/fs/archive_user_save_data.cpp src/core/fs/archive_system_save_data.cpp ) -set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp) +set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selector.cpp src/core/applets/software_keyboard.cpp src/core/applets/applet_manager.cpp + src/core/applets/error_applet.cpp +) set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) # Frontend source files @@ -244,7 +246,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/services/news_u.hpp include/applets/software_keyboard.hpp include/applets/applet_manager.hpp include/fs/archive_user_save_data.hpp include/services/amiibo_device.hpp include/services/nfc_types.hpp include/swap.hpp include/services/csnd.hpp include/services/nwm_uds.hpp include/fs/archive_system_save_data.hpp include/lua_manager.hpp include/memory_mapped_file.hpp include/hydra_icon.hpp - include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/scheduler.hpp + include/PICA/dynapica/shader_rec_emitter_arm64.hpp include/scheduler.hpp include/applets/error_applet.hpp ) cmrc_add_resource_library( diff --git a/include/applets/applet_manager.hpp b/include/applets/applet_manager.hpp index e75e1268..d8cfff12 100644 --- a/include/applets/applet_manager.hpp +++ b/include/applets/applet_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "applets/error_applet.hpp" #include "applets/mii_selector.hpp" #include "applets/software_keyboard.hpp" #include "helpers.hpp" @@ -11,6 +12,7 @@ namespace Applets { class AppletManager { MiiSelectorApplet miiSelector; SoftwareKeyboardApplet swkbd; + ErrorApplet error; std::optional nextParameter = std::nullopt; public: diff --git a/include/applets/error_applet.hpp b/include/applets/error_applet.hpp new file mode 100644 index 00000000..4dcc319d --- /dev/null +++ b/include/applets/error_applet.hpp @@ -0,0 +1,15 @@ +#include + +#include "applets/applet.hpp" + +namespace Applets { + class ErrorApplet final : public AppletBase { + public: + virtual const char* name() override { return "Error/EULA Agreement"; } + virtual Result::HorizonResult start(const MemoryBlock* sharedMem, const std::vector& parameters, u32 appID) override; + virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; + virtual void reset() override; + + ErrorApplet(Memory& memory, std::optional& nextParam) : AppletBase(memory, nextParam) {} + }; +} // namespace Applets \ No newline at end of file diff --git a/src/core/applets/applet_manager.cpp b/src/core/applets/applet_manager.cpp index c2791777..cdb19319 100644 --- a/src/core/applets/applet_manager.cpp +++ b/src/core/applets/applet_manager.cpp @@ -4,13 +4,14 @@ using namespace Applets; -AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter) {} +AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter), error(mem, nextParameter) {} void AppletManager::reset() { nextParameter = std::nullopt; miiSelector.reset(); swkbd.reset(); + error.reset(); } AppletBase* AppletManager::getApplet(u32 id) { @@ -21,6 +22,9 @@ AppletBase* AppletManager::getApplet(u32 id) { case AppletIDs::SoftwareKeyboard: case AppletIDs::SoftwareKeyboard2: return &swkbd; + case AppletIDs::ErrDisp: + case AppletIDs::ErrDisp2: return &error; + default: return nullptr; } } diff --git a/src/core/applets/error_applet.cpp b/src/core/applets/error_applet.cpp new file mode 100644 index 00000000..5acbcbba --- /dev/null +++ b/src/core/applets/error_applet.cpp @@ -0,0 +1,32 @@ +#include "applets/error_applet.hpp" +#include "kernel/handles.hpp" + +using namespace Applets; + +void ErrorApplet::reset() {} + +Result::HorizonResult ErrorApplet::start(const MemoryBlock* sharedMem, const std::vector& parameters, u32 appID) { + Applets::Parameter param = Applets::Parameter{ + .senderID = appID, + .destID = AppletIDs::Application, + .signal = static_cast(APTSignal::WakeupByExit), + .object = 0, + .data = parameters, // TODO: Figure out how the data format for this applet + }; + + nextParameter = param; + return Result::Success; +} + +Result::HorizonResult ErrorApplet::receiveParameter(const Applets::Parameter& parameter) { + Applets::Parameter param = Applets::Parameter{ + .senderID = parameter.destID, + .destID = AppletIDs::Application, + .signal = static_cast(APTSignal::Response), + .object = KernelHandles::APTCaptureSharedMemHandle, + .data = {}, + }; + + nextParameter = param; + return Result::Success; +} \ No newline at end of file diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index 5b7637e3..d6dd79da 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -35,8 +35,6 @@ Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock* sharedMem, con } Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) { - Helpers::warn("Mii Selector: Unimplemented ReceiveParameter"); - Applets::Parameter param = Applets::Parameter{ .senderID = parameter.destID, .destID = AppletIDs::Application, diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index 520e89c1..4a91b790 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -10,8 +10,6 @@ using namespace Applets; void SoftwareKeyboardApplet::reset() {} Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Parameter& parameter) { - Helpers::warn("Software keyboard: Unimplemented ReceiveParameter"); - switch (parameter.signal) { // Signal == request -> Applet is asking swkbd for a shared memory handle for backing up the framebuffer before opening the applet case u32(APTSignal::Request): { From 52accdde4334c7fdc16971cfe561634528edbb7d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 25 Jan 2024 21:45:15 +0200 Subject: [PATCH 30/50] Test to fix android build --- include/scheduler.hpp | 2 ++ src/emulator.cpp | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/scheduler.hpp b/include/scheduler.hpp index 92328878..5645f47d 100644 --- a/include/scheduler.hpp +++ b/include/scheduler.hpp @@ -48,6 +48,8 @@ struct Scheduler { // Clear any pending events events.clear(); + addEvent(Scheduler::EventType::VBlank, arm11Clock / 60); + // Add a dummy event to always keep the scheduler non-empty addEvent(EventType::Panic, std::numeric_limits::max()); } diff --git a/src/emulator.cpp b/src/emulator.cpp index 1ac8d5b2..c567cbc7 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -48,7 +48,6 @@ void Emulator::reset(ReloadOption reload) { memory.reset(); // Reset scheduler and add a VBlank event scheduler.reset(); - scheduler.addEvent(Scheduler::EventType::VBlank, CPU::ticksPerSec / 60); // Kernel must be reset last because it depends on CPU/Memory state kernel.reset(); From 99a1a0133ddb4f575f49ae740a5cfa1e41d63ba7 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 01:58:21 +0200 Subject: [PATCH 31/50] Initial idle skip implementation --- include/cpu_dynarmic.hpp | 2 ++ include/kernel/kernel.hpp | 1 + include/kernel/kernel_types.hpp | 1 + src/core/kernel/events.cpp | 2 ++ src/core/kernel/threads.cpp | 35 ++++++++++++++++++++++++++------- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/include/cpu_dynarmic.hpp b/include/cpu_dynarmic.hpp index 97acddd0..048bc62a 100644 --- a/include/cpu_dynarmic.hpp +++ b/include/cpu_dynarmic.hpp @@ -179,6 +179,8 @@ class CPU { return scheduler; } + void addTicks(u64 ticks) { env.AddTicks(ticks); } + void clearCache() { jit->ClearCache(); } void runFrame(); }; \ No newline at end of file diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index d35d9031..e78a588a 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -95,6 +95,7 @@ public: void releaseMutex(Mutex* moo); void cancelTimer(Timer* timer); void signalTimer(Handle timerHandle, Timer* timer); + u64 getWakeupTick(s64 ns); // Wake up the thread with the highest priority out of all threads in the waitlist // Returns the index of the woken up thread diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 79684e17..2c357037 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -123,6 +123,7 @@ struct Thread { bool waitAll; // For WaitSynchronizationN: The "out" pointer u32 outPointer; + u64 wakeupTick; // Thread context used for switching between threads std::array gprs; diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index 7da4788e..b5b1c06e 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -127,6 +127,7 @@ void Kernel::waitSynchronization1() { t.waitList.resize(1); t.status = ThreadStatus::WaitSync1; t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); t.waitingNanoseconds = ns; t.waitList[0] = handle; @@ -222,6 +223,7 @@ void Kernel::waitSynchronizationN() { t.outPointer = outPointer; t.waitingNanoseconds = ns; t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); for (s32 i = 0; i < handleCount; i++) { t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index 239a6617..cea48bd2 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -52,14 +52,8 @@ bool Kernel::canThreadRun(const Thread& t) { return true; } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) { - const u64 elapsedTicks = cpu.getTicks() - t.sleepTick; - - constexpr double ticksPerSec = double(CPU::ticksPerSec); - constexpr double nsPerTick = ticksPerSec / 1000000000.0; - // TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All} - const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick); - return elapsedNs >= t.waitingNanoseconds; + return cpu.getTicks() >= t.wakeupTick; } // Handle timeouts and stuff here @@ -82,6 +76,14 @@ std::optional Kernel::getNextThread() { return std::nullopt; } +u64 Kernel::getWakeupTick(s64 ns) { + if (ns == -1) { + return std::numeric_limits::max(); + } + + return cpu.getTicks() + Scheduler::nsToCycles(ns); +} + // See if there is a higher priority, ready thread and switch to that void Kernel::rescheduleThreads() { Thread& current = threads[currentThreadIndex]; // Current running thread @@ -368,6 +370,24 @@ void Kernel::sleepThread(s64 ns) { if (index != idleThreadIndex) { switchThread(index); } + } else { + if (currentThreadIndex == idleThreadIndex) { + const Scheduler& scheduler = cpu.getScheduler(); + u64 timestamp = scheduler.nextTimestamp; + + for (auto i : threadIndices) { + const Thread& t = threads[i]; + if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAny || + t.status == ThreadStatus::WaitSyncAll) { + timestamp = std::min(timestamp, t.wakeupTick); + } + } + + if (timestamp > scheduler.currentTimestamp) { + u64 idleCycles = timestamp - scheduler.currentTimestamp; + cpu.addTicks(idleCycles); + } + } } } else { // If we're sleeping for >= 0 ns Thread& t = threads[currentThreadIndex]; @@ -375,6 +395,7 @@ void Kernel::sleepThread(s64 ns) { t.status = ThreadStatus::WaitSleep; t.waitingNanoseconds = ns; t.sleepTick = cpu.getTicks(); + t.wakeupTick = getWakeupTick(ns); requireReschedule(); } From 491b415759348c5cb5a93f7007e9865148a88ccb Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:38:27 +0200 Subject: [PATCH 32/50] Remove thread sleepTick/waitingNs --- include/kernel/kernel_types.hpp | 76 ++++++++++++++++----------------- src/core/kernel/events.cpp | 4 -- src/core/kernel/idle_thread.cpp | 13 +----- src/core/kernel/threads.cpp | 2 - 4 files changed, 37 insertions(+), 58 deletions(-) diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 2c357037..01af4bd9 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -83,57 +83,53 @@ struct Port { }; struct Session { - Handle portHandle; // The port this session is subscribed to - Session(Handle portHandle) : portHandle(portHandle) {} + Handle portHandle; // The port this session is subscribed to + Session(Handle portHandle) : portHandle(portHandle) {} }; enum class ThreadStatus { - Running, // Currently running - Ready, // Ready to run - WaitArbiter, // Waiting on an address arbiter - WaitSleep, // Waiting due to a SleepThread SVC - WaitSync1, // Waiting for the single object in the wait list to be ready - WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready - WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready - WaitIPC, // Waiting for the reply from an IPC request - Dormant, // Created but not yet made ready - Dead // Run to completion, or forcefully terminated + Running, // Currently running + Ready, // Ready to run + WaitArbiter, // Waiting on an address arbiter + WaitSleep, // Waiting due to a SleepThread SVC + WaitSync1, // Waiting for the single object in the wait list to be ready + WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready + WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready + WaitIPC, // Waiting for the reply from an IPC request + Dormant, // Created but not yet made ready + Dead // Run to completion, or forcefully terminated }; struct Thread { - u32 initialSP; // Initial r13 value - u32 entrypoint; // Initial r15 value - u32 priority; - u32 arg; - ProcessorID processorID; - ThreadStatus status; - Handle handle; // OS handle for this thread - int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on + u32 initialSP; // Initial r13 value + u32 entrypoint; // Initial r15 value + u32 priority; + u32 arg; + ProcessorID processorID; + ThreadStatus status; + Handle handle; // OS handle for this thread + int index; // Index of the thread. 0 for the first thread, 1 for the second, and so on - // The waiting address for threads that are waiting on an AddressArbiter - u32 waitingAddress; + // The waiting address for threads that are waiting on an AddressArbiter + u32 waitingAddress; - // The nanoseconds until a thread wakes up from being asleep or from timing out while waiting on an arbiter - u64 waitingNanoseconds; - // The tick this thread went to sleep on - u64 sleepTick; - // For WaitSynchronization(N): A vector of objects this thread is waiting for - std::vector waitList; - // For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one - bool waitAll; - // For WaitSynchronizationN: The "out" pointer - u32 outPointer; + // For WaitSynchronization(N): A vector of objects this thread is waiting for + std::vector waitList; + // For WaitSynchronizationN: Shows whether the object should wait for all objects in the wait list or just one + bool waitAll; + // For WaitSynchronizationN: The "out" pointer + u32 outPointer; u64 wakeupTick; - // Thread context used for switching between threads - std::array gprs; - std::array fprs; // Stored as u32 because dynarmic does it - u32 cpsr; - u32 fpscr; - u32 tlsBase; // Base pointer for thread-local storage + // Thread context used for switching between threads + std::array gprs; + std::array fprs; // Stored as u32 because dynarmic does it + u32 cpsr; + u32 fpscr; + u32 tlsBase; // Base pointer for thread-local storage - // A list of threads waiting for this thread to terminate. Yes, threads are sync objects too. - u64 threadsWaitingForTermination; + // A list of threads waiting for this thread to terminate. Yes, threads are sync objects too. + u64 threadsWaitingForTermination; }; static const char* kernelObjectTypeToString(KernelObjectType t) { diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index b5b1c06e..b2f89fbf 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -126,9 +126,7 @@ void Kernel::waitSynchronization1() { auto& t = threads[currentThreadIndex]; t.waitList.resize(1); t.status = ThreadStatus::WaitSync1; - t.sleepTick = cpu.getTicks(); t.wakeupTick = getWakeupTick(ns); - t.waitingNanoseconds = ns; t.waitList[0] = handle; // Add the current thread to the object's wait list @@ -221,8 +219,6 @@ void Kernel::waitSynchronizationN() { t.waitList.resize(handleCount); t.status = ThreadStatus::WaitSyncAny; t.outPointer = outPointer; - t.waitingNanoseconds = ns; - t.sleepTick = cpu.getTicks(); t.wakeupTick = getWakeupTick(ns); for (s32 i = 0; i < handleCount; i++) { diff --git a/src/core/kernel/idle_thread.cpp b/src/core/kernel/idle_thread.cpp index 5abba373..d666968b 100644 --- a/src/core/kernel/idle_thread.cpp +++ b/src/core/kernel/idle_thread.cpp @@ -8,13 +8,6 @@ The code for our idle thread looks like this idle_thread_main: - mov r0, #4096 @ Loop counter - - .loop: - nop; nop; nop; nop @ NOP 4 times to waste some cycles - subs r0, #1 @ Decrement counter by 1, go back to looping if loop counter != 0 - bne .loop - // Sleep for 0 seconds with the SleepThread SVC, which just yields execution mov r0, #0 mov r1, #0 @@ -24,14 +17,10 @@ idle_thread_main: */ static constexpr u8 idleThreadCode[] = { - 0x01, 0x0A, 0xA0, 0xE3, // mov r0, #4096 - 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, // nop (4 times) - 0x01, 0x00, 0x50, 0xE2, // subs r0, #1 - 0xF9, 0xFF, 0xFF, 0x1A, // bne loop 0x00, 0x00, 0xA0, 0xE3, // mov r0, #0 0x00, 0x10, 0xA0, 0xE3, // mov r1, #0 0x0A, 0x00, 0x00, 0xEF, // svc SleepThread - 0xF4, 0xFF, 0xFF, 0xEA // b idle_thread_main + 0xFB, 0xFF, 0xFF, 0xEA // b idle_thread_main }; // Set up an idle thread to run when no thread is able to run diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index cea48bd2..bf05fd89 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -393,8 +393,6 @@ void Kernel::sleepThread(s64 ns) { Thread& t = threads[currentThreadIndex]; t.status = ThreadStatus::WaitSleep; - t.waitingNanoseconds = ns; - t.sleepTick = cpu.getTicks(); t.wakeupTick = getWakeupTick(ns); requireReschedule(); From 8ff2d7f9b2bb2b70a7420d6482ba99dd36d16a98 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 15:44:01 +0200 Subject: [PATCH 33/50] Update comment --- src/core/kernel/threads.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index bf05fd89..3a6201c1 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -77,6 +77,7 @@ std::optional Kernel::getNextThread() { } u64 Kernel::getWakeupTick(s64 ns) { + // Timeout == -1 means that the thread doesn't plan on waking up automatically if (ns == -1) { return std::numeric_limits::max(); } From 63f54478f0840a168a942999c894edad83bc6b59 Mon Sep 17 00:00:00 2001 From: offtkp Date: Sat, 27 Jan 2024 17:01:02 +0200 Subject: [PATCH 34/50] Add cheat picker window --- CMakeLists.txt | 6 +- include/panda_qt/cheats_window.hpp | 26 +++ include/panda_qt/main_window.hpp | 5 + src/panda_qt/cheats_window.cpp | 313 +++++++++++++++++++++++++++++ src/panda_qt/main_window.cpp | 14 +- 5 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 include/panda_qt/cheats_window.hpp create mode 100644 src/panda_qt/cheats_window.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index baa37466..9748b8f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION endif() if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) + set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() project(Alber) @@ -197,10 +197,10 @@ set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) if(NOT ANDROID) if(ENABLE_QT_GUI) set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp - src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp + src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp ) set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp - include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp + include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp ) source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) diff --git a/include/panda_qt/cheats_window.hpp b/include/panda_qt/cheats_window.hpp new file mode 100644 index 00000000..2160a1f6 --- /dev/null +++ b/include/panda_qt/cheats_window.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include "emulator.hpp" + +class QListWidget; + +class CheatsWindow final : public QWidget +{ + Q_OBJECT + +public: + CheatsWindow(Emulator* emu, const std::filesystem::path& path, QWidget* parent = nullptr); + ~CheatsWindow() = default; + +private: + void addEntry(); + void removeClicked(); + + QListWidget* cheatList; + std::filesystem::path cheatPath; + Emulator* emu; +}; diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 7dfb91b7..39a8b35f 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -12,6 +12,7 @@ #include "emulator.hpp" #include "panda_qt/about_window.hpp" #include "panda_qt/config_window.hpp" +#include "panda_qt/cheats_window.hpp" #include "panda_qt/screen.hpp" #include "panda_qt/text_editor.hpp" #include "services/hid.hpp" @@ -54,15 +55,19 @@ class MainWindow : public QMainWindow { ScreenWidget screen; AboutWindow* aboutWindow; ConfigWindow* configWindow; + CheatsWindow* cheatsEditor; TextEditorWindow* luaEditor; QMenuBar* menuBar = nullptr; + QAction* cheatsEditorAction = nullptr; + void swapEmuBuffer(); void emuThreadMainLoop(); void selectLuaFile(); void selectROM(); void dumpRomFS(); void openLuaEditor(); + void openCheatsEditor(); void showAboutMenu(); void sendMessage(const EmulatorMessage& message); void dispatchMessage(const EmulatorMessage& message); diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp new file mode 100644 index 00000000..909ba0cb --- /dev/null +++ b/src/panda_qt/cheats_window.cpp @@ -0,0 +1,313 @@ +#include "panda_qt/cheats_window.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cheats.hpp" +#include "emulator.hpp" + +using CheatHandle = u32; + +CheatHandle BAD_CHEAT = 0xFFFFFFFF; + +struct CheatMetadata +{ + CheatHandle handle = BAD_CHEAT; + std::string name = "New cheat"; + std::string code; + bool enabled = true; +}; + +u32 addCheat(Emulator* emu, u8* data, size_t size) +{ + if ((size % 8) != 0) { + return BAD_CHEAT; + } + + Cheats::Cheat cheat; + cheat.enabled = true; + cheat.type = Cheats::CheatType::ActionReplay; + + for (size_t i = 0; i < size; i += 8) { + auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); }; + + // Data is passed to us in big endian so we bswap + u32 firstWord = Common::swap32(read32(data + i)); + u32 secondWord = Common::swap32(read32(data + i + 4)); + cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord}); + } + + return emu->getCheats().addCheat(cheat); +} + +class CheatEntryWidget : public QWidget +{ +public: + CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent); + + void Update() + { + name->setText(metadata.name.c_str()); + enabled->setChecked(metadata.enabled); + update(); + } + + void Remove() + { + emu->getCheats().removeCheat(metadata.handle); + cheatList->takeItem(cheatList->row(listItem)); + deleteLater(); + } + + const CheatMetadata& GetMetadata() + { + return metadata; + } + + void SetMetadata(const CheatMetadata& metadata) + { + this->metadata = metadata; + } + +private: + void checkboxChanged(int state); + void editClicked(); + + Emulator* emu; + CheatMetadata metadata; + u32 handle; + QLabel* name; + QCheckBox* enabled; + QListWidget* cheatList; + QListWidgetItem* listItem; +}; + +class CheatEditDialog : public QDialog +{ +public: + CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry); + + void accepted(); + void rejected(); +private: + Emulator* emu; + CheatEntryWidget& cheatEntry; + QTextEdit* codeEdit; + QLineEdit* nameEdit; +}; + +CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent) + : QWidget(), emu(emu), metadata(metadata), cheatList(parent) +{ + QHBoxLayout* layout = new QHBoxLayout; + + enabled = new QCheckBox; + enabled->setChecked(metadata.enabled); + + name = new QLabel(metadata.name.c_str()); + QPushButton* buttonEdit = new QPushButton(tr("Edit")); + + connect(enabled, &QCheckBox::stateChanged, this, &CheatEntryWidget::checkboxChanged); + connect(buttonEdit, &QPushButton::clicked, this, &CheatEntryWidget::editClicked); + + layout->addWidget(enabled); + layout->addWidget(name); + layout->addWidget(buttonEdit); + setLayout(layout); + + listItem = new QListWidgetItem; + listItem->setSizeHint(sizeHint()); + parent->addItem(listItem); + parent->setItemWidget(listItem, this); +} + +void CheatEntryWidget::checkboxChanged(int state) +{ + bool enabled = state == Qt::Checked; + if (metadata.handle == BAD_CHEAT) + { + printf("Cheat handle is bad, this shouldn't happen\n"); + return; + } + + if (enabled) + { + emu->getCheats().enableCheat(metadata.handle); + metadata.enabled = true; + } + else + { + emu->getCheats().disableCheat(metadata.handle); + metadata.enabled = false; + } +} + +void CheatEntryWidget::editClicked() +{ + CheatEditDialog* dialog = new CheatEditDialog(emu, *this); + dialog->show(); +} + +CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) +{ + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + + QVBoxLayout* layout = new QVBoxLayout; + const CheatMetadata& metadata = cheatEntry.GetMetadata(); + codeEdit = new QTextEdit; + nameEdit = new QLineEdit; + nameEdit->setText(metadata.name.c_str()); + nameEdit->setPlaceholderText(tr("Cheat name")); + layout->addWidget(nameEdit); + + QFont font; + font.setFamily("Courier"); + font.setFixedPitch(true); + font.setPointSize(10); + codeEdit->setFont(font); + + if (metadata.code.size() != 0) + { + // Nicely format it like so: + // 01234567 89ABCDEF + // 01234567 89ABCDEF + std::string formattedCode; + for (size_t i = 0; i < metadata.code.size(); i += 2) + { + if (i != 0) { + if (i % 8 == 0 && i % 16 != 0) + { + formattedCode += " "; + } + else if (i % 16 == 0) + { + formattedCode += "\n"; + } + } + + formattedCode += metadata.code[i]; + formattedCode += metadata.code[i + 1]; + } + codeEdit->setText(formattedCode.c_str()); + } + + layout->addWidget(codeEdit); + setLayout(layout); + + auto buttons = QDialogButtonBox::Ok | QDialogButtonBox::Cancel; + QDialogButtonBox* button_box = new QDialogButtonBox(buttons); + layout->addWidget(button_box); + + connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(this, &QDialog::rejected, this, &CheatEditDialog::rejected); + connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted); +} + +void CheatEditDialog::accepted() +{ + QString code = codeEdit->toPlainText(); + code.replace(QRegularExpression("[^0-9a-fA-F]"), ""); + + CheatMetadata metadata = cheatEntry.GetMetadata(); + bool isEditing = metadata.handle != BAD_CHEAT; + metadata.name = nameEdit->text().toStdString(); + metadata.code = code.toStdString(); + + std::vector bytes; + for (size_t i = 0; i < metadata.code.size(); i += 2) + { + std::string hex = metadata.code.substr(i, 2); + bytes.push_back((uint8_t)std::stoul(hex, nullptr, 16)); + } + + if (isEditing) + { + emu->getCheats().removeCheat(metadata.handle); + u32 handle = addCheat(emu, bytes.data(), bytes.size()); + metadata.handle = handle; + cheatEntry.SetMetadata(metadata); + } + else + { + if (metadata.name.empty()) + { + metadata.name = tr("Cheat code").toStdString(); + } + u32 handle = addCheat(emu, bytes.data(), bytes.size()); + metadata.handle = handle; + cheatEntry.SetMetadata(metadata); + } + + cheatEntry.Update(); +} + +void CheatEditDialog::rejected() +{ + bool isEditing = cheatEntry.GetMetadata().handle != BAD_CHEAT; + + if (!isEditing) + { + // Was adding a cheat but pressed cancel + cheatEntry.Remove(); + } +} + +CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent) +: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) +{ + QVBoxLayout* layout = new QVBoxLayout; + layout->setContentsMargins(6, 6, 6, 6); + setLayout(layout); + + cheatList = new QListWidget; + layout->addWidget(cheatList); + + QWidget* buttonBox = new QWidget; + QHBoxLayout* buttonLayout = new QHBoxLayout; + + QPushButton* buttonAdd = new QPushButton(tr("Add")); + QPushButton* buttonRemove = new QPushButton(tr("Remove")); + + connect(buttonAdd, &QPushButton::clicked, this, &CheatsWindow::addEntry); + connect(buttonRemove, &QPushButton::clicked, this, &CheatsWindow::removeClicked); + + buttonLayout->addWidget(buttonAdd); + buttonLayout->addWidget(buttonRemove); + buttonBox->setLayout(buttonLayout); + + layout->addWidget(buttonBox); + + // TODO: load cheats from saved cheats per game + // for (const CheatMetadata& metadata : getSavedCheats()) + // { + // new CheatEntryWidget(emu, metadata, cheatList); + // } +} + +void CheatsWindow::addEntry() +{ + // CheatEntryWidget is added to the list when it's created + CheatEntryWidget* entry = new CheatEntryWidget(emu, {BAD_CHEAT, "New cheat", "", true}, cheatList); + CheatEditDialog* dialog = new CheatEditDialog(emu, *entry); + dialog->show(); +} + +void CheatsWindow::removeClicked() +{ + QListWidgetItem* item = cheatList->currentItem(); + if (item == nullptr) + { + return; + } + + CheatEntryWidget* entry = static_cast(cheatList->itemWidget(item)); + entry->Remove(); +} diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 5c661119..be5e4fd6 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -48,20 +48,24 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor")); + cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor")); + cheatsEditorAction->setEnabled(false); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor); + connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor); auto aboutAction = aboutMenu->addAction(tr("About Panda3DS")); connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu); + emu = new Emulator(); + emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight); + // Set up misc objects aboutWindow = new AboutWindow(nullptr); configWindow = new ConfigWindow(this); + cheatsEditor = new CheatsWindow(emu, {}); luaEditor = new TextEditorWindow(this, "script.lua", ""); - emu = new Emulator(); - emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight); - auto args = QCoreApplication::arguments(); if (args.size() > 1) { auto romPath = std::filesystem::current_path() / args.at(1).toStdU16String(); @@ -184,6 +188,7 @@ MainWindow::~MainWindow() { delete menuBar; delete aboutWindow; delete configWindow; + delete cheatsEditor; delete luaEditor; } @@ -234,10 +239,13 @@ void MainWindow::showAboutMenu() { void MainWindow::openLuaEditor() { luaEditor->show(); } +void MainWindow::openCheatsEditor() { cheatsEditor->show(); } + void MainWindow::dispatchMessage(const EmulatorMessage& message) { switch (message.type) { case MessageType::LoadROM: emu->loadROM(*message.path.p); + cheatsEditorAction->setEnabled(true); // Clean up the allocated path delete message.path.p; break; From a473a34794e189c251079f50b95941ac1110fd91 Mon Sep 17 00:00:00 2001 From: offtkp Date: Sat, 27 Jan 2024 18:20:42 +0200 Subject: [PATCH 35/50] Use message queues --- CMakeLists.txt | 2 +- include/cheats.hpp | 3 + include/panda_qt/cheats_window.hpp | 28 +- include/panda_qt/main_window.hpp | 16 +- src/core/cheats.cpp | 22 ++ src/panda_qt/cheats_window.cpp | 429 ++++++++++++----------------- src/panda_qt/main_window.cpp | 35 ++- 7 files changed, 266 insertions(+), 269 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9748b8f8..ac6cdfb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION endif() if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE RelWithDebInfo) + set(CMAKE_BUILD_TYPE Release) endif() project(Alber) diff --git a/include/cheats.hpp b/include/cheats.hpp index 2be25827..ca8abe1d 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -9,6 +9,8 @@ // Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time class Memory; +constexpr u32 badCheatHandle = 0xFFFFFFFF; + class Cheats { public: enum class CheatType { @@ -24,6 +26,7 @@ class Cheats { Cheats(Memory& mem, HIDService& hid); u32 addCheat(const Cheat& cheat); + u32 addCheat(const u8* data, size_t size); void removeCheat(u32 id); void enableCheat(u32 id); void disableCheat(u32 id); diff --git a/include/panda_qt/cheats_window.hpp b/include/panda_qt/cheats_window.hpp index 2160a1f6..c82b2bd8 100644 --- a/include/panda_qt/cheats_window.hpp +++ b/include/panda_qt/cheats_window.hpp @@ -1,26 +1,26 @@ #pragma once -#include -#include #include #include +#include +#include + #include "emulator.hpp" class QListWidget; -class CheatsWindow final : public QWidget -{ - Q_OBJECT +class CheatsWindow final : public QWidget { + Q_OBJECT -public: - CheatsWindow(Emulator* emu, const std::filesystem::path& path, QWidget* parent = nullptr); - ~CheatsWindow() = default; + public: + CheatsWindow(Emulator* emu, const std::filesystem::path& path, QWidget* parent = nullptr); + ~CheatsWindow() = default; -private: - void addEntry(); - void removeClicked(); + private: + void addEntry(); + void removeClicked(); - QListWidget* cheatList; - std::filesystem::path cheatPath; - Emulator* emu; + QListWidget* cheatList; + std::filesystem::path cheatPath; + Emulator* emu; }; diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 39a8b35f..d5eccc93 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -17,12 +17,19 @@ #include "panda_qt/text_editor.hpp" #include "services/hid.hpp" +struct CheatMessage +{ + u32 handle; + std::vector cheat; + std::function callback; +}; + class MainWindow : public QMainWindow { Q_OBJECT private: // Types of messages we might send from the GUI thread to the emulator thread - enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript }; + enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript, EditCheat }; // Tagged union representing our message queue messages struct EmulatorMessage { @@ -40,6 +47,10 @@ class MainWindow : public QMainWindow { struct { std::string* str; } string; + + struct { + CheatMessage* c; + } cheat; }; }; @@ -59,8 +70,6 @@ class MainWindow : public QMainWindow { TextEditorWindow* luaEditor; QMenuBar* menuBar = nullptr; - QAction* cheatsEditorAction = nullptr; - void swapEmuBuffer(); void emuThreadMainLoop(); void selectLuaFile(); @@ -83,4 +92,5 @@ class MainWindow : public QMainWindow { void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; void loadLuaScript(const std::string& code); + void editCheat(u32 handle, const std::vector& cheat, const std::function& callback); }; \ No newline at end of file diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 83e7cdc4..7b8b71c2 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -1,4 +1,5 @@ #include "cheats.hpp" +#include "swap.hpp" Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); } @@ -23,6 +24,27 @@ u32 Cheats::addCheat(const Cheat& cheat) { return cheats.size() - 1; } +u32 Cheats::addCheat(const u8* data, size_t size) { + if ((size % 8) != 0) { + return badCheatHandle; + } + + Cheats::Cheat cheat; + cheat.enabled = true; + cheat.type = Cheats::CheatType::ActionReplay; + + for (size_t i = 0; i < size; i += 8) { + auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); }; + + // Data is passed to us in big endian so we bswap + u32 firstWord = Common::swap32(read32(data + i)); + u32 secondWord = Common::swap32(read32(data + i + 4)); + cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord}); + } + + return addCheat(cheat); +} + void Cheats::removeCheat(u32 id) { if (id >= cheats.size()) { return; diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index 909ba0cb..c6628125 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -1,313 +1,248 @@ #include "panda_qt/cheats_window.hpp" -#include -#include -#include -#include -#include + #include #include -#include -#include #include +#include +#include +#include +#include +#include +#include + #include "cheats.hpp" #include "emulator.hpp" +#include "panda_qt/main_window.hpp" -using CheatHandle = u32; +MainWindow* mainWindow = nullptr; -CheatHandle BAD_CHEAT = 0xFFFFFFFF; - -struct CheatMetadata -{ - CheatHandle handle = BAD_CHEAT; - std::string name = "New cheat"; - std::string code; - bool enabled = true; +struct CheatMetadata { + u32 handle = badCheatHandle; + std::string name = "New cheat"; + std::string code; + bool enabled = true; }; -u32 addCheat(Emulator* emu, u8* data, size_t size) -{ - if ((size % 8) != 0) { - return BAD_CHEAT; +class CheatEntryWidget : public QWidget { + public: + CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent); + + void Update() { + name->setText(metadata.name.c_str()); + enabled->setChecked(metadata.enabled); + update(); } - Cheats::Cheat cheat; - cheat.enabled = true; - cheat.type = Cheats::CheatType::ActionReplay; - - for (size_t i = 0; i < size; i += 8) { - auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); }; - - // Data is passed to us in big endian so we bswap - u32 firstWord = Common::swap32(read32(data + i)); - u32 secondWord = Common::swap32(read32(data + i + 4)); - cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord}); + void Remove() { + emu->getCheats().removeCheat(metadata.handle); + cheatList->takeItem(cheatList->row(listItem)); + deleteLater(); } - return emu->getCheats().addCheat(cheat); -} + const CheatMetadata& GetMetadata() { return metadata; } -class CheatEntryWidget : public QWidget -{ -public: - CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent); + void SetMetadata(const CheatMetadata& metadata) { this->metadata = metadata; } - void Update() - { - name->setText(metadata.name.c_str()); - enabled->setChecked(metadata.enabled); - update(); - } + private: + void checkboxChanged(int state); + void editClicked(); - void Remove() - { - emu->getCheats().removeCheat(metadata.handle); - cheatList->takeItem(cheatList->row(listItem)); - deleteLater(); - } - - const CheatMetadata& GetMetadata() - { - return metadata; - } - - void SetMetadata(const CheatMetadata& metadata) - { - this->metadata = metadata; - } - -private: - void checkboxChanged(int state); - void editClicked(); - - Emulator* emu; - CheatMetadata metadata; - u32 handle; - QLabel* name; - QCheckBox* enabled; - QListWidget* cheatList; - QListWidgetItem* listItem; + Emulator* emu; + CheatMetadata metadata; + u32 handle; + QLabel* name; + QCheckBox* enabled; + QListWidget* cheatList; + QListWidgetItem* listItem; }; -class CheatEditDialog : public QDialog -{ -public: - CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry); +class CheatEditDialog : public QDialog { + public: + CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry); - void accepted(); - void rejected(); -private: - Emulator* emu; - CheatEntryWidget& cheatEntry; - QTextEdit* codeEdit; - QLineEdit* nameEdit; + void accepted(); + void rejected(); + + private: + Emulator* emu; + CheatEntryWidget& cheatEntry; + QTextEdit* codeEdit; + QLineEdit* nameEdit; }; CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent) - : QWidget(), emu(emu), metadata(metadata), cheatList(parent) -{ - QHBoxLayout* layout = new QHBoxLayout; + : QWidget(), emu(emu), metadata(metadata), cheatList(parent) { + QHBoxLayout* layout = new QHBoxLayout; - enabled = new QCheckBox; - enabled->setChecked(metadata.enabled); + enabled = new QCheckBox; + enabled->setChecked(metadata.enabled); - name = new QLabel(metadata.name.c_str()); - QPushButton* buttonEdit = new QPushButton(tr("Edit")); + name = new QLabel(metadata.name.c_str()); + QPushButton* buttonEdit = new QPushButton(tr("Edit")); - connect(enabled, &QCheckBox::stateChanged, this, &CheatEntryWidget::checkboxChanged); - connect(buttonEdit, &QPushButton::clicked, this, &CheatEntryWidget::editClicked); + connect(enabled, &QCheckBox::stateChanged, this, &CheatEntryWidget::checkboxChanged); + connect(buttonEdit, &QPushButton::clicked, this, &CheatEntryWidget::editClicked); - layout->addWidget(enabled); - layout->addWidget(name); - layout->addWidget(buttonEdit); - setLayout(layout); + layout->addWidget(enabled); + layout->addWidget(name); + layout->addWidget(buttonEdit); + setLayout(layout); - listItem = new QListWidgetItem; - listItem->setSizeHint(sizeHint()); - parent->addItem(listItem); - parent->setItemWidget(listItem, this); + listItem = new QListWidgetItem; + listItem->setSizeHint(sizeHint()); + parent->addItem(listItem); + parent->setItemWidget(listItem, this); } -void CheatEntryWidget::checkboxChanged(int state) -{ - bool enabled = state == Qt::Checked; - if (metadata.handle == BAD_CHEAT) - { - printf("Cheat handle is bad, this shouldn't happen\n"); - return; - } +void CheatEntryWidget::checkboxChanged(int state) { + bool enabled = state == Qt::Checked; + if (metadata.handle == badCheatHandle) { + printf("Cheat handle is bad, this shouldn't happen\n"); + return; + } - if (enabled) - { - emu->getCheats().enableCheat(metadata.handle); - metadata.enabled = true; - } - else - { - emu->getCheats().disableCheat(metadata.handle); - metadata.enabled = false; - } + if (enabled) { + emu->getCheats().enableCheat(metadata.handle); + metadata.enabled = true; + } else { + emu->getCheats().disableCheat(metadata.handle); + metadata.enabled = false; + } } -void CheatEntryWidget::editClicked() -{ - CheatEditDialog* dialog = new CheatEditDialog(emu, *this); - dialog->show(); +void CheatEntryWidget::editClicked() { + CheatEditDialog* dialog = new CheatEditDialog(emu, *this); + dialog->show(); } -CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) -{ - setAttribute(Qt::WA_DeleteOnClose); - setModal(true); +CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) { + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); - QVBoxLayout* layout = new QVBoxLayout; - const CheatMetadata& metadata = cheatEntry.GetMetadata(); - codeEdit = new QTextEdit; - nameEdit = new QLineEdit; - nameEdit->setText(metadata.name.c_str()); - nameEdit->setPlaceholderText(tr("Cheat name")); - layout->addWidget(nameEdit); + QVBoxLayout* layout = new QVBoxLayout; + const CheatMetadata& metadata = cheatEntry.GetMetadata(); + codeEdit = new QTextEdit; + nameEdit = new QLineEdit; + nameEdit->setText(metadata.name.c_str()); + nameEdit->setPlaceholderText(tr("Cheat name")); + layout->addWidget(nameEdit); - QFont font; - font.setFamily("Courier"); - font.setFixedPitch(true); - font.setPointSize(10); - codeEdit->setFont(font); + QFont font; + font.setFamily("Courier"); + font.setFixedPitch(true); + font.setPointSize(10); + codeEdit->setFont(font); - if (metadata.code.size() != 0) - { - // Nicely format it like so: - // 01234567 89ABCDEF - // 01234567 89ABCDEF - std::string formattedCode; - for (size_t i = 0; i < metadata.code.size(); i += 2) - { - if (i != 0) { - if (i % 8 == 0 && i % 16 != 0) - { - formattedCode += " "; - } - else if (i % 16 == 0) - { - formattedCode += "\n"; - } - } + if (metadata.code.size() != 0) { + // Nicely format it like so: + // 01234567 89ABCDEF + // 01234567 89ABCDEF + std::string formattedCode; + for (size_t i = 0; i < metadata.code.size(); i += 2) { + if (i != 0) { + if (i % 8 == 0 && i % 16 != 0) { + formattedCode += " "; + } else if (i % 16 == 0) { + formattedCode += "\n"; + } + } - formattedCode += metadata.code[i]; - formattedCode += metadata.code[i + 1]; - } - codeEdit->setText(formattedCode.c_str()); - } + formattedCode += metadata.code[i]; + formattedCode += metadata.code[i + 1]; + } + codeEdit->setText(formattedCode.c_str()); + } - layout->addWidget(codeEdit); - setLayout(layout); + layout->addWidget(codeEdit); + setLayout(layout); - auto buttons = QDialogButtonBox::Ok | QDialogButtonBox::Cancel; - QDialogButtonBox* button_box = new QDialogButtonBox(buttons); - layout->addWidget(button_box); + auto buttons = QDialogButtonBox::Ok | QDialogButtonBox::Cancel; + QDialogButtonBox* button_box = new QDialogButtonBox(buttons); + layout->addWidget(button_box); - connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); - connect(this, &QDialog::rejected, this, &CheatEditDialog::rejected); - connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted); + connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(this, &QDialog::rejected, this, &CheatEditDialog::rejected); + connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted); } -void CheatEditDialog::accepted() -{ - QString code = codeEdit->toPlainText(); - code.replace(QRegularExpression("[^0-9a-fA-F]"), ""); +void CheatEditDialog::accepted() { + QString code = codeEdit->toPlainText(); + code.replace(QRegularExpression("[^0-9a-fA-F]"), ""); - CheatMetadata metadata = cheatEntry.GetMetadata(); - bool isEditing = metadata.handle != BAD_CHEAT; - metadata.name = nameEdit->text().toStdString(); - metadata.code = code.toStdString(); + CheatMetadata metadata = cheatEntry.GetMetadata(); + metadata.name = nameEdit->text().toStdString(); + metadata.code = code.toStdString(); - std::vector bytes; - for (size_t i = 0; i < metadata.code.size(); i += 2) - { - std::string hex = metadata.code.substr(i, 2); - bytes.push_back((uint8_t)std::stoul(hex, nullptr, 16)); - } + std::vector bytes; + for (size_t i = 0; i < metadata.code.size(); i += 2) { + std::string hex = metadata.code.substr(i, 2); + bytes.push_back((uint8_t)std::stoul(hex, nullptr, 16)); + } - if (isEditing) - { - emu->getCheats().removeCheat(metadata.handle); - u32 handle = addCheat(emu, bytes.data(), bytes.size()); - metadata.handle = handle; - cheatEntry.SetMetadata(metadata); - } - else - { - if (metadata.name.empty()) - { - metadata.name = tr("Cheat code").toStdString(); - } - u32 handle = addCheat(emu, bytes.data(), bytes.size()); - metadata.handle = handle; - cheatEntry.SetMetadata(metadata); - } - - cheatEntry.Update(); + mainWindow->editCheat(cheatEntry.GetMetadata().handle, bytes, [this](u32 handle) { + CheatMetadata metadata = cheatEntry.GetMetadata(); + metadata.handle = handle; + cheatEntry.SetMetadata(metadata); + cheatEntry.Update(); + }); } -void CheatEditDialog::rejected() -{ - bool isEditing = cheatEntry.GetMetadata().handle != BAD_CHEAT; +void CheatEditDialog::rejected() { + bool isEditing = cheatEntry.GetMetadata().handle != badCheatHandle; - if (!isEditing) - { - // Was adding a cheat but pressed cancel - cheatEntry.Remove(); - } + if (!isEditing) { + // Was adding a cheat but pressed cancel + cheatEntry.Remove(); + } } CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent) -: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) -{ - QVBoxLayout* layout = new QVBoxLayout; - layout->setContentsMargins(6, 6, 6, 6); - setLayout(layout); + : QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) { + mainWindow = static_cast(parent); - cheatList = new QListWidget; - layout->addWidget(cheatList); + QVBoxLayout* layout = new QVBoxLayout; + layout->setContentsMargins(6, 6, 6, 6); + setLayout(layout); - QWidget* buttonBox = new QWidget; - QHBoxLayout* buttonLayout = new QHBoxLayout; + cheatList = new QListWidget; + layout->addWidget(cheatList); - QPushButton* buttonAdd = new QPushButton(tr("Add")); - QPushButton* buttonRemove = new QPushButton(tr("Remove")); + QWidget* buttonBox = new QWidget; + QHBoxLayout* buttonLayout = new QHBoxLayout; - connect(buttonAdd, &QPushButton::clicked, this, &CheatsWindow::addEntry); - connect(buttonRemove, &QPushButton::clicked, this, &CheatsWindow::removeClicked); + QPushButton* buttonAdd = new QPushButton(tr("Add")); + QPushButton* buttonRemove = new QPushButton(tr("Remove")); - buttonLayout->addWidget(buttonAdd); - buttonLayout->addWidget(buttonRemove); - buttonBox->setLayout(buttonLayout); + connect(buttonAdd, &QPushButton::clicked, this, &CheatsWindow::addEntry); + connect(buttonRemove, &QPushButton::clicked, this, &CheatsWindow::removeClicked); - layout->addWidget(buttonBox); + buttonLayout->addWidget(buttonAdd); + buttonLayout->addWidget(buttonRemove); + buttonBox->setLayout(buttonLayout); - // TODO: load cheats from saved cheats per game - // for (const CheatMetadata& metadata : getSavedCheats()) - // { - // new CheatEntryWidget(emu, metadata, cheatList); - // } + layout->addWidget(buttonBox); + + // TODO: load cheats from saved cheats per game + // for (const CheatMetadata& metadata : getSavedCheats()) + // { + // new CheatEntryWidget(emu, metadata, cheatList); + // } } -void CheatsWindow::addEntry() -{ - // CheatEntryWidget is added to the list when it's created - CheatEntryWidget* entry = new CheatEntryWidget(emu, {BAD_CHEAT, "New cheat", "", true}, cheatList); - CheatEditDialog* dialog = new CheatEditDialog(emu, *entry); - dialog->show(); +void CheatsWindow::addEntry() { + // CheatEntryWidget is added to the list when it's created + CheatEntryWidget* entry = new CheatEntryWidget(emu, {badCheatHandle, "New cheat", "", true}, cheatList); + CheatEditDialog* dialog = new CheatEditDialog(emu, *entry); + dialog->show(); } -void CheatsWindow::removeClicked() -{ - QListWidgetItem* item = cheatList->currentItem(); - if (item == nullptr) - { - return; - } +void CheatsWindow::removeClicked() { + QListWidgetItem* item = cheatList->currentItem(); + if (item == nullptr) { + return; + } - CheatEntryWidget* entry = static_cast(cheatList->itemWidget(item)); - entry->Remove(); + CheatEntryWidget* entry = static_cast(cheatList->itemWidget(item)); + entry->Remove(); } diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index be5e4fd6..d1f86173 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -6,6 +6,8 @@ #include #include +#include "cheats.hpp" + MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) { setWindowTitle("Alber"); // Enable drop events for loading ROMs @@ -48,8 +50,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor")); - cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor")); - cheatsEditorAction->setEnabled(false); + auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor); connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor); @@ -63,7 +64,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) // Set up misc objects aboutWindow = new AboutWindow(nullptr); configWindow = new ConfigWindow(this); - cheatsEditor = new CheatsWindow(emu, {}); + cheatsEditor = new CheatsWindow(emu, {}, this); luaEditor = new TextEditorWindow(this, "script.lua", ""); auto args = QCoreApplication::arguments(); @@ -245,7 +246,6 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { switch (message.type) { case MessageType::LoadROM: emu->loadROM(*message.path.p); - cheatsEditorAction->setEnabled(true); // Clean up the allocated path delete message.path.p; break; @@ -255,6 +255,21 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { delete message.string.str; break; + case MessageType::EditCheat: { + u32 handle = message.cheat.c->handle; + const std::vector& cheat = message.cheat.c->cheat; + const std::function& callback = message.cheat.c->callback; + bool isEditing = handle != badCheatHandle; + if (isEditing) { + emu->getCheats().removeCheat(handle); + u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size()); + } else { + u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size()); + callback(handle); + } + delete message.cheat.c; + } break; + case MessageType::Pause: emu->pause(); break; case MessageType::Resume: emu->resume(); break; case MessageType::TogglePause: emu->togglePause(); break; @@ -326,4 +341,16 @@ void MainWindow::loadLuaScript(const std::string& code) { // Make a copy of the code on the heap to send via the message queue message.string.str = new std::string(code); sendMessage(message); +} + +void MainWindow::editCheat(u32 handle, const std::vector& cheat, const std::function& callback) { + EmulatorMessage message{.type = MessageType::EditCheat}; + + CheatMessage* c = new CheatMessage(); + c->handle = handle; + c->cheat = cheat; + c->callback = callback; + + message.cheat.c = c; + sendMessage(message); } \ No newline at end of file From a2276c922f524629c5f3fe76be79b4eb454bc7c9 Mon Sep 17 00:00:00 2001 From: offtkp Date: Sat, 27 Jan 2024 18:21:44 +0200 Subject: [PATCH 36/50] Reduce code duplication --- src/hydra_core.cpp | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index d67ffe2f..acbf30a8 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -134,25 +134,7 @@ void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = c void HydraCore::setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) { checkButtonCallback = callback; } u32 HydraCore::addCheat(const u8* data, u32 size) { - // Every 3DS cheat is a multiple of 64 bits == 8 bytes - if ((size % 8) != 0) { - return hydra::BAD_CHEAT; - } - - Cheats::Cheat cheat; - cheat.enabled = true; - cheat.type = Cheats::CheatType::ActionReplay; - - for (u32 i = 0; i < size; i += 8) { - auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); }; - - // Data is passed to us in big endian so we bswap - u32 firstWord = Common::swap32(read32(data + i)); - u32 secondWord = Common::swap32(read32(data + i + 4)); - cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord}); - } - - return emulator->getCheats().addCheat(cheat); + return emulator->getCheats().addCheat(data, size); }; void HydraCore::removeCheat(u32 id) { emulator->getCheats().removeCheat(id); } From 3d52692536e541909f05e178305222b2f69d29c6 Mon Sep 17 00:00:00 2001 From: offtkp Date: Sat, 27 Jan 2024 19:01:05 +0200 Subject: [PATCH 37/50] Threading shenanigans --- include/panda_qt/main_window.hpp | 1 + src/panda_qt/cheats_window.cpp | 31 ++++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index d5eccc93..f7757d73 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index c6628125..74be1e94 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "cheats.hpp" #include "emulator.hpp" @@ -23,6 +25,19 @@ struct CheatMetadata { bool enabled = true; }; +void dispatchToMainThread(std::function callback) +{ + QTimer* timer = new QTimer(); + timer->moveToThread(qApp->thread()); + timer->setSingleShot(true); + QObject::connect(timer, &QTimer::timeout, [=]() + { + callback(); + timer->deleteLater(); + }); + QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); +} + class CheatEntryWidget : public QWidget { public: CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent); @@ -181,16 +196,22 @@ void CheatEditDialog::accepted() { } mainWindow->editCheat(cheatEntry.GetMetadata().handle, bytes, [this](u32 handle) { - CheatMetadata metadata = cheatEntry.GetMetadata(); - metadata.handle = handle; - cheatEntry.SetMetadata(metadata); - cheatEntry.Update(); + dispatchToMainThread([this, handle]() { + if (handle == badCheatHandle) { + cheatEntry.Remove(); + return; + } else { + CheatMetadata metadata = cheatEntry.GetMetadata(); + metadata.handle = handle; + cheatEntry.SetMetadata(metadata); + cheatEntry.Update(); + } + }); }); } void CheatEditDialog::rejected() { bool isEditing = cheatEntry.GetMetadata().handle != badCheatHandle; - if (!isEditing) { // Was adding a cheat but pressed cancel cheatEntry.Remove(); From 2229adbd21471aa1b0c693314c5a176214ff39c1 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 19:52:54 +0200 Subject: [PATCH 38/50] More action replay opcodes --- src/core/action_replay.cpp | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/core/action_replay.cpp b/src/core/action_replay.cpp index e8467425..1ef494a2 100644 --- a/src/core/action_replay.cpp +++ b/src/core/action_replay.cpp @@ -139,6 +139,65 @@ void ActionReplay::executeDType(const Cheat& cheat, u32 instruction) { switch (instruction) { case 0xD3000000: offset1 = cheat[pc++]; break; case 0xD3000001: offset2 = cheat[pc++]; break; + + case 0xD6000000: + write32(*activeOffset + cheat[pc++], u32(*activeData)); + *activeOffset += 4; + break; + + case 0xD6000001: + write32(*activeOffset + cheat[pc++], u32(data1)); + *activeOffset += 4; + break; + + case 0xD6000002: + write32(*activeOffset + cheat[pc++], u32(data2)); + *activeOffset += 4; + break; + + case 0xD7000000: + write16(*activeOffset + cheat[pc++], u16(*activeData)); + *activeOffset += 2; + break; + + case 0xD7000001: + write16(*activeOffset + cheat[pc++], u16(data1)); + *activeOffset += 2; + break; + + case 0xD7000002: + write16(*activeOffset + cheat[pc++], u16(data2)); + *activeOffset += 2; + break; + + case 0xD8000000: + write8(*activeOffset + cheat[pc++], u8(*activeData)); + *activeOffset += 1; + break; + + case 0xD8000001: + write8(*activeOffset + cheat[pc++], u8(data1)); + *activeOffset += 1; + break; + + case 0xD8000002: + write8(*activeOffset + cheat[pc++], u8(data2)); + *activeOffset += 1; + break; + + + case 0xD9000000: *activeData = read32(cheat[pc++] + *activeOffset); break; + case 0xD9000001: data1 = read32(cheat[pc++] + *activeOffset); break; + case 0xD9000002: data2 = read32(cheat[pc++] + *activeOffset); break; + + case 0xDA000000: *activeData = read16(cheat[pc++] + *activeOffset); break; + case 0xDA000001: data1 = read16(cheat[pc++] + *activeOffset); break; + case 0xDA000002: data2 = read16(cheat[pc++] + *activeOffset); break; + + case 0xDB000000: *activeData = read8(cheat[pc++] + *activeOffset); break; + case 0xDB000001: data1 = read8(cheat[pc++] + *activeOffset); break; + case 0xDB000002: data2 = read8(cheat[pc++] + *activeOffset); break; + case 0xDC000000: *activeOffset += cheat[pc++]; break; // DD000000 XXXXXXXX - if KEYPAD has value XXXXXXXX execute next block From e3c9f0b219618a72741ef168fd0b8b4bbcb5f629 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 20:11:53 +0200 Subject: [PATCH 39/50] [Qt] Implement circlepad --- include/panda_qt/main_window.hpp | 19 ++++++++++++++++++- src/panda_qt/main_window.cpp | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index f7757d73..9be065cf 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -30,7 +30,20 @@ class MainWindow : public QMainWindow { private: // Types of messages we might send from the GUI thread to the emulator thread - enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript, EditCheat }; + enum class MessageType { + LoadROM, + Reset, + Pause, + Resume, + TogglePause, + DumpRomFS, + PressKey, + ReleaseKey, + SetCirclePadX, + SetCirclePadY, + LoadLuaScript, + EditCheat, + }; // Tagged union representing our message queue messages struct EmulatorMessage { @@ -45,6 +58,10 @@ class MainWindow : public QMainWindow { u32 key; } key; + struct { + s16 value; + } circlepad; + struct { std::string* str; } string; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index d1f86173..e603b780 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -276,6 +276,8 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break; case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break; case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; + case MessageType::SetCirclePadX: emu->getServiceManager().getHID().setCirclepadX(message.circlepad.value); break; + case MessageType::SetCirclePadY: emu->getServiceManager().getHID().setCirclepadY(message.circlepad.value); break; } } @@ -283,7 +285,12 @@ void MainWindow::keyPressEvent(QKeyEvent* event) { auto pressKey = [this](u32 key) { EmulatorMessage message{.type = MessageType::PressKey}; message.key.key = key; + sendMessage(message); + }; + auto setCirclePad = [this](MessageType type, s16 value) { + EmulatorMessage message{.type = type}; + message.circlepad.value = value; sendMessage(message); }; @@ -296,6 +303,11 @@ void MainWindow::keyPressEvent(QKeyEvent* event) { case Qt::Key_Q: pressKey(HID::Keys::L); break; case Qt::Key_P: pressKey(HID::Keys::R); break; + case Qt::Key_W: setCirclePad(MessageType::SetCirclePadY, 0x9C); break; + case Qt::Key_A: setCirclePad(MessageType::SetCirclePadX, -0x9C); break; + case Qt::Key_S: setCirclePad(MessageType::SetCirclePadY, -0x9C); break; + case Qt::Key_D: setCirclePad(MessageType::SetCirclePadX, 0x9C); break; + case Qt::Key_Right: pressKey(HID::Keys::Right); break; case Qt::Key_Left: pressKey(HID::Keys::Left); break; case Qt::Key_Up: pressKey(HID::Keys::Up); break; @@ -312,7 +324,12 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) { auto releaseKey = [this](u32 key) { EmulatorMessage message{.type = MessageType::ReleaseKey}; message.key.key = key; + sendMessage(message); + }; + auto releaseCirclePad = [this](MessageType type) { + EmulatorMessage message{.type = type}; + message.circlepad.value = 0; sendMessage(message); }; @@ -325,6 +342,12 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) { case Qt::Key_Q: releaseKey(HID::Keys::L); break; case Qt::Key_P: releaseKey(HID::Keys::R); break; + case Qt::Key_W: + case Qt::Key_S: releaseCirclePad(MessageType::SetCirclePadY); break; + + case Qt::Key_A: + case Qt::Key_D: releaseCirclePad(MessageType::SetCirclePadX); break; + case Qt::Key_Right: releaseKey(HID::Keys::Right); break; case Qt::Key_Left: releaseKey(HID::Keys::Left); break; case Qt::Key_Up: releaseKey(HID::Keys::Up); break; From 864604c1e7100e7622bc6ef7d208f2d04fbd6c14 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 20:27:46 +0200 Subject: [PATCH 40/50] Formatting --- src/panda_qt/cheats_window.cpp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index 74be1e94..1f359396 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -54,9 +54,8 @@ class CheatEntryWidget : public QWidget { deleteLater(); } - const CheatMetadata& GetMetadata() { return metadata; } - - void SetMetadata(const CheatMetadata& metadata) { this->metadata = metadata; } + const CheatMetadata& getMetadata() { return metadata; } + void setMetadata(const CheatMetadata& metadata) { this->metadata = metadata; } private: void checkboxChanged(int state); @@ -135,7 +134,7 @@ CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : setModal(true); QVBoxLayout* layout = new QVBoxLayout; - const CheatMetadata& metadata = cheatEntry.GetMetadata(); + const CheatMetadata& metadata = cheatEntry.getMetadata(); codeEdit = new QTextEdit; nameEdit = new QLineEdit; nameEdit->setText(metadata.name.c_str()); @@ -172,11 +171,11 @@ CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : setLayout(layout); auto buttons = QDialogButtonBox::Ok | QDialogButtonBox::Cancel; - QDialogButtonBox* button_box = new QDialogButtonBox(buttons); - layout->addWidget(button_box); + QDialogButtonBox* buttonBox = new QDialogButtonBox(buttons); + layout->addWidget(buttonBox); - connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(this, &QDialog::rejected, this, &CheatEditDialog::rejected); connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted); } @@ -185,25 +184,25 @@ void CheatEditDialog::accepted() { QString code = codeEdit->toPlainText(); code.replace(QRegularExpression("[^0-9a-fA-F]"), ""); - CheatMetadata metadata = cheatEntry.GetMetadata(); + CheatMetadata metadata = cheatEntry.getMetadata(); metadata.name = nameEdit->text().toStdString(); metadata.code = code.toStdString(); - std::vector bytes; + std::vector bytes; for (size_t i = 0; i < metadata.code.size(); i += 2) { std::string hex = metadata.code.substr(i, 2); - bytes.push_back((uint8_t)std::stoul(hex, nullptr, 16)); + bytes.push_back((u8)std::stoul(hex, nullptr, 16)); } - mainWindow->editCheat(cheatEntry.GetMetadata().handle, bytes, [this](u32 handle) { + mainWindow->editCheat(cheatEntry.getMetadata().handle, bytes, [this](u32 handle) { dispatchToMainThread([this, handle]() { if (handle == badCheatHandle) { cheatEntry.Remove(); return; } else { - CheatMetadata metadata = cheatEntry.GetMetadata(); + CheatMetadata metadata = cheatEntry.getMetadata(); metadata.handle = handle; - cheatEntry.SetMetadata(metadata); + cheatEntry.setMetadata(metadata); cheatEntry.Update(); } }); @@ -211,9 +210,9 @@ void CheatEditDialog::accepted() { } void CheatEditDialog::rejected() { - bool isEditing = cheatEntry.GetMetadata().handle != badCheatHandle; + bool isEditing = cheatEntry.getMetadata().handle != badCheatHandle; if (!isEditing) { - // Was adding a cheat but pressed cancel + // Was adding a cheat but user pressed cancel cheatEntry.Remove(); } } From 31eea40ea54d985f82573c7ac15550b52423131b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 20:33:35 +0200 Subject: [PATCH 41/50] Fix cheat dialog forgetting cheat names/codes --- src/panda_qt/cheats_window.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index 1f359396..6636cae7 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -187,6 +187,7 @@ void CheatEditDialog::accepted() { CheatMetadata metadata = cheatEntry.getMetadata(); metadata.name = nameEdit->text().toStdString(); metadata.code = code.toStdString(); + cheatEntry.setMetadata(metadata); std::vector bytes; for (size_t i = 0; i < metadata.code.size(); i += 2) { From 01b6380242e4deeee832ba2115b4c83d20fe8cc3 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 27 Jan 2024 20:38:17 +0200 Subject: [PATCH 42/50] Remove global definition of badCheatHandle --- include/cheats.hpp | 3 +-- include/panda_qt/main_window.hpp | 3 +-- src/panda_qt/cheats_window.cpp | 33 ++++++++++++++++---------------- src/panda_qt/main_window.cpp | 3 +-- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/include/cheats.hpp b/include/cheats.hpp index ca8abe1d..b90c080b 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -9,8 +9,6 @@ // Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time class Memory; -constexpr u32 badCheatHandle = 0xFFFFFFFF; - class Cheats { public: enum class CheatType { @@ -35,6 +33,7 @@ class Cheats { void clear(); bool haveCheats() const { return cheatsLoaded; } + static constexpr u32 badCheatHandle = 0xFFFFFFFF; private: ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 9be065cf..c03cffbb 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -18,8 +18,7 @@ #include "panda_qt/text_editor.hpp" #include "services/hid.hpp" -struct CheatMessage -{ +struct CheatMessage { u32 handle; std::vector cheat; std::function callback; diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index 6636cae7..dbd251cc 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -19,14 +19,13 @@ MainWindow* mainWindow = nullptr; struct CheatMetadata { - u32 handle = badCheatHandle; + u32 handle = Cheats::badCheatHandle; std::string name = "New cheat"; std::string code; bool enabled = true; }; -void dispatchToMainThread(std::function callback) -{ +void dispatchToMainThread(std::function callback) { QTimer* timer = new QTimer(); timer->moveToThread(qApp->thread()); timer->setSingleShot(true); @@ -110,7 +109,7 @@ CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListW void CheatEntryWidget::checkboxChanged(int state) { bool enabled = state == Qt::Checked; - if (metadata.handle == badCheatHandle) { + if (metadata.handle == Cheats::badCheatHandle) { printf("Cheat handle is bad, this shouldn't happen\n"); return; } @@ -196,22 +195,22 @@ void CheatEditDialog::accepted() { } mainWindow->editCheat(cheatEntry.getMetadata().handle, bytes, [this](u32 handle) { - dispatchToMainThread([this, handle]() { - if (handle == badCheatHandle) { - cheatEntry.Remove(); - return; - } else { - CheatMetadata metadata = cheatEntry.getMetadata(); - metadata.handle = handle; - cheatEntry.setMetadata(metadata); - cheatEntry.Update(); - } - }); + dispatchToMainThread([this, handle]() { + if (handle == Cheats::badCheatHandle) { + cheatEntry.Remove(); + return; + } else { + CheatMetadata metadata = cheatEntry.getMetadata(); + metadata.handle = handle; + cheatEntry.setMetadata(metadata); + cheatEntry.Update(); + } + }); }); } void CheatEditDialog::rejected() { - bool isEditing = cheatEntry.getMetadata().handle != badCheatHandle; + bool isEditing = cheatEntry.getMetadata().handle != Cheats::badCheatHandle; if (!isEditing) { // Was adding a cheat but user pressed cancel cheatEntry.Remove(); @@ -253,7 +252,7 @@ CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath void CheatsWindow::addEntry() { // CheatEntryWidget is added to the list when it's created - CheatEntryWidget* entry = new CheatEntryWidget(emu, {badCheatHandle, "New cheat", "", true}, cheatList); + CheatEntryWidget* entry = new CheatEntryWidget(emu, {Cheats::badCheatHandle, "New cheat", "", true}, cheatList); CheatEditDialog* dialog = new CheatEditDialog(emu, *entry); dialog->show(); } diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index e603b780..1d7118bc 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -239,7 +239,6 @@ void MainWindow::showAboutMenu() { } void MainWindow::openLuaEditor() { luaEditor->show(); } - void MainWindow::openCheatsEditor() { cheatsEditor->show(); } void MainWindow::dispatchMessage(const EmulatorMessage& message) { @@ -259,7 +258,7 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { u32 handle = message.cheat.c->handle; const std::vector& cheat = message.cheat.c->cheat; const std::function& callback = message.cheat.c->callback; - bool isEditing = handle != badCheatHandle; + bool isEditing = handle != Cheats::badCheatHandle; if (isEditing) { emu->getCheats().removeCheat(handle); u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size()); From c6d0769a95b5a9a0d3a584351e072f34b559aa3a Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 28 Jan 2024 00:32:20 +0200 Subject: [PATCH 43/50] Update text_editor.cpp --- src/panda_qt/text_editor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panda_qt/text_editor.cpp b/src/panda_qt/text_editor.cpp index c189c2ce..a31a829f 100644 --- a/src/panda_qt/text_editor.cpp +++ b/src/panda_qt/text_editor.cpp @@ -16,8 +16,8 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider); // Default to standard mode instead of vim mode, initialize text box - zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName()); zepWidget.GetEditor().InitWithText(filename, initialText); + zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName()); // Layout for widgets QVBoxLayout* mainLayout = new QVBoxLayout(); @@ -41,4 +41,4 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, mainLayout->addWidget(button); mainLayout->addWidget(&zepWidget); -} \ No newline at end of file +} From fdc8bfd58e65626fbe4381336e38fe36baade770 Mon Sep 17 00:00:00 2001 From: Ishan09811 <156402647+Ishan09811@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:34:45 +0530 Subject: [PATCH 44/50] Make navigation Bar Fully Transparent (#381) * Make navigation Bar Fully Transparent * if the theme color is white then don't use the light navigation bar * fixes navigation bar visibility in light mode --- src/pandroid/app/src/main/res/values/themes.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pandroid/app/src/main/res/values/themes.xml b/src/pandroid/app/src/main/res/values/themes.xml index 2ade7102..343fab28 100644 --- a/src/pandroid/app/src/main/res/values/themes.xml +++ b/src/pandroid/app/src/main/res/values/themes.xml @@ -24,7 +24,11 @@ 32sp - - \ No newline at end of file + From eabd22423c4098be90776e86324035fa2172f5ff Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:36:49 +0200 Subject: [PATCH 45/50] Remove outdated stubs in Mii Selector applet --- src/core/applets/mii_selector.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index d6dd79da..ab6455fc 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -22,7 +22,6 @@ Result::HorizonResult MiiSelectorApplet::start(const MemoryBlock* sharedMem, con // Thanks to Citra devs as always for the default mii data and other applet help output = getDefaultMii(); output.returnCode = 0; // Success - // output.selectedMiiData = miiData; output.selectedGuestMiiIndex = std::numeric_limits::max(); output.miiChecksum = boost::crc<16, 0x1021, 0, 0, false, false>(&output.selectedMiiData, sizeof(MiiData) + sizeof(output.unknown1)); @@ -84,4 +83,4 @@ MiiResult MiiSelectorApplet::getDefaultMii() { result.guestMiiName.fill(0x0); return result; -} \ No newline at end of file +} From 9cc3fc0c4c1f5a5ba5223f4341b83af9ad77ffc9 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 29 Jan 2024 21:07:21 +0200 Subject: [PATCH 46/50] [Qt] Add touchscreen --- include/panda_qt/main_window.hpp | 10 ++++++++ src/panda_qt/main_window.cpp | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index c03cffbb..c2db9ac1 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -42,6 +42,8 @@ class MainWindow : public QMainWindow { SetCirclePadY, LoadLuaScript, EditCheat, + PressTouchscreen, + ReleaseTouchscreen, }; // Tagged union representing our message queue messages @@ -68,6 +70,11 @@ class MainWindow : public QMainWindow { struct { CheatMessage* c; } cheat; + + struct { + u16 x; + u16 y; + } touchscreen; }; }; @@ -108,6 +115,9 @@ class MainWindow : public QMainWindow { void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void loadLuaScript(const std::string& code); void editCheat(u32 handle, const std::vector& cheat, const std::function& callback); }; \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 1d7118bc..de70cc18 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -277,6 +278,10 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; case MessageType::SetCirclePadX: emu->getServiceManager().getHID().setCirclepadX(message.circlepad.value); break; case MessageType::SetCirclePadY: emu->getServiceManager().getHID().setCirclepadY(message.circlepad.value); break; + case MessageType::PressTouchscreen: + emu->getServiceManager().getHID().setTouchScreenPress(message.touchscreen.x, message.touchscreen.y); + break; + case MessageType::ReleaseTouchscreen: emu->getServiceManager().getHID().releaseTouchScreen(); break; } } @@ -357,6 +362,40 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) { } } +void MainWindow::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::MouseButton::LeftButton) { + const QPointF clickPos = event->globalPosition(); + const QPointF widgetPos = screen.mapFromGlobal(clickPos); + + // Press is inside the screen area + if (widgetPos.x() >= 0 && widgetPos.x() < screen.width() && widgetPos.y() >= 0 && widgetPos.y() < screen.height()) { + // Go from widget positions to [0, 400) for x and [0, 480) for y + uint x = (uint)std::round(widgetPos.x() / screen.width() * 400.f); + uint y = (uint)std::round(widgetPos.y() / screen.height() * 480.f); + + // Check if touch falls in the touch screen area + if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { + // Convert to 3DS coordinates + u16 x_converted = static_cast(x) - 40; + u16 y_converted = static_cast(y) - 240; + + EmulatorMessage message{.type = MessageType::PressTouchscreen}; + message.touchscreen.x = x_converted; + message.touchscreen.y = y_converted; + sendMessage(message); + } else { + sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen}); + } + } + } +} + +void MainWindow::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::MouseButton::LeftButton) { + sendMessage(EmulatorMessage{.type = MessageType::ReleaseTouchscreen}); + } +} + void MainWindow::loadLuaScript(const std::string& code) { EmulatorMessage message{.type = MessageType::LoadLuaScript}; From e6d012d05d487ce2ceea7afc4da7372175a46754 Mon Sep 17 00:00:00 2001 From: Ishan09811 <156402647+Ishan09811@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:34:03 +0530 Subject: [PATCH 47/50] Compile release APK on CI *Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> --- .github/workflows/Android_Build.yml | 56 +++++++++++++------ src/pandroid/app/build.gradle.kts | 16 +++++- src/pandroid/app/src/main/AndroidManifest.xml | 2 + 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/.github/workflows/Android_Build.yml b/.github/workflows/Android_Build.yml index 7625e18d..137577c1 100644 --- a/.github/workflows/Android_Build.yml +++ b/.github/workflows/Android_Build.yml @@ -6,15 +6,19 @@ on: - master pull_request: -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - jobs: x64: runs-on: ubuntu-latest + strategy: + matrix: + build_type: + - release + steps: + - name: Set BUILD_TYPE variable + run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV + - uses: actions/checkout@v2 - name: Fetch submodules run: git submodule update --init --recursive @@ -29,7 +33,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v3 with: - distribution: 'zulu' # See 'Supported distributions' for available options + distribution: 'zulu' java-version: '17' - name: Configure CMake @@ -37,23 +41,36 @@ jobs: - name: Build run: | + # Apply patch for GLES compatibility git apply ./.github/gles.patch - cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + # Build the project with CMake + cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }} + # Move the generated library to the appropriate location mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/x86_64/ + # Build the Android app with Gradle cd src/pandroid - ./gradlew assembleDebug + ./gradlew assemble${{ env.BUILD_TYPE }} cd ../.. - - name: Upload executable + - name: Upload artifacts uses: actions/upload-artifact@v2 with: - name: Android APK (x86-64) - path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk' + name: Android APKs (x86-64) + path: | + ./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk arm64: runs-on: ubuntu-latest + strategy: + matrix: + build_type: + - release + steps: + - name: Set BUILD_TYPE variable + run: echo "BUILD_TYPE=${{ matrix.build_type }}" >> $GITHUB_ENV + - uses: actions/checkout@v2 - name: Fetch submodules run: git submodule update --init --recursive @@ -68,7 +85,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v3 with: - distribution: 'zulu' # See 'Supported distributions' for available options + distribution: 'zulu' java-version: '17' - name: Configure CMake @@ -76,16 +93,21 @@ jobs: - name: Build run: | + # Apply patch for GLES compatibility git apply ./.github/gles.patch - cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + # Build the project with CMake + cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }} + # Move the generated library to the appropriate location mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/arm64-v8a/ + # Build the Android app with Gradle cd src/pandroid - ./gradlew assembleDebug + ./gradlew assemble${{ env.BUILD_TYPE }} + ls -R app/build/outputs cd ../.. - - name: Upload executable + - name: Upload artifacts uses: actions/upload-artifact@v2 with: - name: Android APK (arm64) - path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk' - + name: Android APKs (arm64) + path: | + ./src/pandroid/app/build/outputs/apk/${{ env.BUILD_TYPE }}/app-${{ env.BUILD_TYPE }}.apk diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts index f1feaf0d..201d5db1 100644 --- a/src/pandroid/app/build.gradle.kts +++ b/src/pandroid/app/build.gradle.kts @@ -21,8 +21,20 @@ android { } buildTypes { - release { + getByName("release") { isMinifyEnabled = false + isShrinkResources = false + isDebuggable = false + signingConfig = signingConfigs.getByName("debug") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + getByName("debug") { + isMinifyEnabled = false + isShrinkResources = false + isDebuggable = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -41,4 +53,4 @@ dependencies { implementation("androidx.preference:preference:1.2.1") implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("com.google.code.gson:gson:2.10.1") -} \ No newline at end of file +} diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index 9f767654..b6276493 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -20,6 +20,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:isGame="true" + android:hardwareAccelerated="true" android:theme="@style/Theme.Pandroid" tools:targetApi="31"> Date: Thu, 1 Feb 2024 11:46:15 -0400 Subject: [PATCH 48/50] Logger (#397) * Initial commit * add shader-jit option * add translate to word "graphics' for ptbr * Native logger * Bonk * fix --------- Co-authored-by: gabriel Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> --- include/logger.hpp | 10 +- src/jni_driver.cpp | 7 ++ src/pandroid/app/src/main/AndroidManifest.xml | 2 + .../com/panda3ds/pandroid/AlberDriver.java | 2 + .../panda3ds/pandroid/app/GameActivity.java | 6 + .../pandroid/app/PandroidApplication.java | 6 + .../app/base/BasePreferenceFragment.java | 9 ++ .../pandroid/app/main/SettingsFragment.java | 2 + .../app/preferences/DeveloperPreferences.java | 49 +++++++++ .../pandroid/app/services/LoggerService.java | 104 ++++++++++++++++++ .../pandroid/data/config/GlobalConfig.java | 4 +- .../pandroid/lang/PipeStreamTask.java | 40 +++++++ .../java/com/panda3ds/pandroid/lang/Task.java | 2 + .../panda3ds/pandroid/utils/FileUtils.java | 19 ++++ .../pandroid/utils/PerformanceMonitor.java | 64 +++++++++++ .../pandroid/view/PandaGlRenderer.java | 12 +- .../pandroid/view/utils/PerformanceView.java | 64 +++++++++++ .../src/main/res/values-pt-rBR/strings.xml | 11 +- .../app/src/main/res/values/strings.xml | 9 ++ .../main/res/xml/developer_preferences.xml | 29 +++++ .../src/main/res/xml/start_preferences.xml | 7 ++ 21 files changed, 454 insertions(+), 4 deletions(-) create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java create mode 100644 src/pandroid/app/src/main/res/xml/developer_preferences.xml diff --git a/include/logger.hpp b/include/logger.hpp index 82d90410..e021a685 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -2,6 +2,10 @@ #include #include +#ifdef __ANDROID__ +#include +#endif + namespace Log { // Our logger class template @@ -12,7 +16,11 @@ namespace Log { std::va_list args; va_start(args, fmt); +#ifdef __ANDROID__ + __android_log_vprint(ANDROID_LOG_DEFAULT, "Panda3DS", fmt, args); +#else std::vprintf(fmt, args); +#endif va_end(args); } }; @@ -81,4 +89,4 @@ namespace Log { #else #define MAKE_LOG_FUNCTION(functionName, logger) MAKE_LOG_FUNCTION_USER(functionName, logger) #endif -} +} \ No newline at end of file diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 6eeb727a..d962f23e 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -35,6 +35,13 @@ JNIEnv* jniEnv() { extern "C" { +#define MAKE_SETTING(functionName, type, settingName) \ +AlberFunction(void, functionName) (JNIEnv* env, jobject obj, type value) { emulator->getConfig().settingName = value; } + +MAKE_SETTING(setShaderJitEnabled, jboolean, shaderJitEnabled) + +#undef MAKE_SETTING + AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { env->GetJavaVM(&jvm); } AlberFunction(void, Pause)(JNIEnv* env, jobject obj) { emulator->pause(); } AlberFunction(void, Resume)(JNIEnv* env, jobject obj) { emulator->resume(); } diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index b6276493..c66d37af 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -48,5 +48,7 @@ + + diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java index 5cff703c..00b7842b 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java @@ -22,5 +22,7 @@ public class AlberDriver { public static native void LoadLuaScript(String script); public static native byte[] GetSmdh(); + public static native void setShaderJitEnabled(boolean enable); + static { System.loadLibrary("Alber"); } } \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java index aced6faa..f7050e99 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java @@ -21,6 +21,7 @@ import com.panda3ds.pandroid.input.InputMap; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.PandaGlSurfaceView; import com.panda3ds.pandroid.view.PandaLayoutController; +import com.panda3ds.pandroid.view.utils.PerformanceView; public class GameActivity extends BaseActivity { private final DrawerFragment drawerFragment = new DrawerFragment(); @@ -56,6 +57,11 @@ public class GameActivity extends BaseActivity { ((CheckBox) findViewById(R.id.hide_screen_controller)).setChecked(GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE)); getSupportFragmentManager().beginTransaction().replace(R.id.drawer_fragment, drawerFragment).commitNow(); + + if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) { + PerformanceView view = new PerformanceView(this); + ((FrameLayout) findViewById(R.id.panda_gl_frame)).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } } @Override diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java index 02fbbbcc..b0cdc935 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java @@ -2,11 +2,13 @@ package com.panda3ds.pandroid.app; import android.app.Application; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.services.LoggerService; import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.input.InputMap; import com.panda3ds.pandroid.utils.GameUtils; @@ -24,6 +26,10 @@ public class PandroidApplication extends Application { GameUtils.initialize(); InputMap.initialize(); AlberDriver.Setup(); + + if (GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE)) { + startService(new Intent(this, LoggerService.class)); + } } public static int getThemeId() { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java index 3cd28f4b..9482df1d 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java @@ -1,8 +1,13 @@ package com.panda3ds.pandroid.app.base; import android.annotation.SuppressLint; + +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.SwitchPreference; + import com.panda3ds.pandroid.lang.Function; @@ -15,4 +20,8 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { return false; }); } + + protected void setActivityTitle(@StringRes int titleId) { + ((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(titleId); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java index b3bebf8f..bfe33a2b 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java @@ -8,6 +8,7 @@ import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.app.PreferenceActivity; import com.panda3ds.pandroid.app.base.BasePreferenceFragment; import com.panda3ds.pandroid.app.preferences.AppearancePreferences; +import com.panda3ds.pandroid.app.preferences.DeveloperPreferences; import com.panda3ds.pandroid.app.preferences.InputPreferences; public class SettingsFragment extends BasePreferenceFragment { @@ -16,5 +17,6 @@ public class SettingsFragment extends BasePreferenceFragment { setPreferencesFromResource(R.xml.start_preferences, rootKey); setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class)); setItemClick("appearance", (item)-> PreferenceActivity.launch(requireContext(), AppearancePreferences.class)); + setItemClick("developer", (item)-> PreferenceActivity.launch(requireContext(), DeveloperPreferences.class)); } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java new file mode 100644 index 00000000..f131f0a0 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java @@ -0,0 +1,49 @@ +package com.panda3ds.pandroid.app.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.preference.SwitchPreference; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.app.base.BasePreferenceFragment; +import com.panda3ds.pandroid.app.services.LoggerService; +import com.panda3ds.pandroid.data.config.GlobalConfig; + +public class DeveloperPreferences extends BasePreferenceFragment { + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.developer_preferences, rootKey); + setActivityTitle(R.string.developer_options); + + setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreference) pref).isChecked())); + setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreference) pref).isChecked())); + setItemClick("loggerService", pref -> { + boolean checked = ((SwitchPreference) pref).isChecked(); + Context ctx = PandroidApplication.getAppContext(); + if (checked) { + ctx.startService(new Intent(ctx, LoggerService.class)); + } else { + ctx.stopService(new Intent(ctx, LoggerService.class)); + } + GlobalConfig.set(GlobalConfig.KEY_LOGGER_SERVICE, checked); + }); + + refresh(); + } + + @Override + public void onResume() { + super.onResume(); + refresh(); + } + + private void refresh() { + ((SwitchPreference) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)); + ((SwitchPreference) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE)); + ((SwitchPreference) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT)); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java new file mode 100644 index 00000000..e44f3503 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java @@ -0,0 +1,104 @@ +package com.panda3ds.pandroid.app.services; + +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.Build; +import android.os.IBinder; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.panda3ds.pandroid.lang.PipeStreamTask; +import com.panda3ds.pandroid.lang.Task; +import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.utils.FileUtils; + +import java.io.OutputStream; +import java.util.Arrays; + +public class LoggerService extends Service { + private static final long MAX_LOG_SIZE = 1024 * 1024 * 4; // 4MB + + private PipeStreamTask errorTask; + private PipeStreamTask outputTask; + private Process logcat; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + try { + Runtime.getRuntime().exec(new String[]{"logcat", "-c"}).waitFor(); + logcat = Runtime.getRuntime().exec(new String[]{"logcat"}); + String logPath = getExternalMediaDirs()[0].getAbsolutePath(); + FileUtils.createDir(logPath, "logs"); + logPath = logPath + "/logs"; + + if (FileUtils.exists(logPath + "/last.txt")) { + FileUtils.delete(logPath + "/last.txt"); + } + + if (FileUtils.exists(logPath + "/current.txt")) { + FileUtils.rename(logPath + "/current.txt", "last.txt"); + } + + OutputStream stream = FileUtils.getOutputStream(logPath + "/current.txt"); + errorTask = new PipeStreamTask(logcat.getErrorStream(), stream, MAX_LOG_SIZE); + outputTask = new PipeStreamTask(logcat.getInputStream(), stream, MAX_LOG_SIZE); + + errorTask.start(); + outputTask.start(); + + Log.i(Constants.LOG_TAG, "Started logger service"); + logDeviceInfo(); + } catch (Exception e) { + stopSelf(); + Log.e(Constants.LOG_TAG, "Failed to start logger service"); + } + } + + private void logDeviceInfo() { + Log.i(Constants.LOG_TAG, "----------------------"); + Log.i(Constants.LOG_TAG, "Android SDK: " + Build.VERSION.SDK_INT); + Log.i(Constants.LOG_TAG, "Device: " + Build.DEVICE); + Log.i(Constants.LOG_TAG, "Model: " + Build.MANUFACTURER + " " + Build.MODEL); + Log.i(Constants.LOG_TAG, "ABIs: " + Arrays.toString(Build.SUPPORTED_ABIS)); + try { + PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0); + Log.i(Constants.LOG_TAG, ""); + Log.i(Constants.LOG_TAG, "Package: " + info.packageName); + Log.i(Constants.LOG_TAG, "Install location: " + info.installLocation); + Log.i(Constants.LOG_TAG, "App version: " + info.versionName + " (" + info.versionCode + ")"); + } catch (Exception e) { + Log.e(Constants.LOG_TAG, "Error obtaining package info: " + e); + } + Log.i(Constants.LOG_TAG, "----------------------"); + } + + @Override + public void onTaskRemoved(Intent rootIntent) { + stopSelf(); + //This is a time for app save save log file + try { + Thread.sleep(1000); + } catch (Exception e) {} + super.onTaskRemoved(rootIntent); + } + + @Override + public void onDestroy() { + Log.i(Constants.LOG_TAG, "Logger service terminating"); + errorTask.close(); + outputTask.close(); + try { + logcat.destroy(); + } catch (Throwable t) {} + super.onDestroy(); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index d6dbe3b8..bff1f9e0 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -5,7 +5,6 @@ import com.panda3ds.pandroid.data.GsonConfigParser; import com.panda3ds.pandroid.utils.Constants; import java.io.Serializable; -import java.util.HashMap; import java.util.Map; public class GlobalConfig { @@ -19,6 +18,9 @@ public class GlobalConfig { public static DataModel data; + public static final Key KEY_SHADER_JIT = new Key<>("emu.shader_jit", false); + public static final Key KEY_SHOW_PERFORMANCE_OVERLAY = new Key<>("dev.performanceOverlay", false); + public static final Key KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false); public static final Key KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID); public static final Key KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java new file mode 100644 index 00000000..e4bbda98 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java @@ -0,0 +1,40 @@ +package com.panda3ds.pandroid.lang; + +import java.io.InputStream; +import java.io.OutputStream; + +public class PipeStreamTask extends Task { + private final InputStream input; + private final OutputStream output; + private final long limit; + private long size; + + public PipeStreamTask(InputStream input, OutputStream output, long limit) { + this.input = input; + this.output = output; + this.limit = limit; + } + + @Override + public void run() { + super.run(); + int data; + try { + while ((data = input.read()) != -1) { + output.write(data); + if (++size > limit) { + break; + } + } + } catch (Exception e) {} + close(); + } + + public void close() { + try { + output.flush(); + output.close(); + input.close(); + } catch (Exception e) {} + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java index 7745883d..8de344b4 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java @@ -5,6 +5,8 @@ public class Task extends Thread { super(runnable); } + protected Task() {} + public void runSync() { start(); waitFinish(); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java index 45faf5a4..1746f1c9 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java @@ -70,6 +70,25 @@ public class FileUtils { return parseFile(path).exists(); } + public static void rename(String path, String newName){ + parseFile(path).renameTo(newName); + } + + public static void delete(String path) { + DocumentFile file = parseFile(path); + + if (file.exists()) { + if (file.isDirectory()) { + String[] children = listFiles(path); + for (String child : children) { + delete(path + "/" + child); + } + } + + file.delete(); + } + } + public static boolean createDir(String path, String name) { DocumentFile folder = parseFile(path); if (folder.findFile(name) != null) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java new file mode 100644 index 00000000..23adbf13 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java @@ -0,0 +1,64 @@ +package com.panda3ds.pandroid.utils; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.Debug; +import android.os.Process; + +import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.data.config.GlobalConfig; + +public class PerformanceMonitor { + private static int fps = 1; + private static String backend = ""; + private static int frames = 0; + private static long lastUpdate = 0; + private static long totalMemory = 1; + private static long availableMemory = 0; + + public static void initialize(String backendName) { + fps = 1; + backend = backendName; + } + + public static void runFrame() { + if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) { + frames++; + if (System.currentTimeMillis() - lastUpdate > 1000) { + lastUpdate = System.currentTimeMillis(); + fps = frames; + frames = 0; + try { + Context ctx = PandroidApplication.getAppContext(); + ActivityManager manager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); + manager.getMemoryInfo(info); + totalMemory = info.totalMem; + availableMemory = info.availMem; + } catch (Exception e) {} + } + } + } + + public static long getUsedMemory() { + return Math.max(1, totalMemory - availableMemory); + } + + public static long getTotalMemory() { + return totalMemory; + } + + public static long getAvailableMemory() { + return availableMemory; + } + + public static int getFps() { + return fps; + } + + public static String getBackend() { + return backend; + } + + public static void destroy() {} +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java index 52e609a3..c39b36b3 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java @@ -7,8 +7,10 @@ import android.graphics.Rect; import android.opengl.GLSurfaceView; import android.util.Log; import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.data.config.GlobalConfig; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.GameUtils; +import com.panda3ds.pandroid.utils.PerformanceMonitor; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout; @@ -38,9 +40,12 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer if (screenTexture != 0) { glDeleteTextures(1, new int[] {screenTexture}, 0); } - if (screenFbo != 0) { + + if (screenFbo != 0) { glDeleteFramebuffers(1, new int[] {screenFbo}, 0); } + + PerformanceMonitor.destroy(); super.finalize(); } @@ -78,6 +83,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer glBindFramebuffer(GL_FRAMEBUFFER, 0); AlberDriver.Initialize(); + AlberDriver.setShaderJitEnabled(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT)); AlberDriver.LoadRom(romPath); // Load the SMDH @@ -92,6 +98,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer GameUtils.removeGame(game); GameUtils.addGame(GameMetadata.applySMDH(game, smdh)); } + + PerformanceMonitor.initialize(getBackendName()); } public void onDrawFrame(GL10 unused) { @@ -114,6 +122,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer screenHeight - bottomScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR ); } + + PerformanceMonitor.runFrame(); } public void onSurfaceChanged(GL10 unused, int width, int height) { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java new file mode 100644 index 00000000..e4d7be15 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java @@ -0,0 +1,64 @@ +package com.panda3ds.pandroid.view.utils; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.text.Html; +import android.util.AttributeSet; +import android.util.TypedValue; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; + +import com.panda3ds.pandroid.data.config.GlobalConfig; +import com.panda3ds.pandroid.utils.PerformanceMonitor; + +public class PerformanceView extends AppCompatTextView { + private boolean running = false; + + public PerformanceView(@NonNull Context context) { + this(context, null); + } + + public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs,0); + } + + public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()); + setPadding(padding,padding,padding,padding); + setTextColor(Color.WHITE); + setShadowLayer(padding,0,0,Color.BLACK); + } + + public void refresh(){ + running = isShown(); + if (!running) { + return; + } + + String debug = ""; + + // Calculate total memory in MB and the current memory usage + int memoryTotalMb = (int) Math.round(PerformanceMonitor.getTotalMemory() / (1024.0 * 1024.0)); + int memoryUsageMb = (int) Math.round(PerformanceMonitor.getUsedMemory() / (1024.0 * 1024.0)); + + debug += "FPS: " + PerformanceMonitor.getFps() + "
"; + debug += "RAM: " + Math.round(((float) memoryUsageMb / memoryTotalMb) * 100) + "% (" + memoryUsageMb + "MB/" + memoryTotalMb + "MB)
"; + debug += "BACKEND: " + PerformanceMonitor.getBackend() + (GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT) ? " + JIT" : "") + "
"; + setText(Html.fromHtml(debug, Html.FROM_HTML_MODE_COMPACT)); + postDelayed(this::refresh, 250); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (!running) { + refresh(); + } + } +} diff --git a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml index 065b9e4f..1198d66b 100644 --- a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml @@ -45,4 +45,13 @@ Abrir arquivo Criar novo Executando \"%s\" ... - \ No newline at end of file + Opções de desenvolvedor + Depuração, mostrar fps, etc. + Monitor de desempenho + Mostrar um overlay com fps, memoria, etc. + Depuração + Grave os registros para um arquivo. + Shader Jit + Usar recompilador de shaders. + Gráficos + diff --git a/src/pandroid/app/src/main/res/values/strings.xml b/src/pandroid/app/src/main/res/values/strings.xml index e0de62e1..4c64439c 100644 --- a/src/pandroid/app/src/main/res/values/strings.xml +++ b/src/pandroid/app/src/main/res/values/strings.xml @@ -46,4 +46,13 @@ Open file Create new Running \"%s\" ... + Developer options + Logger, FPS Counter, etc. + Performance monitor + Show overlay with fps, memory, etc. + Logger + Store application logs to file. + Shader JIT + Use shader recompiler. + Graphics diff --git a/src/pandroid/app/src/main/res/xml/developer_preferences.xml b/src/pandroid/app/src/main/res/xml/developer_preferences.xml new file mode 100644 index 00000000..96ce8906 --- /dev/null +++ b/src/pandroid/app/src/main/res/xml/developer_preferences.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/xml/start_preferences.xml b/src/pandroid/app/src/main/res/xml/start_preferences.xml index 878aa920..5eeb1954 100644 --- a/src/pandroid/app/src/main/res/xml/start_preferences.xml +++ b/src/pandroid/app/src/main/res/xml/start_preferences.xml @@ -23,4 +23,11 @@ app:summary="@string/pref_appearance_summary" app:layout="@layout/preference_start_item"/> + + \ No newline at end of file From 362b8e6621f7b604b6b1fb0d5f94eae5e253f21a Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 1 Feb 2024 20:02:53 +0200 Subject: [PATCH 49/50] Update luv bindings to be disabled on Android --- CMakeLists.txt | 16 ++++++++-------- src/lua.cpp | 8 ++++++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9238264..e2548312 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,11 +40,6 @@ option(ENABLE_LUAJIT "Enable scripting with the Lua programming language" ON) option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) option(BUILD_HYDRA_CORE "Build a Hydra core" OFF) -if(ENABLE_LUAJIT AND ANDROID) - message(STATUS "Enabled LuaJIT on Android build. Automatically disabling it until it works properly") - set(ENABLE_LUAJIT OFF) -endif() - include_directories(${PROJECT_SOURCE_DIR}/include/) include_directories(${PROJECT_SOURCE_DIR}/include/kernel) include_directories (${FMT_INCLUDE_DIR}) @@ -271,8 +266,8 @@ set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp third_party/xxhash/xxhash.c ) -if(ENABLE_LUAJIT) - # Build luv and libuv for TCP Lua server usage +if(ENABLE_LUAJIT AND NOT ANDROID) + # Build luv and libuv for Lua TCP server usage if we're not on Android include_directories(third_party/luv/src) include_directories(third_party/luv/deps/lua-compat-5.3/c-api) include_directories(third_party/libuv/include) @@ -446,7 +441,12 @@ endif() if(ENABLE_LUAJIT) target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_LUA=1") - target_link_libraries(Alber PRIVATE libluajit uv_a) + target_link_libraries(Alber PRIVATE libluajit) + + # If we're not on Android, link libuv too + if (NOT ANDROID) + target_link_libraries(Alber PRIVATE uv_a) + endif() endif() if(ENABLE_OPENGL) diff --git a/src/lua.cpp b/src/lua.cpp index ccfe955d..09c63173 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -1,9 +1,11 @@ #ifdef PANDA3DS_ENABLE_LUA #include "lua_manager.hpp" +#ifndef __ANDROID__ extern "C" { #include "luv.h" } +#endif void LuaManager::initialize() { L = luaL_newstate(); // Open Lua @@ -13,13 +15,15 @@ void LuaManager::initialize() { initialized = false; return; } - luaL_openlibs(L); + +#ifndef __ANDROID__ lua_pushstring(L, "luv"); luaopen_luv(L); lua_settable(L, LUA_GLOBALSINDEX); - initializeThunks(); +#endif + initializeThunks(); initialized = true; haveScript = false; } From ce63596716a723f7aba8e0f38e3caecd234a9f3b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:42:04 +0200 Subject: [PATCH 50/50] Do not build libuv shared library --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2548312..456d6513 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,6 +272,7 @@ if(ENABLE_LUAJIT AND NOT ANDROID) include_directories(third_party/luv/deps/lua-compat-5.3/c-api) include_directories(third_party/libuv/include) set(THIRD_PARTY_SOURCE_FILES ${THIRD_PARTY_SOURCE_FILES} third_party/luv/src/luv.c) + set(LIBUV_BUILD_SHARED OFF) add_subdirectory(third_party/libuv) endif()