|
|
@ -15,11 +15,13 @@ |
|
|
#include "common/string_util.h"
|
|
|
#include "common/string_util.h"
|
|
|
#include "common/swap.h"
|
|
|
#include "common/swap.h"
|
|
|
#include "core/file_sys/control_metadata.h"
|
|
|
#include "core/file_sys/control_metadata.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "common/logging/log.h"
|
|
|
#include "core/file_sys/vfs/vfs.h"
|
|
|
#include "core/file_sys/vfs/vfs.h"
|
|
|
|
|
|
|
|
|
namespace FileSys { |
|
|
namespace FileSys { |
|
|
|
|
|
|
|
|
const std::array<const char*, 16> LANGUAGE_NAMES{{ |
|
|
|
|
|
|
|
|
const std::array<const char*, 18> LANGUAGE_NAMES{{ |
|
|
"AmericanEnglish", |
|
|
"AmericanEnglish", |
|
|
"BritishEnglish", |
|
|
"BritishEnglish", |
|
|
"Japanese", |
|
|
"Japanese", |
|
|
@ -36,16 +38,16 @@ const std::array<const char*, 16> LANGUAGE_NAMES{{ |
|
|
"TraditionalChinese", |
|
|
"TraditionalChinese", |
|
|
"SimplifiedChinese", |
|
|
"SimplifiedChinese", |
|
|
"BrazilianPortuguese", |
|
|
"BrazilianPortuguese", |
|
|
|
|
|
"Polish", |
|
|
|
|
|
"Thai" |
|
|
}}; |
|
|
}}; |
|
|
|
|
|
|
|
|
namespace { |
|
|
namespace { |
|
|
constexpr std::size_t LEGACY_LANGUAGE_REGION_SIZE = sizeof(std::array<LanguageEntry, 16>); |
|
|
|
|
|
constexpr std::size_t PACKED_LANGUAGE_REGION_MAX_SIZE = sizeof(LanguageEntry) * 32; |
|
|
|
|
|
|
|
|
constexpr std::size_t MAX_EXPANDED_LANG_SIZE = sizeof(LanguageEntry) * 32; |
|
|
|
|
|
|
|
|
bool InflateRawDeflate(std::span<const u8> compressed, std::vector<u8>& out) { |
|
|
|
|
|
if (compressed.empty() || compressed.size() > std::numeric_limits<uInt>::max()) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
bool InflateRawDeflate(std::span<const u8> compressed, std::vector<u8>& out) |
|
|
|
|
|
{ |
|
|
|
|
|
if (compressed.empty()) return false; |
|
|
|
|
|
|
|
|
z_stream stream{}; |
|
|
z_stream stream{}; |
|
|
stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(compressed.data())); |
|
|
stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(compressed.data())); |
|
|
@ -54,54 +56,20 @@ bool InflateRawDeflate(std::span<const u8> compressed, std::vector<u8>& out) { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
std::array<u8, 0x1000> chunk{}; |
|
|
|
|
|
int ret = Z_OK; |
|
|
|
|
|
while (ret == Z_OK) { |
|
|
|
|
|
stream.next_out = reinterpret_cast<Bytef*>(chunk.data()); |
|
|
|
|
|
stream.avail_out = static_cast<uInt>(chunk.size()); |
|
|
|
|
|
ret = inflate(&stream, Z_NO_FLUSH); |
|
|
|
|
|
if (ret != Z_OK && ret != Z_STREAM_END) { |
|
|
|
|
|
inflateEnd(&stream); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const auto produced = chunk.size() - static_cast<std::size_t>(stream.avail_out); |
|
|
|
|
|
if (produced != 0) { |
|
|
|
|
|
if (out.size() + produced > PACKED_LANGUAGE_REGION_MAX_SIZE) { |
|
|
|
|
|
inflateEnd(&stream); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
out.insert(out.end(), chunk.begin(), |
|
|
|
|
|
chunk.begin() + static_cast<std::ptrdiff_t>(produced)); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
out.resize(MAX_EXPANDED_LANG_SIZE); |
|
|
|
|
|
stream.next_out = reinterpret_cast<Bytef*>(out.data()); |
|
|
|
|
|
stream.avail_out = static_cast<uInt>(out.size()); |
|
|
|
|
|
|
|
|
|
|
|
int ret = inflate(&stream, Z_FINISH); |
|
|
inflateEnd(&stream); |
|
|
inflateEnd(&stream); |
|
|
return ret == Z_STREAM_END; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void DecodePackedLanguageEntries(RawNACP& raw) { |
|
|
|
|
|
auto* packed_region = reinterpret_cast<u8*>(raw.language_entries.data()); |
|
|
|
|
|
u16_le compressed_size_le{}; |
|
|
|
|
|
std::memcpy(&compressed_size_le, packed_region, sizeof(compressed_size_le)); |
|
|
|
|
|
const auto compressed_size = static_cast<std::size_t>(compressed_size_le); |
|
|
|
|
|
|
|
|
|
|
|
if (compressed_size == 0 || compressed_size > LEGACY_LANGUAGE_REGION_SIZE - sizeof(u16_le)) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
std::vector<u8> decompressed; |
|
|
|
|
|
if (!InflateRawDeflate( |
|
|
|
|
|
std::span<const u8>(packed_region + sizeof(u16_le), compressed_size), decompressed)) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (decompressed.size() < LEGACY_LANGUAGE_REGION_SIZE || |
|
|
|
|
|
decompressed.size() % sizeof(LanguageEntry) != 0) { |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
if (ret != Z_STREAM_END && ret != Z_OK) { |
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
std::memcpy(raw.language_entries.data(), decompressed.data(), LEGACY_LANGUAGE_REGION_SIZE); |
|
|
|
|
|
|
|
|
// Shrink to actual decompressed size
|
|
|
|
|
|
out.resize(stream.total_out); |
|
|
|
|
|
return true; |
|
|
} |
|
|
} |
|
|
} // namespace
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
@ -115,7 +83,7 @@ std::string LanguageEntry::GetDeveloperName() const { |
|
|
developer_name.size()); |
|
|
developer_name.size()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
constexpr std::array<Language, 18> language_to_codes = {{ |
|
|
|
|
|
|
|
|
constexpr std::array<Language, 20> language_to_codes = {{ |
|
|
Language::Japanese, |
|
|
Language::Japanese, |
|
|
Language::AmericanEnglish, |
|
|
Language::AmericanEnglish, |
|
|
Language::French, |
|
|
Language::French, |
|
|
@ -134,39 +102,54 @@ constexpr std::array<Language, 18> language_to_codes = {{ |
|
|
Language::SimplifiedChinese, |
|
|
Language::SimplifiedChinese, |
|
|
Language::TraditionalChinese, |
|
|
Language::TraditionalChinese, |
|
|
Language::BrazilianPortuguese, |
|
|
Language::BrazilianPortuguese, |
|
|
|
|
|
Language::Polish, |
|
|
|
|
|
Language::Thai |
|
|
}}; |
|
|
}}; |
|
|
|
|
|
|
|
|
NACP::NACP() = default; |
|
|
NACP::NACP() = default; |
|
|
|
|
|
|
|
|
NACP::NACP(VirtualFile file) { |
|
|
|
|
|
|
|
|
NACP::NACP(VirtualFile file) |
|
|
|
|
|
{ |
|
|
file->ReadObject(&raw); |
|
|
file->ReadObject(&raw); |
|
|
DecodePackedLanguageEntries(raw); |
|
|
|
|
|
|
|
|
if (raw.titles_data_format == TitleDataFormat::Compressed) { |
|
|
|
|
|
const u16 compressed_size = raw.language_entries.compressed_data.buffer_size; |
|
|
|
|
|
std::span<const u8> compressed_payload{raw.language_entries.compressed_data.buffer, |
|
|
|
|
|
compressed_size}; |
|
|
|
|
|
|
|
|
|
|
|
std::vector<u8> decompressed; |
|
|
|
|
|
if (InflateRawDeflate(compressed_payload, decompressed)) { |
|
|
|
|
|
const size_t entry_count = decompressed.size() / sizeof(LanguageEntry); |
|
|
|
|
|
language_entries.resize(entry_count); |
|
|
|
|
|
std::memcpy(language_entries.data(), decompressed.data(), decompressed.size()); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
language_entries.resize(16); |
|
|
|
|
|
std::memcpy(language_entries.data(), raw.language_entries.language_entries.data(), |
|
|
|
|
|
sizeof(raw.language_entries.language_entries)); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
NACP::~NACP() = default; |
|
|
NACP::~NACP() = default; |
|
|
|
|
|
|
|
|
const LanguageEntry& NACP::GetLanguageEntry() const { |
|
|
const LanguageEntry& NACP::GetLanguageEntry() const { |
|
|
Language language = |
|
|
|
|
|
language_to_codes[static_cast<s32>(Settings::values.language_index.GetValue())]; |
|
|
|
|
|
|
|
|
u32 index = static_cast<u32>(Settings::values.language_index.GetValue()); |
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
const auto& language_entry = raw.language_entries.at(static_cast<u8>(language)); |
|
|
|
|
|
if (!language_entry.GetApplicationName().empty()) |
|
|
|
|
|
return language_entry; |
|
|
|
|
|
|
|
|
if (index < language_entries.size()) { |
|
|
|
|
|
return language_entries[index]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const auto& language_entry : raw.language_entries) { |
|
|
|
|
|
if (!language_entry.GetApplicationName().empty()) |
|
|
|
|
|
return language_entry; |
|
|
|
|
|
|
|
|
for (const auto& entry : language_entries) { |
|
|
|
|
|
return entry; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return raw.language_entries.at(static_cast<u8>(Language::AmericanEnglish)); |
|
|
|
|
|
|
|
|
return language_entries.at(static_cast<u8>(Language::AmericanEnglish)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
std::array<std::string, 16> NACP::GetApplicationNames() const { |
|
|
|
|
|
std::array<std::string, 16> names{}; |
|
|
|
|
|
for (size_t i = 0; i < raw.language_entries.size(); ++i) { |
|
|
|
|
|
names[i] = raw.language_entries[i].GetApplicationName(); |
|
|
|
|
|
|
|
|
std::vector<std::string> NACP::GetApplicationNames() const { |
|
|
|
|
|
std::vector<std::string> names; |
|
|
|
|
|
names.reserve(language_entries.size()); |
|
|
|
|
|
for (const auto& entry : language_entries) { |
|
|
|
|
|
names.push_back(entry.GetApplicationName()); |
|
|
} |
|
|
} |
|
|
return names; |
|
|
return names; |
|
|
} |
|
|
} |
|
|
|