@@ -0,0 +1,34 @@
package org.dolphinemu.dolphinemu.features.sysupdate.ui;

import android.app.Dialog;
import android.os.Bundle;

import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;

import org.dolphinemu.dolphinemu.R;

public class SystemMenuNotInstalledDialogFragment extends DialogFragment
{
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
return new AlertDialog.Builder(requireContext(), R.style.DolphinDialogBase)
.setTitle(R.string.system_menu_not_installed_title)
.setMessage(R.string.system_menu_not_installed_message)
.setPositiveButton(R.string.yes, (dialog, which) ->
{
FragmentManager fragmentManager = getParentFragmentManager();
OnlineUpdateRegionSelectDialogFragment dialogFragment =
new OnlineUpdateRegionSelectDialogFragment();
dialogFragment.show(fragmentManager, "OnlineUpdateRegionSelectDialogFragment");
dismiss();
})
.setNegativeButton(R.string.no, (dialog, which) ->
{
dismiss();
})
.create();
}
}
@@ -0,0 +1,90 @@
package org.dolphinemu.dolphinemu.features.sysupdate.ui;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import org.dolphinemu.dolphinemu.utils.WiiUtils;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SystemUpdateViewModel extends ViewModel
{
private static final ExecutorService executor = Executors.newFixedThreadPool(1);

private final MutableLiveData<Integer> mProgressData = new MutableLiveData<>();
private final MutableLiveData<Integer> mTotalData = new MutableLiveData<>();
private final MutableLiveData<Long> mTitleIdData = new MutableLiveData<>();
private final MutableLiveData<Integer> mResultData = new MutableLiveData<>();

private boolean mCanceled = false;
private int mRegion;

public SystemUpdateViewModel()
{
clear();
}

public void setRegion(int region)
{
mRegion = region;
}

public int getRegion()
{
return mRegion;
}

public MutableLiveData<Integer> getProgressData()
{
return mProgressData;
}

public MutableLiveData<Integer> getTotalData()
{
return mTotalData;
}

public MutableLiveData<Long> getTitleIdData()
{
return mTitleIdData;
}

public MutableLiveData<Integer> getResultData()
{
return mResultData;
}

public void setCanceled()
{
mCanceled = true;
}

public void startUpdate(String region)
{
mCanceled = false;

executor.execute(() ->
{
int result = WiiUtils.doOnlineUpdate(region, ((processed, total, titleId) ->
{
mProgressData.postValue(processed);
mTotalData.postValue(total);
mTitleIdData.postValue(titleId);

return !mCanceled;
}));

mResultData.postValue(result);
});
}

public void clear()
{
mProgressData.setValue(0);
mTotalData.setValue(0);
mTitleIdData.setValue(0l);
mResultData.setValue(-1);
mCanceled = false;
}
}
Expand Up @@ -6,7 +6,6 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
Expand All @@ -30,21 +29,25 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
{
private static final String KEY_GAMEPATHS = "gamepaths";
private static final String KEY_RIIVOLUTION = "riivolution";
private static final String KEY_SYSTEM_MENU = "systemMenu";

private InputOverlay mInputOverlay;

private String[] mGamePaths;
private boolean mRiivolution;
private boolean mRunWhenSurfaceIsValid;
private boolean mLoadPreviousTemporaryState;
private boolean mLaunchSystemMenu;

private EmulationActivity activity;

public static EmulationFragment newInstance(String[] gamePaths, boolean riivolution)
public static EmulationFragment newInstance(String[] gamePaths, boolean riivolution,
boolean systemMenu)
{
Bundle args = new Bundle();
args.putStringArray(KEY_GAMEPATHS, gamePaths);
args.putBoolean(KEY_RIIVOLUTION, riivolution);
args.putBoolean(KEY_SYSTEM_MENU, systemMenu);

EmulationFragment fragment = new EmulationFragment();
fragment.setArguments(args);
Expand Down Expand Up @@ -77,6 +80,7 @@ public void onCreate(Bundle savedInstanceState)

mGamePaths = getArguments().getStringArray(KEY_GAMEPATHS);
mRiivolution = getArguments().getBoolean(KEY_RIIVOLUTION);
mLaunchSystemMenu = getArguments().getBoolean(KEY_SYSTEM_MENU);
}

/**
Expand Down Expand Up @@ -270,6 +274,11 @@ private void runWithValidSurface()
Log.debug("[EmulationFragment] Starting emulation thread from previous state.");
NativeLibrary.Run(mGamePaths, mRiivolution, getTemporaryStateFilePath(), true);
}
if (mLaunchSystemMenu)
{
Log.debug("[EmulationFragment] Starting emulation thread for the Wii Menu.");
NativeLibrary.RunSystemMenu();
}
else
{
Log.debug("[EmulationFragment] Starting emulation thread.");
Expand Down
Expand Up @@ -14,6 +14,7 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager.widget.ViewPager;

Expand All @@ -27,6 +28,9 @@
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
import org.dolphinemu.dolphinemu.features.sysupdate.ui.OnlineUpdateProgressBarDialogFragment;
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemMenuNotInstalledDialogFragment;
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateViewModel;
import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView;
Expand All @@ -36,6 +40,7 @@
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
import org.dolphinemu.dolphinemu.utils.StartupHandler;
import org.dolphinemu.dolphinemu.utils.WiiUtils;

/**
* The main Activity of the Lollipop style UI. Manages several PlatformGamesFragments, which
Expand Down Expand Up @@ -142,6 +147,14 @@ public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_game_grid, menu);

if (WiiUtils.isSystemMenuInstalled())
{
menu.findItem(R.id.menu_load_wii_system_menu).setTitle(
getString(R.string.grid_menu_load_wii_system_menu_installed,
WiiUtils.getSystemMenuVersion()));
}

return true;
}

Expand Down
Expand Up @@ -3,18 +3,23 @@
package org.dolphinemu.dolphinemu.ui.main;

import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;

import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ComponentActivity;
import androidx.activity.ComponentActivity;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import org.dolphinemu.dolphinemu.BuildConfig;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
import org.dolphinemu.dolphinemu.features.sysupdate.ui.OnlineUpdateProgressBarDialogFragment;
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemMenuNotInstalledDialogFragment;
import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateViewModel;
import org.dolphinemu.dolphinemu.model.GameFileCache;
import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
Expand All @@ -40,10 +45,10 @@
private static boolean sShouldRescanLibrary = true;

private final MainView mView;
private final ComponentActivity mActivity;
private final FragmentActivity mActivity;
private String mDirToAdd;

public MainPresenter(MainView view, ComponentActivity activity)
public MainPresenter(MainView view, FragmentActivity activity)
{
mView = view;
mActivity = activity;
Expand Down Expand Up @@ -94,6 +99,14 @@ public boolean handleOptionSelection(int itemId, ComponentActivity activity)
mView.launchOpenFileActivity(REQUEST_GAME_FILE);
return true;

case R.id.menu_load_wii_system_menu:
launchWiiSystemMenu();
return true;

case R.id.menu_online_system_update:
launchOnlineUpdate();
return true;

case R.id.menu_install_wad:
new AfterDirectoryInitializationRunner().runWithLifecycle(activity, true,
() -> mView.launchOpenFileActivity(REQUEST_WAD_FILE));
Expand Down Expand Up @@ -287,4 +300,43 @@ public static void skipRescanningLibrary()
{
sShouldRescanLibrary = false;
}

private void launchOnlineUpdate()
{
if (WiiUtils.isSystemMenuInstalled())
{
SystemUpdateViewModel viewModel =
new ViewModelProvider(mActivity).get(SystemUpdateViewModel.class);
viewModel.setRegion(-1);
OnlineUpdateProgressBarDialogFragment progressBarFragment =
new OnlineUpdateProgressBarDialogFragment();
progressBarFragment
.show(mActivity.getSupportFragmentManager(), "OnlineUpdateProgressBarDialogFragment");
progressBarFragment.setCancelable(false);
}
else
{
SystemMenuNotInstalledDialogFragment dialogFragment =
new SystemMenuNotInstalledDialogFragment();
dialogFragment
.show(mActivity.getSupportFragmentManager(), "SystemMenuNotInstalledDialogFragment");
}
}

private void launchWiiSystemMenu()
{
WiiUtils.isSystemMenuInstalled();

if (WiiUtils.isSystemMenuInstalled())
{
EmulationActivity.launchSystemMenu(mActivity);
}
else
{
SystemMenuNotInstalledDialogFragment dialogFragment =
new SystemMenuNotInstalledDialogFragment();
dialogFragment
.show(mActivity.getSupportFragmentManager(), "SystemMenuNotInstalledDialogFragment");
}
}
}
Expand Up @@ -378,6 +378,10 @@ private ListRow buildSettingsRow()
R.drawable.ic_folder,
R.string.grid_menu_install_wad));

rowItems.add(new TvSettingsItem(R.id.menu_load_wii_system_menu,
R.drawable.ic_folder,
R.string.grid_menu_load_wii_system_menu));

rowItems.add(new TvSettingsItem(R.id.menu_import_wii_save,
R.drawable.ic_folder,
R.string.grid_menu_import_wii_save));
Expand All @@ -386,6 +390,10 @@ private ListRow buildSettingsRow()
R.drawable.ic_folder,
R.string.grid_menu_import_nand_backup));

rowItems.add(new TvSettingsItem(R.id.menu_online_system_update,
R.drawable.ic_folder,
R.string.grid_menu_online_system_update));

// Create a header for this row.
HeaderItem header = new HeaderItem(R.string.settings, getString(R.string.settings));

Expand Down
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later

package org.dolphinemu.dolphinemu.utils;

import androidx.annotation.Keep;

public interface WiiUpdateCallback
{
@Keep
boolean run(int processed, int total, long titleId);
}
Expand Up @@ -10,9 +10,25 @@
public static final int RESULT_CORRUPTED_SOURCE = 3;
public static final int RESULT_TITLE_MISSING = 4;

public static final int UPDATE_RESULT_SUCCESS = 0;
public static final int UPDATE_RESULT_ALREADY_UP_TO_DATE = 1;
public static final int UPDATE_RESULT_REGION_MISMATCH = 2;
public static final int UPDATE_RESULT_MISSING_UPDATE_PARTITION = 3;
public static final int UPDATE_RESULT_DISC_READ_FAILED = 4;
public static final int UPDATE_RESULT_SERVER_FAILED = 5;
public static final int UPDATE_RESULT_DOWNLOAD_FAILED = 6;
public static final int UPDATE_RESULT_IMPORT_FAILED = 7;
public static final int UPDATE_RESULT_CANCELLED = 8;

public static native boolean installWAD(String file);

public static native int importWiiSave(String file, BooleanSupplier canOverwrite);

public static native void importNANDBin(String file);

public static native int doOnlineUpdate(String region, WiiUpdateCallback callback);

public static native boolean isSystemMenuInstalled();

public static native String getSystemMenuVersion();
}
11 changes: 11 additions & 0 deletions Source/Android/app/src/main/res/menu/menu_game_grid.xml
Expand Up @@ -25,6 +25,11 @@
android:title="@string/grid_menu_install_wad"
app:showAsAction="never"/>

<item
android:id="@+id/menu_load_wii_system_menu"
android:title="@string/grid_menu_load_wii_system_menu"
app:showAsAction="never"/>

<item
android:id="@+id/menu_import_wii_save"
android:title="@string/grid_menu_import_wii_save"
Expand All @@ -35,4 +40,10 @@
android:title="@string/grid_menu_import_nand_backup"
app:showAsAction="never"/>

<item
android:id="@+id/menu_online_system_update"
android:title="@string/grid_menu_online_system_update"
app:showAsAction="never"/>
<group />

</menu>
28 changes: 28 additions & 0 deletions Source/Android/app/src/main/res/values/strings.xml
Expand Up @@ -182,6 +182,29 @@
<string name="osd_messages_description">Display messages over the emulation screen area. These messages include memory card writes, video backend and CPU information, and JIT cache clearing.</string>
<string name="download_game_covers">Download Game Covers from GameTDB.com</string>

<!-- Online Update Region Select Fragment -->
<string name="region_select_title">Please select a region</string>
<string name="europe">Europe</string>
<string name="japan">Japan</string>
<string name="korea">Korea</string>
<string name="united_states">United States</string>

<!-- Online Update Progress Bar Fragment -->
<string name="updating">Updating</string>
<string name="updating_message">Updating title %016x...\nThis can take a while.</string>
<string name="update_success">The emulated Wii console has been updated.</string>
<string name="already_up_to_date">The emulated Wii console is already up-to-date.</string>
<string name="region_mismatch">The game\'s region does not match your console\'s. To avoid issues with the system menu, it is not possible to update the emulated console using this disc.</string>
<string name="missing_update_partition">The game disc does not contain any usable update information.</string>
<string name="disc_read_failed">The game disc does not contain any usable update information.</string>
<string name="server_failed">"Could not download update information from Nintendo. Please check your Internet connection and try again.</string>
<string name="download_failed">Could not download update files from Nintendo. Please check your Internet connection and try again.</string>
<string name="import_failed">"Could not install an update to the Wii system memory. Please refer to logs for more information.</string>
<string name="update_cancelled">The update has been cancelled. It is strongly recommended to finish it in order to avoid inconsistent system software versions.</string>
<string name="update_success_title">Update completed</string>
<string name="update_failed_title">Update failed</string>
<string name="update_cancelled_title">Update cancelled</string>

<!-- Audio Settings -->
<string name="audio_submenu">Audio</string>
<string name="dsp_emulation_engine">DSP Emulation Engine</string>
Expand Down Expand Up @@ -391,6 +414,9 @@
<string name="grid_menu_install_wad">Install WAD</string>
<string name="grid_menu_import_wii_save">Import Wii Save</string>
<string name="grid_menu_import_nand_backup">Import BootMii NAND Backup</string>
<string name="grid_menu_online_system_update">Perform Online System Update</string>
<string name="grid_menu_load_wii_system_menu">Load Wii System Menu</string>
<string name="grid_menu_load_wii_system_menu_installed">Load Wii System Menu (%s)</string>
<string name="import_in_progress">Importing...</string>
<string name="do_not_close_app">Do not close the app!</string>
<string name="wad_install_success">Successfully installed this title to the NAND.</string>
Expand All @@ -401,6 +427,8 @@
<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>
<string name="nand_import_warning">Merging a new NAND over your currently selected NAND will overwrite any channels and savegames that already exist. This process is not reversible, so it is recommended that you keep backups of both NANDs. Are you sure you want to continue?</string>
<string name="system_menu_not_installed_title">Not installed</string>
<string name="system_menu_not_installed_message">The Wii Menu is currently not installed. Would you like to install it now?\nAn internet connection is required to download the update. It is recommended to download the update on Wi-Fi, as the amount of data downloaded may be large.</string>

<!-- Game Properties Screen -->
<string name="properties_details">Details</string>
Expand Down
20 changes: 20 additions & 0 deletions Source/Android/jni/AndroidCommon/IDCache.cpp
Expand Up @@ -73,6 +73,9 @@ static jmethodID s_patch_cheat_constructor;
static jclass s_riivolution_patches_class;
static jfieldID s_riivolution_patches_pointer;

static jclass s_wii_update_cb_class;
static jmethodID s_wii_update_cb_run;

namespace IDCache
{
JNIEnv* GetEnvForThread()
Expand Down Expand Up @@ -338,6 +341,16 @@ jfieldID GetRiivolutionPatchesPointer()
return s_riivolution_patches_pointer;
}

jclass GetWiiUpdateCallbackClass()
{
return s_wii_update_cb_class;
}

jmethodID GetWiiUpdateCallbackFunction()
{
return s_wii_update_cb_run;
}

} // namespace IDCache

extern "C" {
Expand Down Expand Up @@ -474,6 +487,12 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
s_riivolution_patches_pointer = env->GetFieldID(riivolution_patches_class, "mPointer", "J");
env->DeleteLocalRef(riivolution_patches_class);

const jclass wii_update_cb_class =
env->FindClass("org/dolphinemu/dolphinemu/utils/WiiUpdateCallback");
s_wii_update_cb_class = reinterpret_cast<jclass>(env->NewGlobalRef(wii_update_cb_class));
s_wii_update_cb_run = env->GetMethodID(s_wii_update_cb_class, "run", "(IIJ)Z");
env->DeleteLocalRef(wii_update_cb_class);

return JNI_VERSION;
}

Expand All @@ -498,5 +517,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
env->DeleteGlobalRef(s_gecko_cheat_class);
env->DeleteGlobalRef(s_patch_cheat_class);
env->DeleteGlobalRef(s_riivolution_patches_class);
env->DeleteGlobalRef(s_wii_update_cb_class);
}
}
3 changes: 3 additions & 0 deletions Source/Android/jni/AndroidCommon/IDCache.h
Expand Up @@ -72,4 +72,7 @@ jmethodID GetPatchCheatConstructor();
jclass GetRiivolutionPatchesClass();
jfieldID GetRiivolutionPatchesPointer();

jclass GetWiiUpdateCallbackClass();
jmethodID GetWiiUpdateCallbackFunction();

} // namespace IDCache
25 changes: 17 additions & 8 deletions Source/Android/jni/MainAndroid.cpp
Expand Up @@ -32,6 +32,7 @@

#include "Core/Boot/Boot.h"
#include "Core/BootManager.h"
#include "Core/CommonTitles.h"
#include "Core/ConfigLoaders/GameConfigLoader.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
Expand Down Expand Up @@ -547,21 +548,14 @@ static float GetRenderSurfaceScale(JNIEnv* env)
return env->CallStaticFloatMethod(native_library_class, get_render_surface_scale_method);
}

static void Run(JNIEnv* env, const std::vector<std::string>& paths, bool riivolution,
BootSessionData boot_session_data = BootSessionData())
static void Run(JNIEnv* env, std::unique_ptr<BootParameters>&& boot, bool riivolution)
{
ASSERT(!paths.empty());
__android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", paths[0].c_str());

std::unique_lock<std::mutex> host_identity_guard(s_host_identity_lock);

WiimoteReal::InitAdapterClass();

s_have_wm_user_stop = false;

std::unique_ptr<BootParameters> boot =
BootParameters::GenerateFromFile(paths, std::move(boot_session_data));

if (riivolution && std::holds_alternative<BootParameters::Disc>(boot->parameters))
{
const std::string& riivolution_dir = File::GetUserPath(D_RIIVOLUTION_IDX);
Expand Down Expand Up @@ -624,6 +618,15 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths, bool riivolu
IDCache::GetFinishEmulationActivity());
}

static void Run(JNIEnv* env, const std::vector<std::string>& paths, bool riivolution,
BootSessionData boot_session_data = BootSessionData())
{
ASSERT(!paths.empty());
__android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", paths[0].c_str());

Run(env, BootParameters::GenerateFromFile(paths, std::move(boot_session_data)), riivolution);
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2Z(
JNIEnv* env, jclass, jobjectArray jPaths, jboolean jRiivolution)
{
Expand All @@ -641,6 +644,12 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2ZLjava_la
BootSessionData(GetJString(env, jSavestate), delete_state));
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RunSystemMenu(JNIEnv* env,
jclass)
{
Run(env, std::make_unique<BootParameters>(BootParameters::NANDTitle{Titles::SYSTEM_MENU}), false);
}

JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_ChangeDisc(JNIEnv* env, jclass,
jstring jFile)
{
Expand Down
76 changes: 76 additions & 0 deletions Source/Android/jni/WiiUtils.cpp
Expand Up @@ -8,7 +8,11 @@
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"

#include "Common/ScopeGuard.h"
#include "Core/CommonTitles.h"
#include "Core/HW/WiiSave.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/IOS.h"
#include "Core/WiiUtils.h"
#include "DiscIO/NANDImporter.h"

Expand All @@ -35,6 +39,36 @@ static jint ConvertCopyResult(WiiSave::CopyResult result)
static_assert(static_cast<int>(WiiSave::CopyResult::NumberOfEntries) == 5);
}

static jint ConvertUpdateResult(WiiUtils::UpdateResult result)
{
switch (result)
{
case WiiUtils::UpdateResult::Succeeded:
return 0;
case WiiUtils::UpdateResult::AlreadyUpToDate:
return 1;
case WiiUtils::UpdateResult::RegionMismatch:
return 2;
case WiiUtils::UpdateResult::MissingUpdatePartition:
return 3;
case WiiUtils::UpdateResult::DiscReadFailed:
return 4;
case WiiUtils::UpdateResult::ServerFailed:
return 5;
case WiiUtils::UpdateResult::DownloadFailed:
return 6;
case WiiUtils::UpdateResult::ImportFailed:
return 7;
case WiiUtils::UpdateResult::Cancelled:
return 8;
default:
ASSERT(false);
return 1;
}

static_assert(static_cast<int>(WiiUtils::UpdateResult::NumberOfEntries) == 9);
}

extern "C" {

JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_WiiUtils_installWAD(JNIEnv* env,
Expand Down Expand Up @@ -78,4 +112,46 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_WiiUtils_importNANDB
return "";
});
}

JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_utils_WiiUtils_doOnlineUpdate(
JNIEnv* env, jclass, jstring jRegion, jobject jCallback)
{
const std::string region = GetJString(env, jRegion);

jobject jCallbackGlobal = env->NewGlobalRef(jCallback);
Common::ScopeGuard scope_guard([jCallbackGlobal, env] { env->DeleteGlobalRef(jCallbackGlobal); });

const auto callback = [&jCallbackGlobal](int processed, int total, u64 title_id) {
JNIEnv* env = IDCache::GetEnvForThread();
return static_cast<bool>(env->CallBooleanMethod(
jCallbackGlobal, IDCache::GetWiiUpdateCallbackFunction(), processed, total, title_id));
};

WiiUtils::UpdateResult result = WiiUtils::DoOnlineUpdate(callback, region);

return ConvertUpdateResult(result);
}

JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_utils_WiiUtils_isSystemMenuInstalled(JNIEnv* env, jclass)
{
IOS::HLE::Kernel ios;
const auto tmd = ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);

return tmd.IsValid();
}

JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_utils_WiiUtils_getSystemMenuVersion(JNIEnv* env, jclass)
{
IOS::HLE::Kernel ios;
const auto tmd = ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);

if (!tmd.IsValid())
{
return ToJString(env, "");
}

return ToJString(env, DiscIO::GetSysMenuVersionString(tmd.GetTitleVersion()));
}
}
2 changes: 2 additions & 0 deletions Source/Core/Core/WiiUtils.h
Expand Up @@ -82,6 +82,8 @@ enum class UpdateResult
ImportFailed,
// Update was cancelled.
Cancelled,

NumberOfEntries,
};

// Return false to cancel the update as soon as the current title has finished updating.
Expand Down