Merge pull request #7629 from JosJuice/auto-disc-change
Automatic disc change for 2-disc games
This commit is contained in:
commit
1d3e3de44b
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -218,6 +218,7 @@ public final class SettingsFragmentPresenter
|
|||
Setting overclock = null;
|
||||
Setting speedLimit = null;
|
||||
Setting audioStretch = null;
|
||||
Setting autoDiscChange = null;
|
||||
Setting analytics = null;
|
||||
Setting enableSaveState;
|
||||
Setting lockToLandscape;
|
||||
|
@ -230,6 +231,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);
|
||||
|
@ -269,6 +271,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));
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
<string name="wiimote_speaker_description">Enable sound output through the speaker on a real Wiimote (DolphinBar required).</string>
|
||||
<string name="audio_stretch">Audio Stretching</string>
|
||||
<string name="audio_stretch_description">Stretches audio to reduce stuttering. Increases latency.</string>
|
||||
<string name="auto_disc_change">Change Discs Automatically</string>
|
||||
<string name="enable_save_states">Enable Savestates</string>
|
||||
<string name="enable_save_states_description">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.</string>
|
||||
<string name="lock_emulation_landscape">Lock screen to landscape</string>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
|
@ -24,3 +25,15 @@ jstring ToJString(JNIEnv* env, const std::string& str)
|
|||
{
|
||||
return env->NewStringUTF(str.c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> JStringArrayToVector(JNIEnv* env, jobjectArray array)
|
||||
{
|
||||
const jsize size = env->GetArrayLength(array);
|
||||
std::vector<std::string> result;
|
||||
result.reserve(size);
|
||||
|
||||
for (jsize i = 0; i < size; ++i)
|
||||
result.push_back(GetJString(env, (jstring)env->GetObjectArrayElement(array, i)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -10,3 +10,4 @@
|
|||
|
||||
std::string GetJString(JNIEnv* env, jstring jstr);
|
||||
jstring ToJString(JNIEnv* env, const std::string& str);
|
||||
std::vector<std::string> JStringArrayToVector(JNIEnv* env, jobjectArray array);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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<std::string>& paths, bool first_open,
|
||||
std::optional<std::string> 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<BootParameters> boot = BootParameters::GenerateFromFile(path, savestate_path);
|
||||
std::unique_ptr<BootParameters> 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,
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
|
||||
#include "Core/Boot/Boot.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <experimental/filesystem>
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#define HAS_STD_FILESYSTEM
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
@ -54,6 +60,54 @@
|
|||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
std::vector<std::string> ReadM3UFile(const std::string& m3u_path, const std::string& folder_path)
|
||||
{
|
||||
#ifndef HAS_STD_FILESYSTEM
|
||||
ASSERT(folder_path.back() == '/');
|
||||
#endif
|
||||
|
||||
std::vector<std::string> result;
|
||||
std::vector<std::string> nonexistent;
|
||||
|
||||
std::ifstream s;
|
||||
File::OpenFStream(s, m3u_path, std::ios_base::in);
|
||||
|
||||
std::string line;
|
||||
while (std::getline(s, line))
|
||||
{
|
||||
if (StringBeginsWith(line, u8"\uFEFF"))
|
||||
{
|
||||
WARN_LOG(BOOT, "UTF-8 BOM in file: %s", m3u_path.c_str());
|
||||
line.erase(0, 3);
|
||||
}
|
||||
|
||||
if (!line.empty() && line.front() != '#') // Comments start with #
|
||||
{
|
||||
#ifdef HAS_STD_FILESYSTEM
|
||||
const fs::path path_line = fs::u8path(line);
|
||||
const std::string path_to_add =
|
||||
path_line.is_relative() ? fs::u8path(folder_path).append(path_line).u8string() : line;
|
||||
#else
|
||||
const std::string path_to_add = line.front() != '/' ? folder_path + line : line;
|
||||
#endif
|
||||
|
||||
(File::Exists(path_to_add) ? result : nonexistent).push_back(path_to_add);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nonexistent.empty())
|
||||
{
|
||||
PanicAlertT("Files specified in the M3U file \"%s\" were not found:\n%s", m3u_path.c_str(),
|
||||
JoinStrings(nonexistent, "\n").c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (result.empty())
|
||||
PanicAlertT("No paths found in the M3U file \"%s\"", m3u_path.c_str());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BootParameters::BootParameters(Parameters&& parameters_,
|
||||
const std::optional<std::string>& savestate_path_)
|
||||
: parameters(std::move(parameters_)), savestate_path(savestate_path_)
|
||||
|
@ -61,40 +115,67 @@ BootParameters::BootParameters(Parameters&& parameters_,
|
|||
}
|
||||
|
||||
std::unique_ptr<BootParameters>
|
||||
BootParameters::GenerateFromFile(const std::string& path,
|
||||
BootParameters::GenerateFromFile(std::string boot_path,
|
||||
const std::optional<std::string>& savestate_path)
|
||||
{
|
||||
const bool is_drive = Common::IsCDROMDevice(path);
|
||||
return GenerateFromFile(std::vector<std::string>{std::move(boot_path)}, savestate_path);
|
||||
}
|
||||
|
||||
std::unique_ptr<BootParameters>
|
||||
BootParameters::GenerateFromFile(std::vector<std::string> paths,
|
||||
const std::optional<std::string>& savestate_path)
|
||||
{
|
||||
ASSERT(!paths.empty());
|
||||
|
||||
const bool is_drive = Common::IsCDROMDevice(paths.front());
|
||||
// Check if the file exist, we may have gotten it from a --elf command line
|
||||
// that gave an incorrect file name
|
||||
if (!is_drive && !File::Exists(path))
|
||||
if (!is_drive && !File::Exists(paths.front()))
|
||||
{
|
||||
PanicAlertT("The specified file \"%s\" does not exist", path.c_str());
|
||||
PanicAlertT("The specified file \"%s\" does not exist", paths.front().c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string folder_path;
|
||||
std::string extension;
|
||||
SplitPath(path, nullptr, nullptr, &extension);
|
||||
SplitPath(paths.front(), &folder_path, nullptr, &extension);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
|
||||
if (extension == ".m3u" || extension == ".m3u8")
|
||||
{
|
||||
paths = ReadM3UFile(paths.front(), folder_path);
|
||||
if (paths.empty())
|
||||
return {};
|
||||
|
||||
SplitPath(paths.front(), nullptr, nullptr, &extension);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
}
|
||||
|
||||
const std::string path = paths.front();
|
||||
if (paths.size() == 1)
|
||||
paths.clear();
|
||||
|
||||
static const std::unordered_set<std::string> disc_image_extensions = {
|
||||
{".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".dol", ".elf"}};
|
||||
if (disc_image_extensions.find(extension) != disc_image_extensions.end() || is_drive)
|
||||
{
|
||||
std::unique_ptr<DiscIO::Volume> volume = DiscIO::CreateVolumeFromFilename(path);
|
||||
if (volume)
|
||||
return std::make_unique<BootParameters>(Disc{path, std::move(volume)}, savestate_path);
|
||||
{
|
||||
return std::make_unique<BootParameters>(Disc{std::move(path), std::move(volume), paths},
|
||||
savestate_path);
|
||||
}
|
||||
|
||||
if (extension == ".elf")
|
||||
{
|
||||
return std::make_unique<BootParameters>(Executable{path, std::make_unique<ElfReader>(path)},
|
||||
savestate_path);
|
||||
return std::make_unique<BootParameters>(
|
||||
Executable{std::move(path), std::make_unique<ElfReader>(path)}, savestate_path);
|
||||
}
|
||||
|
||||
if (extension == ".dol")
|
||||
{
|
||||
return std::make_unique<BootParameters>(Executable{path, std::make_unique<DolReader>(path)},
|
||||
savestate_path);
|
||||
return std::make_unique<BootParameters>(
|
||||
Executable{std::move(path), std::make_unique<DolReader>(path)}, savestate_path);
|
||||
}
|
||||
|
||||
if (is_drive)
|
||||
|
@ -113,10 +194,10 @@ BootParameters::GenerateFromFile(const std::string& path,
|
|||
}
|
||||
|
||||
if (extension == ".dff")
|
||||
return std::make_unique<BootParameters>(DFF{path}, savestate_path);
|
||||
return std::make_unique<BootParameters>(DFF{std::move(path)}, savestate_path);
|
||||
|
||||
if (extension == ".wad")
|
||||
return std::make_unique<BootParameters>(DiscIO::WiiWAD{path}, savestate_path);
|
||||
return std::make_unique<BootParameters>(DiscIO::WiiWAD{std::move(path)}, savestate_path);
|
||||
|
||||
PanicAlertT("Could not recognize file %s", path.c_str());
|
||||
return {};
|
||||
|
@ -136,10 +217,11 @@ BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_)
|
|||
// Inserts a disc into the emulated disc drive and returns a pointer to it.
|
||||
// The returned pointer must only be used while we are still booting,
|
||||
// because DVDThread can do whatever it wants to the disc after that.
|
||||
static const DiscIO::Volume* SetDisc(std::unique_ptr<DiscIO::Volume> volume)
|
||||
static const DiscIO::Volume* SetDisc(std::unique_ptr<DiscIO::Volume> volume,
|
||||
std::vector<std::string> auto_disc_change_paths = {})
|
||||
{
|
||||
const DiscIO::Volume* pointer = volume.get();
|
||||
DVDInterface::SetDisc(std::move(volume));
|
||||
DVDInterface::SetDisc(std::move(volume), auto_disc_change_paths);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
|
@ -326,7 +408,7 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
|||
bool operator()(BootParameters::Disc& disc) const
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Booting from disc: %s", disc.path.c_str());
|
||||
const DiscIO::Volume* volume = SetDisc(std::move(disc.volume));
|
||||
const DiscIO::Volume* volume = SetDisc(std::move(disc.volume), disc.auto_disc_change_paths);
|
||||
|
||||
if (!volume)
|
||||
return false;
|
||||
|
@ -420,7 +502,7 @@ bool CBoot::BootUp(std::unique_ptr<BootParameters> boot)
|
|||
if (ipl.disc)
|
||||
{
|
||||
NOTICE_LOG(BOOT, "Inserting disc: %s", ipl.disc->path.c_str());
|
||||
SetDisc(DiscIO::CreateVolumeFromFilename(ipl.disc->path));
|
||||
SetDisc(DiscIO::CreateVolumeFromFilename(ipl.disc->path), ipl.disc->auto_disc_change_paths);
|
||||
}
|
||||
|
||||
if (LoadMapFromFilename())
|
||||
|
|
|
@ -40,6 +40,7 @@ struct BootParameters
|
|||
{
|
||||
std::string path;
|
||||
std::unique_ptr<DiscIO::Volume> volume;
|
||||
std::vector<std::string> auto_disc_change_paths;
|
||||
};
|
||||
|
||||
struct Executable
|
||||
|
@ -69,7 +70,9 @@ struct BootParameters
|
|||
};
|
||||
|
||||
static std::unique_ptr<BootParameters>
|
||||
GenerateFromFile(const std::string& boot_path,
|
||||
GenerateFromFile(std::string boot_path, const std::optional<std::string>& savestate_path = {});
|
||||
static std::unique_ptr<BootParameters>
|
||||
GenerateFromFile(std::vector<std::string> paths,
|
||||
const std::optional<std::string>& savestate_path = {});
|
||||
|
||||
using Parameters = std::variant<Disc, Executable, DiscIO::WiiWAD, NANDTitle, IPL, DFF>;
|
||||
|
|
|
@ -104,6 +104,7 @@ const ConfigInfo<u32> MAIN_CUSTOM_RTC_VALUE{{System::Main, "Core", "CustomRTCVal
|
|||
const ConfigInfo<bool> MAIN_ENABLE_SIGNATURE_CHECKS{{System::Main, "Core", "EnableSignatureChecks"},
|
||||
true};
|
||||
const ConfigInfo<bool> MAIN_REDUCE_POLLING_RATE{{System::Main, "Core", "ReducePollingRate"}, false};
|
||||
const ConfigInfo<bool> MAIN_AUTO_DISC_CHANGE{{System::Main, "Core", "AutoDiscChange"}, false};
|
||||
|
||||
// Main.DSP
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ extern const ConfigInfo<bool> MAIN_CUSTOM_RTC_ENABLE;
|
|||
extern const ConfigInfo<u32> MAIN_CUSTOM_RTC_VALUE;
|
||||
extern const ConfigInfo<bool> MAIN_ENABLE_SIGNATURE_CHECKS;
|
||||
extern const ConfigInfo<bool> MAIN_REDUCE_POLLING_RATE;
|
||||
extern const ConfigInfo<bool> MAIN_AUTO_DISC_CHANGE;
|
||||
|
||||
// Main.DSP
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ bool IsSettingSaveable(const Config::ConfigLocation& config_location)
|
|||
Config::MAIN_DEFAULT_ISO.location,
|
||||
Config::MAIN_MEMCARD_A_PATH.location,
|
||||
Config::MAIN_MEMCARD_B_PATH.location,
|
||||
Config::MAIN_AUTO_DISC_CHANGE.location,
|
||||
|
||||
// Graphics.Hardware
|
||||
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
#include "Common/Align.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/AudioInterface.h"
|
||||
|
@ -36,6 +38,8 @@
|
|||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/VolumeWii.h"
|
||||
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
|
||||
// The minimum time it takes for the DVD drive to process a command (in
|
||||
// microseconds)
|
||||
constexpr u64 COMMAND_LATENCY_US = 300;
|
||||
|
@ -231,12 +235,16 @@ static u64 s_read_buffer_end_offset;
|
|||
|
||||
// Disc changing
|
||||
static std::string s_disc_path_to_insert;
|
||||
static std::vector<std::string> s_auto_disc_change_paths;
|
||||
static size_t s_auto_disc_change_index;
|
||||
|
||||
// Events
|
||||
static CoreTiming::EventType* s_finish_executing_command;
|
||||
static CoreTiming::EventType* s_auto_change_disc;
|
||||
static CoreTiming::EventType* s_eject_disc;
|
||||
static CoreTiming::EventType* s_insert_disc;
|
||||
|
||||
static void AutoChangeDiscCallback(u64 userdata, s64 cyclesLate);
|
||||
static void EjectDiscCallback(u64 userdata, s64 cyclesLate);
|
||||
static void InsertDiscCallback(u64 userdata, s64 cyclesLate);
|
||||
static void FinishExecutingCommandCallback(u64 userdata, s64 cycles_late);
|
||||
|
@ -392,6 +400,7 @@ void Init()
|
|||
Reset();
|
||||
s_DICVR.Hex = 1; // Disc Channel relies on cover being open when no disc is inserted
|
||||
|
||||
s_auto_change_disc = CoreTiming::RegisterEvent("AutoChangeDisc", AutoChangeDiscCallback);
|
||||
s_eject_disc = CoreTiming::RegisterEvent("EjectDisc", EjectDiscCallback);
|
||||
s_insert_disc = CoreTiming::RegisterEvent("InsertDisc", InsertDiscCallback);
|
||||
|
||||
|
@ -441,11 +450,21 @@ void Shutdown()
|
|||
DVDThread::Stop();
|
||||
}
|
||||
|
||||
void SetDisc(std::unique_ptr<DiscIO::Volume> disc)
|
||||
void SetDisc(std::unique_ptr<DiscIO::Volume> disc,
|
||||
std::optional<std::vector<std::string>> auto_disc_change_paths = {})
|
||||
{
|
||||
if (disc)
|
||||
s_current_partition = disc->GetGamePartition();
|
||||
|
||||
if (auto_disc_change_paths)
|
||||
{
|
||||
ASSERT_MSG(DISCIO, (*auto_disc_change_paths).size() != 1,
|
||||
"Cannot automatically change between one disc");
|
||||
|
||||
s_auto_disc_change_paths = *auto_disc_change_paths;
|
||||
s_auto_disc_change_index = 0;
|
||||
}
|
||||
|
||||
DVDThread::SetDisc(std::move(disc));
|
||||
SetLidOpen();
|
||||
}
|
||||
|
@ -455,9 +474,14 @@ bool IsDiscInside()
|
|||
return DVDThread::HasDisc();
|
||||
}
|
||||
|
||||
static void AutoChangeDiscCallback(u64 userdata, s64 cyclesLate)
|
||||
{
|
||||
AutoChangeDisc();
|
||||
}
|
||||
|
||||
static void EjectDiscCallback(u64 userdata, s64 cyclesLate)
|
||||
{
|
||||
SetDisc(nullptr);
|
||||
SetDisc(nullptr, {});
|
||||
}
|
||||
|
||||
static void InsertDiscCallback(u64 userdata, s64 cyclesLate)
|
||||
|
@ -466,7 +490,7 @@ static void InsertDiscCallback(u64 userdata, s64 cyclesLate)
|
|||
DiscIO::CreateVolumeFromFilename(s_disc_path_to_insert);
|
||||
|
||||
if (new_volume)
|
||||
SetDisc(std::move(new_volume));
|
||||
SetDisc(std::move(new_volume), {});
|
||||
else
|
||||
PanicAlertT("The disc that was about to be inserted couldn't be found.");
|
||||
|
||||
|
@ -479,6 +503,20 @@ void EjectDisc()
|
|||
CoreTiming::ScheduleEvent(0, s_eject_disc);
|
||||
}
|
||||
|
||||
// Must only be called on the CPU thread
|
||||
void ChangeDisc(const std::vector<std::string>& paths)
|
||||
{
|
||||
ASSERT_MSG(DISCIO, !paths.empty(), "Trying to insert an empty list of discs");
|
||||
|
||||
if (paths.size() > 1)
|
||||
{
|
||||
s_auto_disc_change_paths = paths;
|
||||
s_auto_disc_change_index = 0;
|
||||
}
|
||||
|
||||
ChangeDisc(paths[0]);
|
||||
}
|
||||
|
||||
// Must only be called on the CPU thread
|
||||
void ChangeDisc(const std::string& new_path)
|
||||
{
|
||||
|
@ -493,6 +531,28 @@ void ChangeDisc(const std::string& new_path)
|
|||
s_disc_path_to_insert = new_path;
|
||||
CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), s_insert_disc);
|
||||
Movie::SignalDiscChange(new_path);
|
||||
|
||||
for (size_t i = 0; i < s_auto_disc_change_paths.size(); ++i)
|
||||
{
|
||||
if (s_auto_disc_change_paths[i] == new_path)
|
||||
{
|
||||
s_auto_disc_change_index = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
s_auto_disc_change_paths.clear();
|
||||
}
|
||||
|
||||
// Must only be called on the CPU thread
|
||||
bool AutoChangeDisc()
|
||||
{
|
||||
if (s_auto_disc_change_paths.empty())
|
||||
return false;
|
||||
|
||||
s_auto_disc_change_index = (s_auto_disc_change_index + 1) % s_auto_disc_change_paths.size();
|
||||
ChangeDisc(s_auto_disc_change_paths[s_auto_disc_change_index]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetLidOpen()
|
||||
|
@ -983,12 +1043,25 @@ void ExecuteCommand(u32 command_0, u32 command_1, u32 command_2, u32 output_addr
|
|||
break;
|
||||
|
||||
case DVDLowStopMotor:
|
||||
{
|
||||
INFO_LOG(DVDINTERFACE, "DVDLowStopMotor %s %s", command_1 ? "eject" : "",
|
||||
command_2 ? "kill!" : "");
|
||||
|
||||
if (command_1 && !command_2)
|
||||
const bool force_eject = command_1 && !command_2;
|
||||
|
||||
if (Config::Get(Config::MAIN_AUTO_DISC_CHANGE) && !Movie::IsPlayingInput() &&
|
||||
DVDThread::IsInsertedDiscRunning() && !s_auto_disc_change_paths.empty())
|
||||
{
|
||||
CoreTiming::ScheduleEvent(force_eject ? 0 : SystemTimers::GetTicksPerSecond() / 2,
|
||||
s_auto_change_disc);
|
||||
OSD::AddMessage("Changing discs automatically...", OSD::Duration::NORMAL);
|
||||
}
|
||||
else if (force_eject)
|
||||
{
|
||||
EjectDiscCallback(0, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// DVD Audio Enable/Disable (Immediate). GC uses this, and apparently Wii also does...?
|
||||
case DVDLowAudioBufferConfig:
|
||||
|
|
|
@ -111,10 +111,13 @@ void DoState(PointerWrap& p);
|
|||
|
||||
void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
|
||||
|
||||
void SetDisc(std::unique_ptr<DiscIO::Volume> disc);
|
||||
void SetDisc(std::unique_ptr<DiscIO::Volume> disc,
|
||||
std::optional<std::vector<std::string>> auto_disc_change_paths);
|
||||
bool IsDiscInside();
|
||||
void EjectDisc(); // Must only be called on the CPU thread
|
||||
void ChangeDisc(const std::string& new_path); // Must only be called on the CPU thread
|
||||
void EjectDisc(); // Must only be called on the CPU thread
|
||||
void ChangeDisc(const std::vector<std::string>& paths); // Must only be called on the CPU thread
|
||||
void ChangeDisc(const std::string& new_path); // Must only be called on the CPU thread
|
||||
bool AutoChangeDisc(); // Must only be called on the CPU thread
|
||||
|
||||
// This function returns true and calls SConfig::SetRunningGameMetadata(Volume&, Partition&)
|
||||
// if both of the following conditions are true:
|
||||
|
|
|
@ -216,6 +216,16 @@ IOS::ES::TicketReader GetTicket(const DiscIO::Partition& partition)
|
|||
return s_disc->GetTicket(partition);
|
||||
}
|
||||
|
||||
bool IsInsertedDiscRunning()
|
||||
{
|
||||
if (!s_disc)
|
||||
return false;
|
||||
|
||||
WaitUntilIdle();
|
||||
|
||||
return SConfig::GetInstance().GetGameID() == s_disc->GetGameID();
|
||||
}
|
||||
|
||||
bool UpdateRunningGameMetadata(const DiscIO::Partition& partition, std::optional<u64> title_id)
|
||||
{
|
||||
if (!s_disc)
|
||||
|
|
|
@ -47,6 +47,7 @@ DiscIO::Platform GetDiscType();
|
|||
u64 PartitionOffsetToRawOffset(u64 offset, const DiscIO::Partition& partition);
|
||||
IOS::ES::TMDReader GetTMD(const DiscIO::Partition& partition);
|
||||
IOS::ES::TicketReader GetTicket(const DiscIO::Partition& partition);
|
||||
bool IsInsertedDiscRunning();
|
||||
// This function returns true and calls SConfig::SetRunningGameMetadata(Volume&, Partition&)
|
||||
// if both of the following conditions are true:
|
||||
// - A disc is inserted
|
||||
|
|
|
@ -1176,29 +1176,13 @@ void PlayController(GCPadStatus* PadStatus, int controllerID)
|
|||
PadStatus->button |= PAD_TRIGGER_R;
|
||||
if (s_padState.disc)
|
||||
{
|
||||
// This implementation assumes the disc change will only happen once. Trying
|
||||
// to change more than that will cause it to load the last disc every time.
|
||||
// As far as I know, there are no 3+ disc games, so this should be fine.
|
||||
bool found = false;
|
||||
std::string path;
|
||||
for (const std::string& iso_folder : SConfig::GetInstance().m_ISOFolder)
|
||||
{
|
||||
path = iso_folder + '/' + s_discChange;
|
||||
if (File::Exists(path))
|
||||
Core::RunAsCPUThread([] {
|
||||
if (!DVDInterface::AutoChangeDisc())
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
CPU::Break();
|
||||
PanicAlertT("Change the disc to %s", s_discChange.c_str());
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
Core::RunAsCPUThread([&path] { DVDInterface::ChangeDisc(path); });
|
||||
}
|
||||
else
|
||||
{
|
||||
CPU::Break();
|
||||
PanicAlertT("Change the disc to %s", s_discChange.c_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (s_padState.reset)
|
||||
|
|
|
@ -368,7 +368,10 @@ int main(int argc, char* argv[])
|
|||
std::unique_ptr<BootParameters> boot;
|
||||
if (options.is_set("exec"))
|
||||
{
|
||||
boot = BootParameters::GenerateFromFile(static_cast<const char*>(options.get("exec")));
|
||||
const std::list<std::string> paths_list = options.all("exec");
|
||||
const std::vector<std::string> paths{std::make_move_iterator(std::begin(paths_list)),
|
||||
std::make_move_iterator(std::end(paths_list))};
|
||||
boot = BootParameters::GenerateFromFile(paths);
|
||||
}
|
||||
else if (options.is_set("nand_title"))
|
||||
{
|
||||
|
|
|
@ -737,6 +737,17 @@ bool GameList::HasMultipleSelected() const
|
|||
m_grid->selectionModel()->selectedIndexes().size() > 1;
|
||||
}
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile> GameList::FindGame(const std::string& path) const
|
||||
{
|
||||
return m_model->FindGame(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile>
|
||||
GameList::FindSecondDisc(const UICommon::GameFile& game) const
|
||||
{
|
||||
return m_model->FindSecondDisc(game);
|
||||
}
|
||||
|
||||
void GameList::SetViewColumn(int col, bool view)
|
||||
{
|
||||
m_list->setColumnHidden(col, !view);
|
||||
|
|
|
@ -30,6 +30,8 @@ public:
|
|||
std::shared_ptr<const UICommon::GameFile> GetSelectedGame() const;
|
||||
QList<std::shared_ptr<const UICommon::GameFile>> GetSelectedGames() const;
|
||||
bool HasMultipleSelected() const;
|
||||
std::shared_ptr<const UICommon::GameFile> FindGame(const std::string& path) const;
|
||||
std::shared_ptr<const UICommon::GameFile> FindSecondDisc(const UICommon::GameFile& game) const;
|
||||
|
||||
void SetListView() { SetPreferredView(true); }
|
||||
void SetGridView() { SetPreferredView(false); }
|
||||
|
|
|
@ -278,7 +278,7 @@ void GameListModel::AddGame(const std::shared_ptr<const UICommon::GameFile>& gam
|
|||
|
||||
void GameListModel::UpdateGame(const std::shared_ptr<const UICommon::GameFile>& game)
|
||||
{
|
||||
int index = FindGame(game->GetFilePath());
|
||||
int index = FindGameIndex(game->GetFilePath());
|
||||
if (index < 0)
|
||||
{
|
||||
AddGame(game);
|
||||
|
@ -292,7 +292,7 @@ void GameListModel::UpdateGame(const std::shared_ptr<const UICommon::GameFile>&
|
|||
|
||||
void GameListModel::RemoveGame(const std::string& path)
|
||||
{
|
||||
int entry = FindGame(path);
|
||||
int entry = FindGameIndex(path);
|
||||
if (entry < 0)
|
||||
return;
|
||||
|
||||
|
@ -301,7 +301,13 @@ void GameListModel::RemoveGame(const std::string& path)
|
|||
endRemoveRows();
|
||||
}
|
||||
|
||||
int GameListModel::FindGame(const std::string& path) const
|
||||
std::shared_ptr<const UICommon::GameFile> GameListModel::FindGame(const std::string& path) const
|
||||
{
|
||||
const int index = FindGameIndex(path);
|
||||
return index < 0 ? nullptr : m_games[index];
|
||||
}
|
||||
|
||||
int GameListModel::FindGameIndex(const std::string& path) const
|
||||
{
|
||||
for (int i = 0; i < m_games.size(); i++)
|
||||
{
|
||||
|
@ -311,6 +317,29 @@ int GameListModel::FindGame(const std::string& path) const
|
|||
return -1;
|
||||
}
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile>
|
||||
GameListModel::FindSecondDisc(const UICommon::GameFile& game) const
|
||||
{
|
||||
std::shared_ptr<const UICommon::GameFile> match_without_revision = nullptr;
|
||||
|
||||
if (DiscIO::IsDisc(game.GetPlatform()))
|
||||
{
|
||||
for (auto& other_game : m_games)
|
||||
{
|
||||
if (game.GetGameID() == other_game->GetGameID() &&
|
||||
game.GetDiscNumber() != other_game->GetDiscNumber())
|
||||
{
|
||||
if (game.GetRevision() == other_game->GetRevision())
|
||||
return other_game;
|
||||
else
|
||||
match_without_revision = other_game;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return match_without_revision;
|
||||
}
|
||||
|
||||
void GameListModel::SetSearchTerm(const QString& term)
|
||||
{
|
||||
m_term = term;
|
||||
|
|
|
@ -63,6 +63,9 @@ public:
|
|||
void UpdateGame(const std::shared_ptr<const UICommon::GameFile>& game);
|
||||
void RemoveGame(const std::string& path);
|
||||
|
||||
std::shared_ptr<const UICommon::GameFile> FindGame(const std::string& path) const;
|
||||
std::shared_ptr<const UICommon::GameFile> FindSecondDisc(const UICommon::GameFile& game) const;
|
||||
|
||||
void SetScale(float scale);
|
||||
float GetScale() const;
|
||||
|
||||
|
@ -79,7 +82,7 @@ public:
|
|||
|
||||
private:
|
||||
// Index in m_games, or -1 if it isn't found
|
||||
int FindGame(const std::string& path) const;
|
||||
int FindGameIndex(const std::string& path) const;
|
||||
|
||||
QStringList m_tag_list;
|
||||
QMap<QString, QVariant> m_game_tags;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
<string>gcm</string>
|
||||
<string>gcz</string>
|
||||
<string>iso</string>
|
||||
<string>m3u</string>
|
||||
<string>tgc</string>
|
||||
<string>wad</string>
|
||||
<string>wbfs</string>
|
||||
|
|
|
@ -147,7 +147,10 @@ int main(int argc, char* argv[])
|
|||
std::unique_ptr<BootParameters> boot;
|
||||
if (options.is_set("exec"))
|
||||
{
|
||||
boot = BootParameters::GenerateFromFile(static_cast<const char*>(options.get("exec")));
|
||||
const std::list<std::string> paths_list = options.all("exec");
|
||||
const std::vector<std::string> paths{std::make_move_iterator(std::begin(paths_list)),
|
||||
std::make_move_iterator(std::end(paths_list))};
|
||||
boot = BootParameters::GenerateFromFile(paths);
|
||||
}
|
||||
else if (options.is_set("nand_title"))
|
||||
{
|
||||
|
|
|
@ -168,6 +168,17 @@ static WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
return wsi;
|
||||
}
|
||||
|
||||
static std::vector<std::string> StringListToStdVector(QStringList list)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(list.size());
|
||||
|
||||
for (const QString& s : list)
|
||||
result.push_back(s.toStdString());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters) : QMainWindow(nullptr)
|
||||
{
|
||||
setWindowTitle(QString::fromStdString(Common::scm_rev_str));
|
||||
|
@ -387,7 +398,7 @@ void MainWindow::ConnectMenuBar()
|
|||
connect(m_menu_bar, &MenuBar::EjectDisc, this, &MainWindow::EjectDisc);
|
||||
connect(m_menu_bar, &MenuBar::ChangeDisc, this, &MainWindow::ChangeDisc);
|
||||
connect(m_menu_bar, &MenuBar::BootDVDBackup, this,
|
||||
[this](const QString& drive) { StartGame(drive); });
|
||||
[this](const QString& drive) { StartGame(drive, ScanForSecondDisc::No); });
|
||||
|
||||
// Emulation
|
||||
connect(m_menu_bar, &MenuBar::Pause, this, &MainWindow::Pause);
|
||||
|
@ -610,30 +621,30 @@ void MainWindow::RefreshGameList()
|
|||
Settings::Instance().RefreshGameList();
|
||||
}
|
||||
|
||||
QString MainWindow::PromptFileName()
|
||||
QStringList MainWindow::PromptFileNames()
|
||||
{
|
||||
auto& settings = Settings::Instance().GetQSettings();
|
||||
QString path = QFileDialog::getOpenFileName(
|
||||
QStringList paths = QFileDialog::getOpenFileNames(
|
||||
this, tr("Select a File"),
|
||||
settings.value(QStringLiteral("mainwindow/lastdir"), QStringLiteral("")).toString(),
|
||||
tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad *.dff);;"
|
||||
tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad *.dff *.m3u);;"
|
||||
"All Files (*)"));
|
||||
|
||||
if (!path.isEmpty())
|
||||
if (!paths.isEmpty())
|
||||
{
|
||||
settings.setValue(QStringLiteral("mainwindow/lastdir"),
|
||||
QFileInfo(path).absoluteDir().absolutePath());
|
||||
QFileInfo(paths.front()).absoluteDir().absolutePath());
|
||||
}
|
||||
|
||||
return path;
|
||||
return paths;
|
||||
}
|
||||
|
||||
void MainWindow::ChangeDisc()
|
||||
{
|
||||
QString file = PromptFileName();
|
||||
std::vector<std::string> paths = StringListToStdVector(PromptFileNames());
|
||||
|
||||
if (!file.isEmpty())
|
||||
Core::RunAsCPUThread([&file] { DVDInterface::ChangeDisc(file.toStdString()); });
|
||||
if (!paths.empty())
|
||||
Core::RunAsCPUThread([&paths] { DVDInterface::ChangeDisc(paths); });
|
||||
}
|
||||
|
||||
void MainWindow::EjectDisc()
|
||||
|
@ -643,9 +654,9 @@ void MainWindow::EjectDisc()
|
|||
|
||||
void MainWindow::Open()
|
||||
{
|
||||
QString file = PromptFileName();
|
||||
if (!file.isEmpty())
|
||||
StartGame(file);
|
||||
QStringList files = PromptFileNames();
|
||||
if (!files.isEmpty())
|
||||
StartGame(StringListToStdVector(files));
|
||||
}
|
||||
|
||||
void MainWindow::Play(const std::optional<std::string>& savestate_path)
|
||||
|
@ -664,7 +675,7 @@ void MainWindow::Play(const std::optional<std::string>& savestate_path)
|
|||
std::shared_ptr<const UICommon::GameFile> selection = m_game_list->GetSelectedGame();
|
||||
if (selection)
|
||||
{
|
||||
StartGame(selection->GetFilePath(), savestate_path);
|
||||
StartGame(selection->GetFilePath(), ScanForSecondDisc::Yes, savestate_path);
|
||||
EnableScreenSaver(false);
|
||||
}
|
||||
else
|
||||
|
@ -672,7 +683,7 @@ void MainWindow::Play(const std::optional<std::string>& savestate_path)
|
|||
const QString default_path = QString::fromStdString(Config::Get(Config::MAIN_DEFAULT_ISO));
|
||||
if (!default_path.isEmpty() && QFile::exists(default_path))
|
||||
{
|
||||
StartGame(default_path, savestate_path);
|
||||
StartGame(default_path, ScanForSecondDisc::Yes, savestate_path);
|
||||
EnableScreenSaver(false);
|
||||
}
|
||||
else
|
||||
|
@ -833,17 +844,46 @@ void MainWindow::ScreenShot()
|
|||
Core::SaveScreenShot();
|
||||
}
|
||||
|
||||
void MainWindow::StartGame(const QString& path, const std::optional<std::string>& savestate_path)
|
||||
void MainWindow::ScanForSecondDiscAndStartGame(const UICommon::GameFile& game,
|
||||
const std::optional<std::string>& savestate_path)
|
||||
{
|
||||
StartGame(path.toStdString(), savestate_path);
|
||||
auto second_game = m_game_list->FindSecondDisc(game);
|
||||
|
||||
std::vector<std::string> paths = {game.GetFilePath()};
|
||||
if (second_game != nullptr)
|
||||
paths.push_back(second_game->GetFilePath());
|
||||
|
||||
StartGame(paths, savestate_path);
|
||||
}
|
||||
|
||||
void MainWindow::StartGame(const std::string& path,
|
||||
void MainWindow::StartGame(const QString& path, ScanForSecondDisc scan,
|
||||
const std::optional<std::string>& savestate_path)
|
||||
{
|
||||
StartGame(path.toStdString(), scan, savestate_path);
|
||||
}
|
||||
|
||||
void MainWindow::StartGame(const std::string& path, ScanForSecondDisc scan,
|
||||
const std::optional<std::string>& savestate_path)
|
||||
{
|
||||
if (scan == ScanForSecondDisc::Yes)
|
||||
{
|
||||
std::shared_ptr<const UICommon::GameFile> game = m_game_list->FindGame(path);
|
||||
if (game != nullptr)
|
||||
{
|
||||
ScanForSecondDiscAndStartGame(*game, savestate_path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
StartGame(BootParameters::GenerateFromFile(path, savestate_path));
|
||||
}
|
||||
|
||||
void MainWindow::StartGame(const std::vector<std::string>& paths,
|
||||
const std::optional<std::string>& savestate_path)
|
||||
{
|
||||
StartGame(BootParameters::GenerateFromFile(paths, savestate_path));
|
||||
}
|
||||
|
||||
void MainWindow::StartGame(std::unique_ptr<BootParameters>&& parameters)
|
||||
{
|
||||
// If we're running, only start a new game once we've stopped the last.
|
||||
|
@ -1075,7 +1115,7 @@ void MainWindow::ShowFIFOPlayer()
|
|||
{
|
||||
m_fifo_window = new FIFOPlayerWindow(this);
|
||||
connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this,
|
||||
[this](const QString& path) { StartGame(path); });
|
||||
[this](const QString& path) { StartGame(path, ScanForSecondDisc::No); });
|
||||
}
|
||||
|
||||
m_fifo_window->show();
|
||||
|
@ -1170,7 +1210,7 @@ void MainWindow::NetPlayInit()
|
|||
#endif
|
||||
|
||||
connect(m_netplay_dialog, &NetPlayDialog::Boot, this,
|
||||
[this](const QString& path) { StartGame(path); });
|
||||
[this](const QString& path) { StartGame(path, ScanForSecondDisc::Yes); });
|
||||
connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::ForceStop);
|
||||
connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit);
|
||||
connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin);
|
||||
|
@ -1346,38 +1386,48 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event)
|
|||
|
||||
void MainWindow::dropEvent(QDropEvent* event)
|
||||
{
|
||||
const auto& urls = event->mimeData()->urls();
|
||||
const QList<QUrl>& urls = event->mimeData()->urls();
|
||||
if (urls.empty())
|
||||
return;
|
||||
|
||||
const auto& url = urls[0];
|
||||
QFileInfo file_info(url.toLocalFile());
|
||||
QStringList files;
|
||||
QStringList folders;
|
||||
|
||||
auto path = file_info.filePath();
|
||||
|
||||
if (!file_info.exists() || !file_info.isReadable())
|
||||
for (const QUrl& url : urls)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to open '%1'").arg(path));
|
||||
return;
|
||||
QFileInfo file_info(url.toLocalFile());
|
||||
QString path = file_info.filePath();
|
||||
|
||||
if (!file_info.exists() || !file_info.isReadable())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to open '%1'").arg(path));
|
||||
return;
|
||||
}
|
||||
|
||||
(file_info.isFile() ? files : folders).append(path);
|
||||
}
|
||||
|
||||
if (file_info.isFile())
|
||||
if (!files.isEmpty())
|
||||
{
|
||||
StartGame(path);
|
||||
StartGame(StringListToStdVector(files));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& settings = Settings::Instance();
|
||||
Settings& settings = Settings::Instance();
|
||||
const bool show_confirm = settings.GetPaths().size() != 0;
|
||||
|
||||
if (settings.GetPaths().size() != 0)
|
||||
for (const QString& folder : folders)
|
||||
{
|
||||
if (QMessageBox::question(
|
||||
this, tr("Confirm"),
|
||||
tr("Do you want to add \"%1\" to the list of Game Paths?").arg(path)) !=
|
||||
QMessageBox::Yes)
|
||||
return;
|
||||
if (show_confirm)
|
||||
{
|
||||
if (QMessageBox::question(
|
||||
this, tr("Confirm"),
|
||||
tr("Do you want to add \"%1\" to the list of Game Paths?").arg(folder)) !=
|
||||
QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
settings.AddPath(folder);
|
||||
}
|
||||
settings.AddPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QStringList>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -47,6 +48,11 @@ namespace DiscIO
|
|||
enum class Region;
|
||||
}
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
namespace X11Utils
|
||||
{
|
||||
class XRRConfiguration;
|
||||
|
@ -115,8 +121,20 @@ private:
|
|||
|
||||
void InitCoreCallbacks();
|
||||
|
||||
void StartGame(const QString& path, const std::optional<std::string>& savestate_path = {});
|
||||
void StartGame(const std::string& path, const std::optional<std::string>& savestate_path = {});
|
||||
enum class ScanForSecondDisc
|
||||
{
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
|
||||
void ScanForSecondDiscAndStartGame(const UICommon::GameFile& game,
|
||||
const std::optional<std::string>& savestate_path = {});
|
||||
void StartGame(const QString& path, ScanForSecondDisc scan,
|
||||
const std::optional<std::string>& savestate_path = {});
|
||||
void StartGame(const std::string& path, ScanForSecondDisc scan,
|
||||
const std::optional<std::string>& savestate_path = {});
|
||||
void StartGame(const std::vector<std::string>& paths,
|
||||
const std::optional<std::string>& savestate_path = {});
|
||||
void StartGame(std::unique_ptr<BootParameters>&& parameters);
|
||||
void ShowRenderWidget();
|
||||
void HideRenderWidget(bool reinit = true);
|
||||
|
@ -155,7 +173,7 @@ private:
|
|||
void ChangeDisc();
|
||||
void EjectDisc();
|
||||
|
||||
QString PromptFileName();
|
||||
QStringList PromptFileNames();
|
||||
|
||||
void EnableScreenSaver(bool enable);
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ void GeneralPane::ConnectLayout()
|
|||
{
|
||||
connect(m_checkbox_dualcore, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig);
|
||||
connect(m_checkbox_cheats, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig);
|
||||
connect(m_checkbox_auto_disc_change, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig);
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
connect(m_checkbox_discord_presence, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig);
|
||||
#endif
|
||||
|
@ -137,6 +138,9 @@ void GeneralPane::CreateBasic()
|
|||
m_checkbox_cheats = new QCheckBox(tr("Enable Cheats"));
|
||||
basic_group_layout->addWidget(m_checkbox_cheats);
|
||||
|
||||
m_checkbox_auto_disc_change = new QCheckBox(tr("Change Discs Automatically"));
|
||||
basic_group_layout->addWidget(m_checkbox_auto_disc_change);
|
||||
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
m_checkbox_discord_presence = new QCheckBox(tr("Show Current Game on Discord"));
|
||||
basic_group_layout->addWidget(m_checkbox_discord_presence);
|
||||
|
@ -236,6 +240,7 @@ void GeneralPane::LoadConfig()
|
|||
#endif
|
||||
m_checkbox_dualcore->setChecked(SConfig::GetInstance().bCPUThread);
|
||||
m_checkbox_cheats->setChecked(Settings::Instance().GetCheatsEnabled());
|
||||
m_checkbox_auto_disc_change->setChecked(Config::Get(Config::MAIN_AUTO_DISC_CHANGE));
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
m_checkbox_discord_presence->setChecked(Config::Get(Config::MAIN_USE_DISCORD_PRESENCE));
|
||||
#endif
|
||||
|
@ -295,6 +300,7 @@ void GeneralPane::OnSaveConfig()
|
|||
settings.bCPUThread = m_checkbox_dualcore->isChecked();
|
||||
Config::SetBaseOrCurrent(Config::MAIN_CPU_THREAD, m_checkbox_dualcore->isChecked());
|
||||
Settings::Instance().SetCheatsEnabled(m_checkbox_cheats->isChecked());
|
||||
Config::SetBase(Config::MAIN_AUTO_DISC_CHANGE, m_checkbox_auto_disc_change->isChecked());
|
||||
Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, m_checkbox_cheats->isChecked());
|
||||
settings.m_EmulationSpeed = m_combobox_speedlimit->currentIndex() * 0.1f;
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ private:
|
|||
QComboBox* m_combobox_update_track;
|
||||
QCheckBox* m_checkbox_dualcore;
|
||||
QCheckBox* m_checkbox_cheats;
|
||||
QCheckBox* m_checkbox_auto_disc_change;
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
QCheckBox* m_checkbox_discord_presence;
|
||||
#endif
|
||||
|
|
|
@ -43,7 +43,7 @@ void PathPane::BrowseDefaultGame()
|
|||
{
|
||||
QString file = QDir::toNativeSeparators(QFileDialog::getOpenFileName(
|
||||
this, tr("Select a Game"), Settings::Instance().GetDefaultGame(),
|
||||
tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad);;"
|
||||
tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad *.m3u);;"
|
||||
"All Files (*)")));
|
||||
|
||||
if (!file.isEmpty())
|
||||
|
|
|
@ -75,7 +75,7 @@ std::unique_ptr<optparse::OptionParser> CreateParser(ParserOptions options)
|
|||
parser->add_option("-u", "--user").action("store").help("User folder path");
|
||||
parser->add_option("-m", "--movie").action("store").help("Play a movie file");
|
||||
parser->add_option("-e", "--exec")
|
||||
.action("store")
|
||||
.action("append")
|
||||
.metavar("<file>")
|
||||
.type("string")
|
||||
.help("Load the specified file");
|
||||
|
|
Loading…
Reference in New Issue