mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-23 03:15:58 -04:00
add better support for dependencies
- create_geode_file now redirects to a new function called setup_geode_mod - setup_geode_mod auto-links loader - setup_geode_mod invokes CLI (if v1.4.0+) to automatically check your dependencies and install them + link their headers and libs to your project - fix Result::expect not working on non-copiable types - add in-memory functions for file::Zip and file::Unzip - ComparableVersionInfo now always returns false if major versions dont match
This commit is contained in:
parent
ae1eb8bb71
commit
f32aaa8b12
15 changed files with 388 additions and 200 deletions
|
@ -1,4 +1,4 @@
|
|||
set(GEODE_CLI_MINIMUM_VERSION 1.0.5)
|
||||
set(GEODE_CLI_MINIMUM_VERSION 1.2.0)
|
||||
|
||||
# for passing CLI through CMake arguments
|
||||
if (DEFINED CLI_PATH)
|
||||
|
@ -12,7 +12,7 @@ endif()
|
|||
|
||||
# Check if CLI was found
|
||||
if (GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
message(STATUS "Unable to find Geode CLI")
|
||||
message(WARNING "Unable to find Geode CLI")
|
||||
else()
|
||||
# `geode --version` returns `geode x.x.x\n` so gotta do some wacky shit
|
||||
execute_process(
|
||||
|
@ -36,40 +36,30 @@ else()
|
|||
message(STATUS "Found Geode CLI: ${GEODE_CLI} (version ${GEODE_CLI_VERSION})")
|
||||
endif()
|
||||
|
||||
function(create_geode_file_old proname)
|
||||
message(
|
||||
DEPRECATION
|
||||
"create_geode_file_old has been deprecated. "
|
||||
"Please update to the new (v1.x.x) version of Geode CLI."
|
||||
)
|
||||
# Clear cache of mods being built so mods from earlier
|
||||
# configures dont appear on the list
|
||||
set(GEODE_MODS_BEING_BUILT "" CACHE INTERNAL "GEODE_MODS_BEING_BUILT")
|
||||
|
||||
# todo: add EXTERNAL argument that takes a list of external mod ids
|
||||
# current workaround is to manually append to GEODE_MODS_BEING_BUILT before
|
||||
# calling setup_geode_mod if the mod depends on external dependencies that
|
||||
# aren't being built
|
||||
function(setup_geode_mod proname)
|
||||
# Get DONT_INSTALL argument
|
||||
set(options DONT_INSTALL)
|
||||
set(multiValueArgs EXTERNALS)
|
||||
cmake_parse_arguments(SETUP_GEODE_MOD "${options}" "" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if (GEODE_DISABLE_CLI_CALLS)
|
||||
message("Skipping creating geode file")
|
||||
message("Skipping setting up geode mod ${proname}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
message(STATUS "Creating geode file")
|
||||
|
||||
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
message(WARNING "create_geode_file called, but Geode CLI was not found - You will need to manually package the .geode files")
|
||||
else()
|
||||
|
||||
add_custom_target(${proname}_PACKAGE ALL
|
||||
DEPENDS ${proname}
|
||||
COMMAND ${GEODE_CLI} pkg ${CMAKE_CURRENT_SOURCE_DIR} $<TARGET_FILE_DIR:${proname}> $<TARGET_FILE_DIR:${proname}>/${proname}.geode --install --cached
|
||||
VERBATIM USES_TERMINAL
|
||||
message(FATAL_ERROR
|
||||
"setup_geode_mod called, but Geode CLI was not found - "
|
||||
"Please install CLI: https://docs.geode-sdk.org/info/installcli/"
|
||||
)
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
function(create_geode_file proname)
|
||||
# Get DONT_INSTALL argument
|
||||
set(options DONT_INSTALL)
|
||||
cmake_parse_arguments(CREATE_GEODE_FILE "${options}" "" "" ${ARGN})
|
||||
|
||||
if (GEODE_DISABLE_CLI_CALLS)
|
||||
message("Skipping creating geode file for ${proname}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
@ -77,31 +67,91 @@ function(create_geode_file proname)
|
|||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/mod.json ${CMAKE_CURRENT_BINARY_DIR}/what.txt)
|
||||
set_target_properties(${proname} PROPERTIES CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/mod.json)
|
||||
|
||||
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
message(WARNING "create_geode_file called, but Geode CLI was not found - You will need to manually package the .geode files")
|
||||
return()
|
||||
message(STATUS "Setting up ${proname}")
|
||||
|
||||
# Read mod.json
|
||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/mod.json" MOD_JSON)
|
||||
string(JSON MOD_ID GET "${MOD_JSON}" "id")
|
||||
string(JSON MOD_VERSION GET "${MOD_JSON}" "version")
|
||||
string(JSON MOD_HAS_API ERROR_VARIABLE MOD_DOESNT_HAVE_API GET "${MOD_JSON}" "api")
|
||||
|
||||
# Add this mod to the list of known externals mods
|
||||
list(APPEND GEODE_MODS_BEING_BUILT "${MOD_ID}:${MOD_VERSION}")
|
||||
# Ensure that the list of mods being built is global (persists between setup_geode_mod calls)
|
||||
set(GEODE_MODS_BEING_BUILT ${GEODE_MODS_BEING_BUILT} CACHE INTERNAL "GEODE_MODS_BEING_BUILT")
|
||||
|
||||
# Add function arg externals to the list but don't cache that
|
||||
if (SETUP_GEODE_MOD_EXTERNALS)
|
||||
list(APPEND GEODE_MODS_BEING_BUILT ${SETUP_GEODE_MOD_EXTERNALS})
|
||||
endif()
|
||||
|
||||
message(STATUS "Creating geode file for ${proname}")
|
||||
|
||||
# Check dependencies using CLI
|
||||
execute_process(
|
||||
COMMAND ${GEODE_CLI} package get-id ${CMAKE_CURRENT_SOURCE_DIR} --raw
|
||||
OUTPUT_VARIABLE MOD_ID
|
||||
COMMAND ${GEODE_CLI} package setup ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}
|
||||
--externals ${GEODE_MODS_BEING_BUILT}
|
||||
)
|
||||
|
||||
if (CREATE_GEODE_FILE_DONT_INSTALL)
|
||||
|
||||
# Check if --install should be passed
|
||||
if (SETUP_GEODE_MOD_DONT_INSTALL)
|
||||
message(STATUS "Skipping installing ${proname}")
|
||||
set(INSTALL_ARG "")
|
||||
else()
|
||||
set(INSTALL_ARG "--install")
|
||||
endif()
|
||||
|
||||
# The lib binary should be passed only if some headers were provided
|
||||
if (MOD_HAS_API)
|
||||
message(STATUS "Including library & headers with ${proname}")
|
||||
set(HAS_HEADERS On)
|
||||
else()
|
||||
set(HAS_HEADERS Off)
|
||||
endif()
|
||||
|
||||
# Add package target + make output name the mod id
|
||||
set_target_properties(${proname} PROPERTIES PREFIX "")
|
||||
set_target_properties(${proname} PROPERTIES OUTPUT_NAME ${MOD_ID})
|
||||
add_custom_target(${proname}_PACKAGE ALL
|
||||
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
|
||||
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR} --binary $<TARGET_FILE:${proname}> --output $<TARGET_FILE_DIR:${proname}>/${proname}.geode ${INSTALL_ARG}
|
||||
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
--binary $<TARGET_FILE:${proname}> $<$<BOOL:${HAS_HEADERS}>:$<TARGET_LINKER_FILE:${proname}>>
|
||||
--output $<TARGET_FILE_DIR:${proname}>/${proname}.geode
|
||||
${INSTALL_ARG}
|
||||
VERBATIM USES_TERMINAL
|
||||
)
|
||||
|
||||
# Add dependency dir to include path
|
||||
if (EXISTS "${CMAKE_CURRENT_BINARY_DIR}/geode-deps")
|
||||
|
||||
if (WIN32)
|
||||
file(GLOB libs ${CMAKE_CURRENT_BINARY_DIR}/geode-deps/*/*.lib)
|
||||
elseif (APPLE)
|
||||
file(GLOB libs ${CMAKE_CURRENT_BINARY_DIR}/geode-deps/*/*.dylib)
|
||||
else()
|
||||
message(FATAL_ERROR "Library extension not defined on this platform")
|
||||
endif()
|
||||
|
||||
message(STATUS "libs: ${libs}")
|
||||
|
||||
# Link all libs
|
||||
target_include_directories(${proname} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/geode-deps")
|
||||
target_link_libraries(${proname} ${libs})
|
||||
|
||||
endif()
|
||||
|
||||
# Link Geode to the mod
|
||||
target_link_libraries(${proname} geode-sdk)
|
||||
|
||||
endfunction()
|
||||
|
||||
function(create_geode_file proname)
|
||||
# todo: deprecate at some point ig
|
||||
# message(DEPRECATION
|
||||
# "create_geode_file has been replaced with setup_geode_mod - "
|
||||
# "please replace the function call"
|
||||
# )
|
||||
|
||||
# forward all args
|
||||
setup_geode_mod(${proname} ${ARGN})
|
||||
endfunction()
|
||||
|
||||
function(package_geode_resources proname src dest)
|
||||
|
|
|
@ -133,12 +133,15 @@ if (NOT GEODE_BUILDING_DOCS)
|
|||
# Markdown support
|
||||
CPMAddPackage("gh:mity/md4c#e9ff661")
|
||||
|
||||
# Zip support (needed for in-memory streams, which zlib's minizip doesn't support)
|
||||
CPMAddPackage("gh:zlib-ng/minizip-ng#cee6d8c")
|
||||
|
||||
# Regex support
|
||||
CPMAddPackage("gh:google/re2#954656f")
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${md4c_SOURCE_DIR}/src)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} md4c re2)
|
||||
target_link_libraries(${PROJECT_NAME} md4c re2 minizip)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} z TulipHook geode-sdk mat-json)
|
||||
|
|
|
@ -125,6 +125,10 @@ namespace geode {
|
|||
* Whether this mod has to be loaded before the loading screen or not
|
||||
*/
|
||||
bool needsEarlyLoad = false;
|
||||
/**
|
||||
* Whether this mod is an API or not
|
||||
*/
|
||||
bool isAPI = false;
|
||||
/**
|
||||
* Create ModInfo from an unzipped .geode package
|
||||
*/
|
||||
|
|
|
@ -157,6 +157,21 @@ namespace geode {
|
|||
}
|
||||
|
||||
template <class... Args>
|
||||
requires(std::is_constructible_v<T, T &&>)
|
||||
[[nodiscard]] Result<T, std::string> expect(std::string const& format, Args&&... args) {
|
||||
if (this->isErr()) {
|
||||
return impl::Failure<std::string>(fmt::format(
|
||||
fmt::runtime(format), std::forward<Args>(args)...,
|
||||
fmt::arg("error", this->unwrapErr())
|
||||
));
|
||||
}
|
||||
else {
|
||||
return std::move(*this);
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
requires(std::is_constructible_v<T, T const&>)
|
||||
[[nodiscard]] Result<T, std::string> expect(std::string const& format, Args&&... args)
|
||||
const {
|
||||
if (this->isErr()) {
|
||||
|
|
|
@ -117,6 +117,10 @@ namespace geode {
|
|||
static Result<ComparableVersionInfo> parse(std::string const& string);
|
||||
|
||||
constexpr bool compare(VersionInfo const& version) const {
|
||||
// opposing major versions never match
|
||||
if (m_version.getMajor() != version.getMajor()) {
|
||||
return false;
|
||||
}
|
||||
switch (m_compare) {
|
||||
case VersionCompare::Exact: return m_version == version; break;
|
||||
case VersionCompare::LessEq: return m_version <= version; break;
|
||||
|
|
|
@ -33,6 +33,8 @@ namespace geode::utils::file {
|
|||
ghc::filesystem::path const& path, bool recursive = false
|
||||
);
|
||||
|
||||
class Unzip;
|
||||
|
||||
class GEODE_DLL Zip final {
|
||||
public:
|
||||
using Path = ghc::filesystem::path;
|
||||
|
@ -47,6 +49,9 @@ namespace geode::utils::file {
|
|||
Result<> addAllFromRecurse(
|
||||
Path const& dir, Path const& entry
|
||||
);
|
||||
|
||||
// for sharing Impl
|
||||
friend class Unzip;
|
||||
|
||||
public:
|
||||
Zip(Zip const&) = delete;
|
||||
|
@ -59,10 +64,22 @@ namespace geode::utils::file {
|
|||
static Result<Zip> create(Path const& file);
|
||||
|
||||
/**
|
||||
* Path to the opened zip
|
||||
* Create zipper for in-memory data
|
||||
*/
|
||||
static Result<Zip> create();
|
||||
|
||||
/**
|
||||
* Path to the created zip
|
||||
* @returns The path to the zip that is being created, or an empty path
|
||||
* if the zip was opened in memory
|
||||
*/
|
||||
Path getPath() const;
|
||||
|
||||
/**
|
||||
* Get the zipped data
|
||||
*/
|
||||
ByteVector getData() const;
|
||||
|
||||
/**
|
||||
* Add an entry to the zip with data
|
||||
*/
|
||||
|
@ -95,7 +112,7 @@ namespace geode::utils::file {
|
|||
|
||||
class GEODE_DLL Unzip final {
|
||||
private:
|
||||
class Impl;
|
||||
using Impl = Zip::Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
||||
Unzip();
|
||||
|
@ -113,8 +130,15 @@ namespace geode::utils::file {
|
|||
*/
|
||||
static Result<Unzip> create(Path const& file);
|
||||
|
||||
/**
|
||||
* Create unzipper for data in-memory
|
||||
*/
|
||||
static Result<Unzip> create(ByteVector const& data);
|
||||
|
||||
/**
|
||||
* Path to the opened zip
|
||||
* @returns The path to the zip that is being read, or an empty path
|
||||
* if the zip was opened in memory
|
||||
*/
|
||||
Path getPath() const;
|
||||
|
||||
|
|
Binary file not shown.
|
@ -46,7 +46,6 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
auto root = checker.root("[mod.json]").obj();
|
||||
|
||||
root.addKnownKey("geode");
|
||||
root.addKnownKey("binary");
|
||||
|
||||
root.needs("id").validate(&ModInfo::validateID).into(info.id);
|
||||
root.needs("version").into(info.version);
|
||||
|
@ -57,6 +56,9 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
root.has("toggleable").into(info.supportsDisabling);
|
||||
root.has("unloadable").into(info.supportsUnloading);
|
||||
root.has("early-load").into(info.needsEarlyLoad);
|
||||
if (root.has("api")) {
|
||||
info.isAPI = true;
|
||||
}
|
||||
|
||||
for (auto& dep : root.has("dependencies").iterate()) {
|
||||
auto obj = dep.obj();
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
#include <../support/zip_support/ZipUtils.h>
|
||||
#include <../support/zip_support/ioapi.h>
|
||||
#include <../support/zip_support/unzip.h>
|
||||
#include <../support/zip_support/zip.h>
|
||||
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/utils/file.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <Geode/utils/string.hpp>
|
||||
#include <json.hpp>
|
||||
#include <fstream>
|
||||
#include <mz.h>
|
||||
#include <mz_os.h>
|
||||
#include <mz_strm.h>
|
||||
#include <mz_strm_os.h>
|
||||
#include <mz_strm_mem.h>
|
||||
#include <mz_zip.h>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
using namespace geode::utils::file;
|
||||
|
@ -139,100 +142,249 @@ static constexpr auto MAX_ENTRY_PATH_LEN = 256;
|
|||
|
||||
struct ZipEntry {
|
||||
bool isDirectory;
|
||||
unz_file_pos pos;
|
||||
ZPOS64_T compressedSize;
|
||||
ZPOS64_T uncompressedSize;
|
||||
int64_t compressedSize;
|
||||
int64_t uncompressedSize;
|
||||
};
|
||||
|
||||
class file::Unzip::Impl final {
|
||||
class Zip::Impl final {
|
||||
public:
|
||||
using Path = Unzip::Path;
|
||||
using Path = Zip::Path;
|
||||
|
||||
private:
|
||||
unzFile m_zip;
|
||||
Path m_zipPath;
|
||||
void* m_handle = nullptr;
|
||||
void* m_stream = nullptr;
|
||||
int32_t m_mode;
|
||||
std::variant<Path, ByteVector> m_srcDest;
|
||||
std::unordered_map<Path, ZipEntry> m_entries;
|
||||
|
||||
public:
|
||||
Result<> init() {
|
||||
// open stream from file
|
||||
if (std::holds_alternative<Path>(m_srcDest)) {
|
||||
auto& path = std::get<Path>(m_srcDest);
|
||||
// open file
|
||||
if (!mz_stream_os_create(&m_stream)) {
|
||||
return Err("Unable to open file");
|
||||
}
|
||||
if (mz_stream_os_open(
|
||||
m_stream,
|
||||
reinterpret_cast<const char*>(path.u8string().c_str()),
|
||||
m_mode
|
||||
) != MZ_OK) {
|
||||
return Err("Unable to read file");
|
||||
}
|
||||
}
|
||||
// open stream from memory stream
|
||||
else {
|
||||
auto& src = std::get<ByteVector>(m_srcDest);
|
||||
if (!mz_stream_mem_create(&m_stream)) {
|
||||
return Err("Unable to create memory stream");
|
||||
}
|
||||
// mz_stream_mem_set_buffer doesn't memcpy so we gotta store the data
|
||||
// elsewhere
|
||||
if (m_mode == MZ_OPEN_MODE_READ) {
|
||||
mz_stream_mem_set_buffer(m_stream, src.data(), src.size());
|
||||
}
|
||||
else {
|
||||
mz_stream_mem_set_grow_size(m_stream, 128 * 1024);
|
||||
}
|
||||
if (mz_stream_open(m_stream, nullptr, m_mode) != MZ_OK) {
|
||||
return Err("Unable to read memory stream");
|
||||
}
|
||||
}
|
||||
|
||||
// open zip
|
||||
if (!mz_zip_create(&m_handle)) {
|
||||
return Err("Unable to create zip handler");
|
||||
}
|
||||
if (mz_zip_open(m_handle, m_stream, m_mode) != MZ_OK) {
|
||||
return Err("Unable to open zip");
|
||||
}
|
||||
|
||||
// get list of entries
|
||||
if (!this->loadEntries()) {
|
||||
return Err("Unable to read zip");
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
bool loadEntries() {
|
||||
// Clear old entries
|
||||
m_entries.clear();
|
||||
|
||||
char fileName[MAX_ENTRY_PATH_LEN + 1];
|
||||
unz_file_info64 fileInfo;
|
||||
|
||||
// Read first file
|
||||
if (unzGoToFirstFile64(m_zip, &fileInfo, fileName, sizeof(fileName) - 1)) {
|
||||
uint64_t entryCount;
|
||||
if (mz_zip_get_number_entry(m_handle, &entryCount) != MZ_OK) {
|
||||
return false;
|
||||
}
|
||||
// Loop over all files
|
||||
while (true) {
|
||||
// Read file and add to entries
|
||||
unz_file_pos pos;
|
||||
if (unzGetFilePos(m_zip, &pos) == UNZ_OK) {
|
||||
auto len = strlen(fileName);
|
||||
m_entries.insert({
|
||||
fileName,
|
||||
ZipEntry {
|
||||
.isDirectory =
|
||||
fileInfo.uncompressed_size == 0 &&
|
||||
len > 0 &&
|
||||
(fileName[len - 1] == '/' || fileName[len - 1] == '\\'),
|
||||
.pos = pos,
|
||||
.compressedSize = fileInfo.compressed_size,
|
||||
.uncompressedSize = fileInfo.uncompressed_size,
|
||||
},
|
||||
});
|
||||
}
|
||||
// Read next file, or break on error
|
||||
if (unzGoToNextFile64(m_zip, &fileInfo, fileName, sizeof(fileName) - 1) != UNZ_OK) {
|
||||
break;
|
||||
auto err = mz_zip_goto_first_entry(m_handle) != MZ_OK;
|
||||
while (err == MZ_OK) {
|
||||
mz_zip_file* info = nullptr;
|
||||
if (mz_zip_entry_get_info(m_handle, &info) != MZ_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Path filePath;
|
||||
filePath.assign(info->filename, info->filename + info->filename_size);
|
||||
m_entries.insert({ filePath, ZipEntry {
|
||||
.isDirectory = mz_zip_entry_is_dir(m_handle) == MZ_OK,
|
||||
.compressedSize = info->compressed_size,
|
||||
.uncompressedSize = info->uncompressed_size,
|
||||
} });
|
||||
|
||||
err = mz_zip_goto_next_entry(m_handle);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Result<> mzTry(int32_t code) {
|
||||
if (code == MZ_OK) {
|
||||
return Ok();
|
||||
}
|
||||
else {
|
||||
return Err(std::to_string(code));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static Result<std::unique_ptr<Impl>> inFile(Path const& path, int32_t mode) {
|
||||
auto ret = std::make_unique<Impl>();
|
||||
ret->m_mode = mode;
|
||||
ret->m_srcDest = path;
|
||||
GEODE_UNWRAP(ret->init());
|
||||
return Ok(std::move(ret));
|
||||
}
|
||||
|
||||
static Result<std::unique_ptr<Impl>> fromMemory(ByteVector const& raw) {
|
||||
auto ret = std::make_unique<Impl>();
|
||||
ret->m_mode = MZ_OPEN_MODE_READ;
|
||||
ret->m_srcDest = raw;
|
||||
GEODE_UNWRAP(ret->init());
|
||||
return Ok(std::move(ret));
|
||||
}
|
||||
|
||||
static Result<std::unique_ptr<Impl>> intoMemory() {
|
||||
auto ret = std::make_unique<Impl>();
|
||||
ret->m_mode = MZ_OPEN_MODE_CREATE;
|
||||
ret->m_srcDest = ByteVector();
|
||||
GEODE_UNWRAP(ret->init());
|
||||
return Ok(std::move(ret));
|
||||
}
|
||||
|
||||
Result<ByteVector> extract(Path const& name) {
|
||||
if (!m_entries.count(name)) {
|
||||
return Err("Entry not found");
|
||||
}
|
||||
|
||||
auto entry = m_entries.at(name);
|
||||
|
||||
if (entry.isDirectory) {
|
||||
return Err("Entry is directory");
|
||||
}
|
||||
|
||||
if (unzGoToFilePos(m_zip, &entry.pos) != UNZ_OK) {
|
||||
return Err("Unable to navigate to entry");
|
||||
}
|
||||
if (unzOpenCurrentFile(m_zip) != UNZ_OK) {
|
||||
return Err("Unable to open entry");
|
||||
}
|
||||
GEODE_UNWRAP(
|
||||
mzTry(mz_zip_goto_first_entry(m_handle))
|
||||
.expect("Unable to navigate to first entry (code {error})")
|
||||
);
|
||||
|
||||
GEODE_UNWRAP(
|
||||
mzTry(mz_zip_locate_entry(
|
||||
m_handle,
|
||||
reinterpret_cast<const char*>(name.u8string().c_str()),
|
||||
1
|
||||
)).expect("Unable to navigate to entry (code {error})")
|
||||
);
|
||||
|
||||
GEODE_UNWRAP(
|
||||
mzTry(mz_zip_entry_read_open(m_handle, 0, nullptr))
|
||||
.expect("Unable to open entry (code {error})")
|
||||
);
|
||||
|
||||
ByteVector res;
|
||||
res.resize(entry.uncompressedSize);
|
||||
auto size = unzReadCurrentFile(m_zip, res.data(), entry.uncompressedSize);
|
||||
if (size < 0 || size != entry.uncompressedSize) {
|
||||
unzCloseCurrentFile(m_zip);
|
||||
return Err("Unable to extract entry");
|
||||
auto read = mz_zip_entry_read(m_handle, res.data(), entry.uncompressedSize);
|
||||
if (read < 0) {
|
||||
mz_zip_entry_close(m_handle);
|
||||
return Err("Unable to read entry (code " + std::to_string(read) + ")");
|
||||
}
|
||||
unzCloseCurrentFile(m_zip);
|
||||
mz_zip_entry_close(m_handle);
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
std::unordered_map<Path, ZipEntry>& entries() {
|
||||
Result<> addFolder(Path const& path) {
|
||||
auto strPath = path.u8string();
|
||||
if (!strPath.ends_with(u8"/") && !strPath.ends_with(u8"\\")) {
|
||||
strPath += u8"/";
|
||||
}
|
||||
|
||||
mz_zip_file info = { 0 };
|
||||
info.version_madeby = MZ_VERSION_MADEBY;
|
||||
info.compression_method = MZ_COMPRESS_METHOD_DEFLATE;
|
||||
info.filename = reinterpret_cast<const char*>(strPath.c_str());
|
||||
info.uncompressed_size = 0;
|
||||
info.flag = MZ_ZIP_FLAG_UTF8;
|
||||
info.external_fa = FILE_ATTRIBUTE_DIRECTORY;
|
||||
info.aes_version = MZ_AES_VERSION;
|
||||
|
||||
|
||||
GEODE_UNWRAP(
|
||||
mzTry(mz_zip_entry_write_open(m_handle, &info, MZ_COMPRESS_LEVEL_DEFAULT, 0, nullptr))
|
||||
.expect("Unable to open entry for writing (code {error})")
|
||||
);
|
||||
mz_zip_entry_close(m_handle);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> add(Path const& path, ByteVector const& data) {
|
||||
mz_zip_file info = { 0 };
|
||||
info.version_madeby = MZ_VERSION_MADEBY;
|
||||
info.compression_method = MZ_COMPRESS_METHOD_DEFLATE;
|
||||
info.filename = reinterpret_cast<const char*>(path.u8string().c_str());
|
||||
info.uncompressed_size = data.size();
|
||||
info.aes_version = MZ_AES_VERSION;
|
||||
|
||||
GEODE_UNWRAP(
|
||||
mzTry(mz_zip_entry_write_open(m_handle, &info, MZ_COMPRESS_LEVEL_DEFAULT, 0, nullptr))
|
||||
.expect("Unable to open entry for writing (code {error})")
|
||||
);
|
||||
auto written = mz_zip_entry_write(m_handle, data.data(), data.size());
|
||||
if (written < 0) {
|
||||
mz_zip_entry_close(m_handle);
|
||||
return Err("Unable to write entry data (code " + std::to_string(written) + ")");
|
||||
}
|
||||
mz_zip_entry_close(m_handle);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
ByteVector compressedData() const {
|
||||
if (!std::holds_alternative<ByteVector>(m_srcDest)) {
|
||||
return ByteVector();
|
||||
}
|
||||
const uint8_t* buf = nullptr;
|
||||
mz_stream_mem_get_buffer(m_stream, reinterpret_cast<const void**>(&buf));
|
||||
mz_stream_mem_seek(m_stream, 0, MZ_SEEK_END);
|
||||
auto size = mz_stream_mem_tell(m_stream);
|
||||
return ByteVector(buf, buf + size);
|
||||
}
|
||||
|
||||
Path getPath() const {
|
||||
if (std::holds_alternative<Path>(m_srcDest)) {
|
||||
return std::get<Path>(m_srcDest);
|
||||
}
|
||||
return Path();
|
||||
}
|
||||
|
||||
std::unordered_map<Path, ZipEntry> getEntries() const {
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
Path& path() {
|
||||
return m_zipPath;
|
||||
}
|
||||
|
||||
Impl(unzFile zip, Path const& path) : m_zip(zip), m_zipPath(path) {}
|
||||
|
||||
~Impl() {
|
||||
unzClose(m_zip);
|
||||
if (m_handle) {
|
||||
mz_zip_close(m_handle);
|
||||
mz_zip_delete(&m_handle);
|
||||
}
|
||||
if (m_stream) {
|
||||
mz_stream_close(m_stream);
|
||||
mz_stream_delete(&m_stream);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -247,28 +399,25 @@ Unzip::Unzip(Unzip&& other) : m_impl(std::move(other.m_impl)) {
|
|||
}
|
||||
|
||||
Result<Unzip> Unzip::create(Path const& file) {
|
||||
// todo: make sure unicode paths work
|
||||
auto zip = unzOpen(file.generic_string().c_str());
|
||||
if (!zip) {
|
||||
return Err("Unable to open zip file");
|
||||
}
|
||||
auto impl = std::make_unique<Unzip::Impl>(zip, file);
|
||||
if (!impl->loadEntries()) {
|
||||
return Err("Unable to read zip file");
|
||||
}
|
||||
GEODE_UNWRAP_INTO(auto impl, Zip::Impl::inFile(file, MZ_OPEN_MODE_READ));
|
||||
return Ok(Unzip(std::move(impl)));
|
||||
}
|
||||
|
||||
Result<Unzip> Unzip::create(ByteVector const& data) {
|
||||
GEODE_UNWRAP_INTO(auto impl, Zip::Impl::fromMemory(data));
|
||||
return Ok(Unzip(std::move(impl)));
|
||||
}
|
||||
|
||||
Unzip::Path Unzip::getPath() const {
|
||||
return m_impl->path();
|
||||
return m_impl->getPath();
|
||||
}
|
||||
|
||||
std::vector<Unzip::Path> Unzip::getEntries() const {
|
||||
return map::keys(m_impl->entries());
|
||||
return map::keys(m_impl->getEntries());
|
||||
}
|
||||
|
||||
bool Unzip::hasEntry(Path const& name) {
|
||||
return m_impl->entries().count(name);
|
||||
return m_impl->getEntries().count(name);
|
||||
}
|
||||
|
||||
Result<ByteVector> Unzip::extract(Path const& name) {
|
||||
|
@ -287,7 +436,7 @@ Result<> Unzip::extractTo(Path const& name, Path const& path) {
|
|||
|
||||
Result<> Unzip::extractAllTo(Path const& dir) {
|
||||
GEODE_UNWRAP(file::createDirectoryAll(dir));
|
||||
for (auto& [entry, info] : m_impl->entries()) {
|
||||
for (auto& [entry, info] : m_impl->getEntries()) {
|
||||
// make sure zip files like root/../../file.txt don't get extracted to
|
||||
// avoid zip attacks
|
||||
if (!ghc::filesystem::relative(dir / entry, dir).empty()) {
|
||||
|
@ -325,67 +474,6 @@ Result<> Unzip::intoDir(
|
|||
|
||||
// Zip
|
||||
|
||||
class file::Zip::Impl final {
|
||||
public:
|
||||
using Path = Unzip::Path;
|
||||
|
||||
private:
|
||||
zipFile m_zip;
|
||||
Path m_zipPath;
|
||||
|
||||
public:
|
||||
Path& path() {
|
||||
return m_zipPath;
|
||||
}
|
||||
|
||||
Result<> addFolder(Path const& path) {
|
||||
// a directory in a zip is just a file with no data and which ends in
|
||||
// a slash
|
||||
auto strPath = path.generic_string();
|
||||
if (!strPath.ends_with("/") && !strPath.ends_with("\\")) {
|
||||
strPath += "/";
|
||||
}
|
||||
if (zipOpenNewFileInZip(
|
||||
m_zip, strPath.c_str(),
|
||||
nullptr, nullptr, 0, nullptr, 0, nullptr,
|
||||
Z_DEFLATED, Z_DEFAULT_COMPRESSION
|
||||
) != ZIP_OK) {
|
||||
return Err("Unable to create directory " + path.string());
|
||||
}
|
||||
zipCloseFileInZip(m_zip);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> add(Path const& path, ByteVector const& data) {
|
||||
// open entry
|
||||
zip_fileinfo info = { 0 };
|
||||
if (zipOpenNewFileInZip(
|
||||
m_zip, path.generic_string().c_str(),
|
||||
&info, nullptr, 0, nullptr, 0, nullptr,
|
||||
Z_DEFLATED, Z_DEFAULT_COMPRESSION
|
||||
) != ZIP_OK) {
|
||||
return Err("Unable to create entry " + path.string());
|
||||
}
|
||||
|
||||
// write data
|
||||
if (zipWriteInFileInZip(m_zip, data.data(), data.size()) != ZIP_OK) {
|
||||
zipCloseFileInZip(m_zip);
|
||||
return Err("Unable to write entry " + path.string());
|
||||
}
|
||||
|
||||
// make sure to close!
|
||||
zipCloseFileInZip(m_zip);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Impl(zipFile zip, Path const& path) : m_zip(zip), m_zipPath(path) {}
|
||||
|
||||
~Impl() {
|
||||
zipClose(m_zip, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
Zip::Zip() : m_impl(nullptr) {}
|
||||
|
||||
Zip::~Zip() {}
|
||||
|
@ -397,17 +485,21 @@ Zip::Zip(Zip&& other) : m_impl(std::move(other.m_impl)) {
|
|||
}
|
||||
|
||||
Result<Zip> Zip::create(Path const& file) {
|
||||
// todo: make sure unicode paths work
|
||||
auto zip = zipOpen(file.generic_string().c_str(), APPEND_STATUS_CREATE);
|
||||
if (!zip) {
|
||||
return Err("Unable to open zip file");
|
||||
}
|
||||
auto impl = std::make_unique<Zip::Impl>(zip, file);
|
||||
GEODE_UNWRAP_INTO(auto impl, Zip::Impl::inFile(file, MZ_OPEN_MODE_CREATE | MZ_OPEN_MODE_WRITE));
|
||||
return Ok(Zip(std::move(impl)));
|
||||
}
|
||||
|
||||
Result<Zip> Zip::create() {
|
||||
GEODE_UNWRAP_INTO(auto impl, Zip::Impl::intoMemory());
|
||||
return Ok(Zip(std::move(impl)));
|
||||
}
|
||||
|
||||
Zip::Path Zip::getPath() const {
|
||||
return m_impl->path();
|
||||
return m_impl->getPath();
|
||||
}
|
||||
|
||||
ByteVector Zip::getData() const {
|
||||
return m_impl->compressedData();
|
||||
}
|
||||
|
||||
Result<> Zip::add(Path const& path, ByteVector const& data) {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
add_subdirectory(main)
|
||||
add_subdirectory(dependency)
|
||||
add_subdirectory(main)
|
||||
add_subdirectory(members)
|
|
@ -12,9 +12,4 @@ add_compile_definitions(EXPORTING_MOD)
|
|||
set(GEODE_LINK_SOURCE ON)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
geode-sdk
|
||||
)
|
||||
|
||||
create_geode_file(${PROJECT_NAME} DONT_INSTALL)
|
||||
|
|
|
@ -5,7 +5,8 @@ USE_GEODE_NAMESPACE();
|
|||
#include <Geode/modify/MenuLayer.hpp>
|
||||
#include <Geode/loader/SettingNode.hpp>
|
||||
#include <Geode/loader/ModJsonTest.hpp>
|
||||
|
||||
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
|
||||
#include <Geode/binding/FLAlertLayer.hpp>
|
||||
|
||||
enum class Icon {
|
||||
Steve,
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
"name": "Geode Test Dependency",
|
||||
"developer": "Geode Team",
|
||||
"description": "Unit test dependency for Geode",
|
||||
"api": {
|
||||
"include": []
|
||||
},
|
||||
"settings": {
|
||||
"its-raining-after-all": {
|
||||
"type": "bool",
|
||||
|
|
|
@ -10,9 +10,4 @@ target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
|
|||
set(GEODE_LINK_SOURCE ON)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
geode-sdk
|
||||
)
|
||||
|
||||
create_geode_file(${PROJECT_NAME} DONT_INSTALL)
|
||||
setup_geode_mod(${PROJECT_NAME} DONT_INSTALL)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"geode": "0.6.1",
|
||||
"geode": "1.0.0",
|
||||
"version": "1.0.0",
|
||||
"id": "geode.test",
|
||||
"name": "Geode Test",
|
||||
|
|
Loading…
Reference in a new issue