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:
HJfod 2023-01-31 14:48:34 +02:00
parent ae1eb8bb71
commit f32aaa8b12
15 changed files with 388 additions and 200 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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
*/

View file

@ -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()) {

View file

@ -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;

View file

@ -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.

View file

@ -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();

View file

@ -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) {

View file

@ -1,3 +1,3 @@
add_subdirectory(main)
add_subdirectory(dependency)
add_subdirectory(main)
add_subdirectory(members)

View file

@ -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)

View file

@ -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,

View file

@ -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",

View file

@ -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)

View file

@ -1,5 +1,5 @@
{
"geode": "0.6.1",
"geode": "1.0.0",
"version": "1.0.0",
"id": "geode.test",
"name": "Geode Test",