Add support for WUHB file format (#1190)

This commit is contained in:
goeiecool9999 2024-05-05 02:35:01 +02:00 committed by GitHub
parent f28043e0e9
commit dc480ac00b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 617 additions and 3 deletions

View File

@ -10,6 +10,7 @@ add_library(CemuCafe
Filesystem/fscDeviceRedirect.cpp
Filesystem/fscDeviceWua.cpp
Filesystem/fscDeviceWud.cpp
Filesystem/fscDeviceWuhb.cpp
Filesystem/fsc.h
Filesystem/FST/FST.cpp
Filesystem/FST/FST.h
@ -18,6 +19,9 @@ add_library(CemuCafe
Filesystem/FST/KeyCache.h
Filesystem/WUD/wud.cpp
Filesystem/WUD/wud.h
Filesystem/WUHB/RomFSStructs.h
Filesystem/WUHB/WUHBReader.cpp
Filesystem/WUHB/WUHBReader.h
GamePatch.cpp
GamePatch.h
GameProfile/GameProfile.cpp

View File

@ -0,0 +1,40 @@
#pragma once
struct romfs_header_t
{
uint32 header_magic;
uint32be header_size;
uint64be dir_hash_table_ofs;
uint64be dir_hash_table_size;
uint64be dir_table_ofs;
uint64be dir_table_size;
uint64be file_hash_table_ofs;
uint64be file_hash_table_size;
uint64be file_table_ofs;
uint64be file_table_size;
uint64be file_partition_ofs;
};
struct romfs_direntry_t
{
uint32be parent;
uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling")
uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child")
uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file")
uint32be hash;
uint32be name_size;
std::string name;
};
struct romfs_fentry_t
{
uint32be parent;
uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling")
uint64be offset;
uint64be size;
uint32be hash;
uint32be name_size;
std::string name;
};
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF

View File

@ -0,0 +1,224 @@
#include "WUHBReader.h"
WUHBReader* WUHBReader::FromPath(const fs::path& path)
{
FileStream* fileIn{FileStream::openFile2(path)};
if (!fileIn)
return nullptr;
WUHBReader* ret = new WUHBReader(fileIn);
if (!ret->CheckMagicValue())
{
delete ret;
return nullptr;
}
if (!ret->ReadHeader())
{
delete ret;
return nullptr;
}
return ret;
}
static const romfs_direntry_t fallbackDirEntry{
.parent = ROMFS_ENTRY_EMPTY,
.listNext = ROMFS_ENTRY_EMPTY,
.dirListHead = ROMFS_ENTRY_EMPTY,
.fileListHead = ROMFS_ENTRY_EMPTY,
.hash = ROMFS_ENTRY_EMPTY,
.name_size = 0,
.name = ""
};
static const romfs_fentry_t fallbackFileEntry{
.parent = ROMFS_ENTRY_EMPTY,
.listNext = ROMFS_ENTRY_EMPTY,
.offset = 0,
.size = 0,
.hash = ROMFS_ENTRY_EMPTY,
.name_size = 0,
.name = ""
};
template<bool File>
const WUHBReader::EntryType<File>& WUHBReader::GetFallback()
{
if constexpr (File)
return fallbackFileEntry;
else
return fallbackDirEntry;
}
template<bool File>
WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const
{
auto fallback = GetFallback<File>();
if(offset == ROMFS_ENTRY_EMPTY)
return fallback;
const char* typeName = File ? "fentry" : "direntry";
EntryType<File> ret;
if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size))
{
cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName);
return fallback;
}
// read the entry
m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset);
auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name));
if (read != offsetof(EntryType<File>, name))
{
cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset);
return fallback;
}
// read the name
ret.name.resize(ret.name_size);
read = m_fileIn->readData(ret.name.data(), ret.name_size);
if (read != ret.name_size)
{
cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName);
return fallback;
}
return ret;
}
romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const
{
return GetEntry<false>(offset);
}
romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const
{
return GetEntry<true>(offset);
}
uint64 WUHBReader::GetFileSize(uint32 entryOffset) const
{
return GetFileEntry(entryOffset).size;
}
uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const
{
const auto fileEntry = GetFileEntry(entryOffset);
if (fileOffset >= fileEntry.size)
return 0;
const uint64 readAmount = std::min(length, fileEntry.size - fileOffset);
const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset;
m_fileIn->SetPosition(wuhbOffset);
return m_fileIn->readData(buffer, readAmount);
}
uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const
{
const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size);
const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs);
const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32);
const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32);
m_fileIn->SetPosition(hash_table_entry_offset);
uint32 tableOffset;
if (!m_fileIn->readU32(tableOffset))
{
cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset);
return ROMFS_ENTRY_EMPTY;
}
return uint32be::from_bevalue(tableOffset);
}
template<bool T>
bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const
{
for (;;)
{
if (entryOffset == ROMFS_ENTRY_EMPTY)
return false;
auto entry = GetEntry<T>(entryOffset);
if (entry.name == targetName)
return true;
entryOffset = entry.hash;
}
return false;
}
uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const
{
uint32 currentEntryOffset = 0;
auto look = [&](const fs::path& part, bool lookInFileHT) {
const auto partString = part.string();
currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT);
if (lookInFileHT)
return SearchHashList<true>(currentEntryOffset, part);
else
return SearchHashList<false>(currentEntryOffset, part);
};
// look for the root entry
if (!look("", false))
return ROMFS_ENTRY_EMPTY;
auto it = path.begin();
while (it != path.end())
{
fs::path part = *it;
++it;
// no need to recurse after trailing forward slash (e.g. directory/)
if (part.empty() && !isFile)
break;
// skip leading forward slash
if (part == "/")
continue;
// if the lookup target is a file and this is the last iteration, look in the file hash table instead.
if (!look(part, it == path.end() && isFile))
return ROMFS_ENTRY_EMPTY;
}
return currentEntryOffset;
}
bool WUHBReader::CheckMagicValue() const
{
uint8 magic[4];
m_fileIn->SetPosition(0);
int read = m_fileIn->readData(magic, 4);
if (read != 4)
{
cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers");
return false;
}
static_assert(sizeof(magic) == s_headerMagicValue.size());
return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0;
}
bool WUHBReader::ReadHeader()
{
m_fileIn->SetPosition(0);
auto read = m_fileIn->readData(&m_header, sizeof(m_header));
auto readSuccess = read == sizeof(m_header);
if (!readSuccess)
cemuLog_log(LogType::Force, "Failed to read WUHB header");
return readSuccess;
}
unsigned char WUHBReader::NormalizeChar(unsigned char c)
{
if (c >= 'a' && c <= 'z')
{
return c + 'A' - 'a';
}
else
{
return c;
}
}
uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len)
{
cemu_assert(path != nullptr || path_len == 0);
uint32 hash = parent ^ 123456789;
for (uint32 i = 0; i < path_len; i++)
{
hash = (hash >> 5) | (hash << 27);
hash ^= NormalizeChar(path[start + i]);
}
return hash;
}

View File

@ -0,0 +1,45 @@
#pragma once
#include <Common/FileStream.h>
#include "RomFSStructs.h"
class WUHBReader
{
public:
static WUHBReader* FromPath(const fs::path& path);
romfs_direntry_t GetDirEntry(uint32 offset) const;
romfs_fentry_t GetFileEntry(uint32 offset) const;
uint64 GetFileSize(uint32 entryOffset) const;
uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const;
uint32 Lookup(const std::filesystem::path& path, bool isFile) const;
private:
WUHBReader(FileStream* file)
: m_fileIn(file)
{
cemu_assert_debug(file != nullptr);
};
WUHBReader() = delete;
romfs_header_t m_header;
std::unique_ptr<FileStream> m_fileIn;
constexpr static std::string_view s_headerMagicValue = "WUHB";
bool ReadHeader();
bool CheckMagicValue() const;
static inline unsigned char NormalizeChar(unsigned char c);
static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len);
template<bool File>
using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>;
template<bool File>
static const EntryType<File>& GetFallback();
template<bool File>
EntryType<File> GetEntry(uint32 offset) const;
template<bool T>
bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const;
uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const;
};

View File

@ -204,6 +204,9 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination
// wua device
bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);
// wuhb device
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority);
// hostFS device
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority);

View File

@ -0,0 +1,151 @@
#include "Filesystem/WUHB/WUHBReader.h"
#include "Cafe/Filesystem/fsc.h"
#include "Cafe/Filesystem/FST/FST.h"
class FSCDeviceWuhbFileCtx : public FSCVirtualFile
{
public:
FSCDeviceWuhbFileCtx(WUHBReader* reader, uint32 entryOffset, uint32 fscType)
: m_wuhbReader(reader), m_entryOffset(entryOffset), m_fscType(fscType)
{
cemu_assert(entryOffset != ROMFS_ENTRY_EMPTY);
if (fscType == FSC_TYPE_DIRECTORY)
{
romfs_direntry_t entry = reader->GetDirEntry(entryOffset);
m_dirIterOffset = entry.dirListHead;
m_fileIterOffset = entry.fileListHead;
}
}
sint32 fscGetType() override
{
return m_fscType;
}
uint64 fscQueryValueU64(uint32 id) override
{
if (m_fscType == FSC_TYPE_FILE)
{
if (id == FSC_QUERY_SIZE)
return m_wuhbReader->GetFileSize(m_entryOffset);
else if (id == FSC_QUERY_WRITEABLE)
return 0; // WUHB images are read-only
else
cemu_assert_error();
}
else
{
cemu_assert_unimplemented();
}
return 0;
}
uint32 fscWriteData(void* buffer, uint32 size) override
{
cemu_assert_error();
return 0;
}
uint32 fscReadData(void* buffer, uint32 size) override
{
if (m_fscType != FSC_TYPE_FILE)
return 0;
auto read = m_wuhbReader->ReadFromFile(m_entryOffset, m_seek, size, buffer);
m_seek += read;
return read;
}
void fscSetSeek(uint64 seek) override
{
m_seek = seek;
}
uint64 fscGetSeek() override
{
if (m_fscType != FSC_TYPE_FILE)
return 0;
return m_seek;
}
void fscSetFileLength(uint64 endOffset) override
{
cemu_assert_error();
}
bool fscDirNext(FSCDirEntry* dirEntry) override
{
if (m_dirIterOffset != ROMFS_ENTRY_EMPTY)
{
romfs_direntry_t entry = m_wuhbReader->GetDirEntry(m_dirIterOffset);
m_dirIterOffset = entry.listNext;
if(entry.name_size > 0)
{
dirEntry->isDirectory = true;
dirEntry->isFile = false;
dirEntry->fileSize = 0;
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
return true;
}
}
if (m_fileIterOffset != ROMFS_ENTRY_EMPTY)
{
romfs_fentry_t entry = m_wuhbReader->GetFileEntry(m_fileIterOffset);
m_fileIterOffset = entry.listNext;
if(entry.name_size > 0)
{
dirEntry->isDirectory = false;
dirEntry->isFile = true;
dirEntry->fileSize = entry.size;
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
return true;
}
}
return false;
}
private:
WUHBReader* m_wuhbReader{};
uint32 m_fscType;
uint32 m_entryOffset = ROMFS_ENTRY_EMPTY;
uint32 m_dirIterOffset = ROMFS_ENTRY_EMPTY;
uint32 m_fileIterOffset = ROMFS_ENTRY_EMPTY;
uint64 m_seek = 0;
};
class fscDeviceWUHB : public fscDeviceC
{
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
{
WUHBReader* reader = (WUHBReader*)ctx;
cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUHB is not supported
bool isFile;
uint32 table_offset = ROMFS_ENTRY_EMPTY;
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
{
table_offset = reader->Lookup(path, false);
isFile = false;
}
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
{
table_offset = reader->Lookup(path, true);
isFile = true;
}
if (table_offset == ROMFS_ENTRY_EMPTY)
{
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
return nullptr;
}
*fscStatus = FSC_STATUS_OK;
return new FSCDeviceWuhbFileCtx(reader, table_offset, isFile ? FSC_TYPE_FILE : FSC_TYPE_DIRECTORY);
}
// singleton
public:
static fscDeviceWUHB& instance()
{
static fscDeviceWUHB _instance;
return _instance;
}
};
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, WUHBReader* wuhbReader, sint32 priority)
{
return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUHB::instance(), wuhbReader, priority) == FSC_STATUS_OK;
}

View File

@ -1,9 +1,12 @@
#include "TitleInfo.h"
#include "Cafe/Filesystem/fscDeviceHostFS.h"
#include "Cafe/Filesystem/WUHB/WUHBReader.h"
#include "Cafe/Filesystem/FST/FST.h"
#include "pugixml.hpp"
#include "Common/FileStream.h"
#include <zarchive/zarchivereader.h>
#include "util/IniParser/IniParser.h"
#include "util/crypto/crc32.h"
#include "config/ActiveSettings.h"
#include "util/helpers/helpers.h"
@ -97,6 +100,7 @@ TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo)
m_isValid = false;
if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS &&
cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE &&
cachedInfo.titleDataFormat != TitleDataFormat::WUHB &&
cachedInfo.titleDataFormat != TitleDataFormat::WUD &&
cachedInfo.titleDataFormat != TitleDataFormat::NUS &&
cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE)
@ -245,6 +249,16 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF
delete zar;
return foundBase;
}
else if (boost::iends_with(filenameStr, ".wuhb"))
{
std::unique_ptr<WUHBReader> reader{WUHBReader::FromPath(path)};
if(reader)
{
formatOut = TitleDataFormat::WUHB;
pathOut = path;
return true;
}
}
// note: Since a Wii U archive file (.wua) contains multiple titles we shouldn't auto-detect them here
// instead TitleInfo has a second constructor which takes a subpath
// unable to determine type by extension, check contents
@ -436,6 +450,23 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder,
return false;
}
}
else if (m_titleFormat == TitleDataFormat::WUHB)
{
if (!m_wuhbreader)
{
m_wuhbreader = WUHBReader::FromPath(m_fullPath);
if (!m_wuhbreader)
return false;
}
bool r = FSCDeviceWUHB_Mount(virtualPath, subfolder, m_wuhbreader, mountPriority);
if (!r)
{
cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder);
delete m_wuhbreader;
m_wuhbreader = nullptr;
return false;
}
}
else
{
cemu_assert_unimplemented();
@ -467,6 +498,12 @@ void TitleInfo::Unmount(std::string_view virtualPath)
if (m_mountpoints.empty())
m_zarchive = nullptr;
}
if (m_wuhbreader)
{
cemu_assert_debug(m_titleFormat == TitleDataFormat::WUHB);
delete m_wuhbreader;
m_wuhbreader = nullptr;
}
}
return;
}
@ -502,6 +539,20 @@ bool TitleInfo::ParseXmlInfo()
auto xmlData = fsc_extractFile(fmt::format("{}meta/meta.xml", mountPath).c_str());
if(xmlData)
m_parsedMetaXml = ParsedMetaXml::Parse(xmlData->data(), xmlData->size());
if(!m_parsedMetaXml)
{
// meta/meta.ini (WUHB)
auto iniData = fsc_extractFile(fmt::format("{}meta/meta.ini", mountPath).c_str());
if (iniData)
m_parsedMetaXml = ParseAromaIni(*iniData);
if(m_parsedMetaXml)
{
m_parsedCosXml = new ParsedCosXml{.argstr = "root.rpx"};
m_parsedAppXml = new ParsedAppXml{m_parsedMetaXml->m_title_id, 0, 0, 0, 0};
}
}
// code/app.xml
xmlData = fsc_extractFile(fmt::format("{}code/app.xml", mountPath).c_str());
if(xmlData)
@ -539,6 +590,34 @@ bool TitleInfo::ParseXmlInfo()
return true;
}
ParsedMetaXml* TitleInfo::ParseAromaIni(std::span<unsigned char> content)
{
IniParser parser{content};
while (parser.NextSection() && parser.GetCurrentSectionName() != "menu")
continue;
if (parser.GetCurrentSectionName() != "menu")
return nullptr;
auto parsed = std::make_unique<ParsedMetaXml>();
const auto author = parser.FindOption("author");
if (author)
parsed->m_publisher[(size_t)CafeConsoleLanguage::EN] = *author;
const auto longName = parser.FindOption("longname");
if (longName)
parsed->m_long_name[(size_t)CafeConsoleLanguage::EN] = *longName;
const auto shortName = parser.FindOption("shortname");
if (shortName)
parsed->m_short_name[(size_t)CafeConsoleLanguage::EN] = *shortName;
auto checksumInput = std::string{*author}.append(*longName).append(*shortName);
parsed->m_title_id = (0x0005000Full<<32) | crc32_calc(checksumInput.data(), checksumInput.length());
return parsed.release();
}
bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData)
{
pugi::xml_document app_doc;
@ -695,6 +774,9 @@ std::string TitleInfo::GetPrintPath() const
case TitleDataFormat::WIIU_ARCHIVE:
tmp.append(" [WUA]");
break;
case TitleDataFormat::WUHB:
tmp.append(" [WUHB]");
break;
default:
break;
}

View File

@ -127,6 +127,7 @@ public:
WUD = 2, // WUD or WUX
WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua)
NUS = 4, // NUS format. Directory with .app files, title.tik and title.tmd
WUHB = 5,
// error
INVALID_STRUCTURE = 0,
};
@ -265,6 +266,7 @@ private:
bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut);
void CalcUID();
void SetInvalidReason(InvalidReason reason);
ParsedMetaXml* ParseAromaIni(std::span<unsigned char> content);
bool ParseAppXml(std::vector<uint8>& appXmlData);
bool m_isValid{ false };
@ -277,6 +279,7 @@ private:
std::vector<std::pair<sint32, std::string>> m_mountpoints;
class FSTVolume* m_wudVolume{};
class ZArchiveReader* m_zarchive{};
class WUHBReader* m_wuhbreader{};
// xml info
bool m_hasParsedXmlFiles{ false };
ParsedMetaXml* m_parsedMetaXml{};

View File

@ -342,7 +342,8 @@ bool _IsKnownFileNameOrExtension(const fs::path& path)
fileExtension == ".wud" ||
fileExtension == ".wux" ||
fileExtension == ".iso" ||
fileExtension == ".wua";
fileExtension == ".wua" ||
fileExtension == ".wuhb";
// note: To detect extracted titles with RPX we rely on the presence of the content,code,meta directory structure
}

View File

@ -643,16 +643,18 @@ void MainWindow::OnFileMenu(wxCommandEvent& event)
if (menuId == MAINFRAME_MENU_ID_FILE_LOAD)
{
const auto wildcard = formatWxString(
"{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf;title.tmd"
"{}|*.wud;*.wux;*.wua;*.wuhb;*.iso;*.rpx;*.elf;title.tmd"
"|{}|*.wud;*.wux;*.iso"
"|{}|title.tmd"
"|{}|*.wua"
"|{}|*.wuhb"
"|{}|*.rpx;*.elf"
"|{}|*",
_("All Wii U files (*.wud, *.wux, *.wua, *.iso, *.rpx, *.elf)"),
_("All Wii U files (*.wud, *.wux, *.wua, *.wuhb, *.iso, *.rpx, *.elf)"),
_("Wii U image (*.wud, *.wux, *.iso, *.wad)"),
_("Wii U NUS content"),
_("Wii U archive (*.wua)"),
_("Wii U homebrew bundle (*.wuhb)"),
_("Wii U executable (*.rpx, *.elf)"),
_("All files (*.*)")
);

View File

@ -1230,6 +1230,16 @@ void wxGameList::AsyncWorkerThread()
if(!titleInfo.Mount(tempMountPath, "", FSC_PRIORITY_BASE))
continue;
auto tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga").c_str());
// try iconTex.tga.gz
if (!tgaData)
{
tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga.gz").c_str());
if (tgaData)
{
auto decompressed = zlibDecompress(*tgaData, 70*1024);
std::swap(tgaData, decompressed);
}
}
bool iconSuccessfullyLoaded = false;
if (tgaData && tgaData->size() > 16)
{

View File

@ -948,6 +948,8 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu
return _("NUS");
case wxTitleManagerList::EntryFormat::WUA:
return _("WUA");
case wxTitleManagerList::EntryFormat::WUHB:
return _("WUHB");
}
return "";
}
@ -1022,6 +1024,9 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt
case TitleInfo::TitleDataFormat::WIIU_ARCHIVE:
entryFormat = EntryFormat::WUA;
break;
case TitleInfo::TitleDataFormat::WUHB:
entryFormat = EntryFormat::WUHB;
break;
case TitleInfo::TitleDataFormat::HOST_FS:
default:
entryFormat = EntryFormat::Folder;

View File

@ -44,6 +44,7 @@ public:
WUD,
NUS,
WUA,
WUHB,
};
// sort by column, if -1 will sort by last column or default (=titleid)

View File

@ -11,6 +11,8 @@
#include <boost/random/uniform_int.hpp>
#include <zlib.h>
#if BOOST_OS_WINDOWS
#include <TlHelp32.h>
@ -437,3 +439,42 @@ std::string GenerateRandomString(const size_t length, const std::string_view cha
return result;
}
std::optional<std::vector<uint8>> zlibDecompress(const std::vector<uint8>& compressed, size_t sizeHint)
{
int err;
std::vector<uint8> decompressed;
size_t outWritten = 0;
size_t bytesPerIteration = sizeHint;
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = compressed.size();
stream.next_in = (Bytef*)compressed.data();
err = inflateInit2(&stream, 32); // 32 is a zlib magic value to enable header detection
if (err != Z_OK)
return {};
do
{
decompressed.resize(decompressed.size() + bytesPerIteration);
const auto availBefore = decompressed.size() - outWritten;
stream.avail_out = availBefore;
stream.next_out = decompressed.data() + outWritten;
err = inflate(&stream, Z_NO_FLUSH);
if (!(err == Z_OK || err == Z_STREAM_END))
{
inflateEnd(&stream);
return {};
}
outWritten += availBefore - stream.avail_out;
bytesPerIteration *= 2;
}
while (err != Z_STREAM_END);
inflateEnd(&stream);
decompressed.resize(stream.total_out);
return decompressed;
}

View File

@ -257,3 +257,5 @@ bool IsWindows81OrGreater();
bool IsWindows10OrGreater();
fs::path GetParentProcess();
std::optional<std::vector<uint8>> zlibDecompress(const std::vector<uint8>& compressed, size_t sizeHint = 32*1024);