diff --git a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp index a9d75ed0e4..a02a7ae5eb 100644 --- a/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp +++ b/builds/android/app/src/gamebrowser/org_easyrpg_player_game_browser.cpp @@ -187,6 +187,10 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass, // jpath is the SAF path to the game, is converted to FilesystemView "root" std::string spath = jstring_to_string(env, jpath); auto root = FileFinder::Root().Create(spath); + if (!root) { + return nullptr; + } + root.ClearCache(); auto ge_list = FileFinder::FindGames(root); @@ -196,7 +200,7 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass, if (ge_list.empty()) { // No games found - return jgame_array; + return nullptr; } jmethodID jgame_constructor_unsupported = env->GetMethodID(jgame_class, "", "(I)V"); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java index dfe119198e..4c9d798829 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/Helper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/Helper.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; @@ -17,6 +18,8 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; @@ -356,4 +359,41 @@ public static Bitmap createBitmapFromRGBA(byte[] rgba, int width, int height) { return bitmap; } + public static void attachOpenFolderButton(Context context, Button button, Uri uri) { + if (android.os.Build.VERSION.SDK_INT >= 26) { + button.setOnClickListener(v -> { + openFileBrowser(context, uri); + }); + } else { + // ACTION_OPEN_DOCUMENT does not support providing an URI + // Useless, remove the button + ViewGroup layout = (ViewGroup) button.getParent(); + if(layout != null) { + layout.removeView(button); + } + } + } + + public static boolean openFileBrowser(Context context, Uri uri) { + if (android.os.Build.VERSION.SDK_INT >= 29) { + // Open the file explorer in the folder specified by URI + // This opens a real file browser which allows file operations like view, copy, etc. + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(uri, DocumentsContract.Document.MIME_TYPE_DIR); + context.startActivity(intent); + } else if (android.os.Build.VERSION.SDK_INT >= 26) { + // Open the file explorer in the folder specified by URI + // This opens a (useless) file chooser which closes itself after selecting a file + // Still better than nothing because the user can see where the folder is + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri); + context.startActivity(intent); + } else { + // ACTION_OPEN_DOCUMENT does not support providing an URI + return false; + } + + return true; + } } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java index d9c67217c8..800501bc6c 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/Game.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; +import org.easyrpg.player.Helper; import org.easyrpg.player.settings.SettingsManager; import java.io.ByteArrayOutputStream; @@ -188,6 +189,20 @@ public String toString() { return getDisplayTitle(); } + public Uri createSaveUri(Context context) { + if (!getSavePath().isEmpty()) { + DocumentFile saveFolder = Helper.createFolderInSave(context, getSavePath()); + + if (saveFolder != null) { + return saveFolder.getUri(); + } + } else { + return Uri.parse(getGameFolderPath()); + } + + return null; + } + public static Game fromCacheEntry(Context context, String cache) { String[] entries = cache.split(String.valueOf(escapeCode)); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java index 2206c522fb..746c885af3 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserActivity.java @@ -23,7 +23,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; @@ -33,10 +32,13 @@ import com.google.android.material.navigation.NavigationView; import org.easyrpg.player.BaseActivity; +import org.easyrpg.player.Helper; import org.easyrpg.player.R; import org.easyrpg.player.settings.SettingsManager; import org.libsdl.app.SDL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -358,22 +360,33 @@ public void onBindViewHolder(final ViewHolder holder, final int position) { // Settings Button holder.settingsButton.setOnClickListener(v -> { - String[] choices_list = { + ArrayList choices_list = new ArrayList(Arrays.asList( activity.getResources().getString(R.string.select_game_region), activity.getResources().getString(R.string.game_rename), activity.getResources().getString(R.string.launch_debug_mode) - }; + )); + + if (android.os.Build.VERSION.SDK_INT >= 26) { + choices_list.add(activity.getResources().getString(R.string.open_save_folder)); + } + + // It's 2025 and converting an ArrayList to an Array is still hot-garbage in Java + // because of type erasure and ugly APIs + String[] choices_list_arr = new String[choices_list.size()]; + choices_list.toArray(choices_list_arr); AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder .setTitle(R.string.settings) - .setItems(choices_list, (dialog, which) -> { + .setItems(choices_list_arr, (dialog, which) -> { if (which == 0) { - chooseRegion(activity, holder, gameList.get(position)); + chooseRegion(activity, holder, game); } else if (which == 1) { - renameGame(activity, holder, gameList.get(position)); + renameGame(activity, holder, game); } else if (which == 2) { launchGame(position, true); + } else if (which == 3) { + Helper.openFileBrowser(activity, game.createSaveUri(activity)); } }); builder.show(); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java index 131dfd1212..fbe4271a06 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameBrowserHelper.java @@ -194,14 +194,22 @@ public static SafError dealAfterFolderSelected(Activity activity, int requestCod List items = Helper.listChildrenDocuments(activity, folder.getUri()); int item_count = 0; + for (String[] item: items) { - if (item[0] == null || Helper.isDirectoryFromMimeType(item[1]) || item[0].endsWith(".nomedia")) { + if (item[2] == null || + item[2].contains(".") || + item[2].equals(SettingsManager.RTP_FOLDER_NAME) || + item[2].equals(SettingsManager.GAMES_FOLDER_NAME) || + item[2].equals(SettingsManager.SOUND_FONTS_FOLDER_NAME) || + item[2].equals(SettingsManager.SAVES_FOLDER_NAME) || + item[2].equals(SettingsManager.FONTS_FOLDER_NAME) + ) { continue; } item_count += 1; - if (item_count >= 3) { + if (item_count > 3) { return SafError.FOLDER_NOT_ALMOST_EMPTY; } } diff --git a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java index 543c77f0df..687f388d65 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/game_browser/GameScanner.java @@ -171,6 +171,10 @@ private void scanRootFolder(Activity activity, Uri folderURI) { Game[] candidates = findGames(fileURIs.get(i).toString(), names.get(i)); + if (candidates == null) { + continue; + } + for (Game candidate: candidates) { if (candidate != null) { gameList.add(candidate); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java index f23cc78845..a8fde730b8 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsAudioActivity.java @@ -37,21 +37,7 @@ protected void onCreate(Bundle savedInstanceState) { // Setup UI components // The Soundfont Button Button button = this.findViewById(R.id.button_open_soundfont_folder); - // We can open the file picker in a specific folder only with API >= 26 - if (android.os.Build.VERSION.SDK_INT >= 26) { - button.setOnClickListener(v -> { - // Open the file explorer in the "soundfont" folder - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, SettingsManager.getSoundFontsFolderURI(this)); - startActivity(intent); - }); - } else { - ViewGroup layout = (ViewGroup) button.getParent(); - if(layout != null) { - layout.removeView(button); - } - } + Helper.attachOpenFolderButton(this, button, SettingsManager.getSoundFontsFolderURI(this)); configureMusicVolume(); configureSoundVolume(); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java index f3546c1624..baa373d9c9 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsFontActivity.java @@ -49,21 +49,7 @@ protected void onCreate(Bundle savedInstanceState) { // Setup UI components // The Font Button Button button = this.findViewById(R.id.button_open_font_folder); - // We can open the file picker in a specific folder only with API >= 26 - if (android.os.Build.VERSION.SDK_INT >= 26) { - button.setOnClickListener(v -> { - // Open the file explorer in the "fonts" folder - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, SettingsManager.getFontsFolderURI(this)); - startActivity(intent); - }); - } else { - ViewGroup layout = (ViewGroup) button.getParent(); - if(layout != null) { - layout.removeView(button); - } - } + Helper.attachOpenFolderButton(this, button, SettingsManager.getFontsFolderURI(this)); configureFont1Size(); configureFont2Size(); diff --git a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java index 388041125d..c43ddaacc4 100644 --- a/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java +++ b/builds/android/app/src/main/java/org/easyrpg/player/settings/SettingsGamesFolderActivity.java @@ -13,6 +13,7 @@ import androidx.appcompat.app.AppCompatActivity; import org.easyrpg.player.BaseActivity; +import org.easyrpg.player.Helper; import org.easyrpg.player.R; import org.easyrpg.player.game_browser.GameBrowserActivity; import org.easyrpg.player.game_browser.GameBrowserHelper; @@ -41,39 +42,11 @@ public void onCreate(Bundle savedInstanceState) { // Setup UI components // The "Open Game Folder" Button Button openGameFolderButton = this.findViewById(R.id.open_game_folder); - // We can open the file picker in a specific folder only with API >= 26 - if (android.os.Build.VERSION.SDK_INT >= 26) { - openGameFolderButton.setOnClickListener(v -> { - // Open the file explorer in the "soundfont" folder - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, SettingsManager.getGamesFolderURI(this)); - startActivity(intent); - }); - } else { - ViewGroup layout = (ViewGroup) openGameFolderButton.getParent(); - if(layout != null) { - layout.removeView(openGameFolderButton); - } - } + Helper.attachOpenFolderButton(this, openGameFolderButton, SettingsManager.getGamesFolderURI(this)); // The "Open RTP Folder" Button Button openRTPFolderButton = this.findViewById(R.id.open_rtp_folder); - // We can open the file picker in a specific folder only with API >= 26 - if (android.os.Build.VERSION.SDK_INT >= 26) { - openRTPFolderButton.setOnClickListener(v -> { - // Open the file explorer in the "soundfont" folder - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, SettingsManager.getRTPFolderURI(this)); - startActivity(intent); - }); - } else { - ViewGroup layout = (ViewGroup) openRTPFolderButton.getParent(); - if(layout != null) { - layout.removeView(openRTPFolderButton); - } - } + Helper.attachOpenFolderButton(this, openRTPFolderButton, SettingsManager.getRTPFolderURI(this)); // Video button findViewById(R.id.watch_video).setOnClickListener(v -> { diff --git a/builds/android/app/src/main/res/values/strings.xml b/builds/android/app/src/main/res/values/strings.xml index 3ccec56121..fcf472fa4b 100644 --- a/builds/android/app/src/main/res/values/strings.xml +++ b/builds/android/app/src/main/res/values/strings.xml @@ -34,6 +34,7 @@ Change the layout Launch in debug mode Rename game + Open savegame folder Choose a layout Unknown region Changing region failed diff --git a/src/filesystem_lzh.cpp b/src/filesystem_lzh.cpp index d69de8793d..f4925c4106 100644 --- a/src/filesystem_lzh.cpp +++ b/src/filesystem_lzh.cpp @@ -99,6 +99,15 @@ LzhFilesystem::LzhFilesystem(std::string base_path, FilesystemView parent_fs, St // Compressed data offset is manually calculated to reduce calls to tellg() auto last_offset = is.tellg(); + // Read one file, when it fails consider it not an Lzh archive + if (lha_reader_next_file(lha_reader.get()) == nullptr) { + Output::Debug("LzhFS: {} is not a valid archive", GetPath()); + return; + } + + is.clear(); + is.seekg(last_offset); + // Guess the encoding if (encoding.empty()) { std::stringstream filename_guess;