From 2160e64be113660149f6cda3649d6458dd1ffe1f Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Fri, 22 Aug 2025 04:11:11 -0400 Subject: [PATCH] ui changes cover downloader etc. --- PERFORMANCE_OPTIMIZATION_REPORT.md | 142 -- app/build.gradle | 10 +- app/src/main/AndroidManifest.xml | 8 +- .../SDL3/src/core/android/SDL_android.c | 6 +- app/src/main/cpp/native-lib.cpp | 504 ++++++- app/src/main/cpp/pcsx2/CDVD/ChdFileReader.cpp | 46 +- app/src/main/cpp/pcsx2/x86/microVU.cpp | 42 +- app/src/main/cpp/pcsx2/x86/microVU.h | 49 +- app/src/main/ic_launcher-playstore.png | Bin 0 -> 44016 bytes .../com/izzy2lost/psx2/CoversAdapter.java | 98 ++ .../psx2/GameSettingsDialogFragment.java | 259 ++++ .../psx2/GamesCoverDialogFragment.java | 388 +++++ .../izzy2lost/psx2}/HIDDevice.java | 2 +- .../psx2}/HIDDeviceBLESteamController.java | 2 +- .../izzy2lost/psx2}/HIDDeviceManager.java | 2 +- .../izzy2lost/psx2}/HIDDeviceUSB.java | 2 +- .../java/com/izzy2lost/psx2/JoystickView.java | 117 ++ .../java/com/izzy2lost/psx2/MainActivity.java | 1280 +++++++++++++++++ .../izzy2lost/psx2}/NativeApp.java | 28 +- .../izzy2lost/psx2}/SDLControllerManager.java | 2 +- .../izzy2lost/psx2}/SDLSurface.java | 3 +- .../izzy2lost/psx2/SavesDialogFragment.java | 205 +++ .../psx2/SettingsDialogFragment.java | 276 ++++ .../kr/co/iefriends/pcsx2/MainActivity.java | 539 ------- .../main/res/drawable/bg_control_cluster.xml | 7 + app/src/main/res/drawable/bg_joystick.xml | 6 + .../main/res/drawable/bg_joystick_knob.xml | 6 + app/src/main/res/drawable/ic_arrow_down.xml | 13 + app/src/main/res/drawable/ic_arrow_left.xml | 13 + app/src/main/res/drawable/ic_arrow_right.xml | 13 + app/src/main/res/drawable/ic_arrow_up.xml | 13 + app/src/main/res/drawable/ic_home.xml | 10 + app/src/main/res/drawable/ic_info.xml | 18 + .../res/drawable/ic_launcher_foreground.xml | 234 ++- app/src/main/res/drawable/ic_power.xml | 10 + app/src/main/res/drawable/ic_ps2_circle.xml | 12 + app/src/main/res/drawable/ic_ps2_square.xml | 12 + app/src/main/res/drawable/ic_ps2_triangle.xml | 12 + app/src/main/res/drawable/ic_ps2_x.xml | 12 + app/src/main/res/drawable/ic_reboot.xml | 10 + app/src/main/res/drawable/ic_unhide.xml | 79 + .../main/res/drawable/psx2_logo2_fixed.xml | 211 +++ .../main/res/layout-land/activity_main.xml | 468 ++++++ .../res/layout-land/dialog_covers_grid.xml | 73 + .../main/res/layout-port/activity_main.xml | 508 +++++++ .../res/layout-port/dialog_covers_grid.xml | 73 + app/src/main/res/layout/activity_main.xml | 140 +- .../main/res/layout/dialog_covers_grid.xml | 72 + .../main/res/layout/dialog_game_settings.xml | 147 ++ app/src/main/res/layout/dialog_saves.xml | 22 + .../res/layout/dialog_screenshot_preview.xml | 35 + app/src/main/res/layout/dialog_settings.xml | 201 +++ app/src/main/res/layout/item_cover.xml | 37 + app/src/main/res/layout/item_save_slot.xml | 76 + .../ic_launcher.xml | 5 +- .../ic_launcher_round.xml | 5 +- app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1987 bytes app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 1404 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3618 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 2898 -> 0 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1241 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 982 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2109 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 1772 -> 0 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2997 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 1900 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5414 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 3918 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 5212 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 2884 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 9413 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 5914 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 7906 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 3844 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 14050 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 7778 -> 0 bytes app/src/main/res/values-night/themes.xml | 12 +- app/src/main/res/values/arrays.xml | 37 + app/src/main/res/values/colors.xml | 13 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 5 +- app/src/main/res/values/themes.xml | 16 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 294 ++-- gradlew.bat | 15 +- settings.gradle | 2 +- 87 files changed, 6032 insertions(+), 934 deletions(-) delete mode 100644 PERFORMANCE_OPTIMIZATION_REPORT.md create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java create mode 100644 app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java create mode 100644 app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java rename app/src/main/java/{kr/co/iefriends/pcsx2 => com/izzy2lost/psx2}/HIDDevice.java (94%) rename app/src/main/java/{kr/co/iefriends/pcsx2 => com/izzy2lost/psx2}/HIDDeviceBLESteamController.java (99%) rename app/src/main/java/{kr/co/iefriends/pcsx2 => com/izzy2lost/psx2}/HIDDeviceManager.java (99%) rename app/src/main/java/{kr/co/iefriends/pcsx2 => com/izzy2lost/psx2}/HIDDeviceUSB.java (99%) create mode 100644 app/src/main/java/com/izzy2lost/psx2/JoystickView.java create mode 100644 app/src/main/java/com/izzy2lost/psx2/MainActivity.java rename app/src/main/java/{kr/co/iefriends/pcsx2 => com/izzy2lost/psx2}/NativeApp.java (65%) rename app/src/main/java/{kr/co/iefriends/pcsx2 => com/izzy2lost/psx2}/SDLControllerManager.java (99%) rename app/src/main/java/{kr/co/iefriends/pcsx2 => com/izzy2lost/psx2}/SDLSurface.java (97%) create mode 100644 app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java create mode 100644 app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java delete mode 100644 app/src/main/java/kr/co/iefriends/pcsx2/MainActivity.java create mode 100644 app/src/main/res/drawable/bg_control_cluster.xml create mode 100644 app/src/main/res/drawable/bg_joystick.xml create mode 100644 app/src/main/res/drawable/bg_joystick_knob.xml create mode 100644 app/src/main/res/drawable/ic_arrow_down.xml create mode 100644 app/src/main/res/drawable/ic_arrow_left.xml create mode 100644 app/src/main/res/drawable/ic_arrow_right.xml create mode 100644 app/src/main/res/drawable/ic_arrow_up.xml create mode 100644 app/src/main/res/drawable/ic_home.xml create mode 100644 app/src/main/res/drawable/ic_info.xml create mode 100644 app/src/main/res/drawable/ic_power.xml create mode 100644 app/src/main/res/drawable/ic_ps2_circle.xml create mode 100644 app/src/main/res/drawable/ic_ps2_square.xml create mode 100644 app/src/main/res/drawable/ic_ps2_triangle.xml create mode 100644 app/src/main/res/drawable/ic_ps2_x.xml create mode 100644 app/src/main/res/drawable/ic_reboot.xml create mode 100644 app/src/main/res/drawable/ic_unhide.xml create mode 100644 app/src/main/res/drawable/psx2_logo2_fixed.xml create mode 100644 app/src/main/res/layout-land/activity_main.xml create mode 100644 app/src/main/res/layout-land/dialog_covers_grid.xml create mode 100644 app/src/main/res/layout-port/activity_main.xml create mode 100644 app/src/main/res/layout-port/dialog_covers_grid.xml create mode 100644 app/src/main/res/layout/dialog_covers_grid.xml create mode 100644 app/src/main/res/layout/dialog_game_settings.xml create mode 100644 app/src/main/res/layout/dialog_saves.xml create mode 100644 app/src/main/res/layout/dialog_screenshot_preview.xml create mode 100644 app/src/main/res/layout/dialog_settings.xml create mode 100644 app/src/main/res/layout/item_cover.xml create mode 100644 app/src/main/res/layout/item_save_slot.xml rename app/src/main/res/{mipmap-anydpi => mipmap-anydpi-v26}/ic_launcher.xml (56%) rename app/src/main/res/{mipmap-anydpi => mipmap-anydpi-v26}/ic_launcher_round.xml (56%) create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml diff --git a/PERFORMANCE_OPTIMIZATION_REPORT.md b/PERFORMANCE_OPTIMIZATION_REPORT.md deleted file mode 100644 index f7faf15..0000000 --- a/PERFORMANCE_OPTIMIZATION_REPORT.md +++ /dev/null @@ -1,142 +0,0 @@ -# PCSX2 ARM64 Performance Optimization Report - -## Executive Summary - -This report identifies 5 key areas for performance optimization in the PCSX2 ARM64 emulator codebase. The analysis focused on memory allocation patterns, redundant operations, and ARM64-specific optimization opportunities. - -## 1. Instruction Cache Reallocation Issue (HIGH PRIORITY) - -**Location**: `app/src/main/cpp/pcsx2/x86/ix86-32/iR5900.cpp` lines 2708-2714 - -**Issue**: The instruction cache (`s_pInstCache`) is frequently reallocated during block recompilation using a naive growth strategy. Every time a block requires more instructions than the current cache size, the entire cache is freed and reallocated with only a small increment (+10 instructions). - -**Code Pattern**: -```cpp -if (s_nInstCacheSize < (s_nEndBlock - startpc) / 4 + 1) -{ - free(s_pInstCache); - s_nInstCacheSize = (s_nEndBlock - startpc) / 4 + 10; - s_pInstCache = (EEINST*)malloc(sizeof(EEINST) * s_nInstCacheSize); -} -``` - -**Impact**: HIGH - This occurs during every block recompilation that exceeds the current cache size, causing: -- Frequent malloc/free operations (expensive system calls) -- Memory fragmentation -- Loss of existing cache data -- Poor cache locality - -**Solution**: Implement exponential growth strategy with data preservation to minimize future reallocations. - -## 2. MicroVU Memory Allocation Patterns (MEDIUM PRIORITY) - -**Location**: `app/src/main/cpp/pcsx2/x86/microVU.cpp` lines 138-139, `microVU.h` lines 190, 163, 170 - -**Issue**: Frequent `_aligned_malloc` and `_aligned_free` operations for microProgram structures and microBlockLink objects. - -**Code Patterns**: -```cpp -microProgram* prog = (microProgram*)_aligned_malloc(sizeof(microProgram), 64); -microBlockLink* newBlock = (microBlockLink*)_aligned_malloc(sizeof(microBlockLink), 32); -_aligned_free(freeI); -``` - -**Impact**: MEDIUM - Occurs during VU program creation/deletion: -- Aligned memory allocation is more expensive than regular malloc -- Frequent allocation/deallocation during emulation -- Memory fragmentation from different alignment requirements - -**Solution**: Implement object pools or pre-allocated memory regions for these frequently used structures. - -## 3. Redundant Memory Clearing Operations (LOW-MEDIUM PRIORITY) - -**Location**: Multiple files with `std::memset` patterns - -**Issue**: Unnecessary zero-initialization of large structures, particularly: -- `microVU_Branch.inl` lines 17-18: Clearing lpState structures -- `iCore.cpp` lines 32, 930-933: Clearing register arrays -- `microVU_Compile.inl`: Multiple memset operations - -**Code Patterns**: -```cpp -std::memset(µVU0.prog.lpState, 0, sizeof(microVU1.prog.lpState)); -std::memset(xmmregs, 0, sizeof(xmmregs)); -std::memset(pinst, 0, sizeof(EEINST)); -``` - -**Impact**: LOW-MEDIUM - Cumulative effect across many operations: -- Unnecessary CPU cycles spent zeroing memory -- Some structures are immediately overwritten after clearing -- Cache pollution from touching large memory regions - -**Solution**: Optimize initialization patterns and avoid redundant clears where data is immediately overwritten. - -## 4. ARM64 NEON SIMD Optimization Opportunities (MEDIUM PRIORITY) - -**Location**: `app/src/main/cpp/pcsx2/arm64/Vif_UnpackNEON.cpp` - -**Issue**: While the code already uses NEON instructions, there are opportunities for additional optimizations: - -**Current Implementation Analysis**: -- VIF unpacking uses individual NEON operations -- Some operations could be combined or vectorized further -- Potential for better instruction scheduling - -**Code Example** (lines 294-295): -```cpp -armAsm->Shl(destReg.V4S(), destReg.V4S(), 24); -armAsm->Ushr(destReg.V4S(), destReg.V4S(), 24); -``` - -**Impact**: MEDIUM - Affects graphics data processing performance: -- VIF unpacking is on the critical path for graphics rendering -- Better NEON utilization could improve frame rates -- ARM64-specific optimizations not fully exploited - -**Solution**: Implement more efficient NEON instruction sequences and better utilize ARM64 capabilities. - -## 5. Loop Optimization Opportunities (LOW-MEDIUM PRIORITY) - -**Location**: Various files with for/while loops - -**Issue**: Some loops could benefit from unrolling or vectorization, particularly in: -- Memory copying operations -- Register clearing loops -- Block iteration patterns - -**Examples**: -- `BaseblockEx.cpp` lines 76-80: Simple iteration that could be unrolled -- `microVU.h` lines 127-129, 253-255: Loops over fixed-size arrays - -**Impact**: LOW-MEDIUM - Depends on loop frequency: -- Hot loops in recompilation paths could benefit from optimization -- Some loops are over small, fixed-size arrays suitable for unrolling -- Profile-guided optimization needed to identify highest impact loops - -**Solution**: Profile-guided optimization of hot loops with unrolling or vectorization where appropriate. - -## Performance Impact Assessment - -| Issue | Priority | Frequency | Impact per Operation | Overall Impact | -|-------|----------|-----------|---------------------|----------------| -| Instruction Cache Reallocation | HIGH | Every oversized block | High | HIGH | -| MicroVU Memory Allocation | MEDIUM | VU program lifecycle | Medium | MEDIUM | -| Redundant Memory Clearing | LOW-MEDIUM | Various operations | Low | LOW-MEDIUM | -| NEON Optimizations | MEDIUM | Graphics processing | Medium | MEDIUM | -| Loop Optimizations | LOW-MEDIUM | Various | Low-Medium | LOW-MEDIUM | - -## Recommendation - -**Immediate Action**: Implement the instruction cache optimization (Issue #1) as it has the highest impact and is straightforward to fix. - -**Future Work**: Address the MicroVU memory allocation patterns and explore additional NEON optimizations for graphics performance improvements. - -## Implementation Notes - -The instruction cache optimization should: -1. Use exponential growth (doubling) to reduce future reallocations -2. Preserve existing cache data during resize operations -3. Maintain the same API and behavior -4. Follow existing error handling patterns in the codebase - -This optimization will significantly reduce malloc/free overhead during block recompilation, which is a critical performance path in the emulator. diff --git a/app/build.gradle b/app/build.gradle index 946edb4..d07601b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,19 +3,19 @@ plugins { } android { - namespace 'kr.co.iefriends.pcsx2' + namespace 'com.izzy2lost.psx2' compileSdk 34 ndkVersion '27.0.12077973' defaultConfig { - applicationId "kr.co.iefriends.pcsx2" + applicationId "com.izzy2lost.psx2" minSdk 26 targetSdk 34 versionCode 1 versionName "1.0" // APK - setProperty("archivesBaseName","PCSX2_${versionCode}_${new Date().format('yyyyMMddHHmm')}") + setProperty("archivesBaseName","PSX2_${versionCode}_${new Date().format('yyyyMMddHHmm')}") externalNativeBuild { cmake { @@ -59,4 +59,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'androidx.documentfile:documentfile:1.0.1' + implementation 'androidx.recyclerview:recyclerview:1.3.2' + implementation 'com.github.bumptech.glide:glide:4.16.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b409b3b..7ad5fa3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,8 @@ android:installLocation="preferExternal" tools:ignore="MissingLeanbackLauncher"> + + @@ -38,16 +40,16 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="false" - android:resizeableActivity="false" - android:theme="@style/Theme.PCSX2" + android:theme="@style/Theme.PSX2" tools:targetApi="31"> diff --git a/app/src/main/cpp/3rdparty/SDL3/src/core/android/SDL_android.c b/app/src/main/cpp/3rdparty/SDL3/src/core/android/SDL_android.c index f33b022..691e392 100644 --- a/app/src/main/cpp/3rdparty/SDL3/src/core/android/SDL_android.c +++ b/app/src/main/cpp/3rdparty/SDL3/src/core/android/SDL_android.c @@ -45,7 +45,7 @@ #include #include -#define SDL_JAVA_PREFIX kr_co_iefriends_pcsx2 +#define SDL_JAVA_PREFIX com_izzy2lost_psx2 #define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function) #define CONCAT2(prefix, class, function) Java_##prefix##_##class##_##function #define SDL_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function) @@ -559,8 +559,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) // register_methods(env, "kr/co/iefriends/pcsx2/SDLActivity", SDLActivity_tab, SDL_arraysize(SDLActivity_tab)); // register_methods(env, "kr/co/iefriends/pcsx2/SDLInputConnection", SDLInputConnection_tab, SDL_arraysize(SDLInputConnection_tab)); // register_methods(env, "kr/co/iefriends/pcsx2/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab)); - register_methods(env, "kr/co/iefriends/pcsx2/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab)); - register_methods(env, "kr/co/iefriends/pcsx2/HIDDeviceManager", HIDDeviceManager_tab, SDL_arraysize(HIDDeviceManager_tab)); + register_methods(env, "com/izzy2lost/psx2/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab)); + register_methods(env, "com/izzy2lost/psx2/HIDDeviceManager", HIDDeviceManager_tab, SDL_arraysize(HIDDeviceManager_tab)); return JNI_VERSION_1_4; } diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index a6581ab..3fad007 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -4,9 +4,11 @@ #include "PrecompiledHeader.h" #include "common/StringUtil.h" #include "common/FileSystem.h" +#include "common/Error.h" #include "common/ZipHelpers.h" #include "pcsx2/GS.h" #include "pcsx2/VMManager.h" +#include "CDVD/CDVD.h" #include "PerformanceMetrics.h" #include "GameList.h" #include "GS/GSPerfMon.h" @@ -14,6 +16,7 @@ #include "ImGui/ImGuiManager.h" #include "common/Path.h" #include "common/MemorySettingsInterface.h" +#include "pcsx2/INISettingsInterface.h" #include "SIO/Pad/Pad.h" #include "Input/InputManager.h" #include "ImGui/ImGuiFullscreen.h" @@ -32,6 +35,10 @@ int s_window_height = 0; ANativeWindow* s_window = nullptr; static MemorySettingsInterface s_settings_interface; +static int s_pending_renderer = -1; // -1 = none; else 12=OpenGL,13=SW,14=Vulkan + +// Fallback JNI access for content:// when SDL's Android env is not yet ready +// (no JNI fallback) //// std::string GetJavaString(JNIEnv *env, jstring jstr) { @@ -44,9 +51,122 @@ std::string GetJavaString(JNIEnv *env, jstring jstr) { return cpp_string; } +static void ApplyPerGameSettingsForPath(const std::string& game_path) +{ + // Determine serial via CDVD using the same path the core will open + Error error; + std::string serial; + auto* prev = CDVD; + CDVD = &CDVDapi_Iso; + if (CDVD->open(game_path, &error)) + { + (void)DoCDVDdetectDiskType(); + cdvdGetDiscInfo(&serial, nullptr, nullptr, nullptr, nullptr); + DoCDVDclose(); + } + CDVD = prev; + + if (serial.empty()) + return; + + // Build settings path and load + const std::string settings_dir = Path::Combine(EmuFolders::DataRoot, "gamesettings"); + const std::string settings_path = Path::Combine(settings_dir, serial + ".ini"); + INISettingsInterface per_game(settings_path); + if (!per_game.Load()) + return; + + // Map known keys into our in-memory settings layer and apply where possible + std::string s; + float fval = 0.0f; + bool bval = false; + + if (per_game.GetStringValue("EmuCore/GS", "Renderer", &s)) + { + s_settings_interface.SetStringValue("EmuCore/GS", "Renderer", s.c_str()); + // Defer actual renderer switch until VM is initialized + int rend = -1; + if (StringUtil::Strcasecmp(s.c_str(), "OpenGL") == 0) rend = 12; + else if (StringUtil::Strcasecmp(s.c_str(), "Software") == 0) rend = 13; + else if (StringUtil::Strcasecmp(s.c_str(), "Vulkan") == 0) rend = 14; + if (rend >= 0) + s_pending_renderer = rend; + } + if (per_game.GetFloatValue("EmuCore/GS", "upscale_multiplier", &fval)) + s_settings_interface.SetFloatValue("EmuCore/GS", "upscale_multiplier", fval); + int abl_int = -1; + if (per_game.GetIntValue("EmuCore/GS", "accurate_blending_unit", &abl_int)) + { + s_settings_interface.SetStringValue("EmuCore/GS", "accurate_blending_unit", StringUtil::StdStringFromFormat("%d", abl_int).c_str()); + } + else if (per_game.GetStringValue("EmuCore/GS", "accurate_blending_unit", &s)) + { + int lvl = 1; + if (StringUtil::Strcasecmp(s.c_str(), "Minimum") == 0) lvl = 0; + else if (StringUtil::Strcasecmp(s.c_str(), "Basic") == 0) lvl = 1; + else if (StringUtil::Strcasecmp(s.c_str(), "Medium") == 0) lvl = 2; + else if (StringUtil::Strcasecmp(s.c_str(), "High") == 0) lvl = 3; + else if (StringUtil::Strcasecmp(s.c_str(), "Full") == 0) lvl = 4; + else if (StringUtil::Strcasecmp(s.c_str(), "Maximum") == 0) lvl = 5; + s_settings_interface.SetStringValue("EmuCore/GS", "accurate_blending_unit", StringUtil::StdStringFromFormat("%d", lvl).c_str()); + } + + if (per_game.GetBoolValue("EmuCore", "EnableWideScreenPatches", &bval)) + s_settings_interface.SetBoolValue("EmuCore", "EnableWideScreenPatches", bval); + if (per_game.GetBoolValue("EmuCore", "EnableNoInterlacingPatches", &bval)) + s_settings_interface.SetBoolValue("EmuCore", "EnableNoInterlacingPatches", bval); + if (per_game.GetBoolValue("EmuCore", "EnablePatches", &bval)) + s_settings_interface.SetBoolValue("EmuCore", "EnablePatches", bval); + if (per_game.GetBoolValue("EmuCore", "EnableCheats", &bval)) + s_settings_interface.SetBoolValue("EmuCore", "EnableCheats", bval); +} + extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_initialize(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_setHudVisible(JNIEnv* env, jclass clazz, jboolean p_visible) +{ + const bool visible = (p_visible == JNI_TRUE); + MemorySettingsInterface& si = s_settings_interface; + + // Toggle most HUD/OSD elements together + si.SetBoolValue("EmuCore/GS", "OsdShowSpeed", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowFPS", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowVPS", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowCPU", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowGPU", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowResolution", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowGSStats", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowIndicators", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowSettings", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowInputs", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowFrameTimes", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowVersion", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowHardwareInfo", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowVideoCapture", visible); + si.SetBoolValue("EmuCore/GS", "OsdShowInputRec", visible); + + // Apply changes to the running VM/renderer if active + VMManager::ApplySettings(); + if (MTGS::IsOpen()) + MTGS::ApplySettings(); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_setBlendingAccuracy(JNIEnv* env, jclass, jint level) +{ + // level: 0..5 -> numeric string + if (level < 0) level = 0; if (level > 5) level = 5; + s_settings_interface.SetStringValue("EmuCore/GS", "accurate_blending_unit", StringUtil::StdStringFromFormat("%d", level).c_str()); + if (VMManager::HasValidVM()) + VMManager::ApplySettings(); + if (MTGS::IsOpen()) + MTGS::ApplySettings(); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_initialize(JNIEnv *env, jclass clazz, jstring p_szpath, jint p_apiVer) { std::string _szPath = GetJavaString(env, p_szpath); EmuFolders::AppRoot = _szPath; @@ -62,6 +182,11 @@ Java_kr_co_iefriends_pcsx2_NativeApp_initialize(JNIEnv *env, jclass clazz, MemorySettingsInterface &si = s_settings_interface; Host::Internal::SetBaseSettingsLayer(&si); + // Initialize emulator folders and ensure they exist (including GameSettings) + EmuFolders::SetDefaults(si); + EmuFolders::LoadConfig(si); + EmuFolders::EnsureFoldersExist(); + VMManager::SetDefaultSettings(si, true, true, true, true, true); // complete as quickly as possible @@ -85,10 +210,22 @@ Java_kr_co_iefriends_pcsx2_NativeApp_initialize(JNIEnv *env, jclass clazz, si.SetBoolValue("Logging", "EnableTimestamps", true); si.SetBoolValue("Logging", "EnableVerbose", true); - // and show some stats :) - si.SetBoolValue("EmuCore/GS", "OsdShowFPS", true); - si.SetBoolValue("EmuCore/GS", "OsdShowResolution", true); - si.SetBoolValue("EmuCore/GS", "OsdShowGSStats", true); + // Default to a clean screen: hide HUD/OSD overlays by default + si.SetBoolValue("EmuCore/GS", "OsdShowSpeed", false); + si.SetBoolValue("EmuCore/GS", "OsdShowFPS", false); + si.SetBoolValue("EmuCore/GS", "OsdShowVPS", false); + si.SetBoolValue("EmuCore/GS", "OsdShowCPU", false); + si.SetBoolValue("EmuCore/GS", "OsdShowGPU", false); + si.SetBoolValue("EmuCore/GS", "OsdShowResolution", false); + si.SetBoolValue("EmuCore/GS", "OsdShowGSStats", false); + si.SetBoolValue("EmuCore/GS", "OsdShowIndicators", false); + si.SetBoolValue("EmuCore/GS", "OsdShowSettings", false); + si.SetBoolValue("EmuCore/GS", "OsdShowInputs", false); + si.SetBoolValue("EmuCore/GS", "OsdShowFrameTimes", false); + si.SetBoolValue("EmuCore/GS", "OsdShowVersion", false); + si.SetBoolValue("EmuCore/GS", "OsdShowHardwareInfo", false); + si.SetBoolValue("EmuCore/GS", "OsdShowVideoCapture", false); + si.SetBoolValue("EmuCore/GS", "OsdShowInputRec", false); // // remove memory cards, so we don't have sharing violations // for (u32 i = 0; i < 2; i++) @@ -103,7 +240,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_initialize(JNIEnv *env, jclass clazz, extern "C" JNIEXPORT jstring JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_getGameTitle(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_getGameTitle(JNIEnv *env, jclass clazz, jstring p_szpath) { std::string _szPath = GetJavaString(env, p_szpath); @@ -122,41 +259,88 @@ Java_kr_co_iefriends_pcsx2_NativeApp_getGameTitle(JNIEnv *env, jclass clazz, extern "C" JNIEXPORT jstring JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_getGameSerial(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_getCurrentGameSerial(JNIEnv *env, jclass clazz) { std::string ret = VMManager::GetDiscSerial(); return env->NewStringUTF(ret.c_str()); } extern "C" JNIEXPORT jfloat JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_getFPS(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_getFPS(JNIEnv *env, jclass clazz) { return (jfloat)PerformanceMetrics::GetFPS(); } extern "C" JNIEXPORT jstring JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_getPauseGameTitle(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_getPauseGameTitle(JNIEnv *env, jclass clazz) { std::string ret = VMManager::GetTitle(true); return env->NewStringUTF(ret.c_str()); } extern "C" JNIEXPORT jstring JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_getPauseGameSerial(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_getPauseGameSerial(JNIEnv *env, jclass clazz) { std::string ret = StringUtil::StdStringFromFormat("%s (%08X)", VMManager::GetDiscSerial().c_str(), VMManager::GetDiscCRC()); return env->NewStringUTF(ret.c_str()); } +extern "C" +JNIEXPORT jstring JNICALL +Java_com_izzy2lost_psx2_NativeApp_getGameSerial(JNIEnv* env, jclass, jstring p_uri) +{ + if (!p_uri) + return env->NewStringUTF(""); + std::string path = GetJavaString(env, p_uri); + + // Direct CDVD open to support content:// URIs for ISO/CHD + Error error; + std::string serial; + auto* prev = CDVD; + CDVD = &CDVDapi_Iso; + if (CDVD->open(path, &error)) + { + (void)DoCDVDdetectDiskType(); + cdvdGetDiscInfo(&serial, nullptr, nullptr, nullptr, nullptr); + DoCDVDclose(); + } + CDVD = prev; + return env->NewStringUTF(serial.c_str()); +} + +extern "C" +JNIEXPORT jstring JNICALL +Java_com_izzy2lost_psx2_NativeApp_getGameCrc(JNIEnv* env, jclass, jstring p_uri) +{ + if (!p_uri) + return env->NewStringUTF(""); + std::string path = GetJavaString(env, p_uri); + + Error error; + u32 crc = 0; + auto* prev = CDVD; + CDVD = &CDVDapi_Iso; + if (CDVD->open(path, &error)) + { + (void)DoCDVDdetectDiskType(); + cdvdGetDiscInfo(nullptr, nullptr, nullptr, &crc, nullptr); + DoCDVDclose(); + } + CDVD = prev; + + const std::string crc_hex = (crc != 0) ? StringUtil::StdStringFromFormat("%08X", crc) : std::string(""); + return env->NewStringUTF(crc_hex.c_str()); +} + extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_setPadVibration(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_setPadVibration(JNIEnv *env, jclass clazz, jboolean p_isOnOff) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_setPadButton(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_setPadButton(JNIEnv *env, jclass clazz, jint p_key, jint p_range, jboolean p_keyPressed) { PadDualshock2::Inputs _key; switch (p_key) { @@ -191,66 +375,305 @@ Java_kr_co_iefriends_pcsx2_NativeApp_setPadButton(JNIEnv *env, jclass clazz, } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_resetKeyStatus(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_resetKeyStatus(JNIEnv *env, jclass clazz) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_setEnableCheats(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_setEnableCheats(JNIEnv *env, jclass clazz, jboolean p_isonoff) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_setAspectRatio(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_setAspectRatio(JNIEnv *env, jclass clazz, jint p_type) { + // AspectRatio values: 0=Stretch, 1=Auto 4:3/3:2, 2=4:3, 3=16:9, 4=10:7 + const char* aspect_ratio_names[] = { + "Stretch", + "Auto 4:3/3:2", + "4:3", + "16:9", + "10:7" + }; + + if (p_type >= 0 && p_type < 5) { + s_settings_interface.SetStringValue("EmuCore/GS", "AspectRatio", aspect_ratio_names[p_type]); + + // Apply settings immediately if emulation is running + if (VMManager::HasValidVM()) { + VMManager::ApplySettings(); + } + } } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_speedhackLimitermode(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_speedhackLimitermode(JNIEnv *env, jclass clazz, jint p_value) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_speedhackEecyclerate(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_speedhackEecyclerate(JNIEnv *env, jclass clazz, jint p_value) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_speedhackEecycleskip(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_speedhackEecycleskip(JNIEnv *env, jclass clazz, jint p_value) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_renderUpscalemultiplier(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_renderUpscalemultiplier(JNIEnv *env, jclass clazz, jfloat p_value) { + if (p_value < 1.0f) p_value = 1.0f; // Ensure minimum 1x + if (p_value > 12.0f) p_value = 12.0f; // Cap at maximum 12x + + s_settings_interface.SetFloatValue("EmuCore/GS", "upscale_multiplier", p_value); + + // Apply the settings immediately if emulation is running + if (VMManager::HasValidVM()) { + VMManager::ApplySettings(); + } } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_renderMipmap(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_setWidescreenPatches(JNIEnv *env, jclass clazz, + jboolean p_enabled) { + s_settings_interface.SetBoolValue("EmuCore", "EnableWideScreenPatches", p_enabled); + + // Apply the settings immediately if emulation is running + if (VMManager::HasValidVM()) { + VMManager::ApplySettings(); + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_setNoInterlacingPatches(JNIEnv *env, jclass clazz, + jboolean p_enabled) { + s_settings_interface.SetBoolValue("EmuCore", "EnableNoInterlacingPatches", p_enabled); + + // Apply the settings immediately if emulation is running + if (VMManager::HasValidVM()) { + VMManager::ApplySettings(); + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_setLoadTextures(JNIEnv *env, jclass clazz, + jboolean p_enabled) { + s_settings_interface.SetBoolValue("EmuCore/GS", "LoadTextureReplacements", p_enabled); + + // Apply the settings immediately if emulation is running + if (VMManager::HasValidVM()) { + VMManager::ApplySettings(); + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_setAsyncTextureLoading(JNIEnv *env, jclass clazz, + jboolean p_enabled) { + s_settings_interface.SetBoolValue("EmuCore/GS", "LoadTextureReplacementsAsync", p_enabled); + + // Apply the settings immediately if emulation is running + if (VMManager::HasValidVM()) { + VMManager::ApplySettings(); + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_saveGameSettings(JNIEnv *env, jclass clazz, jstring p_filename, + jint p_blending_accuracy, jint p_renderer, + jint p_resolution, jboolean p_widescreen_patches, + jboolean p_no_interlacing_patches, jboolean p_enable_patches, + jboolean p_enable_cheats) +{ + if (!p_filename) + return; + + const char* filename_chars = env->GetStringUTFChars(p_filename, nullptr); + if (!filename_chars) + return; + + // Use DataRoot directly for game settings to ensure write permissions + std::string settings_dir = Path::Combine(EmuFolders::DataRoot, "gamesettings"); + std::string settings_path = Path::Combine(settings_dir, filename_chars); + env->ReleaseStringUTFChars(p_filename, filename_chars); + + // Debug logging + printf("PCSX2: Saving game settings to: %s\n", settings_path.c_str()); + printf("PCSX2: Settings directory: %s\n", settings_dir.c_str()); + printf("PCSX2: Blending: %d, Renderer: %d, Resolution: %d\n", p_blending_accuracy, p_renderer, p_resolution); + printf("PCSX2: Widescreen: %d, NoInterlacing: %d, Patches: %d, Cheats: %d\n", + p_widescreen_patches, p_no_interlacing_patches, p_enable_patches, p_enable_cheats); + + // Ensure directory exists + FileSystem::CreateDirectoryPath(settings_dir.c_str(), false); + + // Build and write INI content directly to avoid any ambiguous formatting + const char* renderers[] = {"Auto", "Vulkan", "OpenGL", "Software"}; + + std::string ini; + ini.reserve(512); + ini += "[EmuCore/GS]\n"; + // Renderer + if (p_renderer >= 0 && p_renderer < 4) + ini += std::string("Renderer=") + renderers[p_renderer] + "\n"; + // Resolution scale (1..8) + if (p_resolution >= 0 && p_resolution <= 7) + { + float multiplier = 1.0f + (float)p_resolution; + ini += "upscale_multiplier=" + StringUtil::StdStringFromFormat("%.2f", multiplier) + "\n"; + } + // Blending accuracy as numeric (0..5) + if (p_blending_accuracy >= 0 && p_blending_accuracy < 6) + ini += std::string("accurate_blending_unit=") + StringUtil::StdStringFromFormat("%d", p_blending_accuracy) + "\n"; + + ini += "\n[EmuCore]\n"; + ini += std::string("EnableWideScreenPatches=") + (p_widescreen_patches ? "true" : "false") + "\n"; + ini += std::string("EnableNoInterlacingPatches=") + (p_no_interlacing_patches ? "true" : "false") + "\n"; + ini += std::string("EnablePatches=") + (p_enable_patches ? "true" : "false") + "\n"; + ini += std::string("EnableCheats=") + (p_enable_cheats ? "true" : "false") + "\n"; + + const bool ok = FileSystem::WriteStringToFile(settings_path.c_str(), ini); + printf("PCSX2: Settings write %s: %s\n", ok ? "OK" : "FAILED", settings_path.c_str()); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_saveGameSettingsToPath(JNIEnv *env, jclass clazz, jstring p_full_path, + jint p_blending_accuracy, jint p_renderer, + jint p_resolution, jboolean p_widescreen_patches, + jboolean p_no_interlacing_patches, jboolean p_enable_patches, + jboolean p_enable_cheats) +{ + if (!p_full_path) + return; + + const char* path_chars = env->GetStringUTFChars(p_full_path, nullptr); + if (!path_chars) + return; + + std::string settings_path(path_chars); + env->ReleaseStringUTFChars(p_full_path, path_chars); + + // Debug logging + printf("PCSX2: Saving game settings to full path: %s\n", settings_path.c_str()); + printf("PCSX2: Blending: %d, Renderer: %d, Resolution: %d\n", p_blending_accuracy, p_renderer, p_resolution); + printf("PCSX2: Widescreen: %d, NoInterlacing: %d, Patches: %d, Cheats: %d\n", + p_widescreen_patches, p_no_interlacing_patches, p_enable_patches, p_enable_cheats); + + // Ensure parent directory exists + std::string parent_dir(Path::GetDirectory(settings_path)); + printf("PCSX2: Parent directory: %s\n", parent_dir.c_str()); + bool dir_created = FileSystem::CreateDirectoryPath(parent_dir.c_str(), false); + printf("PCSX2: Directory creation result: %s\n", dir_created ? "SUCCESS" : "FAILED"); + + // Check if we can write to the directory + bool can_write = FileSystem::DirectoryExists(parent_dir.c_str()); + printf("PCSX2: Directory exists: %s\n", can_write ? "YES" : "NO"); + + INISettingsInterface game_settings(settings_path); + + // Blending accuracy (0=Minimum, 1=Basic, 2=Medium, 3=High, 4=Full, 5=Maximum) + const char* blend_levels[] = {"Minimum", "Basic", "Medium", "High", "Full", "Maximum"}; + if (p_blending_accuracy >= 0 && p_blending_accuracy < 6) { + game_settings.SetStringValue("EmuCore/GS", "accurate_blending_unit", blend_levels[p_blending_accuracy]); + } + + // Renderer (0=Auto, 1=Vulkan, 2=OpenGL, 3=Software) + const char* renderers[] = {"Auto", "Vulkan", "OpenGL", "Software"}; + if (p_renderer >= 0 && p_renderer < 4) { + game_settings.SetStringValue("EmuCore/GS", "Renderer", renderers[p_renderer]); + } + + // Resolution multiplier (same as global scale entries) + if (p_resolution >= 0 && p_resolution <= 7) { + float multiplier = 1.0f + (float)p_resolution; + game_settings.SetFloatValue("EmuCore/GS", "upscale_multiplier", multiplier); + } + + // Patches + game_settings.SetBoolValue("EmuCore", "EnableWideScreenPatches", p_widescreen_patches); + game_settings.SetBoolValue("EmuCore", "EnableNoInterlacingPatches", p_no_interlacing_patches); + game_settings.SetBoolValue("EmuCore", "EnablePatches", p_enable_patches); + game_settings.SetBoolValue("EmuCore", "EnableCheats", p_enable_cheats); + + // Test basic file write first + std::FILE* test_file = std::fopen(settings_path.c_str(), "w"); + if (test_file) { + fprintf(test_file, "# Test file write\n"); + std::fclose(test_file); + printf("PCSX2: Basic file write test: SUCCESS\n"); + } else { + printf("PCSX2: Basic file write test: FAILED - errno: %d\n", errno); + return; + } + + bool save_result = game_settings.Save(); + printf("PCSX2: Settings save result: %s\n", save_result ? "SUCCESS" : "FAILED"); + + // Check if file actually exists and has content + if (FileSystem::FileExists(settings_path.c_str())) { + s64 file_size = FileSystem::GetPathFileSize(settings_path.c_str()); + printf("PCSX2: File exists with size: %lld bytes\n", file_size); + } else { + printf("PCSX2: File does not exist after save attempt\n"); + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_deleteGameSettings(JNIEnv *env, jclass clazz, jstring p_filename) +{ + if (!p_filename) + return; + + const char* filename_chars = env->GetStringUTFChars(p_filename, nullptr); + if (!filename_chars) + return; + + // Use DataRoot directly for game settings to ensure write permissions + std::string settings_dir = Path::Combine(EmuFolders::DataRoot, "gamesettings"); + std::string settings_path = Path::Combine(settings_dir, filename_chars); + env->ReleaseStringUTFChars(p_filename, filename_chars); + + if (FileSystem::FileExists(settings_path.c_str())) { + FileSystem::DeleteFilePath(settings_path.c_str()); + } +} + + + +extern "C" +JNIEXPORT void JNICALL +Java_com_izzy2lost_psx2_NativeApp_renderMipmap(JNIEnv *env, jclass clazz, jint p_value) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_renderHalfpixeloffset(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_renderHalfpixeloffset(JNIEnv *env, jclass clazz, jint p_value) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_renderPreloading(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_renderPreloading(JNIEnv *env, jclass clazz, jint p_value) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_renderGpu(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_renderGpu(JNIEnv *env, jclass clazz, jint p_value) { EmuConfig.GS.Renderer = static_cast(p_value); if(MTGS::IsOpen()) { @@ -260,12 +683,12 @@ Java_kr_co_iefriends_pcsx2_NativeApp_renderGpu(JNIEnv *env, jclass clazz, extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_onNativeSurfaceCreated(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_onNativeSurfaceCreated(JNIEnv *env, jclass clazz) { } extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_onNativeSurfaceChanged(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_onNativeSurfaceChanged(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height) { if(s_window) { ANativeWindow_release(s_window); @@ -287,7 +710,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_onNativeSurfaceChanged(JNIEnv *env, jclass extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_onNativeSurfaceDestroyed(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_onNativeSurfaceDestroyed(JNIEnv *env, jclass clazz) { if(s_window) { ANativeWindow_release(s_window); s_window = nullptr; @@ -388,7 +811,7 @@ int FileSystem::OpenFDFileContent(const char* filename) if(env == nullptr) { return -1; } - jclass NativeApp = env->FindClass("kr/co/iefriends/pcsx2/NativeApp"); + jclass NativeApp = env->FindClass("com/izzy2lost/psx2/NativeApp"); jmethodID openContentUri = env->GetStaticMethodID(NativeApp, "openContentUri", "(Ljava/lang/String;)I"); jstring j_filename = env->NewStringUTF(filename); @@ -399,7 +822,7 @@ int FileSystem::OpenFDFileContent(const char* filename) extern "C" JNIEXPORT jboolean JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_runVMThread(JNIEnv *env, jclass clazz, +Java_com_izzy2lost_psx2_NativeApp_runVMThread(JNIEnv *env, jclass clazz, jstring p_szpath) { std::string _szPath = GetJavaString(env, p_szpath); @@ -416,6 +839,9 @@ Java_kr_co_iefriends_pcsx2_NativeApp_runVMThread(JNIEnv *env, jclass clazz, VMBootParameters boot_params; boot_params.filename = _szPath; + // Apply per-game settings (if any) before applying core settings + ApplyPerGameSettingsForPath(_szPath); + if (!VMManager::Internal::CPUThreadInitialize()) { VMManager::Internal::CPUThreadShutdown(); } @@ -425,6 +851,14 @@ Java_kr_co_iefriends_pcsx2_NativeApp_runVMThread(JNIEnv *env, jclass clazz, if (VMManager::Initialize(boot_params)) { + // If a per-game renderer was requested, apply it now that VM is up. + if (s_pending_renderer >= 0) + { + EmuConfig.GS.Renderer = static_cast(s_pending_renderer); + s_pending_renderer = -1; + if (MTGS::IsOpen()) + MTGS::ApplySettings(); + } VMState _vmState = VMState::Running; VMManager::SetState(_vmState); //// @@ -451,7 +885,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_runVMThread(JNIEnv *env, jclass clazz, extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_pause(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_pause(JNIEnv *env, jclass clazz) { std::thread([] { VMManager::SetPaused(true); }).detach(); @@ -459,7 +893,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_pause(JNIEnv *env, jclass clazz) { extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_resume(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_resume(JNIEnv *env, jclass clazz) { std::thread([] { VMManager::SetPaused(false); }).detach(); @@ -467,7 +901,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_resume(JNIEnv *env, jclass clazz) { extern "C" JNIEXPORT void JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_shutdown(JNIEnv *env, jclass clazz) { +Java_com_izzy2lost_psx2_NativeApp_shutdown(JNIEnv *env, jclass clazz) { std::thread([] { VMManager::SetState(VMState::Stopping); }).detach(); @@ -476,7 +910,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_shutdown(JNIEnv *env, jclass clazz) { extern "C" JNIEXPORT jboolean JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_saveStateToSlot(JNIEnv *env, jclass clazz, jint p_slot) { +Java_com_izzy2lost_psx2_NativeApp_saveStateToSlot(JNIEnv *env, jclass clazz, jint p_slot) { if (!VMManager::HasValidVM()) { return false; } @@ -508,7 +942,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_saveStateToSlot(JNIEnv *env, jclass clazz, extern "C" JNIEXPORT jboolean JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_loadStateFromSlot(JNIEnv *env, jclass clazz, jint p_slot) { +Java_com_izzy2lost_psx2_NativeApp_loadStateFromSlot(JNIEnv *env, jclass clazz, jint p_slot) { if (!VMManager::HasValidVM()) { return false; } @@ -542,7 +976,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_loadStateFromSlot(JNIEnv *env, jclass clazz extern "C" JNIEXPORT jstring JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_getGamePathSlot(JNIEnv *env, jclass clazz, jint p_slot) { +Java_com_izzy2lost_psx2_NativeApp_getGamePathSlot(JNIEnv *env, jclass clazz, jint p_slot) { std::string _filename = VMManager::GetSaveStateFileName(VMManager::GetDiscSerial().c_str(), VMManager::GetDiscCRC(), p_slot); if(!_filename.empty()) { return env->NewStringUTF(_filename.c_str()); @@ -552,7 +986,7 @@ Java_kr_co_iefriends_pcsx2_NativeApp_getGamePathSlot(JNIEnv *env, jclass clazz, extern "C" JNIEXPORT jbyteArray JNICALL -Java_kr_co_iefriends_pcsx2_NativeApp_getImageSlot(JNIEnv *env, jclass clazz, jint p_slot) { +Java_com_izzy2lost_psx2_NativeApp_getImageSlot(JNIEnv *env, jclass clazz, jint p_slot) { jbyteArray retArr = nullptr; std::string _filename = VMManager::GetSaveStateFileName(VMManager::GetDiscSerial().c_str(), VMManager::GetDiscCRC(), p_slot); diff --git a/app/src/main/cpp/pcsx2/CDVD/ChdFileReader.cpp b/app/src/main/cpp/pcsx2/CDVD/ChdFileReader.cpp index 20033ee..0c929a1 100644 --- a/app/src/main/cpp/pcsx2/CDVD/ChdFileReader.cpp +++ b/app/src/main/cpp/pcsx2/CDVD/ChdFileReader.cpp @@ -16,6 +16,10 @@ #include "fmt/format.h" #include "xxhash.h" +#ifndef _WIN32 +#include +#endif + static constexpr u32 MAX_PARENTS = 32; // Surely someone wouldn't be insane enough to go beyond this... static std::vector> s_chd_hash_cache; // static std::recursive_mutex s_chd_hash_cache_mutex; @@ -366,17 +370,43 @@ static chd_file* OpenCHD(const std::string& filename, FileSystem::ManagedCFilePt bool ChdFileReader::Open2(std::string filename, Error* error) { - Close2(); + Close2(); - m_filename = std::move(filename); + m_filename = std::move(filename); - auto fp = FileSystem::OpenManagedSharedCFile(m_filename.c_str(), "rb", FileSystem::FileShareMode::DenyWrite, error); - if (!fp) - return false; + FileSystem::ManagedCFilePtr fp; + // Support Android Storage Access Framework content URIs (e.g., from the picker) + if (m_filename.rfind("content://", 0) == 0) + { + const int fd = FileSystem::OpenFDFileContent(m_filename.c_str()); + if (fd < 0) + { + Error::SetStringView(error, "Failed to open CHD content URI."); + return false; + } - ChdFile = OpenCHD(m_filename, std::move(fp), error, 0); - if (!ChdFile) - return false; + std::FILE* f = fdopen(fd, "rb"); + if (!f) + { +#ifndef _WIN32 + close(fd); +#endif + Error::SetStringView(error, "Failed to create file stream for CHD content URI."); + return false; + } + + fp.reset(f); + } + else + { + fp = FileSystem::OpenManagedSharedCFile(m_filename.c_str(), "rb", FileSystem::FileShareMode::DenyWrite, error); + if (!fp) + return false; + } + + ChdFile = OpenCHD(m_filename, std::move(fp), error, 0); + if (!ChdFile) + return false; const chd_header* chd_header = chd_get_header(ChdFile); hunk_size = chd_header->hunkbytes; diff --git a/app/src/main/cpp/pcsx2/x86/microVU.cpp b/app/src/main/cpp/pcsx2/x86/microVU.cpp index e6cf3df..23ac7d8 100644 --- a/app/src/main/cpp/pcsx2/x86/microVU.cpp +++ b/app/src/main/cpp/pcsx2/x86/microVU.cpp @@ -122,24 +122,46 @@ __fi void mVUclear(mV, u32 addr, u32 size) //------------------------------------------------------------------ // Deletes a program +// Simple reuse pool for microProgram allocations to avoid frequent aligned malloc/free. +static std::vector s_microprog_pool; +static constexpr size_t kMicroProgPoolMax = 128; + __ri void mVUdeleteProg(microVU& mVU, microProgram*& prog) { u32 i, e = (mVU.progSize >> 1); // mVU.progSize / 2 - for (i = 0; i < e; ++i) - { - safe_delete(prog->block[i]); - } - safe_delete(prog->ranges); - safe_aligned_free(prog); + for (i = 0; i < e; ++i) + { + safe_delete(prog->block[i]); + } + safe_delete(prog->ranges); + // Reuse the microProgram object to reduce allocator overhead. + if (s_microprog_pool.size() < kMicroProgPoolMax) + { + s_microprog_pool.push_back(prog); + } + else + { + safe_aligned_free(prog); + } } // Creates a new Micro Program __ri microProgram* mVUcreateProg(microVU& mVU, int startPC) { - auto* prog = (microProgram*)_aligned_malloc(sizeof(microProgram), 64); - memset(prog, 0, sizeof(microProgram)); - prog->idx = mVU.prog.total++; - prog->ranges = new std::deque(); + microProgram* prog = nullptr; + if (!s_microprog_pool.empty()) + { + prog = s_microprog_pool.back(); + s_microprog_pool.pop_back(); + std::memset(prog, 0, sizeof(microProgram)); + } + else + { + prog = (microProgram*)_aligned_malloc(sizeof(microProgram), 64); + std::memset(prog, 0, sizeof(microProgram)); + } + prog->idx = mVU.prog.total++; + prog->ranges = new std::deque(); prog->startPC = startPC; if(doWholeProgCompare) mVUcacheProg(mVU, *prog); // Cache Micro Program diff --git a/app/src/main/cpp/pcsx2/x86/microVU.h b/app/src/main/cpp/pcsx2/x86/microVU.h index 4441cf8..1c9cb80 100644 --- a/app/src/main/cpp/pcsx2/x86/microVU.h +++ b/app/src/main/cpp/pcsx2/x86/microVU.h @@ -26,10 +26,11 @@ class microBlockManager; struct microBlockLink { - microBlock block; - microBlockLink* next; + microBlock block; + microBlockLink* next; }; + struct microBlockLinkRef { microBlock* pBlock; @@ -146,6 +147,37 @@ private: std::vector quickLookup; int qListI, fListI; + // Simple free-list pool for microBlockLink to reduce aligned allocations. + static microBlockLink* s_free_links; + static int s_free_link_count; + static constexpr int s_free_link_max = 4096; + + static microBlockLink* allocLink() + { + if (s_free_links) + { + microBlockLink* p = s_free_links; + s_free_links = s_free_links->next; + s_free_link_count--; + return p; + } + return (microBlockLink*)_aligned_malloc(sizeof(microBlockLink), 32); + } + static void freeLink(microBlockLink* p) + { + if (!p) return; + if (s_free_link_count < s_free_link_max) + { + p->next = s_free_links; + s_free_links = p; + s_free_link_count++; + } + else + { + _aligned_free(p); + } + } + public: inline int getFullListCount() const { return fListI; } microBlockManager() @@ -162,14 +194,14 @@ public: microBlockLink* freeI = linkI; safe_delete_array(linkI->block.jumpCache); linkI = linkI->next; - _aligned_free(freeI); + freeLink(freeI); } for (microBlockLink* linkI = fBlockList; linkI != nullptr;) { microBlockLink* freeI = linkI; safe_delete_array(linkI->block.jumpCache); linkI = linkI->next; - _aligned_free(freeI); + freeLink(freeI); } qListI = fListI = 0; qBlockEnd = qBlockList = nullptr; @@ -189,7 +221,7 @@ public: microBlockLink*& blockList = fullCmp ? fBlockList : qBlockList; microBlockLink*& blockEnd = fullCmp ? fBlockEnd : qBlockEnd; - microBlockLink* newBlock = (microBlockLink*)_aligned_malloc(sizeof(microBlockLink), 32); + microBlockLink* newBlock = allocLink(); newBlock->block.jumpCache = nullptr; newBlock->next = nullptr; @@ -266,9 +298,12 @@ public: linkI = linkI->next; } } -}; + }; +// Static members initialization (after class definition to avoid incomplete-type errors) +inline microBlockLink* microBlockManager::s_free_links = nullptr; +inline int microBlockManager::s_free_link_count = 0; -// microVU rec structs + // microVU rec structs //alignas(16) microVU microVU0; //alignas(16) microVU microVU1; diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..05a008480b61010abf1d050f02289f552bb53e75 GIT binary patch literal 44016 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajKFnGE+hE&A8*_&PydiC13 z+6<+Hh2nuMu2t`4g|eg+6 zMrQ`a344!M_wFpa6}Q06v7oT_$Nw^;NqfKD%3f=E-1NCjagY7qm;UuaP8^CYcgk<@ zv~*jY;E4hKVk;w3mk9!khx!0(R*_4@w6SbB04&* z^H=DL3SB?-I5~crm!|d7oF_4w(q_#KMtjvxFMgZ!wDFUFcbD?*ugXv4mO4yiU+Qr* z{mH{=i9wsZyF}JscjdB;X-oQ6)?~M^VCV8D6SrO5ajbbsg{-{hmkDvnqDz(Rk z%?{@;3g`?9vMbwp<<7^&S8h#BT>R{tc2Mor%*v{l>JREUS6Kbd|McLpZcoOiee+uG zcu)BvtKjeV`@lCI`@K)%Kizl`8?>fM`*yX*KaIEML95RGZ2v3Oe7wz0+jX_chP~|* z;ycfNSgrY`AabR^_CwG9NZZWr-kWefbmiuETdu_YU3n#Ls`J`AYqqSY)DElBGOcS$ zpYpVF<-D(-H_2q_Yz;2<)Dw%oa_X$ooP05E-B(Z28eQ+|Jd3^({4Dat`>WHRn7qm2 z{^#yx@}lozXyENq)hFwkC#K)rFf%CkU;V?%zuPO*=12bHycM9Q$S)X>dTI8jjP{D( zzfbBfO1i{Wnf37hlfqx7V)c_Qhd+=CpZnQdGjP9ldd!Kp2hx6oiX3b}u*d#sV) zemR-iKk;`jz0X(jtQYtEv)bd&aizj~!Oou^D!-VgyX<{G$?p9wl}EDETlT-5v|)S9 zCFzzu4=2T~-@W8{wjO8AQB}s{F^~Rl^8VAeYsqiH=_Smu@g<(m59@ujeKN`K{O%=> z9j9-pKd1Wk+wLWA?WS+3yr#PL`>rKV<))k5e>*8{cg&@fcJEnvhu=ph+t@!UX{fEwR!|9K1bbqwu{!sb(;l^BPC#yH{17O&#Wfzw&S}}Tzb6S+;^trw0IwD zXWp~SEq?N`&GzQKzcowN9i5k%yn9>Ao!nEc=O<--{&(4Cxv$y$NfWN?dG6o;XeRr| z`jb=EwV(4W6=g06=LPBbUg} zsejZi!2I5sXOFrN_oDlPFMn_IxDu^2F~7@YkD}1GSsw4hU7g|tIqM>qJP(|>V7d!a zU&oLA8jrp=d3-;tba$Tk%c6iKpBV+`|5cj%t+S*iY{}uciQlSSZoL!A+#I~*D&NFu zw-1I*yf!=We#idx$N#T8S{?TAueQrBy~BpnAKc(}WqSVj^Rq{r(;v&EJ&IY@+2Sww z(wOtP6{Av`&bfM%eaAg&=06m$T=}EV*@bOc2alYBkx67qg#E;aoJxz6T;1Hed%A^Y z3TPT>`lNWRIQrz?kNY#SFQt6*y!@e*?P+7z)!ggTC+v28cxmd>jZ@w}`g|$sEs=?LFpRoqsa8`~UoqSNkKorqo_4(B5q@ZZ)8>^y!aCKT3Q&|lC$W7*%9Cdb1e{Tvzg zPrX0y-xYJ9g5i_&M#)dTZ2zXdGQ7jl(Dr-XY{7#E_8(+Cl-Pei&5+NpN@Px%mfKtB z2VdBmMJ)b`So~>RU_YnMXilBboH~1pzb+PkSuFnUxGT1?;E!R!pNkf5@fQCyWQ>K5 z_cT1_Z-30+{FuM>G6)?!VHeT(m_Mrdq76sG=kL*(mj4|}|4F=LKm4=(z|ZzWKikiK zwy5vP;*!*wOQ3Huh#7MnmO-r?r~*H5uGR}Ir%z+>b?8p|I4T5Oo>)=V(_`z=(KKM(yDZ`1*d3zLa!K^D^g#z{%B}U7Xoxw$z<|8}Bv0e7)Bx_lkS*rv(`fD>4K*o|>JZF2K0= z{vNHf+vgf4ed{bdzQpCQ+_q$)(?6$k&P-ajcH8d<6Q8GsY`Lu?>UVCI=AKT557K|v z&eW_Zx>fy2V7>0o+#5VUmj!fQ3c8i=@{Pyg&+$CTALSDbt2G$vHZr_$W^{Pxd+Ly9 z=*N>&1-?&=X6jq!E&FHxy6kYN9ll)CtIm|& z`7If1rW>|-Z|95aX7@|Jd3yGqpJG^hF}z`A?Po5AxteE$80>`iPkQBTxA91*to-eb zoG01MocC!){0Y>&X?c{R!K&J5$z|{BzUqdbSS=1c*HJW(@~bh4W0!6I%)%VkrfkdA z-sQJg-fDtNi2~=3|341Da8Te&Qs7&}e@N0<=F34tB}G0Z1-?Uye1{bHsvbuyv`EwE zXing0o+rT8D8R}n!1`|idy6AKM{}M4YpkO<2Xj0})4A-A#z!2?I~wIqf7~y?+$g|g ztH{!%$l|2HQsvCia{kcsvj@`sn;1M)nP)bP9YGH6W zAi&WO!qMwt%ZDIe61%*!Lf(()8 z>MKgxw#@t1S~)-ObjkwNpS_?GnDxNoxs#S0JNAAvGh?;PX_w;qH4)k@5% z{={CeKtVBEyrE1i|M1Bq7s1wx&hyP5RK&M4pS#7%u<-(SlA?sHw||LQXJ+u*7$;$al2(#qDN@a#q-KFYzr?#@4Hwxfz-k*s?e-H?6p@wWprd zh4F|(|53}`3{~fjrk`)S&>+CkEYLCI(M?tZyL52}CKm_$j)o@-1(*dGHFuUV^0YHZ zOiXyoBq5ypmT3XQj&{w60IunEF>*D#mfBn~iSR1*G(5K}X@9BODqHh@-dl=O+ilhR z`|AQ%`BeDMoAYa3$lRaeE6u*!_Z+^gTYUAI_W!BPx4z{6koWz!#se&YQ-rVrE-@(Xy(6p{x{#^x6$XF=bvqp_Var2db-b-{}0M9 zE{i{>&Cr&W@ZNBBt^2-eXR9}#k1(J4w|WYvRJ!%Iza9(SPkFZY}q4wA* z6K}b{P*6Ok$FRxm|77R8X}{u|TTR0cD6dsIAw`OS;p5LXCx86JH`>T8Zd^j2YtxxNce^K{jQi<&xwOIb8iAN@=&7JQre7`yK zQL%!rmF;vj+3(9e*)P<^&)QOL=6ma%?_{&;1Mf^Sd86KK%GhY^JL|p2=u1#$-`$RnG14Qq(A(wx^ex{`|J<w~OnT(3Em2*``p2H@9D1&M zApQJt%QJ^}o;g@~_Sny}M=JgNC+A(hwB+{{laI6KsQsIKNhQH)7E7Q1=cXu#@#b!UcIIyKm0sM-GK%Zu-eB z%pZ_?Df;_gK5^ffYd!|OI*@(nCwrf*y0dVs8-q{BR&Iv7tO>pU3U!0ed+)8BKl6FZ z;{Wjt?;I{lnedBDK9o9d!lTmKrxVR;eSf{W)R??9=ZDTpf!%kqew#b~Zr!x-@{*A4 ziWTwl#s(S@dzPr~nzMQL(X6JTGfv?`25ttup&SQHmx`f?# z{$9P_@6EN>eGer3?M+{D@78(V(&Ej}idRmaeRaNfu71|qQ{KL*sc~5v+Iu4M0*fR0 zwoZE%Q0>WAD)u~}d}4d6MLhpk@h3_Do?lYvuAJ}fociwhrId4?Tn)QD8ri$NKi-mE z8uC3cO>39aouZWj`@Q6(H(j@=^o@J`wqR=dG`Z=wD&(Z}ES1lS)oSaC*Xieq&za8m zxo(1Av3}S4IVCS`v}1Rj@tMBP_|nqfUrJsY>c;+Aw!~RvnYRMRtO15! zj?v)Bh7$^Z?(l!x_kQ=0{~lJi-gElf9QgLzOwK`eqMFAltIy}%7|iCEMg{%4Z?Mzp z*s)HZJ)I0Q9-WlDwEn(orq$~uf?Zb5UlO)1`+g_r;@w3F8{K_^RMyT8SaqC3Q`+dN z&d!y^*Y{|NUc9EwdxMGbZ_~+;l`Gejx_PBP-Na;&&-IH{mf?lY+;=+y7}qqjh_Y4X zy!9?g^PB$h%RaHHC3D{1`NwdB=cigU!wc77!40pbT#5PJTN3qeaY@w7iN5Xu>Ayo) z?lf*+ZeX-wna|u~^xC`mwQ5dG)7|HEMyTf%|18b& z_iXXCHw!rq2nVJA6u(q(G%|DBqxLs97hLn!_u74Q`qA`@p)Yqm*!5*bxiizV9ZR0; zZ2xlUNx)^-L(98de8gTF8>VM!l$ESw2|R2d5Rvjg!Qxn>rMJt<&ag!*1Yi6q zWm$1@k=ysu(=#-*lb^h>n^~ck=aF^B)YD^M%C`EpvNo{&hdCPvtZF$64V!iO5FnjNU zI>}jYmIwK+wE5Gplkcn8oC*6@hO2CqzqL<2;6^lqPv-`q)|Y$>`zD{85y2>$z;DiN zX3dc^|6xJNleLCM{B~D;;@vGYXr*enVPMBA+G_cFsmd>aTv(outH~Sm+zR9z)_f1N_Ht*!JE&n|#cT{#f zu2_;f<0kXk>rG4#&IRvCah)vbYu(Cq(R<0K3+ZR=O5Xb|ICg4sQ2kxY%nk3FOk#dF zo5aml^SvH3Y1-=gDce?GytuBY`#sl0z0XofYPs%bRZRtRm#MpMOr>6_JTA zJPW=R{3$H>!&&gBGx-x+d)A~Cjs^dmH!-;c8wANEuy*$O9I|zPZZ9?W|BQ-kmKBYQ zgg?yjIb>`U>0bXUrzO>wv0IQyR;@PEtx<~cU%+FA+od}$+eB>L7~*v^aoHE?Tr1VR zao*Ga)gLXdz3ug7swzXt^79H8{;PH@`d;w%-af^*-=aP;+{&Ehrv9=vM?H8+j%dg7 z)Mvl5-%faNG1Exo{DBWg=FD~4$=Z;|JVzuimFbIo>9sxX_oG+2zmhLq@^keC&F}6} z-wjWzr5;Y7J#o9(+*L+T-@ZB1xzn&oE_aXFAD_El?|bj%73cYS{eOem|7Me@>1}59 zfwR`^)HoZq-1~Fr%S%O1RWr9fnzSXZF7lSnZk^oeag(-n>pT8g?)}>};i2n~btaki z|JgI``OoZSIOqNOXT0~@qw_sW*B7ra`!1g*xzXv3k!E*b)4cuP{O{d=D|ayMgt@sW zgSXwpZ=3g>aK0V6yw~>of7>4^&bL3#X*k&zvAX$Zp9sTt`MG|&zbaxUFZF9aeac;b znwD_<`Ne;>eh7YhHGYw@h4xqTdFrJY!_cA;mo?M+4Zr+4M7SkBeI2(b7Y6Yb*8 zw_#gnpXVkc*R1FEcK0@ihR8aEiB!zrBdNT+a#@q`WQDK~b27AU1jVxd|EHW;6W3W4 zyYv%>cBOB3ZoJoz+2Kq6&tIG3u#>gn<}2=Bdlz{#?wFPzGyT}?H5}hoZ8m+JTK)T@ zY)2nU=}%ADX<|aJjb2)NNOZ1XT!-I|7USzD7~w^ae+IieBai0@qCND z)7SqNH@NOKUq3caOWG#zlYx7+5B|r5hKC`~axj^~Ff@r6!FV}C} z*8D3oj>F$8<1D9OADjF_OU1OrE$<8eUA$bx((tU+V}~=-fhS!bI2!Jp_Y70{al~MH ziTv-iKli0F9iNflcDr=f;S3*F&-p2pMX#!Zl8+W<{(rN*;KOy*pUO4g!#{I1IG*q3 zIgtG)dzbbBUE8=lDXVJidaJ+PcyL#(d&T#Ub6px29bM|8TtC?>^7p=JoA*ump(tr| z#F%@@PNzMr2UxxcJ}!Je`FHF@w_-bUW4m4d*4%da&k%g-C-eQ~1t*Uic=c`CcG2ye z>Ed+Ws;iO9)|9OkjeDK{gX`SiIVSh2Nz1(+bGi%Mtt*ka2eD)QMQ~B(!DF3QHe6#)K zn)&J06-P?C*WBE%@r5~X@|xJhU4O2#*E$|)@!_0V5nAQ5Q)BM(xzoQHpYvK=xqMUY zJiWW2a&;G;T)hz4*!2C_=6vHP(p(JIA`IL6nOeI}G+&KA^zw`O?N9S0{(F@@U!dG* zmF?$!*f%5Rh0LtH{Tge(#Y{K*eL?a?SU?Gz>~^QOi;Z7wxp<0oA?HFLqZLcTj8-I+ zFetuYaB|G|WPbBc z`mr&3lHn!!5|i}|9+n;1&GVDvuPD;Vh+r!%MjPZgM!{OvSkK-rPeeN%G%;q=eH{Geer~kjZOS5>XnsfSl zuTuY`8ufjGlXw1Yvij2|y?>Hg^uEchUXlYTp<^8#W zAF=pM9j^ zXWfh4C9m^m#!X)HyQ(~$C8w>%nK7ZA>B08JuhVv&TYI_jR%hDO#b*nD-Jf-C;nR1g zFXh!QdnvL-XbHbn>$zK+2R@d&r$6XFcc9+%LkY)D>*jc6h7wMNyhY269oQ#y@GX#+ zs<$`!vm`KVp~hr|Q^L#>rmAGEP-jbEVQCQSz9hjgS%Zt|fJo1$D5evC=e!VV5Np0@ z!NALL*>6cn|3f#K%J}W~xp}u-$(>xK*K0m^THN}$Nk{B=FZ~}KwsHL(ri25GG4eMS z-8*JlryU&i?#Hn^-gEa>Zpq#9*=^ebkDXa*E~~FEKX>T-lH*4{s|l2JbRC$@!El$& z;@V|@mY#opS-O`F7{9g?uUgUb_D8+S7WE8E|5 z^4j}en{)4bo}Med!}ZQ1;E>Kc-USCR>{}DwWWiHB>5V1%-P=w`x zM9II$HjWL#i;J2VmRDZ;zxHMQ_7)y%^{>zB)0kxBbH%fFm+jXsxbUhU+yOZC!%>pK_B%CS?{EuS~_T)W=km+4*?v2Js${bC>!&<5;wCDI#o>X~YKai>2fHtJ zapm=Xe>rV|n1$PEU%kwY|7Mv)7kkcHyXhNCw$g%Sr_XyHUV7i-?$YH;k6rT5{ay23 zY?sI)!Anx&fgc(lZOg7Szo_}=c-tGp=Qj%;Yrgz>Dz)>sfb0|Lf`8|qi!%Hzlow9m zK7QZneu?*G-T27+{B!3&EBf)+!q(c2Cw2Yr`4iR!pYz@yvf8iXS>p#W1}TnJryZ0Z z-4QZ*HD{|?&-YjR%Fh-&ul-Xk*n3qoZ1dbHUp$RCD~qPR>6@K8@7eAB2R;6?bJ~o9Rc&~o?@={ggt}T(P z7TL#IWe9C(wfCC-^SY1Fh6Ojnm^Mt1v}k{6p)SfStjKcgaiv`HQHD&u32PJ{L~}N* zad;r;8Mape!keNsyU-$zx8zv4E^&9Y(36TBS$w=i5-pz!}}nCgjhKjvS1 z5fk;sO?|^@UEgi-)7_%$r?^S$doJcpc*`GD{xtm4nV`9C=YGe`a_bOWtM>K6#GYpo z7bccJP5ga!O4`yVzT56R+}`YUQu^$Rz{zu8Y`u7!+}t;liYc$`^m~H#2ldRh?YL-k80hb@~?n zuXg|TwiW)l8a?yvie2IHt=`u%_Fr74f8pCXuYTq5o)fQmkIR?w*vnAfmrO%hXdzHWB+$rOr?4O#G83T-&!mjf)-aFu*sdk{M+u!@KYrN-a z*885vE5(nNzf=7s(|5f-=XG}Kav$}nZ=YO@%ubw^Rs1h;)vuTL zO3q5Jn_JLYv09?T&)5FWqV0k@yWXv9b>(^y@-;Vq3hS8{;nxp!Pg$+yD!*Rcb+y?v zcCD)$wsGCPIq^Wgo9o=Zs8#2FY~HziZCRb}#P@e5>?psexo<`|gUaH^@7nvUKg~GD zl<=j@z}8xBfzlB_!I;O@e}6w)<}h(ln)h_JwOR7llg@<;nZ29k$v)TUND;$5rUVtM z{|glipGK7}v#(|2wvGCD@9#T{^=nG@L{qI!VZpC-LZ=4-0&u=m-EeYdUK?kdyqmy=O|v+A%|)Bf*=vKjvE*PbUSZ{xg6bi>4=(7%>tXN=*^UUNpi;NX*4j=gK% z+JC)}x#iAJU77#9FTZL@*|>EJN@;AneOdF(j-3n~%!(UWUuH1;zb)9e^lx#VMV;@j z&x=j&@-5&0a*rqXtGg9jMb?L1yIgWGD&nZcuX~rDEu4aPT}W-SO%3xrhi25cw2O(MejH3b9q1Un|ggq;0DI?-D)ol z&zqjxd$4KpBXi5USDIhg>!h>Hh-T>V*mkWU_*c5u?acHuzpwm!VYYMk&aI!9Ys9SD z^z;Ywv6T0>*C!^G+D#X`yIm?Jr>w>f-CT0J4)P}by} zGgJOwliazmdacj{W&W9x{JxoU=dR&+5tKN>vkQO7SJnk7x@SsyRP$+i`|xLYIRw zQ=TG&i9pD9ru4Tgl7f>pb<^jV9ltV@Ay#ecw3fWx92+D*@v=6^ay9IE@wH+@lyLBx z!!eUZrFd(+{`AMWK7T*$-U}V23pbL#JuBfcV`MnAJzBY0L00g<#1g9)ZI91SxmLa2 zvpe?Hm;KGc8=IbrgnqD6x_am5GG&ju>DR)$L%#H^%CB$U_5NaR{H~Zqa~JnoAiDYW0PAir)IqQ-`>Hl5%a8Y^ZMBR>)C34 z`Pk?Cvi1u|&dKJo@Mhp?XO>`MG+43UyQ<%3^Sn?FwNe+>cjw|h%y+rIusdvB@}z>u zx8LtC`@8CK{?5nRoK|I#i|!SB$6Ne+vAw|F@R5H@hySV4{(Lot5>5pvmxs?i{@99~ zZ~fn`bhrP&NBMJ8`q+(^{QbAO;E%wU=<}XyL)AZ@yQlkWd8AQQ(JAX|pY9Z_t_}By zP+hu@GeY&vipPK3BV|yc-9+U!|5 zr(`quVdG&FnwPDAMMMAum7&t|8L2cy3f15)QbB(=!j)lQQXn;<29?`gZeMKI@vGu#a*@$Uw!l2 zygv2|fit9^YLUOUOB+UW1EPyRkHzg^qCZ`y?GbyHsO-x2B#tk;pLKXm$CXW!~~HQVpIy4s#Rs|;P1%h{JqJ7tqun=(aq zY-jM0T~U4DuF&KvQ|rs~x#sQ;y!`N*hyi1r>uM8|%nsjrzT@%fXF2a2*H?YBHe2X> z`j)HwM~;+#Ib18%E^lUte-} zv$!wUgFF8YN5!evZ*P0Ya`E$(NcXw5XSj;;Ox`bH>EEfr>AX%_ zaKXs~CLABqm>hze{2S)a5XgJVkZJI~C6ccq>5=p&ho`r;Z??SwZ5B36RaxfJoTqld zZ~t_w{|l1sy;!VzIePv)=6hDccaKRSJ8Q%B&q{Uroh7w`i<_NyxLKcj+Vic{Wx>}S z%lD>Vi|@XgRlogn*5sHME7s(k2$}pjo}1yPP5C_$maap4-+!MI%kW|^f91_09^Y*l zHU|ooW#r$^sCX~R+r)e+QuQwHqVq}3trAatz0D^+=v-`5eQ+;J!?I?%2};Kpx6FJP z`F;Lx{>mMh8~*+%=M>FrJ#ClSl(>1>=Ir&qLT2UvW$OFC>d612x}5S4t@rS&|K~b# zW?@XF+#VK({k35|?o4%kNAD{uJTN_{{vy=e?T`6`AL~75-xa^0Uo-Eq@P{=s=9&w* z&N20uYLPE!)nu&l@52yLYL%(-hMv0npwB) z^N}0L)06`lSDZg!tyJ3|$##!d*gd9iNIIygmn$) zR?FA#Emc=}^7Ztj4;!O@)%N`Us<`j?>aex#YjpbWF)2u@*rsp&IOA}^pY;)r^A4`x zc}B>8IkUL$hx~2w4JR&tThmzG-(TbCRVG)_f41X~*%k3AEAF3VQJHn%;L@s@J>^3+DvYHdVkm6&|{cl(4*Dd_j1+;-pRZ{_m}UK=(_%{Vz<@a z`z_nvt~t}bD}B%5H=l(JJ$-CDmR#Jq>0l*GLmsntBZr67qiG!z!`GD7Z|OaLTyAQ$ z5{KyZkky}8DqPH7yJ+>ZQ;#qGZT(#EXXd0Y=5v0>KYXHihkbv>`Y$uoFDqYAohQ-p z{lj*JC-+Nc+we2$rzdRrAA4?=-JE?l>$)%2OgM@UpI75_3&L($;C2sMP+ltrTJkRxU^_f2D_ve3zLNe~TzuF93 zGiqPITH@Qj|CKSPOaZ==`qA8Rfs(L1>Q)r<4JfrnIsHB@$Qj`-rg+5Z2* z^r*Y%zUqIn4p@*;Sh>Q}|M8bAj3)QKEWN2L_idV~>lAaD9bFEFtVx@eb(C&d zW6X8$`2SeV0EXJwns5KUKl{9Y+JemMKE4&YoU_&C_qa2>5Zj=#$E|OdR;hRf`@j53 zq4)YRujDj7F+X1)EwMSNq-5>>r{DBdKR%Ye^j|(>b-_R8`-i_bILmhh{-1Doy8wgK zb;js7+Skkud?+{B-|W3>?*h+?qHkNGe}DL&X;h!J_X$v4SJ`{? z_|m^q)~7`5iFP|0C91fW^}rVnZ-xxt3Ew8~H~F_jYTZJ%Tld-`I3S&%o$;&=Xj2Q*^oh}x#>|gh~SubijN^hm(&kX}((X;U1I2=7X795B4b=`vfY~#)p>tQu<4P0PIS+vc5lmtElbQ}a4rFEVDld%QONm1Bm|g3fzP3YS)Ktzt^> zS`j_#>?St1rMvhHZ>sO@pX(3HZ6XX3Ghc7ho%`$hxg`(Vf`vXWj83}u-G%eM3&W9v z*M6H$j4NP?<&Sq=v(F5ac`AGHCt8w5B)4CU-d)d|968 zGHm+O(#3H9WBlwxj#k;hF~QCEpWFbiIu&6kd*{x$U}d&x`QEyAw%7hiPu@?8DB=0r z_v|d|d{u@L@rFJI=X+OH-rFPV{&PiZn&!i}h|j9qJDFIQc>G+xxxDoS&qOW;Usjpg z19}W5&M_Kq{^@s?*iA|N)+*g@^fXrU;)30Of0WPoH1YGAuhDaycUL}Ft!VRBTJ~U$ z;V=JhLJZ7`43iqJF>%PPul#3kDEMyHif3^rzbxNe>hDu^xZ^9^Yy0zM{|+0xlK*&V zE$e}VJ>L%3UQY5iRQNR4=jE3Fb0j}LKXd2r?J3537f+o3(#m?H@9hgep)WJ9+H4g6 z`mA0}Ir**|gHO=&_o@ubS|gbf3f_cf8r_!Ch&;G)e`Z4pWF-FkTm}c`>q!Q;PQKix zc)$6%ir8G|JJxZIPI8PB3NEZ-Th6!O?7a6q#uwhNQ&!O}&Gt$xT6@1xL?hoC*Kx>WT+q3oi7)cz!&`5Cr(3P|pS3mE_vx={wQ{Xq!8Lw&Ut5~T ze^~qD{HX~OUM8J7q}D4OENr)(!GmoU*N*EywC9!9ukI;+c&Yz(U+wk_xwCeutzZ2A z9Q!_Fri2sG(?0K?$soaW{Jwmy8RJ{4f9I_}m3#i)o341r{;c1_HODXOwy3`PHOuUb z!`=CA$xK_6R&8Vd+8f2}z}oPuOEZq?z>lr~Z8?S0;TgLgv1{2STj$w98kci_`!a~E zjfvL!dOzu0{ONsm#E3--&^ziue9&=9>HaS9~s?Eyb^OZ7U%}0A-pJwl#``mFFviAR%zjsUrX~TJ z0%kK@P_TVFWh;Zvb?4{4%Qm!ZfB46?(J7M2;X8->-b7c_t#|B_jc)hD=99M z`OX{_xb8~w&PR-PqW>35s)-g@xXm=&{h6y_U(|obLkHe(NNYONa^TOcJ^tDjSziy{ z7T~P)=CTPm#1Y*abA8|H!+*M$>D3$wvH01k^mh-auD#;Z5GA&_R4shzmPw1EEwXZ3gSEk<{?zFFYVyJX*F$Hu4y?yJAN zOy3$=;rQoyn%4`SIoDG-?r>JJHdy_)*mLYb`F_op5({gyrfJJbZ~0t4bL9zrDV84| z>l6;m(_bE`#Bk1zRrzn@0{gmaHVlRJ=ca^N?>|)gOQ-ru{-;8dx0y+tC7H*6|9CQe z@oh!zBT>6kUg`X}@`GzF>jO}kcl5;x>FuZEVT}Y8hLT%xpX0I_0=nO(pZS&W$a-F` z(@qwL|IOFr5=9w&w>=4|)ehUOy!`KbcY{wC6*jKfx8UM3ebo;U%TBULY*k;+U|{;_ zqtu5vVO_=EukX&1f3T#}@}l61X*0LU{x|=$!hh3pj&u8$zI>Fw^!1ua3(T5Cw^&fp< zUzZfeD8PUGzPSDOSv9^<=YHOD`D<_ZPPB4Itr6j&_3eJnJb&M>x63ZNzT9^}RdI8n-mY)& zRT&;Rh}7}TE^)RxXO*7CoKfR+^Frpd#b50H*|weZ3*SAN^U&G5=FgWooiY02J=d3) zCGYHB;}=`6%4oe(e#{Su!wRf!h6@MO zbURl)UwhCoN_Wlu-v$37XC5lzWH>9b?COK8H5=wJ-aT~aqJ3bAq~~j!7sVG74vSy< zd!jU$N2^AD-fY=QRtF1?JKC36&xhQu4PQEMT5DUyg+~GW+h62bm(>LxpS$(l{hE`O zFL!AKH$8i}d3pB{pOe}QkJKYN8GIZZ?yjdo9~<43b$je$qP_Uni4Rw}?X?+9bT@D{950x^8Q?hdZfvo-gy!TZ3 z?XKP_dt=YED<*J>kLYHWMWx~<2l|!&-Dg*3urX%f{JZ|o6CsCrk_@%a>T7#>pxG{; z#bKlSl9c63;&m9D?w=53_!am5@xhqR%#8$F8 z97&rv;cdYl0mirUPfs}(eeR^Ow%ycKoBwO2HmiFsY(I6Q?|-q)hlt#ji&jP&Hm%?K zr8sl{n^h&TkEX>Hf4nL6^Svs=m7nd8zc;)LUwZS8@wHw5XH0+c@R_2F?&Lp#3l@03 zNUl-;bF@QZx++7SAd7|NmASIa0eUsx@-;j2Ahn+egSj}v-uq|H?EBv8b!XoK-A6oK zc5%lo0(;+G&s}84UN5qL`W>Mf2ETguI7R`!szcXO=j*Yic>L|XKjXPqFzfU;SM=i@ zvsk%o6{b!yPh?4(`XhJin?*nLiaj4N)m-zjs6P8^x;MiMvjZDeDjmFRy!7pz_e-A% ziOSc1W>J0?Uo+vi^j+VDOPeo2_p9-2A#SBc?F+#|EV zX$&`3rMl=c7F?EKxS@QnF!edN{@rAW?Sg{Rv$P_be|?e6-0;x*WzCCOKlW+}?P6z2 zh}L7=z}Y0UzHYaRm96@vtn;BOcfZs2w>hwS*@fQk8}nUfKJauad68}wS?5d?m3 z>t_@QGaA_5`6OQXi6JAlCtlto7ZR@DyBRL9`%i9s5tn(_Oyujm(#_loPt|raEV{p~ zj4^{N@bA1`$8WiaR?APmbN|82OaGR<>am~we96mc`HD>Iycy;#y=VRKPpQ}eWrjlz z1}u)(@9q+LFzuPO$?hfBxPMI7wEeyLU?A(qKW3apwO?}ncn&rZnC(`0y7qbn<3!+%clmcEU;>Gc}j^A1bX z7oPe(vFu`>^LCY~Z*A}XWMZ(nvZ1{*?y8GYEPwolPYipiUfVF-jq})^#`xjDY!$IN ztO{{2E=>w+xnlcoR>QKZ>$opHEm+1P)4*|KVmQMWHb()*x!*gsRDNqSxmDx0>ap$B zwEw=R+Whu@{jy@+?yyyw{r$FCw_?wx&i9@j8ouk4C$5u2V_bJVw+v66{lcZCi%FpS=5=aMdj> zD#ZPB>9skN_qM^gVJnLlJkwg7r ziLX9|{+zM&X7jXV+LxC+&f5MZqU-Uwqs3O%>lr!@Wb5BxN=x^8{d3p5|1%@M>&sdk zyJGgi&Q8(3-E;9h&6{r$R(4*j|8U~-GN}g@rT^|RIV}JG@Q-@i&Lu~EPKUoJ3l`mY z@Az4;$Hf?4{kdMFXu3Ua$}MZ-Bk5OpU+($URuY|kk=5i@ajevvvlD*Fy;}48kVwl; zO>O(hv$iK4O%Hl3X0upldvI)I+x)#NHk#<0PF1a3xz$2{tN7EUeVYu#x$P$Jl6!UD z`e6oRz>kYvY5O==wAEd>s}2zSsJb}b=anHHoVi1J7&9O6+_m~3y(@` zecIpD<`mU`E?8Z#I^)8m`q@ejQS1wDD^8rZ`{5Gv=b{XI%@R`h5-pWPNeVcDr`+?QBP*m}1$oE2YkP;JAU>@Ed6WBrczJ=~Wnf3dzadfQYo zx5#mq|2_p7^HI7KF`r*o-^T{{vU;p z@(&$ruj(;Oa=h|7%=Eyv>iy^aIM*mS2y=<*1fHs^Df%C@V#AaxbN&S1H$LzASogly z@@C0Pk1Z{pyscz)&|q;8sC&03cskRa7s7hpy;JAUe!i*bRh?$k&OXa!CwJf5&>j-~ zqyCKH>otFZZmlc!JZ>s+SO4J0X9s^i7h;fNSrN&={P2&p?hchw@n}Pxs3-21Up!0S z&Zx+y+ZOsTGuSC|{?c8a?I-VZ);qaEVq)P%rOAumGC9oM|H%5G_W3XW-Iq13dVAyN zVaO&auSw}H;Va!$`RM!T%M;`y7q6+Z*4rg}?VIn+f5EX_W>4YPo&OD%HqU-

KCS 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 literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java b/app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java new file mode 100644 index 0000000..2b41f6a --- /dev/null +++ b/app/src/main/java/com/izzy2lost/psx2/CoversAdapter.java @@ -0,0 +1,98 @@ +package com.izzy2lost.psx2; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import java.io.File; + +public class CoversAdapter extends RecyclerView.Adapter { + public interface OnItemClick { + void onClick(int position); + } + + public interface OnItemLongClick { + void onLongClick(int position); + } + + private final Context context; + private final String[] titles; + private final String[] coverUrls; + private final String[] localPaths; // absolute file paths for cached covers (may be null) + private final OnItemClick onItemClick; + private final OnItemLongClick onItemLongClick; + + public CoversAdapter(Context context, String[] titles, String[] coverUrls, String[] localPaths, OnItemClick click) { + this(context, titles, coverUrls, localPaths, click, null); + } + + public CoversAdapter(Context context, String[] titles, String[] coverUrls, String[] localPaths, OnItemClick click, OnItemLongClick longClick) { + this.context = context; + this.titles = titles; + this.coverUrls = coverUrls; + this.localPaths = localPaths; + this.onItemClick = click; + this.onItemLongClick = longClick; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_cover, parent, false); + return new VH(v); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + holder.title.setText(titles[position]); + String url = coverUrls[position]; + String local = (localPaths != null && position < localPaths.length) ? localPaths[position] : null; + Object source = null; + if (local != null) { + File f = new File(local); + if (f.exists() && f.length() > 0) source = f; + } + if (source == null) source = url; + + Glide.with(context) + .load(source) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .fitCenter() + .placeholder(android.R.color.transparent) + .error(android.R.color.transparent) + .into(holder.cover); + holder.itemView.setOnClickListener(v -> { + if (onItemClick != null) onItemClick.onClick(position); + }); + holder.itemView.setOnLongClickListener(v -> { + if (onItemLongClick != null) { + onItemLongClick.onLongClick(position); + return true; + } + return false; + }); + } + + @Override + public int getItemCount() { + return titles.length; + } + + static class VH extends RecyclerView.ViewHolder { + final ImageView cover; + final TextView title; + VH(@NonNull View itemView) { + super(itemView); + cover = itemView.findViewById(R.id.image_cover); + title = itemView.findViewById(R.id.text_title); + } + } +} diff --git a/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java new file mode 100644 index 0000000..e8e93e7 --- /dev/null +++ b/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java @@ -0,0 +1,259 @@ +package com.izzy2lost.psx2; + +import android.app.Dialog; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.Switch; +import android.widget.TextView; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +public class GameSettingsDialogFragment extends DialogFragment { + + private static final String ARG_GAME_TITLE = "game_title"; + private static final String ARG_GAME_URI = "game_uri"; + private static final String ARG_GAME_SERIAL = "game_serial"; + private static final String ARG_GAME_CRC = "game_crc"; + + public static GameSettingsDialogFragment newInstance(String gameTitle, String gameUri, String gameSerial, String gameCrc) { + GameSettingsDialogFragment fragment = new GameSettingsDialogFragment(); + Bundle args = new Bundle(); + args.putString(ARG_GAME_TITLE, gameTitle); + args.putString(ARG_GAME_URI, gameUri); + args.putString(ARG_GAME_SERIAL, gameSerial); + args.putString(ARG_GAME_CRC, gameCrc); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Context ctx = requireContext(); + View view = LayoutInflater.from(ctx).inflate(R.layout.dialog_game_settings, null, false); + + Bundle args = getArguments(); + String gameTitle = args != null ? args.getString(ARG_GAME_TITLE, "Unknown Game") : "Unknown Game"; + String gameUri = args != null ? args.getString(ARG_GAME_URI, "") : ""; + String gameSerial = args != null ? args.getString(ARG_GAME_SERIAL, "") : ""; + String gameCrc = args != null ? args.getString(ARG_GAME_CRC, "") : ""; + + // Set title + TextView titleView = view.findViewById(R.id.tv_game_title); + titleView.setText(gameTitle); + + TextView serialView = view.findViewById(R.id.tv_game_serial); + if (!gameSerial.isEmpty() || !gameCrc.isEmpty()) { + serialView.setText(String.format("Serial: %s | CRC: %s", + gameSerial.isEmpty() ? "Unknown" : gameSerial, + gameCrc.isEmpty() ? "Unknown" : gameCrc)); + serialView.setVisibility(View.VISIBLE); + } else { + serialView.setVisibility(View.GONE); + } + + // Blending Accuracy Spinner + Spinner spBlendingAccuracy = view.findViewById(R.id.sp_blending_accuracy); + ArrayAdapter blendingAdapter = ArrayAdapter.createFromResource(ctx, + R.array.blending_accuracy_entries, android.R.layout.simple_spinner_item); + blendingAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spBlendingAccuracy.setAdapter(blendingAdapter); + + // Renderer Spinner + Spinner spRenderer = view.findViewById(R.id.sp_renderer); + ArrayAdapter rendererAdapter = ArrayAdapter.createFromResource(ctx, + R.array.renderer_entries, android.R.layout.simple_spinner_item); + rendererAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spRenderer.setAdapter(rendererAdapter); + + // Resolution Multiplier Spinner + Spinner spResolution = view.findViewById(R.id.sp_resolution); + ArrayAdapter resolutionAdapter = ArrayAdapter.createFromResource(ctx, + R.array.scale_entries, android.R.layout.simple_spinner_item); + resolutionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spResolution.setAdapter(resolutionAdapter); + + // Switches + Switch swWidescreenPatches = view.findViewById(R.id.sw_widescreen_patches); + Switch swNoInterlacingPatches = view.findViewById(R.id.sw_no_interlacing_patches); + Switch swEnablePatchCodes = view.findViewById(R.id.sw_enable_patch_codes); + Switch swEnableCheats = view.findViewById(R.id.sw_enable_cheats); + + // Load existing per-game settings from INI and prefill widgets + try { + String serial = gameSerial; + if (serial == null || serial.isEmpty()) { + serial = NativeApp.getCurrentGameSerial(); + } + if (serial != null && !serial.isEmpty()) { + // Build INI path + String dataRoot = getContext().getExternalFilesDir(null).getAbsolutePath(); + java.io.File ini = new java.io.File(new java.io.File(dataRoot, "gamesettings"), serial + ".ini"); + if (ini.exists()) { + String content = new String(java.nio.file.Files.readAllBytes(ini.toPath())); + // Very light parsing + java.util.regex.Matcher m; + m = java.util.regex.Pattern.compile("(?m)^Renderer=\\s*(.+)$").matcher(content); + if (m.find()) { + String rv = m.group(1).trim(); + int idx = 0; + if ("Vulkan".equalsIgnoreCase(rv)) idx = 1; + else if ("OpenGL".equalsIgnoreCase(rv)) idx = 2; + else if ("Software".equalsIgnoreCase(rv)) idx = 3; + spRenderer.setSelection(idx); + } + m = java.util.regex.Pattern.compile("(?m)^upscale_multiplier=\\s*([0-9]+(?:\\.[0-9]+)?)$").matcher(content); + if (m.find()) { + try { float mult = Float.parseFloat(m.group(1)); int sel = Math.max(0, Math.min(7, Math.round(mult - 1))); spResolution.setSelection(sel); } catch (Exception ignored) {} + } + m = java.util.regex.Pattern.compile("(?m)^accurate_blending_unit=\\s*(.+)$").matcher(content); + if (m.find()) { + String bv = m.group(1).trim(); + int idx = 1; // default Basic + try { + int num = Integer.parseInt(bv); + if (num >= 0 && num <= 5) idx = num; + } catch (Exception e) { + if ("Minimum".equalsIgnoreCase(bv)) idx = 0; + else if ("Basic".equalsIgnoreCase(bv)) idx = 1; + else if ("Medium".equalsIgnoreCase(bv)) idx = 2; + else if ("High".equalsIgnoreCase(bv)) idx = 3; + else if ("Full".equalsIgnoreCase(bv)) idx = 4; + else if ("Maximum".equalsIgnoreCase(bv)) idx = 5; + } + spBlendingAccuracy.setSelection(idx); + } + m = java.util.regex.Pattern.compile("(?m)^EnableWideScreenPatches=\\s*(true|false)$").matcher(content); + if (m.find()) swWidescreenPatches.setChecked(Boolean.parseBoolean(m.group(1))); + m = java.util.regex.Pattern.compile("(?m)^EnableNoInterlacingPatches=\\s*(true|false)$").matcher(content); + if (m.find()) swNoInterlacingPatches.setChecked(Boolean.parseBoolean(m.group(1))); + m = java.util.regex.Pattern.compile("(?m)^EnableCheats=\\s*(true|false)$").matcher(content); + if (m.find()) swEnableCheats.setChecked(Boolean.parseBoolean(m.group(1))); + m = java.util.regex.Pattern.compile("(?m)^EnablePatches=\\s*(true|false)$").matcher(content); + if (m.find()) swEnablePatchCodes.setChecked(Boolean.parseBoolean(m.group(1))); + } + } + } catch (Throwable ignored) { + // Fallback to defaults if loading fails + spBlendingAccuracy.setSelection(1); + spRenderer.setSelection(0); + spResolution.setSelection(0); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(ctx); + builder.setTitle("Per-Game Settings") + .setView(view) + .setNegativeButton("Cancel", (d, w) -> d.dismiss()) + .setPositiveButton("Save", (d, w) -> { + // Apply blending to runtime as well for immediate effect + NativeApp.setBlendingAccuracy(spBlendingAccuracy.getSelectedItemPosition()); + + saveGameSettings(gameSerial, gameCrc, + spBlendingAccuracy.getSelectedItemPosition(), + spRenderer.getSelectedItemPosition(), + spResolution.getSelectedItemPosition(), + swWidescreenPatches.isChecked(), + swNoInterlacingPatches.isChecked(), + /*enablePatches*/ swEnablePatchCodes.isChecked(), + swEnableCheats.isChecked()); + d.dismiss(); + }) + .setNeutralButton("Reset to Global", (d, w) -> { + // TODO: Delete game-specific settings file + deleteGameSettings(gameSerial, gameCrc); + d.dismiss(); + }); + + // Import PNACH button wiring + com.google.android.material.button.MaterialButton btnImport = view.findViewById(R.id.btn_import_pnach); + if (btnImport != null) { + btnImport.setOnClickListener(v -> { + final String[] choices = new String[]{"Import as Cheats", "Import as Patch Codes"}; + new AlertDialog.Builder(ctx) + .setTitle("Import PNACH") + .setItems(choices, (dlg, which) -> { + boolean asCheats = (which == 0); + // Prepare picker + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + // Store choice in tag + view.setTag(R.id.btn_import_pnach, asCheats); + registerForActivityResult(new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(), result -> { + try { + if (result.getResultCode() != android.app.Activity.RESULT_OK) return; + Intent data = result.getData(); if (data == null) return; + android.net.Uri uri = data.getData(); if (uri == null) return; + boolean importAsCheats = Boolean.TRUE.equals(view.getTag(R.id.btn_import_pnach)); + String serialLoad = gameSerial; + if (serialLoad == null || serialLoad.isEmpty()) { + try { serialLoad = NativeApp.getCurrentGameSerial(); } catch (Throwable ignored) {} + } + if (serialLoad == null || serialLoad.isEmpty()) { + android.widget.Toast.makeText(ctx, "Serial unknown; cannot import", android.widget.Toast.LENGTH_SHORT).show(); + return; + } + java.io.File baseDir = ctx.getExternalFilesDir(null); + if (baseDir == null) baseDir = ctx.getFilesDir(); + java.io.File targetDir = new java.io.File(baseDir, importAsCheats ? "cheats" : "patches"); + if (!targetDir.exists()) targetDir.mkdirs(); + java.io.File outFile = new java.io.File(targetDir, serialLoad + ".pnach"); + android.content.ContentResolver cr = ctx.getContentResolver(); + java.io.InputStream in = cr.openInputStream(uri); + if (in == null) { android.widget.Toast.makeText(ctx, "Failed to open file", android.widget.Toast.LENGTH_SHORT).show(); return; } + java.io.FileOutputStream fos = new java.io.FileOutputStream(outFile); + byte[] buf = new byte[8192]; int n; while ((n = in.read(buf)) != -1) fos.write(buf, 0, n); + fos.flush(); fos.close(); in.close(); + android.widget.Toast.makeText(ctx, (importAsCheats ? "Cheats" : "Patch Codes") + " imported for " + serialLoad, android.widget.Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + android.widget.Toast.makeText(ctx, "Import failed: " + e.getMessage(), android.widget.Toast.LENGTH_SHORT).show(); + } + }).launch(intent); + }) + .show(); + }); + } + + return builder.create(); + } + + private void saveGameSettings(String gameSerial, String gameCrc, + int blendingAccuracy, int renderer, int resolution, + boolean widescreenPatches, boolean noInterlacingPatches, + boolean enablePatches, boolean enableCheats) { + + // Create the settings filename based on serial (like SLUS-12345.ini) + if (gameSerial == null || gameSerial.isEmpty()) { + android.util.Log.w("GameSettings", "No game serial available, cannot save settings"); + return; + } + String filename = gameSerial + ".ini"; + + // Save to PCSX2's DataRoot/gamesettings via native helper. + NativeApp.saveGameSettings(filename, blendingAccuracy, renderer, resolution, + widescreenPatches, noInterlacingPatches, enablePatches, enableCheats); + + android.widget.Toast.makeText(requireContext(), "Game settings saved: " + filename, android.widget.Toast.LENGTH_SHORT).show(); + } + + private void deleteGameSettings(String gameSerial, String gameCrc) { + String filename = ""; + if (!gameSerial.isEmpty()) { + filename = gameSerial + ".ini"; + } else { + return; + } + + NativeApp.deleteGameSettings(filename); + } +} diff --git a/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java new file mode 100644 index 0000000..695f2e4 --- /dev/null +++ b/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java @@ -0,0 +1,388 @@ +package com.izzy2lost.psx2; + +import android.app.Dialog; +import android.content.Context; +import android.net.Uri; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Toast; +import android.os.Build; +import android.view.WindowInsets; +import android.view.WindowInsetsController; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public class GamesCoverDialogFragment extends DialogFragment { + private CoversAdapter adapter; + private String[] titles; + private String[] uris; + private String[] coverUrls; + private String[] localPaths; + private RecyclerView rv; + private GridLayoutManager glm; + + public interface OnGameSelectedListener { + void onGameSelected(String gameUri); + } + + private static final String ARG_TITLES = "titles"; + private static final String ARG_URIS = "uris"; + + public static GamesCoverDialogFragment newInstance(String[] titles, String[] uris) { + GamesCoverDialogFragment f = new GamesCoverDialogFragment(); + Bundle b = new Bundle(); + b.putStringArray(ARG_TITLES, titles); + b.putStringArray(ARG_URIS, uris); + f.setArguments(b); + return f; + } + + private OnGameSelectedListener listener; + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (context instanceof OnGameSelectedListener) { + listener = (OnGameSelectedListener) context; + } + } + + @Override + public void onResume() { + super.onResume(); + // Re-assert fixed span (2/4) after resume to avoid any flips + if (rv != null && glm != null) { + int currentOrientation = getResources().getConfiguration().orientation; + int fixedSpan = (currentOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) ? 4 : 2; + if (fixedSpan != glm.getSpanCount()) { + glm.setSpanCount(fixedSpan); + } + } + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + LayoutInflater inflater = LayoutInflater.from(requireContext()); + View root = inflater.inflate(R.layout.dialog_covers_grid, null, false); + + rv = root.findViewById(R.id.recycler_covers); + rv.setHasFixedSize(true); + glm = new GridLayoutManager(requireContext(), 3); + rv.setLayoutManager(glm); + // spacing decoration (8dp) using half on each side so the gap between items is exactly spacingPx + final int spacingPx = (int) (8 * getResources().getDisplayMetrics().density); + final int half = Math.max(1, spacingPx / 2); + rv.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(android.graphics.Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.set(half, half, half, half); + } + }); + // Hard lock spans based on orientation only + rv.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + int currentOrientation = getResources().getConfiguration().orientation; + int fixedSpan = (currentOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) ? 4 : 2; + if (fixedSpan != glm.getSpanCount()) { glm.setSpanCount(fixedSpan); } + }); + // Set initial fixed span as soon as possible + root.post(() -> { + int currentOrientation = getResources().getConfiguration().orientation; + int fixedSpan = (currentOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) ? 4 : 2; + if (fixedSpan != glm.getSpanCount()) { glm.setSpanCount(fixedSpan); } + }); + + titles = getArguments() != null ? getArguments().getStringArray(ARG_TITLES) : new String[0]; + uris = getArguments() != null ? getArguments().getStringArray(ARG_URIS) : new String[0]; + coverUrls = new String[uris.length]; + localPaths = new String[uris.length]; + SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); + for (int i = 0; i < uris.length; i++) { + String saved = prefs.getString("serial:" + uris[i], null); + String serial = saved; + if (serial == null || serial.isEmpty()) { + // Ask native core for the real serial (supports ISO/CHD and content://) + try { + String nativeSerial = NativeApp.getGameSerial(uris[i]); + if (nativeSerial != null && !nativeSerial.isEmpty()) { + serial = normalizeSerial(nativeSerial); + prefs.edit().putString("serial:" + uris[i], serial).apply(); + } + } catch (Throwable ignored) {} + } + if (serial == null || serial.isEmpty()) { + // Heuristic fallback from filename + serial = buildSerialFromUri(uris[i]); + } + coverUrls[i] = buildCoverUrlFromSerial(serial); + localPaths[i] = new java.io.File(getCoversDir(), serial + ".png").getAbsolutePath(); + } + + adapter = new CoversAdapter(requireContext(), titles, coverUrls, localPaths, + position -> { + // Regular click - start game + if (listener != null && position >= 0 && position < uris.length) { + listener.onGameSelected(uris[position]); + dismissAllowingStateLoss(); + } + }, + position -> { + // Long click - show game settings + if (position >= 0 && position < uris.length) { + showGameSettings(titles[position], uris[position]); + } + }); + rv.setAdapter(adapter); + + // Toolbar buttons + View btnHome = root.findViewById(R.id.btn_home); + if (btnHome != null) btnHome.setOnClickListener(v -> dismissAllowingStateLoss()); + View btnDownload = root.findViewById(R.id.btn_download); + if (btnDownload != null) btnDownload.setOnClickListener(v -> startDownloadCovers()); + + AlertDialog dialog = new AlertDialog.Builder(requireContext()) + .setView(root) + .create(); + return dialog; + } + + @Override + public void onStart() { + super.onStart(); + Dialog d = getDialog(); + if (d != null) { + Window w = d.getWindow(); + if (w != null) { + w.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + w.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + // Hide status bar for true full-screen dialog + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + w.setDecorFitsSystemWindows(false); + WindowInsetsController controller = w.getInsetsController(); + if (controller != null) { + controller.hide(WindowInsets.Type.statusBars()); + controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + } + } else { + w.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + } + } + + private int calculateSpanForWidth(int rvWidthPx, int itemDp, int spacingPx) { + float density = getResources().getDisplayMetrics().density; + int usable = Math.max(0, rvWidthPx); + int itemPx = (int) (itemDp * density); + // Include spacing in the packing calculation to avoid oscillation + // span = floor((usable + spacing) / (itemPx + spacing)) + int span = (itemPx > 0) ? (int) Math.floor((usable + (double) spacingPx) / (itemPx + (double) spacingPx)) : 1; + return Math.max(2, Math.max(1, span)); + } + + private void preloadCovers(String[] urls) { + // Use Glide to warm cache + for (String url : urls) { + if (url == null) continue; + com.bumptech.glide.Glide.with(requireContext()).load(url).preload(); + } + } + + private void startDownloadCovers() { + Toast.makeText(requireContext(), "Downloading covers in background", Toast.LENGTH_SHORT).show(); + new Thread(() -> { + // Try to refine serials/URLs by scanning disc contents first + SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + for (int i = 0; i < uris.length; i++) { + try { + // Prefer native serial extraction so CHDs work + String better = null; + try { better = NativeApp.getGameSerial(uris[i]); } catch (Throwable ignored) {} + if (better == null) better = extractSerialFromUri(uris[i]); + if (better != null && !better.equalsIgnoreCase(serialFromUrl(coverUrls[i]))) { + String serial = normalizeSerial(better); + coverUrls[i] = buildCoverUrlFromSerial(serial); + localPaths[i] = new java.io.File(getCoversDir(), serial + ".png").getAbsolutePath(); + editor.putString("serial:" + uris[i], serial); + } + } catch (Exception ignored) { } + } + editor.apply(); + + int total = coverUrls.length; + int ok = 0; + java.io.File dir = getCoversDir(); + if (!dir.exists()) dir.mkdirs(); + for (int i = 0; i < total; i++) { + String url = coverUrls[i]; + String outPath = localPaths[i]; + if (isFileValid(outPath)) { ok++; continue; } + try { + if (downloadToFile(url, outPath)) ok++; + } catch (Exception ignored) { } + } + final int downloaded = ok; + if (isAdded()) requireActivity().runOnUiThread(() -> { + Toast.makeText(requireContext(), "Covers ready: " + downloaded + "/" + total, Toast.LENGTH_SHORT).show(); + // refresh adapter to prefer local files now + if (adapter != null) adapter.notifyDataSetChanged(); + }); + }).start(); + } + + private static String serialFromUrl(String url) { + if (url == null) return null; + int slash = url.lastIndexOf('/'); + int dot = url.lastIndexOf('.'); + if (slash >= 0 && dot > slash) return url.substring(slash + 1, dot); + return null; + } + + private java.io.File getCoversDir() { + java.io.File base = requireContext().getExternalFilesDir("covers"); + if (base == null) base = new java.io.File(requireContext().getFilesDir(), "covers"); + return base; + } + + private static boolean isFileValid(String path) { + if (path == null) return false; + java.io.File f = new java.io.File(path); + return f.exists() && f.length() > 0; + } + + private static boolean downloadToFile(String urlStr, String outPath) throws Exception { + java.net.URL url = new java.net.URL(urlStr); + java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(10000); + conn.setReadTimeout(15000); + conn.setInstanceFollowRedirects(true); + conn.connect(); + int code = conn.getResponseCode(); + if (code != 200) { conn.disconnect(); return false; } + java.io.File outFile = new java.io.File(outPath); + java.io.File parent = outFile.getParentFile(); + if (parent != null && !parent.exists()) parent.mkdirs(); + java.io.InputStream in = conn.getInputStream(); + java.io.FileOutputStream fos = new java.io.FileOutputStream(outFile); + byte[] buf = new byte[8192]; + int n; + while ((n = in.read(buf)) != -1) fos.write(buf, 0, n); + fos.flush(); + fos.close(); + in.close(); + conn.disconnect(); + return true; + } + + private static String buildSerialFromUri(String gameUri) { + // Try to infer PS2 serial from file name: e.g., SLUS-20312 or SLPS_123.45 style + String last = Uri.parse(gameUri).getLastPathSegment(); + if (last == null) last = ""; + last = last.replace('_', '-'); + // remove extension + int dot = last.lastIndexOf('.'); + if (dot > 0) last = last.substring(0, dot); + String serial = null; + // Very simple heuristic: find token like XXXX-XXXXX + String upper = last.toUpperCase(); + java.util.regex.Matcher m = java.util.regex.Pattern.compile("([A-Z]{4,5}-[0-9]{3,5})").matcher(upper); + if (m.find()) { + serial = m.group(1); + } + if (serial == null) { + serial = upper; + } + return serial; + } + + private static String buildCoverUrlFromSerial(String serial) { + return "https://raw.githubusercontent.com/izzy2lost/ps2-covers/main/covers/3d/" + serial + ".png"; + } + + private String extractSerialFromUri(String gameUri) { + try { + java.io.InputStream in = requireContext().getContentResolver().openInputStream(Uri.parse(gameUri)); + if (in == null) return null; + // Read first 8MB searching for SYSTEM.CNF contents, e.g., "BOOT2 = cdrom0:\\SLUS_203.12;1" + final int MAX_BYTES = 8 * 1024 * 1024; + final byte[] buf = new byte[64 * 1024]; + int read; + int total = 0; + StringBuilder sb = new StringBuilder(); + while ((read = in.read(buf)) != -1 && total < MAX_BYTES) { + total += read; + // append as ASCII + sb.append(new String(buf, 0, read)); + // try to match as we go to avoid huge strings + String found = findSerialInString(sb); + if (found != null) { in.close(); return found; } + if (sb.length() > 512 * 1024) sb.delete(0, sb.length() - 128 * 1024); // keep window + } + in.close(); + } catch (Exception ignored) { } + return null; + } + + private static String findSerialInString(CharSequence cs) { + // Match common forms: SLUS_203.12, SLPM_650.51, SCES_123.45 etc. + java.util.regex.Matcher m = java.util.regex.Pattern + .compile("([A-Z]{4,5})[_-]([0-9]{3})\\.([0-9]{2})") + .matcher(cs); + if (m.find()) { + String prefix = m.group(1); + String part1 = m.group(2); + String part2 = m.group(3); + return prefix + "-" + part1 + part2; // SLUS-20312 + } + return null; + } + + private static String normalizeSerial(String serial) { + if (serial == null) return null; + String s = serial.toUpperCase().replace('_', '-'); + // If form like XXXX-123.45 -> XXXX-12345 + s = s.replaceAll("([A-Z]{4,5})-([0-9]{3})\\.([0-9]{2})", "$1-$2$3"); + return s; + } + + private void showGameSettings(String gameTitle, String gameUri) { + // Prefer native extraction so CHDs work + String gameSerial = null; + try { gameSerial = NativeApp.getGameSerial(gameUri); } catch (Throwable ignored) {} + if (gameSerial == null || gameSerial.isEmpty()) { + gameSerial = extractSerialFromUri(gameUri); + } + if (gameSerial == null || gameSerial.isEmpty()) { + gameSerial = buildSerialFromUri(gameUri); + } + gameSerial = normalizeSerial(gameSerial); + + // CRC (native if available) + String gameCrc = null; + try { gameCrc = NativeApp.getGameCrc(gameUri); } catch (Throwable ignored) {} + if (gameCrc == null || gameCrc.isEmpty()) { + gameCrc = String.format("%08X", Math.abs(gameUri.hashCode())); + } + + // Debug logging + android.util.Log.d("GameSettings", "Opening game settings for: " + gameTitle); + android.util.Log.d("GameSettings", "URI: " + gameUri); + android.util.Log.d("GameSettings", "Extracted Serial: " + gameSerial); + android.util.Log.d("GameSettings", "Generated CRC: " + gameCrc); + + GameSettingsDialogFragment dialog = GameSettingsDialogFragment.newInstance( + gameTitle, gameUri, gameSerial, gameCrc); + dialog.show(getParentFragmentManager(), "game_settings"); + } +} diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDevice.java b/app/src/main/java/com/izzy2lost/psx2/HIDDevice.java similarity index 94% rename from app/src/main/java/kr/co/iefriends/pcsx2/HIDDevice.java rename to app/src/main/java/com/izzy2lost/psx2/HIDDevice.java index 33e7de0..a606502 100644 --- a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDevice.java +++ b/app/src/main/java/com/izzy2lost/psx2/HIDDevice.java @@ -1,4 +1,4 @@ -package kr.co.iefriends.pcsx2; +package com.izzy2lost.psx2; import android.hardware.usb.UsbDevice; diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceBLESteamController.java b/app/src/main/java/com/izzy2lost/psx2/HIDDeviceBLESteamController.java similarity index 99% rename from app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceBLESteamController.java rename to app/src/main/java/com/izzy2lost/psx2/HIDDeviceBLESteamController.java index a08b86c..b73af38 100644 --- a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceBLESteamController.java +++ b/app/src/main/java/com/izzy2lost/psx2/HIDDeviceBLESteamController.java @@ -1,4 +1,4 @@ -package kr.co.iefriends.pcsx2; +package com.izzy2lost.psx2; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceManager.java b/app/src/main/java/com/izzy2lost/psx2/HIDDeviceManager.java similarity index 99% rename from app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceManager.java rename to app/src/main/java/com/izzy2lost/psx2/HIDDeviceManager.java index b55692b..285feaf 100644 --- a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceManager.java +++ b/app/src/main/java/com/izzy2lost/psx2/HIDDeviceManager.java @@ -1,4 +1,4 @@ -package kr.co.iefriends.pcsx2; +package com.izzy2lost.psx2; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceUSB.java b/app/src/main/java/com/izzy2lost/psx2/HIDDeviceUSB.java similarity index 99% rename from app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceUSB.java rename to app/src/main/java/com/izzy2lost/psx2/HIDDeviceUSB.java index f7804ac..bc3d3e4 100644 --- a/app/src/main/java/kr/co/iefriends/pcsx2/HIDDeviceUSB.java +++ b/app/src/main/java/com/izzy2lost/psx2/HIDDeviceUSB.java @@ -1,4 +1,4 @@ -package kr.co.iefriends.pcsx2; +package com.izzy2lost.psx2; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; diff --git a/app/src/main/java/com/izzy2lost/psx2/JoystickView.java b/app/src/main/java/com/izzy2lost/psx2/JoystickView.java new file mode 100644 index 0000000..78c6dc3 --- /dev/null +++ b/app/src/main/java/com/izzy2lost/psx2/JoystickView.java @@ -0,0 +1,117 @@ +package com.izzy2lost.psx2; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import androidx.core.content.ContextCompat; + +public class JoystickView extends View { + public interface OnMoveListener { + void onMove(float nx, float ny, int action); + } + + private final Paint basePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint ringPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint knobPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private float centerX, centerY, radius, knobX, knobY, knobRadius; + private boolean isDragging = false; + private OnMoveListener listener; + + public JoystickView(Context ctx) { super(ctx); init(); } + public JoystickView(Context ctx, AttributeSet attrs) { super(ctx, attrs); init(); } + public JoystickView(Context ctx, AttributeSet attrs, int defStyle) { super(ctx, attrs, defStyle); init(); } + + private void init() { + basePaint.setColor(0x22000000); // subtle fill + basePaint.setStyle(Paint.Style.FILL); + // Match Settings/Controls outline (brand primary blue) + int brandBlue = ContextCompat.getColor(getContext(), R.color.brand_primary); + ringPaint.setColor(brandBlue); + ringPaint.setStyle(Paint.Style.STROKE); + ringPaint.setStrokeWidth(dp(2)); + // Knob uses the same brand blue + knobPaint.setColor(brandBlue); + knobPaint.setStyle(Paint.Style.FILL); + setClickable(true); + } + + public void setOnMoveListener(OnMoveListener l) { this.listener = l; } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + centerX = w / 2f; + centerY = h / 2f; + // Make the visible base circle smaller relative to the view size + radius = Math.min(w, h) * 0.32f; + knobRadius = radius * 0.30f; + resetKnob(); + } + + private void resetKnob() { + knobX = centerX; + knobY = centerY; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + // Base circle + canvas.drawCircle(centerX, centerY, radius, basePaint); + // Outer thin ring + canvas.drawCircle(centerX, centerY, radius, ringPaint); + // Knob + canvas.drawCircle(knobX, knobY, knobRadius, knobPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + isDragging = true; + // fallthrough to move + case MotionEvent.ACTION_MOVE: + if (isDragging) { + float dx = event.getX() - centerX; + float dy = event.getY() - centerY; + // Clamp to circle + float dist = (float)Math.hypot(dx, dy); + if (dist > radius) { + float scale = radius / dist; + dx *= scale; + dy *= scale; + } + knobX = centerX + dx; + knobY = centerY + dy; + invalidate(); + if (listener != null) { + // Normalize to [-1,1], invert Y so up is negative value (screen y grows down) + float nx = dx / radius; + float ny = dy / radius; + listener.onMove(clamp(nx), clamp(ny), MotionEvent.ACTION_MOVE); + } + } + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + isDragging = false; + resetKnob(); + if (listener != null) listener.onMove(0f, 0f, MotionEvent.ACTION_UP); + return true; + } + return super.onTouchEvent(event); + } + + private static float clamp(float v) { return Math.max(-1f, Math.min(1f, v)); } + + private float dp(float d) { + return d * getResources().getDisplayMetrics().density; + } +} diff --git a/app/src/main/java/com/izzy2lost/psx2/MainActivity.java b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java new file mode 100644 index 0000000..028f037 --- /dev/null +++ b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java @@ -0,0 +1,1280 @@ +package com.izzy2lost.psx2; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.database.Cursor; +import android.os.Bundle; +import android.text.TextUtils; +import android.net.Uri; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.view.ViewGroup; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.content.ClipData; +import android.content.SharedPreferences; +import android.os.Build; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; +import android.view.WindowManager; +import android.util.TypedValue; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.documentfile.provider.DocumentFile; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.content.ContextCompat; +import androidx.activity.OnBackPressedCallback; + +import com.google.android.material.button.MaterialButton; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import android.provider.OpenableColumns; +import org.json.JSONArray; +import org.json.JSONException; +import androidx.fragment.app.FragmentManager; +import java.util.List; + +public class MainActivity extends AppCompatActivity implements GamesCoverDialogFragment.OnGameSelectedListener { + private String m_szGamefile = ""; + + private HIDDeviceManager mHIDDeviceManager; + private Thread mEmulationThread = null; + private boolean mHudVisible = false; + + + // Track joystick directional pressed state to avoid duplicate down events + private boolean joyUpPressed = false; + private boolean joyDownPressed = false; + private boolean joyLeftPressed = false; + private boolean joyRightPressed = false; + + private boolean isThread() { + if (mEmulationThread != null) { + Thread.State _thread_state = mEmulationThread.getState(); + return _thread_state == Thread.State.BLOCKED + || _thread_state == Thread.State.RUNNABLE + || _thread_state == Thread.State.TIMED_WAITING + || _thread_state == Thread.State.WAITING; + } + return false; + } + + // Expose whether a game has been chosen (non-empty path) + public boolean hasSelectedGame() { + return !TextUtils.isEmpty(m_szGamefile); + } + + + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // Keep fullscreen on rotate and reflow constraints without recreating + hideStatusBar(); + applyConstraintsForOrientation(newConfig.orientation); + } + + private void applyConstraintsForOrientation(int orientation) { + // Views present in both layouts + View quick = findViewById(R.id.ll_quick_actions); + View btnSettings = findViewById(R.id.btn_settings); + View btnControls = findViewById(R.id.btn_toggle_controls); + View llJoy = findViewById(R.id.ll_pad_joy); + View llDpad = findViewById(R.id.ll_pad_dpad); + View llRight = findViewById(R.id.ll_pad_right_buttons); + View llSelectStart = findViewById(R.id.ll_pad_select_start); + + // Use helper dp() + + if (quick != null && btnSettings != null && btnControls != null) { + ConstraintLayout.LayoutParams lp = safeCLP(quick); + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + // Between settings and controls, top-aligned + lp.width = 0; // chain between start/end + lp.topToTop = btnSettings.getId(); + lp.topToBottom = ConstraintLayout.LayoutParams.UNSET; + lp.startToEnd = btnSettings.getId(); + lp.endToStart = btnControls.getId(); + lp.startToStart = ConstraintLayout.LayoutParams.UNSET; + lp.endToEnd = ConstraintLayout.LayoutParams.UNSET; + lp.topMargin = dp(0); + lp.horizontalChainStyle = ConstraintLayout.LayoutParams.CHAIN_PACKED; + } else { + // Centered under settings + lp.width = ConstraintLayout.LayoutParams.WRAP_CONTENT; + lp.topToTop = ConstraintLayout.LayoutParams.UNSET; + lp.topToBottom = btnSettings.getId(); + lp.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; + lp.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; + lp.startToEnd = ConstraintLayout.LayoutParams.UNSET; + lp.endToStart = ConstraintLayout.LayoutParams.UNSET; + lp.topMargin = dp(16); + lp.horizontalChainStyle = ConstraintLayout.LayoutParams.CHAIN_SPREAD; + } + quick.setLayoutParams(lp); + } + + if (llJoy != null) { + ConstraintLayout.LayoutParams lp = safeCLP(llJoy); + lp.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; + lp.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; + lp.endToEnd = ConstraintLayout.LayoutParams.UNSET; + lp.topToTop = ConstraintLayout.LayoutParams.UNSET; + lp.topToBottom = ConstraintLayout.LayoutParams.UNSET; + int m = (orientation == Configuration.ORIENTATION_LANDSCAPE) ? 6 : 6; + lp.setMargins(dp(m), dp(m), dp(m), dp(m)); + llJoy.setLayoutParams(lp); + // Nudge joystick further left in both orientations to avoid Select overlap + llJoy.setTranslationX(-dp(28)); + } + + if (llDpad != null && llJoy != null) { + ConstraintLayout.LayoutParams lp = safeCLP(llDpad); + // Above-left of joystick for both, with slight spacing + lp.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; + lp.endToEnd = ConstraintLayout.LayoutParams.UNSET; + lp.bottomToTop = llJoy.getId(); + lp.bottomToBottom = ConstraintLayout.LayoutParams.UNSET; + lp.topToTop = ConstraintLayout.LayoutParams.UNSET; + lp.topToBottom = ConstraintLayout.LayoutParams.UNSET; + lp.setMargins(dp(0), dp(0), dp(0), dp(orientation == Configuration.ORIENTATION_LANDSCAPE ? 2 : 0)); + llDpad.setLayoutParams(lp); + } + + if (llRight != null) { + ConstraintLayout.LayoutParams lp = safeCLP(llRight); + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + lp.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; + lp.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; + lp.bottomToTop = ConstraintLayout.LayoutParams.UNSET; + lp.setMargins(dp(12), dp(12), dp(12), dp(12)); + } else { + // Above Select/Start, aligned to end + if (llSelectStart != null) { + lp.bottomToTop = llSelectStart.getId(); + } else { + lp.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; + } + lp.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; + lp.bottomToBottom = (llSelectStart == null) ? ConstraintLayout.LayoutParams.PARENT_ID : ConstraintLayout.LayoutParams.UNSET; + lp.setMargins(0,0,0,dp(8)); + } + llRight.setLayoutParams(lp); + } + + if (llSelectStart != null) { + ConstraintLayout.LayoutParams lp = safeCLP(llSelectStart); + lp.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; + lp.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; + lp.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; + lp.setMargins(0,0,0,dp(orientation == Configuration.ORIENTATION_LANDSCAPE ? 8 : 0)); + llSelectStart.setLayoutParams(lp); + } + } + + private ConstraintLayout.LayoutParams safeCLP(View v) { + ViewGroup.LayoutParams p = v.getLayoutParams(); + if (p instanceof ConstraintLayout.LayoutParams) return (ConstraintLayout.LayoutParams) p; + ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(p); + v.setLayoutParams(lp); + return lp; + } + + private int dp(int d) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, d, getResources().getDisplayMetrics()); + } + + private void hideStatusBar() { + Window w = getWindow(); + if (w == null) return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowInsetsController controller = w.getInsetsController(); + if (controller != null) { + controller.hide(WindowInsets.Type.statusBars()); + controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + } + } else { + w.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + private void pickGamesFolder() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + startActivityResultGamesFolderPick.launch(intent); + } + + private void showGamesListOrReselect(Uri treeUri) { + // Re-scan quickly each time to keep list fresh + String[] names; + String[] uris; + try { + GameList list = scanGamesFromTreeUri(treeUri); + names = list.names; + uris = list.uris; + } catch (Exception e) { + names = new String[0]; + uris = new String[0]; + } + // Make effectively-final copies for use in lambda + final String[] namesFinal = names; + final String[] urisFinal = uris; + + if (namesFinal.length == 0) { + new AlertDialog.Builder(this) + .setTitle("GAMES") + .setMessage("No games found. Pick a folder?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Pick Folder", (d,w) -> pickGamesFolder()) + .show(); + return; + } + // Show covers grid dialog fragment + GamesCoverDialogFragment frag = GamesCoverDialogFragment.newInstance(namesFinal, urisFinal); + FragmentManager fm = getSupportFragmentManager(); + frag.show(fm, "covers_dialog"); + } + + @Override + public void onGameSelected(String gameUri) { + if (!TextUtils.isEmpty(gameUri)) { + // Avoid any pre-VM native calls here; just set the game and launch. + m_szGamefile = gameUri; + restartEmuThread(); + } + } + + private static final String[] GAME_EXTS = new String[]{ + ".iso", ".bin", ".img", ".mdf", ".nrg", ".chd" + }; + + private static boolean hasGameExt(String name) { + if (TextUtils.isEmpty(name)) return false; + String lower = name.toLowerCase(); + for (String ext : GAME_EXTS) { + if (lower.endsWith(ext)) return true; + } + return false; + } + + private static class GameList { + final String[] names; + final String[] uris; + GameList(String[] n, String[] u) { names = n; uris = u; } + } + + private GameList scanGamesFromTreeUri(Uri treeUri) { + DocumentFile dir = DocumentFile.fromTreeUri(this, treeUri); + if (dir == null || !dir.isDirectory()) return new GameList(new String[0], new String[0]); + java.util.ArrayList nameList = new java.util.ArrayList<>(); + java.util.ArrayList uriList = new java.util.ArrayList<>(); + scanGamesRecursive(dir, nameList, uriList); + + // Persist folder and latest list + SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); + JSONArray arr = new JSONArray(); + for (int i = 0; i < nameList.size(); i++) { + JSONArray pair = new JSONArray(); + try { + pair.put(nameList.get(i)); + pair.put(uriList.get(i)); + } catch (Exception ignored) {} + arr.put(pair); + } + prefs.edit() + .putString("games_folder_uri", treeUri.toString()) + .putString("games_list_json", arr.toString()) + .apply(); + + return new GameList(nameList.toArray(new String[0]), uriList.toArray(new String[0])); + } + + private void scanGamesRecursive(DocumentFile dir, java.util.List names, java.util.List uris) { + DocumentFile[] children = dir.listFiles(); + if (children == null) return; + for (DocumentFile child : children) { + if (child == null) continue; + if (child.isDirectory()) { + scanGamesRecursive(child, names, uris); + } else if (child.isFile()) { + String name = child.getName(); + if (hasGameExt(name)) { + names.add(name); + uris.add(child.getUri().toString()); + } + } + } + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + hideStatusBar(); + + // Setup back button handler + setupBackPressedHandler(); + + // Default resources + copyAssetAll(getApplicationContext(), "bios"); + copyAssetAll(getApplicationContext(), "resources"); + + Initialize(); + + makeButtonTouch(); + + setSurfaceView(new SDLSurface(this)); + + // Improve button outline contrast across all MaterialButtons + tintAllMaterialButtonOutlines(); + + // Apply saved graphics settings (renderer, scaling, aspect) + applySavedSettings(); + + // Apply orientation-specific constraints once at startup + int currentOrientation = getResources().getConfiguration().orientation; + applyConstraintsForOrientation(currentOrientation); + + + } + + private void setupBackPressedHandler() { + OnBackPressedCallback callback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + showExitDialog(); + } + }; + getOnBackPressedDispatcher().addCallback(this, callback); + } + + private void makeButtonTouch() { + MaterialButton btn_file = findViewById(R.id.btn_file); + if(btn_file != null) { + // Tap: show games list (from persisted folder). If none, prompt to pick folder. + btn_file.setOnClickListener(v -> { + SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); + String folderUri = prefs.getString("games_folder_uri", null); + if (TextUtils.isEmpty(folderUri)) { + pickGamesFolder(); + return; + } + showGamesListOrReselect(Uri.parse(folderUri)); + }); + // Long-press: reselect games folder + btn_file.setOnLongClickListener(v -> { + pickGamesFolder(); + return true; + }); + } + + // Combined saves dialog + MaterialButton btn_saves = findViewById(R.id.btn_saves); + if(btn_saves != null) { + btn_saves.setOnClickListener(v -> { + SavesDialogFragment dialog = new SavesDialogFragment(); + dialog.show(getSupportFragmentManager(), "saves_dialog"); + }); + } + + // BIOS picker + MaterialButton btn_bios = findViewById(R.id.btn_bios); + if (btn_bios != null) { + // Tap: pick multiple BIOS files + btn_bios.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + intent.setType("*/*"); + startActivityResultBiosPick.launch(intent); + }); + // Long-press: pick a BIOS folder + btn_bios.setOnLongClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + startActivityResultBiosFolderPick.launch(intent); + return true; + }); + } + + // Settings button opens dialog + MaterialButton btn_settings = findViewById(R.id.btn_settings); + if (btn_settings != null) { + btn_settings.setOnClickListener(v -> { + FragmentManager fm = getSupportFragmentManager(); + SettingsDialogFragment dialog = new SettingsDialogFragment(); + dialog.show(fm, "settings_dialog"); + }); + } + + // Toggle on-screen controls visibility + MaterialButton btnToggleControls = findViewById(R.id.btn_toggle_controls); + if (btnToggleControls != null) { + btnToggleControls.setOnClickListener(v -> { + View llDpad = findViewById(R.id.ll_pad_dpad); + View llRight = findViewById(R.id.ll_pad_right_buttons); + View llSelectStart = findViewById(R.id.ll_pad_select_start); + View llJoy = findViewById(R.id.ll_pad_joy); + + boolean currentlyVisible = (llDpad != null && llDpad.getVisibility() == View.VISIBLE); + setControlsVisible(!currentlyVisible); + }); + } + + // HUD toggle moved to Settings (Developer section) + + // Hide UI button + MaterialButton btn_hide_ui = findViewById(R.id.btn_hide_ui); + if(btn_hide_ui != null) { + btn_hide_ui.setOnClickListener(v -> { + toggleAllUIVisibility(); + }); + } + + // Small unhide button (appears when all UI is hidden) + MaterialButton btn_unhide_ui = findViewById(R.id.btn_unhide_ui); + if(btn_unhide_ui != null) { + btn_unhide_ui.setOnClickListener(v -> { + toggleAllUIVisibility(); // Show UI again + }); + } + + ////// + // RENDERER + + MaterialButton btn_ogl = findViewById(R.id.btn_ogl); + if(btn_ogl != null) { + btn_ogl.setOnClickListener(v -> { + NativeApp.renderGpu(12); + }); + } + MaterialButton btn_vulkan = findViewById(R.id.btn_vulkan); + if(btn_vulkan != null) { + btn_vulkan.setOnClickListener(v -> { + NativeApp.renderGpu(14); + }); + } + MaterialButton btn_sw = findViewById(R.id.btn_sw); + if(btn_sw != null) { + btn_sw.setOnClickListener(v -> { + NativeApp.renderGpu(13); + }); + } + + ////// + // PAD + + MaterialButton btn_pad_select = findViewById(R.id.btn_pad_select); + if(btn_pad_select != null) { + btn_pad_select.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_SELECT); + return true; + }); + } + MaterialButton btn_pad_start = findViewById(R.id.btn_pad_start); + if(btn_pad_start != null) { + btn_pad_start.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_START); + return true; + }); + } + + MaterialButton btn_pad_a = findViewById(R.id.btn_pad_a); + if(btn_pad_a != null) { + btn_pad_a.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_A); + return true; + }); + } + MaterialButton btn_pad_b = findViewById(R.id.btn_pad_b); + if(btn_pad_b != null) { + btn_pad_b.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_B); + return true; + }); + } + MaterialButton btn_pad_x = findViewById(R.id.btn_pad_x); + if(btn_pad_x != null) { + btn_pad_x.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_X); + return true; + }); + } + MaterialButton btn_pad_y = findViewById(R.id.btn_pad_y); + if(btn_pad_y != null) { + btn_pad_y.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_Y); + return true; + }); + } + + //// + + MaterialButton btn_pad_l1 = findViewById(R.id.btn_pad_l1); + if(btn_pad_l1 != null) { + btn_pad_l1.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_L1); + return true; + }); + } + MaterialButton btn_pad_r1 = findViewById(R.id.btn_pad_r1); + if(btn_pad_r1 != null) { + btn_pad_r1.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_R1); + return true; + }); + } + + MaterialButton btn_pad_l2 = findViewById(R.id.btn_pad_l2); + if(btn_pad_l2 != null) { + btn_pad_l2.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_L2); + return true; + }); + } + MaterialButton btn_pad_r2 = findViewById(R.id.btn_pad_r2); + if(btn_pad_r2 != null) { + btn_pad_r2.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_R2); + return true; + }); + } + + MaterialButton btn_pad_l3 = findViewById(R.id.btn_pad_l3); + if(btn_pad_l3 != null) { + btn_pad_l3.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_THUMBL); + return true; + }); + } + MaterialButton btn_pad_r3 = findViewById(R.id.btn_pad_r3); + if(btn_pad_r3 != null) { + btn_pad_r3.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_THUMBR); + return true; + }); + } + + //// + + final int PAD_L_UP = 110; + final int PAD_L_RIGHT = 111; + final int PAD_L_DOWN = 112; + final int PAD_L_LEFT = 113; + + final int PAD_R_UP = 120; + final int PAD_R_RIGHT = 121; + final int PAD_R_DOWN = 122; + final int PAD_R_LEFT = 123; + + MaterialButton btn_pad_joy_lt = findViewById(R.id.btn_pad_joy_lt); + if(btn_pad_joy_lt != null) { + btn_pad_joy_lt.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_UP); + sendKeyAction(v, event.getAction(), PAD_L_LEFT); + return true; + }); + } + MaterialButton btn_pad_joy_t = findViewById(R.id.btn_pad_joy_t); + if(btn_pad_joy_t != null) { + btn_pad_joy_t.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_UP); + return true; + }); + } + MaterialButton btn_pad_joy_rt = findViewById(R.id.btn_pad_joy_rt); + if(btn_pad_joy_rt != null) { + btn_pad_joy_rt.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_UP); + sendKeyAction(v, event.getAction(), PAD_L_RIGHT); + return true; + }); + } + MaterialButton btn_pad_joy_l = findViewById(R.id.btn_pad_joy_l); + if(btn_pad_joy_l != null) { + btn_pad_joy_l.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_LEFT); + return true; + }); + } + MaterialButton btn_pad_joy_r = findViewById(R.id.btn_pad_joy_r); + if(btn_pad_joy_r != null) { + btn_pad_joy_r.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_RIGHT); + return true; + }); + } + MaterialButton btn_pad_joy_lb = findViewById(R.id.btn_pad_joy_lb); + if(btn_pad_joy_lb != null) { + btn_pad_joy_lb.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_LEFT); + sendKeyAction(v, event.getAction(), PAD_L_DOWN); + return true; + }); + } + MaterialButton btn_pad_joy_b = findViewById(R.id.btn_pad_joy_b); + if(btn_pad_joy_b != null) { + btn_pad_joy_b.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_DOWN); + return true; + }); + } + MaterialButton btn_pad_joy_rb = findViewById(R.id.btn_pad_joy_rb); + if(btn_pad_joy_rb != null) { + btn_pad_joy_rb.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), PAD_L_RIGHT); + sendKeyAction(v, event.getAction(), PAD_L_DOWN); + return true; + }); + } + + // Draggable JoystickView (portrait/landscape layouts) + View joystick = findViewById(R.id.joystick_view); + if (joystick instanceof JoystickView) { + JoystickView jv = (JoystickView) joystick; + jv.setOnMoveListener((nx, ny, action) -> { + // Thresholds + final float T = 0.3f; + boolean up = ny < -T; + boolean down = ny > T; + boolean left = nx < -T; + boolean right = nx > T; + + // Issue key down/up transitions only when state changes + if (up != joyUpPressed) { + sendKeyAction(jv, up ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP, PAD_L_UP); + joyUpPressed = up; + } + if (down != joyDownPressed) { + sendKeyAction(jv, down ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP, PAD_L_DOWN); + joyDownPressed = down; + } + if (left != joyLeftPressed) { + sendKeyAction(jv, left ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP, PAD_L_LEFT); + joyLeftPressed = left; + } + if (right != joyRightPressed) { + sendKeyAction(jv, right ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP, PAD_L_RIGHT); + joyRightPressed = right; + } + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + // Ensure all released + if (joyUpPressed) sendKeyAction(jv, MotionEvent.ACTION_UP, PAD_L_UP); + if (joyDownPressed) sendKeyAction(jv, MotionEvent.ACTION_UP, PAD_L_DOWN); + if (joyLeftPressed) sendKeyAction(jv, MotionEvent.ACTION_UP, PAD_L_LEFT); + if (joyRightPressed) sendKeyAction(jv, MotionEvent.ACTION_UP, PAD_L_RIGHT); + joyUpPressed = joyDownPressed = joyLeftPressed = joyRightPressed = false; + } + }); + } + + //// + + MaterialButton btn_pad_dir_top = findViewById(R.id.btn_pad_dir_top); + if(btn_pad_dir_top != null) { + btn_pad_dir_top.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_UP); + return true; + }); + } + MaterialButton btn_pad_dir_bottom = findViewById(R.id.btn_pad_dir_bottom); + if(btn_pad_dir_bottom != null) { + btn_pad_dir_bottom.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_DOWN); + return true; + }); + } + MaterialButton btn_pad_dir_left = findViewById(R.id.btn_pad_dir_left); + if(btn_pad_dir_left != null) { + btn_pad_dir_left.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_LEFT); + return true; + }); + } + MaterialButton btn_pad_dir_right = findViewById(R.id.btn_pad_dir_right); + if(btn_pad_dir_right != null) { + btn_pad_dir_right.setOnTouchListener((v, event) -> { + sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_RIGHT); + return true; + }); + } + } + + private boolean allUIHidden = false; + + private void setControlsVisible(boolean visible) { + int vis = visible ? View.VISIBLE : View.GONE; + View llDpad = findViewById(R.id.ll_pad_dpad); + View llRight = findViewById(R.id.ll_pad_right_buttons); + View llSelectStart = findViewById(R.id.ll_pad_select_start); + View llJoy = findViewById(R.id.ll_pad_joy); + + if (llDpad != null) llDpad.setVisibility(vis); + if (llRight != null) llRight.setVisibility(vis); + if (llSelectStart != null) llSelectStart.setVisibility(vis); + if (llJoy != null) llJoy.setVisibility(vis); + } + + private void toggleAllUIVisibility() { + allUIHidden = !allUIHidden; + int vis = allUIHidden ? View.GONE : View.VISIBLE; + + // Hide/show all UI elements + View btnSettings = findViewById(R.id.btn_settings); + View btnToggleControls = findViewById(R.id.btn_toggle_controls); + View btnFile = findViewById(R.id.btn_file); + View btnBios = findViewById(R.id.btn_bios); + View btnSaves = findViewById(R.id.btn_saves); + View btnHideUI = findViewById(R.id.btn_hide_ui); + + if (btnSettings != null) btnSettings.setVisibility(vis); + if (btnToggleControls != null) btnToggleControls.setVisibility(vis); + if (btnFile != null) btnFile.setVisibility(vis); + if (btnBios != null) btnBios.setVisibility(vis); + if (btnSaves != null) btnSaves.setVisibility(vis); + if (btnHideUI != null) btnHideUI.setVisibility(vis); + + // Hide/show on-screen controls + if (!allUIHidden) { + // When showing UI again, restore controls to their previous state + setControlsVisible(true); // You might want to remember the previous state + } else { + // When hiding all UI, also hide controls + setControlsVisible(false); + } + + // Show a small unhide button when UI is hidden + View unhideButton = findViewById(R.id.btn_unhide_ui); + if (unhideButton != null) { + unhideButton.setVisibility(allUIHidden ? View.VISIBLE : View.GONE); + } + } + + private void tintAllMaterialButtonOutlines() { + // Brand strokes + final ColorStateList strokeDefault = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.brand_outline)); + final ColorStateList strokePrimary = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.brand_primary)); + final ColorStateList strokeSecondary = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.brand_secondary)); + + View root = findViewById(android.R.id.content); + if (root instanceof ViewGroup) { + traverseAndTintButtons((ViewGroup) root, strokeDefault, strokePrimary, strokeSecondary); + } + } + + private void traverseAndTintButtons(ViewGroup group, ColorStateList strokeDefault, ColorStateList strokePrimary, ColorStateList strokeSecondary) { + for (int i = 0; i < group.getChildCount(); i++) { + View child = group.getChildAt(i); + if (child instanceof ViewGroup) { + traverseAndTintButtons((ViewGroup) child, strokeDefault, strokePrimary, strokeSecondary); + } + if (child instanceof MaterialButton) { + MaterialButton mb = (MaterialButton) child; + // Ensure ripple matches brand globally + mb.setRippleColor(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.brand_ripple))); + int id = mb.getId(); + if (id == R.id.btn_settings || id == R.id.btn_toggle_controls) { + mb.setStrokeColor(strokePrimary); + } else if (id == R.id.btn_file || id == R.id.btn_bios || id == R.id.btn_saves || id == R.id.btn_hide_ui) { + mb.setStrokeColor(strokeSecondary); + } else if (id == R.id.btn_pad_y) { // Triangle = green + final int base = ContextCompat.getColor(this, R.color.ps2_triangle_green); + ColorStateList stateful = pressedColorStateList(base); + mb.setStrokeColor(stateful); + mb.setTextColor(stateful); + } else if (id == R.id.btn_pad_b) { // Circle = red + final int base = ContextCompat.getColor(this, R.color.ps2_circle_red); + ColorStateList stateful = pressedColorStateList(base); + mb.setStrokeColor(stateful); + mb.setTextColor(stateful); + } else if (id == R.id.btn_pad_a) { // Cross = blue + final int base = ContextCompat.getColor(this, R.color.ps2_cross_blue); + ColorStateList stateful = pressedColorStateList(base); + mb.setStrokeColor(stateful); + mb.setTextColor(stateful); + } else if (id == R.id.btn_pad_x) { // Square = pink + final int base = ContextCompat.getColor(this, R.color.ps2_square_pink); + ColorStateList stateful = pressedColorStateList(base); + mb.setStrokeColor(stateful); + mb.setTextColor(stateful); + } else if (id == R.id.btn_pad_dir_top || id == R.id.btn_pad_dir_left || id == R.id.btn_pad_dir_right || id == R.id.btn_pad_dir_bottom) { + // D-pad arrow icon tint to brand accent + ColorStateList iconTint = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.brand_accent)); + mb.setIconTint(iconTint); + } else { + mb.setStrokeColor(strokeDefault); + } + mb.setStrokeWidth(1); + } + } + } + + private ColorStateList pressedColorStateList(int base) { + int pressed = darkenColor(base, 0.85f); // 15% darker when pressed + int[][] states = new int[][]{ + new int[]{android.R.attr.state_pressed}, + new int[]{} + }; + int[] colors = new int[]{ + pressed, + base + }; + return new ColorStateList(states, colors); + } + + private int darkenColor(int color, float factor) { + int a = (color >> 24) & 0xFF; + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + r = Math.max(0, Math.min(255, (int)(r * factor))); + g = Math.max(0, Math.min(255, (int)(g * factor))); + b = Math.max(0, Math.min(255, (int)(b * factor))); + return (a << 24) | (r << 16) | (g << 8) | b; + } + + private void applySavedSettings() { + SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); + // Renderer: 12=OpenGL, 13=Software, 14=Vulkan + int renderer = prefs.getInt("renderer", 14); + NativeApp.renderGpu(renderer); + + // Resolution scale multiplier (float), default 1.0 + float scale = prefs.getFloat("upscale_multiplier", 1.0f); + NativeApp.renderUpscalemultiplier(scale); + + // Aspect ratio: 0=Stretch, 1=Auto 4:3/3:2, 2=4:3, 3=16:9, 4=10:7 + int aspectRatio = prefs.getInt("aspect_ratio", 1); // Default to Auto 4:3/3:2 + NativeApp.setAspectRatio(aspectRatio); + + // Widescreen patches + boolean widescreenPatches = prefs.getBoolean("widescreen_patches", false); + NativeApp.setWidescreenPatches(widescreenPatches); + + // No interlacing patches + boolean noInterlacingPatches = prefs.getBoolean("no_interlacing_patches", false); + NativeApp.setNoInterlacingPatches(noInterlacingPatches); + + // HUD visibility + boolean hudVisible = prefs.getBoolean("hud_visible", false); + NativeApp.setHudVisible(hudVisible); + } + + public final ActivityResultLauncher startActivityResultLocalFilePlay = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if(result.getResultCode() == Activity.RESULT_OK) { + try { + Intent _intent = result.getData(); + if(_intent != null) { + m_szGamefile = _intent.getDataString(); + if(!TextUtils.isEmpty(m_szGamefile)) { + restartEmuThread(); + } + } + } + catch (Exception ignored) {} + } + } + ); + + public final ActivityResultLauncher startActivityResultBiosPick = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + try { + Intent data = result.getData(); + if (data != null) { + File biosDir = new File(getApplicationContext().getExternalFilesDir(null), "bios"); + if (!biosDir.exists()) biosDir.mkdirs(); + + ClipData clipData = data.getClipData(); + if (clipData != null && clipData.getItemCount() > 0) { + for (int i = 0; i < clipData.getItemCount(); i++) { + Uri uri = clipData.getItemAt(i).getUri(); + importSingleBiosUri(uri, biosDir); + } + } else { + Uri uri = data.getData(); + if (uri != null) { + importSingleBiosUri(uri, biosDir); + } + } + + } + } catch (Exception ignored) {} + } + }); + + public final ActivityResultLauncher startActivityResultBiosFolderPick = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + try { + Intent data = result.getData(); + if (data != null) { + Uri treeUri = data.getData(); + if (treeUri != null) { + // Persist read permission for future imports, optional + final int takeFlags = (data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)); + getContentResolver().takePersistableUriPermission(treeUri, takeFlags); + + DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri); + if (pickedDir != null && pickedDir.isDirectory()) { + File biosDir = new File(getApplicationContext().getExternalFilesDir(null), "bios"); + if (!biosDir.exists()) biosDir.mkdirs(); + copyDocumentTreeToDirectory(pickedDir, biosDir); + } + } + } + } catch (Exception ignored) {} + } + }); + + public final ActivityResultLauncher startActivityResultGamesFolderPick = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + try { + Intent data = result.getData(); + if (data != null) { + Uri treeUri = data.getData(); + if (treeUri != null) { + final int takeFlags = (data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)); + try { + getContentResolver().takePersistableUriPermission(treeUri, takeFlags); + } catch (SecurityException ignored) {} + // Save folder and show games + getSharedPreferences("app_prefs", MODE_PRIVATE) + .edit() + .putString("games_folder_uri", treeUri.toString()) + .apply(); + showGamesListOrReselect(treeUri); + } + } + } catch (Exception ignored) {} + } + }); + + private void importSingleBiosUri(Uri uri, File biosDir) { + if (uri == null) return; + String displayName = getDisplayNameFromUri(this, uri); + if (TextUtils.isEmpty(displayName)) displayName = "bios.bin"; + File outFile = new File(biosDir, displayName); + copyUriToFile(this, uri, outFile); + } + + private void copyDocumentTreeToDirectory(DocumentFile dir, File outDir) { + if (dir == null || !dir.isDirectory()) return; + DocumentFile[] children = dir.listFiles(); + if (children == null) return; + for (DocumentFile child : children) { + if (child == null) continue; + if (child.isDirectory()) { + File sub = new File(outDir, child.getName() != null ? child.getName() : "folder"); + if (!sub.exists()) sub.mkdirs(); + copyDocumentTreeToDirectory(child, sub); + } else if (child.isFile()) { + String name = child.getName(); + if (TextUtils.isEmpty(name)) name = "bios.bin"; + File dest = new File(outDir, name); + copyUriToFile(this, child.getUri(), dest); + } + } + } + + private static String getDisplayNameFromUri(Context context, Uri uri) { + String name = null; + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor != null) { + try { + int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (nameIndex >= 0 && cursor.moveToFirst()) { + name = cursor.getString(nameIndex); + } + } finally { + cursor.close(); + } + } + if (TextUtils.isEmpty(name)) { + name = uri.getLastPathSegment(); + } + return name; + } + + private static boolean copyUriToFile(Context context, Uri uri, File destFile) { + InputStream is = null; + FileOutputStream os = null; + try { + is = context.getContentResolver().openInputStream(uri); + if (is == null) return false; + os = new FileOutputStream(destFile); + byte[] buffer = new byte[8192]; + int read; + while ((read = is.read(buffer)) != -1) { + os.write(buffer, 0, read); + } + os.flush(); + return true; + } catch (Exception e) { + return false; + } finally { + try { if (is != null) is.close(); } catch (Exception ignored) {} + try { if (os != null) os.close(); } catch (Exception ignored) {} + } + } + + @Override + protected void onPause() { + NativeApp.pause(); + super.onPause(); + //// + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(true); + } + } + + @Override + protected void onResume() { + NativeApp.resume(); + super.onResume(); + //// + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(false); + } + // Re-assert full screen when returning to the activity + hideStatusBar(); + } + + @Override + protected void onDestroy() { + NativeApp.shutdown(); + super.onDestroy(); + //// + if (mHIDDeviceManager != null) { + HIDDeviceManager.release(mHIDDeviceManager); + mHIDDeviceManager = null; + } + //// + if (mEmulationThread != null) { + try { + mEmulationThread.join(); + mEmulationThread = null; + } + catch (InterruptedException ignored) {} + } + + int appPid = android.os.Process.myPid(); + android.os.Process.killProcess(appPid); + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + + public void Initialize() { + NativeApp.initializeOnce(getApplicationContext()); + + // Set up JNI + SDLControllerManager.nativeSetupJNI(); + + // Initialize state + SDLControllerManager.initialize(); + + // Load and apply saved settings + SettingsDialogFragment.loadAndApplySettings(this); + + mHIDDeviceManager = HIDDeviceManager.acquire(this); + } + + private void setSurfaceView(Object p_value) { + FrameLayout fl_board = findViewById(R.id.fl_board); + if(fl_board != null) { + if(fl_board.getChildCount() > 0) { + fl_board.removeAllViews(); + } + //// + if(p_value instanceof SDLSurface) { + fl_board.addView((SDLSurface)p_value); + } + } + } + + public void startEmuThread() { + if(!isThread()) { + mEmulationThread = new Thread(() -> NativeApp.runVMThread(m_szGamefile)); + mEmulationThread.start(); + } + } + + private void restartEmuThread() { + NativeApp.shutdown(); + if (mEmulationThread != null) { + try { + mEmulationThread.join(); + mEmulationThread = null; + } + catch (InterruptedException ignored) {} + } + //// + startEmuThread(); + } + + // Public API for UI components to reboot the emulator + public void rebootEmu() { + if (!TextUtils.isEmpty(m_szGamefile)) { + restartEmuThread(); + } else { + // No game loaded; just shutdown for safety + NativeApp.shutdown(); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) { + SDLControllerManager.handleJoystickMotionEvent(event); + return true; + } + return super.onGenericMotionEvent(event); + } + + @Override + public boolean onKeyDown(int p_keyCode, KeyEvent p_event) { + if ((p_event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + if (p_event.getRepeatCount() == 0) { + SDLControllerManager.onNativePadDown(p_event.getDeviceId(), p_keyCode); + return true; + } + } + else { + if (p_keyCode == KeyEvent.KEYCODE_BACK) { + showExitDialog(); + return true; + } + } + return super.onKeyDown(p_keyCode, p_event); + } + + @Override + public boolean onKeyUp(int p_keyCode, KeyEvent p_event) { + if ((p_event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + if (p_event.getRepeatCount() == 0) { + SDLControllerManager.onNativePadUp(p_event.getDeviceId(), p_keyCode); + return true; + } + } + return super.onKeyUp(p_keyCode, p_event); + } + + public static void sendKeyAction(View p_view, int p_action, int p_keycode) { + if(p_action == MotionEvent.ACTION_DOWN) { + p_view.setPressed(true); + int pad_force = 0; + if(p_keycode >= 110) { + float _abs = 90; // Joystic test value + _abs = Math.min(_abs, 100); + pad_force = (int) (_abs * 32766.0f / 100); + } + NativeApp.setPadButton(p_keycode, pad_force, true); + } else if(p_action == MotionEvent.ACTION_UP || p_action == MotionEvent.ACTION_CANCEL) { + p_view.setPressed(false); + NativeApp.setPadButton(p_keycode, 0, false); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Asset copy helpers used on first launch to seed default resources + private void copyAssetAll(Context context, String srcPath) { + AssetManager assetMgr = context.getAssets(); + try { + String[] assets = assetMgr.list(srcPath); + String destPath = context.getExternalFilesDir(null) + File.separator + srcPath; + if (assets != null) { + if (assets.length == 0) { + copyFile(context, srcPath, destPath); + } else { + File dir = new File(destPath); + if (!dir.exists()) dir.mkdirs(); + for (String element : assets) { + copyAssetAll(context, srcPath + File.separator + element); + } + } + } + } catch (IOException ignored) {} + } + + private static void copyFile(Context context, String srcFile, String destFile) { + AssetManager assetMgr = context.getAssets(); + InputStream is = null; + FileOutputStream os = null; + try { + is = assetMgr.open(srcFile); + boolean exists = new File(destFile).exists(); + if (srcFile.contains("shaders")) { + exists = false; // always refresh shaders + } + if (!exists) { + File parent = new File(destFile).getParentFile(); + if (parent != null && !parent.exists()) parent.mkdirs(); + os = new FileOutputStream(destFile); + byte[] buffer = new byte[4096]; + int read; + while ((read = is.read(buffer)) != -1) { + os.write(buffer, 0, read); + } + os.flush(); + } + } catch (IOException ignored) { + } finally { + try { if (is != null) is.close(); } catch (IOException ignored) {} + try { if (os != null) os.close(); } catch (IOException ignored) {} + } + } + + + + @Override + public void onBackPressed() { + // Fallback for older Android versions + showExitDialog(); + } + + private void showExitDialog() { + new AlertDialog.Builder(this) + .setTitle("Exit App") + .setMessage("Do you want to exit PSX2?") + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton("Exit", (dialog, which) -> { + // Stop emulator first + NativeApp.shutdown(); + // Quit the app + finishAffinity(); + finishAndRemoveTask(); + // As a fallback ensure process exit + System.exit(0); + }) + .setNegativeButton("Cancel", null) + .show(); + } +} diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/NativeApp.java b/app/src/main/java/com/izzy2lost/psx2/NativeApp.java similarity index 65% rename from app/src/main/java/kr/co/iefriends/pcsx2/NativeApp.java rename to app/src/main/java/com/izzy2lost/psx2/NativeApp.java index 6f3e14a..676fe05 100644 --- a/app/src/main/java/kr/co/iefriends/pcsx2/NativeApp.java +++ b/app/src/main/java/com/izzy2lost/psx2/NativeApp.java @@ -1,4 +1,4 @@ -package kr.co.iefriends.pcsx2; +package com.izzy2lost.psx2; import android.content.ContentResolver; import android.content.Context; @@ -58,6 +58,32 @@ public class NativeApp { public static native void renderGpu(int value); public static native void renderPreloading(int value); + // HUD/OSD visibility toggle + public static native void setHudVisible(boolean visible); + + // Widescreen and interlacing patches + public static native void setWidescreenPatches(boolean enabled); + public static native void setNoInterlacingPatches(boolean enabled); + + // Texture loading options for texture packs + public static native void setLoadTextures(boolean enabled); + public static native void setAsyncTextureLoading(boolean enabled); + public static native void setBlendingAccuracy(int level); + + // Per-game settings + public static native void saveGameSettings(String filename, int blendingAccuracy, int renderer, + int resolution, boolean widescreenPatches, + boolean noInterlacingPatches, boolean enablePatches, + boolean enableCheats); + public static native void saveGameSettingsToPath(String fullPath, int blendingAccuracy, int renderer, + int resolution, boolean widescreenPatches, + boolean noInterlacingPatches, boolean enablePatches, + boolean enableCheats); + public static native void deleteGameSettings(String filename); + public static native String getGameSerial(String gameUri); + public static native String getGameCrc(String gameUri); + public static native String getCurrentGameSerial(); + public static native void onNativeSurfaceCreated(); public static native void onNativeSurfaceChanged(Surface surface, int w, int h); public static native void onNativeSurfaceDestroyed(); diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/SDLControllerManager.java b/app/src/main/java/com/izzy2lost/psx2/SDLControllerManager.java similarity index 99% rename from app/src/main/java/kr/co/iefriends/pcsx2/SDLControllerManager.java rename to app/src/main/java/com/izzy2lost/psx2/SDLControllerManager.java index 1897911..00e985f 100644 --- a/app/src/main/java/kr/co/iefriends/pcsx2/SDLControllerManager.java +++ b/app/src/main/java/com/izzy2lost/psx2/SDLControllerManager.java @@ -1,4 +1,4 @@ -package kr.co.iefriends.pcsx2; +package com.izzy2lost.psx2; import android.content.Context; import android.os.Build; diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/SDLSurface.java b/app/src/main/java/com/izzy2lost/psx2/SDLSurface.java similarity index 97% rename from app/src/main/java/kr/co/iefriends/pcsx2/SDLSurface.java rename to app/src/main/java/com/izzy2lost/psx2/SDLSurface.java index 15753e0..cae0cb5 100644 --- a/app/src/main/java/kr/co/iefriends/pcsx2/SDLSurface.java +++ b/app/src/main/java/com/izzy2lost/psx2/SDLSurface.java @@ -1,4 +1,4 @@ -package kr.co.iefriends.pcsx2; +package com.izzy2lost.psx2; import android.content.Context; import android.util.AttributeSet; @@ -36,7 +36,6 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback { @Override public void surfaceChanged(@NonNull SurfaceHolder p_holder, int p_format, int p_width, int p_height) { NativeApp.onNativeSurfaceChanged(p_holder.getSurface(), p_width, p_height); - //// MainActivity _nativeActivity = (MainActivity) getContext(); if(_nativeActivity != null) { _nativeActivity.startEmuThread(); diff --git a/app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java new file mode 100644 index 0000000..28b79c5 --- /dev/null +++ b/app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java @@ -0,0 +1,205 @@ +package com.izzy2lost.psx2; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class SavesDialogFragment extends DialogFragment { + + public static class SaveSlot { + public int slot; + public String title; + public String timestamp; + public byte[] screenshot; + public boolean isEmpty; + public String gamePath; + + public SaveSlot(int slot, String gamePath) { + this.slot = slot; + this.gamePath = gamePath; + this.isEmpty = true; + this.title = "Empty Slot " + slot; + this.timestamp = ""; + } + } + + private static class SaveSlotAdapter extends RecyclerView.Adapter { + private List saveSlots; + private OnSlotClickListener listener; + + interface OnSlotClickListener { + void onSave(int slot); + void onLoad(int slot); + } + + SaveSlotAdapter(List saveSlots, OnSlotClickListener listener) { + this.saveSlots = saveSlots; + this.listener = listener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_save_slot, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + SaveSlot slot = saveSlots.get(position); + holder.bind(slot, listener); + } + + @Override + public int getItemCount() { + return saveSlots.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + TextView title, timestamp; + ImageView screenshot; + View saveButton, loadButton; + + ViewHolder(@NonNull View itemView) { + super(itemView); + title = itemView.findViewById(R.id.tv_slot_title); + timestamp = itemView.findViewById(R.id.tv_slot_timestamp); + screenshot = itemView.findViewById(R.id.iv_slot_screenshot); + saveButton = itemView.findViewById(R.id.btn_slot_save); + loadButton = itemView.findViewById(R.id.btn_slot_load); + } + + void bind(SaveSlot slot, OnSlotClickListener listener) { + title.setText(slot.title); + timestamp.setText(slot.timestamp); + + // Set screenshot if available + if (slot.screenshot != null && slot.screenshot.length > 0) { + Bitmap bitmap = BitmapFactory.decodeByteArray(slot.screenshot, 0, slot.screenshot.length); + screenshot.setImageBitmap(bitmap); + screenshot.setVisibility(View.VISIBLE); + + // Make screenshot clickable to show enlarged version + screenshot.setOnClickListener(v -> showEnlargedScreenshot(slot.screenshot, slot.title)); + } else { + screenshot.setVisibility(View.GONE); + screenshot.setOnClickListener(null); + } + + // Save button - always enabled + saveButton.setOnClickListener(v -> listener.onSave(slot.slot)); + + // Load button - only enabled if slot has data + loadButton.setEnabled(!slot.isEmpty); + loadButton.setAlpha(slot.isEmpty ? 0.5f : 1.0f); + loadButton.setOnClickListener(v -> { + if (!slot.isEmpty) { + listener.onLoad(slot.slot); + } + }); + } + + private void showEnlargedScreenshot(byte[] screenshotData, String title) { + if (screenshotData == null || screenshotData.length == 0) return; + + Context context = itemView.getContext(); + Bitmap bitmap = BitmapFactory.decodeByteArray(screenshotData, 0, screenshotData.length); + + // Create enlarged screenshot dialog + AlertDialog.Builder builder = new AlertDialog.Builder(context); + View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_screenshot_preview, null); + + ImageView enlargedImageView = dialogView.findViewById(R.id.iv_enlarged_screenshot); + TextView titleView = dialogView.findViewById(R.id.tv_screenshot_title); + + enlargedImageView.setImageBitmap(bitmap); + titleView.setText(title); + + builder.setView(dialogView) + .setPositiveButton("Close", (dialog, which) -> dialog.dismiss()) + .create() + .show(); + } + } + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Context ctx = requireContext(); + View view = LayoutInflater.from(ctx).inflate(R.layout.dialog_saves, null, false); + + RecyclerView recyclerView = view.findViewById(R.id.rv_save_slots); + recyclerView.setLayoutManager(new LinearLayoutManager(ctx)); + + // Create save slots (1-10) + List saveSlots = new ArrayList<>(); + for (int i = 1; i <= 10; i++) { + SaveSlot slot = new SaveSlot(i, NativeApp.getGamePathSlot(i)); + + // Check if slot has data and get screenshot + byte[] screenshot = NativeApp.getImageSlot(i); + if (screenshot != null && screenshot.length > 0) { + slot.isEmpty = false; + slot.screenshot = screenshot; + slot.title = "Save Slot " + i; + + // Create a reasonable timestamp (you might want to get this from native code) + slot.timestamp = "Saved " + SimpleDateFormat.getDateTimeInstance( + SimpleDateFormat.SHORT, SimpleDateFormat.SHORT, Locale.getDefault()) + .format(new Date()); + } + + saveSlots.add(slot); + } + + SaveSlotAdapter adapter = new SaveSlotAdapter(saveSlots, new SaveSlotAdapter.OnSlotClickListener() { + @Override + public void onSave(int slot) { + if (NativeApp.saveStateToSlot(slot)) { + // Success - refresh the dialog or close it + dismiss(); + } + NativeApp.resume(); + } + + @Override + public void onLoad(int slot) { + if (NativeApp.loadStateFromSlot(slot)) { + // Success + dismiss(); + } + NativeApp.resume(); + } + }); + + recyclerView.setAdapter(adapter); + + AlertDialog.Builder builder = new AlertDialog.Builder(ctx); + builder.setTitle("Save States") + .setView(view) + .setNegativeButton("Cancel", (d, w) -> d.dismiss()); + + return builder.create(); + } +} diff --git a/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java new file mode 100644 index 0000000..3d97a85 --- /dev/null +++ b/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java @@ -0,0 +1,276 @@ +package com.izzy2lost.psx2; + +import android.app.Dialog; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Spinner; +import android.widget.Switch; +import android.content.res.ColorStateList; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.core.widget.CompoundButtonCompat; +import androidx.core.content.ContextCompat; + +public class SettingsDialogFragment extends DialogFragment { + + private static final String PREFS = "app_prefs"; + // Renderer constants (match native GSRendererType values used elsewhere) + private static final int RENDERER_OPENGL = 12; + private static final int RENDERER_SOFTWARE = 13; + private static final int RENDERER_VULKAN = 14; + + // Static method to load and apply settings on app startup + public static void loadAndApplySettings(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE); + + int renderer = prefs.getInt("renderer", RENDERER_VULKAN); + float scale = prefs.getFloat("upscale_multiplier", 1.0f); + int aspectRatio = prefs.getInt("aspect_ratio", 1); + boolean widescreenPatches = prefs.getBoolean("widescreen_patches", false); + boolean noInterlacingPatches = prefs.getBoolean("no_interlacing_patches", false); + boolean loadTextures = prefs.getBoolean("load_textures", false); + boolean asyncTextureLoading = prefs.getBoolean("async_texture_loading", true); + boolean hudVisible = prefs.getBoolean("hud_visible", false); + + // Apply all settings + NativeApp.renderGpu(renderer); + NativeApp.renderUpscalemultiplier(scale); + NativeApp.setAspectRatio(aspectRatio); + NativeApp.setWidescreenPatches(widescreenPatches); + NativeApp.setNoInterlacingPatches(noInterlacingPatches); + NativeApp.setLoadTextures(loadTextures); + NativeApp.setAsyncTextureLoading(asyncTextureLoading); + NativeApp.setHudVisible(hudVisible); + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Context ctx = requireContext(); + View view = LayoutInflater.from(ctx).inflate(R.layout.dialog_settings, null, false); + + RadioGroup rgRenderer = view.findViewById(R.id.rg_renderer); + RadioButton rbGl = view.findViewById(R.id.rb_renderer_gl); + RadioButton rbVk = view.findViewById(R.id.rb_renderer_vk); + RadioButton rbSw = view.findViewById(R.id.rb_renderer_sw); + Spinner spScale = view.findViewById(R.id.sp_scale); + Spinner spAspectRatio = view.findViewById(R.id.sp_aspect_ratio); + Switch swWidescreen = view.findViewById(R.id.sw_widescreen); + Switch swNoInterlacing = view.findViewById(R.id.sw_no_interlacing); + Switch swLoadTextures = view.findViewById(R.id.sw_load_textures); + Switch swAsyncTextureLoading = view.findViewById(R.id.sw_async_texture_loading); + Switch swDevHud = view.findViewById(R.id.sw_dev_hud); + View btnPower = view.findViewById(R.id.btn_power); + View btnReboot = view.findViewById(R.id.btn_reboot); + + // Brand tints for checked/activated states to replace aqua + int brand = ContextCompat.getColor(ctx, R.color.brand_primary); + int outline = ContextCompat.getColor(ctx, R.color.brand_outline); + int[][] states = new int[][]{ + new int[]{android.R.attr.state_checked}, + new int[]{} + }; + int[] colors = new int[]{ + brand, + outline + }; + ColorStateList brandChecked = new ColorStateList(states, colors); + + // RadioButtons + if (rbGl != null) CompoundButtonCompat.setButtonTintList(rbGl, brandChecked); + if (rbVk != null) CompoundButtonCompat.setButtonTintList(rbVk, brandChecked); + if (rbSw != null) CompoundButtonCompat.setButtonTintList(rbSw, brandChecked); + + // Switches (thumb = brand when checked; track = subtle brand when checked) + ColorStateList thumbTint = new ColorStateList( + new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}}, + new int[]{brand, outline} + ); + int brandTrack = ColorStateList.valueOf(brand).withAlpha(100).getDefaultColor(); + int outlineTrack = ColorStateList.valueOf(outline).withAlpha(80).getDefaultColor(); + ColorStateList trackTint = new ColorStateList( + new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}}, + new int[]{brandTrack, outlineTrack} + ); + if (swWidescreen != null) { + swWidescreen.setThumbTintList(thumbTint); + swWidescreen.setTrackTintList(trackTint); + } + if (swNoInterlacing != null) { + swNoInterlacing.setThumbTintList(thumbTint); + swNoInterlacing.setTrackTintList(trackTint); + } + if (swLoadTextures != null) { + swLoadTextures.setThumbTintList(thumbTint); + swLoadTextures.setTrackTintList(trackTint); + } + if (swAsyncTextureLoading != null) { + swAsyncTextureLoading.setThumbTintList(thumbTint); + swAsyncTextureLoading.setTrackTintList(trackTint); + } + if (swDevHud != null) { + swDevHud.setThumbTintList(thumbTint); + swDevHud.setTrackTintList(trackTint); + } + + if (btnPower != null) { + btnPower.setOnClickListener(v -> { + new AlertDialog.Builder(requireContext()) + .setTitle("Power Off") + .setMessage("Quit the app?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Quit", (d1, w1) -> { + // Stop emulator first + NativeApp.shutdown(); + // Close dialog + dismissAllowingStateLoss(); + // Quit the whole app/activity task + if (getActivity() != null) { + getActivity().finishAffinity(); + getActivity().finishAndRemoveTask(); + } + // As a fallback ensure process exit + System.exit(0); + }) + .show(); + }); + } + + if (btnReboot != null) { + btnReboot.setOnClickListener(v -> { + new AlertDialog.Builder(requireContext()) + .setTitle("Reboot") + .setMessage("Restart the current game?") + .setNegativeButton("Cancel", null) + .setPositiveButton("Reboot", (d1, w1) -> { + if (requireActivity() instanceof MainActivity) { + ((MainActivity) requireActivity()).rebootEmu(); + } else { + NativeApp.shutdown(); + } + dismissAllowingStateLoss(); + }) + .show(); + }); + } + + // Populate scale spinner (1x..8x) + ArrayAdapter scaleAdapter = ArrayAdapter.createFromResource(ctx, + R.array.scale_entries, android.R.layout.simple_spinner_item); + scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spScale.setAdapter(scaleAdapter); + + // Populate aspect ratio spinner + ArrayAdapter aspectAdapter = ArrayAdapter.createFromResource(ctx, + R.array.aspect_ratio_entries, android.R.layout.simple_spinner_item); + aspectAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spAspectRatio.setAdapter(aspectAdapter); + + SharedPreferences prefs = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE); + int savedRenderer = prefs.getInt("renderer", RENDERER_VULKAN); + float savedScale = prefs.getFloat("upscale_multiplier", 1.0f); + int savedAspectRatio = prefs.getInt("aspect_ratio", 1); // 1 = Auto 4:3/3:2 (recommended) + boolean savedWidescreen = prefs.getBoolean("widescreen_patches", false); + boolean savedNoInterlacing = prefs.getBoolean("no_interlacing_patches", false); + boolean savedLoadTextures = prefs.getBoolean("load_textures", false); + boolean savedAsyncTextureLoading = prefs.getBoolean("async_texture_loading", true); + boolean savedHud = prefs.getBoolean("hud_visible", false); + + if (savedRenderer == RENDERER_VULKAN) rbVk.setChecked(true); + else if (savedRenderer == RENDERER_SOFTWARE) rbSw.setChecked(true); + else rbGl.setChecked(true); + + int scaleIndex = scaleToIndex(savedScale); + if (scaleIndex < 0 || scaleIndex >= scaleAdapter.getCount()) scaleIndex = 0; + spScale.setSelection(scaleIndex); + + if (savedAspectRatio >= 0 && savedAspectRatio < aspectAdapter.getCount()) { + spAspectRatio.setSelection(savedAspectRatio); + } else { + spAspectRatio.setSelection(1); // Default to Auto 4:3/3:2 + } + + swWidescreen.setChecked(savedWidescreen); + swNoInterlacing.setChecked(savedNoInterlacing); + swLoadTextures.setChecked(savedLoadTextures); + swAsyncTextureLoading.setChecked(savedAsyncTextureLoading); + if (swDevHud != null) swDevHud.setChecked(savedHud); + + AlertDialog.Builder b = new AlertDialog.Builder(requireContext()); + b.setTitle("Graphics Settings") + .setView(view) + .setNegativeButton("Cancel", (d, w) -> d.dismiss()) + .setPositiveButton("Save", (d, w) -> { + int renderer = RENDERER_OPENGL; + int checked = rgRenderer.getCheckedRadioButtonId(); + if (checked == R.id.rb_renderer_vk) renderer = RENDERER_VULKAN; + else if (checked == R.id.rb_renderer_sw) renderer = RENDERER_SOFTWARE; + + float scale = indexToScale(spScale.getSelectedItemPosition()); + int aspectRatio = spAspectRatio.getSelectedItemPosition(); + boolean widescreenPatches = swWidescreen.isChecked(); + boolean noInterlacingPatches = swNoInterlacing.isChecked(); + boolean loadTextures = swLoadTextures.isChecked(); + boolean asyncTextureLoading = swAsyncTextureLoading.isChecked(); + boolean hudVisible = (swDevHud != null && swDevHud.isChecked()); + + // Persist + prefs.edit() + .putInt("renderer", renderer) + .putFloat("upscale_multiplier", scale) + .putInt("aspect_ratio", aspectRatio) + .putBoolean("widescreen_patches", widescreenPatches) + .putBoolean("no_interlacing_patches", noInterlacingPatches) + .putBoolean("load_textures", loadTextures) + .putBoolean("async_texture_loading", asyncTextureLoading) + .putBoolean("hud_visible", hudVisible) + .apply(); + + // Apply immediately + NativeApp.renderGpu(renderer); + NativeApp.renderUpscalemultiplier(scale); + NativeApp.setAspectRatio(aspectRatio); + NativeApp.setWidescreenPatches(widescreenPatches); + NativeApp.setNoInterlacingPatches(noInterlacingPatches); + NativeApp.setLoadTextures(loadTextures); + NativeApp.setAsyncTextureLoading(asyncTextureLoading); + NativeApp.setHudVisible(hudVisible); + }); + + return b.create(); + } + + private static int scaleToIndex(float scale) { + if (scale <= 1.0f) return 0; + if (scale <= 2.0f) return 1; + if (scale <= 3.0f) return 2; + if (scale <= 4.0f) return 3; + if (scale <= 5.0f) return 4; + if (scale <= 6.0f) return 5; + if (scale <= 7.0f) return 6; + return 7; // 8x or higher + } + + private static float indexToScale(int index) { + switch (index) { + case 1: return 2.0f; + case 2: return 3.0f; + case 3: return 4.0f; + case 4: return 5.0f; + case 5: return 6.0f; + case 6: return 7.0f; + case 7: return 8.0f; + case 0: + default: return 1.0f; + } + } +} diff --git a/app/src/main/java/kr/co/iefriends/pcsx2/MainActivity.java b/app/src/main/java/kr/co/iefriends/pcsx2/MainActivity.java deleted file mode 100644 index c9a982c..0000000 --- a/app/src/main/java/kr/co/iefriends/pcsx2/MainActivity.java +++ /dev/null @@ -1,539 +0,0 @@ -package kr.co.iefriends.pcsx2; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; - -import com.google.android.material.button.MaterialButton; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -public class MainActivity extends AppCompatActivity { - private String m_szGamefile = ""; - - private HIDDeviceManager mHIDDeviceManager; - private Thread mEmulationThread = null; - - private boolean isThread() { - if (mEmulationThread != null) { - Thread.State _thread_state = mEmulationThread.getState(); - return _thread_state == Thread.State.BLOCKED - || _thread_state == Thread.State.RUNNABLE - || _thread_state == Thread.State.TIMED_WAITING - || _thread_state == Thread.State.WAITING; - } - return false; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - // Default resources - copyAssetAll(getApplicationContext(), "bios"); - copyAssetAll(getApplicationContext(), "resources"); - - Initialize(); - - makeButtonTouch(); - - setSurfaceView(new SDLSurface(this)); - } - - // Buttons - private void makeButtonTouch() { - // Game file - MaterialButton btn_file = findViewById(R.id.btn_file); - if(btn_file != null) { - btn_file.setOnClickListener(v -> { - // Internal storage - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); - intent.setType("*/*"); - startActivityResultLocalFilePlay.launch(intent); - }); - } - - // Game save - MaterialButton btn_save = findViewById(R.id.btn_save); - if(btn_save != null) { - btn_save.setOnClickListener(v -> { - if(NativeApp.saveStateToSlot(1)) { - // Success - } else { - // Failed - } - NativeApp.resume(); - }); - } - - // Game load - MaterialButton btn_load = findViewById(R.id.btn_load); - if(btn_load != null) { - btn_load.setOnClickListener(v -> { - if(NativeApp.loadStateFromSlot(1)) { - // Success - } else { - // Failed - } - NativeApp.resume(); - }); - } - - ////// - // RENDERER - - MaterialButton btn_ogl = findViewById(R.id.btn_ogl); - if(btn_ogl != null) { - btn_ogl.setOnClickListener(v -> { - NativeApp.renderGpu(12); - }); - } - MaterialButton btn_vulkan = findViewById(R.id.btn_vulkan); - if(btn_vulkan != null) { - btn_vulkan.setOnClickListener(v -> { - NativeApp.renderGpu(14); - }); - } - MaterialButton btn_sw = findViewById(R.id.btn_sw); - if(btn_sw != null) { - btn_sw.setOnClickListener(v -> { - NativeApp.renderGpu(13); - }); - } - - ////// - // PAD - - MaterialButton btn_pad_select = findViewById(R.id.btn_pad_select); - if(btn_pad_select != null) { - btn_pad_select.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_SELECT); - return true; - }); - } - MaterialButton btn_pad_start = findViewById(R.id.btn_pad_start); - if(btn_pad_start != null) { - btn_pad_start.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_START); - return true; - }); - } - - MaterialButton btn_pad_a = findViewById(R.id.btn_pad_a); - if(btn_pad_a != null) { - btn_pad_a.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_A); - return true; - }); - } - MaterialButton btn_pad_b = findViewById(R.id.btn_pad_b); - if(btn_pad_b != null) { - btn_pad_b.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_B); - return true; - }); - } - MaterialButton btn_pad_x = findViewById(R.id.btn_pad_x); - if(btn_pad_x != null) { - btn_pad_x.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_X); - return true; - }); - } - MaterialButton btn_pad_y = findViewById(R.id.btn_pad_y); - if(btn_pad_y != null) { - btn_pad_y.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_Y); - return true; - }); - } - - //// - - MaterialButton btn_pad_l1 = findViewById(R.id.btn_pad_l1); - if(btn_pad_l1 != null) { - btn_pad_l1.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_L1); - return true; - }); - } - MaterialButton btn_pad_r1 = findViewById(R.id.btn_pad_r1); - if(btn_pad_r1 != null) { - btn_pad_r1.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_R1); - return true; - }); - } - - MaterialButton btn_pad_l2 = findViewById(R.id.btn_pad_l2); - if(btn_pad_l2 != null) { - btn_pad_l2.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_L2); - return true; - }); - } - MaterialButton btn_pad_r2 = findViewById(R.id.btn_pad_r2); - if(btn_pad_r2 != null) { - btn_pad_r2.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_R2); - return true; - }); - } - - MaterialButton btn_pad_l3 = findViewById(R.id.btn_pad_l3); - if(btn_pad_l3 != null) { - btn_pad_l3.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_THUMBL); - return true; - }); - } - MaterialButton btn_pad_r3 = findViewById(R.id.btn_pad_r3); - if(btn_pad_r3 != null) { - btn_pad_r3.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_BUTTON_THUMBR); - return true; - }); - } - - //// - - final int PAD_L_UP = 110; - final int PAD_L_RIGHT = 111; - final int PAD_L_DOWN = 112; - final int PAD_L_LEFT = 113; - - final int PAD_R_UP = 120; - final int PAD_R_RIGHT = 121; - final int PAD_R_DOWN = 122; - final int PAD_R_LEFT = 123; - - MaterialButton btn_pad_joy_lt = findViewById(R.id.btn_pad_joy_lt); - if(btn_pad_joy_lt != null) { - btn_pad_joy_lt.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_UP); - sendKeyAction(v, event.getAction(), PAD_L_LEFT); - return true; - }); - } - MaterialButton btn_pad_joy_t = findViewById(R.id.btn_pad_joy_t); - if(btn_pad_joy_t != null) { - btn_pad_joy_t.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_UP); - return true; - }); - } - MaterialButton btn_pad_joy_rt = findViewById(R.id.btn_pad_joy_rt); - if(btn_pad_joy_rt != null) { - btn_pad_joy_rt.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_UP); - sendKeyAction(v, event.getAction(), PAD_L_RIGHT); - return true; - }); - } - MaterialButton btn_pad_joy_l = findViewById(R.id.btn_pad_joy_l); - if(btn_pad_joy_l != null) { - btn_pad_joy_l.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_LEFT); - return true; - }); - } - MaterialButton btn_pad_joy_r = findViewById(R.id.btn_pad_joy_r); - if(btn_pad_joy_r != null) { - btn_pad_joy_r.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_RIGHT); - return true; - }); - } - MaterialButton btn_pad_joy_lb = findViewById(R.id.btn_pad_joy_lb); - if(btn_pad_joy_lb != null) { - btn_pad_joy_lb.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_LEFT); - sendKeyAction(v, event.getAction(), PAD_L_DOWN); - return true; - }); - } - MaterialButton btn_pad_joy_b = findViewById(R.id.btn_pad_joy_b); - if(btn_pad_joy_b != null) { - btn_pad_joy_b.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_DOWN); - return true; - }); - } - MaterialButton btn_pad_joy_rb = findViewById(R.id.btn_pad_joy_rb); - if(btn_pad_joy_rb != null) { - btn_pad_joy_rb.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), PAD_L_RIGHT); - sendKeyAction(v, event.getAction(), PAD_L_DOWN); - return true; - }); - } - - //// - - MaterialButton btn_pad_dir_top = findViewById(R.id.btn_pad_dir_top); - if(btn_pad_dir_top != null) { - btn_pad_dir_top.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_UP); - return true; - }); - } - MaterialButton btn_pad_dir_bottom = findViewById(R.id.btn_pad_dir_bottom); - if(btn_pad_dir_bottom != null) { - btn_pad_dir_bottom.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_DOWN); - return true; - }); - } - MaterialButton btn_pad_dir_left = findViewById(R.id.btn_pad_dir_left); - if(btn_pad_dir_left != null) { - btn_pad_dir_left.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_LEFT); - return true; - }); - } - MaterialButton btn_pad_dir_right = findViewById(R.id.btn_pad_dir_right); - if(btn_pad_dir_right != null) { - btn_pad_dir_right.setOnTouchListener((v, event) -> { - sendKeyAction(v, event.getAction(), KeyEvent.KEYCODE_DPAD_RIGHT); - return true; - }); - } - } - - public final ActivityResultLauncher startActivityResultLocalFilePlay = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if(result.getResultCode() == Activity.RESULT_OK) { - try { - Intent _intent = result.getData(); - if(_intent != null) { - m_szGamefile = _intent.getDataString(); - if(!TextUtils.isEmpty(m_szGamefile)) { - restartEmuThread(); - } - } - } catch (Exception ignored) {} - } - }); - - @Override - public void onConfigurationChanged(@NonNull Configuration p_newConfig) { - super.onConfigurationChanged(p_newConfig); - } - - @Override - protected void onPause() { - NativeApp.pause(); - super.onPause(); - //// - if (mHIDDeviceManager != null) { - mHIDDeviceManager.setFrozen(true); - } - } - - @Override - protected void onResume() { - NativeApp.resume(); - super.onResume(); - //// - if (mHIDDeviceManager != null) { - mHIDDeviceManager.setFrozen(false); - } - } - - @Override - protected void onDestroy() { - NativeApp.shutdown(); - super.onDestroy(); - //// - if (mHIDDeviceManager != null) { - HIDDeviceManager.release(mHIDDeviceManager); - mHIDDeviceManager = null; - } - //// - if (mEmulationThread != null) { - try { - mEmulationThread.join(); - mEmulationThread = null; - } - catch (InterruptedException ignored) {} - } - - int appPid = android.os.Process.myPid(); - android.os.Process.killProcess(appPid); - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - - public void Initialize() { - NativeApp.initializeOnce(getApplicationContext()); - - // Set up JNI - SDLControllerManager.nativeSetupJNI(); - - // Initialize state - SDLControllerManager.initialize(); - - mHIDDeviceManager = HIDDeviceManager.acquire(this); - } - - private void setSurfaceView(Object p_value) { - FrameLayout fl_board = findViewById(R.id.fl_board); - if(fl_board != null) { - if(fl_board.getChildCount() > 0) { - fl_board.removeAllViews(); - } - //// - if(p_value instanceof SDLSurface) { - fl_board.addView((SDLSurface)p_value); - } - } - } - - public void startEmuThread() { - if(!isThread()) { - mEmulationThread = new Thread(() -> NativeApp.runVMThread(m_szGamefile)); - mEmulationThread.start(); - } - } - - private void restartEmuThread() { - NativeApp.shutdown(); - if (mEmulationThread != null) { - try { - mEmulationThread.join(); - mEmulationThread = null; - } - catch (InterruptedException ignored) {} - } - //// - startEmuThread(); - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public boolean onGenericMotionEvent(MotionEvent event) { - if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) { - SDLControllerManager.handleJoystickMotionEvent(event); - return true; - } - return super.onGenericMotionEvent(event); - } - - @Override - public boolean onKeyDown(int p_keyCode, KeyEvent p_event) { - if ((p_event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - if (p_event.getRepeatCount() == 0) { - SDLControllerManager.onNativePadDown(p_event.getDeviceId(), p_keyCode); - return true; - } - } - else { - if (p_keyCode == KeyEvent.KEYCODE_BACK) { - finish(); - return true; - } - } - return super.onKeyDown(p_keyCode, p_event); - } - - @Override - public boolean onKeyUp(int p_keyCode, KeyEvent p_event) { - if ((p_event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { - if (p_event.getRepeatCount() == 0) { - SDLControllerManager.onNativePadUp(p_event.getDeviceId(), p_keyCode); - return true; - } - } - return super.onKeyUp(p_keyCode, p_event); - } - - public static void sendKeyAction(View p_view, int p_action, int p_keycode) { - if(p_action == MotionEvent.ACTION_DOWN) { - p_view.setPressed(true); - int pad_force = 0; - if(p_keycode >= 110) { - float _abs = 90; // Joystic test value - _abs = Math.min(_abs, 100); - pad_force = (int) (_abs * 32766.0f / 100); - } - NativeApp.setPadButton(p_keycode, pad_force, true); - } else if(p_action == MotionEvent.ACTION_UP || p_action == MotionEvent.ACTION_CANCEL) { - p_view.setPressed(false); - NativeApp.setPadButton(p_keycode, 0, false); - } - } - - ////////////////////////////////////////////////////////////////////////////////////////////// - - public static void copyAssetAll(Context p_context, String srcPath) { - AssetManager assetMgr = p_context.getAssets(); - String[] assets = null; - try { - String destPath = p_context.getExternalFilesDir(null) + File.separator + srcPath; - assets = assetMgr.list(srcPath); - if(assets != null) { - if (assets.length == 0) { - copyFile(p_context, srcPath, destPath); - } else { - File dir = new File(destPath); - if (!dir.exists()) - dir.mkdir(); - for (String element : assets) { - copyAssetAll(p_context, srcPath + File.separator + element); - } - } - } - } - catch (IOException ignored) {} - } - - public static void copyFile(Context p_context, String srcFile, String destFile) { - AssetManager assetMgr = p_context.getAssets(); - - InputStream is = null; - FileOutputStream os = null; - try { - is = assetMgr.open(srcFile); - boolean _exists = new File(destFile).exists(); - if(srcFile.contains("shaders")) { - _exists = false; - } - if(!_exists) - { - os = new FileOutputStream(destFile); - - byte[] buffer = new byte[1024]; - int read; - while ((read = is.read(buffer)) != -1) { - os.write(buffer, 0, read); - } - is.close(); - os.flush(); - os.close(); - } - } - catch (IOException ignored) {} - } -} diff --git a/app/src/main/res/drawable/bg_control_cluster.xml b/app/src/main/res/drawable/bg_control_cluster.xml new file mode 100644 index 0000000..7640605 --- /dev/null +++ b/app/src/main/res/drawable/bg_control_cluster.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_joystick.xml b/app/src/main/res/drawable/bg_joystick.xml new file mode 100644 index 0000000..0e3f976 --- /dev/null +++ b/app/src/main/res/drawable/bg_joystick.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_joystick_knob.xml b/app/src/main/res/drawable/bg_joystick_knob.xml new file mode 100644 index 0000000..026cd41 --- /dev/null +++ b/app/src/main/res/drawable/bg_joystick_knob.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 0000000..e53fb22 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 0000000..281f673 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 0000000..8a6d198 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 0000000..ab9d711 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..09c6fe7 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..492686d --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 2b068d1..c5c88b3 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -2,29 +2,215 @@ xmlns:aapt="http://schemas.android.com/aapt" android:width="108dp" android:height="108dp" - android:viewportWidth="108" - android:viewportHeight="108"> - - - - - - - + android:viewportWidth="512" + android:viewportHeight="512"> + + + + + + + + - \ No newline at end of file + android:pathData="M53,173.2L116,173.2L116,218.2L53,218.2" + android:strokeLineJoin="round" + android:strokeWidth="8.1" + android:fillColor="#00000000" + android:strokeLineCap="round"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_power.xml b/app/src/main/res/drawable/ic_power.xml new file mode 100644 index 0000000..9d882b6 --- /dev/null +++ b/app/src/main/res/drawable/ic_power.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_ps2_circle.xml b/app/src/main/res/drawable/ic_ps2_circle.xml new file mode 100644 index 0000000..bdb5e48 --- /dev/null +++ b/app/src/main/res/drawable/ic_ps2_circle.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_ps2_square.xml b/app/src/main/res/drawable/ic_ps2_square.xml new file mode 100644 index 0000000..6aa9e3b --- /dev/null +++ b/app/src/main/res/drawable/ic_ps2_square.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_ps2_triangle.xml b/app/src/main/res/drawable/ic_ps2_triangle.xml new file mode 100644 index 0000000..33ae793 --- /dev/null +++ b/app/src/main/res/drawable/ic_ps2_triangle.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_ps2_x.xml b/app/src/main/res/drawable/ic_ps2_x.xml new file mode 100644 index 0000000..36b8caa --- /dev/null +++ b/app/src/main/res/drawable/ic_ps2_x.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_reboot.xml b/app/src/main/res/drawable/ic_reboot.xml new file mode 100644 index 0000000..247aa21 --- /dev/null +++ b/app/src/main/res/drawable/ic_reboot.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_unhide.xml b/app/src/main/res/drawable/ic_unhide.xml new file mode 100644 index 0000000..b522953 --- /dev/null +++ b/app/src/main/res/drawable/ic_unhide.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/psx2_logo2_fixed.xml b/app/src/main/res/drawable/psx2_logo2_fixed.xml new file mode 100644 index 0000000..9131205 --- /dev/null +++ b/app/src/main/res/drawable/psx2_logo2_fixed.xml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..313d61a --- /dev/null +++ b/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-land/dialog_covers_grid.xml b/app/src/main/res/layout-land/dialog_covers_grid.xml new file mode 100644 index 0000000..b7bf474 --- /dev/null +++ b/app/src/main/res/layout-land/dialog_covers_grid.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-port/activity_main.xml b/app/src/main/res/layout-port/activity_main.xml new file mode 100644 index 0000000..d773c97 --- /dev/null +++ b/app/src/main/res/layout-port/activity_main.xml @@ -0,0 +1,508 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-port/dialog_covers_grid.xml b/app/src/main/res/layout-port/dialog_covers_grid.xml new file mode 100644 index 0000000..b7bf474 --- /dev/null +++ b/app/src/main/res/layout-port/dialog_covers_grid.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f5a4a0e..48bf983 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -12,12 +12,27 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + + @@ -55,6 +70,54 @@ app:strokeColor="#80000000" /> + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/tv_long_press_hint"> + + @@ -223,8 +300,13 @@ style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="▢" - android:textColor="@color/white" + android:text="" + android:gravity="center" + app:icon="@drawable/ic_ps2_square" + app:iconTint="#FF80C0" + app:iconPadding="0dp" + app:iconSize="20dp" + app:iconGravity="textStart" app:rippleColor="#80cdcdcd" app:strokeColor="#80000000" /> @@ -233,8 +315,13 @@ style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="△" - android:textColor="@color/white" + android:text="" + android:gravity="center" + app:icon="@drawable/ic_ps2_triangle" + app:iconTint="#00D000" + app:iconPadding="0dp" + app:iconSize="20dp" + app:iconGravity="textStart" app:rippleColor="#80cdcdcd" app:strokeColor="#80000000" /> @@ -249,8 +336,13 @@ style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="⨉" - android:textColor="@color/white" + android:text="" + android:gravity="center" + app:icon="@drawable/ic_ps2_x" + app:iconTint="#4080FF" + app:iconPadding="0dp" + app:iconSize="20dp" + app:iconGravity="textStart" app:rippleColor="#80cdcdcd" app:strokeColor="#80000000" /> @@ -259,8 +351,13 @@ style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="○" - android:textColor="@color/white" + android:text="" + android:gravity="center" + app:icon="@drawable/ic_ps2_circle" + app:iconTint="#FF3030" + app:iconPadding="0dp" + app:iconSize="20dp" + app:iconGravity="textStart" app:rippleColor="#80cdcdcd" app:strokeColor="#80000000" /> @@ -272,6 +369,7 @@ android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"> @@ -388,11 +486,13 @@ @@ -410,7 +510,8 @@ android:text="↑" android:textColor="@color/white" app:rippleColor="#80cdcdcd" - app:strokeColor="#80000000" /> + app:strokeWidth="0dp" + app:strokeColor="@android:color/transparent" /> + app:strokeWidth="0dp" + app:strokeColor="@android:color/transparent" /> + app:strokeWidth="0dp" + app:strokeColor="@android:color/transparent" /> + app:strokeWidth="0dp" + app:strokeColor="@android:color/transparent" /> diff --git a/app/src/main/res/layout/dialog_covers_grid.xml b/app/src/main/res/layout/dialog_covers_grid.xml new file mode 100644 index 0000000..500b4c2 --- /dev/null +++ b/app/src/main/res/layout/dialog_covers_grid.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_game_settings.xml b/app/src/main/res/layout/dialog_game_settings.xml new file mode 100644 index 0000000..a5a2569 --- /dev/null +++ b/app/src/main/res/layout/dialog_game_settings.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_saves.xml b/app/src/main/res/layout/dialog_saves.xml new file mode 100644 index 0000000..e64c02a --- /dev/null +++ b/app/src/main/res/layout/dialog_saves.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/layout/dialog_screenshot_preview.xml b/app/src/main/res/layout/dialog_screenshot_preview.xml new file mode 100644 index 0000000..8ea081b --- /dev/null +++ b/app/src/main/res/layout/dialog_screenshot_preview.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_settings.xml b/app/src/main/res/layout/dialog_settings.xml new file mode 100644 index 0000000..f988ab5 --- /dev/null +++ b/app/src/main/res/layout/dialog_settings.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_cover.xml b/app/src/main/res/layout/item_cover.xml new file mode 100644 index 0000000..5f41dd7 --- /dev/null +++ b/app/src/main/res/layout/item_cover.xml @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_save_slot.xml b/app/src/main/res/layout/item_save_slot.xml new file mode 100644 index 0000000..1268cc0 --- /dev/null +++ b/app/src/main/res/layout/item_save_slot.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 56% rename from app/src/main/res/mipmap-anydpi/ic_launcher.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6f3b755..7353dbd 100644 --- a/app/src/main/res/mipmap-anydpi/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 56% rename from app/src/main/res/mipmap-anydpi/ic_launcher_round.xml rename to app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 6f3b755..7353dbd 100644 --- a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4c3a02da82aa3f8c880654580b20d3a00335cf8d GIT binary patch literal 1987 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2898 zcmWIYbaV6KW?%?+bqWXzu!!JdU|7^dl3uQO%=KtB6@_8OJ`?s99PuHc(Wv{Uvn57cQnzH^! z4YT&W4>v_49d0g>;5}1QsqLw*bguIK-eRRko_V#EY72aobT&L-pQO|$YAra)UWXy) z?H0HH>?%h$pNjLSyTHFTTZD1@o5kYirK&#Ospl5Bs^WBcdditu$Df4zc4P@8o+&!k z$5~=Hz27y&v^)8mahg-|JA1KhUV`UTT02EQ$+(1e7MN)IY?yFdc~#TV8Gl98qIT3X z<+w`qp4Rl)QQ*p5k{WN6_9(e-vDU^NGYy%0D%Eo$*=nm_Iu+cp7Fo9EyM*zH8S<-( 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 ovnzgd{?9o4BIu2n{`8h0i$C!PvP=E;mwU3- ziy3A;k`q%IjXIQ@$K|NnFBtpW~B zjr~iPWfbvzp0F*X{G|BOr|KIQPJOg&M-j{H*_9KvrgH3b6jXKl+4#x3FHvrW__NN; z%K}ADtwo+lz7Mw8RFV?{4{*h9n0w|Y#aif(QGHKi)N@z;$qz7sn}3-ASak(fsDaz z5he}xGiEc{4WeY7QYW-uJ%9MpfyIm3+Ap7T4i8#7CnfmIJ%NxYXRn>-a@tQm@zD&Q zP|0xK`8eypx7Hc8xx9_R*)J@%wS82+fB3r1WSdSeUguO>tzF9>NL%-_yV*P3e)rkH z;!jfT>p!el`uB18t?m3C_}cq|^$*28`!?=B?Xy&)j6Lw)PmMYJ`&p`gKi1f0%Fz;> zDt6%F|1AO6jHjlU8GX>3^=I?P^K;z#cWrbn*;mQE?`3zvHkr2S--;Es>MIdS828T9SQq>2w35QuWOYUw_XN|8camC2hLEj2Nfi7le)--+%PmGUbn)TD#p2 z%-_ow?CVi8spWCdnbSMk#RP2jc6}3$?^2xd)yra6Y0baL#DX7Mzvt9396wNa&`{8| zu)tqt@_zm~j~v3@&T_5z$aZVd{e^`uWV&kaYNknOIdhinE0S(ZmS28F)9}U~4*vK< z_c}ulW~@$6m1Mc`faebPw!K?Qq%D7>Z+!j2_e$BB6HGAgoMc}MD2@44{n3%6fA@Tjw4eq8&; zEoO1+FU=OHQfm8fNGoUV;{frTChm*)Q{B*i zkE#Bdnrt@fTJ`V^k5`I*@mZg6{)Xwo)DJOfj3&>D98+~}9;m3A5#t-MQQ9kToncf! z%A`ZDa`YtboHf|-F6b`L;>i!ix7vQY^-)zVzK4Z(DbP0 Hl+XkKYEnmc literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d64e58ba64d180ce43ee13bf9a17835fbca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 982 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611da081676d42f6c3f78a2c91e7bcedddedb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1772 zcmWIYbaQ*c#=sEn>J$(bU=hK^z`!8Dz`&pnMvguK9x+S|42%o1Z*R-I{VKRR_O_X_ z^vvAbVQHV4bMD_b_PMm~oSu2zw)-*3-E@E^|?El(}TXJtN zGyM?1dh-P_hvly7^Pcbj^Y6F*=kxwLuUHPe+GM%l*dBKK%`fK5pIObfZ z2U@NXWrA_R2Cf+Sf=lfF*^|Zcp*zy130q&}_cR#(H z{~u3cRZS6Z$xdX<2w9zQiX%AeS`(Yjvp|WZ1)Mq}9~$iV&L5XLwczpjDHbjEA4Nx?jQY9E50B(=5AwH{IM^g z)eG|MmVNJSc=C8gTuAfQ=gGSstqIPSbdpgl=q_Tjty^+L@y|;8HBPRm7IQirE~Qf8v3vi0~2iPiN<(SvFq^U6*%# z?^G2{*GVZ)_vMuqbETGgPL_+d|Iz6B|HHv+a=t?CMcUz83!fWW=j}OB|L?5%{O9p^ zZWu1}e!R+0**Q0M?Xlcl-=xfHer$+~y=}7Y=^D3Vr=At9%lbOYbai;CWxA*rTUdaF zLJ|uD!vt}Lxr|x{3@iF-{w*N3)rWHzi~e}|LyY)>~F&V|2`$Z_lwQ1w@URY zKVomi|GM{+UH+l|Hs&jJ|My+}yz^i4<>PZeDyUx1xb;~UI40Ekc=EzRTi2rzh;?hed#*(i;e?0N%CC}4Tma95H)sm;qe&@*} zD!$3}+5(1u|Edjp--bH%*G4?OS>B#lEOCxiZQ5TBlklv!dyE5$YggNz^mz4t(_wyo z#*aMxFB7iJH#}A!KH(L^;(4>4Zrb|%@K^Omf0pYs@BRNPbjOc*<(lT3ZvLNtZt>#_ zub2OK4WIpbYVU=Fd;d_d@dXc8-pPonn6G~m%;FhlXNTA%$6a5TIyKQ)@`nWf(tT?l zFZeg-|D*N)6t?egI(ciKrI@miq+>?eYq7wCY_^ltndD6`GG31le%>X%CZuii7R^6O zvnNWv_`?6dX7T%qub%@%P2OF39QRBvYlRkzJIl(s{}%1M{q~L2vaj84e9bFY9;o@T z=WXDQck??9p0`i?P`qc>?AkPz>n4YGrVA}DTe|qk*?HTmUsbz^vZ-ZOY@aT#UhlQ_ z)1^<^mo~NJ^F39pacW+**GyA{>$k%cwrAmU{v0Tn=)a>vc!}`qLtm#e1aJGDn37?- z=IJ5RLW5N-9ZhlDf|sqZ-S|G;a?yrllfzzXMU9_5JJqo@`Ga^JhoF~;_1z{}#+Vyn zr|x-edok^Y`~SoKH}W5;*G+X&h!T*CD_iqv<>bxs@0R(mx7_x;!X`UDcJDotYN14*kn_7jwr}zihOj{bNGVOcxxf%|EtzTbQJuki$dZvwg z)ozC9>*v1LZdYB+oBj37)5Y6Z>_yvzxZBhIGTo~d3tFj{QJu%;yiv1dvh#i)rRp=*JjrimCi=5XTmE|Hjnx}7p7$-#5c+FYu^^z!B0Vn-KqJ`V;40Lb8F AkN^Mx diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8bbdedba556dd023d485a7127f926504bfc90a44 GIT binary patch literal 2997 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

( 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 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..01b751a0a0e8b49f0d1e8333954b94e84ab159ae GIT binary patch literal 9413 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

YaWBj_FR`uWPPKP&spKguyWYOuW5 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d6427e6fa1074b79ccd52ef67ac15c5637e85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3844 zcmWIYbaVT|$G{No>J$(bU=hK^z`!8Dz`(E{j2wLeJldHU7(>b%bUDJ3u)$aAX7!}(6$UFx zSAJ(;QR8K7Q)4t%i+=x8TwYyK?P*%phS&RMFPd4^=k>ODyUiUb+w~U`j$i(`{EcNx zX0h4Z?B#Y{6Nv6&-#EViZ?%=zb_mtm-8m%igMtLg>q%5EcXXQvfLM45im!z zY(`Of&gO=bPjM`(1aJ!@ERx-wv2=J6*7Qs!s4Ev)(YIAf+&bNI{FtF51YU75I5 z(fL)Qt&a{LtBt``nY-P&6-8H`nTfee$JbPQb-m&X%F(l5Riw=$-{<|jZ%wbHL+;lb z*6bJ7YS=tzGc11H^&)OU!{_jKC)GAdT;V&;d|qdfM;ZHx%Nw3;+4?*DLlTFex$W*f zzf`5BHmc;iEi2w{QPL{P9Je#;%&H&3sVYSc-e);%O8(A?74wskzEb=pe}C2Qi{;nX zA3ZOcc_OGNT)O=_zx}U=Cpf;z*!(pqe*Ej<{w)*J&bYpt#LxfZ=i>FkRdI!f&Isnz z8(!);arN#~p_~~L-&pT_<#MS==!vJ$422cXIf4Z|*pxSkK6y7OpiAX!&xwep)3TT3 zZzTU><}?G|1tc; zUa6ju_Wer#t9@_(mDV5rs{7CR3iDUzKkuK0XPJAOwzZ~e{s8}&E7CjUQJ&HY<SKKvfO^VCzn)BnFqJ08klIC19l-HvVN zpM?F^sku~cr)z1v!81)nS()_(pW}`*=9!a1QorARTqkZA-X8j9YwJq6LvLeM*B2l8 zykVxX&>EE%F^?B-ULQ6Uu83EZe=g@{;{8R<(Z%$afR1d{!g5b|H(~LryO|DtHt$i*Jp!Yu7CejS27u&h}|Nnnt^m()?X0BW3$>8FpLR0E4`fr*xYyYh2 zo~|;Ai+`2e&R67=uj5uU*Im24W6zVRMy$D#6B-!)eR#>hz#x48{}uUoJ*{XFroj83 zuIL3Fc`otwut8|9=6lY1v11vdv`KF~?!yPSv*m+|PD>zHx&2)!qFcWL2^cglSw@ zIluXVfpX}s!1#GOi{k!DEPe5P@g|k%$KT{XDSIAq?by9Qz_f1PJ>Q;rK^;}!+xF

0vl^(ubK57i3$j`RXVdItc2Zu zTjP%3T{qqDyHu$1wf>en`}f$lI?l)hfy+`aCTxu8`Mp4Wt8~lzsk7fIKUzdbU&D@u>@#|8JoKt2mbv8|~*&2E%ct_*EIJ>)7o&PKE5_$1H zYTdgu^~m!0A4{%YZ`c&qZ)$yr|AXm!sqXjcKR?_Pn|Ao^jNS9)1$Q|wns@3F%fsvg zwSO(SHf}Wd&gY@5dZETG=E3!B4m$1-}j)^|n6tq;mOd3k8c0DH_GHdj+bC#}?wC!#j7mQS#vf_fXiT$x+$0-Ud(~5MTubmb>WqqsT;k{xpsIQhc5wmzv^leMXZ>rUpkvod!E z%4X!{CT@DfUb}1l9;OF%&cZj$=~`?izExW$`!W#MmrU&bU9x zY4?Y|ihvD`aVe?!Jd*bn>a_Ljw%A+cxQM63#5tgD>7pvIfk zXZB`A(+B?|xK;mhgAxy6j8m5a|tYYQtz-&)`wciH>w!z4Jl6EbdLNgW&t4n&Z^`r` zg&vWF3I>a?I;mM9pQdx?h+g`4^#9q@#m5&cUv`wUW67(N(f8cqnde#^*{|z4`#8&% z$(*MTuC#KwaPVof`>D`&U$yg@H74@YWB+XNzOk9HzHI)?S*96(9jadGnl+RhV?HYU zTPymOU!2^Y9Y;SYsn1QEpStG70rM^O4*D71J+(bX{#n9KLDg&XW+cq#d%E33PUWv_ zkfOTshO5hBt#1C8*W^yL{vnXQVRqiLErQMrj6U1<_5@j|Em*A4E4-te^ZzpYDI0B` zXg;x>w9{g(u&mK%{^Fz8n{xBlOZuI;!E!86S+BwIO2Xl&Fh%#ZoBUQU_O0g4%ANk> z0Jmq=?VG;}VuQcWs7ieBJAdXY(fhi`7ADvReRQ>AUivA=PwM}&`iLJJJJZ{_&k3Y2 zUvhnu->Tx7bEg!!9%6moQp;o7lU20XJ=QDEK<4}Dx`6F-nzAlROmUdnwpX%JBgyHE&EJd_^1F8lZE|Tb`4Z9o=_5<>!KzQ} zGZoDj*B>l-_{cMi-TF`Qmi1X-=QLK8d3<{luV|^Id8CphQgh85osBxngzXY9@84_h z%f`4XHqy%Sk8jJOIRCXp`#HYZ9tjb8r7sFp_47S~Y8LMZ0|ACD*T9*?vNtsq>c#FZ;4AF83!N-}7Zh7R+l= zw!4tMaFcX!P3Els8SK9LLhRyJK z+&t%0aQM50`k&8S%~Uwp?rm21|LFNWlV+G(ACydR-+6qUWz*kv3iZ{C%If{&>euW) z@@W5$w|~FeitEW*v$L+4y5z_58%Bq(G3{FX-GAnci|2Se?q;?>GnLW&F(+=#|9Z#w z-#_o;-&VrrqOwwO%B-^WHLF&}EjZj>w|%nOHqFIdHlY~LM|@kt^cB?v?aRN!JSJaHjLeW zb$GNz$NM?U8z(oNo3`r8e%lS3CEre$zVUyZ&{wym*PqV*QMTjIf~G4kJUQ0C+9_cC zLSfq{(=7&@u0G04(^)DtZ};(To{cq(j|-+htN*jEpuxFk0?Ru8XP3^z-&SNkURt^2 zTSfZcN5x^>ML|ky1rH}oViJ7+@cetDleV!hBksSwEn~4q@Xz`GmCrjHOK)GEd0p-B z(Y6SS8poDMuE(=E|4rGmcvp9MsGHU9V8)Ek>HE*irk%F9rK7RU-2N}yiz$uUO57Qx zXRAv!tdNVQd!7^~xNg;>=ojmFGC8tun)m$oP3PKhc*onS@K5?rr^}yu zUmUrRUESJJ=>F0}m;TMP>i;SjwwR~)_nQ*mc_z*-WwSXs6&fs_bqPz9s|2|0xg0$2 t@u$Pda=rYAtrOikX7%&GRsG&;Z1Lh{&yL;Ao6ROYUN$pxVgmyL003>zdZqvX diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..521189208965b520f49565215cf0e7d90aa29eba GIT binary patch literal 14050 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae37cbc3587421d6889eadd1d91fbf1994d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7778 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 diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 9281ae2..fa51bf4 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,6 +1,6 @@ - + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..35db0ec --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,37 @@ + + + + 1x (Native ~480p) + 2x (~720p HD) + 3x (~1080p FHD) + 4x (~1440p QHD) + 5x (~1800p QHD+) + 6x (~2160p 4K UHD) + 7x (~2520p) + 8x (~2880p 5K UHD) + + + + Stretch (Fill Screen) + Auto 4:3/3:2 (Recommended) + 4:3 (Original PS2) + 16:9 (Widescreen) + 10:7 (Uncommon) + + + + Minimum (Fastest, may break effects) + Basic (Recommended) + Medium (Good compatibility) + High (Better effects) + Full (Best quality, slower) + Maximum (Perfect, very slow) + + + + Auto (Recommended) + Hardware (Vulkan) + Hardware (OpenGL) + Software (Slow, accurate) + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f8c6127..78b6cb4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,17 @@ #FF018786 #FF000000 #FFFFFFFF + + + #33A1FF + #7C52FF + #66CCFF + #8F61FF + #8033A1FF + + + #00D000 + #FF3030 + #4080FF + #FF80C0 \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..beab31f --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #000000 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6cff8d8..2f30268 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ - PCSX2 - \ No newline at end of file + PSX2 + Long press a game cover for per-game settings + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index b1aa576..868e007 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,8 +1,22 @@ - + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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

l-wUqma}v2c(1Oi^~*7`Q^d3B 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 literal 59203 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 YhOdRl$_CP6#$d+qN{E5M`3i^!03mLhYXATM diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f5059fc..1af9e09 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Sun May 12 00:33:42 KST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..1aa94a4 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,99 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd3..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle b/settings.gradle index cb5199e..cad152a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,5 +13,5 @@ dependencyResolutionManagement { } } -rootProject.name = "PCSX2" +rootProject.name = "PSX2" include ':app'