By only loading data from disk when creating an instance of this new class instead of on every method call, we save a lot of file open operations, which due to SAF are very expensive. This should noticeably speed up game list scanning. No intended change in what metadata is shown.
150 lines
4.9 KiB
C++
150 lines
4.9 KiB
C++
// Copyright 2017 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "common/string_util.h"
|
|
#include "core/hle/service/am/am.h"
|
|
#include "core/hle/service/fs/archive.h"
|
|
#include "core/loader/loader.h"
|
|
#include "core/loader/smdh.h"
|
|
#include "jni/android_common/android_common.h"
|
|
#include "jni/id_cache.h"
|
|
|
|
namespace {
|
|
|
|
std::vector<u8> GetSMDHData(const std::string& path) {
|
|
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(path);
|
|
if (!loader) {
|
|
return {};
|
|
}
|
|
|
|
u64 program_id = 0;
|
|
loader->ReadProgramId(program_id);
|
|
|
|
std::vector<u8> smdh = [program_id, &loader]() -> std::vector<u8> {
|
|
std::vector<u8> original_smdh;
|
|
loader->ReadIcon(original_smdh);
|
|
|
|
if (program_id < 0x00040000'00000000 || program_id > 0x00040000'FFFFFFFF)
|
|
return original_smdh;
|
|
|
|
std::string update_path = Service::AM::GetTitleContentPath(
|
|
Service::FS::MediaType::SDMC, program_id + 0x0000000E'00000000);
|
|
|
|
if (!FileUtil::Exists(update_path))
|
|
return original_smdh;
|
|
|
|
std::unique_ptr<Loader::AppLoader> update_loader = Loader::GetLoader(update_path);
|
|
|
|
if (!update_loader)
|
|
return original_smdh;
|
|
|
|
std::vector<u8> update_smdh;
|
|
update_loader->ReadIcon(update_smdh);
|
|
return update_smdh;
|
|
}();
|
|
|
|
return smdh;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
static Loader::SMDH* GetPointer(JNIEnv* env, jobject obj) {
|
|
return reinterpret_cast<Loader::SMDH*>(env->GetLongField(obj, IDCache::GetGameInfoPointer()));
|
|
}
|
|
|
|
JNIEXPORT jlong JNICALL Java_org_citra_citra_1emu_model_GameInfo_initialize(JNIEnv* env, jclass,
|
|
jstring j_path) {
|
|
std::vector<u8> smdh_data = GetSMDHData(GetJString(env, j_path));
|
|
|
|
Loader::SMDH* smdh = nullptr;
|
|
if (Loader::IsValidSMDH(smdh_data)) {
|
|
smdh = new Loader::SMDH;
|
|
memcpy(smdh, smdh_data.data(), sizeof(Loader::SMDH));
|
|
}
|
|
return reinterpret_cast<jlong>(smdh);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_model_GameInfo_finalize(JNIEnv* env, jobject obj) {
|
|
delete GetPointer(env, obj);
|
|
}
|
|
|
|
jstring Java_org_citra_citra_1emu_model_GameInfo_getTitle(JNIEnv* env, jobject obj) {
|
|
Loader::SMDH* smdh = GetPointer(env, obj);
|
|
Loader::SMDH::TitleLanguage language = Loader::SMDH::TitleLanguage::English;
|
|
|
|
// Get the title from SMDH in UTF-16 format
|
|
std::u16string title{
|
|
reinterpret_cast<char16_t*>(smdh->titles[static_cast<size_t>(language)].long_title.data())};
|
|
|
|
return ToJString(env, Common::UTF16ToUTF8(title).data());
|
|
}
|
|
|
|
jstring Java_org_citra_citra_1emu_model_GameInfo_getCompany(JNIEnv* env, jobject obj) {
|
|
Loader::SMDH* smdh = GetPointer(env, obj);
|
|
Loader::SMDH::TitleLanguage language = Loader::SMDH::TitleLanguage::English;
|
|
|
|
// Get the Publisher's name from SMDH in UTF-16 format
|
|
char16_t* publisher;
|
|
publisher =
|
|
reinterpret_cast<char16_t*>(smdh->titles[static_cast<size_t>(language)].publisher.data());
|
|
|
|
return ToJString(env, Common::UTF16ToUTF8(publisher).data());
|
|
}
|
|
|
|
jstring Java_org_citra_citra_1emu_model_GameInfo_getRegions(JNIEnv* env, jobject obj) {
|
|
Loader::SMDH* smdh = GetPointer(env, obj);
|
|
|
|
using GameRegion = Loader::SMDH::GameRegion;
|
|
static const std::map<GameRegion, const char*> regions_map = {
|
|
{GameRegion::Japan, "Japan"}, {GameRegion::NorthAmerica, "North America"},
|
|
{GameRegion::Europe, "Europe"}, {GameRegion::Australia, "Australia"},
|
|
{GameRegion::China, "China"}, {GameRegion::Korea, "Korea"},
|
|
{GameRegion::Taiwan, "Taiwan"}};
|
|
std::vector<GameRegion> regions = smdh->GetRegions();
|
|
|
|
if (regions.empty()) {
|
|
return ToJString(env, "Invalid region");
|
|
}
|
|
|
|
const bool region_free =
|
|
std::all_of(regions_map.begin(), regions_map.end(), [®ions](const auto& it) {
|
|
return std::find(regions.begin(), regions.end(), it.first) != regions.end();
|
|
});
|
|
|
|
if (region_free) {
|
|
return ToJString(env, "Region free");
|
|
}
|
|
|
|
const std::string separator = ", ";
|
|
std::string result = regions_map.at(regions.front());
|
|
for (auto region = ++regions.begin(); region != regions.end(); ++region) {
|
|
result += separator + regions_map.at(*region);
|
|
}
|
|
|
|
return ToJString(env, result);
|
|
}
|
|
|
|
jintArray Java_org_citra_citra_1emu_model_GameInfo_getIcon(JNIEnv* env, jobject obj) {
|
|
Loader::SMDH* smdh = GetPointer(env, obj);
|
|
|
|
// Always get a 48x48(large) icon
|
|
std::vector<u16> icon_data = smdh->GetIcon(true);
|
|
if (icon_data.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
jintArray icon = env->NewIntArray(static_cast<jsize>(icon_data.size() / 2));
|
|
env->SetIntArrayRegion(icon, 0, env->GetArrayLength(icon),
|
|
reinterpret_cast<jint*>(icon_data.data()));
|
|
|
|
return icon;
|
|
}
|
|
}
|