mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 19:07:01 +02:00
Zarchive Loading (#724)
* Simplify archive loading and harden filesystem integration * Restore directory entry name length assertion * Keep graphics-dependent filesystem tests out of CI --------- Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/archive.h"
|
||||
#include "common/file.h"
|
||||
#include "ArchiveTestFixture.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "common/threads.h"
|
||||
@@ -214,6 +216,119 @@ void CheckUnmappedPaths(const std::filesystem::path &root) {
|
||||
FileSystem::Umount("/app0");
|
||||
}
|
||||
|
||||
void CheckArchiveMount(const std::filesystem::path &root) {
|
||||
const auto archive = root / u8"game-日本語.zar";
|
||||
std::vector<uint8_t> payload(2 * 64 * 1024 + 33);
|
||||
for (size_t i = 0; i < payload.size(); ++i) {
|
||||
payload[i] = static_cast<uint8_t>(i * 37 + 11);
|
||||
}
|
||||
Check(ArchiveTests::CreateArchive(archive, payload), "create mounted archive fixture");
|
||||
const auto archive_root = Common::MakeArchivePath(archive);
|
||||
const auto host_member = archive_root / "assets/subdir/data.bin";
|
||||
constexpr char GuestMember[] = "/app0/assets/subdir/data.bin";
|
||||
FileSystem::Mount(archive_root, "/app0");
|
||||
Check(FileSystem::GetRealFilename(GuestMember) == host_member &&
|
||||
FileSystem::GetRealFilename("/app01/eboot.bin").empty(),
|
||||
"resolve archive mounts at path-component boundaries");
|
||||
Check(FileSystem::KernelOpen("/app0/../outside.bin", 0, 0) ==
|
||||
Libs::LibKernel::KERNEL_ERROR_ENOENT &&
|
||||
FileSystem::KernelOpen("/app0/missing.bin", 0, 0) ==
|
||||
Libs::LibKernel::KERNEL_ERROR_ENOENT,
|
||||
"reject traversal outside an archive and missing members");
|
||||
|
||||
FileSystem::FileStat path_stat{}, descriptor_stat{};
|
||||
Check(FileSystem::KernelStat(GuestMember, &path_stat) == OK &&
|
||||
path_stat.st_size == payload.size() &&
|
||||
path_stat.st_size == Common::File::Size(host_member) &&
|
||||
FileSystem::KernelCheckReachability(GuestMember) == OK,
|
||||
"archive path stat and reachability agree with Common::File");
|
||||
const int fd = FileSystem::KernelOpen("/app0/ASSETS/subdir/DATA.BIN", 0, 0);
|
||||
Check(fd >= 3 && FileSystem::KernelFstat(fd, &descriptor_stat) == OK &&
|
||||
descriptor_stat.st_size == path_stat.st_size &&
|
||||
descriptor_stat.st_mode == path_stat.st_mode,
|
||||
"case-insensitive archive open and descriptor stat agree");
|
||||
std::array<uint8_t, 97> bytes{};
|
||||
constexpr int64_t Offset = 64 * 1024 - 19;
|
||||
Check(FileSystem::KernelPread(fd, bytes.data(), bytes.size(), Offset) == bytes.size() &&
|
||||
std::equal(bytes.begin(), bytes.end(), payload.begin() + Offset) &&
|
||||
FileSystem::KernelLseek(fd, 0, 1) == 0,
|
||||
"archive pread crosses a compression block without moving position");
|
||||
Check(FileSystem::KernelLseek(fd, Offset, 0) == Offset &&
|
||||
FileSystem::KernelRead(fd, bytes.data(), bytes.size()) == bytes.size() &&
|
||||
std::equal(bytes.begin(), bytes.end(), payload.begin() + Offset) &&
|
||||
FileSystem::KernelLseek(fd, 0, 1) == Offset + bytes.size(),
|
||||
"archive seek and sequential read share descriptor position");
|
||||
Check(FileSystem::KernelLseek(fd, -9, 2) == payload.size() - 9 &&
|
||||
FileSystem::KernelRead(fd, bytes.data(), bytes.size()) == 9 &&
|
||||
std::equal(bytes.begin(), bytes.begin() + 9, payload.end() - 9) &&
|
||||
FileSystem::KernelRead(fd, bytes.data(), bytes.size()) == 0,
|
||||
"archive reads stop at the member boundary");
|
||||
Check(FileSystem::KernelWrite(fd, bytes.data(), 1) ==
|
||||
Libs::LibKernel::KERNEL_ERROR_EBADF &&
|
||||
FileSystem::KernelPwrite(fd, bytes.data(), 1, 0) ==
|
||||
Libs::LibKernel::KERNEL_ERROR_EBADF &&
|
||||
FileSystem::KernelFtruncate(fd, 0) ==
|
||||
Libs::LibKernel::KERNEL_ERROR_EBADF,
|
||||
"archive read-only descriptors reject write and truncate");
|
||||
|
||||
const auto unicode_guest = std::string("/app0/") + std::string(ArchiveTests::UnicodeFilename);
|
||||
const int unicode = FileSystem::KernelOpen(unicode_guest.c_str(), 0, 0);
|
||||
Check(unicode >= 3 && FileSystem::KernelRead(unicode, bytes.data(), bytes.size()) ==
|
||||
ArchiveTests::Eboot.size() &&
|
||||
std::memcmp(bytes.data(), ArchiveTests::Eboot.data(), ArchiveTests::Eboot.size()) == 0,
|
||||
"open and read a Unicode archive member");
|
||||
Check(FileSystem::KernelClose(unicode) == OK, "close Unicode archive member");
|
||||
|
||||
const int directory = FileSystem::KernelOpen("/app0/", 0x00020000, 0);
|
||||
Check(directory >= 3, "open mounted archive directory");
|
||||
std::array<char, 512> block{};
|
||||
const auto expected_entries = Common::File::GetDirEntries(archive_root);
|
||||
size_t entries_seen = 0;
|
||||
for (;;) {
|
||||
const int length = FileSystem::KernelGetdents(directory, block.data(), block.size());
|
||||
Check(length >= 0 && length <= block.size(), "read archive directory records");
|
||||
if (length == 0) {
|
||||
break;
|
||||
}
|
||||
for (size_t offset = 0; offset < static_cast<size_t>(length);) {
|
||||
uint16_t record_length = 0;
|
||||
Check(length - offset >= 8, "archive directory record header fits");
|
||||
std::memcpy(&record_length, block.data() + offset + 4, sizeof(record_length));
|
||||
const auto name_length = static_cast<uint8_t>(block[offset + 7]);
|
||||
Check(record_length >= 9 + name_length && record_length <= length - offset,
|
||||
"archive directory record fits");
|
||||
const std::string_view name(block.data() + offset + 8, name_length);
|
||||
Check(std::any_of(expected_entries.begin(), expected_entries.end(), [&](const auto &entry) {
|
||||
return entry.name == name && block[offset + 6] == (entry.is_file ? 8 : 4);
|
||||
}), "guest directory entry matches Common::File name and type");
|
||||
++entries_seen;
|
||||
offset += record_length;
|
||||
}
|
||||
}
|
||||
Check(entries_seen == expected_entries.size(), "guest enumerates every archive directory entry");
|
||||
Check(FileSystem::KernelClose(directory) == OK, "close archive directory");
|
||||
|
||||
for (const int flags : {1, 2, 0x0200, 0x0400}) {
|
||||
Check(FileSystem::KernelOpen(GuestMember, flags, 0777) ==
|
||||
Libs::LibKernel::KERNEL_ERROR_EROFS,
|
||||
"archive rejects write, create and truncate open flags");
|
||||
}
|
||||
Check(FileSystem::KernelUnlink(GuestMember) == Libs::LibKernel::KERNEL_ERROR_EROFS &&
|
||||
FileSystem::KernelMkdir("/app0/new-dir", 0777) == Libs::LibKernel::KERNEL_ERROR_EROFS &&
|
||||
FileSystem::KernelRmdir("/app0/assets") == Libs::LibKernel::KERNEL_ERROR_EROFS &&
|
||||
FileSystem::KernelRename(GuestMember, "/app0/renamed.bin") ==
|
||||
Libs::LibKernel::KERNEL_ERROR_EROFS,
|
||||
"archive mount rejects path mutations");
|
||||
FileSystem::Umount("/app0");
|
||||
Check(FileSystem::GetRealFilename(GuestMember).empty() &&
|
||||
FileSystem::KernelOpen(GuestMember, 0, 0) == Libs::LibKernel::KERNEL_ERROR_ENOENT &&
|
||||
FileSystem::KernelLseek(fd, 0, 0) == 0 &&
|
||||
FileSystem::KernelRead(fd, bytes.data(), bytes.size()) == bytes.size() &&
|
||||
std::equal(bytes.begin(), bytes.end(), payload.begin()),
|
||||
"unmount hides archive paths while open descriptors retain the reader");
|
||||
Check(FileSystem::KernelClose(fd) == OK, "close last archive descriptor");
|
||||
}
|
||||
|
||||
void CheckUnicodePaths(const std::filesystem::path &root) {
|
||||
constexpr std::string_view HostDirectory =
|
||||
"Test\xc3\xa9-\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e";
|
||||
@@ -567,7 +682,7 @@ void CheckSocketWakeup() {
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
int main(int, char**) {
|
||||
Common::InitializeThreads();
|
||||
Common::Subsystems subsystems;
|
||||
subsystems.Initialize<Config::Lifecycle>();
|
||||
@@ -589,6 +704,7 @@ int main() {
|
||||
FileSystem::Initialize();
|
||||
CheckMountRoot(temporary.Path());
|
||||
CheckUnmappedPaths(temporary.Path());
|
||||
CheckArchiveMount(temporary.Path());
|
||||
CheckUnicodePaths(temporary.Path());
|
||||
CheckUnicodeLogPath(temporary.Path());
|
||||
CheckDirectoryStream(temporary.Path());
|
||||
|
||||
Reference in New Issue
Block a user