Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2378,6 +2378,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/FileLoaders/RamCachingFileLoader.h
Core/FileLoaders/RetryingFileLoader.cpp
Core/FileLoaders/RetryingFileLoader.h
Core/FileLoaders/ZipFileLoader.cpp
Core/FileLoaders/ZipFileLoader.h
Core/MIPS/MIPS.cpp
Core/MIPS/MIPS.h
Core/MIPS/MIPSAnalyst.cpp
Expand Down
9 changes: 9 additions & 0 deletions Common/StringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,15 @@ std::string_view KeepAfterLast(std::string_view s, char c) {
}
}

std::string_view KeepIncludingLast(std::string_view s, char c) {
size_t pos = s.rfind(c);
if (pos != std::string_view::npos) {
return s.substr(pos);
} else {
return s;
}
}

void SkipSpace(const char **ptr) {
while (**ptr && isspace(**ptr)) {
(*ptr)++;
Expand Down
1 change: 1 addition & 0 deletions Common/StringUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ std::string StripSpaces(const std::string &s);
std::string StripQuotes(const std::string &s);

std::string_view KeepAfterLast(std::string_view s, char c);
std::string_view KeepIncludingLast(std::string_view s, char c);

std::string_view StripSpaces(std::string_view s);
std::string_view StripQuotes(std::string_view s);
Expand Down
2 changes: 2 additions & 0 deletions Core/Core.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@
<ClCompile Include="Debugger\WebSocket\SteppingSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\WebSocketUtils.cpp" />
<ClCompile Include="Dialog\PSPOskConstants.cpp" />
<ClCompile Include="FileLoaders\ZipFileLoader.cpp" />
<ClCompile Include="FileSystems\BlobFileSystem.cpp" />
<ClCompile Include="FrameTiming.cpp" />
<ClCompile Include="HLE\AtracCtx.cpp" />
Expand Down Expand Up @@ -1186,6 +1187,7 @@
<ClInclude Include="Debugger\WebSocket\LogBroadcaster.h" />
<ClInclude Include="Debugger\WebSocket\SteppingBroadcaster.h" />
<ClInclude Include="Dialog\PSPOskConstants.h" />
<ClInclude Include="FileLoaders\ZipFileLoader.h" />
<ClInclude Include="FileSystems\BlobFileSystem.h" />
<ClInclude Include="FrameTiming.h" />
<ClInclude Include="HLE\AtracCtx.h" />
Expand Down
6 changes: 6 additions & 0 deletions Core/Core.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,9 @@
<ClCompile Include="HLE\sceReg.cpp">
<Filter>HLE\Libraries</Filter>
</ClCompile>
<ClCompile Include="FileLoaders\ZipFileLoader.cpp">
<Filter>FileLoaders</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
Expand Down Expand Up @@ -2184,6 +2187,9 @@
<ClInclude Include="HLE\sceReg.h">
<Filter>HLE\Libraries</Filter>
</ClInclude>
<ClInclude Include="FileLoaders\ZipFileLoader.h">
<Filter>FileLoaders</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.TXT" />
Expand Down
1 change: 1 addition & 0 deletions Core/FileLoaders/LocalFileLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "Common/CommonTypes.h"
#include "Common/File/Path.h"
#include "Common/StringUtils.h"
#include "Core/Loaders.h"

#ifdef _WIN32
Expand Down
152 changes: 152 additions & 0 deletions Core/FileLoaders/ZipFileLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#include <ext/libzip/zip.h>

#include "Core/FileLoaders/LocalFileLoader.h"
#include "Core/FileLoaders/ZipFileLoader.h"

ZipFileLoader::ZipFileLoader(FileLoader *sourceLoader)
: ProxiedFileLoader(sourceLoader), zipArchive_(nullptr) {
if (!backend_ || !backend_->Exists() || backend_->IsDirectory()) {
// bad
}

zip_error_t error{};
zip_source_t* zipSource = zip_source_function_create([](void* userdata, void* data, zip_uint64_t len, zip_source_cmd_t cmd) -> zip_int64_t {
ZipFileLoader *loader = (ZipFileLoader *)userdata;
return loader->ZipSourceCallback(data, len, cmd);
}, this, &error);
if (!zipSource) {
ERROR_LOG(Log::IO, "Failed to create ZIP source: %s", zip_error_strerror(&error));
return;
}

zipArchive_ = zip_open_from_source(zipSource, ZIP_RDONLY, &error);
if (!zipArchive_) {
ERROR_LOG(Log::IO, "Failed to open ZIP archive: %s", zip_error_strerror(&error));
zip_source_free(zipSource);
}
}

ZipFileLoader::~ZipFileLoader() {
if (dataFile_) {
zip_fclose(dataFile_);
}
if (zipArchive_) {
zip_discard(zipArchive_);
}
if (data_) {
free(data_);
}
}

bool ZipFileLoader::Initialize(int fileIndex) {
_dbg_assert_(!data_);

struct zip_stat zstat;
int retval = zip_stat_index(zipArchive_, fileIndex, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat);
if (retval < 0) {
return false;
}
const char *name = zip_get_name(zipArchive_, fileIndex, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED);
fileExtension_ = KeepIncludingLast(name, '.');

_dbg_assert_(zstat.index == fileIndex);
dataFileSize_ = zstat.size;
dataFile_ = zip_fopen_index(zipArchive_, zstat.index, ZIP_FL_UNCHANGED);
data_ = (u8 *)malloc(dataFileSize_);
return data_ != nullptr;
}

size_t ZipFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) {
if (!dataFile_ || absolutePos < 0 || absolutePos >= dataFileSize_) {
return 0;
}

if (absolutePos + bytes > dataFileSize_) {
// TODO: This could go negative..
bytes = dataFileSize_ - absolutePos;
}

// Decompress until the requested point, filling up data_ as we go. TODO: Do on thread.
while (dataReadPos_ < absolutePos + bytes) {
int remaining = BLOCK_SIZE;
if (dataReadPos_ + remaining > dataFileSize_) {
remaining = dataFileSize_ - dataReadPos_;
}
zip_int64_t retval = zip_fread(dataFile_, data_ + dataReadPos_, remaining);
_dbg_assert_(retval == remaining);
dataReadPos_ += retval;
}

// Perform the read.
memcpy(data, data_ + absolutePos, bytes);
return bytes;
}

zip_int64_t ZipFileLoader::ZipSourceCallback(void *data, zip_uint64_t len, zip_source_cmd_t cmd) {
switch (cmd) {
case ZIP_SOURCE_OPEN:
{
zipReadPos_ = 0;
return 0;
}
case ZIP_SOURCE_READ:
{
size_t readBytes = static_cast<zip_int64_t>(backend_->ReadAt(zipReadPos_, len, data));
zipReadPos_ += readBytes;
return readBytes;
}
case ZIP_SOURCE_SEEK:
{
struct SeekData {
zip_int64_t offset;
int whence;
};
if (len < sizeof(SeekData)) {
return -1; // Invalid argument size
}
const SeekData *seekData = static_cast<const SeekData*>(data);
zip_int64_t new_offset;
switch (seekData->whence) {
case SEEK_SET:
new_offset = seekData->offset;
break;
case SEEK_CUR:
new_offset = zipReadPos_ + seekData->offset;
break;
case SEEK_END:
new_offset = backend_->FileSize() + seekData->offset;
break;
default:
return -1; // Invalid 'whence' value
}
if (new_offset < 0 || new_offset > backend_->FileSize()) {
return -1; // Offset out of bounds
}
zipReadPos_ = new_offset;
return 0;
}
case ZIP_SOURCE_TELL:
return zipReadPos_;
case ZIP_SOURCE_CLOSE:
return 0;
case ZIP_SOURCE_STAT:
{
if (len < sizeof(zip_stat_t)) {
return -1;
}
zip_stat_t* st = static_cast<zip_stat_t*>(data);
zip_stat_init(st);
st->valid = ZIP_STAT_SIZE;
st->size = static_cast<zip_uint64_t>(backend_->FileSize());
return sizeof(zip_stat_t);
}
case ZIP_SOURCE_ERROR:
return -1;
case ZIP_SOURCE_FREE:
return 0;
case ZIP_SOURCE_SUPPORTS:
return zip_source_make_command_bitmap(ZIP_SOURCE_READ, ZIP_SOURCE_SEEK, ZIP_SOURCE_TELL, ZIP_SOURCE_OPEN, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT);
default:
return -1;
}
}
65 changes: 65 additions & 0 deletions Core/FileLoaders/ZipFileLoader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#pragma once

#include <mutex>

#include <ext/libzip/zip.h>

#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/File/Path.h"
#include "Common/StringUtils.h"
#include "Core/Loaders.h"

// Exposes a single (chosen) file from a zip file as another file loader.
// Useful in a bunch of possible chains.
class ZipFileLoader : public ProxiedFileLoader {
public:
ZipFileLoader(FileLoader *sourceLoader);
~ZipFileLoader() override;

zip_t *GetZip() const {
return zipArchive_;
}

bool Initialize(int fileIndex);

bool Exists() override {
return dataFile_ != nullptr;
}

bool IsDirectory() override {
return false;
}

s64 FileSize() override {
return dataFileSize_;
}

Path GetPath() const override {
return backend_->GetPath();
}

size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data, Flags flags = Flags::NONE) override {
return ReadAt(absolutePos, bytes * count, data, flags) / bytes;
}
size_t ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags = Flags::NONE) override;

std::string GetFileExtension() const override {
return fileExtension_;
}

private:
zip_int64_t ZipSourceCallback(void* data, zip_uint64_t len, zip_source_cmd_t cmd);

enum {
BLOCK_SIZE = 65536,
};
zip_t *zipArchive_ = nullptr;
s64 zipReadPos_ = 0;

zip_file_t *dataFile_ = nullptr;
uint8_t *data_ = nullptr; // malloc/free
s64 dataReadPos_ = 0;
s64 dataFileSize_ = 0;
std::string fileExtension_;
};
4 changes: 4 additions & 0 deletions Core/FileSystems/FileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

#pragma once

// TODO: Somehow unify FileSystem, VFS and FileLoaders. Actually, maybe FileSystem and VFS have the most in common
// but file systems should be able to contain FileLoader as files. Then we can do stuff like playing homebrew directly
// out of zip files, and similar tricks.

#include <vector>
#include <string>
#include <cstring>
Expand Down
Loading