Merge pull request #9452 from JosJuice/android-wii-saves

Android: Add "Import Wii Save"
This commit is contained in:
Léo Lam 2021-01-27 23:00:47 +01:00 committed by GitHub
commit 96e3360f05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 324 additions and 60 deletions

View File

@ -444,8 +444,6 @@ public final class NativeLibrary
public static native void ReloadLoggerConfig();
public static native boolean InstallWAD(String file);
public static native boolean ConvertDiscImage(String inPath, String outPath, int platform,
int format, int blockSize, int compression, int compressionLevel, boolean scrub,
CompressCallback callback);

View File

@ -175,21 +175,12 @@ public final class MainActivity extends AppCompatActivity implements MainView
}
@Override
public void launchOpenFileActivity()
public void launchOpenFileActivity(int requestCode)
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, MainPresenter.REQUEST_GAME_FILE);
}
@Override
public void launchInstallWAD()
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, MainPresenter.REQUEST_WAD_FILE);
startActivityForResult(intent, requestCode);
}
/**
@ -229,6 +220,11 @@ public final class MainActivity extends AppCompatActivity implements MainView
FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.WAD_EXTENSION,
() -> mPresenter.installWAD(result.getData().toString()));
break;
case MainPresenter.REQUEST_WII_SAVE_FILE:
FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.BIN_EXTENSION,
() -> mPresenter.importWiiSave(result.getData().toString()));
break;
}
}
else

View File

@ -7,24 +7,26 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.dolphinemu.dolphinemu.BuildConfig;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
import org.dolphinemu.dolphinemu.model.GameFileCache;
import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
import org.dolphinemu.dolphinemu.utils.BooleanSupplier;
import org.dolphinemu.dolphinemu.utils.CompletableFuture;
import org.dolphinemu.dolphinemu.utils.ContentHandler;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.WiiUtils;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public final class MainPresenter
{
@ -32,6 +34,7 @@ public final class MainPresenter
public static final int REQUEST_GAME_FILE = 2;
public static final int REQUEST_SD_FILE = 3;
public static final int REQUEST_WAD_FILE = 4;
public static final int REQUEST_WII_SAVE_FILE = 5;
private final MainView mView;
private final Context mContext;
@ -92,11 +95,17 @@ public final class MainPresenter
return true;
case R.id.menu_open_file:
mView.launchOpenFileActivity();
mView.launchOpenFileActivity(REQUEST_GAME_FILE);
return true;
case R.id.menu_install_wad:
new AfterDirectoryInitializationRunner().run(context, true, mView::launchInstallWAD);
new AfterDirectoryInitializationRunner().run(context, true,
() -> mView.launchOpenFileActivity(REQUEST_WAD_FILE));
return true;
case R.id.menu_import_wii_save:
new AfterDirectoryInitializationRunner().run(context, true,
() -> mView.launchOpenFileActivity(REQUEST_WII_SAVE_FILE));
return true;
}
@ -150,32 +159,98 @@ public final class MainPresenter
mDirToAdd = uri.toString();
}
public void installWAD(String file)
public void installWAD(String path)
{
runOnThreadAndShowResult(R.string.import_in_progress, () ->
{
boolean success = WiiUtils.installWAD(path);
int message = success ? R.string.wad_install_success : R.string.wad_install_failure;
return mContext.getResources().getString(message);
});
}
public void importWiiSave(String path)
{
final Activity mainPresenterActivity = (Activity) mContext;
AlertDialog dialog = new AlertDialog.Builder(mContext, R.style.DolphinDialogBase).create();
dialog.setTitle("Installing WAD");
dialog.setMessage("Installing...");
dialog.setCancelable(false);
dialog.show();
CompletableFuture<Boolean> canOverwriteFuture = new CompletableFuture<>();
Thread installWADThread = new Thread(() ->
runOnThreadAndShowResult(R.string.import_in_progress, () ->
{
if (NativeLibrary.InstallWAD(file))
BooleanSupplier canOverwrite = () ->
{
mainPresenterActivity.runOnUiThread(
() -> Toast.makeText(mContext, R.string.wad_install_success, Toast.LENGTH_SHORT)
.show());
mainPresenterActivity.runOnUiThread(() ->
{
AlertDialog.Builder builder =
new AlertDialog.Builder(mContext, R.style.DolphinDialogBase);
builder.setMessage(R.string.wii_save_exists);
builder.setCancelable(false);
builder.setPositiveButton(R.string.yes, (dialog, i) -> canOverwriteFuture.complete(true));
builder.setNegativeButton(R.string.no, (dialog, i) -> canOverwriteFuture.complete(false));
builder.show();
});
try
{
return canOverwriteFuture.get();
}
else
catch (ExecutionException | InterruptedException e)
{
mainPresenterActivity.runOnUiThread(
() -> Toast.makeText(mContext, R.string.wad_install_failure, Toast.LENGTH_SHORT)
.show());
// Shouldn't happen
throw new RuntimeException(e);
}
mainPresenterActivity.runOnUiThread(dialog::dismiss);
}, "InstallWAD");
installWADThread.start();
};
int result = WiiUtils.importWiiSave(path, canOverwrite);
int message;
switch (result)
{
case WiiUtils.RESULT_SUCCESS:
message = R.string.wii_save_import_success;
break;
case WiiUtils.RESULT_CORRUPTED_SOURCE:
message = R.string.wii_save_import_corruped_source;
break;
case WiiUtils.RESULT_TITLE_MISSING:
message = R.string.wii_save_import_title_missing;
break;
case WiiUtils.RESULT_CANCELLED:
return null;
default:
message = R.string.wii_save_import_error;
break;
}
return mContext.getResources().getString(message);
});
}
private void runOnThreadAndShowResult(int progressMessage, Supplier<String> f)
{
final Activity mainPresenterActivity = (Activity) mContext;
AlertDialog progressDialog = new AlertDialog.Builder(mContext, R.style.DolphinDialogBase)
.create();
progressDialog.setTitle(progressMessage);
progressDialog.setCancelable(false);
progressDialog.show();
new Thread(() ->
{
String result = f.get();
mainPresenterActivity.runOnUiThread(() ->
{
progressDialog.dismiss();
if (result != null)
{
AlertDialog.Builder builder =
new AlertDialog.Builder(mContext, R.style.DolphinDialogBase);
builder.setMessage(result);
builder.setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss());
builder.show();
}
});
}, mContext.getResources().getString(progressMessage)).start();
}
}

View File

@ -21,9 +21,7 @@ public interface MainView
void launchFileListActivity();
void launchOpenFileActivity();
void launchInstallWAD();
void launchOpenFileActivity(int requestCode);
/**
* To be called when the game file cache is updated.

View File

@ -180,21 +180,12 @@ public final class TvMainActivity extends FragmentActivity implements MainView
}
@Override
public void launchOpenFileActivity()
public void launchOpenFileActivity(int requestCode)
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, MainPresenter.REQUEST_GAME_FILE);
}
@Override
public void launchInstallWAD()
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, MainPresenter.REQUEST_WAD_FILE);
startActivityForResult(intent, requestCode);
}
@Override
@ -253,6 +244,11 @@ public final class TvMainActivity extends FragmentActivity implements MainView
FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.WAD_EXTENSION,
() -> mPresenter.installWAD(result.getData().toString()));
break;
case MainPresenter.REQUEST_WII_SAVE_FILE:
FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.BIN_EXTENSION,
() -> mPresenter.importWiiSave(result.getData().toString()));
break;
}
}
else
@ -353,6 +349,10 @@ public final class TvMainActivity extends FragmentActivity implements MainView
R.drawable.ic_folder,
R.string.grid_menu_install_wad));
rowItems.add(new TvSettingsItem(R.id.menu_import_wii_save,
R.drawable.ic_folder,
R.string.grid_menu_import_wii_save));
// Create a header for this row.
HeaderItem header =
new HeaderItem(R.string.preferences_settings, getString(R.string.preferences_settings));

View File

@ -0,0 +1,6 @@
package org.dolphinemu.dolphinemu.utils;
public interface BooleanSupplier
{
boolean get();
}

View File

@ -0,0 +1,95 @@
package org.dolphinemu.dolphinemu.utils;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Simplified re-implementation of a subset of {@link java.util.concurrent.CompletableFuture}.
* Replace this class with that class once we have full Java 8 support (once we require API 24).
*/
public class CompletableFuture<T> implements Future<T>
{
private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
private boolean isDone = false;
private T result = null;
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled()
{
return false;
}
@Override
public boolean isDone()
{
return isDone;
}
@Override
public T get() throws ExecutionException, InterruptedException
{
lock.lock();
try
{
while (!isDone)
done.await();
return result;
}
finally
{
lock.unlock();
}
}
@Override
public T get(long timeout, TimeUnit unit)
throws ExecutionException, InterruptedException, TimeoutException
{
lock.lock();
try
{
while (!isDone)
{
if (!done.await(timeout, unit))
throw new TimeoutException();
}
return result;
}
finally
{
lock.unlock();
}
}
public boolean complete(T value)
{
lock.lock();
try
{
boolean wasDone = isDone;
result = value;
isDone = true;
done.signalAll();
return !wasDone;
}
finally
{
lock.unlock();
}
}
}

View File

@ -37,6 +37,9 @@ public final class FileBrowserHelper
GAME_LIKE_EXTENSIONS.add("dff");
}
public static final HashSet<String> BIN_EXTENSION = new HashSet<>(Collections.singletonList(
"bin"));
public static final HashSet<String> RAW_EXTENSION = new HashSet<>(Collections.singletonList(
"raw"));

View File

@ -0,0 +1,14 @@
package org.dolphinemu.dolphinemu.utils;
public final class WiiUtils
{
public static final int RESULT_SUCCESS = 0;
public static final int RESULT_ERROR = 1;
public static final int RESULT_CANCELLED = 2;
public static final int RESULT_CORRUPTED_SOURCE = 3;
public static final int RESULT_TITLE_MISSING = 4;
public static native boolean installWAD(String file);
public static native int importWiiSave(String file, BooleanSupplier canOverwrite);
}

View File

@ -25,4 +25,9 @@
android:title="@string/grid_menu_install_wad"
app:showAsAction="never"/>
<item
android:id="@+id/menu_import_wii_save"
android:title="@string/grid_menu_import_wii_save"
app:showAsAction="never"/>
</menu>

View File

@ -329,8 +329,15 @@
<string name="grid_menu_refresh">Refresh Library</string>
<string name="grid_menu_open_file">Open File</string>
<string name="grid_menu_install_wad">Install WAD</string>
<string name="grid_menu_import_wii_save">Import Wii Save</string>
<string name="import_in_progress">Importing...</string>
<string name="wad_install_success">Successfully installed this title to the NAND.</string>
<string name="wad_install_failure">Failed to install this title to the NAND.</string>
<string name="wii_save_exists">Save data for this title already exists in the NAND. Consider backing up the current data before overwriting.\nOverwrite now?</string>
<string name="wii_save_import_success">Successfully imported save file.</string>
<string name="wii_save_import_error">Failed to import save file. Your NAND may be corrupt, or something is preventing access to files within it.</string>
<string name="wii_save_import_corruped_source">Failed to import save file. The given file appears to be corrupted or is not a valid Wii save.</string>
<string name="wii_save_import_title_missing">Failed to import save file. Please launch the game once, then try again.</string>
<!-- Game Properties Screen -->
<string name="properties_details">Details</string>

View File

@ -56,6 +56,9 @@ static jmethodID s_network_helper_get_network_ip_address;
static jmethodID s_network_helper_get_network_prefix_length;
static jmethodID s_network_helper_get_network_gateway;
static jclass s_boolean_supplier_class;
static jmethodID s_boolean_supplier_get;
namespace IDCache
{
JNIEnv* GetEnvForThread()
@ -261,6 +264,11 @@ jmethodID GetNetworkHelperGetNetworkGateway()
return s_network_helper_get_network_gateway;
}
jmethodID GetBooleanSupplierGet()
{
return s_boolean_supplier_get;
}
} // namespace IDCache
#ifdef __cplusplus
@ -361,6 +369,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
s_network_helper_get_network_gateway =
env->GetStaticMethodID(s_network_helper_class, "GetNetworkGateway", "()I");
const jclass boolean_supplier_class =
env->FindClass("org/dolphinemu/dolphinemu/utils/BooleanSupplier");
s_boolean_supplier_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_supplier_class));
s_boolean_supplier_get = env->GetMethodID(s_boolean_supplier_class, "get", "()Z");
return JNI_VERSION;
}

View File

@ -56,4 +56,6 @@ jmethodID GetNetworkHelperGetNetworkIpAddress();
jmethodID GetNetworkHelperGetNetworkPrefixLength();
jmethodID GetNetworkHelperGetNetworkGateway();
jmethodID GetBooleanSupplierGet();
} // namespace IDCache

View File

@ -5,6 +5,7 @@ add_library(main SHARED
IniFile.cpp
MainAndroid.cpp
NativeConfig.cpp
WiiUtils.cpp
)
target_link_libraries(main

View File

@ -45,7 +45,6 @@
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/Profiler.h"
#include "Core/State.h"
#include "Core/WiiUtils.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Enums.h"
@ -596,14 +595,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ReloadLogger
Common::Log::LogManager::Init();
}
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_InstallWAD(JNIEnv* env,
jclass,
jstring jFile)
{
const std::string path = GetJString(env, jFile);
return static_cast<jboolean>(WiiUtils::InstallWAD(path));
}
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ConvertDiscImage(
JNIEnv* env, jclass, jstring jInPath, jstring jOutPath, jint jPlatform, jint jFormat,
jint jBlockSize, jint jCompression, jint jCompressionLevel, jboolean jScrub, jobject jCallback)

View File

@ -0,0 +1,59 @@
// Copyright 2021 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <string>
#include <jni.h>
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
#include "Core/HW/WiiSave.h"
#include "Core/WiiUtils.h"
// The hardcoded values here must match WiiUtils.java
static jint ConvertCopyResult(WiiSave::CopyResult result)
{
switch (result)
{
case WiiSave::CopyResult::Success:
return 0;
case WiiSave::CopyResult::Error:
return 1;
case WiiSave::CopyResult::Cancelled:
return 2;
case WiiSave::CopyResult::CorruptedSource:
return 3;
case WiiSave::CopyResult::TitleMissing:
return 4;
default:
ASSERT(false);
return 1;
}
static_assert(static_cast<int>(WiiSave::CopyResult::NumberOfEntries) == 5);
}
extern "C" {
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_WiiUtils_installWAD(JNIEnv* env,
jclass,
jstring jFile)
{
const std::string path = GetJString(env, jFile);
return static_cast<jboolean>(WiiUtils::InstallWAD(path));
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_utils_WiiUtils_importWiiSave(
JNIEnv* env, jclass, jstring jFile, jobject jCanOverwrite)
{
const std::string path = GetJString(env, jFile);
const auto can_overwrite = [&] {
const jmethodID get = IDCache::GetBooleanSupplierGet();
return static_cast<bool>(env->CallBooleanMethod(jCanOverwrite, get));
};
return ConvertCopyResult(WiiSave::Import(path, can_overwrite));
}
}

View File

@ -39,6 +39,7 @@ enum class CopyResult
Cancelled,
CorruptedSource,
TitleMissing,
NumberOfEntries
};
CopyResult Copy(Storage* source, Storage* destination);