mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-23 17:22:00 +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/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())};
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
@ -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);
|
counter++;
|
||||||
applicationPublisher = span(entry.applicationPublisher).as_string(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
#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};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user