mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-13 09:51:31 +02:00
Android: Add Gecko code downloading
This commit is contained in:
parent
47efd3317d
commit
53ae1a0725
@ -29,7 +29,8 @@ public class GamePropertiesDialog extends DialogFragment
|
||||
{
|
||||
public static final String TAG = "GamePropertiesDialog";
|
||||
private static final String ARG_PATH = "path";
|
||||
private static final String ARG_GAMEID = "game_id";
|
||||
private static final String ARG_GAME_ID = "game_id";
|
||||
private static final String ARG_GAMETDB_ID = "gametdb_id";
|
||||
public static final String ARG_REVISION = "revision";
|
||||
private static final String ARG_PLATFORM = "platform";
|
||||
private static final String ARG_SHOULD_ALLOW_CONVERSION = "should_allow_conversion";
|
||||
@ -40,7 +41,8 @@ public class GamePropertiesDialog extends DialogFragment
|
||||
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(ARG_PATH, gameFile.getPath());
|
||||
arguments.putString(ARG_GAMEID, gameFile.getGameId());
|
||||
arguments.putString(ARG_GAME_ID, gameFile.getGameId());
|
||||
arguments.putString(ARG_GAMETDB_ID, gameFile.getGameTdbId());
|
||||
arguments.putInt(ARG_REVISION, gameFile.getRevision());
|
||||
arguments.putInt(ARG_PLATFORM, gameFile.getPlatform());
|
||||
arguments.putBoolean(ARG_SHOULD_ALLOW_CONVERSION, gameFile.shouldAllowConversion());
|
||||
@ -54,7 +56,8 @@ public class GamePropertiesDialog extends DialogFragment
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
final String path = requireArguments().getString(ARG_PATH);
|
||||
final String gameId = requireArguments().getString(ARG_GAMEID);
|
||||
final String gameId = requireArguments().getString(ARG_GAME_ID);
|
||||
final String gameTdbId = requireArguments().getString(ARG_GAMETDB_ID);
|
||||
final int revision = requireArguments().getInt(ARG_REVISION);
|
||||
final int platform = requireArguments().getInt(ARG_PLATFORM);
|
||||
final boolean shouldAllowConversion =
|
||||
@ -93,7 +96,7 @@ public class GamePropertiesDialog extends DialogFragment
|
||||
SettingsActivity.launch(getContext(), MenuTag.SETTINGS, gameId, revision, isWii));
|
||||
|
||||
itemsBuilder.add(R.string.properties_edit_cheats, (dialog, i) ->
|
||||
CheatsActivity.launch(getContext(), gameId, revision, isWii));
|
||||
CheatsActivity.launch(getContext(), gameId, gameTdbId, revision, isWii));
|
||||
|
||||
itemsBuilder.add(R.string.properties_clear_game_settings, (dialog, i) ->
|
||||
clearGameSettings(gameId));
|
||||
|
@ -21,6 +21,7 @@ public class CheatsViewModel extends ViewModel
|
||||
private final MutableLiveData<Integer> mCheatAddedEvent = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Integer> mCheatChangedEvent = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Integer> mCheatDeletedEvent = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Integer> mGeckoCheatsDownloadedEvent = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Boolean> mOpenDetailsViewEvent = new MutableLiveData<>(false);
|
||||
|
||||
private ArrayList<PatchCheat> mPatchCheats;
|
||||
@ -236,6 +237,38 @@ public class CheatsViewModel extends ViewModel
|
||||
mCheatDeletedEvent.setValue(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* When Gecko cheats are downloaded, the integer stored in the returned LiveData
|
||||
* changes to the number of cheats added, then changes back to null.
|
||||
*/
|
||||
public LiveData<Integer> getGeckoCheatsDownloadedEvent()
|
||||
{
|
||||
return mGeckoCheatsDownloadedEvent;
|
||||
}
|
||||
|
||||
public int addDownloadedGeckoCodes(GeckoCheat[] cheats)
|
||||
{
|
||||
int cheatsAdded = 0;
|
||||
|
||||
for (GeckoCheat cheat : cheats)
|
||||
{
|
||||
if (!mGeckoCheats.contains(cheat))
|
||||
{
|
||||
mGeckoCheats.add(cheat);
|
||||
cheatsAdded++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cheatsAdded != 0)
|
||||
{
|
||||
mGeckoCheatsNeedSaving = true;
|
||||
mGeckoCheatsDownloadedEvent.setValue(cheatsAdded);
|
||||
mGeckoCheatsDownloadedEvent.setValue(null);
|
||||
}
|
||||
|
||||
return cheatsAdded;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getOpenDetailsViewEvent()
|
||||
{
|
||||
return mOpenDetailsViewEvent;
|
||||
|
@ -4,6 +4,7 @@ package org.dolphinemu.dolphinemu.features.cheats.model;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class GeckoCheat extends AbstractCheat
|
||||
{
|
||||
@ -26,6 +27,12 @@ public class GeckoCheat extends AbstractCheat
|
||||
|
||||
private native long createNew();
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj)
|
||||
{
|
||||
return obj != null && getClass() == obj.getClass() && equalsImpl((GeckoCheat) obj);
|
||||
}
|
||||
|
||||
public boolean supportsCreator()
|
||||
{
|
||||
return true;
|
||||
@ -52,6 +59,8 @@ public class GeckoCheat extends AbstractCheat
|
||||
|
||||
public native boolean getEnabled();
|
||||
|
||||
public native boolean equalsImpl(@NonNull GeckoCheat other);
|
||||
|
||||
@Override
|
||||
protected native int trySetImpl(@NonNull String name, @NonNull String creator,
|
||||
@NonNull String notes, @NonNull String code);
|
||||
@ -63,4 +72,7 @@ public class GeckoCheat extends AbstractCheat
|
||||
public static native GeckoCheat[] loadCodes(String gameId, int revision);
|
||||
|
||||
public static native void saveCodes(String gameId, int revision, GeckoCheat[] codes);
|
||||
|
||||
@Nullable
|
||||
public static native GeckoCheat[] downloadCodes(String gameTdbId);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.features.cheats.model.ARCheat;
|
||||
@ -17,6 +18,7 @@ public class ActionViewHolder extends CheatItemViewHolder implements View.OnClic
|
||||
{
|
||||
private final TextView mName;
|
||||
|
||||
private CheatsActivity mActivity;
|
||||
private CheatsViewModel mViewModel;
|
||||
private int mString;
|
||||
private int mPosition;
|
||||
@ -30,9 +32,10 @@ public class ActionViewHolder extends CheatItemViewHolder implements View.OnClic
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
public void bind(CheatsViewModel viewModel, CheatItem item, int position)
|
||||
public void bind(CheatsActivity activity, CheatItem item, int position)
|
||||
{
|
||||
mViewModel = viewModel;
|
||||
mActivity = activity;
|
||||
mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
|
||||
mString = item.getString();
|
||||
mPosition = position;
|
||||
|
||||
@ -56,5 +59,9 @@ public class ActionViewHolder extends CheatItemViewHolder implements View.OnClic
|
||||
mViewModel.startAddingCheat(new PatchCheat(), mPosition);
|
||||
mViewModel.openDetailsView();
|
||||
}
|
||||
else if (mString == R.string.cheats_download_gecko)
|
||||
{
|
||||
mActivity.downloadGeckoCodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,5 +16,5 @@ public abstract class CheatItemViewHolder extends RecyclerView.ViewHolder
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public abstract void bind(CheatsViewModel viewModel, CheatItem item, int position);
|
||||
public abstract void bind(CheatsActivity activity, CheatItem item, int position);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import android.widget.CompoundButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
@ -34,11 +35,11 @@ public class CheatViewHolder extends CheatItemViewHolder
|
||||
mCheckbox = itemView.findViewById(R.id.checkbox);
|
||||
}
|
||||
|
||||
public void bind(CheatsViewModel viewModel, CheatItem item, int position)
|
||||
public void bind(CheatsActivity activity, CheatItem item, int position)
|
||||
{
|
||||
mCheckbox.setOnCheckedChangeListener(null);
|
||||
|
||||
mViewModel = viewModel;
|
||||
mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
|
||||
mCheat = item.getCheat();
|
||||
mPosition = position;
|
||||
|
||||
|
@ -9,6 +9,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@ -17,6 +18,7 @@ import androidx.slidingpanelayout.widget.SlidingPaneLayout;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat;
|
||||
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel;
|
||||
import org.dolphinemu.dolphinemu.features.cheats.model.GeckoCheat;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
import org.dolphinemu.dolphinemu.ui.TwoPaneOnBackPressedCallback;
|
||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
|
||||
@ -25,10 +27,12 @@ public class CheatsActivity extends AppCompatActivity
|
||||
implements SlidingPaneLayout.PanelSlideListener
|
||||
{
|
||||
private static final String ARG_GAME_ID = "game_id";
|
||||
private static final String ARG_GAMETDB_ID = "gametdb_id";
|
||||
private static final String ARG_REVISION = "revision";
|
||||
private static final String ARG_IS_WII = "is_wii";
|
||||
|
||||
private String mGameId;
|
||||
private String mGameTdbId;
|
||||
private int mRevision;
|
||||
private boolean mIsWii;
|
||||
private CheatsViewModel mViewModel;
|
||||
@ -40,10 +44,12 @@ public class CheatsActivity extends AppCompatActivity
|
||||
private View mCheatListLastFocus;
|
||||
private View mCheatDetailsLastFocus;
|
||||
|
||||
public static void launch(Context context, String gameId, int revision, boolean isWii)
|
||||
public static void launch(Context context, String gameId, String gameTdbId, int revision,
|
||||
boolean isWii)
|
||||
{
|
||||
Intent intent = new Intent(context, CheatsActivity.class);
|
||||
intent.putExtra(ARG_GAME_ID, gameId);
|
||||
intent.putExtra(ARG_GAMETDB_ID, gameTdbId);
|
||||
intent.putExtra(ARG_REVISION, revision);
|
||||
intent.putExtra(ARG_IS_WII, isWii);
|
||||
context.startActivity(intent);
|
||||
@ -58,6 +64,7 @@ public class CheatsActivity extends AppCompatActivity
|
||||
|
||||
Intent intent = getIntent();
|
||||
mGameId = intent.getStringExtra(ARG_GAME_ID);
|
||||
mGameTdbId = intent.getStringExtra(ARG_GAMETDB_ID);
|
||||
mRevision = intent.getIntExtra(ARG_REVISION, 0);
|
||||
mIsWii = intent.getBooleanExtra(ARG_IS_WII, true);
|
||||
|
||||
@ -161,6 +168,49 @@ public class CheatsActivity extends AppCompatActivity
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void downloadGeckoCodes()
|
||||
{
|
||||
AlertDialog progressDialog = new AlertDialog.Builder(this, R.style.DolphinDialogBase).create();
|
||||
progressDialog.setTitle(R.string.cheats_downloading);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.show();
|
||||
|
||||
new Thread(() ->
|
||||
{
|
||||
GeckoCheat[] codes = GeckoCheat.downloadCodes(mGameTdbId);
|
||||
|
||||
runOnUiThread(() ->
|
||||
{
|
||||
progressDialog.dismiss();
|
||||
|
||||
if (codes == null)
|
||||
{
|
||||
new AlertDialog.Builder(this, R.style.DolphinDialogBase)
|
||||
.setMessage(getString(R.string.cheats_download_failed))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
else if (codes.length == 0)
|
||||
{
|
||||
new AlertDialog.Builder(this, R.style.DolphinDialogBase)
|
||||
.setMessage(getString(R.string.cheats_download_empty))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
int cheatsAdded = mViewModel.addDownloadedGeckoCodes(codes);
|
||||
String message = getString(R.string.cheats_download_succeeded, codes.length, cheatsAdded);
|
||||
|
||||
new AlertDialog.Builder(this, R.style.DolphinDialogBase)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void setOnFocusChangeListenerRecursively(@NonNull View view,
|
||||
View.OnFocusChangeListener listener)
|
||||
{
|
||||
|
@ -45,6 +45,16 @@ public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
|
||||
if (position != null)
|
||||
notifyItemRemoved(position);
|
||||
});
|
||||
|
||||
mViewModel.getGeckoCheatsDownloadedEvent().observe(activity, (cheatsAdded) ->
|
||||
{
|
||||
if (cheatsAdded != null)
|
||||
{
|
||||
int positionEnd = getItemCount() - 2; // Skip "Add Gecko Code" and "Download Gecko Codes"
|
||||
int positionStart = positionEnd - cheatsAdded;
|
||||
notifyItemRangeInserted(positionStart, cheatsAdded);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -75,14 +85,14 @@ public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull CheatItemViewHolder holder, int position)
|
||||
{
|
||||
holder.bind(mViewModel, getItemAt(position), position);
|
||||
holder.bind(mActivity, getItemAt(position), position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount()
|
||||
{
|
||||
return mViewModel.getARCheats().size() + mViewModel.getGeckoCheats().size() +
|
||||
mViewModel.getPatchCheats().size() + 6;
|
||||
mViewModel.getPatchCheats().size() + 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -144,6 +154,9 @@ public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
|
||||
return new CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_add_gecko);
|
||||
position -= 1;
|
||||
|
||||
if (position == 0)
|
||||
return new CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_download_gecko);
|
||||
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ public class HeaderViewHolder extends CheatItemViewHolder
|
||||
mHeaderName = itemView.findViewById(R.id.text_header_name);
|
||||
}
|
||||
|
||||
public void bind(CheatsViewModel viewModel, CheatItem item, int position)
|
||||
public void bind(CheatsActivity activity, CheatItem item, int position)
|
||||
{
|
||||
mHeaderName.setText(item.getString());
|
||||
}
|
||||
|
@ -395,6 +395,7 @@
|
||||
<string name="cheats_add_ar">Add New AR Code</string>
|
||||
<string name="cheats_add_gecko">Add New Gecko Code</string>
|
||||
<string name="cheats_add_patch">Add New Patch</string>
|
||||
<string name="cheats_download_gecko">Download Gecko Codes</string>
|
||||
<string name="cheats_name">Name</string>
|
||||
<string name="cheats_creator">Creator</string>
|
||||
<string name="cheats_notes">Notes</string>
|
||||
@ -406,6 +407,10 @@
|
||||
<string name="cheats_error_no_code_lines">Code can\'t be empty</string>
|
||||
<string name="cheats_error_on_line">Error on line %1$d</string>
|
||||
<string name="cheats_error_mixed_encryption">Lines must either be all encrypted or all decrypted</string>
|
||||
<string name="cheats_downloading">Downloading...</string>
|
||||
<string name="cheats_download_failed">Failed to download codes.</string>
|
||||
<string name="cheats_download_empty">File contained no codes.</string>
|
||||
<string name="cheats_download_succeeded">Downloaded %1$d codes. (added %2$d)</string>
|
||||
<string name="cheats_disabled_warning">Dolphin\'s cheat system is currently disabled.</string>
|
||||
<string name="cheats_open_settings">Settings</string>
|
||||
|
||||
|
@ -92,6 +92,13 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getEnabled(JNIEn
|
||||
return static_cast<jboolean>(GetPointer(env, obj)->enabled);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_equalsImpl(JNIEnv* env, jobject obj,
|
||||
jobject other)
|
||||
{
|
||||
return *GetPointer(env, obj) == *GetPointer(env, other);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_trySetImpl(
|
||||
JNIEnv* env, jobject obj, jstring name, jstring creator, jstring notes, jstring code_string)
|
||||
{
|
||||
@ -180,4 +187,26 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_Geck
|
||||
Gecko::SaveCodes(game_ini_local, vector);
|
||||
game_ini_local.Save(ini_path);
|
||||
}
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_downloadCodes(JNIEnv* env, jclass,
|
||||
jstring jGameTdbId)
|
||||
{
|
||||
const std::string gametdb_id = GetJString(env, jGameTdbId);
|
||||
|
||||
bool success = true;
|
||||
const std::vector<Gecko::GeckoCode> codes = Gecko::DownloadCodes(gametdb_id, &success, false);
|
||||
|
||||
if (!success)
|
||||
return nullptr;
|
||||
|
||||
const jobjectArray array =
|
||||
env->NewObjectArray(static_cast<jsize>(codes.size()), IDCache::GetGeckoCheatClass(), nullptr);
|
||||
|
||||
jsize i = 0;
|
||||
for (const Gecko::GeckoCode& code : codes)
|
||||
env->SetObjectArrayElement(array, i++, GeckoCheatToJava(env, code));
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,13 @@
|
||||
|
||||
namespace Gecko
|
||||
{
|
||||
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded)
|
||||
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded, bool use_https)
|
||||
{
|
||||
// TODO: Fix https://bugs.dolphin-emu.org/issues/11772 so we don't need this workaround
|
||||
const std::string protocol = use_https ? "https://" : "http://";
|
||||
|
||||
// codes.rc24.xyz is a mirror of the now defunct geckocodes.org.
|
||||
std::string endpoint{"https://codes.rc24.xyz/txt.php?txt=" + gametdb_id};
|
||||
std::string endpoint{protocol + "codes.rc24.xyz/txt.php?txt=" + gametdb_id};
|
||||
Common::HttpRequest http;
|
||||
|
||||
// The server always redirects once to the same location.
|
||||
|
@ -14,7 +14,8 @@ class IniFile;
|
||||
namespace Gecko
|
||||
{
|
||||
std::vector<GeckoCode> LoadCodes(const IniFile& globalIni, const IniFile& localIni);
|
||||
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded);
|
||||
std::vector<GeckoCode> DownloadCodes(std::string gametdb_id, bool* succeeded,
|
||||
bool use_https = true);
|
||||
void SaveCodes(IniFile& inifile, const std::vector<GeckoCode>& gcodes);
|
||||
|
||||
std::optional<GeckoCode::Code> DeserializeLine(const std::string& line);
|
||||
|
Loading…
x
Reference in New Issue
Block a user