mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-04 23:35:12 +01:00
Implement language support for rom loaders
This commit is contained in:
parent
3c5509e52a
commit
fdc7e1238a
@ -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())};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user