fix crash on settings change

Now changing settings globally or per game doesn't crash the app. Works on the fly, without crashing.
This commit is contained in:
izzy2lost
2025-09-02 10:36:29 -04:00
parent 82e9ea45d7
commit 7f04373064
5 changed files with 212 additions and 61 deletions
@@ -15,7 +15,6 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
// Use Material 3 dialog builder for pergame settings dialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.fragment.app.DialogFragment;
@@ -200,18 +199,42 @@ public class GameSettingsDialogFragment extends DialogFragment {
.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());
final int blendLevel = spBlendingAccuracy.getSelectedItemPosition();
final int rendererIdx = spRenderer.getSelectedItemPosition();
final int resIdx = spResolution.getSelectedItemPosition();
final boolean wide = swWidescreenPatches.isChecked();
final boolean noInt = swNoInterlacingPatches.isChecked();
final boolean enablePatches = swEnablePatchCodes.isChecked();
final boolean enableCheats = swEnableCheats.isChecked();
// Persist per-game INI explicitly (supports Auto as well)
writeGameSettingsIni(ctx, gameSerial, gameCrc,
spBlendingAccuracy.getSelectedItemPosition(),
spRenderer.getSelectedItemPosition(),
spResolution.getSelectedItemPosition(),
swWidescreenPatches.isChecked(),
swNoInterlacingPatches.isChecked(),
/*enablePatches*/ swEnablePatchCodes.isChecked(),
swEnableCheats.isChecked());
blendLevel, rendererIdx, resIdx, wide, noInt,
/*enablePatches*/ enablePatches, enableCheats);
// Live-apply per-game settings in one batch
try {
int renderer;
// rendererIdx: 0=Auto,1=Vulkan,2=OpenGL,3=Software
if (rendererIdx == 0) renderer = -1;
else if (rendererIdx == 1) renderer = 14;
else if (rendererIdx == 2) renderer = 12;
else renderer = 13;
float scale = Math.max(1, Math.min(8, resIdx + 1));
NativeApp.applyPerGameSettingsBatch(renderer, scale, blendLevel, wide, noInt, enablePatches, enableCheats);
} catch (Throwable t) {
android.util.Log.e("GameSettings", "Per-game batch apply failed: " + t.getMessage());
}
// Refresh quick UI (renderer label) to reflect runtime renderer
try {
android.app.Activity a = getActivity();
if (a instanceof MainActivity) {
((MainActivity) a).runOnUiThread(() -> ((MainActivity) a).refreshQuickUi());
}
} catch (Throwable ignored) {}
d.dismiss();
})
.setNeutralButton("Reset to Global", (d, w) -> {
@@ -825,10 +825,24 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
private void updateRendererButtonLabel() {
MaterialButton btn_bios = findViewById(R.id.btn_bios);
if (btn_bios != null) {
btn_bios.setText(rendererShortLabel(getCurrentRendererPref()));
int current = getCurrentRendererPref();
try {
// Prefer runtime renderer from core to reflect per-game overrides
int runtime = NativeApp.getCurrentRenderer();
// Only accept expected values
if (runtime == -1 || runtime == 12 || runtime == 13 || runtime == 14) {
current = runtime;
}
} catch (Throwable ignored) {}
btn_bios.setText(rendererShortLabel(current));
}
}
// Public minimal UI refresh hook for dialogs
public void refreshQuickUi() {
updateRendererButtonLabel();
}
private void setRendererAndSave(int renderer) {
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
prefs.edit().putInt("renderer", renderer).apply();
@@ -37,7 +37,6 @@ public class NativeApp {
public static native void initialize(String path, int apiVer);
public static native String getGameTitle(String path);
// New: Resolve game title from a URI (content:// or file://). Implement in native when available.
public static native String getGameTitleFromUri(String gameUri);
public static native String getGameSerial();
public static native float getFPS();
@@ -78,6 +77,29 @@ public class NativeApp {
public static native void setShadeBoostContrast(int contrast);
public static native void setShadeBoostSaturation(int saturation);
// Apply multiple settings in one atomic batch (safer live updates)
public static native void applyGlobalSettingsBatch(int renderer,
float upscaleMultiplier,
int aspectRatio,
int blendingAccuracy,
boolean widescreenPatches,
boolean noInterlacingPatches,
boolean loadTextures,
boolean asyncTextureLoading,
boolean hudVisible);
// Apply per-game settings (subset) in one batch
public static native void applyPerGameSettingsBatch(int renderer,
float upscaleMultiplier,
int blendingAccuracy,
boolean widescreenPatches,
boolean noInterlacingPatches,
boolean enablePatches,
boolean enableCheats);
// Query current runtime renderer from the core (reflects global/per-game)
public static native int getCurrentRenderer();
// Per-game settings
public static native void saveGameSettings(String filename, int blendingAccuracy, int renderer,
int resolution, boolean widescreenPatches,
@@ -15,7 +15,6 @@ import android.content.res.ColorStateList;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
// Use MaterialAlertDialogBuilder for Material 3 dialogs.
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.fragment.app.DialogFragment;
import androidx.core.widget.CompoundButtonCompat;
@@ -39,8 +38,8 @@ public class SettingsDialogFragment extends DialogFragment {
float scale = prefs.getFloat("upscale_multiplier", 1.0f);
int aspectRatio = prefs.getInt("aspect_ratio", 1);
int blendingAccuracy = prefs.getInt("blending_accuracy", 1); // 0..5
boolean widescreenPatches = prefs.getBoolean("widescreen_patches", false);
boolean noInterlacingPatches = prefs.getBoolean("no_interlacing_patches", false);
boolean widescreenPatches = prefs.getBoolean("widescreen_patches", true);
boolean noInterlacingPatches = prefs.getBoolean("no_interlacing_patches", true);
boolean loadTextures = prefs.getBoolean("load_textures", false);
boolean asyncTextureLoading = prefs.getBoolean("async_texture_loading", true);
boolean hudVisible = prefs.getBoolean("hud_visible", false);
@@ -184,8 +183,8 @@ public class SettingsDialogFragment extends DialogFragment {
int savedRenderer = prefs.getInt("renderer", RENDERER_AUTO);
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 savedWidescreen = prefs.getBoolean("widescreen_patches", true);
boolean savedNoInterlacing = prefs.getBoolean("no_interlacing_patches", true);
boolean savedLoadTextures = prefs.getBoolean("load_textures", false);
boolean savedAsyncTextureLoading = prefs.getBoolean("async_texture_loading", true);
boolean savedHud = prefs.getBoolean("hud_visible", false);
@@ -217,7 +216,7 @@ public class SettingsDialogFragment extends DialogFragment {
MaterialAlertDialogBuilder b = new MaterialAlertDialogBuilder(requireContext(),
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog);
b.setTitle("Graphics Settings")
b.setTitle("Global Settings")
.setView(view)
.setNegativeButton("Cancel", (d, w) -> d.dismiss())
.setPositiveButton("Save", (d, w) -> {
@@ -236,52 +235,35 @@ public class SettingsDialogFragment extends DialogFragment {
boolean asyncTextureLoading = swAsyncTextureLoading.isChecked();
boolean hudVisible = (swDevHud != null && swDevHud.isChecked());
// Debug logging
android.util.Log.d("SettingsDialog", "Saving renderer setting: " + renderer +
" (-1=Auto, 12=OpenGL, 13=Software, 14=Vulkan)");
// Save renderer setting first with commit() to ensure immediate write
prefs.edit().putInt("renderer", renderer).apply();
android.util.Log.d("SettingsDialog", "Renderer setting saved successfully");
// Small delay to ensure setting is persisted
// Persist settings to SharedPreferences
int blendingLevel = spBlending.getSelectedItemPosition();
prefs.edit()
.putInt("renderer", renderer)
.putFloat("upscale_multiplier", scale)
.putInt("aspect_ratio", aspectRatio)
.putInt("blending_accuracy", blendingLevel)
.putBoolean("widescreen_patches", widescreenPatches)
.putBoolean("no_interlacing_patches", noInterlacingPatches)
.putBoolean("load_textures", loadTextures)
.putBoolean("async_texture_loading", asyncTextureLoading)
.putBoolean("hud_visible", hudVisible)
.apply();
// Apply in one batch to avoid repeated ApplySettings calls
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {}
// Apply renderer change (might crash, but setting is already saved)
try {
NativeApp.renderGpu(renderer);
android.util.Log.d("SettingsDialog", "Applied renderer change to: " + renderer);
} catch (Exception e) {
android.util.Log.e("SettingsDialog", "Failed to apply renderer: " + e.getMessage());
// If renderer change fails, we'll still have the setting saved
// for next app restart
NativeApp.applyGlobalSettingsBatch(renderer, scale, aspectRatio, blendingLevel,
widescreenPatches, noInterlacingPatches, loadTextures, asyncTextureLoading, hudVisible);
} catch (Throwable t) {
android.util.Log.e("SettingsDialog", "Batch apply failed: " + t.getMessage());
}
// Persist all other settings
int blendingLevel = spBlending.getSelectedItemPosition();
prefs.edit()
.putFloat("upscale_multiplier", scale)
.putInt("aspect_ratio", aspectRatio)
.putInt("blending_accuracy", blendingLevel)
.putBoolean("widescreen_patches", widescreenPatches)
.putBoolean("no_interlacing_patches", noInterlacingPatches)
.putBoolean("load_textures", loadTextures)
.putBoolean("async_texture_loading", asyncTextureLoading)
.putBoolean("hud_visible", hudVisible)
.apply();
// Apply other settings
NativeApp.renderUpscalemultiplier(scale);
NativeApp.setAspectRatio(aspectRatio);
NativeApp.setBlendingAccuracy(blendingLevel);
NativeApp.setWidescreenPatches(widescreenPatches);
NativeApp.setNoInterlacingPatches(noInterlacingPatches);
NativeApp.setLoadTextures(loadTextures);
NativeApp.setAsyncTextureLoading(asyncTextureLoading);
NativeApp.setHudVisible(hudVisible);
// Refresh quick UI (renderer label) if hosting activity is MainActivity
try {
android.app.Activity a = getActivity();
if (a instanceof MainActivity) {
((MainActivity) a).runOnUiThread(() -> ((MainActivity) a).refreshQuickUi());
}
} catch (Throwable ignored) {}
});
return b.create();