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 14 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,45 @@ 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);
std::vector<FileFinder::GameEntry> 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])) {
game_in_main_dir = true;
}
if (ge_list.size() == 1) {
if (ge_list[0].type == FileFinder::ProjectType::Supported &&
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;

// 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
auto dir = jstring_to_string(env, jmain_dir_name);
jstring jtitle = env->NewStringUTF(dir.c_str());
jmethodID jset_title_method = env->GetMethodID(jgame_class, "setTitle", "(Ljava/lang/String;)V");
env->CallVoidMethod(jgame_object, jset_title_method, jtitle);

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

std::string full_path = FileFinder::GetFullFilesystemPath(fs);
std::string game_dir_name;
Expand All @@ -236,7 +253,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 +365,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 @@ -35,9 +35,18 @@ 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.gameFolderPath = "";
this.gameFolderName = "";
this.projectType = ProjectType.getProjectType(projectTypeId);
}

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 Down Expand Up @@ -121,6 +130,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,10 +193,18 @@ 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 != 8 || !entries[0].equals(cacheVersion)) {
return null;
}

int parsedProjectType = Integer.parseInt(entries[7]);
if (parsedProjectType > ProjectType.SUPPORTED.ordinal()) {
Game g = new Game(parsedProjectType);
g.setTitle(entries[4]);

return g;
}

String savePath = entries[1];
DocumentFile gameFolder = DocumentFile.fromTreeUri(context, Uri.parse(entries[2]));
if (gameFolder == null) {
Expand All @@ -200,7 +225,7 @@ public static Game fromCacheEntry(Context context, String cache) {
titleScreen = Base64.decode(entries[6], 0);
}

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

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

// Cache structure: savePath | gameFolderPath | title | titleRaw | titleScreen
// Cache structure: savePath | gameFolderPath | title | titleRaw | titleScreen | projectType
sb.append(cacheVersion); // 0
sb.append(escapeCode);
sb.append(savePath); // 1
Expand All @@ -241,8 +266,17 @@ public String toCacheEntry() {
} else {
sb.append("null");
}
sb.append(escapeCode);
sb.append(projectType.ordinal());

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(String.format("%s\n%s", activity.getResources().getString(R.string.unsupported_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,30 @@ 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 part1 = context.getString(R.string.unsupported_engine_explanation_1).replace("$ENGINE", projectType);
String part2 = context.getString(R.string.unsupported_engine_explanation_2);
String part3 = context.getString(R.string.unsupported_engine_explanation_3);

builder
.setTitle(R.string.information)
.setMessage(part1 + '\n' + '\n' + part2 + '\n' + '\n' + part3)
.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,30 @@
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)");

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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@
android:textColor="#222"
android:textSize="20sp"
android:textStyle="bold"/>

<TextView
android:id="@+id/subtitle"

android:layout_width="wrap_content"
android:layout_height="wrap_content"

android:layout_below="@id/title"
android:layout_toStartOf="@+id/game_browser_thumbnail_option_button"
android:layout_toEndOf="@+id/screen"
android:padding="4dp"

android:textColor="#666"
android:textSize="14sp"
android:textStyle="normal" />

<ImageButton
android:id="@+id/game_browser_thumbnail_option_button"

Expand Down
5 changes: 5 additions & 0 deletions builds/android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,9 @@ Please tell us in detail what went wrong.\n\n
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="menu">Open Android menu</string>
<string name="unsupported_engine">Unsupported engine:</string>
<string name="unsupported_engine_explanation_1">This game cannot be played in EasyRPG because it was created with $ENGINE.</string>
<string name="unsupported_engine_explanation_2">EasyRPG is designed to support only RPG Maker 2000 and 2003 games, with no plans to expand to other engines.</string>
<string name="unsupported_engine_explanation_3">For software capable of launching this game, please check the Play Store.</string>
<string name="information">Information</string>
</resources>
15 changes: 15 additions & 0 deletions src/directory_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ std::unique_ptr<DirectoryTree> DirectoryTree::Create(Filesystem& fs) {
return tree;
}

bool DirectoryTree::WildcardMatch(const StringView& pattern, const StringView& text) {
if (pattern.length() != text.length()) {
return false;
}

std::string pattern_norm = make_key(pattern);
std::string text_norm = make_key(text);

return std::equal(pattern_norm.begin(), pattern_norm.end(),
text_norm.begin(),
[](char p, char t) {
return p == '?' || p == t;
});
}

DirectoryTree::DirectoryListType* DirectoryTree::ListDirectory(StringView path) const {
std::vector<Entry> entries;
std::string fs_path = ToString(path);
Expand Down
Loading
Loading