mirror of
https://github.com/izzy2lost/PSX2.git
synced 2026-07-05 15:18:36 -07:00
better SAF - texture pack support etc.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package com.izzy2lost.psx2;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CheatsDialogFragment extends DialogFragment {
|
||||
|
||||
private ArrayAdapter<String> adapter;
|
||||
private ArrayList<String> items = new ArrayList<>();
|
||||
|
||||
private static final int REQ_IMPORT_PNACH = 1001;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
Context ctx = requireContext();
|
||||
View view = LayoutInflater.from(ctx).inflate(R.layout.simple_list, null, false);
|
||||
ListView lv = view.findViewById(android.R.id.list);
|
||||
adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_list_item_1, items);
|
||||
lv.setAdapter(adapter);
|
||||
lv.setOnItemLongClickListener((parent, v, position, id) -> {
|
||||
String name = items.get(position);
|
||||
deleteCheat(name);
|
||||
return true;
|
||||
});
|
||||
|
||||
refreshList();
|
||||
|
||||
return new MaterialAlertDialogBuilder(ctx, com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog)
|
||||
.setCustomTitle(UiUtils.centeredDialogTitle(ctx, "Manage Cheats"))
|
||||
.setView(view)
|
||||
.setNegativeButton("Close", null)
|
||||
.setPositiveButton("Import For Game", (d, w) -> startImport())
|
||||
.create();
|
||||
}
|
||||
|
||||
private void refreshList() {
|
||||
items.clear();
|
||||
String[] names = NativeApp.listSafFilenames("cheats");
|
||||
if (names != null && names.length > 0) {
|
||||
for (String n : names) if (n != null && n.endsWith(".pnach")) items.add(n);
|
||||
} else {
|
||||
java.io.File dir = new java.io.File(requireContext().getExternalFilesDir(null), "cheats");
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
java.io.File[] arr = dir.listFiles((f, n) -> n != null && n.endsWith(".pnach"));
|
||||
if (arr != null) for (java.io.File f : arr) items.add(f.getName());
|
||||
}
|
||||
if (adapter != null) adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void startImport() {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, REQ_IMPORT_PNACH);
|
||||
} catch (Throwable t) {
|
||||
Toast.makeText(requireContext(), "No file picker available", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQ_IMPORT_PNACH && resultCode == android.app.Activity.RESULT_OK && data != null) {
|
||||
Uri uri = data.getData();
|
||||
if (uri == null) return;
|
||||
String serial = null;
|
||||
try { serial = NativeApp.getCurrentGameSerial(); } catch (Throwable ignored) {}
|
||||
if (serial == null || serial.isEmpty()) {
|
||||
Toast.makeText(requireContext(), "Unknown game serial", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
String outName = serial + ".pnach";
|
||||
android.net.Uri dataRoot = SafManager.getDataRootUri(requireContext());
|
||||
if (dataRoot != null) {
|
||||
androidx.documentfile.provider.DocumentFile target = SafManager.createChild(requireContext(), new String[]{"cheats"}, outName, "application/octet-stream");
|
||||
if (target != null) {
|
||||
try (InputStream in = requireContext().getContentResolver().openInputStream(uri)) {
|
||||
if (in != null && SafManager.copyFromStream(requireContext(), in, target.getUri())) {
|
||||
Toast.makeText(requireContext(), "Imported to Data Folder", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "Import failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(requireContext(), "Import failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
java.io.File dir = new java.io.File(requireContext().getExternalFilesDir(null), "cheats");
|
||||
if (!dir.exists()) dir.mkdirs();
|
||||
java.io.File out = new java.io.File(dir, outName);
|
||||
try (InputStream in = requireContext().getContentResolver().openInputStream(uri);
|
||||
java.io.FileOutputStream os = new java.io.FileOutputStream(out)) {
|
||||
if (in != null) {
|
||||
byte[] buf = new byte[8192]; int n; while ((n = in.read(buf)) != -1) os.write(buf, 0, n);
|
||||
os.flush();
|
||||
Toast.makeText(requireContext(), "Imported", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(requireContext(), "Import failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
refreshList();
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteCheat(String name) {
|
||||
android.net.Uri dataRoot = SafManager.getDataRootUri(requireContext());
|
||||
boolean ok = false;
|
||||
if (dataRoot != null) {
|
||||
androidx.documentfile.provider.DocumentFile f = SafManager.getChild(requireContext(), new String[]{"cheats"}, name);
|
||||
if (f != null) ok = f.delete();
|
||||
} else {
|
||||
java.io.File dir = new java.io.File(requireContext().getExternalFilesDir(null), "cheats");
|
||||
java.io.File f = new java.io.File(dir, name);
|
||||
ok = f.delete();
|
||||
}
|
||||
if (ok) {
|
||||
Toast.makeText(requireContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
||||
refreshList();
|
||||
} else {
|
||||
Toast.makeText(requireContext(), "Delete failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
|
||||
android.net.Uri dataRoot = SafManager.getDataRootUri(ctx);
|
||||
if (dataRoot != null) {
|
||||
String subdir = mImportAsCheats ? "cheats" : "patches";
|
||||
androidx.documentfile.provider.DocumentFile target = SafManager.createChild(ctx, new String[]{subdir}, gameSerial + ".pnach", "text/plain");
|
||||
androidx.documentfile.provider.DocumentFile target = SafManager.createChild(ctx, new String[]{subdir}, gameSerial + ".pnach", "application/octet-stream");
|
||||
if (target != null) {
|
||||
try (java.io.InputStream in2 = cr.openInputStream(android.net.Uri.fromFile(outFile))) {
|
||||
SafManager.copyFromStream(ctx, in2, target.getUri());
|
||||
@@ -428,7 +428,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
|
||||
android.net.Uri dataRoot = SafManager.getDataRootUri(ctx);
|
||||
if (dataRoot != null) {
|
||||
try {
|
||||
androidx.documentfile.provider.DocumentFile target = SafManager.createChild(ctx, new String[]{"gamesettings"}, fileName, "text/plain");
|
||||
androidx.documentfile.provider.DocumentFile target = SafManager.createChild(ctx, new String[]{"gamesettings"}, fileName, "application/octet-stream");
|
||||
if (target != null) {
|
||||
byte[] data = sb.toString().getBytes("UTF-8");
|
||||
SafManager.writeBytes(ctx, target.getUri(), data);
|
||||
|
||||
@@ -521,6 +521,13 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
SettingsDialogFragment dialog = new SettingsDialogFragment();
|
||||
dialog.show(fm, "settings_dialog");
|
||||
});
|
||||
// Long-press to open Cheats manager
|
||||
btn_settings.setOnLongClickListener(v -> {
|
||||
FragmentManager fm2 = getSupportFragmentManager();
|
||||
CheatsDialogFragment cd = new CheatsDialogFragment();
|
||||
cd.show(fm2, "cheats_dialog");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle all UI visibility (including controls)
|
||||
@@ -1742,3 +1749,4 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class NativeApp {
|
||||
initialize(externalFilesDir.getAbsolutePath(), android.os.Build.VERSION.SDK_INT);
|
||||
}
|
||||
|
||||
public static native void initialize(String path, int apiVer);
|
||||
public static native void initialize(String path, int apiVer);
|
||||
public static native String getGameTitle(String path);
|
||||
public static native String getGameTitleFromUri(String gameUri);
|
||||
public static native String getGameSerial();
|
||||
@@ -69,6 +69,7 @@ public class NativeApp {
|
||||
// Texture loading options for texture packs
|
||||
public static native void setLoadTextures(boolean enabled);
|
||||
public static native void setAsyncTextureLoading(boolean enabled);
|
||||
public static native void setPrecacheTextureReplacements(boolean enabled);
|
||||
public static native void setBlendingAccuracy(int level);
|
||||
|
||||
// Shade Boost (brightness/contrast/saturation)
|
||||
@@ -154,7 +155,7 @@ public class NativeApp {
|
||||
public static native void onNativeSurfaceChanged(Surface surface, int w, int h);
|
||||
public static native void onNativeSurfaceDestroyed();
|
||||
|
||||
public static native boolean runVMThread(String path);
|
||||
public static native boolean runVMThread(String path);
|
||||
|
||||
public static native void pause();
|
||||
public static native void resume();
|
||||
@@ -166,17 +167,149 @@ public class NativeApp {
|
||||
public static native byte[] getImageSlot(int slot);
|
||||
|
||||
// Call jni
|
||||
public static int openContentUri(String uriString) {
|
||||
Context _context = getContext();
|
||||
if(_context != null) {
|
||||
ContentResolver _contentResolver = _context.getContentResolver();
|
||||
try {
|
||||
ParcelFileDescriptor filePfd = _contentResolver.openFileDescriptor(Uri.parse(uriString), "r");
|
||||
if (filePfd != null) {
|
||||
return filePfd.detachFd(); // Take ownership of the fd.
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
public static int openContentUri(String uriString) {
|
||||
Context _context = getContext();
|
||||
if(_context != null) {
|
||||
ContentResolver _contentResolver = _context.getContentResolver();
|
||||
try {
|
||||
ParcelFileDescriptor filePfd = _contentResolver.openFileDescriptor(Uri.parse(uriString), "r");
|
||||
if (filePfd != null) {
|
||||
return filePfd.detachFd(); // Take ownership of the fd.
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Indicates whether a SAF Data Root has been selected by the user.
|
||||
public static boolean hasSafDataRoot() {
|
||||
return SafManager.getDataRootUri(getContext()) != null;
|
||||
}
|
||||
|
||||
// Open a SAF content Uri with the requested mode ("r", "w", or "rw"). Returns a detached FD or -1.
|
||||
public static int openContentUriMode(String uriString, String mode) {
|
||||
Context _context = getContext();
|
||||
if(_context != null) {
|
||||
ContentResolver _contentResolver = _context.getContentResolver();
|
||||
try {
|
||||
ParcelFileDescriptor filePfd = _contentResolver.openFileDescriptor(Uri.parse(uriString), mode);
|
||||
if (filePfd != null) {
|
||||
return filePfd.detachFd();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Resolve a child document Uri within the SAF Data Root.
|
||||
// subdir: e.g., "gamesettings", filename: e.g., "SLUS-12345.ini". If create is true, creates file.
|
||||
public static String resolveSafChildUri(String subdir, String filename, boolean create) {
|
||||
Uri root = SafManager.getDataRootUri(getContext());
|
||||
if (root == null) return null;
|
||||
try {
|
||||
androidx.documentfile.provider.DocumentFile df;
|
||||
if (create) {
|
||||
df = SafManager.createChild(getContext(), new String[]{subdir}, filename, "application/octet-stream");
|
||||
} else {
|
||||
df = SafManager.getChild(getContext(), new String[]{subdir}, filename);
|
||||
}
|
||||
return (df != null) ? df.getUri().toString() : null;
|
||||
} catch (Throwable ignored) { }
|
||||
return null;
|
||||
}
|
||||
|
||||
// Resolve a file path relative to the SAF Data Root. Accepts nested paths like
|
||||
// "textures/SLUS-12345/replacements/subdir/file.png". If create is true, creates the file.
|
||||
public static String resolveSafPathUri(String relativePath, boolean create) {
|
||||
if (relativePath == null) return null;
|
||||
Uri root = SafManager.getDataRootUri(getContext());
|
||||
if (root == null) return null;
|
||||
try {
|
||||
String[] parts = relativePath.split("/");
|
||||
if (parts.length == 0) return null;
|
||||
String[] dirSegs;
|
||||
String filename;
|
||||
if (parts.length == 1) {
|
||||
dirSegs = new String[]{};
|
||||
filename = parts[0];
|
||||
} else {
|
||||
dirSegs = new String[parts.length - 1];
|
||||
System.arraycopy(parts, 0, dirSegs, 0, parts.length - 1);
|
||||
filename = parts[parts.length - 1];
|
||||
}
|
||||
androidx.documentfile.provider.DocumentFile df;
|
||||
if (create) {
|
||||
df = SafManager.createChild(getContext(), dirSegs, filename, "application/octet-stream");
|
||||
} else {
|
||||
df = SafManager.getChild(getContext(), dirSegs, filename);
|
||||
}
|
||||
return (df != null) ? df.getUri().toString() : null;
|
||||
} catch (Throwable ignored) { }
|
||||
return null;
|
||||
}
|
||||
|
||||
// List files under a relative SAF directory recursively. Returns full relative paths from the root.
|
||||
public static String[] listSafRecursiveFiles(String relativeDir) {
|
||||
java.util.ArrayList<String> out = new java.util.ArrayList<>();
|
||||
try {
|
||||
androidx.documentfile.provider.DocumentFile base = SafManager.getOrCreateDir(getContext(), relativeDir.split("/"));
|
||||
if (base == null || !base.exists()) return new String[0];
|
||||
walkDirRecursive(base, relativeDir, out);
|
||||
} catch (Throwable ignored) { }
|
||||
return out.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static void walkDirRecursive(androidx.documentfile.provider.DocumentFile dir, String relPrefix, java.util.ArrayList<String> out) {
|
||||
androidx.documentfile.provider.DocumentFile[] arr = dir.listFiles();
|
||||
if (arr == null) return;
|
||||
for (androidx.documentfile.provider.DocumentFile f : arr) {
|
||||
if (f == null) continue;
|
||||
String name = f.getName();
|
||||
if (name == null || name.isEmpty()) continue;
|
||||
if (f.isDirectory()) {
|
||||
walkDirRecursive(f, relPrefix + "/" + name, out);
|
||||
} else if (f.isFile()) {
|
||||
out.add(relPrefix + "/" + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List files directly under a relative SAF directory (non-recursive). Returns full relative paths.
|
||||
public static String[] listSafFilesFlat(String relativeDir) {
|
||||
java.util.ArrayList<String> out = new java.util.ArrayList<>();
|
||||
try {
|
||||
androidx.documentfile.provider.DocumentFile dir = SafManager.getOrCreateDir(getContext(), relativeDir.split("/"));
|
||||
if (dir == null || !dir.isDirectory()) return new String[0];
|
||||
androidx.documentfile.provider.DocumentFile[] arr = dir.listFiles();
|
||||
if (arr != null) {
|
||||
for (androidx.documentfile.provider.DocumentFile f : arr) {
|
||||
if (f != null && f.isFile()) {
|
||||
String name = f.getName();
|
||||
if (name != null && !name.isEmpty()) out.add(relativeDir + "/" + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) { }
|
||||
return out.toArray(new String[0]);
|
||||
}
|
||||
|
||||
// List filenames (files only) under a SAF subdirectory (e.g., "cheats", "patches").
|
||||
public static String[] listSafFilenames(String subdir) {
|
||||
try {
|
||||
androidx.documentfile.provider.DocumentFile dir = SafManager.getOrCreateDir(getContext(), subdir);
|
||||
if (dir == null || !dir.isDirectory()) return new String[0];
|
||||
androidx.documentfile.provider.DocumentFile[] arr = dir.listFiles();
|
||||
java.util.ArrayList<String> out = new java.util.ArrayList<>();
|
||||
if (arr != null) {
|
||||
for (androidx.documentfile.provider.DocumentFile f : arr) {
|
||||
if (f != null && f.isFile()) {
|
||||
String name = f.getName();
|
||||
if (name != null && !name.isEmpty()) out.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out.toArray(new String[0]);
|
||||
} catch (Throwable ignored) { }
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
boolean noInterlacingPatches = prefs.getBoolean("no_interlacing_patches", true);
|
||||
boolean loadTextures = prefs.getBoolean("load_textures", false);
|
||||
boolean asyncTextureLoading = prefs.getBoolean("async_texture_loading", true);
|
||||
boolean precacheTextures = prefs.getBoolean("precache_textures", false);
|
||||
boolean hudVisible = prefs.getBoolean("hud_visible", false);
|
||||
|
||||
// Debug logging
|
||||
@@ -58,6 +59,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
NativeApp.setNoInterlacingPatches(noInterlacingPatches);
|
||||
NativeApp.setLoadTextures(loadTextures);
|
||||
NativeApp.setAsyncTextureLoading(asyncTextureLoading);
|
||||
NativeApp.setPrecacheTextureReplacements(precacheTextures);
|
||||
NativeApp.setHudVisible(hudVisible);
|
||||
|
||||
// Set brighter default brightness (60 instead of 50)
|
||||
@@ -85,6 +87,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
MaterialSwitch swNoInterlacing = view.findViewById(R.id.sw_no_interlacing);
|
||||
MaterialSwitch swLoadTextures = view.findViewById(R.id.sw_load_textures);
|
||||
MaterialSwitch swAsyncTextureLoading = view.findViewById(R.id.sw_async_texture_loading);
|
||||
MaterialSwitch swPrecacheTextures = view.findViewById(R.id.sw_precache_textures);
|
||||
MaterialSwitch swDevHud = view.findViewById(R.id.sw_dev_hud);
|
||||
View btnPower = view.findViewById(R.id.btn_power);
|
||||
View btnReboot = view.findViewById(R.id.btn_reboot);
|
||||
@@ -187,6 +190,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
boolean savedNoInterlacing = prefs.getBoolean("no_interlacing_patches", true);
|
||||
boolean savedLoadTextures = prefs.getBoolean("load_textures", false);
|
||||
boolean savedAsyncTextureLoading = prefs.getBoolean("async_texture_loading", true);
|
||||
boolean savedPrecacheTextures = prefs.getBoolean("precache_textures", false);
|
||||
boolean savedHud = prefs.getBoolean("hud_visible", false);
|
||||
boolean savedCheatsGlobal = prefs.getBoolean("enable_cheats", false);
|
||||
int savedBlending = prefs.getInt("blending_accuracy", 1);
|
||||
@@ -213,6 +217,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
swNoInterlacing.setChecked(savedNoInterlacing);
|
||||
swLoadTextures.setChecked(savedLoadTextures);
|
||||
swAsyncTextureLoading.setChecked(savedAsyncTextureLoading);
|
||||
if (swPrecacheTextures != null) swPrecacheTextures.setChecked(savedPrecacheTextures);
|
||||
if (swDevHud != null) swDevHud.setChecked(savedHud);
|
||||
MaterialSwitch swCheatsGlobal = view.findViewById(R.id.sw_enable_cheats_global);
|
||||
if (swCheatsGlobal != null) swCheatsGlobal.setChecked(savedCheatsGlobal);
|
||||
@@ -235,7 +240,8 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
boolean widescreenPatches = swWidescreen.isChecked();
|
||||
boolean noInterlacingPatches = swNoInterlacing.isChecked();
|
||||
boolean loadTextures = swLoadTextures.isChecked();
|
||||
boolean asyncTextureLoading = swAsyncTextureLoading.isChecked();
|
||||
boolean asyncTextureLoading = swAsyncTextureLoading.isChecked();
|
||||
boolean precacheTextureReplacements = swPrecacheTextures != null && swPrecacheTextures.isChecked();
|
||||
boolean hudVisible = (swDevHud != null && swDevHud.isChecked());
|
||||
boolean enableCheatsGlobal = swCheatsGlobal != null && swCheatsGlobal.isChecked();
|
||||
|
||||
@@ -250,17 +256,20 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
.putBoolean("no_interlacing_patches", noInterlacingPatches)
|
||||
.putBoolean("load_textures", loadTextures)
|
||||
.putBoolean("async_texture_loading", asyncTextureLoading)
|
||||
.putBoolean("precache_textures", precacheTextureReplacements)
|
||||
.putBoolean("hud_visible", hudVisible)
|
||||
.putBoolean("enable_cheats", enableCheatsGlobal)
|
||||
.apply();
|
||||
|
||||
// Apply in one batch to avoid repeated ApplySettings calls
|
||||
try {
|
||||
NativeApp.applyGlobalSettingsBatch(renderer, scale, aspectRatio, blendingLevel,
|
||||
widescreenPatches, noInterlacingPatches, loadTextures, asyncTextureLoading, hudVisible);
|
||||
} catch (Throwable t) {
|
||||
android.util.Log.e("SettingsDialog", "Batch apply failed: " + t.getMessage());
|
||||
}
|
||||
try {
|
||||
NativeApp.applyGlobalSettingsBatch(renderer, scale, aspectRatio, blendingLevel,
|
||||
widescreenPatches, noInterlacingPatches, loadTextures, asyncTextureLoading, hudVisible);
|
||||
// Apply precache separately (not part of the batch JNI)
|
||||
NativeApp.setPrecacheTextureReplacements(precacheTextureReplacements);
|
||||
} catch (Throwable t) {
|
||||
android.util.Log.e("SettingsDialog", "Apply failed: " + t.getMessage());
|
||||
}
|
||||
|
||||
// Refresh quick UI (renderer label) if hosting activity is MainActivity
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user