Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QoL: detect unsupported engines #3326

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d01908f
Add detecting project type of the game
Primekick Jan 6, 2025
8c636c8
Change FileFinder::FindGames to find and return game entries (includi…
Primekick Jan 6, 2025
e21e7c1
Add creating a Game instance from unsupported project type
Primekick Jan 7, 2025
9197e55
Change Game card to include additional information for unsupported pr…
Primekick Jan 7, 2025
f0777b0
Fix crash on loading games from cache
Primekick Jan 7, 2025
92a76cb
Show unsupported games last on the list
Primekick Jan 7, 2025
08a4eef
Add explanation dialog when trying to launch unsupported game
Primekick Jan 7, 2025
caaff24
Notify user when trying to launch a game running on a known unsupport…
Primekick Jan 7, 2025
8886081
Show information for unsupported engines directly on the game list
Primekick Jan 7, 2025
dfc5295
Add wildcard file search
Primekick Jan 7, 2025
2d78864
Split up and change the unsupported engine explanation
Primekick Jan 7, 2025
3556522
Rename fs_list to ge_list for clarity
Primekick Jan 7, 2025
945e522
Add more newlines between unsupported project explanation parts, fix …
Primekick Jan 7, 2025
cc4975a
Add encrypted 2k3MP to unsupported engines
Primekick Jan 14, 2025
305a1ea
Android: Fix "Show folder name" setting for unsupported games
Ghabry Jan 20, 2025
ea40afb
FileFilder: Make wildcard processing an option
Ghabry Jan 20, 2025
9dc544e
Game Browser: Only determine the project type on Windows/UNIX platforms
Ghabry Jan 20, 2025
2c19a17
(Sim)RM95: Use detection suggested by @florianessl
Ghabry Jan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,28 +189,30 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass,
auto root = FileFinder::Root().Create(spath);
root.ClearCache();

std::vector<FilesystemView> fs_list = FileFinder::FindGames(root);
auto ge_list = FileFinder::FindGames(root);

jclass jgame_class = env->FindClass("org/easyrpg/player/game_browser/Game");
jobjectArray jgame_array = env->NewObjectArray(fs_list.size(), jgame_class, nullptr);
jobjectArray jgame_array = env->NewObjectArray(ge_list.size(), jgame_class, nullptr);

if (fs_list.empty()) {
if (ge_list.empty()) {
// No games found
return jgame_array;
}

jmethodID jgame_constructor = env->GetMethodID(jgame_class, "<init>", "(Ljava/lang/String;Ljava/lang/String;[B)V");
jmethodID jgame_constructor_unsupported = env->GetMethodID(jgame_class, "<init>", "(I)V");
jmethodID jgame_constructor_supported = env->GetMethodID(jgame_class, "<init>", "(Ljava/lang/String;Ljava/lang/String;[BI)V");

std::string root_path = FileFinder::GetFullFilesystemPath(root);
bool game_in_main_dir = false;
if (fs_list.size() == 1) {
if (root_path == FileFinder::GetFullFilesystemPath(fs_list[0])) {
if (ge_list.size() == 1) {
if (root_path == FileFinder::GetFullFilesystemPath(ge_list[0].fs)) {
game_in_main_dir = true;
}
}

for (size_t i = 0; i < fs_list.size(); ++i) {
auto& fs = fs_list[i];
for (size_t i = 0; i < ge_list.size(); ++i) {
auto& ge = ge_list[i];
auto& fs = ge.fs;

std::string full_path = FileFinder::GetFullFilesystemPath(fs);
std::string game_dir_name;
Expand All @@ -222,6 +224,19 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass,
game_dir_name = std::get<1>(FileFinder::GetPathAndFilename(fs.GetFullPath()));
}

// If game is unsupported, create a Game object with only directory name as title and project type id and continue early
if (ge.type > FileFinder::ProjectType::Supported) {
jobject jgame_object = env->NewObject(jgame_class, jgame_constructor_unsupported, (int)ge.type);

// Use the directory name as the title
jstring jfolder = env->NewStringUTF(game_dir_name.c_str());
jmethodID jset_folder_name_method = env->GetMethodID(jgame_class, "setGameFolderName", "(Ljava/lang/String;)V");
env->CallVoidMethod(jgame_object, jset_folder_name_method, jfolder);

env->SetObjectArrayElement(jgame_array, i, jgame_object);
continue;
}

std::string save_path;
if (!fs.IsFeatureSupported(Filesystem::Feature::Write)) {
// Is an archive and needs a redirected save path
Expand All @@ -236,7 +251,7 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass,
}

// Append subdirectory when the archive contains more than one game
if (fs_list.size() > 1) {
if (ge_list.size() > 1) {
save_path += FileFinder::GetFullFilesystemPath(fs).substr(root_path.size());
}

Expand Down Expand Up @@ -348,7 +363,7 @@ Java_org_easyrpg_player_game_1browser_GameScanner_findGames(JNIEnv *env, jclass,
/* Create an instance of "Game" */
jstring jgame_path = env->NewStringUTF(("content://" + full_path).c_str());
jstring jsave_path = env->NewStringUTF(save_path.c_str());
jobject jgame_object = env->NewObject(jgame_class, jgame_constructor, jgame_path, jsave_path, title_image);
jobject jgame_object = env->NewObject(jgame_class, jgame_constructor_supported, jgame_path, jsave_path, title_image, (int)ge.type);

if (title_from_ini) {
// Store the raw string in the Game instance so it can be reencoded later via user setting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.easyrpg.player.game_browser.Game;
import org.easyrpg.player.game_browser.GameBrowserActivity;
import org.easyrpg.player.game_browser.GameBrowserHelper;
import org.easyrpg.player.game_browser.ProjectType;
import org.easyrpg.player.player.AssetUtils;
import org.easyrpg.player.settings.SettingsManager;

Expand Down Expand Up @@ -122,7 +123,7 @@ private void startGameStandalone() {
String saveDir = getExternalFilesDir(null).getAbsolutePath() + "/Save";
new File(saveDir).mkdirs();

Game project = new Game(gameDir, saveDir, null);
Game project = new Game(gameDir, saveDir, null, ProjectType.SUPPORTED.ordinal());
project.setStandalone(true);
GameBrowserHelper.launchGame(this, project);
finish();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@

public class Game implements Comparable<Game> {
final static char escapeCode = '\u0001';
final static String cacheVersion = "1";
/** The title shown in the Game Browser */
private String title;
private String title = "";
/** Bytes of the title string in an unspecified encoding */
private byte[] titleRaw = null;
/** Human readable version of the game directory. Shown in the game browser
* when the specific setting is enabled.
*/
private String gameFolderName;
private String gameFolderName = "";
/** Path to the game folder (forwarded via --project-path */
private final String gameFolderPath;
/** Relative path to the save directory, made absolute by launchGame (forwarded via --save-path) */
Expand All @@ -35,9 +34,17 @@ public class Game implements Comparable<Game> {
private Bitmap titleScreen = null;
/** Game is launched from the APK via standalone mode */
private boolean standalone = false;
/** Associated project type. Used to differentiane between supported engines and known but unsupported engines */
private ProjectType projectType = ProjectType.UNKNOWN;

public Game(String gameFolderPath, String saveFolder, byte[] titleScreen) {
public Game(int projectTypeId) {
this.projectType = ProjectType.getProjectType(projectTypeId);
this.gameFolderPath = "";
}

public Game(String gameFolderPath, String saveFolder, byte[] titleScreen, int projectTypeId) {
this.gameFolderPath = gameFolderPath;
this.projectType = ProjectType.getProjectType(projectTypeId);

// is only relative here, launchGame will put this in the "saves" directory
if (!saveFolder.isEmpty()) {
Expand All @@ -57,7 +64,7 @@ public String getDisplayTitle() {
return customTitle;
}

if (SettingsManager.getGameBrowserLabelMode() == 0) {
if (SettingsManager.getGameBrowserLabelMode() == 0 && !getTitle().isEmpty()) {
return getTitle();
} else {
return gameFolderName;
Expand Down Expand Up @@ -121,6 +128,14 @@ private boolean isFavoriteFromSettings() {

@Override
public int compareTo(Game game) {
// Unsupported games last
if (this.projectType == ProjectType.SUPPORTED && game.projectType.ordinal() > ProjectType.SUPPORTED.ordinal()) {
return -1;
}
if (this.projectType.ordinal() > ProjectType.SUPPORTED.ordinal() && game.projectType == ProjectType.SUPPORTED) {
return 1;
}
// Favorites first
if (this.isFavorite() && !game.isFavorite()) {
return -1;
}
Expand Down Expand Up @@ -176,31 +191,39 @@ public String toString() {
public static Game fromCacheEntry(Context context, String cache) {
String[] entries = cache.split(String.valueOf(escapeCode));

if (entries.length != 7 || !entries[0].equals(cacheVersion)) {
if (entries.length != 7) {
return null;
}

String savePath = entries[1];
DocumentFile gameFolder = DocumentFile.fromTreeUri(context, Uri.parse(entries[2]));
int parsedProjectType = Integer.parseInt(entries[6]);
if (parsedProjectType > ProjectType.SUPPORTED.ordinal()) {
// Unsupported game
Game g = new Game(parsedProjectType);
g.setGameFolderName(entries[2]);
return g;
}

String savePath = entries[0];
DocumentFile gameFolder = DocumentFile.fromTreeUri(context, Uri.parse(entries[1]));
if (gameFolder == null) {
return null;
}

String gameFolderName = entries[3];
String gameFolderName = entries[2];

String title = entries[4];
String title = entries[3];

byte[] titleRaw = null;
if (!entries[5].equals("null")) {
titleRaw = Base64.decode(entries[5], 0);
if (!entries[4].equals("null")) {
titleRaw = Base64.decode(entries[4], 0);
}

byte[] titleScreen = null;
if (!entries[6].equals("null")) {
titleScreen = Base64.decode(entries[6], 0);
if (!entries[5].equals("null")) {
titleScreen = Base64.decode(entries[5], 0);
}

Game g = new Game(entries[2], savePath, titleScreen);
Game g = new Game(entries[1], savePath, titleScreen, parsedProjectType);
g.setTitle(title);
g.titleRaw = titleRaw;

Expand All @@ -216,33 +239,40 @@ public static Game fromCacheEntry(Context context, String cache) {
public String toCacheEntry() {
StringBuilder sb = new StringBuilder();

// Cache structure: savePath | gameFolderPath | title | titleRaw | titleScreen
sb.append(cacheVersion); // 0
// Cache structure: savePath | gameFolderPath | gameFolderName | title | titleRaw | titleScreen | projectType
sb.append(savePath); // 0
sb.append(escapeCode);
sb.append(savePath); // 1
sb.append(gameFolderPath); // 1
sb.append(escapeCode);
sb.append(gameFolderPath); // 2
sb.append(gameFolderName); // 2
sb.append(escapeCode);
sb.append(gameFolderName); // 3
sb.append(title); // 3
sb.append(escapeCode);
sb.append(title); // 4
sb.append(escapeCode);
if (titleRaw != null) { // 5
if (titleRaw != null) { // 4
sb.append(Base64.encodeToString(titleRaw, Base64.NO_WRAP));
} else {
sb.append("null");
}
sb.append(escapeCode);
if (titleScreen != null) { // 6
if (titleScreen != null) { // 5
ByteArrayOutputStream baos = new ByteArrayOutputStream();
titleScreen.compress(Bitmap.CompressFormat.PNG, 90, baos);
byte[] b = baos.toByteArray();
sb.append(Base64.encodeToString(b, Base64.NO_WRAP));
} else {
sb.append("null");
}
sb.append(escapeCode);
sb.append(projectType.ordinal()); // 6

return sb.toString();
}

public boolean isProjectTypeUnsupported() {
return this.projectType.ordinal() > ProjectType.SUPPORTED.ordinal();
}

public String getProjectTypeLabel() {
return this.projectType.getLabel();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.text.InputType;
import android.util.DisplayMetrics;
import android.util.Log;
Expand All @@ -16,13 +15,11 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
Expand Down Expand Up @@ -333,6 +330,24 @@ public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
public void onBindViewHolder(final ViewHolder holder, final int position) {
final Game game = gameList.get(position);

if (game.isProjectTypeUnsupported()) {
// Title
holder.title.setText(game.getDisplayTitle());

// Subtitle - engine unsupported message
holder.subtitle.setText(activity.getResources().getString(R.string.unsupported_engine_card).replace("$ENGINE", game.getProjectTypeLabel()));

// Hide settings button
holder.settingsButton.setVisibility(View.INVISIBLE);

// Add click listeners
holder.title.setOnClickListener(v -> showUnsupportedProjectTypeExplanation(activity, game.getProjectTypeLabel()));
holder.subtitle.setOnClickListener(v -> showUnsupportedProjectTypeExplanation(activity, game.getProjectTypeLabel()));
holder.titleScreen.setOnClickListener(v -> showUnsupportedProjectTypeExplanation(activity, game.getProjectTypeLabel()));

return;
}

// Title
holder.title.setText(game.getDisplayTitle());
holder.title.setOnClickListener(v -> launchGame(position, false));
Expand Down Expand Up @@ -448,14 +463,28 @@ public void renameGame(final Context context, final ViewHolder holder, final Gam
builder.show();
}

private void showUnsupportedProjectTypeExplanation(final Context context, String projectType) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);

String message = context.getString(R.string.unsupported_engine_explanation).replace("$ENGINE", projectType);

builder
.setTitle(R.string.unsupported_engine_title)
.setMessage(message)
.setNeutralButton(R.string.ok, null);
builder.show();
}

public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public TextView subtitle;
public ImageView titleScreen;
public ImageButton settingsButton, favoriteButton;

public ViewHolder(View v) {
super(v);
this.title = v.findViewById(R.id.title);
this.subtitle = v.findViewById(R.id.subtitle);
this.titleScreen = v.findViewById(R.id.screen);
this.settingsButton = v.findViewById(R.id.game_browser_thumbnail_option_button);
this.favoriteButton = v.findViewById(R.id.game_browser_thumbnail_favorite_button);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private void scanGames(Activity activity){
private int scanFolderHash(Context context, Uri folderURI) {
StringBuilder sb = new StringBuilder();

sb.append("2"); // Bump this when the cache layout changes
sb.append("3"); // Bump this when the cache layout changes
for (String[] array : Helper.listChildrenDocuments(context, folderURI)) {
sb.append(array[0]);
sb.append(array[1]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.easyrpg.player.game_browser;

import android.content.Context;

import org.easyrpg.player.R;

public enum ProjectType {
UNKNOWN("Unknown"),
SUPPORTED("Supported"),
RPG_MAKER_XP("RPG Maker XP"),
RPG_MAKER_VX("RPG Maker VX"),
RPG_MAKER_VX_ACE("RPG Maker VX Ace"),
RPG_MAKER_MV_MZ("RPG Maker MV/MZ"),
WOLF_RPG_EDITOR("Wolf RPG Editor"),
ENCRYPTED_2K3_MANIACS("Encrypted 2k3 (Maniacs Patch)"),
RPG_MAKER_95("RPG Maker 95"),
SIM_RPG_MAKER_95("Sim RPG Maker 95");

private final String label;

ProjectType(String label) {
this.label = label;
}

public String getLabel() {
return this.label;
}

public static ProjectType getProjectType(int i) {
return ProjectType.values()[i];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@
android:gravity="center_horizontal"
/>

<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"

android:layout_alignParentStart="true"

android:layout_centerVertical="true"
android:gravity="center_horizontal"
android:padding="8dp"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="normal" />

<ImageButton
android:id="@+id/game_browser_thumbnail_favorite_button"
android:background="@android:color/transparent"
Expand Down
Loading
Loading