diff --git a/app/src/main/cpp/loader_jni.cpp b/app/src/main/cpp/loader_jni.cpp index f472bb89..314804f2 100644 --- a/app/src/main/cpp/loader_jni.cpp +++ b/app/src/main/cpp/loader_jni.cpp @@ -11,7 +11,7 @@ #include "skyline/loader/nsp.h" #include "skyline/jvm.h" -extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEnv *env, jobject thiz, jint jformat, jint fd, jstring appFilesPathJstring) { +extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEnv *env, jobject thiz, jint jformat, jint fd, jstring appFilesPathJstring, jint systemLanguage) { skyline::loader::RomFormat format{static_cast(jformat)}; auto appFilesPath{env->GetStringUTFChars(appFilesPathJstring, nullptr)}; @@ -53,8 +53,12 @@ extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEn jfieldID rawIconField{env->GetFieldID(clazz, "rawIcon", "[B")}; if (loader->nacp) { - env->SetObjectField(thiz, applicationNameField, env->NewStringUTF(loader->nacp->applicationName.c_str())); - env->SetObjectField(thiz, applicationAuthorField, env->NewStringUTF(loader->nacp->applicationPublisher.c_str())); + auto language{skyline::languages::GetApplicationLanguage(static_cast(systemLanguage))}; + if ((1 << static_cast(language) & loader->nacp->supportedTitleLanguages) == 0) + language = loader->nacp->GetFirstSupportedTitleLanguage(); + + env->SetObjectField(thiz, applicationNameField, env->NewStringUTF(loader->nacp->GetApplicationName(language).c_str())); + env->SetObjectField(thiz, applicationAuthorField, env->NewStringUTF(loader->nacp->GetApplicationPublisher(language).c_str())); auto icon{loader->GetIcon()}; jbyteArray iconByteArray{env->NewByteArray(icon.size())}; diff --git a/app/src/main/cpp/skyline/common/languages.h b/app/src/main/cpp/skyline/common/languages.h index cfdff685..8de5f2a0 100644 --- a/app/src/main/cpp/skyline/common/languages.h +++ b/app/src/main/cpp/skyline/common/languages.h @@ -3,6 +3,7 @@ #pragma once +#include #include namespace skyline { @@ -14,6 +15,45 @@ namespace skyline { } namespace languages { + enum class SystemLanguage : u32 { + Japanese = 0, + AmericanEnglish, + French, + German, + Italian, + Spanish, + Chinese, + Korean, + Dutch, + Portuguese, + Russian, + Taiwanese, + BritishEnglish, + CanadianFrench, + LatinAmericanSpanish, + SimplifiedChinese, + TraditionalChinese, + BrazilianPortuguese, + }; + + enum class ApplicationLanguage : u32 { + AmericanEnglish = 0, + BritishEnglish, + Japanese, + French, + German, + LatinAmericanSpanish, + Spanish, + Italian, + Dutch, + CanadianFrench, + Portuguese, + Russian, + Korean, + TraditionalChinese, + SimplifiedChinese, + }; + constexpr std::array LanguageCodeList{ util::MakeMagic("ja"), util::MakeMagic("en-US"), @@ -38,5 +78,58 @@ namespace skyline { constexpr LanguageCode GetLanguageCode(SystemLanguage language) { return LanguageCodeList.at(static_cast(language)); } + + #define LANG_MAP_ENTRY(lang) {SystemLanguage::lang, ApplicationLanguage::lang} + #define LANG_MAP_ENTRY_CST(sysLang, appLang) {SystemLanguage::sysLang, ApplicationLanguage::appLang} + constexpr frz::unordered_map SystemToAppMap{ + LANG_MAP_ENTRY(Japanese), + LANG_MAP_ENTRY(AmericanEnglish), + LANG_MAP_ENTRY(French), + LANG_MAP_ENTRY(German), + LANG_MAP_ENTRY(Italian), + LANG_MAP_ENTRY(Spanish), + LANG_MAP_ENTRY_CST(Chinese, SimplifiedChinese), + LANG_MAP_ENTRY(Korean), + LANG_MAP_ENTRY(Dutch), + LANG_MAP_ENTRY(Portuguese), + LANG_MAP_ENTRY(Russian), + LANG_MAP_ENTRY_CST(Taiwanese, TraditionalChinese), + LANG_MAP_ENTRY(BritishEnglish), + LANG_MAP_ENTRY(CanadianFrench), + LANG_MAP_ENTRY(LatinAmericanSpanish), + LANG_MAP_ENTRY(SimplifiedChinese), + LANG_MAP_ENTRY(TraditionalChinese), + LANG_MAP_ENTRY_CST(BrazilianPortuguese, Portuguese), + }; + #undef LANG_MAP_ENTRY + #undef LANG_MAP_ENTRY_CST + + constexpr ApplicationLanguage GetApplicationLanguage(SystemLanguage systemLanguage) { + return SystemToAppMap.at(systemLanguage); + } + + #define LANG_MAP_ENTRY_REVERSE(lang) {ApplicationLanguage::lang, SystemLanguage::lang} + constexpr frz::unordered_map AppToSystemMap{ + LANG_MAP_ENTRY_REVERSE(Japanese), + LANG_MAP_ENTRY_REVERSE(AmericanEnglish), + LANG_MAP_ENTRY_REVERSE(French), + LANG_MAP_ENTRY_REVERSE(German), + LANG_MAP_ENTRY_REVERSE(Italian), + LANG_MAP_ENTRY_REVERSE(Spanish), + LANG_MAP_ENTRY_REVERSE(Korean), + LANG_MAP_ENTRY_REVERSE(Dutch), + LANG_MAP_ENTRY_REVERSE(Portuguese), + LANG_MAP_ENTRY_REVERSE(Russian), + LANG_MAP_ENTRY_REVERSE(BritishEnglish), + LANG_MAP_ENTRY_REVERSE(CanadianFrench), + LANG_MAP_ENTRY_REVERSE(LatinAmericanSpanish), + LANG_MAP_ENTRY_REVERSE(SimplifiedChinese), + LANG_MAP_ENTRY_REVERSE(TraditionalChinese), + }; + #undef LANG_MAP_ENTRY_REVERSE + + constexpr SystemLanguage GetSystemLanguage(ApplicationLanguage applicationLanguage) { + return AppToSystemMap.at(applicationLanguage); + } } } \ No newline at end of file diff --git a/app/src/main/cpp/skyline/loader/nro.cpp b/app/src/main/cpp/skyline/loader/nro.cpp index 0e80fac0..93d183c3 100644 --- a/app/src/main/cpp/skyline/loader/nro.cpp +++ b/app/src/main/cpp/skyline/loader/nro.cpp @@ -64,7 +64,8 @@ namespace skyline::loader { } state.process->memory.InitializeVmm(memory::AddressSpaceType::AddressSpace39Bit); - auto loadInfo{LoadExecutable(process, state, executable, 0, nacp->applicationName.empty() ? "main.nro" : nacp->applicationName + ".nro")}; + auto applicationName{nacp->GetApplicationName(nacp->GetFirstSupportedTitleLanguage())}; + auto loadInfo{LoadExecutable(process, state, executable, 0, applicationName.empty() ? "main.nro" : applicationName + ".nro")}; state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size); return loadInfo.entry; diff --git a/app/src/main/cpp/skyline/vfs/nacp.cpp b/app/src/main/cpp/skyline/vfs/nacp.cpp index 2b45c34b..ddf3527f 100644 --- a/app/src/main/cpp/skyline/vfs/nacp.cpp +++ b/app/src/main/cpp/skyline/vfs/nacp.cpp @@ -1,21 +1,33 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include #include "nacp.h" namespace skyline::vfs { NACP::NACP(const std::shared_ptr &backing) { nacpContents = backing->Read(); - // TODO: Select based on language settings, complete struct, yada yada - - // Iterate till we get to the first populated entry + u32 counter{0}; for (auto &entry : nacpContents.titleEntries) { - if (entry.applicationName.front() == '\0') - continue; - - applicationName = span(entry.applicationName).as_string(true); - applicationPublisher = span(entry.applicationPublisher).as_string(true); + if (entry.applicationName.front() != '\0') { + supportedTitleLanguages |= (1 << counter); + } + counter++; } } + + languages::ApplicationLanguage NACP::GetFirstSupportedTitleLanguage() { + return static_cast(std::countr_zero(supportedTitleLanguages)); + } + + std::string NACP::GetApplicationName(languages::ApplicationLanguage language) { + auto applicationName{span(nacpContents.titleEntries.at(static_cast(language)).applicationName)}; + return std::string(applicationName.as_string(true)); + } + + std::string NACP::GetApplicationPublisher(languages::ApplicationLanguage language) { + auto applicationPublisher{span(nacpContents.titleEntries.at(static_cast(language)).applicationPublisher)}; + return std::string(applicationPublisher.as_string(true)); + } } diff --git a/app/src/main/cpp/skyline/vfs/nacp.h b/app/src/main/cpp/skyline/vfs/nacp.h index 6a5502e1..f5cafa03 100644 --- a/app/src/main/cpp/skyline/vfs/nacp.h +++ b/app/src/main/cpp/skyline/vfs/nacp.h @@ -3,6 +3,7 @@ #pragma once +#include #include "backing.h" namespace skyline::vfs { @@ -24,17 +25,24 @@ namespace skyline::vfs { public: struct NacpData { std::array titleEntries; //!< Title entries for each language - u8 _pad0_[0x78]; - u64 saveDataOwnerId; //!< The ID that should be used for this application's savedata + u8 _pad0_[0x2C]; + u32 supportedLanguageFlag; //!< A bitmask containing the game's supported languages u8 _pad1_[0x78]; + u64 saveDataOwnerId; //!< The ID that should be used for this application's savedata + u8 _pad2_[0x48]; std::array seedForPseudoDeviceId; //!< Seed that is combined with the device seed for generating the pseudo-device ID - u8 _pad2_[0xF00]; + u8 _pad3_[0xF00]; } nacpContents{}; static_assert(sizeof(NacpData) == 0x4000); NACP(const std::shared_ptr &backing); - std::string applicationName; //!< The name of the application in the currently selected language - std::string applicationPublisher; //!< The publisher of the application in the currently selected language + languages::ApplicationLanguage GetFirstSupportedTitleLanguage(); + + std::string GetApplicationName(languages::ApplicationLanguage language); + + std::string GetApplicationPublisher(languages::ApplicationLanguage language); + + u32 supportedTitleLanguages{0}; }; } diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt index 64c8c24e..ab3af3f4 100644 --- a/app/src/main/java/emu/skyline/MainActivity.kt +++ b/app/src/main/java/emu/skyline/MainActivity.kt @@ -292,7 +292,7 @@ class MainActivity : AppCompatActivity() { } private fun loadRoms(loadFromFile : Boolean) { - viewModel.loadRoms(loadFromFile, Uri.parse(settings.searchLocation)) + viewModel.loadRoms(loadFromFile, Uri.parse(settings.searchLocation), Integer.parseInt(settings.systemLanguage)) settings.refreshRequired = false } diff --git a/app/src/main/java/emu/skyline/MainViewModel.kt b/app/src/main/java/emu/skyline/MainViewModel.kt index 2963dc5d..985dc7d8 100644 --- a/app/src/main/java/emu/skyline/MainViewModel.kt +++ b/app/src/main/java/emu/skyline/MainViewModel.kt @@ -43,7 +43,7 @@ class MainViewModel @Inject constructor(@ApplicationContext context : Context, p * * @param loadFromFile If this is false then trying to load cached adapter data is skipped entirely */ - fun loadRoms(loadFromFile : Boolean, searchLocation : Uri) { + fun loadRoms(loadFromFile : Boolean, searchLocation : Uri, systemLanguage : Int) { if (state == MainState.Loading) return state = MainState.Loading @@ -64,7 +64,7 @@ class MainViewModel @Inject constructor(@ApplicationContext context : Context, p MainState.Loaded(HashMap()) } else { try { - val romElements = romProvider.loadRoms(searchLocation) + val romElements = romProvider.loadRoms(searchLocation, systemLanguage) romElements.toFile(romsFile) MainState.Loaded(romElements) } catch (e : Exception) { diff --git a/app/src/main/java/emu/skyline/RomProvider.kt b/app/src/main/java/emu/skyline/RomProvider.kt index b0b8ec59..7e451ec7 100644 --- a/app/src/main/java/emu/skyline/RomProvider.kt +++ b/app/src/main/java/emu/skyline/RomProvider.kt @@ -18,21 +18,21 @@ class RomProvider @Inject constructor(@ApplicationContext private val context : * This adds all files in [directory] with [extension] as an entry using [RomFile] to load metadata */ @SuppressLint("DefaultLocale") - private fun addEntries(fileFormats : Map, directory : DocumentFile, entries : HashMap>) { + private fun addEntries(fileFormats : Map, directory : DocumentFile, entries : HashMap>, systemLanguage : Int) { directory.listFiles().forEach { file -> if (file.isDirectory) { - addEntries(fileFormats, file, entries) + addEntries(fileFormats, file, entries, systemLanguage) } else { fileFormats[file.name?.substringAfterLast(".")?.toLowerCase()]?.let { romFormat-> - entries.getOrPut(romFormat, { arrayListOf() }).add(RomFile(context, romFormat, file.uri).appEntry) + entries.getOrPut(romFormat, { arrayListOf() }).add(RomFile(context, romFormat, file.uri, systemLanguage).appEntry) } } } } - fun loadRoms(searchLocation : Uri) = DocumentFile.fromTreeUri(context, searchLocation)!!.let { documentFile -> + fun loadRoms(searchLocation : Uri, systemLanguage : Int) = DocumentFile.fromTreeUri(context, searchLocation)!!.let { documentFile -> hashMapOf>().apply { - addEntries(mapOf("nro" to NRO, "nso" to NSO, "nca" to NCA, "nsp" to NSP, "xci" to XCI), documentFile, this) + addEntries(mapOf("nro" to NRO, "nso" to NSO, "nca" to NCA, "nsp" to NSP, "xci" to XCI), documentFile, this, systemLanguage) } } } diff --git a/app/src/main/java/emu/skyline/loader/RomFile.kt b/app/src/main/java/emu/skyline/loader/RomFile.kt index 2fbf4c07..4192b8d4 100644 --- a/app/src/main/java/emu/skyline/loader/RomFile.kt +++ b/app/src/main/java/emu/skyline/loader/RomFile.kt @@ -103,7 +103,7 @@ data class AppEntry(var name : String, var author : String?, var icon : Bitmap?, /** * This class is used as interface between libskyline and Kotlin for loaders */ -internal class RomFile(context : Context, format : RomFormat, uri : Uri) { +internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemLanguage : Int) { /** * @note This field is filled in by native code */ @@ -130,7 +130,7 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri) { System.loadLibrary("skyline") context.contentResolver.openFileDescriptor(uri, "r")!!.use { - result = LoaderResult.get(populate(format.ordinal, it.fd, context.filesDir.canonicalPath + "/")) + result = LoaderResult.get(populate(format.ordinal, it.fd, context.filesDir.canonicalPath + "/", systemLanguage)) } appEntry = applicationName?.let { name -> @@ -149,5 +149,5 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri) { * @param appFilesPath Path to internal app data storage, needed to read imported keys * @return A pointer to the newly allocated object, or 0 if the ROM is invalid */ - private external fun populate(format : Int, romFd : Int, appFilesPath : String) : Int + private external fun populate(format : Int, romFd : Int, appFilesPath : String, systemLanguage : Int) : Int } \ No newline at end of file