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/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)};
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<skyline::languages::SystemLanguage>(systemLanguage))};
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()};
jbyteArray iconByteArray{env->NewByteArray(icon.size())};

View File

@ -3,6 +3,7 @@
#pragma once
#include <frozen/unordered_map.h>
#include <common.h>
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<LanguageCode, constant::NewLanguageCodeListSize> LanguageCodeList{
util::MakeMagic<LanguageCode>("ja"),
util::MakeMagic<LanguageCode>("en-US"),
@ -38,5 +78,58 @@ namespace skyline {
constexpr LanguageCode GetLanguageCode(SystemLanguage 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);
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;

View File

@ -1,21 +1,33 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <bit>
#include "nacp.h"
namespace skyline::vfs {
NACP::NACP(const std::shared_ptr<vfs::Backing> &backing) {
nacpContents = backing->Read<NacpData>();
// 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<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
#include <common/languages.h>
#include "backing.h"
namespace skyline::vfs {
@ -24,17 +25,24 @@ namespace skyline::vfs {
public:
struct NacpData {
std::array<ApplicationTitle, 0x10> 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<u8, 8> 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<vfs::Backing> &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};
};
}

View File

@ -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
}

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
*/
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) {

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
*/
@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 ->
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<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
*/
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
}