diff --git a/.github/workflows/Android_Build.yml b/.github/workflows/Android_Build.yml index 137577c1..2d0fd844 100644 --- a/.github/workflows/Android_Build.yml +++ b/.github/workflows/Android_Build.yml @@ -45,8 +45,11 @@ jobs: git apply ./.github/gles.patch # Build the project with CMake cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }} - # Move the generated library to the appropriate location + + # Strip the generated library and move it to the appropriate location + ${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --strip-unneeded ./build/libAlber.so mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/x86_64/ + # Build the Android app with Gradle cd src/pandroid ./gradlew assemble${{ env.BUILD_TYPE }} @@ -97,8 +100,11 @@ jobs: git apply ./.github/gles.patch # Build the project with CMake cmake --build ${{github.workspace}}/build --config ${{ env.BUILD_TYPE }} - # Move the generated library to the appropriate location + + # Strip the generated library and move it to the appropriate location + ${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --strip-unneeded ./build/libAlber.so mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/arm64-v8a/ + # Build the Android app with Gradle cd src/pandroid ./gradlew assemble${{ env.BUILD_TYPE }} diff --git a/include/audio/dsp_core.hpp b/include/audio/dsp_core.hpp index 6f46d49b..1a556f28 100644 --- a/include/audio/dsp_core.hpp +++ b/include/audio/dsp_core.hpp @@ -40,6 +40,7 @@ namespace Audio { enum class Type { Null, Teakra }; DSPCore(Memory& mem, Scheduler& scheduler, DSPService& dspService) : mem(mem), scheduler(scheduler), dspService(dspService) {} + virtual ~DSPCore() {} virtual void reset() = 0; virtual void runAudioFrame() = 0; diff --git a/include/audio/null_core.hpp b/include/audio/null_core.hpp index 136a76ac..7d6f1c9e 100644 --- a/include/audio/null_core.hpp +++ b/include/audio/null_core.hpp @@ -24,6 +24,7 @@ namespace Audio { public: NullDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService) : DSPCore(mem, scheduler, dspService) {} + ~NullDSP() override {} void reset() override; void runAudioFrame() override; diff --git a/include/audio/teakra_core.hpp b/include/audio/teakra_core.hpp index db44f5ed..6a011231 100644 --- a/include/audio/teakra_core.hpp +++ b/include/audio/teakra_core.hpp @@ -78,6 +78,7 @@ namespace Audio { public: TeakraDSP(Memory& mem, Scheduler& scheduler, DSPService& dspService); + ~TeakraDSP() override {} void reset() override; diff --git a/include/emulator.hpp b/include/emulator.hpp index 920fd7f7..47fbc839 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -135,6 +135,7 @@ class Emulator { ServiceManager& getServiceManager() { return kernel.getServiceManager(); } LuaManager& getLua() { return lua; } Scheduler& getScheduler() { return scheduler; } + Memory& getMemory() { return memory; } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/include/loader/ncch.hpp b/include/loader/ncch.hpp index c5ef2465..42ce1590 100644 --- a/include/loader/ncch.hpp +++ b/include/loader/ncch.hpp @@ -36,6 +36,7 @@ struct NCCH { }; u64 partitionIndex = 0; + u64 programID = 0; u64 fileOffset = 0; bool isNew3DS = false; diff --git a/include/lua_manager.hpp b/include/lua_manager.hpp index 50b8dd61..46fd553a 100644 --- a/include/lua_manager.hpp +++ b/include/lua_manager.hpp @@ -2,7 +2,6 @@ #include #include "helpers.hpp" -#include "memory.hpp" // The kinds of events that can cause a Lua call. // Frame: Call program on frame end @@ -11,6 +10,8 @@ enum class LuaEvent { Frame, }; +class Emulator; + #ifdef PANDA3DS_ENABLE_LUA extern "C" { #include @@ -30,9 +31,9 @@ class LuaManager { public: // For Lua we must have some global pointers to our emulator objects to use them in script code via thunks. See the thunks in lua.cpp as an // example - static Memory* g_memory; + static Emulator* g_emulator; - LuaManager(Memory& mem) { g_memory = &mem; } + LuaManager(Emulator& emulator) { g_emulator = &emulator; } void close(); void initialize(); @@ -51,7 +52,7 @@ class LuaManager { #else // Lua not enabled, Lua manager does nothing class LuaManager { public: - LuaManager(Memory& mem) {} + LuaManager(Emulator& emulator) {} void close() {} void initialize() {} diff --git a/include/memory.hpp b/include/memory.hpp index e2716e2a..1b6e622c 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -275,6 +275,8 @@ private: // File handle for reading the loaded ncch IOFile CXIFile; + std::optional getProgramID(); + u8* getDSPMem() { return dspRam; } u8* getDSPDataMem() { return &dspRam[DSP_DATA_MEMORY_OFFSET]; } u8* getDSPCodeMem() { return &dspRam[DSP_CODE_MEMORY_OFFSET]; } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 1f534fa0..3bf73e5d 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -32,7 +32,7 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break exheaderSize = *(u32*)&header[0x180]; - const u64 programID = *(u64*)&header[0x118]; + programID = *(u64*)&header[0x118]; // Read NCCH flags secondaryKeySlot = header[0x188 + 3]; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index fdc8c475..09b49eee 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -524,4 +524,14 @@ void Memory::copySharedFont(u8* pointer) { auto fonts = cmrc::ConsoleFonts::get_filesystem(); auto font = fonts.open("CitraSharedFontUSRelocated.bin"); std::memcpy(pointer, font.begin(), font.size()); +} + +std::optional Memory::getProgramID() { + auto cxi = getCXI(); + + if (cxi) { + return cxi->programID; + } + + return std::nullopt; } \ No newline at end of file diff --git a/src/core/services/nwm_uds.cpp b/src/core/services/nwm_uds.cpp index 9c0ef95f..7752e503 100644 --- a/src/core/services/nwm_uds.cpp +++ b/src/core/services/nwm_uds.cpp @@ -37,7 +37,8 @@ void NwmUdsService::initializeWithVersion(u32 messagePointer) { initialized = true; - mem.write32(messagePointer + 4, Result::Success); + // Stubbed to fail temporarily, since some games will break trying to establish networks otherwise + mem.write32(messagePointer + 4, Result::FailurePlaceholder); mem.write32(messagePointer + 8, 0); mem.write32(messagePointer + 12, eventHandle.value()); } diff --git a/src/emulator.cpp b/src/emulator.cpp index 528590b6..7ddefd6c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -18,7 +18,7 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; Emulator::Emulator() : 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) + cheats(memory, kernel.getServiceManager().getHID()), lua(*this), running(false), programRunning(false) #ifdef PANDA3DS_ENABLE_HTTP_SERVER , httpServer(this) diff --git a/src/lua.cpp b/src/lua.cpp index 09c63173..d12faf7e 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -1,4 +1,5 @@ #ifdef PANDA3DS_ENABLE_LUA +#include "emulator.hpp" #include "lua_manager.hpp" #ifndef __ANDROID__ @@ -42,7 +43,7 @@ void LuaManager::loadFile(const char* path) { if (!initialized) { initialize(); } - + // If init failed, don't execute if (!initialized) { printf("Lua initialization failed, file won't run\n"); @@ -88,8 +89,8 @@ void LuaManager::loadString(const std::string& code) { } void LuaManager::signalEventInternal(LuaEvent e) { - lua_getglobal(L, "eventHandler"); // We want to call the event handler - lua_pushnumber(L, static_cast(e)); // Push event type + lua_getglobal(L, "eventHandler"); // We want to call the event handler + lua_pushnumber(L, static_cast(e)); // Push event type // Call the function with 1 argument and 0 outputs, without an error handler lua_pcall(L, 1, 0, 0); @@ -103,28 +104,105 @@ void LuaManager::reset() { // Initialize C++ thunks for Lua code to call here // All code beyond this point is terrible and full of global state, don't judge -Memory* LuaManager::g_memory = nullptr; +Emulator* LuaManager::g_emulator = nullptr; -#define MAKE_MEMORY_FUNCTIONS(size) \ - static int read##size##Thunk(lua_State* L) { \ - const u32 vaddr = (u32)lua_tonumber(L, 1); \ - lua_pushnumber(L, LuaManager::g_memory->read##size(vaddr)); \ - return 1; \ - } \ - static int write##size##Thunk(lua_State* L) { \ - const u32 vaddr = (u32)lua_tonumber(L, 1); \ - const u##size value = (u##size)lua_tonumber(L, 2); \ - LuaManager::g_memory->write##size(vaddr, value); \ - return 0; \ +#define MAKE_MEMORY_FUNCTIONS(size) \ + static int read##size##Thunk(lua_State* L) { \ + const u32 vaddr = (u32)lua_tonumber(L, 1); \ + lua_pushnumber(L, LuaManager::g_emulator->getMemory().read##size(vaddr)); \ + return 1; \ + } \ + static int write##size##Thunk(lua_State* L) { \ + const u32 vaddr = (u32)lua_tonumber(L, 1); \ + const u##size value = (u##size)lua_tonumber(L, 2); \ + LuaManager::g_emulator->getMemory().write##size(vaddr, value); \ + return 0; \ } - MAKE_MEMORY_FUNCTIONS(8) MAKE_MEMORY_FUNCTIONS(16) MAKE_MEMORY_FUNCTIONS(32) MAKE_MEMORY_FUNCTIONS(64) #undef MAKE_MEMORY_FUNCTIONS +static int getAppIDThunk(lua_State* L) { + std::optional id = LuaManager::g_emulator->getMemory().getProgramID(); + + // If the app has an ID, return true + its ID + // Otherwise return false and 0 as the ID + if (id.has_value()) { + lua_pushboolean(L, 1); // Return true + lua_pushnumber(L, u32(*id)); // Return bottom 32 bits + lua_pushnumber(L, u32(*id >> 32)); // Return top 32 bits + } else { + lua_pushboolean(L, 0); // Return false + // Return no ID + lua_pushnumber(L, 0); + lua_pushnumber(L, 0); + } + + return 3; +} + +static int pauseThunk(lua_State* L) { + LuaManager::g_emulator->pause(); + return 0; +} + +static int resumeThunk(lua_State* L) { + LuaManager::g_emulator->resume(); + return 0; +} + +static int resetThunk(lua_State* L) { + LuaManager::g_emulator->reset(Emulator::ReloadOption::Reload); + return 0; +} + +static int loadROMThunk(lua_State* L) { + // Path argument is invalid, report that loading failed and exit + if (lua_type(L, -1) != LUA_TSTRING) { + lua_pushboolean(L, 0); + return 1; + } + + size_t pathLength; + const char* const str = lua_tolstring(L, -1, &pathLength); + + const auto path = std::filesystem::path(std::string(str, pathLength)); + // Load ROM and reply if it succeeded or not + lua_pushboolean(L, LuaManager::g_emulator->loadROM(path) ? 1 : 0); + return 1; +} + +static int getButtonsThunk(lua_State* L) { + auto buttons = LuaManager::g_emulator->getServiceManager().getHID().getOldButtons(); + lua_pushinteger(L, static_cast(buttons)); + + return 1; +} + +static int getCirclepadThunk(lua_State* L) { + auto& hid = LuaManager::g_emulator->getServiceManager().getHID(); + s16 x = hid.getCirclepadX(); + s16 y = hid.getCirclepadY(); + + lua_pushinteger(L, static_cast(x)); + lua_pushinteger(L, static_cast(y)); + return 2; +} + +static int getButtonThunk(lua_State* L) { + auto& hid = LuaManager::g_emulator->getServiceManager().getHID(); + // This function accepts a mask. You can use it to check if one or more buttons are pressed at a time + const u32 mask = (u32)lua_tonumber(L, 1); + const bool result = (hid.getOldButtons() & mask) == mask; + + // Return whether the selected buttons are all pressed + lua_pushboolean(L, result ? 1 : 0); + return 1; +} + // clang-format off static constexpr luaL_Reg functions[] = { { "__read8", read8Thunk }, @@ -135,6 +213,14 @@ static constexpr luaL_Reg functions[] = { { "__write16", write16Thunk }, { "__write32", write32Thunk }, { "__write64", write64Thunk }, + { "__getAppID", getAppIDThunk }, + { "__pause", pauseThunk}, + { "__resume", resumeThunk}, + { "__reset", resetThunk}, + { "__loadROM", loadROMThunk}, + { "__getButtons", getButtonsThunk}, + { "__getCirclepad", getCirclepadThunk}, + { "__getButton", getButtonThunk}, { nullptr, nullptr }, }; // clang-format on @@ -150,7 +236,35 @@ void LuaManager::initializeThunks() { write16 = function(addr, value) GLOBALS.__write16(addr, value) end, write32 = function(addr, value) GLOBALS.__write32(addr, value) end, write64 = function(addr, value) GLOBALS.__write64(addr, value) end, + + getAppID = function() + local ffi = require("ffi") + + result, low, high = GLOBALS.__getAppID() + id = bit.bor(ffi.cast("uint64_t", low), (bit.lshift(ffi.cast("uint64_t", high), 32))) + return result, id + end, + + pause = function() GLOBALS.__pause() end, + resume = function() GLOBALS.__resume() end, + reset = function() GLOBALS.__reset() end, + loadROM = function(path) return GLOBALS.__loadROM(path) end, + + getButtons = function() return GLOBALS.__getButtons() end, + getButton = function(button) return GLOBALS.__getButton(button) end, + getCirclepad = function() return GLOBALS.__getCirclepad() end, + Frame = __Frame, + ButtonA = __ButtonA, + ButtonB = __ButtonB, + ButtonX = __ButtonX, + ButtonY = __ButtonY, + ButtonL = __ButtonL, + ButtonR = __ButtonR, + ButtonUp = __ButtonUp, + ButtonDown = __ButtonDown, + ButtonLeft = __ButtonLeft, + ButtonRight= __ButtonRight, } )"; @@ -160,8 +274,21 @@ void LuaManager::initializeThunks() { }; luaL_register(L, "GLOBALS", functions); + // Add values for event enum addIntConstant(LuaEvent::Frame, "__Frame"); + // Add enums for 3DS keys + addIntConstant(HID::Keys::A, "__ButtonA"); + addIntConstant(HID::Keys::B, "__ButtonB"); + addIntConstant(HID::Keys::X, "__ButtonX"); + addIntConstant(HID::Keys::Y, "__ButtonY"); + addIntConstant(HID::Keys::Up, "__ButtonUp"); + addIntConstant(HID::Keys::Down, "__ButtonDown"); + addIntConstant(HID::Keys::Left, "__ButtonLeft"); + addIntConstant(HID::Keys::Right, "__ButtonRight"); + addIntConstant(HID::Keys::L, "__ButtonL"); + addIntConstant(HID::Keys::R, "__ButtonR"); + // Call our Lua runtime initialization before any Lua script runs luaL_loadstring(L, runtimeInit); int ret = lua_pcall(L, 0, 0, 0); // tell Lua to run the script @@ -174,4 +301,4 @@ void LuaManager::initializeThunks() { } } -#endif \ No newline at end of file +#endif diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts index 201d5db1..b67f9419 100644 --- a/src/pandroid/app/build.gradle.kts +++ b/src/pandroid/app/build.gradle.kts @@ -22,8 +22,8 @@ android { buildTypes { getByName("release") { - isMinifyEnabled = false - isShrinkResources = false + isMinifyEnabled = true + isShrinkResources = true isDebuggable = false signingConfig = signingConfigs.getByName("debug") proguardFiles( diff --git a/src/pandroid/app/proguard-rules.pro b/src/pandroid/app/proguard-rules.pro index 481bb434..31c24c5a 100644 --- a/src/pandroid/app/proguard-rules.pro +++ b/src/pandroid/app/proguard-rules.pro @@ -1,16 +1,19 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# +# Pandroid Proguard Rules # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} +# Keep all JNI and C++ related classes and methods +-keepclasseswithmembernames class * { + native ; +} + +# Keep all native libraries and their methods +-keep class * { + native ; +} + +# Keep all classes in the specified package and its subpackages +-keep class com.panda3ds.pandroid.** {*;} # Uncomment this to preserve the line number information for # debugging stack traces. @@ -18,4 +21,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile