Android: route native file creation through Java, and scope patch state per game

Two fixes that both live in the JNI layer.

Folder memory cards on a user-chosen data folder crashed on the first
new save. FUSE-backed shared storage denies libc file CREATION even
though mkdir is already routed through Java, so SaveYAMLToFile opened a
not-yet-existing _pcsx2_index with an unchecked OpenCFile and then
dereferenced null. Existing saves reuse that file, which is exactly why
only new saves crashed. Null-check the write, and add a
CreateFileViaJava fallback in OpenCFile so a denied create is retried
through the Java file API - the same libc/Java asymmetry that
CreateDirectoryPath already relies on.

Patch and cheat enable-state was written to the base settings layer,
keyed only by patch name, so enabling e.g. "Widescreen 16:9" for one
game switched on the identically named patch in every other game.
LayeredSettingsInterface returns the first non-empty layer with the game
layer ahead of the base one, so upstream keys this per serial and CRC;
do the same, and strip the migrated names from the base list so an empty
per-game list cannot fall back through to it.

The per-game INI exporter also rebuilt the file from scratch, dropping
every key it does not own - the patch lists above, and per-game
MemoryCards and Gamefixes overrides. Load the existing file and clear
only the sections the exporter actually writes.
This commit is contained in:
jpolo1224
2026-07-20 14:05:51 -04:00
parent 9232a10663
commit 07d03a5eac
5 changed files with 217 additions and 5 deletions
+12
View File
@@ -1020,6 +1020,18 @@ std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error*
#endif
std::FILE* fp = std::fopen(filename, mode);
#if defined(__ANDROID__)
// libc fopen(O_CREAT) is denied on the FUSE-emulated external storage Android
// hands out for a user-chosen data folder — the same split that forces
// CreateDirectoryViaJava for mkdir(). Pre-create the empty file via the Java
// File API and retry: opening the now-existing file for a truncating write
// ("w"/"wb") IS permitted on FUSE (which is why writing EXISTING folder-card
// saves already works), so the FIRST save to a folder card on a custom data
// folder can now create its _pcsx2_index / save-data files instead of failing.
// Write/create modes only — never "r"/"r+b" (those must fail on a missing file).
if (!fp && (errno == EACCES || errno == EPERM) && mode[0] != 'r' && CreateFileViaJava(filename))
fp = std::fopen(filename, mode);
#endif
if (!fp)
Error::SetErrno(error, errno);
return fp;
+6
View File
@@ -132,6 +132,12 @@ namespace FileSystem
/// (user-picked custom data folders) despite all-files access. Returns true if
/// the directory exists afterwards.
bool CreateDirectoryViaJava(const char* path);
/// Create an empty file via the Java File API (NativeApp.createFilePath).
/// Fallback for when libc fopen(O_CREAT) is denied on FUSE-emulated external
/// storage (user-picked custom data folders) despite all-files access; once the
/// file exists the native truncating write that follows succeeds. Makes NEW
/// folder-card saves work on a custom data folder. Returns true if it exists after.
bool CreateFileViaJava(const char* path);
#endif
/// Sharing modes for OpenSharedCFile().