diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 425a749d75..6574d22199 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -341,12 +341,12 @@ public final class NativeLibrary /** * Begins emulation. */ - public static native void Run(String path, boolean firstOpen); + public static native void Run(String[] path, boolean firstOpen); /** * Begins emulation from the specified savestate. */ - public static native void Run(String path, String savestatePath, boolean deleteSavestate); + public static native void Run(String[] path, String savestatePath, boolean deleteSavestate); public static native void ChangeDisc(String path); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index 9aa8955416..deadc4a9e9 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -37,6 +37,7 @@ import org.dolphinemu.dolphinemu.fragments.EmulationFragment; import org.dolphinemu.dolphinemu.fragments.MenuFragment; import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment; import org.dolphinemu.dolphinemu.model.GameFile; +import org.dolphinemu.dolphinemu.services.GameFileCacheService; import org.dolphinemu.dolphinemu.ui.main.MainActivity; import org.dolphinemu.dolphinemu.ui.main.MainPresenter; import org.dolphinemu.dolphinemu.ui.platform.Platform; @@ -74,10 +75,10 @@ public final class EmulationActivity extends AppCompatActivity private boolean activityRecreated; private String mSelectedTitle; private int mPlatform; - private String mPath; + private String[] mPaths; private boolean backPressedOnce = false; - public static final String EXTRA_SELECTED_GAME = "SelectedGame"; + public static final String EXTRA_SELECTED_GAMES = "SelectedGames"; public static final String EXTRA_SELECTED_TITLE = "SelectedTitle"; public static final String EXTRA_PLATFORM = "Platform"; @@ -166,11 +167,20 @@ public final class EmulationActivity extends AppCompatActivity .append(R.id.menu_emulation_reset_overlay, EmulationActivity.MENU_ACTION_RESET_OVERLAY); } + private static String[] scanForSecondDisc(GameFile gameFile) + { + GameFile secondFile = GameFileCacheService.findSecondDisc(gameFile); + if (secondFile == null) + return new String[]{gameFile.getPath()}; + else + return new String[]{gameFile.getPath(), secondFile.getPath()}; + } + public static void launch(FragmentActivity activity, GameFile gameFile) { Intent launcher = new Intent(activity, EmulationActivity.class); - launcher.putExtra(EXTRA_SELECTED_GAME, gameFile.getPath()); + launcher.putExtra(EXTRA_SELECTED_GAMES, scanForSecondDisc(gameFile)); launcher.putExtra(EXTRA_SELECTED_TITLE, gameFile.getTitle()); launcher.putExtra(EXTRA_PLATFORM, gameFile.getPlatform()); Bundle options = new Bundle(); @@ -193,7 +203,7 @@ public final class EmulationActivity extends AppCompatActivity { // Get params we were passed Intent gameToEmulate = getIntent(); - mPath = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME); + mPaths = gameToEmulate.getStringArrayExtra(EXTRA_SELECTED_GAMES); mSelectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE); mPlatform = gameToEmulate.getIntExtra(EXTRA_PLATFORM, 0); activityRecreated = false; @@ -201,7 +211,7 @@ public final class EmulationActivity extends AppCompatActivity else { // Could have recreated the activity(rotate) before creating the fragment. If the fragment - // doesn't exist, treat this as a new start. + // doesn't exist, treat this as a new start. activityRecreated = mEmulationFragment != null; restoreState(savedInstanceState); } @@ -264,7 +274,7 @@ public final class EmulationActivity extends AppCompatActivity getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) && mEmulationFragment == null) { - mEmulationFragment = EmulationFragment.newInstance(mPath); + mEmulationFragment = EmulationFragment.newInstance(mPaths); getSupportFragmentManager().beginTransaction() .add(R.id.frame_emulation_fragment, mEmulationFragment) .commit(); @@ -286,7 +296,7 @@ public final class EmulationActivity extends AppCompatActivity { mEmulationFragment.saveTemporaryState(); } - outState.putString(EXTRA_SELECTED_GAME, mPath); + outState.putStringArray(EXTRA_SELECTED_GAMES, mPaths); outState.putString(EXTRA_SELECTED_TITLE, mSelectedTitle); outState.putInt(EXTRA_PLATFORM, mPlatform); super.onSaveInstanceState(outState); @@ -294,7 +304,7 @@ public final class EmulationActivity extends AppCompatActivity protected void restoreState(Bundle savedInstanceState) { - mPath = savedInstanceState.getString(EXTRA_SELECTED_GAME); + mPaths = savedInstanceState.getStringArray(EXTRA_SELECTED_GAMES); mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE); mPlatform = savedInstanceState.getInt(EXTRA_PLATFORM); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java index 19ef69c5be..462c3bb00c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.java @@ -213,6 +213,7 @@ public final class SettingsFragmentPresenter Setting overclock = null; Setting speedLimit = null; Setting audioStretch = null; + Setting autoDiscChange = null; Setting analytics = null; Setting enableSaveState; Setting lockToLandscape; @@ -225,6 +226,7 @@ public final class SettingsFragmentPresenter overclock = coreSection.getSetting(SettingsFile.KEY_OVERCLOCK_PERCENT); speedLimit = coreSection.getSetting(SettingsFile.KEY_SPEED_LIMIT); audioStretch = coreSection.getSetting(SettingsFile.KEY_AUDIO_STRETCH); + autoDiscChange = coreSection.getSetting(SettingsFile.KEY_AUTO_DISC_CHANGE); analytics = analyticsSection.getSetting(SettingsFile.KEY_ANALYTICS_ENABLED); enableSaveState = coreSection.getSetting(SettingsFile.KEY_ENABLE_SAVE_STATES); lockToLandscape = coreSection.getSetting(SettingsFile.KEY_LOCK_LANDSCAPE); @@ -264,6 +266,8 @@ public final class SettingsFragmentPresenter R.string.speed_limit, 0, 200, "%", 100, speedLimit)); sl.add(new CheckBoxSetting(SettingsFile.KEY_AUDIO_STRETCH, Settings.SECTION_INI_CORE, R.string.audio_stretch, R.string.audio_stretch_description, false, audioStretch)); + sl.add(new CheckBoxSetting(SettingsFile.KEY_AUTO_DISC_CHANGE, Settings.SECTION_INI_CORE, + R.string.auto_disc_change, 0, false, autoDiscChange)); sl.add(new CheckBoxSetting(SettingsFile.KEY_ENABLE_SAVE_STATES, Settings.SECTION_INI_CORE, R.string.enable_save_states, R.string.enable_save_states_description, false, enableSaveState)); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java index 322e2f0ae8..deb09cebe6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.java @@ -45,6 +45,7 @@ public final class SettingsFile public static final String KEY_SPEED_LIMIT = "EmulationSpeed"; public static final String KEY_VIDEO_BACKEND = "GFXBackend"; public static final String KEY_AUDIO_STRETCH = "AudioStretch"; + public static final String KEY_AUTO_DISC_CHANGE = "AutoDiscChange"; public static final String KEY_GAME_CUBE_LANGUAGE = "SelectedLanguage"; public static final String KEY_OVERRIDE_GAME_CUBE_LANGUAGE = "OverrideGCLang"; public static final String KEY_SLOT_A_DEVICE = "SlotA"; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java index 38187f881d..dc888d74d4 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -30,7 +30,7 @@ import java.io.File; public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback { - private static final String KEY_GAMEPATH = "gamepath"; + private static final String KEY_GAMEPATHS = "gamepaths"; private SharedPreferences mPreferences; @@ -42,11 +42,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C private EmulationActivity activity; - public static EmulationFragment newInstance(String gamePath) + public static EmulationFragment newInstance(String[] gamePaths) { - Bundle args = new Bundle(); - args.putString(KEY_GAMEPATH, gamePath); + args.putStringArray(KEY_GAMEPATHS, gamePaths); EmulationFragment fragment = new EmulationFragment(); fragment.setArguments(args); @@ -82,13 +81,13 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); - String gamePath = getArguments().getString(KEY_GAMEPATH); + String[] gamePaths = getArguments().getStringArray(KEY_GAMEPATHS); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); boolean firstOpen = preferences.getBoolean(StartupHandler.NEW_SESSION, true); SharedPreferences.Editor sPrefsEditor = preferences.edit(); sPrefsEditor.putBoolean(StartupHandler.NEW_SESSION, false); sPrefsEditor.apply(); - mEmulationState = new EmulationState(gamePath, getTemporaryStateFilePath(), firstOpen); + mEmulationState = new EmulationState(gamePaths, getTemporaryStateFilePath(), firstOpen); } /** @@ -273,7 +272,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C STOPPED, RUNNING, PAUSED } - private final String mGamePath; + private final String[] mGamePaths; private Thread mEmulationThread; private State state; private Surface mSurface; @@ -282,10 +281,10 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C private boolean firstOpen; private final String temporaryStatePath; - EmulationState(String gamePath, String temporaryStatePath, boolean firstOpen) + EmulationState(String[] gamePaths, String temporaryStatePath, boolean firstOpen) { this.firstOpen = firstOpen; - mGamePath = gamePath; + mGamePaths = gamePaths; this.temporaryStatePath = temporaryStatePath; // Starting state is stopped. state = State.STOPPED; @@ -423,12 +422,12 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C if (loadPreviousTemporaryState) { Log.debug("[EmulationFragment] Starting emulation thread from previous state."); - NativeLibrary.Run(mGamePath, temporaryStatePath, true); + NativeLibrary.Run(mGamePaths, temporaryStatePath, true); } else { Log.debug("[EmulationFragment] Starting emulation thread."); - NativeLibrary.Run(mGamePath, firstOpen); + NativeLibrary.Run(mGamePaths, firstOpen); } }, "NativeEmulation"); mEmulationThread.start(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java index 5565753632..45d0eb9060 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java @@ -30,6 +30,10 @@ public class GameFile public native String getGameId(); + public native int getDiscNumber(); + + public native int getRevision(); + public native int[] getBanner(); public native int getBannerWidth(); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java index fa800a96c1..17aef0cd35 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java @@ -61,6 +61,26 @@ public final class GameFileCacheService extends IntentService return null; } + public static GameFile findSecondDisc(GameFile game) + { + GameFile matchWithoutRevision = null; + + GameFile[] allGames = gameFiles.get(); + for (GameFile otherGame : allGames) + { + if (game.getGameId().equals(otherGame.getGameId()) && + game.getDiscNumber() != otherGame.getDiscNumber()) + { + if (game.getRevision() == otherGame.getRevision()) + return otherGame; + else + matchWithoutRevision = otherGame; + } + } + + return matchWithoutRevision; + } + private static void startService(Context context, String action) { Intent intent = new Intent(context, GameFileCacheService.class); diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index f387ad55f5..4949cc4137 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -136,6 +136,7 @@ Enable sound output through the speaker on a real Wiimote (DolphinBar required). Audio Stretching Stretches audio to reduce stuttering. Increases latency. + Change Discs Automatically Enable Savestates WARNING: Savestates may not be compatible with future versions of Dolphin and can make it impossible to create normal saves in some cases. Never use savestates as the only way of saving your progress. Lock screen to landscape diff --git a/Source/Android/jni/AndroidCommon/AndroidCommon.cpp b/Source/Android/jni/AndroidCommon/AndroidCommon.cpp index 350ea5483e..04e34c0f21 100644 --- a/Source/Android/jni/AndroidCommon/AndroidCommon.cpp +++ b/Source/Android/jni/AndroidCommon/AndroidCommon.cpp @@ -5,6 +5,7 @@ #include "jni/AndroidCommon/AndroidCommon.h" #include +#include #include @@ -24,3 +25,15 @@ jstring ToJString(JNIEnv* env, const std::string& str) { return env->NewStringUTF(str.c_str()); } + +std::vector JStringArrayToVector(JNIEnv* env, jobjectArray array) +{ + const jsize size = env->GetArrayLength(array); + std::vector result; + result.reserve(size); + + for (jsize i = 0; i < size; ++i) + result.push_back(GetJString(env, (jstring)env->GetObjectArrayElement(array, i))); + + return result; +} diff --git a/Source/Android/jni/AndroidCommon/AndroidCommon.h b/Source/Android/jni/AndroidCommon/AndroidCommon.h index 25ea59ed30..7b7d1bfc6b 100644 --- a/Source/Android/jni/AndroidCommon/AndroidCommon.h +++ b/Source/Android/jni/AndroidCommon/AndroidCommon.h @@ -10,3 +10,4 @@ std::string GetJString(JNIEnv* env, jstring jstr); jstring ToJString(JNIEnv* env, const std::string& str); +std::vector JStringArrayToVector(JNIEnv* env, jobjectArray array); diff --git a/Source/Android/jni/GameList/GameFile.cpp b/Source/Android/jni/GameList/GameFile.cpp index c11afa8c60..b84426a1ca 100644 --- a/Source/Android/jni/GameList/GameFile.cpp +++ b/Source/Android/jni/GameList/GameFile.cpp @@ -58,6 +58,10 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPath( jobject obj); JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getGameId(JNIEnv* env, jobject obj); +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getDiscNumber(JNIEnv* env, + jobject obj); +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRevision(JNIEnv* env, + jobject obj); JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env, jobject obj); JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerWidth(JNIEnv* env, @@ -119,6 +123,18 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getGameI return ToJString(env, GetRef(env, obj)->GetGameID()); } +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getDiscNumber(JNIEnv* env, + jobject obj) +{ + return env, GetRef(env, obj)->GetDiscNumber(); +} + +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRevision(JNIEnv* env, + jobject obj) +{ + return env, GetRef(env, obj)->GetRevision(); +} + JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env, jobject obj) { diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index f4692af117..c606345abe 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -571,10 +571,11 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimo WiimoteReal::Refresh(); } -static void Run(const std::string& path, bool first_open, +static void Run(const std::vector& paths, bool first_open, std::optional savestate_path = {}, bool delete_savestate = false) { - __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", path.c_str()); + ASSERT(!paths.empty()); + __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", paths[0].c_str()); // Install our callbacks OSD::AddCallback(OSD::CallbackType::Shutdown, ButtonManager::Shutdown); @@ -595,7 +596,7 @@ static void Run(const std::string& path, bool first_open, // No use running the loop when booting fails s_have_wm_user_stop = false; - std::unique_ptr boot = BootParameters::GenerateFromFile(path, savestate_path); + std::unique_ptr boot = BootParameters::GenerateFromFile(paths, savestate_path); boot->delete_savestate = delete_savestate; WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf); if (BootManager::BootCore(std::move(boot), wsi)) @@ -630,17 +631,17 @@ static void Run(const std::string& path, bool first_open, } } -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Z( - JNIEnv* env, jobject obj, jstring jFile, jboolean jfirstOpen) +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2Z( + JNIEnv* env, jobject obj, jobjectArray jPaths, jboolean jfirstOpen) { - Run(GetJString(env, jFile), jfirstOpen); + Run(JStringArrayToVector(env, jPaths), jfirstOpen); } JNIEXPORT void JNICALL -Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z( - JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate, jboolean jDeleteSavestate) +Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2Ljava_lang_String_2Z( + JNIEnv* env, jobject obj, jobjectArray jPaths, jstring jSavestate, jboolean jDeleteSavestate) { - Run(GetJString(env, jFile), false, GetJString(env, jSavestate), jDeleteSavestate); + Run(JStringArrayToVector(env, jPaths), false, GetJString(env, jSavestate), jDeleteSavestate); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ChangeDisc(JNIEnv* env,