Implement language support for rom loaders

This commit is contained in:
sspacelynx 2021-08-16 21:52:36 +02:00 committed by ◱ Mark
parent 3c5509e52a
commit fdc7e1238a
9 changed files with 146 additions and 28 deletions

View File

@ -11,7 +11,7 @@
#include "skyline/loader/nsp.h" #include "skyline/loader/nsp.h"
#include "skyline/jvm.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<skyline::loader::RomFormat>(jformat)}; skyline::loader::RomFormat format{static_cast<skyline::loader::RomFormat>(jformat)};
auto appFilesPath{env->GetStringUTFChars(appFilesPathJstring, nullptr)}; 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")}; jfieldID rawIconField{env->GetFieldID(clazz, "rawIcon", "[B")};
if (loader->nacp) { if (loader->nacp) {
env->SetObjectField(thiz, applicationNameField, env->NewStringUTF(loader->nacp->applicationName.c_str())); auto language{skyline::languages::GetApplicationLanguage(static_cast<skyline::languages::SystemLanguage>(systemLanguage))};
env->SetObjectField(thiz, applicationAuthorField, env->NewStringUTF(loader->nacp->applicationPublisher.c_str())); if ((1 << static_cast<skyline::u32>(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()}; auto icon{loader->GetIcon()};
jbyteArray iconByteArray{env->NewByteArray(icon.size())}; jbyteArray iconByteArray{env->NewByteArray(icon.size())};

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <frozen/unordered_map.h>
#include <common.h> #include <common.h>
namespace skyline { namespace skyline {
@ -14,6 +15,45 @@ namespace skyline {
} }
namespace languages { 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<LanguageCode, constant::NewLanguageCodeListSize> LanguageCodeList{ constexpr std::array<LanguageCode, constant::NewLanguageCodeListSize> LanguageCodeList{
util::MakeMagic<LanguageCode>("ja"), util::MakeMagic<LanguageCode>("ja"),
util::MakeMagic<LanguageCode>("en-US"), util::MakeMagic<LanguageCode>("en-US"),
@ -38,5 +78,58 @@ namespace skyline {
constexpr LanguageCode GetLanguageCode(SystemLanguage language) { constexpr LanguageCode GetLanguageCode(SystemLanguage language) {
return LanguageCodeList.at(static_cast<size_t>(language)); return LanguageCodeList.at(static_cast<size_t>(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<SystemLanguage, ApplicationLanguage, constant::NewLanguageCodeListSize> 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<ApplicationLanguage, SystemLanguage, constant::OldLanguageCodeListSize> 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);
}
} }
} }

View File

@ -64,7 +64,8 @@ namespace skyline::loader {
} }
state.process->memory.InitializeVmm(memory::AddressSpaceType::AddressSpace39Bit); 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); state.process->memory.InitializeRegions(loadInfo.base, loadInfo.size);
return loadInfo.entry; return loadInfo.entry;

View File

@ -1,21 +1,33 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <bit>
#include "nacp.h" #include "nacp.h"
namespace skyline::vfs { namespace skyline::vfs {
NACP::NACP(const std::shared_ptr<vfs::Backing> &backing) { NACP::NACP(const std::shared_ptr<vfs::Backing> &backing) {
nacpContents = backing->Read<NacpData>(); nacpContents = backing->Read<NacpData>();
// TODO: Select based on language settings, complete struct, yada yada u32 counter{0};
// Iterate till we get to the first populated entry
for (auto &entry : nacpContents.titleEntries) { for (auto &entry : nacpContents.titleEntries) {
if (entry.applicationName.front() == '\0') if (entry.applicationName.front() != '\0') {
continue; supportedTitleLanguages |= (1 << counter);
applicationName = span(entry.applicationName).as_string(true);
applicationPublisher = span(entry.applicationPublisher).as_string(true);
} }
counter++;
}
}
languages::ApplicationLanguage NACP::GetFirstSupportedTitleLanguage() {
return static_cast<languages::ApplicationLanguage>(std::countr_zero(supportedTitleLanguages));
}
std::string NACP::GetApplicationName(languages::ApplicationLanguage language) {
auto applicationName{span(nacpContents.titleEntries.at(static_cast<size_t>(language)).applicationName)};
return std::string(applicationName.as_string(true));
}
std::string NACP::GetApplicationPublisher(languages::ApplicationLanguage language) {
auto applicationPublisher{span(nacpContents.titleEntries.at(static_cast<size_t>(language)).applicationPublisher)};
return std::string(applicationPublisher.as_string(true));
} }
} }

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <common/languages.h>
#include "backing.h" #include "backing.h"
namespace skyline::vfs { namespace skyline::vfs {
@ -24,17 +25,24 @@ namespace skyline::vfs {
public: public:
struct NacpData { struct NacpData {
std::array<ApplicationTitle, 0x10> titleEntries; //!< Title entries for each language std::array<ApplicationTitle, 0x10> titleEntries; //!< Title entries for each language
u8 _pad0_[0x78]; u8 _pad0_[0x2C];
u64 saveDataOwnerId; //!< The ID that should be used for this application's savedata u32 supportedLanguageFlag; //!< A bitmask containing the game's supported languages
u8 _pad1_[0x78]; u8 _pad1_[0x78];
u64 saveDataOwnerId; //!< The ID that should be used for this application's savedata
u8 _pad2_[0x48];
std::array<u8, 8> seedForPseudoDeviceId; //!< Seed that is combined with the device seed for generating the pseudo-device ID std::array<u8, 8> seedForPseudoDeviceId; //!< Seed that is combined with the device seed for generating the pseudo-device ID
u8 _pad2_[0xF00]; u8 _pad3_[0xF00];
} nacpContents{}; } nacpContents{};
static_assert(sizeof(NacpData) == 0x4000); static_assert(sizeof(NacpData) == 0x4000);
NACP(const std::shared_ptr<vfs::Backing> &backing); NACP(const std::shared_ptr<vfs::Backing> &backing);
std::string applicationName; //!< The name of the application in the currently selected language languages::ApplicationLanguage GetFirstSupportedTitleLanguage();
std::string applicationPublisher; //!< The publisher of the application in the currently selected language
std::string GetApplicationName(languages::ApplicationLanguage language);
std::string GetApplicationPublisher(languages::ApplicationLanguage language);
u32 supportedTitleLanguages{0};
}; };
} }

View File

@ -292,7 +292,7 @@ class MainActivity : AppCompatActivity() {
} }
private fun loadRoms(loadFromFile : Boolean) { 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 settings.refreshRequired = false
} }

View File

@ -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 * @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 if (state == MainState.Loading) return
state = MainState.Loading state = MainState.Loading
@ -64,7 +64,7 @@ class MainViewModel @Inject constructor(@ApplicationContext context : Context, p
MainState.Loaded(HashMap()) MainState.Loaded(HashMap())
} else { } else {
try { try {
val romElements = romProvider.loadRoms(searchLocation) val romElements = romProvider.loadRoms(searchLocation, systemLanguage)
romElements.toFile(romsFile) romElements.toFile(romsFile)
MainState.Loaded(romElements) MainState.Loaded(romElements)
} catch (e : Exception) { } catch (e : Exception) {

View File

@ -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 * This adds all files in [directory] with [extension] as an entry using [RomFile] to load metadata
*/ */
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private fun addEntries(fileFormats : Map<String, RomFormat>, directory : DocumentFile, entries : HashMap<RomFormat, ArrayList<AppEntry>>) { private fun addEntries(fileFormats : Map<String, RomFormat>, directory : DocumentFile, entries : HashMap<RomFormat, ArrayList<AppEntry>>, systemLanguage : Int) {
directory.listFiles().forEach { file -> directory.listFiles().forEach { file ->
if (file.isDirectory) { if (file.isDirectory) {
addEntries(fileFormats, file, entries) addEntries(fileFormats, file, entries, systemLanguage)
} else { } else {
fileFormats[file.name?.substringAfterLast(".")?.toLowerCase()]?.let { romFormat-> 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<RomFormat, ArrayList<AppEntry>>().apply { hashMapOf<RomFormat, ArrayList<AppEntry>>().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)
} }
} }
} }

View File

@ -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 * 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 * @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") System.loadLibrary("skyline")
context.contentResolver.openFileDescriptor(uri, "r")!!.use { 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 -> 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 * @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 * @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
} }