diff --git a/include/android_utils.hpp b/include/android_utils.hpp new file mode 100644 index 00000000..0e1a016a --- /dev/null +++ b/include/android_utils.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace AndroidUtils { + int openDocument(const char* directory, const char* mode); +} \ No newline at end of file diff --git a/src/io_file.cpp b/src/io_file.cpp index 1f794284..3bfac013 100644 --- a/src/io_file.cpp +++ b/src/io_file.cpp @@ -21,6 +21,10 @@ #include // For ftruncate #endif +#ifdef __ANDROID__ +#include "android_utils.hpp" +#endif + IOFile::IOFile(const std::filesystem::path& path, const char* permissions) : handle(nullptr) { open(path, permissions); } bool IOFile::open(const std::filesystem::path& path, const char* permissions) { @@ -34,8 +38,19 @@ bool IOFile::open(const char* filename, const char* permissions) { if (isOpen()) { close(); } + #ifdef __ANDROID__ + std::string path(filename); + + // Check if this is a URI directory, which will need special handling due to SAF + if (path.find("://") != std::string::npos ) { + handle = fdopen(AndroidUtils::openDocument(filename, permissions), permissions); + } else { + handle = std::fopen(filename, permissions); + } + #else + handle = std::fopen(filename, permissions); + #endif - handle = std::fopen(filename, permissions); return isOpen(); } diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 1af325e3..e4ce2b39 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -7,6 +7,7 @@ #include "emulator.hpp" #include "renderer_gl/renderer_gl.hpp" #include "services/hid.hpp" +#include "android_utils.hpp" std::unique_ptr emulator = nullptr; HIDService* hidService = nullptr; @@ -14,6 +15,9 @@ RendererGL* renderer = nullptr; bool romLoaded = false; JavaVM* jvm = nullptr; +jclass alberClass; +jmethodID alberClassOpenDocument; + #define AlberFunction(type, name) JNIEXPORT type JNICALL Java_com_panda3ds_pandroid_AlberDriver_##name void throwException(JNIEnv* env, const char* message) { @@ -42,7 +46,13 @@ MAKE_SETTING(setShaderJitEnabled, jboolean, shaderJitEnabled) #undef MAKE_SETTING -AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { env->GetJavaVM(&jvm); } +AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { + env->GetJavaVM(&jvm); + + alberClass = (jclass)env->NewGlobalRef((jclass)env->FindClass("com/panda3ds/pandroid/AlberDriver")); + alberClassOpenDocument = env->GetStaticMethodID(alberClass, "openDocument", "(Ljava/lang/String;Ljava/lang/String;)I"); +} + AlberFunction(void, Pause)(JNIEnv* env, jobject obj) { emulator->pause(); } AlberFunction(void, Resume)(JNIEnv* env, jobject obj) { emulator->resume(); } @@ -116,3 +126,17 @@ AlberFunction(jbyteArray, GetSmdh)(JNIEnv* env, jobject obj) { } #undef AlberFunction + +int AndroidUtils::openDocument(const char* path, const char* perms) { + auto env = jniEnv(); + + jstring uri = env->NewStringUTF(path); + jstring jmode = env->NewStringUTF(perms); + + jint result = env->CallStaticIntMethod(alberClass, alberClassOpenDocument, uri, jmode); + + env->DeleteLocalRef(uri); + env->DeleteLocalRef(jmode); + + return (int)result; +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index c66d37af..d1d8f56f 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -2,10 +2,6 @@ - - - - diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java index 7367a447..f7a3394b 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java @@ -1,6 +1,14 @@ package com.panda3ds.pandroid; -import android.util.Log; +import android.content.Context; +import android.net.Uri; +import android.os.ParcelFileDescriptor; + +import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.utils.FileUtils; +import com.panda3ds.pandroid.utils.GameUtils; + +import java.util.Objects; public class AlberDriver { AlberDriver() { super(); } @@ -24,5 +32,26 @@ public class AlberDriver { public static native void setShaderJitEnabled(boolean enable); + public static int openDocument(String path, String mode) { + try { + mode = FileUtils.parseNativeMode(mode); + Context context = PandroidApplication.getAppContext(); + Uri uri = FileUtils.obtainUri(path); + ParcelFileDescriptor parcel; + if (Objects.equals(uri.getScheme(), "game")) { + if (mode.contains("w")) { + throw new IllegalArgumentException("Cannot open ROM file as writable"); + } + uri = FileUtils.obtainUri(GameUtils.getCurrentGame().getRealPath()); + } + parcel = context.getContentResolver().openFileDescriptor(uri, mode); + int fd = parcel.detachFd(); + parcel.close(); + + return fd; + } catch (Exception e) { + throw new RuntimeException(e); + } + } static { System.loadLibrary("Alber"); } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java index 18914a80..5e03e516 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java @@ -1,32 +1,18 @@ package com.panda3ds.pandroid.app; -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; -import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; -import static android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION; - -import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.view.MenuItem; import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; + import com.google.android.material.navigation.NavigationBarView; import com.panda3ds.pandroid.R; -import com.panda3ds.pandroid.app.editor.CodeEditorActivity; import com.panda3ds.pandroid.app.main.GamesFragment; import com.panda3ds.pandroid.app.main.SearchFragment; import com.panda3ds.pandroid.app.main.SettingsFragment; -import java.io.File; - - public class MainActivity extends BaseActivity implements NavigationBarView.OnItemSelectedListener { - private static final int PICK_ROM = 2; - private static final int PERMISSION_REQUEST_CODE = 3; - private final GamesFragment gamesFragment = new GamesFragment(); private final SearchFragment searchFragment = new SearchFragment(); private final SettingsFragment settingsFragment = new SettingsFragment(); @@ -34,16 +20,6 @@ public class MainActivity extends BaseActivity implements NavigationBarView.OnIt @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (!Environment.isExternalStorageManager()) { - Intent intent = new Intent(ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); - startActivity(intent); - } - } else { - ActivityCompat.requestPermissions(this, new String[] {READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); - ActivityCompat.requestPermissions(this, new String[] {WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); - } - setContentView(R.layout.activity_main); NavigationBarView bar = findViewById(R.id.navigation); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/LoadingAlertDialog.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/LoadingAlertDialog.java new file mode 100644 index 00000000..61887fb9 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/LoadingAlertDialog.java @@ -0,0 +1,22 @@ +package com.panda3ds.pandroid.app.base; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.appcompat.widget.AppCompatTextView; + +import com.panda3ds.pandroid.R; + +public class LoadingAlertDialog extends BottomAlertDialog { + public LoadingAlertDialog(@NonNull Context context, @StringRes int title) { + super(context); + View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading,null, false); + setView(view); + setCancelable(false); + ((AppCompatTextView)view.findViewById(R.id.title)) + .setText(title); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java index e15a27f5..a1fa9eec 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/DrawerFragment.java @@ -105,7 +105,7 @@ public class DrawerFragment extends Fragment implements DrawerLayout.DrawerListe close(); } else if (id == R.id.exit) { requireActivity().finish(); - } else if (id == R.id.lua_script){ + } else if (id == R.id.lua_script) { new LuaDialogFragment().show(getParentFragmentManager(), null); } else if (id == R.id.change_orientation) { boolean isLandscape = getResources().getDisplayMetrics().widthPixels > getResources().getDisplayMetrics().heightPixels; diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/LuaDialogFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/LuaDialogFragment.java index da059399..1d573e42 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/LuaDialogFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/LuaDialogFragment.java @@ -19,6 +19,7 @@ import com.panda3ds.pandroid.app.base.BottomAlertDialog; import com.panda3ds.pandroid.app.base.BottomDialogFragment; import com.panda3ds.pandroid.app.editor.CodeEditorActivity; import com.panda3ds.pandroid.lang.Task; +import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.FileUtils; import com.panda3ds.pandroid.view.recycler.AutoFitGridLayout; import com.panda3ds.pandroid.view.recycler.SimpleListAdapter; @@ -94,9 +95,8 @@ public class LuaDialogFragment extends BottomDialogFragment { ((RecyclerView) view.findViewById(R.id.recycler)).setAdapter(adapter); ((RecyclerView) view.findViewById(R.id.recycler)).setLayoutManager(new AutoFitGridLayout(getContext(), 140)); - FileUtils.createDir(FileUtils.getResourcesPath(), "Lua Scripts"); ArrayList files = new ArrayList<>(); - String path = FileUtils.getResourcesPath() + "/Lua Scripts/"; + String path = FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_LUA_SCRIPTS); for (String file : FileUtils.listFiles(path)) { files.add(new LuaFile(file)); } @@ -167,7 +167,7 @@ public class LuaDialogFragment extends BottomDialogFragment { } private LuaFile(String name) { - this(FileUtils.getResourcesPath() + "/Lua Scripts/", name); + this(FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_LUA_SCRIPTS), name); } private String absolutePath() { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java index ff6e4dca..a5c673f5 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java @@ -11,13 +11,17 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.base.LoadingAlertDialog; import com.panda3ds.pandroid.data.game.GameMetadata; +import com.panda3ds.pandroid.lang.Task; +import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.FileUtils; import com.panda3ds.pandroid.utils.GameUtils; import com.panda3ds.pandroid.view.gamesgrid.GamesGridView; - +import java.util.UUID; public class GamesFragment extends Fragment implements ActivityResultCallback { private final ActivityResultContracts.OpenDocument openRomContract = new ActivityResultContracts.OpenDocument(); @@ -49,18 +53,45 @@ public class GamesFragment extends Fragment implements ActivityResultCallback { + String uuid = UUID.randomUUID().toString() + "." + FileUtils.extension(uri); + String name = FileUtils.getName(uri); + FileUtils.copyFile(uri, FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_ELF), uuid); + gameListView.post(() -> { + dialog.hide(); + GameMetadata game = new GameMetadata("elf://" + uuid, name.substring(0, name.length() - 4).trim(), ""); + GameUtils.addGame(game); + GameUtils.launch(requireActivity(), game); + }); + }).start(); + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java index 512a3725..5acf2593 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java @@ -43,6 +43,10 @@ public class GameMetadata { return romPath; } + public String getRealPath() { + return GameUtils.resolvePath(romPath); + } + public String getId() { return id; } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/Constants.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/Constants.java index c72a516a..28276920 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/Constants.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/Constants.java @@ -26,4 +26,6 @@ public class Constants { public static final String PREF_GAME_UTILS = "app.GameUtils"; public static final String PREF_INPUT_MAP = "app.InputMap"; public static final String PREF_SCREEN_CONTROLLER_PROFILES = "app.input.ScreenControllerManager"; + public static final String RESOURCE_FOLDER_ELF = "ELF"; // Folder for caching ELF files + public static final String RESOURCE_FOLDER_LUA_SCRIPTS = "Lua Scripts"; } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java index 1746f1c9..2920c7c6 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java @@ -39,7 +39,7 @@ public class FileUtils { return parseFile(path).getName(); } - public static String getResourcesPath(){ + public static String getResourcesPath() { File file = new File(getPrivatePath(), "config/resources"); if (!file.exists()) { file.mkdirs(); @@ -48,6 +48,13 @@ public class FileUtils { return file.getAbsolutePath(); } + public static String getResourcePath(String name) { + File file = new File(getResourcesPath(), name); + file.mkdirs(); + + return file.getAbsolutePath(); + } + public static String getPrivatePath() { File file = getContext().getFilesDir(); if (!file.exists()) { @@ -66,11 +73,33 @@ public class FileUtils { return file.getAbsolutePath(); } + public static String parseNativeMode(String mode) { + mode = mode.toLowerCase(); + switch (mode) { + case "r": + case "rb": + return "r"; + case "r+": + case "r+b": + case "rb+": + return "rw"; + case "w+": + return "rwt"; + case "w": + case "wb": + return "wt"; + case "wa": + return "wa"; + } + + throw new IllegalArgumentException("Invalid file mode: "+mode); + } + public static boolean exists(String path) { return parseFile(path).exists(); } - public static void rename(String path, String newName){ + public static void rename(String path, String newName) { parseFile(path).renameTo(newName); } @@ -206,7 +235,7 @@ public class FileUtils { } } - public static void updateFile(String path){ + public static void updateFile(String path) { DocumentFile file = parseFile(path); Uri uri = file.getUri(); @@ -232,15 +261,53 @@ public class FileUtils { return parseFile(path).lastModified(); } - public static String[] listFiles(String path){ + public static String[] listFiles(String path) { DocumentFile folder = parseFile(path); DocumentFile[] files = folder.listFiles(); String[] result = new String[files.length]; - for (int i = 0; i < result.length; i++){ + for (int i = 0; i < result.length; i++) { result[i] = files[i].getName(); } return result; } + + public static Uri obtainUri(String path) { + return parseFile(path).getUri(); + } + + public static String extension(String uri) { + String name = getName(uri); + if (!name.contains(".")) { + return name.toLowerCase(); + } + String[] parts = name.split("\\."); + + return parts[parts.length-1].toLowerCase(); + } + + public static boolean copyFile(String source, String path, String name) { + try { + String fullPath = path + "/" + name; + if (!FileUtils.exists(fullPath)) { + FileUtils.delete(fullPath); + } + FileUtils.createFile(path, name); + InputStream in = getInputStream(source); + OutputStream out = getOutputStream(fullPath); + byte[] buffer = new byte[1024 * 128]; //128 KB + int length; + while ((length = in.read(buffer)) != -1) { + out.write(buffer, 0, length); + } + out.flush(); + out.close(); + in.close(); + } catch (Exception e) { + Log.e(Constants.LOG_TAG, "ERROR ON COPY FILE", e); + return false; + } + return true; + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java index b763f7b2..f050af0a 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java @@ -31,7 +31,7 @@ public class GameUtils { public static GameMetadata findByRomPath(String romPath) { for (GameMetadata game : data.games) { - if (Objects.equals(romPath, game.getRomPath())) { + if (Objects.equals(romPath, game.getRealPath())) { return game; } } @@ -40,7 +40,13 @@ public class GameUtils { public static void launch(Context context, GameMetadata game) { currentGame = game; - String path = FileUtils.obtainRealPath(game.getRomPath()); + String path = game.getRealPath(); + if (path.contains("://")) { + String[] parts = Uri.decode(game.getRomPath()).split("/"); + String name = parts[parts.length - 1]; + path = "game://internal/" + name; + } + context.startActivity(new Intent(context, GameActivity.class).putExtra(Constants.ACTIVITY_PARAMETER_PATH, path)); } @@ -58,6 +64,22 @@ public class GameUtils { writeChanges(); } + public static String resolvePath(String path) { + String lower = path.toLowerCase(); + if (!lower.contains("://")) { + return path; + } + + Uri uri = Uri.parse(path); + switch (uri.getScheme().toLowerCase()) { + case "elf": { + return FileUtils.getResourcePath(Constants.RESOURCE_FOLDER_ELF)+"/"+uri.getAuthority(); + } + } + + return path; + } + public static ArrayList getGames() { return new ArrayList<>(data.games); } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/BasicTextEditor.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/BasicTextEditor.java index 1d497656..caabae6b 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/BasicTextEditor.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/BasicTextEditor.java @@ -93,7 +93,7 @@ public class BasicTextEditor extends AppCompatEditText { super.scrollTo(scrollX, scrollY); } - public void adjustScroll(){ + public void adjustScroll() { setScroll(getScrollX(), getScrollY()); } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/syntax/PatternUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/syntax/PatternUtils.java index e3e5128a..accb0326 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/syntax/PatternUtils.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/code/syntax/PatternUtils.java @@ -3,16 +3,17 @@ package com.panda3ds.pandroid.view.code.syntax; import java.util.regex.Pattern; class PatternUtils { - public static Pattern buildGenericKeywords(String... keywords){ - StringBuilder builder = new StringBuilder(); - builder.append("\\b("); - for (int i = 0; i < keywords.length; i++){ - builder.append(keywords[i]); - if (i+1 != keywords.length){ - builder.append("|"); - } - } - builder.append(")\\b"); - return Pattern.compile(builder.toString()); - } + public static Pattern buildGenericKeywords(String... keywords) { + StringBuilder builder = new StringBuilder(); + builder.append("\\b("); + for (int i = 0; i < keywords.length; i++) { + builder.append(keywords[i]); + if (i + 1 != keywords.length) { + builder.append("|"); + } + } + + builder.append(")\\b"); + return Pattern.compile(builder.toString()); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java index e4d7be15..7688b44e 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java @@ -34,7 +34,7 @@ public class PerformanceView extends AppCompatTextView { setShadowLayer(padding,0,0,Color.BLACK); } - public void refresh(){ + public void refresh() { running = isShown(); if (!running) { return; diff --git a/src/pandroid/app/src/main/res/layout/dialog_loading.xml b/src/pandroid/app/src/main/res/layout/dialog_loading.xml new file mode 100644 index 00000000..824aee72 --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/dialog_loading.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml index 26f36faf..96a47941 100644 --- a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml @@ -54,5 +54,6 @@ Shader Jit Usar recompilador de shaders. Gráficos + Carregando Rotacionar diff --git a/src/pandroid/app/src/main/res/values/strings.xml b/src/pandroid/app/src/main/res/values/strings.xml index 9ccaa619..20e6c5c8 100644 --- a/src/pandroid/app/src/main/res/values/strings.xml +++ b/src/pandroid/app/src/main/res/values/strings.xml @@ -59,4 +59,5 @@ Shader JIT Use shader recompiler. Graphics + Loading