mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-01 07:40:18 -04:00
index is now download correctly + that is reported in the UI
This commit is contained in:
parent
996acf15aa
commit
e0d7dbf15b
12 changed files with 175 additions and 186 deletions
bindings
loader
include/Geode
src
hooks
loader
ui
utils
|
@ -5318,7 +5318,7 @@ class TextArea : cocos2d::CCSprite {
|
|||
virtual void draw() {}
|
||||
virtual void setOpacity(unsigned char) = mac 0x19f760, win 0x33800;
|
||||
bool init(gd::string str, char const* font, float width, float height, cocos2d::CCPoint anchor, float scale, bool disableColor) = mac 0x19ec70, win 0x33370, ios 0x92444;
|
||||
static TextArea* create(gd::string str, char const* font, float width, float height, cocos2d::CCPoint anchor, float scale, bool disableColor) = mac 0x19eb40, win 0x33270;
|
||||
static TextArea* create(gd::string str, char const* font, float scale, float width, cocos2d::CCPoint anchor, float height, bool disableColor) = mac 0x19eb40, win 0x33270;
|
||||
void colorAllCharactersTo(cocos2d::ccColor3B color) = win 0x33830;
|
||||
void setString(gd::string str) = mac 0x19eda0, win 0x33480;
|
||||
|
||||
|
|
|
@ -71,8 +71,9 @@ namespace geode {
|
|||
this->enable();
|
||||
}
|
||||
|
||||
EventListener(std::function<Callback> fn, T filter = T()) :
|
||||
m_callback(fn), m_filter(filter) {
|
||||
EventListener(std::function<Callback> fn, T filter = T())
|
||||
: m_callback(fn), m_filter(filter)
|
||||
{
|
||||
this->enable();
|
||||
}
|
||||
|
||||
|
@ -86,14 +87,16 @@ namespace geode {
|
|||
this->enable();
|
||||
}
|
||||
|
||||
// todo: maybe add these?
|
||||
EventListener(EventListener const& other) = delete;
|
||||
EventListener(EventListener&& other) = delete;
|
||||
|
||||
void bind(std::function<Callback> fn) {
|
||||
std::cout << "this: " << this << "\n";
|
||||
m_callback = fn;
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
void bind(C* cls, MemberFn<C> fn) {
|
||||
std::cout << "this: " << this << "\n";
|
||||
m_callback = std::bind(fn, cls, std::placeholders::_1);
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace geode {
|
|||
};
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define $on_mod(type) \
|
||||
template<class> \
|
||||
void GEODE_CONCAT(geodeExecFunction, __LINE__)(ModStateEvent*); \
|
||||
|
@ -56,3 +57,4 @@ static inline auto GEODE_CONCAT(Exec, __LINE__) = (geode::Loader::get()->schedul
|
|||
), 0); \
|
||||
template<class> \
|
||||
void GEODE_CONCAT(geodeExecFunction, __LINE__)(ModStateEvent*)
|
||||
// clang-format on
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
namespace geode {
|
||||
constexpr auto NOTIFICATION_DEFAULT_TIME = 1.f;
|
||||
constexpr auto NOTIFICATION_LONG_TIME = 4.f;
|
||||
|
||||
enum class NotificationIcon {
|
||||
None,
|
||||
|
@ -71,6 +72,12 @@ namespace geode {
|
|||
void setIcon(cocos2d::CCSprite* icon);
|
||||
void setTime(float time);
|
||||
|
||||
/**
|
||||
* Set the wait time to default, wait the time and hide the notification.
|
||||
* Equivalent to setTime(NOTIFICATION_DEFAULT_TIME)
|
||||
*/
|
||||
void waitAndHide();
|
||||
|
||||
/**
|
||||
* Adds the notification to the current scene if it doesn't have a
|
||||
* parent yet, and displays the show animation. If the time for the
|
||||
|
|
|
@ -306,6 +306,53 @@ namespace geode {
|
|||
return m_obj > other.m_obj;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Filter>
|
||||
class EventListenerNode : public cocos2d::CCNode {
|
||||
protected:
|
||||
EventListener<Filter> m_listener;
|
||||
|
||||
public:
|
||||
static EventListenerNode* create(EventListener<Filter> listener) {
|
||||
auto ret = new EventListenerNode();
|
||||
if (ret && ret->init()) {
|
||||
ret->m_listener = listener;
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static EventListenerNode* create(typename Filter::Callback callback) {
|
||||
auto ret = new EventListenerNode();
|
||||
if (ret && ret->init()) {
|
||||
ret->m_listener = EventListener(callback);
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
template <class C>
|
||||
static EventListenerNode* create(
|
||||
C* cls, typename EventListener<Filter>::MemberFn<C> callback
|
||||
) {
|
||||
// for some reason msvc won't let me just call EventListenerNode::create...
|
||||
// it claims no return value...
|
||||
// despite me writing return EventListenerNode::create()......
|
||||
auto ret = new EventListenerNode();
|
||||
if (ret && ret->init()) {
|
||||
ret->m_listener.bind(cls, callback);
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Cocos2d utils
|
||||
|
@ -937,133 +984,4 @@ namespace geode::cocos {
|
|||
return m_dict->allKeys(key)->count();
|
||||
}
|
||||
};
|
||||
|
||||
// namespace for storing implementation stuff for
|
||||
// inline member functions
|
||||
namespace {
|
||||
// class that holds the lambda (probably should've just used
|
||||
// std::function but hey, this one's heap-free!)
|
||||
template <class F, class Ret, class... Args>
|
||||
struct LambdaHolder {
|
||||
bool m_assigned = false;
|
||||
|
||||
// lambdas don't implement operator= so we
|
||||
// gotta do this wacky union stuff
|
||||
union {
|
||||
F m_lambda;
|
||||
};
|
||||
|
||||
LambdaHolder() {}
|
||||
|
||||
~LambdaHolder() {
|
||||
if (m_assigned) {
|
||||
m_lambda.~F();
|
||||
}
|
||||
}
|
||||
|
||||
LambdaHolder(F&& func) {
|
||||
this->assign(std::forward<F>(func));
|
||||
}
|
||||
|
||||
Ret operator()(Args... args) {
|
||||
if (m_assigned) {
|
||||
return m_lambda(std::forward<Args>(args)...);
|
||||
}
|
||||
else {
|
||||
return Ret();
|
||||
}
|
||||
}
|
||||
|
||||
void assign(F&& func) {
|
||||
if (m_assigned) {
|
||||
m_lambda.~F();
|
||||
}
|
||||
new (&m_lambda) F(func);
|
||||
m_assigned = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Extract parameters and return type from a lambda
|
||||
template <class Func>
|
||||
struct ExtractLambda : public ExtractLambda<decltype(&Func::operator())> {};
|
||||
|
||||
template <class C, class R, class... Args>
|
||||
struct ExtractLambda<R (C::*)(Args...) const> {
|
||||
using Ret = R;
|
||||
using Params = std::tuple<Args...>;
|
||||
};
|
||||
|
||||
// Class for storing the member function
|
||||
template <class Base, class Func, class Args>
|
||||
struct InlineMemberFunction;
|
||||
|
||||
template <class Base, class Func, class... Args>
|
||||
struct InlineMemberFunction<Base, Func, std::tuple<Args...>> : public Base {
|
||||
using Ret = typename ExtractLambda<Func>::Ret;
|
||||
using Selector = Ret (Base::*)(Args...);
|
||||
using Holder = LambdaHolder<Func, Ret, Args...>;
|
||||
|
||||
static inline Holder s_selector{};
|
||||
|
||||
Ret selector(Args... args) {
|
||||
return s_selector(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
static Selector get(Func&& function) {
|
||||
s_selector.assign(std::move(function));
|
||||
return static_cast<Selector>(&InlineMemberFunction::selector);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a lambda into a member function pointer. Useful for creating
|
||||
* callbacks that have to be members of a class without having to deal
|
||||
* with all of the boilerplate associated with defining a new class
|
||||
* member function.
|
||||
*
|
||||
* Do note that due to implementation problems, captures may have
|
||||
* unexpected side-effects. In practice, lambda member functions with
|
||||
* captures do not work properly in loops. If you assign the same
|
||||
* member lambda to multiple different targets, they will share the
|
||||
* same captured values.
|
||||
*/
|
||||
template <class Base, class Func>
|
||||
[[deprecated(
|
||||
"Due to too many implementation problems, "
|
||||
"makeMemberFunction will be removed in the future."
|
||||
)]] static auto
|
||||
makeMemberFunction(Func&& function) {
|
||||
return InlineMemberFunction<Base, Func, typename ExtractLambda<Func>::Params>::get(
|
||||
std::move(function)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SEL_MenuHandler out of a lambda with optional captures. Useful
|
||||
* for adding callbacks to CCMenuItemSpriteExtras without needing to add
|
||||
* the callback as a member to a class. Use the GEODE_MENU_SELECTOR class
|
||||
* for even more concise code.
|
||||
*
|
||||
* Do note that due to implementation problems, captures may have
|
||||
* unexpected side-effects. In practice, **you should not expect to be able
|
||||
* to pass any more information than you can pass to a normal menu selector
|
||||
* through captures**. If you assign the same member lambda to multiple
|
||||
* different targets, they will share the same captured values.
|
||||
*/
|
||||
template <class Func>
|
||||
[[deprecated(
|
||||
"Due to too many implementation problems, "
|
||||
"makeMenuSelector will be removed in the future."
|
||||
)]] static cocos2d::SEL_MenuHandler
|
||||
makeMenuSelector(Func&& selector) {
|
||||
return reinterpret_cast<cocos2d::SEL_MenuHandler>(
|
||||
makeMemberFunction<cocos2d::CCObject, Func>(std::move(selector))
|
||||
);
|
||||
}
|
||||
|
||||
#define GEODE_MENU_SELECTOR(senderArg, ...) \
|
||||
makeMenuSelector([this](senderArg) { \
|
||||
__VA_ARGS__; \
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
#include <array>
|
||||
#include <Geode/modify/LoadingLayer.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
||||
bool m_updatingResources;
|
||||
EventListener<ResourceDownloadFilter> m_resourceListener;
|
||||
|
||||
CustomLoadingLayer() : m_updatingResources(false) {}
|
||||
|
||||
|
@ -28,9 +28,11 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
|||
label->setID("geode-loaded-info");
|
||||
this->addChild(label);
|
||||
|
||||
m_fields->m_resourceListener.bind(
|
||||
// for some reason storing the listener as a field caused the
|
||||
// destructor for the field not to be run
|
||||
this->addChild(EventListenerNode<ResourceDownloadFilter>::create(
|
||||
this, &CustomLoadingLayer::updateResourcesProgress
|
||||
);
|
||||
));
|
||||
|
||||
// verify loader resources
|
||||
if (!InternalLoader::get()->verifyLoaderResources()) {
|
||||
|
|
|
@ -19,7 +19,7 @@ USE_GEODE_NAMESPACE();
|
|||
|
||||
class CustomMenuLayer;
|
||||
|
||||
static Ref<Notification> g_indexUpdateNotif = nullptr;
|
||||
static Ref<Notification> INDEX_UPDATE_NOTIF = nullptr;
|
||||
static Ref<CCSprite> g_geodeButton = nullptr;
|
||||
|
||||
struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||
|
@ -99,18 +99,39 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
|||
}
|
||||
|
||||
// update mods index
|
||||
if (!g_indexUpdateNotif && !Index::get()->hasTriedToUpdate()) {
|
||||
g_indexUpdateNotif = Notification::create(
|
||||
if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
|
||||
this->addChild(EventListenerNode<IndexUpdateFilter>::create(
|
||||
this, &CustomMenuLayer::onIndexUpdate
|
||||
));
|
||||
INDEX_UPDATE_NOTIF = Notification::create(
|
||||
"Updating Index", NotificationIcon::Loading, 0
|
||||
);
|
||||
g_indexUpdateNotif->show();
|
||||
|
||||
INDEX_UPDATE_NOTIF->show();
|
||||
Index::get()->update();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onIndexUpdate(IndexUpdateEvent* event) {
|
||||
if (!INDEX_UPDATE_NOTIF) return;
|
||||
std::visit(makeVisitor {
|
||||
[](UpdateProgress const& prog) {},
|
||||
[](UpdateFinished const&) {
|
||||
INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Success);
|
||||
INDEX_UPDATE_NOTIF->setString("Index Up-to-Date");
|
||||
INDEX_UPDATE_NOTIF->waitAndHide();
|
||||
INDEX_UPDATE_NOTIF = nullptr;
|
||||
},
|
||||
[](UpdateError const& info) {
|
||||
INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Error);
|
||||
INDEX_UPDATE_NOTIF->setString(info);
|
||||
INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
|
||||
INDEX_UPDATE_NOTIF = nullptr;
|
||||
},
|
||||
}, event->status);
|
||||
}
|
||||
|
||||
void onGeode(CCObject*) {
|
||||
ModListLayer::scene();
|
||||
}
|
||||
|
|
|
@ -5,12 +5,10 @@ USE_GEODE_NAMESPACE();
|
|||
std::unordered_set<EventListenerProtocol*> Event::s_listeners = {};
|
||||
|
||||
void EventListenerProtocol::enable() {
|
||||
std::cout << "enable " << this << ": " << typeid(*this).name() << "\n";
|
||||
Event::s_listeners.insert(this);
|
||||
}
|
||||
|
||||
void EventListenerProtocol::disable() {
|
||||
std::cout << "disable " << this << "\n";
|
||||
Event::s_listeners.erase(this);
|
||||
}
|
||||
|
||||
|
@ -24,13 +22,8 @@ void Event::postFrom(Mod* m) {
|
|||
if (m) this->sender = m;
|
||||
|
||||
for (auto h : Event::s_listeners) {
|
||||
try {
|
||||
std::cout << h << ": " << typeid(*h).name() << "\n";
|
||||
if (h->passThrough(this) == ListenerResult::Stop) {
|
||||
break;
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
std::cout << "fuck: " << h << ": " << e.what() << "\n";
|
||||
if (h->passThrough(this) == ListenerResult::Stop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ Index::Index() {
|
|||
std::bind(&Index::onSourceUpdate, this, std::placeholders::_1),
|
||||
SourceUpdateFilter()
|
||||
);
|
||||
this->addSource("https://github.com/geode-sdk/index-test");
|
||||
this->addSource("geode-sdk/index-test");
|
||||
}
|
||||
|
||||
Index* Index::get() {
|
||||
|
@ -261,10 +261,11 @@ void Index::downloadSource(IndexSource& src) {
|
|||
}
|
||||
|
||||
// unzip new index
|
||||
auto unzip = file::Unzip::intoDir(targetFile, targetDir, true);
|
||||
auto unzip = file::Unzip::intoDir(targetFile, targetDir, true)
|
||||
.expect("Unable to unzip new index");
|
||||
if (!unzip) {
|
||||
return SourceUpdateEvent(
|
||||
src, UpdateError("Unable to unzip new index")
|
||||
src, UpdateError(unzip.unwrapErr())
|
||||
).post();
|
||||
}
|
||||
|
||||
|
@ -302,26 +303,33 @@ void Index::updateSourceFromLocal(IndexSource& src) {
|
|||
this->cleanupItems();
|
||||
|
||||
// read directory and add new items
|
||||
for (auto& dir : ghc::filesystem::directory_iterator(src.dirname())) {
|
||||
auto addRes = IndexItem::createFromDir(src.repository, dir);
|
||||
if (!addRes) {
|
||||
log::warn("Unable to add index item from {}: {}", dir, addRes.unwrapErr());
|
||||
continue;
|
||||
try {
|
||||
for (auto& dir : ghc::filesystem::directory_iterator(
|
||||
dirs::getIndexDir() / src.dirname()
|
||||
)) {
|
||||
auto addRes = IndexItem::createFromDir(src.repository, dir);
|
||||
if (!addRes) {
|
||||
log::warn("Unable to add index item from {}: {}", dir, addRes.unwrapErr());
|
||||
continue;
|
||||
}
|
||||
auto add = addRes.unwrap();
|
||||
// check if this major version of this item has already been added
|
||||
if (m_items[add->info.m_id].count(add->info.m_version.getMajor())) {
|
||||
log::warn(
|
||||
"Item {}@{} has already been added, skipping",
|
||||
add->info.m_id, add->info.m_version
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// add new major version of this item
|
||||
m_items[add->info.m_id].insert({
|
||||
add->info.m_version.getMajor(),
|
||||
add
|
||||
});
|
||||
}
|
||||
auto add = addRes.unwrap();
|
||||
// check if this major version of this item has already been added
|
||||
if (m_items[add->info.m_id].count(add->info.m_version.getMajor())) {
|
||||
log::warn(
|
||||
"Item {}@{} has already been added, skipping",
|
||||
add->info.m_id, add->info.m_version
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// add new major version of this item
|
||||
m_items[add->info.m_id].insert({
|
||||
add->info.m_version.getMajor(),
|
||||
add
|
||||
});
|
||||
} catch(std::exception& e) {
|
||||
log::warn("Unable to read index source {}: {}", src.dirname(), e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
// mark source as finished
|
||||
|
|
|
@ -193,6 +193,8 @@ void ModCell::updateState() {
|
|||
}
|
||||
|
||||
void ModCell::loadFromMod(Mod* mod) {
|
||||
m_mod = mod;
|
||||
|
||||
this->setupInfo(mod->getModInfo(), false);
|
||||
|
||||
auto viewSpr = ButtonSprite::create(
|
||||
|
@ -264,6 +266,8 @@ IndexItemCell* IndexItemCell::create(
|
|||
}
|
||||
|
||||
void IndexItemCell::loadFromItem(IndexItemHandle item) {
|
||||
m_item = item;
|
||||
|
||||
this->setupInfo(item->info, true);
|
||||
|
||||
auto viewSpr = ButtonSprite::create(
|
||||
|
|
|
@ -145,6 +145,10 @@ void Notification::animateOut() {
|
|||
m_bg->runAction(CCFadeTo::create(NOTIFICATION_FADEOUT, 0));
|
||||
}
|
||||
|
||||
void Notification::waitAndHide() {
|
||||
this->setTime(NOTIFICATION_DEFAULT_TIME);
|
||||
}
|
||||
|
||||
void Notification::show() {
|
||||
if (!m_showing) {
|
||||
if (!s_queue->containsObject(this)) {
|
||||
|
|
|
@ -133,9 +133,10 @@ Result<std::vector<std::string>> utils::file::listFilesRecursively(std::string c
|
|||
static constexpr auto MAX_ENTRY_PATH_LEN = 256;
|
||||
|
||||
struct ZipEntry {
|
||||
unz_file_pos m_pos;
|
||||
ZPOS64_T m_compressedSize;
|
||||
ZPOS64_T m_uncompressedSize;
|
||||
bool isDirectory;
|
||||
unz_file_pos pos;
|
||||
ZPOS64_T compressedSize;
|
||||
ZPOS64_T uncompressedSize;
|
||||
};
|
||||
|
||||
class file::UnzipImpl final {
|
||||
|
@ -164,12 +165,17 @@ public:
|
|||
// 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 {
|
||||
.m_pos = pos,
|
||||
.m_compressedSize = fileInfo.compressed_size,
|
||||
.m_uncompressedSize = fileInfo.uncompressed_size,
|
||||
.isDirectory =
|
||||
fileInfo.uncompressed_size == 0 &&
|
||||
len > 0 &&
|
||||
(fileName[len - 1] == '/' || fileName[len - 1] == '\\'),
|
||||
.pos = pos,
|
||||
.compressedSize = fileInfo.compressed_size,
|
||||
.uncompressedSize = fileInfo.uncompressed_size,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -188,16 +194,20 @@ public:
|
|||
|
||||
auto entry = m_entries.at(name);
|
||||
|
||||
if (unzGoToFilePos(m_zip, &entry.m_pos) != UNZ_OK) {
|
||||
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");
|
||||
}
|
||||
byte_array res;
|
||||
res.resize(entry.m_uncompressedSize);
|
||||
auto size = unzReadCurrentFile(m_zip, res.data(), entry.m_uncompressedSize);
|
||||
if (size < 0 || size != entry.m_uncompressedSize) {
|
||||
res.resize(entry.uncompressedSize);
|
||||
auto size = unzReadCurrentFile(m_zip, res.data(), entry.uncompressedSize);
|
||||
if (size < 0 || size != entry.uncompressedSize) {
|
||||
return Err("Unable to extract entry");
|
||||
}
|
||||
unzCloseCurrentFile(m_zip);
|
||||
|
@ -264,14 +274,31 @@ Result<byte_array> Unzip::extract(Path const& name) {
|
|||
|
||||
Result<> Unzip::extractTo(Path const& name, Path const& path) {
|
||||
GEODE_UNWRAP_INTO(auto bytes, m_impl->extract(name));
|
||||
GEODE_UNWRAP(file::writeBinary(path, bytes));
|
||||
// create containing directories for target path
|
||||
if (path.has_parent_path()) {
|
||||
GEODE_UNWRAP(file::createDirectoryAll(path.parent_path()));
|
||||
}
|
||||
GEODE_UNWRAP(file::writeBinary(path, bytes).expect("Unable to write file {}: {error}", path.string()));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Unzip::extractAllTo(Path const& dir) {
|
||||
GEODE_UNWRAP(file::createDirectoryAll(dir));
|
||||
for (auto& [entry, _] : m_impl->entries()) {
|
||||
GEODE_UNWRAP(this->extractTo(entry, dir / entry));
|
||||
for (auto& [entry, info] : m_impl->entries()) {
|
||||
if (info.isDirectory) {
|
||||
GEODE_UNWRAP(file::createDirectoryAll(entry));
|
||||
} else {
|
||||
// make sure zip files like root/../../file.txt don't get extracted to
|
||||
// avoid zip attacks
|
||||
if (!ghc::filesystem::relative(dir / entry, dir).empty()) {
|
||||
GEODE_UNWRAP(this->extractTo(entry, dir / entry));
|
||||
} else {
|
||||
log::error(
|
||||
"Zip entry '{}' is not contained within zip bounds",
|
||||
dir / entry
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue