Merge pull request #10184 from JosJuice/android-riivolution

Android: Allow starting game with Riivolution patches from the GUI
This commit is contained in:
JosJuice
2021-10-31 23:44:54 +01:00
committed by GitHub
26 changed files with 822 additions and 37 deletions

View File

@ -127,6 +127,11 @@
android:label="@string/user_data_submenu" android:label="@string/user_data_submenu"
android:theme="@style/DolphinSettingsBase" /> android:theme="@style/DolphinSettingsBase" />
<activity
android:name=".features.riivolution.ui.RiivolutionBootActivity"
android:exported="false"
android:theme="@style/DolphinBase" />
<service <service
android:name=".utils.DirectoryInitialization" android:name=".utils.DirectoryInitialization"
android:exported="false"/> android:exported="false"/>

View File

@ -379,12 +379,13 @@ public final class NativeLibrary
/** /**
* Begins emulation. * Begins emulation.
*/ */
public static native void Run(String[] path); public static native void Run(String[] path, boolean riivolution);
/** /**
* Begins emulation from the specified savestate. * Begins emulation from the specified savestate.
*/ */
public static native void Run(String[] path, String savestatePath, boolean deleteSavestate); public static native void Run(String[] path, boolean riivolution, String savestatePath,
boolean deleteSavestate);
public static native void ChangeDisc(String path); public static native void ChangeDisc(String path);

View File

@ -140,6 +140,6 @@ public class AppLinkActivity extends FragmentActivity
mAfterDirectoryInitializationRunner.cancel(); mAfterDirectoryInitializationRunner.cancel();
mAfterDirectoryInitializationRunner = null; mAfterDirectoryInitializationRunner = null;
} }
EmulationActivity.launch(this, GameFileCacheService.findSecondDiscAndGetPaths(game)); EmulationActivity.launch(this, GameFileCacheService.findSecondDiscAndGetPaths(game), false);
} }
} }

View File

@ -79,11 +79,13 @@ public final class EmulationActivity extends AppCompatActivity
private boolean activityRecreated; private boolean activityRecreated;
private String[] mPaths; private String[] mPaths;
private boolean mRiivolution;
private boolean mIgnoreWarnings; private boolean mIgnoreWarnings;
private static boolean sUserPausedEmulation; private static boolean sUserPausedEmulation;
private boolean mMenuToastShown; private boolean mMenuToastShown;
public static final String EXTRA_SELECTED_GAMES = "SelectedGames"; public static final String EXTRA_SELECTED_GAMES = "SelectedGames";
public static final String EXTRA_RIIVOLUTION = "Riivolution";
public static final String EXTRA_IGNORE_WARNINGS = "IgnoreWarnings"; public static final String EXTRA_IGNORE_WARNINGS = "IgnoreWarnings";
public static final String EXTRA_USER_PAUSED_EMULATION = "sUserPausedEmulation"; public static final String EXTRA_USER_PAUSED_EMULATION = "sUserPausedEmulation";
public static final String EXTRA_MENU_TOAST_SHOWN = "MenuToastShown"; public static final String EXTRA_MENU_TOAST_SHOWN = "MenuToastShown";
@ -164,12 +166,12 @@ public final class EmulationActivity extends AppCompatActivity
EmulationActivity.MENU_ACTION_MOTION_CONTROLS); EmulationActivity.MENU_ACTION_MOTION_CONTROLS);
} }
public static void launch(FragmentActivity activity, String filePath) public static void launch(FragmentActivity activity, String filePath, boolean riivolution)
{ {
launch(activity, new String[]{filePath}); launch(activity, new String[]{filePath}, riivolution);
} }
public static void launch(FragmentActivity activity, String[] filePaths) public static void launch(FragmentActivity activity, String[] filePaths, boolean riivolution)
{ {
if (sIgnoreLaunchRequests) if (sIgnoreLaunchRequests)
return; return;
@ -183,7 +185,7 @@ public final class EmulationActivity extends AppCompatActivity
FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_RESOURCEPACK_PATH) && FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_RESOURCEPACK_PATH) &&
FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_SD_PATH)) FileBrowserHelper.isPathEmptyOrValid(StringSetting.MAIN_SD_PATH))
{ {
launchWithoutChecks(activity, filePaths); launchWithoutChecks(activity, filePaths, riivolution);
} }
else else
{ {
@ -192,18 +194,20 @@ public final class EmulationActivity extends AppCompatActivity
builder.setPositiveButton(R.string.yes, (dialogInterface, i) -> builder.setPositiveButton(R.string.yes, (dialogInterface, i) ->
SettingsActivity.launch(activity, MenuTag.CONFIG_PATHS)); SettingsActivity.launch(activity, MenuTag.CONFIG_PATHS));
builder.setNeutralButton(R.string.continue_anyway, (dialogInterface, i) -> builder.setNeutralButton(R.string.continue_anyway, (dialogInterface, i) ->
launchWithoutChecks(activity, filePaths)); launchWithoutChecks(activity, filePaths, riivolution));
builder.show(); builder.show();
} }
}); });
} }
private static void launchWithoutChecks(FragmentActivity activity, String[] filePaths) private static void launchWithoutChecks(FragmentActivity activity, String[] filePaths,
boolean riivolution)
{ {
sIgnoreLaunchRequests = true; sIgnoreLaunchRequests = true;
Intent launcher = new Intent(activity, EmulationActivity.class); Intent launcher = new Intent(activity, EmulationActivity.class);
launcher.putExtra(EXTRA_SELECTED_GAMES, filePaths); launcher.putExtra(EXTRA_SELECTED_GAMES, filePaths);
launcher.putExtra(EXTRA_RIIVOLUTION, riivolution);
activity.startActivity(launcher); activity.startActivity(launcher);
} }
@ -251,6 +255,7 @@ public final class EmulationActivity extends AppCompatActivity
// Get params we were passed // Get params we were passed
Intent gameToEmulate = getIntent(); Intent gameToEmulate = getIntent();
mPaths = gameToEmulate.getStringArrayExtra(EXTRA_SELECTED_GAMES); mPaths = gameToEmulate.getStringArrayExtra(EXTRA_SELECTED_GAMES);
mRiivolution = gameToEmulate.getBooleanExtra(EXTRA_RIIVOLUTION, false);
mIgnoreWarnings = gameToEmulate.getBooleanExtra(EXTRA_IGNORE_WARNINGS, false); mIgnoreWarnings = gameToEmulate.getBooleanExtra(EXTRA_IGNORE_WARNINGS, false);
sUserPausedEmulation = gameToEmulate.getBooleanExtra(EXTRA_USER_PAUSED_EMULATION, false); sUserPausedEmulation = gameToEmulate.getBooleanExtra(EXTRA_USER_PAUSED_EMULATION, false);
mMenuToastShown = false; mMenuToastShown = false;
@ -283,7 +288,7 @@ public final class EmulationActivity extends AppCompatActivity
.findFragmentById(R.id.frame_emulation_fragment); .findFragmentById(R.id.frame_emulation_fragment);
if (mEmulationFragment == null) if (mEmulationFragment == null)
{ {
mEmulationFragment = EmulationFragment.newInstance(mPaths); mEmulationFragment = EmulationFragment.newInstance(mPaths, mRiivolution);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.add(R.id.frame_emulation_fragment, mEmulationFragment) .add(R.id.frame_emulation_fragment, mEmulationFragment)
.commit(); .commit();

View File

@ -141,7 +141,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
GameViewHolder holder = (GameViewHolder) view.getTag(); GameViewHolder holder = (GameViewHolder) view.getTag();
String[] paths = GameFileCacheService.findSecondDiscAndGetPaths(holder.gameFile); String[] paths = GameFileCacheService.findSecondDiscAndGetPaths(holder.gameFile);
EmulationActivity.launch((FragmentActivity) view.getContext(), paths); EmulationActivity.launch((FragmentActivity) view.getContext(), paths, false);
} }
/** /**

View File

@ -15,6 +15,7 @@ import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.ConvertActivity; import org.dolphinemu.dolphinemu.activities.ConvertActivity;
import org.dolphinemu.dolphinemu.features.cheats.ui.CheatsActivity; import org.dolphinemu.dolphinemu.features.cheats.ui.CheatsActivity;
import org.dolphinemu.dolphinemu.features.riivolution.ui.RiivolutionBootActivity;
import org.dolphinemu.dolphinemu.features.settings.model.Settings; import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting; import org.dolphinemu.dolphinemu.features.settings.model.StringSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
@ -34,6 +35,7 @@ public class GamePropertiesDialog extends DialogFragment
private static final String ARG_GAME_ID = "game_id"; private static final String ARG_GAME_ID = "game_id";
private static final String ARG_GAMETDB_ID = "gametdb_id"; private static final String ARG_GAMETDB_ID = "gametdb_id";
public static final String ARG_REVISION = "revision"; public static final String ARG_REVISION = "revision";
public static final String ARG_DISC_NUMBER = "disc_number";
private static final String ARG_PLATFORM = "platform"; private static final String ARG_PLATFORM = "platform";
private static final String ARG_SHOULD_ALLOW_CONVERSION = "should_allow_conversion"; private static final String ARG_SHOULD_ALLOW_CONVERSION = "should_allow_conversion";
@ -46,6 +48,7 @@ public class GamePropertiesDialog extends DialogFragment
arguments.putString(ARG_GAME_ID, gameFile.getGameId()); arguments.putString(ARG_GAME_ID, gameFile.getGameId());
arguments.putString(ARG_GAMETDB_ID, gameFile.getGameTdbId()); arguments.putString(ARG_GAMETDB_ID, gameFile.getGameTdbId());
arguments.putInt(ARG_REVISION, gameFile.getRevision()); arguments.putInt(ARG_REVISION, gameFile.getRevision());
arguments.putInt(ARG_DISC_NUMBER, gameFile.getDiscNumber());
arguments.putInt(ARG_PLATFORM, gameFile.getPlatform()); arguments.putInt(ARG_PLATFORM, gameFile.getPlatform());
arguments.putBoolean(ARG_SHOULD_ALLOW_CONVERSION, gameFile.shouldAllowConversion()); arguments.putBoolean(ARG_SHOULD_ALLOW_CONVERSION, gameFile.shouldAllowConversion());
fragment.setArguments(arguments); fragment.setArguments(arguments);
@ -61,6 +64,7 @@ public class GamePropertiesDialog extends DialogFragment
final String gameId = requireArguments().getString(ARG_GAME_ID); final String gameId = requireArguments().getString(ARG_GAME_ID);
final String gameTdbId = requireArguments().getString(ARG_GAMETDB_ID); final String gameTdbId = requireArguments().getString(ARG_GAMETDB_ID);
final int revision = requireArguments().getInt(ARG_REVISION); final int revision = requireArguments().getInt(ARG_REVISION);
final int discNumber = requireArguments().getInt(ARG_DISC_NUMBER);
final int platform = requireArguments().getInt(ARG_PLATFORM); final int platform = requireArguments().getInt(ARG_PLATFORM);
final boolean shouldAllowConversion = final boolean shouldAllowConversion =
requireArguments().getBoolean(ARG_SHOULD_ALLOW_CONVERSION); requireArguments().getBoolean(ARG_SHOULD_ALLOW_CONVERSION);
@ -75,14 +79,11 @@ public class GamePropertiesDialog extends DialogFragment
GameDetailsDialog.newInstance(path).show(requireActivity() GameDetailsDialog.newInstance(path).show(requireActivity()
.getSupportFragmentManager(), "game_details")); .getSupportFragmentManager(), "game_details"));
if (shouldAllowConversion)
{
itemsBuilder.add(R.string.properties_convert, (dialog, i) ->
ConvertActivity.launch(getContext(), path));
}
if (isDisc) if (isDisc)
{ {
itemsBuilder.add(R.string.properties_start_with_riivolution, (dialog, i) ->
RiivolutionBootActivity.launch(getContext(), path, gameId, revision, discNumber));
itemsBuilder.add(R.string.properties_set_default_iso, (dialog, i) -> itemsBuilder.add(R.string.properties_set_default_iso, (dialog, i) ->
{ {
try (Settings settings = new Settings()) try (Settings settings = new Settings())
@ -94,6 +95,12 @@ public class GamePropertiesDialog extends DialogFragment
}); });
} }
if (shouldAllowConversion)
{
itemsBuilder.add(R.string.properties_convert, (dialog, i) ->
ConvertActivity.launch(getContext(), path));
}
itemsBuilder.add(R.string.properties_edit_game_settings, (dialog, i) -> itemsBuilder.add(R.string.properties_edit_game_settings, (dialog, i) ->
SettingsActivity.launch(getContext(), MenuTag.SETTINGS, gameId, revision, isWii)); SettingsActivity.launch(getContext(), MenuTag.SETTINGS, gameId, revision, isWii));

View File

@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.riivolution.model;
import androidx.annotation.Keep;
public class RiivolutionPatches
{
private String mGameId;
private int mRevision;
private int mDiscNumber;
private boolean mUnsavedChanges = false;
@Keep
private long mPointer;
public RiivolutionPatches(String gameId, int revision, int discNumber)
{
mGameId = gameId;
mRevision = revision;
mDiscNumber = discNumber;
mPointer = initialize();
}
@Override
public native void finalize();
private static native long initialize();
public native int getDiscCount();
public native String getDiscName(int discIndex);
public native int getSectionCount(int discIndex);
public native String getSectionName(int discIndex, int sectionIndex);
public native int getOptionCount(int discIndex, int sectionIndex);
public native String getOptionName(int discIndex, int sectionIndex, int optionIndex);
public native int getChoiceCount(int discIndex, int sectionIndex, int optionIndex);
public native String getChoiceName(int discIndex, int sectionIndex, int optionIndex,
int choiceIndex);
/**
* @return 0 if no choice is selected, otherwise the index of the selected choice plus one.
*/
public native int getSelectedChoice(int discIndex, int sectionIndex, int optionIndex);
/**
* @param choiceIndex 0 to select no choice, otherwise the choice index plus one.
*/
public void setSelectedChoice(int discIndex, int sectionIndex, int optionIndex, int choiceIndex)
{
mUnsavedChanges = true;
setSelectedChoiceImpl(discIndex, sectionIndex, optionIndex, choiceIndex);
}
/**
* @param choiceIndex 0 to select no choice, otherwise the choice index plus one.
*/
private native void setSelectedChoiceImpl(int discIndex, int sectionIndex, int optionIndex,
int choiceIndex);
public void loadConfig()
{
loadConfigImpl(mGameId, mRevision, mDiscNumber);
}
private native void loadConfigImpl(String gameId, int revision, int discNumber);
public void saveConfig()
{
if (mUnsavedChanges)
{
mUnsavedChanges = false;
saveConfigImpl(mGameId);
}
}
private native void saveConfigImpl(String gameId);
}

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.riivolution.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.riivolution.model.RiivolutionPatches;
import java.util.ArrayList;
public class RiivolutionAdapter extends RecyclerView.Adapter<RiivolutionViewHolder>
{
private final Context mContext;
private final RiivolutionPatches mPatches;
private final ArrayList<RiivolutionItem> mItems = new ArrayList<>();
public RiivolutionAdapter(Context context, RiivolutionPatches patches)
{
mContext = context;
mPatches = patches;
int discCount = mPatches.getDiscCount();
for (int i = 0; i < discCount; i++)
{
mItems.add(new RiivolutionItem(i));
int sectionCount = mPatches.getSectionCount(i);
for (int j = 0; j < sectionCount; j++)
{
mItems.add(new RiivolutionItem(i, j));
int optionCount = mPatches.getOptionCount(i, j);
for (int k = 0; k < optionCount; k++)
{
mItems.add(new RiivolutionItem(i, j, k));
}
}
}
}
@NonNull @Override
public RiivolutionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType)
{
case RiivolutionViewHolder.TYPE_HEADER:
View headerView = inflater.inflate(R.layout.list_item_riivolution_header, parent, false);
return new RiivolutionViewHolder(headerView);
case RiivolutionViewHolder.TYPE_OPTION:
View optionView = inflater.inflate(R.layout.list_item_riivolution_option, parent, false);
return new RiivolutionViewHolder(optionView);
default:
throw new UnsupportedOperationException();
}
}
@Override
public void onBindViewHolder(@NonNull RiivolutionViewHolder holder, int position)
{
holder.bind(mContext, mPatches, mItems.get(position));
}
@Override
public int getItemCount()
{
return mItems.size();
}
@Override
public int getItemViewType(int position)
{
return mItems.get(position).mOptionIndex != -1 ?
RiivolutionViewHolder.TYPE_OPTION : RiivolutionViewHolder.TYPE_HEADER;
}
}

View File

@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.riivolution.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.features.riivolution.model.RiivolutionPatches;
import org.dolphinemu.dolphinemu.ui.DividerItemDecoration;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
public class RiivolutionBootActivity extends AppCompatActivity
{
private static final String ARG_GAME_PATH = "game_path";
private static final String ARG_GAME_ID = "game_id";
private static final String ARG_REVISION = "revision";
private static final String ARG_DISC_NUMBER = "disc_number";
private RiivolutionPatches mPatches;
public static void launch(Context context, String gamePath, String gameId, int revision,
int discNumber)
{
Intent launcher = new Intent(context, RiivolutionBootActivity.class);
launcher.putExtra(ARG_GAME_PATH, gamePath);
launcher.putExtra(ARG_GAME_ID, gameId);
launcher.putExtra(ARG_REVISION, revision);
launcher.putExtra(ARG_DISC_NUMBER, discNumber);
context.startActivity(launcher);
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_riivolution_boot);
Intent intent = getIntent();
String path = getIntent().getStringExtra(ARG_GAME_PATH);
String gameId = intent.getStringExtra(ARG_GAME_ID);
int revision = intent.getIntExtra(ARG_REVISION, -1);
int discNumber = intent.getIntExtra(ARG_DISC_NUMBER, -1);
TextView textSdRoot = findViewById(R.id.text_sd_root);
String riivolutionPath = DirectoryInitialization.getUserDirectory() + "/Load/Riivolution";
textSdRoot.setText(getString(R.string.riivolution_sd_root, riivolutionPath));
Button buttonStart = findViewById(R.id.button_start);
buttonStart.setOnClickListener((v) ->
{
if (mPatches != null)
mPatches.saveConfig();
EmulationActivity.launch(this, path, true);
});
new Thread(() ->
{
RiivolutionPatches patches = new RiivolutionPatches(gameId, revision, discNumber);
patches.loadConfig();
runOnUiThread(() -> populateList(patches));
}).start();
}
@Override
protected void onStop()
{
super.onStop();
if (mPatches != null)
mPatches.saveConfig();
}
private void populateList(RiivolutionPatches patches)
{
mPatches = patches;
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setAdapter(new RiivolutionAdapter(this, patches));
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
}

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.riivolution.ui;
public class RiivolutionItem
{
public final int mDiscIndex;
public final int mSectionIndex;
public final int mOptionIndex;
/**
* Constructor for a disc.
*/
public RiivolutionItem(int discIndex)
{
mDiscIndex = discIndex;
mSectionIndex = -1;
mOptionIndex = -1;
}
/**
* Constructor for a section.
*/
public RiivolutionItem(int discIndex, int sectionIndex)
{
mDiscIndex = discIndex;
mSectionIndex = sectionIndex;
mOptionIndex = -1;
}
/**
* Constructor for an option.
*/
public RiivolutionItem(int discIndex, int sectionIndex, int optionIndex)
{
mDiscIndex = discIndex;
mSectionIndex = sectionIndex;
mOptionIndex = optionIndex;
}
}

View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.riivolution.ui;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.riivolution.model.RiivolutionPatches;
public class RiivolutionViewHolder extends RecyclerView.ViewHolder
implements AdapterView.OnItemSelectedListener
{
public static final int TYPE_HEADER = 0;
public static final int TYPE_OPTION = 1;
private final TextView mTextView;
private final Spinner mSpinner;
private RiivolutionPatches mPatches;
private RiivolutionItem mItem;
public RiivolutionViewHolder(@NonNull View itemView)
{
super(itemView);
mTextView = itemView.findViewById(R.id.text_name);
mSpinner = itemView.findViewById(R.id.spinner_choice);
}
public void bind(Context context, RiivolutionPatches patches, RiivolutionItem item)
{
String text;
if (item.mOptionIndex != -1)
text = patches.getOptionName(item.mDiscIndex, item.mSectionIndex, item.mOptionIndex);
else if (item.mSectionIndex != -1)
text = patches.getSectionName(item.mDiscIndex, item.mSectionIndex);
else
text = patches.getDiscName(item.mDiscIndex);
mTextView.setText(text);
if (item.mOptionIndex != -1)
{
mPatches = patches;
mItem = item;
ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
R.layout.list_item_riivolution_header);
int choiceCount = patches.getChoiceCount(mItem.mDiscIndex, mItem.mSectionIndex,
mItem.mOptionIndex);
adapter.add(context.getString(R.string.riivolution_disabled));
for (int i = 0; i < choiceCount; i++)
{
adapter.add(patches.getChoiceName(mItem.mDiscIndex, mItem.mSectionIndex, mItem.mOptionIndex,
i));
}
mSpinner.setAdapter(adapter);
mSpinner.setSelection(patches.getSelectedChoice(mItem.mDiscIndex, mItem.mSectionIndex,
mItem.mOptionIndex));
mSpinner.setOnItemSelectedListener(this);
}
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
mPatches.setSelectedChoice(mItem.mDiscIndex, mItem.mSectionIndex, mItem.mOptionIndex, position);
}
@Override
public void onNothingSelected(AdapterView<?> parent)
{
}
}

View File

@ -29,19 +29,22 @@ import java.io.File;
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
{ {
private static final String KEY_GAMEPATHS = "gamepaths"; private static final String KEY_GAMEPATHS = "gamepaths";
private static final String KEY_RIIVOLUTION = "riivolution";
private InputOverlay mInputOverlay; private InputOverlay mInputOverlay;
private String[] mGamePaths; private String[] mGamePaths;
private boolean mRiivolution;
private boolean mRunWhenSurfaceIsValid; private boolean mRunWhenSurfaceIsValid;
private boolean mLoadPreviousTemporaryState; private boolean mLoadPreviousTemporaryState;
private EmulationActivity activity; private EmulationActivity activity;
public static EmulationFragment newInstance(String[] gamePaths) public static EmulationFragment newInstance(String[] gamePaths, boolean riivolution)
{ {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putStringArray(KEY_GAMEPATHS, gamePaths); args.putStringArray(KEY_GAMEPATHS, gamePaths);
args.putBoolean(KEY_RIIVOLUTION, riivolution);
EmulationFragment fragment = new EmulationFragment(); EmulationFragment fragment = new EmulationFragment();
fragment.setArguments(args); fragment.setArguments(args);
@ -76,6 +79,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
setRetainInstance(true); setRetainInstance(true);
mGamePaths = getArguments().getStringArray(KEY_GAMEPATHS); mGamePaths = getArguments().getStringArray(KEY_GAMEPATHS);
mRiivolution = getArguments().getBoolean(KEY_RIIVOLUTION);
} }
/** /**
@ -267,12 +271,12 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
if (mLoadPreviousTemporaryState) if (mLoadPreviousTemporaryState)
{ {
Log.debug("[EmulationFragment] Starting emulation thread from previous state."); Log.debug("[EmulationFragment] Starting emulation thread from previous state.");
NativeLibrary.Run(mGamePaths, getTemporaryStateFilePath(), true); NativeLibrary.Run(mGamePaths, mRiivolution, getTemporaryStateFilePath(), true);
} }
else else
{ {
Log.debug("[EmulationFragment] Starting emulation thread."); Log.debug("[EmulationFragment] Starting emulation thread.");
NativeLibrary.Run(mGamePaths); NativeLibrary.Run(mGamePaths, mRiivolution);
} }
EmulationActivity.stopIgnoringLaunchRequests(); EmulationActivity.stopIgnoringLaunchRequests();
}, "NativeEmulation"); }, "NativeEmulation");

View File

@ -216,7 +216,7 @@ public final class MainActivity extends AppCompatActivity
case MainPresenter.REQUEST_GAME_FILE: case MainPresenter.REQUEST_GAME_FILE:
FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.runAfterExtensionCheck(this, uri,
FileBrowserHelper.GAME_LIKE_EXTENSIONS, FileBrowserHelper.GAME_LIKE_EXTENSIONS,
() -> EmulationActivity.launch(this, result.getData().toString())); () -> EmulationActivity.launch(this, result.getData().toString(), false));
break; break;
case MainPresenter.REQUEST_WAD_FILE: case MainPresenter.REQUEST_WAD_FILE:

View File

@ -153,7 +153,7 @@ public final class TvMainActivity extends FragmentActivity
// Start the emulation activity and send the path of the clicked ISO to it. // Start the emulation activity and send the path of the clicked ISO to it.
String[] paths = GameFileCacheService.findSecondDiscAndGetPaths(holder.gameFile); String[] paths = GameFileCacheService.findSecondDiscAndGetPaths(holder.gameFile);
EmulationActivity.launch(TvMainActivity.this, paths); EmulationActivity.launch(TvMainActivity.this, paths, false);
} }
}); });
} }
@ -255,7 +255,7 @@ public final class TvMainActivity extends FragmentActivity
case MainPresenter.REQUEST_GAME_FILE: case MainPresenter.REQUEST_GAME_FILE:
FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.runAfterExtensionCheck(this, uri,
FileBrowserHelper.GAME_LIKE_EXTENSIONS, FileBrowserHelper.GAME_LIKE_EXTENSIONS,
() -> EmulationActivity.launch(this, result.getData().toString())); () -> EmulationActivity.launch(this, result.getData().toString(), false));
break; break;
case MainPresenter.REQUEST_WAD_FILE: case MainPresenter.REQUEST_WAD_FILE:

View File

@ -51,7 +51,7 @@ public final class StartupHandler
if (start_files != null && start_files.length > 0) if (start_files != null && start_files.length > 0)
{ {
// Start the emulation activity, send the ISO passed in and finish the main activity // Start the emulation activity, send the ISO passed in and finish the main activity
EmulationActivity.launch(parent, start_files); EmulationActivity.launch(parent, start_files, false);
parent.finish(); parent.finish();
} }
} }

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_sd_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_large"
tools:text="@string/riivolution_sd_root"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/divider" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#1F000000"
android:layout_marginHorizontal="@dimen/spacing_large"
android:layout_marginVertical="@dimen/spacing_small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_sd_root"
app:layout_constraintBottom_toTopOf="@id/scroll_view" />
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider"
app:layout_constraintBottom_toTopOf="@id/button_start">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp" />
</ScrollView>
<Button
android:id="@+id/button_start"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_large"
android:text="@string/riivolution_start"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/scroll_view"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/text_name"
tools:text="Example Section"
android:paddingHorizontal="@dimen/spacing_large"
android:paddingVertical="@dimen/spacing_medlarge" />

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/text_name"
tools:text="Example Option"
android:layout_marginHorizontal="@dimen/spacing_large"
android:layout_marginVertical="@dimen/spacing_medlarge"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/spinner_choice"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<Spinner
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/spinner_choice"
app:layout_constraintStart_toEndOf="@id/text_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -399,8 +399,9 @@
<!-- Game Properties Screen --> <!-- Game Properties Screen -->
<string name="properties_details">Details</string> <string name="properties_details">Details</string>
<string name="properties_convert">Convert File</string> <string name="properties_start_with_riivolution">Start with Riivolution Patches</string>
<string name="properties_set_default_iso">Set as Default ISO</string> <string name="properties_set_default_iso">Set as Default ISO</string>
<string name="properties_convert">Convert File</string>
<string name="properties_edit_game_settings">Edit Game Settings</string> <string name="properties_edit_game_settings">Edit Game Settings</string>
<string name="properties_edit_cheats">Edit Cheats</string> <string name="properties_edit_cheats">Edit Cheats</string>
<string name="properties_clear_game_settings">Clear Game Settings and Cheats</string> <string name="properties_clear_game_settings">Clear Game Settings and Cheats</string>
@ -480,6 +481,11 @@ and a few other programs. It can efficiently compress encrypted Wii data, but no
It can efficiently compress both junk data and encrypted Wii data. It can efficiently compress both junk data and encrypted Wii data.
</string> </string>
<!-- Riivolution Boot Screen -->
<string name="riivolution_sd_root">SD Root: %1$s</string>
<string name="riivolution_disabled">Disabled</string>
<string name="riivolution_start">Start</string>
<!-- Emulation Menu --> <!-- Emulation Menu -->
<string name="pause_emulation">Pause Emulation</string> <string name="pause_emulation">Pause Emulation</string>
<string name="unpause_emulation">Unpause Emulation</string> <string name="unpause_emulation">Unpause Emulation</string>

View File

@ -70,6 +70,9 @@ static jclass s_patch_cheat_class;
static jfieldID s_patch_cheat_pointer; static jfieldID s_patch_cheat_pointer;
static jmethodID s_patch_cheat_constructor; static jmethodID s_patch_cheat_constructor;
static jclass s_riivolution_patches_class;
static jfieldID s_riivolution_patches_pointer;
namespace IDCache namespace IDCache
{ {
JNIEnv* GetEnvForThread() JNIEnv* GetEnvForThread()
@ -325,6 +328,16 @@ jmethodID GetPatchCheatConstructor()
return s_patch_cheat_constructor; return s_patch_cheat_constructor;
} }
jclass GetRiivolutionPatchesClass()
{
return s_riivolution_patches_class;
}
jfieldID GetRiivolutionPatchesPointer()
{
return s_riivolution_patches_pointer;
}
} // namespace IDCache } // namespace IDCache
extern "C" { extern "C" {
@ -454,6 +467,13 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
s_patch_cheat_constructor = env->GetMethodID(patch_cheat_class, "<init>", "(J)V"); s_patch_cheat_constructor = env->GetMethodID(patch_cheat_class, "<init>", "(J)V");
env->DeleteLocalRef(patch_cheat_class); env->DeleteLocalRef(patch_cheat_class);
const jclass riivolution_patches_class =
env->FindClass("org/dolphinemu/dolphinemu/features/riivolution/model/RiivolutionPatches");
s_riivolution_patches_class =
reinterpret_cast<jclass>(env->NewGlobalRef(riivolution_patches_class));
s_riivolution_patches_pointer = env->GetFieldID(riivolution_patches_class, "mPointer", "J");
env->DeleteLocalRef(riivolution_patches_class);
return JNI_VERSION; return JNI_VERSION;
} }
@ -477,5 +497,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
env->DeleteGlobalRef(s_ar_cheat_class); env->DeleteGlobalRef(s_ar_cheat_class);
env->DeleteGlobalRef(s_gecko_cheat_class); env->DeleteGlobalRef(s_gecko_cheat_class);
env->DeleteGlobalRef(s_patch_cheat_class); env->DeleteGlobalRef(s_patch_cheat_class);
env->DeleteGlobalRef(s_riivolution_patches_class);
} }
} }

View File

@ -69,4 +69,7 @@ jclass GetPatchCheatClass();
jfieldID GetPatchCheatPointer(); jfieldID GetPatchCheatPointer();
jmethodID GetPatchCheatConstructor(); jmethodID GetPatchCheatConstructor();
jclass GetRiivolutionPatchesClass();
jfieldID GetRiivolutionPatchesPointer();
} // namespace IDCache } // namespace IDCache

View File

@ -10,6 +10,7 @@ add_library(main SHARED
GameList/GameFileCache.cpp GameList/GameFileCache.cpp
IniFile.cpp IniFile.cpp
MainAndroid.cpp MainAndroid.cpp
RiivolutionPatches.cpp
WiiUtils.cpp WiiUtils.cpp
) )

View File

@ -48,6 +48,7 @@
#include "DiscIO/Blob.h" #include "DiscIO/Blob.h"
#include "DiscIO/Enums.h" #include "DiscIO/Enums.h"
#include "DiscIO/RiivolutionParser.h"
#include "DiscIO/ScrubbedBlob.h" #include "DiscIO/ScrubbedBlob.h"
#include "DiscIO/Volume.h" #include "DiscIO/Volume.h"
@ -547,7 +548,7 @@ static float GetRenderSurfaceScale(JNIEnv* env)
return env->CallStaticFloatMethod(native_library_class, get_render_surface_scale_method); return env->CallStaticFloatMethod(native_library_class, get_render_surface_scale_method);
} }
static void Run(JNIEnv* env, const std::vector<std::string>& paths, static void Run(JNIEnv* env, const std::vector<std::string>& paths, bool riivolution,
const std::optional<std::string>& savestate_path = {}, const std::optional<std::string>& savestate_path = {},
bool delete_savestate = false) bool delete_savestate = false)
{ {
@ -564,6 +565,16 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths,
if (boot) if (boot)
boot->delete_savestate = delete_savestate; boot->delete_savestate = delete_savestate;
if (riivolution && std::holds_alternative<BootParameters::Disc>(boot->parameters))
{
const std::string& riivolution_dir = File::GetUserPath(D_RIIVOLUTION_IDX);
const DiscIO::Volume& volume = *std::get<BootParameters::Disc>(boot->parameters).volume;
AddRiivolutionPatches(boot.get(), DiscIO::Riivolution::GenerateRiivolutionPatchesFromConfig(
riivolution_dir, volume.GetGameID(), volume.GetRevision(),
volume.GetDiscNumber()));
}
WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf, s_surf); WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf, s_surf);
wsi.render_surface_scale = GetRenderSurfaceScale(env); wsi.render_surface_scale = GetRenderSurfaceScale(env);
@ -616,17 +627,19 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths,
IDCache::GetFinishEmulationActivity()); IDCache::GetFinishEmulationActivity());
} }
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2( JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2Z(
JNIEnv* env, jclass, jobjectArray jPaths) JNIEnv* env, jclass, jobjectArray jPaths, jboolean jRiivolution)
{ {
Run(env, JStringArrayToVector(env, jPaths)); Run(env, JStringArrayToVector(env, jPaths), jRiivolution);
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2Ljava_lang_String_2Z( Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2ZLjava_lang_String_2Z(
JNIEnv* env, jclass, jobjectArray jPaths, jstring jSavestate, jboolean jDeleteSavestate) JNIEnv* env, jclass, jobjectArray jPaths, jboolean jRiivolution, jstring jSavestate,
jboolean jDeleteSavestate)
{ {
Run(env, JStringArrayToVector(env, jPaths), GetJString(env, jSavestate), jDeleteSavestate); Run(env, JStringArrayToVector(env, jPaths), jRiivolution, GetJString(env, jSavestate),
jDeleteSavestate);
} }
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ChangeDisc(JNIEnv* env, jclass, JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ChangeDisc(JNIEnv* env, jclass,

View File

@ -0,0 +1,193 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <string_view>
#include <vector>
#include <fmt/format.h>
#include <jni.h>
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"
#include "DiscIO/RiivolutionParser.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
static std::vector<DiscIO::Riivolution::Disc>* GetPointer(JNIEnv* env, jobject obj)
{
return reinterpret_cast<std::vector<DiscIO::Riivolution::Disc>*>(
env->GetLongField(obj, IDCache::GetRiivolutionPatchesPointer()));
}
static std::vector<DiscIO::Riivolution::Disc>& GetReference(JNIEnv* env, jobject obj)
{
return *GetPointer(env, obj);
}
extern "C" {
JNIEXPORT jlong JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_initialize(JNIEnv* env,
jclass obj)
{
return reinterpret_cast<jlong>(new std::vector<DiscIO::Riivolution::Disc>);
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_finalize(JNIEnv* env,
jobject obj)
{
delete GetPointer(env, obj);
}
JNIEXPORT jint JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getDiscCount(
JNIEnv* env, jobject obj)
{
return GetReference(env, obj).size();
}
JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getDiscName(
JNIEnv* env, jobject obj, jint disc_index)
{
std::string filename, extension;
SplitPath(GetReference(env, obj)[disc_index].m_xml_path, nullptr, &filename, &extension);
return ToJString(env, filename + extension);
}
JNIEXPORT jint JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getSectionCount(
JNIEnv* env, jobject obj, jint disc_index)
{
return GetReference(env, obj)[disc_index].m_sections.size();
}
JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getSectionName(
JNIEnv* env, jobject obj, jint disc_index, jint section_index)
{
return ToJString(env, GetReference(env, obj)[disc_index].m_sections[section_index].m_name);
}
JNIEXPORT jint JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getOptionCount(
JNIEnv* env, jobject obj, jint disc_index, jint section_index)
{
return GetReference(env, obj)[disc_index].m_sections[section_index].m_options.size();
}
JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getOptionName(
JNIEnv* env, jobject obj, jint disc_index, jint section_index, jint option_index)
{
return ToJString(
env,
GetReference(env, obj)[disc_index].m_sections[section_index].m_options[option_index].m_name);
}
JNIEXPORT jint JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getChoiceCount(
JNIEnv* env, jobject obj, jint disc_index, jint section_index, jint option_index)
{
return GetReference(env, obj)[disc_index]
.m_sections[section_index]
.m_options[option_index]
.m_choices.size();
}
JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getChoiceName(
JNIEnv* env, jobject obj, jint disc_index, jint section_index, jint option_index,
jint choice_index)
{
return ToJString(env, GetReference(env, obj)[disc_index]
.m_sections[section_index]
.m_options[option_index]
.m_choices[choice_index]
.m_name);
}
JNIEXPORT jint JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_getSelectedChoice(
JNIEnv* env, jobject obj, jint disc_index, jint section_index, jint option_index)
{
return GetReference(env, obj)[disc_index]
.m_sections[section_index]
.m_options[option_index]
.m_selected_choice;
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_setSelectedChoiceImpl(
JNIEnv* env, jobject obj, jint disc_index, jint section_index, jint option_index,
jint choice_index)
{
GetReference(env, obj)[disc_index]
.m_sections[section_index]
.m_options[option_index]
.m_selected_choice = choice_index;
}
static std::optional<DiscIO::Riivolution::Config> LoadConfigXML(const std::string& root_directory,
std::string_view game_id)
{
// The way Riivolution stores settings only makes sense for standard game IDs.
if (!(game_id.size() == 4 || game_id.size() == 6))
return std::nullopt;
return DiscIO::Riivolution::ParseConfigFile(
fmt::format("{}/riivolution/config/{}.xml", root_directory, game_id.substr(0, 4)));
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_loadConfigImpl(
JNIEnv* env, jobject obj, jstring j_game_id, jint revision, jint disc_number)
{
const std::string game_id = GetJString(env, j_game_id);
auto& discs = GetReference(env, obj);
const std::string& riivolution_dir = File::GetUserPath(D_RIIVOLUTION_IDX);
const auto config = LoadConfigXML(riivolution_dir, game_id);
discs.clear();
for (const std::string& path : Common::DoFileSearch({riivolution_dir + "riivolution"}, {".xml"}))
{
auto parsed = DiscIO::Riivolution::ParseFile(path);
if (!parsed || !parsed->IsValidForGame(game_id, revision, disc_number))
continue;
if (config)
DiscIO::Riivolution::ApplyConfigDefaults(&*parsed, *config);
discs.emplace_back(std::move(*parsed));
}
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_riivolution_model_RiivolutionPatches_saveConfigImpl(
JNIEnv* env, jobject obj, jstring j_game_id)
{
const std::string game_id = GetJString(env, j_game_id);
if (!(game_id.size() == 4 || game_id.size() == 6))
return;
DiscIO::Riivolution::Config config;
for (const auto& disc : GetReference(env, obj))
{
for (const auto& section : disc.m_sections)
{
for (const auto& option : section.m_options)
{
std::string id = option.m_id.empty() ? (section.m_name + option.m_name) : option.m_id;
config.m_options.emplace_back(
DiscIO::Riivolution::ConfigOption{std::move(id), option.m_selected_choice});
}
}
}
const std::string& root = File::GetUserPath(D_RIIVOLUTION_IDX);
DiscIO::Riivolution::WriteConfigFile(
fmt::format("{}/riivolution/config/{}.xml", root, game_id.substr(0, 4)), config);
}
}

View File

@ -9,8 +9,10 @@
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include <fmt/format.h>
#include <pugixml.hpp> #include <pugixml.hpp>
#include "Common/FileSearch.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/IOFile.h" #include "Common/IOFile.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
@ -354,7 +356,7 @@ std::vector<Patch> GenerateRiivolutionPatchesFromGameModDescriptor(
{ {
for (auto& option : section.m_options) for (auto& option : section.m_options)
{ {
const auto* info = [&]() -> const DiscIO::GameModDescriptorRiivolutionPatchOption* { const auto* info = [&]() -> const GameModDescriptorRiivolutionPatchOption* {
for (const auto& o : patch_info.options) for (const auto& o : patch_info.options)
{ {
if (o.section_name == section.m_name) if (o.section_name == section.m_name)
@ -374,14 +376,47 @@ std::vector<Patch> GenerateRiivolutionPatchesFromGameModDescriptor(
for (auto& p : parsed->GeneratePatches(game_id)) for (auto& p : parsed->GeneratePatches(game_id))
{ {
p.m_file_data_loader = std::make_shared<DiscIO::Riivolution::FileDataLoaderHostFS>( p.m_file_data_loader =
patch_info.root, parsed->m_xml_path, p.m_root); std::make_shared<FileDataLoaderHostFS>(patch_info.root, parsed->m_xml_path, p.m_root);
result.emplace_back(std::move(p)); result.emplace_back(std::move(p));
} }
} }
return result; return result;
} }
std::vector<Patch> GenerateRiivolutionPatchesFromConfig(const std::string root_directory,
const std::string& game_id,
std::optional<u16> revision,
std::optional<u8> disc_number)
{
std::vector<Patch> result;
// The way Riivolution stores settings only makes sense for standard game IDs.
if (!(game_id.size() == 4 || game_id.size() == 6))
return result;
const std::optional<Config> config = ParseConfigFile(
fmt::format("{}/riivolution/config/{}.xml", root_directory, game_id.substr(0, 4)));
for (const std::string& path : Common::DoFileSearch({root_directory + "riivolution"}, {".xml"}))
{
std::optional<Disc> parsed = ParseFile(path);
if (!parsed || !parsed->IsValidForGame(game_id, revision, disc_number))
continue;
if (config)
ApplyConfigDefaults(&*parsed, *config);
for (auto& patch : parsed->GeneratePatches(game_id))
{
patch.m_file_data_loader =
std::make_shared<FileDataLoaderHostFS>(root_directory, parsed->m_xml_path, patch.m_root);
result.emplace_back(std::move(patch));
}
}
return result;
}
std::optional<Config> ParseConfigFile(const std::string& filename) std::optional<Config> ParseConfigFile(const std::string& filename)
{ {
::File::IOFile f(filename, "rb"); ::File::IOFile f(filename, "rb");
@ -460,7 +495,7 @@ void ApplyConfigDefaults(Disc* disc, const Config& config)
{ {
for (const auto& config_option : config.m_options) for (const auto& config_option : config.m_options)
{ {
auto* matching_option = [&]() -> DiscIO::Riivolution::Option* { auto* matching_option = [&]() -> Option* {
for (auto& section : disc->m_sections) for (auto& section : disc->m_sections)
{ {
for (auto& option : section.m_options) for (auto& option : section.m_options)

View File

@ -143,7 +143,6 @@ struct Memory
std::vector<u8> m_original; std::vector<u8> m_original;
// If true, this memory patch is an ocarina-style patch. // If true, this memory patch is an ocarina-style patch.
// TODO: I'm unsure what this means exactly, need to check some examples...
bool m_ocarina = false; bool m_ocarina = false;
// If true, the offset is not known, and instead we should search for the m_original bytes in // If true, the offset is not known, and instead we should search for the m_original bytes in
@ -223,6 +222,10 @@ std::optional<Disc> ParseString(std::string_view xml, std::string xml_path);
std::vector<Patch> GenerateRiivolutionPatchesFromGameModDescriptor( std::vector<Patch> GenerateRiivolutionPatchesFromGameModDescriptor(
const GameModDescriptorRiivolution& descriptor, const std::string& game_id, const GameModDescriptorRiivolution& descriptor, const std::string& game_id,
std::optional<u16> revision, std::optional<u8> disc_number); std::optional<u16> revision, std::optional<u8> disc_number);
std::vector<Patch> GenerateRiivolutionPatchesFromConfig(const std::string root_directory,
const std::string& game_id,
std::optional<u16> revision,
std::optional<u8> disc_number);
std::optional<Config> ParseConfigFile(const std::string& filename); std::optional<Config> ParseConfigFile(const std::string& filename);
std::optional<Config> ParseConfigString(std::string_view xml); std::optional<Config> ParseConfigString(std::string_view xml);
std::string WriteConfigString(const Config& config); std::string WriteConfigString(const Config& config);