mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Add account settings
This commit is contained in:
+3
-5
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.21.1)
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
|
||||
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
||||
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
||||
@@ -78,10 +78,8 @@ add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH})
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
# enable link time optimization for release builds
|
||||
if(NOT ANDROID)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON)
|
||||
endif()
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON)
|
||||
|
||||
if (MSVC)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT CemuBin)
|
||||
|
||||
@@ -47,7 +47,7 @@ val cemuDataFilesFolder = "../../../bin"
|
||||
android {
|
||||
namespace = "info.cemu.cemu"
|
||||
compileSdk = 35
|
||||
ndkVersion = "26.1.10909125"
|
||||
ndkVersion = "26.3.11579264"
|
||||
defaultConfig {
|
||||
applicationId = "info.cemu.cemu"
|
||||
minSdk = 31
|
||||
@@ -70,7 +70,9 @@ android {
|
||||
packaging {
|
||||
jniLibs.useLegacyPackaging = true
|
||||
}
|
||||
|
||||
val keystoreFilePath: String? = System.getenv("ANDROID_KEYSTORE_FILE")
|
||||
|
||||
signingConfigs {
|
||||
if (keystoreFilePath != null) {
|
||||
create("releaseSigningConfig") {
|
||||
@@ -117,33 +119,35 @@ android {
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.22.1"
|
||||
version = "3.25.0+"
|
||||
path = file("../../../CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
val versionArguments = if (versionMajor != null && versionMinor != null)
|
||||
arrayOf(
|
||||
"-DEMULATOR_VERSION_MAJOR=$versionMajor",
|
||||
"-DEMULATOR_VERSION_MINOR=$versionMinor"
|
||||
) else emptyArray()
|
||||
val cmakeArguments = arrayOf(
|
||||
"-DANDROID_STL=c++_shared",
|
||||
"-DENABLE_VCPKG=ON",
|
||||
"-DVCPKG_TARGET_ANDROID=ON",
|
||||
"-DENABLE_SDL=OFF",
|
||||
"-DENABLE_WXWIDGETS=OFF",
|
||||
"-DENABLE_OPENGL=OFF",
|
||||
"-DENABLE_BLUEZ=OFF",
|
||||
"-DBUNDLE_SPEEX=ON",
|
||||
"-DENABLE_DISCORD_RPC=OFF",
|
||||
"-DENABLE_NSYSHID_LIBUSB=OFF",
|
||||
"-DENABLE_WAYLAND=OFF",
|
||||
"-DENABLE_HIDAPI=OFF"
|
||||
) + versionArguments
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments.addAll(cmakeArguments)
|
||||
arguments(
|
||||
"-DANDROID_STL=c++_shared",
|
||||
"-DENABLE_VCPKG=ON",
|
||||
"-DVCPKG_TARGET_ANDROID=ON",
|
||||
"-DENABLE_SDL=OFF",
|
||||
"-DENABLE_WXWIDGETS=OFF",
|
||||
"-DENABLE_OPENGL=OFF",
|
||||
"-DENABLE_BLUEZ=OFF",
|
||||
"-DBUNDLE_SPEEX=ON",
|
||||
"-DENABLE_DISCORD_RPC=OFF",
|
||||
"-DENABLE_NSYSHID_LIBUSB=OFF",
|
||||
"-DENABLE_WAYLAND=OFF",
|
||||
"-DENABLE_HIDAPI=OFF"
|
||||
)
|
||||
if (versionMajor != null && versionMinor != null) {
|
||||
arguments.addAll(
|
||||
arrayOf(
|
||||
"-DEMULATOR_VERSION_MAJOR=$versionMajor",
|
||||
"-DEMULATOR_VERSION_MINOR=$versionMinor"
|
||||
)
|
||||
)
|
||||
}
|
||||
abiFilters("arm64-v8a")
|
||||
}
|
||||
}
|
||||
@@ -226,13 +230,18 @@ dependencies {
|
||||
implementation(libs.androidx.ui)
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.kotlin.reflect)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.okhttp.coroutines)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.google.android.material)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package info.cemu.cemu
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PlaceholderInstrumentedTest {
|
||||
@Test
|
||||
fun test() {
|
||||
assertTrue(true)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ add_library(CemuAndroid SHARED
|
||||
Image.cpp
|
||||
JNIUtils.cpp
|
||||
NativeActiveSettings.cpp
|
||||
NativeAccount.cpp
|
||||
NativeEmulation.cpp
|
||||
NativeGameTitles.cpp
|
||||
NativeGraphicPacks.cpp
|
||||
|
||||
@@ -22,6 +22,16 @@ namespace JNIUtils
|
||||
return env->NewStringUTF(str.c_str());
|
||||
}
|
||||
|
||||
inline jstring toJString(JNIEnv* env, std::string_view str)
|
||||
{
|
||||
return toJString(env, std::string(str));
|
||||
}
|
||||
|
||||
inline jstring toJString(JNIEnv* env, std::wstring_view str)
|
||||
{
|
||||
return toJString(env, boost::nowide::narrow(str));
|
||||
}
|
||||
|
||||
jobject createJavaStringArrayList(JNIEnv* env, const std::vector<std::string>& stringList);
|
||||
|
||||
jobject createJavaStringArrayList(JNIEnv* env, const std::vector<std::wstring>& stringList);
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
#include <util/helpers/SystemException.h>
|
||||
#include "WindowSystem.h"
|
||||
#include "JNIUtils.h"
|
||||
#include "AndroidAudio.h"
|
||||
#include "AndroidEmulatedController.h"
|
||||
#include "AndroidFilesystemCallbacks.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
#include "GameTitleLoader.h"
|
||||
#include "input/ControllerFactory.h"
|
||||
#include "input/InputManager.h"
|
||||
#include "input/api/Android/AndroidController.h"
|
||||
#include "input/api/Android/AndroidControllerProvider.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Cemu/ncrypto/ncrypto.h"
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_createAccount(JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id, jstring mii_name)
|
||||
{
|
||||
uint32 persistentId = static_cast<uint32>(persistent_id);
|
||||
std::string miiName = JNIUtils::toString(env, mii_name);
|
||||
Account account(persistentId, boost::nowide::widen(miiName));
|
||||
account.Save();
|
||||
Account::RefreshAccounts();
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_deleteAccount([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id)
|
||||
{
|
||||
uint32 persistentId = static_cast<uint32>(persistent_id);
|
||||
const auto& account = Account::GetAccount(persistentId);
|
||||
|
||||
if (account.GetPersistentId() != persistentId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path path = account.GetFileName();
|
||||
|
||||
try
|
||||
{
|
||||
fs::remove_all(path.parent_path());
|
||||
Account::RefreshAccounts();
|
||||
} catch (const std::exception& ex)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to delete account {} {}", path.c_str(), ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_saveAccount(JNIEnv* env, [[maybe_unused]] jclass clazz, jobject account_java)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
jclass accountClass = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$Account");
|
||||
auto getAccountField = [&](const char* fieldName, const char* sig, auto getFieldFn) -> auto {
|
||||
auto getField = std::bind(getFieldFn, env, account_java, std::placeholders::_1);
|
||||
return getField(env->GetFieldID(accountClass, fieldName, sig));
|
||||
};
|
||||
uint32 persistentId = getAccountField("persistentId", "I", &JNIEnv::GetIntField);
|
||||
auto account = Account::GetAccount(persistentId);
|
||||
|
||||
if (account.GetPersistentId() != persistentId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
jstring miiNameJava = static_cast<jstring>(getAccountField("miiName", "Ljava/lang/String;", &JNIEnv::GetObjectField));
|
||||
account.SetMiiName(boost::nowide::widen(JNIUtils::toString(env, miiNameJava)));
|
||||
account.SetCountry(getAccountField("country", "I", &JNIEnv::GetIntField));
|
||||
account.SetGender(getAccountField("gender", "B", &JNIEnv::GetByteField));
|
||||
jstring emailJava = static_cast<jstring>(getAccountField("email", "Ljava/lang/String;", &JNIEnv::GetObjectField));
|
||||
account.SetEmail(JNIUtils::toString(env, emailJava));
|
||||
auto birthdayMillis = milliseconds(getAccountField("birthday", "J", &JNIEnv::GetLongField));
|
||||
system_clock::time_point birthdayTimePoint(birthdayMillis);
|
||||
year_month_day birthdayYMD(floor<days>(time_point(birthdayTimePoint)));
|
||||
if (birthdayYMD.ok())
|
||||
{
|
||||
account.SetBirthYear(static_cast<sint32>(birthdayYMD.year()));
|
||||
account.SetBirthMonth(static_cast<uint32>(birthdayYMD.month()));
|
||||
account.SetBirthDay(static_cast<uint32>(birthdayYMD.day()));
|
||||
}
|
||||
account.Save();
|
||||
|
||||
Account::RefreshAccounts();
|
||||
}
|
||||
|
||||
jlong toUnixTimestampMillis(std::chrono::year_month_day ymd)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
if (!ymd.ok())
|
||||
return 0;
|
||||
|
||||
auto millis = duration_cast<milliseconds>(system_clock::time_point(sys_days(ymd)).time_since_epoch());
|
||||
return millis.count();
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jobjectArray JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_getAccounts(JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
jclass accountClass = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$Account");
|
||||
jmethodID accountCtrId = env->GetMethodID(accountClass, "<init>", "(ILjava/lang/String;JBLjava/lang/String;IZ)V");
|
||||
|
||||
const auto& accounts = Account::GetAccounts();
|
||||
jsize accountsCount = static_cast<jsize>(accounts.size());
|
||||
auto accountsJArray = env->NewObjectArray(accountsCount, accountClass, nullptr);
|
||||
|
||||
for (jint i = 0; i < accounts.size(); i++)
|
||||
{
|
||||
const auto& account = accounts[i];
|
||||
jint persistentId = static_cast<jint>(account.GetPersistentId());
|
||||
jstring miiName = JNIUtils::toJString(env, account.GetMiiName());
|
||||
jlong birthday = toUnixTimestampMillis(year_month_day(year(account.GetBirthYear()), month(account.GetBirthMonth()), day(account.GetBirthDay())));
|
||||
jbyte gender = static_cast<jbyte>(account.GetGender());
|
||||
jstring email = JNIUtils::toJString(env, account.GetEmail());
|
||||
jint country = static_cast<jint>(account.GetCountry());
|
||||
jboolean isValid = account.IsValidOnlineAccount();
|
||||
|
||||
jobject accountJObj = env->NewObject(
|
||||
accountClass,
|
||||
accountCtrId,
|
||||
persistentId,
|
||||
miiName,
|
||||
birthday,
|
||||
gender,
|
||||
email,
|
||||
country,
|
||||
isValid);
|
||||
env->SetObjectArrayElement(accountsJArray, i, accountJObj);
|
||||
}
|
||||
|
||||
return accountsJArray;
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jobjectArray JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_getAccountCountries(JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
jclass countryClass = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$AccountCountry");
|
||||
jmethodID countryCtrId = env->GetMethodID(countryClass, "<init>", "(ILjava/lang/String;)V");
|
||||
|
||||
struct Country
|
||||
{
|
||||
jint index;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
std::vector<Country> countries;
|
||||
for (int i = 0; i < NCrypto::GetCountryCount(); ++i)
|
||||
{
|
||||
const auto countryName = NCrypto::GetCountryAsString(i);
|
||||
if (countryName && (i == 0 || !boost::equals(countryName, "NN")))
|
||||
{
|
||||
countries.push_back({.index = i, .name = countryName});
|
||||
}
|
||||
}
|
||||
|
||||
jobjectArray countriesJava = env->NewObjectArray(countries.size(), countryClass, nullptr);
|
||||
for (int i = 0; i < countries.size(); ++i)
|
||||
{
|
||||
const auto& country = countries[i];
|
||||
jobject countryJava = env->NewObject(countryClass,
|
||||
countryCtrId,
|
||||
country.index,
|
||||
env->NewStringUTF(country.name));
|
||||
env->SetObjectArrayElement(countriesJava, i, countryJava);
|
||||
}
|
||||
|
||||
return countriesJava;
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jobjectArray JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_getAccountValidationErrors(JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id)
|
||||
{
|
||||
using ErrorType = std::pair<jclass, jmethodID>;
|
||||
auto getErrorType = [&](const char* className, const char* ctrSig = "()V") -> ErrorType {
|
||||
using namespace std::placeholders;
|
||||
jclass errorClass = env->FindClass(className);
|
||||
jmethodID ctrMID = env->GetMethodID(errorClass, "<init>", ctrSig);
|
||||
return std::make_pair(errorClass, ctrMID);
|
||||
};
|
||||
auto newError = [&](const ErrorType& error, auto... args) {
|
||||
return env->NewObject(error.first, error.second, args...);
|
||||
};
|
||||
auto missingOTPError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$MissingOTP");
|
||||
auto corruptedOTPError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$CorruptedOTP");
|
||||
auto missingSEEPROMError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$MissingSEEPROM");
|
||||
auto corruptedSEEPROMError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$CorruptedSEEPROM");
|
||||
auto missingFileError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$MissingFile", "(Ljava/lang/String;)V");
|
||||
auto accountError = getErrorType("info/cemu/cemu/nativeinterface/NativeAccount$AccountError", "(I)V");
|
||||
auto baseErrorType = env->FindClass("info/cemu/cemu/nativeinterface/NativeAccount$OnlineValidationError");
|
||||
|
||||
uint32 persistentId = persistent_id;
|
||||
auto account = Account::GetAccount(persistentId);
|
||||
const auto validator = account.ValidateOnlineFiles();
|
||||
|
||||
if (account.GetPersistentId() != persistentId || validator.IsValid())
|
||||
{
|
||||
return env->NewObjectArray(0, baseErrorType, nullptr);
|
||||
}
|
||||
|
||||
std::vector<jobject> errors;
|
||||
|
||||
if (validator.otp == OnlineValidator::FileState::Missing)
|
||||
errors.push_back(newError(missingOTPError));
|
||||
else if (validator.otp == OnlineValidator::FileState::Corrupted)
|
||||
errors.push_back(newError(corruptedOTPError));
|
||||
|
||||
if (validator.seeprom == OnlineValidator::FileState::Missing)
|
||||
errors.push_back(newError(missingSEEPROMError));
|
||||
else if (validator.seeprom == OnlineValidator::FileState::Corrupted)
|
||||
errors.push_back(newError(corruptedSEEPROMError));
|
||||
|
||||
if (!validator.missing_files.empty())
|
||||
{
|
||||
int counter = 0;
|
||||
for (const auto& missingFile : validator.missing_files)
|
||||
{
|
||||
errors.push_back(newError(missingFileError, JNIUtils::toJString(env, missingFile)));
|
||||
|
||||
++counter;
|
||||
if (counter > 10)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!validator.valid_account && validator.account_error != OnlineAccountError::kNone)
|
||||
{
|
||||
errors.push_back(newError(accountError, validator.account_error));
|
||||
}
|
||||
|
||||
jobjectArray errorsJava = env->NewObjectArray(errors.size(), baseErrorType, nullptr);
|
||||
|
||||
for (int i = 0; i < errors.size(); i++)
|
||||
{
|
||||
env->SetObjectArrayElement(errorsJava, i, errors[i]);
|
||||
}
|
||||
|
||||
return errorsJava;
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_isOTPPresent([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
return NCrypto::OTP_IsPresent();
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeAccount_isSEEPROMPresent([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
return NCrypto::SEEPROM_IsPresent();
|
||||
}
|
||||
@@ -33,4 +33,10 @@ extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeActiveSettings_setInternalDir(JNIEnv* env, [[maybe_unused]] jclass clazz, jstring internal_dir)
|
||||
{
|
||||
ActiveSettings::SetInternalDir(JNIUtils::toString(env, internal_dir));
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeActiveSettings_hasRequiredOnlineFiles(JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
return ActiveSettings::HasRequiredOnlineFiles();
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace NativeLocalization
|
||||
}
|
||||
} // namespace NativeLocalization
|
||||
|
||||
extern "C" JNIEXPORT [[maybe_unused]] void JNICALL
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeLocalization_setTranslations(JNIEnv* env, [[maybe_unused]] jclass clazz, jobject translations)
|
||||
{
|
||||
NativeLocalization::g_messages.clear();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "JNIUtils.h"
|
||||
#include "audio/IAudioAPI.h"
|
||||
#include "config/CemuConfig.h"
|
||||
#include "config/NetworkSettings.h"
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_getOverlayPosition([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
@@ -175,7 +176,7 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_addGamesPath(JNIEnv* env, [[m
|
||||
{
|
||||
auto& gamePaths = GetConfig().game_paths;
|
||||
auto gamePath = JNIUtils::toString(env, uri);
|
||||
if (std::any_of(gamePaths.begin(), gamePaths.end(), [&](auto path) { return path == gamePath; }))
|
||||
if (std::any_of(gamePaths.begin(), gamePaths.end(), [&](const auto& path) { return path == gamePath; }))
|
||||
return;
|
||||
gamePaths.push_back(gamePath);
|
||||
}
|
||||
@@ -185,7 +186,7 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_removeGamesPath(JNIEnv* env,
|
||||
{
|
||||
auto gamePath = JNIUtils::toString(env, uri);
|
||||
auto& gamePaths = GetConfig().game_paths;
|
||||
std::erase_if(gamePaths, [&](auto path) { return path == gamePath; });
|
||||
std::erase_if(gamePaths, [&](const auto& path) { return path == gamePath; });
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jobject JNICALL
|
||||
@@ -320,8 +321,9 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_getAudioLatency([[maybe_unuse
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_setAudioLatency([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint latency)
|
||||
{
|
||||
GetConfig().audio_delay = latency / 12;
|
||||
// IAudioAPI::SetAudioDelayOverride(GetConfig().audio_delay);
|
||||
sint32 audioDelay = latency / 12;
|
||||
GetConfig().audio_delay = audioDelay;
|
||||
IAudioAPI::SetAudioDelay(audioDelay);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
|
||||
@@ -351,8 +353,38 @@ Java_info_cemu_cemu_nativeinterface_NativeSettings_setCustomDriverPath(JNIEnv* e
|
||||
GetConfig().custom_driver_path = JNIUtils::toString(env, custom_driver_path);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_getAccountNetworkService([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id)
|
||||
{
|
||||
return static_cast<jint>(GetConfig().GetAccountNetworkService(persistent_id));
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_setAccountNetworkService([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id, jint network_service)
|
||||
{
|
||||
GetConfig().SetAccountSelectedService(persistent_id, static_cast<NetworkService>(network_service));
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_getAccountPersistentId([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
return static_cast<jint>(GetConfig().account.m_persistent_id);
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_setAccountPersistentId([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint persistent_id)
|
||||
{
|
||||
GetConfig().account.m_persistent_id = persistent_id;
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_hasCustomNetworkConfiguration([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
return NetworkConfig::XMLExists();
|
||||
}
|
||||
|
||||
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
|
||||
Java_info_cemu_cemu_nativeinterface_NativeSettings_saveSettings([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
GetConfigHandle().Save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,10 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.PlatformImeOptions
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import info.cemu.cemu.R
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package info.cemu.cemu.core.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.platform.debugInspectorInfo
|
||||
import androidx.compose.ui.semantics.Role
|
||||
|
||||
fun Modifier.clickableWithNoRipple(
|
||||
enabled: Boolean = true,
|
||||
onClickLabel: String? = null,
|
||||
role: Role? = null,
|
||||
onClick: () -> Unit
|
||||
): Modifier = composed(
|
||||
inspectorInfo = debugInspectorInfo {
|
||||
name = "clickable"
|
||||
properties["enabled"] = enabled
|
||||
properties["onClickLabel"] = onClickLabel
|
||||
properties["role"] = role
|
||||
properties["onClick"] = onClick
|
||||
}
|
||||
) {
|
||||
Modifier.clickable(
|
||||
enabled = enabled,
|
||||
onClickLabel = onClickLabel,
|
||||
onClick = onClick,
|
||||
role = role,
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
}
|
||||
@@ -19,27 +19,27 @@ import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import info.cemu.cemu.R
|
||||
import info.cemu.cemu.core.translation.tr
|
||||
|
||||
@Composable
|
||||
fun SingleSelection(
|
||||
label: String,
|
||||
choice: String,
|
||||
choices: List<String>,
|
||||
choices: Collection<String>,
|
||||
isChoiceEnabled: (String) -> Boolean = { true },
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
modifier: Modifier = Modifier.fillMaxWidth(),
|
||||
onChoiceChanged: (String) -> Unit,
|
||||
) {
|
||||
SingleSelection(
|
||||
@@ -48,6 +48,7 @@ fun SingleSelection(
|
||||
choices = choices,
|
||||
modifier = modifier,
|
||||
choiceToString = { it },
|
||||
enabled = enabled,
|
||||
isChoiceEnabled = isChoiceEnabled,
|
||||
onChoiceChanged = onChoiceChanged,
|
||||
)
|
||||
@@ -58,10 +59,11 @@ fun SingleSelection(
|
||||
fun <T> SingleSelection(
|
||||
label: String,
|
||||
initialChoice: () -> T,
|
||||
choices: List<T>,
|
||||
choices: Collection<T>,
|
||||
modifier: Modifier = Modifier.fillMaxWidth(),
|
||||
choiceToString: @Composable (T) -> String,
|
||||
isChoiceEnabled: (T) -> Boolean = { true },
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
onChoiceChanged: (T) -> Unit,
|
||||
) {
|
||||
var choice by rememberSaveable { mutableStateOf(initialChoice()) }
|
||||
@@ -71,6 +73,7 @@ fun <T> SingleSelection(
|
||||
choices = choices,
|
||||
modifier = modifier,
|
||||
isChoiceEnabled = isChoiceEnabled,
|
||||
enabled = enabled,
|
||||
choiceToString = choiceToString,
|
||||
onChoiceChanged = { newChoice ->
|
||||
choice = newChoice
|
||||
@@ -83,32 +86,45 @@ fun <T> SingleSelection(
|
||||
fun <T> SingleSelection(
|
||||
label: String,
|
||||
choice: T,
|
||||
choices: List<T>,
|
||||
choices: Collection<T>,
|
||||
choiceToString: @Composable (T) -> String,
|
||||
modifier: Modifier = Modifier,
|
||||
modifier: Modifier = Modifier.fillMaxWidth(),
|
||||
enabled: Boolean = true,
|
||||
isChoiceEnabled: (T) -> Boolean = { true },
|
||||
onChoiceChanged: (T) -> Unit,
|
||||
) {
|
||||
var showSelectDialog by rememberSaveable { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = modifier
|
||||
.clickable { showSelectDialog = true; }
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
text = choiceToString(choice),
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
fontSize = 16.sp,
|
||||
)
|
||||
|
||||
val clickableModifier = if (enabled) {
|
||||
Modifier.clickable { showSelectDialog = true }
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = if (enabled) 1f else 0.38f)
|
||||
)
|
||||
{
|
||||
Column(
|
||||
modifier = modifier
|
||||
.then(clickableModifier)
|
||||
.padding(8.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
Text(
|
||||
text = choiceToString(choice),
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
fontSize = 16.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (showSelectDialog) {
|
||||
SelectDialog(
|
||||
label = label,
|
||||
@@ -126,7 +142,7 @@ fun <T> SingleSelection(
|
||||
private fun <T> SelectDialog(
|
||||
label: String,
|
||||
currentChoice: T,
|
||||
choices: List<T>,
|
||||
choices: Collection<T>,
|
||||
isChoiceEnabled: (T) -> Boolean,
|
||||
choiceToString: @Composable (T) -> String,
|
||||
onDismissRequest: () -> Unit,
|
||||
|
||||
+10
-10
@@ -1,16 +1,16 @@
|
||||
package info.cemu.cemu.core.nativeenummapper
|
||||
|
||||
import info.cemu.cemu.core.translation.tr
|
||||
import info.cemu.cemu.nativeinterface.NativeGameTitles
|
||||
import info.cemu.cemu.core.translation.tr
|
||||
import info.cemu.cemu.nativeinterface.NativeGameTitles.ConsoleRegion
|
||||
|
||||
fun regionToString(region: Int): String = when (region) {
|
||||
NativeGameTitles.CONSOLE_REGION_JPN -> tr("Japan")
|
||||
NativeGameTitles.CONSOLE_REGION_USA -> tr("USA")
|
||||
NativeGameTitles.CONSOLE_REGION_EUR -> tr("Europe")
|
||||
NativeGameTitles.CONSOLE_REGION_AUS_DEPR -> tr("Australia")
|
||||
NativeGameTitles.CONSOLE_REGION_CHN -> tr("China")
|
||||
NativeGameTitles.CONSOLE_REGION_KOR -> tr("Korea")
|
||||
NativeGameTitles.CONSOLE_REGION_TWN -> tr("Taiwan")
|
||||
NativeGameTitles.CONSOLE_REGION_AUTO -> tr("Auto")
|
||||
ConsoleRegion.JPN -> tr("Japan")
|
||||
ConsoleRegion.USA -> tr("USA")
|
||||
ConsoleRegion.EUR -> tr("Europe")
|
||||
ConsoleRegion.AUS_DEPR -> tr("Australia")
|
||||
ConsoleRegion.CHN -> tr("China")
|
||||
ConsoleRegion.KOR -> tr("Korea")
|
||||
ConsoleRegion.TWN -> tr("Taiwan")
|
||||
ConsoleRegion.AUTO -> tr("Auto")
|
||||
else -> tr("Many")
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package info.cemu.cemu.core.nativeenummapper
|
||||
|
||||
import info.cemu.cemu.core.translation.tr
|
||||
import info.cemu.cemu.nativeinterface.NativeInput
|
||||
import info.cemu.cemu.nativeinterface.NativeInput.EmulatedControllerType
|
||||
|
||||
fun controllerTypeToString(type: Int) = when (type) {
|
||||
NativeInput.EMULATED_CONTROLLER_TYPE_DISABLED -> tr("Disabled")
|
||||
NativeInput.EMULATED_CONTROLLER_TYPE_VPAD -> tr("Wii U GamePad")
|
||||
NativeInput.EMULATED_CONTROLLER_TYPE_PRO -> tr("Wii U Pro Controller")
|
||||
NativeInput.EMULATED_CONTROLLER_TYPE_WIIMOTE -> tr("Wiimote")
|
||||
NativeInput.EMULATED_CONTROLLER_TYPE_CLASSIC -> tr("Wii U Classic Controller")
|
||||
EmulatedControllerType.DISABLED -> tr("Disabled")
|
||||
EmulatedControllerType.VPAD -> tr("Wii U GamePad")
|
||||
EmulatedControllerType.PRO -> tr("Wii U Pro Controller")
|
||||
EmulatedControllerType.WIIMOTE -> tr("Wiimote")
|
||||
EmulatedControllerType.CLASSIC -> tr("Wii U Classic Controller")
|
||||
else -> throw IllegalArgumentException("Invalid controller type: $type")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package info.cemu.cemu.core.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
@@ -21,7 +20,7 @@ fun CemuTheme(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
dynamicColor -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ fun setLanguage(languageCode: String, context: Context) {
|
||||
CurrentLanguage = translation.locale.language
|
||||
}
|
||||
|
||||
fun getCurrentLocale() = I18n.locale
|
||||
|
||||
fun setTranslations(context: Context) {
|
||||
val assetTranslations = context.assets.list(TRANSLATIONS_FOLDER)?.filter { language ->
|
||||
if (language == DEFAULT_LANGUAGE)
|
||||
|
||||
@@ -65,7 +65,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
NativeEmulation.setSurface(surfaceHolder.surface, isMainCanvas)
|
||||
surfaceSet = true
|
||||
} catch (exception: NativeException) {
|
||||
onEmulationError(tr(">Failed creating surface: {0}", exception.message!!))
|
||||
onEmulationError(tr("Failed creating surface: {0}", exception.message!!))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,9 +241,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
setFullscreen()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
inputOverlaySurfaceView = binding.inputOverlay
|
||||
|
||||
inputOverlaySurfaceView.setVisible(overlaySettings.isOverlayEnabled)
|
||||
initializeInputOverlay()
|
||||
|
||||
binding.sideMenu.configureSideMenu()
|
||||
|
||||
@@ -294,16 +293,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
|
||||
val mainCanvasHolder = mainCanvas.holder
|
||||
mainCanvasHolder.addCallback(CanvasSurfaceHolderCallback(isMainCanvas = true))
|
||||
mainCanvasHolder.addCallback(object : SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
}
|
||||
|
||||
override fun surfaceChanged(
|
||||
holder: SurfaceHolder,
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int,
|
||||
) {
|
||||
mainCanvasHolder.addCallback(object : SurfaceChangedListener() {
|
||||
override fun surfaceChanged() {
|
||||
if (hasEmulationError) {
|
||||
return
|
||||
}
|
||||
@@ -312,13 +303,16 @@ class EmulationActivity : AppCompatActivity() {
|
||||
startGame(launchPath)
|
||||
}
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
}
|
||||
})
|
||||
mainCanvas.setOnTouchListener(CanvasOnTouchListener(isTV = true))
|
||||
}
|
||||
|
||||
private fun initializeInputOverlay() {
|
||||
inputOverlaySurfaceView = binding.inputOverlay
|
||||
|
||||
inputOverlaySurfaceView.setVisible(overlaySettings.isOverlayEnabled)
|
||||
}
|
||||
|
||||
private fun toastMessage(text: String) {
|
||||
toast?.cancel()
|
||||
toast = Toast.makeText(this, text, Toast.LENGTH_SHORT)
|
||||
@@ -327,15 +321,16 @@ class EmulationActivity : AppCompatActivity() {
|
||||
|
||||
private fun startGame(launchPath: String) {
|
||||
val result = NativeEmulation.startGame(launchPath)
|
||||
if (result == NativeEmulation.START_GAME_SUCCESSFUL) {
|
||||
if (result == NativeEmulation.StartGameStatusCode.SUCCESSFUL) {
|
||||
return
|
||||
}
|
||||
val errorMessage = when (result) {
|
||||
NativeEmulation.START_GAME_ERROR_GAME_BASE_FILES_NOT_FOUND -> tr("Unable to launch game because the base files were not found.")
|
||||
NativeEmulation.START_GAME_ERROR_NO_DISC_KEY -> tr("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title.")
|
||||
NativeEmulation.START_GAME_ERROR_NO_TITLE_TIK -> tr("Could not decrypt title because title.tik is missing.")
|
||||
NativeEmulation.StartGameStatusCode.ERROR_GAME_BASE_FILES_NOT_FOUND -> tr("Unable to launch game because the base files were not found.")
|
||||
NativeEmulation.StartGameStatusCode.ERROR_NO_DISC_KEY -> tr("Could not decrypt title. Make sure that keys.txt contains the correct disc key for this title.")
|
||||
NativeEmulation.StartGameStatusCode.ERROR_NO_TITLE_TIK -> tr("Could not decrypt title because title.tik is missing.")
|
||||
else -> tr("Unable to launch game\nPath: {0}", launchPath)
|
||||
}
|
||||
|
||||
onEmulationError(errorMessage)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package info.cemu.cemu.emulation
|
||||
|
||||
import android.view.SurfaceHolder
|
||||
|
||||
abstract class SurfaceChangedListener : SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
|
||||
|
||||
}
|
||||
|
||||
abstract fun surfaceChanged()
|
||||
|
||||
override fun surfaceChanged(
|
||||
surfaceHolder: SurfaceHolder,
|
||||
format: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
) {
|
||||
surfaceChanged()
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) {
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import info.cemu.cemu.core.components.ScreenContent
|
||||
import info.cemu.cemu.core.nativeenummapper.regionToString
|
||||
import info.cemu.cemu.core.translation.tr
|
||||
import info.cemu.cemu.nativeinterface.NativeGameTitles
|
||||
import info.cemu.cemu.nativeinterface.NativeGameTitles.Game
|
||||
@@ -106,16 +107,3 @@ private fun <T> TitleDetailsEntry(entryName: String, entryData: T?) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun regionToString(region: Int): String = when (region) {
|
||||
NativeGameTitles.CONSOLE_REGION_JPN -> tr("Japan")
|
||||
NativeGameTitles.CONSOLE_REGION_USA -> tr("USA")
|
||||
NativeGameTitles.CONSOLE_REGION_EUR -> tr("Europe")
|
||||
NativeGameTitles.CONSOLE_REGION_AUS_DEPR -> tr("Australia")
|
||||
NativeGameTitles.CONSOLE_REGION_CHN -> tr("China")
|
||||
NativeGameTitles.CONSOLE_REGION_KOR -> tr("Korea")
|
||||
NativeGameTitles.CONSOLE_REGION_TWN -> tr("Taiwan")
|
||||
NativeGameTitles.CONSOLE_REGION_AUTO -> tr("Auto")
|
||||
else -> tr("Many")
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,10 @@ fun GameProfileEditScreen(game: NativeGameTitles.Game?, navigateBack: () -> Unit
|
||||
label = tr("CPU mode"),
|
||||
initialChoice = { NativeGameTitles.getCpuModeForTitle(titleId) },
|
||||
choices = listOf(
|
||||
NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER,
|
||||
NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER,
|
||||
NativeGameTitles.CPU_MODE_MULTICORERECOMPILER,
|
||||
NativeGameTitles.CPU_MODE_AUTO
|
||||
NativeGameTitles.CPUMode.SINGLECOREINTERPRETER,
|
||||
NativeGameTitles.CPUMode.SINGLECORERECOMPILER,
|
||||
NativeGameTitles.CPUMode.MULTICORERECOMPILER,
|
||||
NativeGameTitles.CPUMode.AUTO
|
||||
),
|
||||
choiceToString = { cpuMode -> cpuModeToString(cpuMode) },
|
||||
onChoiceChanged = { cpuMode -> NativeGameTitles.setCpuModeForTitle(titleId, cpuMode) }
|
||||
@@ -80,8 +80,8 @@ fun GameProfileEditScreen(game: NativeGameTitles.Game?, navigateBack: () -> Unit
|
||||
}
|
||||
|
||||
private fun cpuModeToString(cpuMode: Int): String = when (cpuMode) {
|
||||
NativeGameTitles.CPU_MODE_SINGLECOREINTERPRETER -> tr("Single-core interpreter")
|
||||
NativeGameTitles.CPU_MODE_SINGLECORERECOMPILER -> tr("Single-core recompiler")
|
||||
NativeGameTitles.CPU_MODE_MULTICORERECOMPILER -> tr("Multi-core recompiler")
|
||||
NativeGameTitles.CPUMode.SINGLECOREINTERPRETER -> tr("Single-core interpreter")
|
||||
NativeGameTitles.CPUMode.SINGLECORERECOMPILER -> tr("Single-core recompiler")
|
||||
NativeGameTitles.CPUMode.MULTICORERECOMPILER -> tr("Multi-core recompiler")
|
||||
else -> tr("Auto (recommended)")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user