mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-10 12:04:36 -04:00
lots of index-related stuff (see details)
- show crashlog on crash when GEODE_DEBUG is enabled - fix mod resources not being loaded when installed from index + more index reworking, it's actually pretty neat now - fix ModInfoLayer crashing when leaving it while downloading - fix other index crashes - fix queueInGDThread sometimes leaving out functions - add new file utils and deprecate ones that don't use ghc::filesystem::path - index mods now show their about.md files - general cleanup and goodcodeification
This commit is contained in:
parent
f0557056c7
commit
7dd94422b4
14 changed files with 417 additions and 191 deletions
loader
include/Geode
src
index
internal
load
ui/internal
utils
|
@ -182,6 +182,7 @@ namespace geode {
|
|||
* Mod::m_addResourcesToSearchPath to true
|
||||
* first
|
||||
*/
|
||||
void updateModResourcePaths(Mod*);
|
||||
void updateResourcePaths();
|
||||
void updateModResources(Mod* mod);
|
||||
void updateResources();
|
||||
|
|
|
@ -175,6 +175,14 @@ namespace geode {
|
|||
* format
|
||||
*/
|
||||
static Result<ModInfo> createFromSchemaV010(ModJson const& json);
|
||||
|
||||
Result<> addSpecialFiles(ghc::filesystem::path const& dir);
|
||||
Result<> addSpecialFiles(cocos2d::ZipFile& zip);
|
||||
|
||||
std::vector<std::pair<
|
||||
std::string,
|
||||
std::optional<std::string>*
|
||||
>> getSpecialFiles();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,22 +7,34 @@
|
|||
#include <fs/filesystem.hpp>
|
||||
|
||||
namespace geode::utils::file {
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<std::string> readString(std::string const& path);
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<std::string> readString(std::wstring const& path);
|
||||
GEODE_DLL Result<std::string> readString(ghc::filesystem::path const& path);
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<byte_array> readBinary(std::string const& path);
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<byte_array> readBinary(std::wstring const& path);
|
||||
GEODE_DLL Result<byte_array> readBinary(ghc::filesystem::path const& path);
|
||||
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<> writeString(std::string const& path, std::string const& data);
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<> writeString(std::wstring const& path, std::string const& data);
|
||||
GEODE_DLL Result<> writeString(ghc::filesystem::path const& path, std::string const& data);
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<> writeBinary(std::string const& path, byte_array const& data);
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<> writeBinary(std::wstring const& path, byte_array const& data);
|
||||
GEODE_DLL Result<> writeBinary(ghc::filesystem::path const& path, byte_array const& data);
|
||||
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<> createDirectory(std::string const& path);
|
||||
GEODE_DLL Result<> createDirectory(ghc::filesystem::path const& path);
|
||||
[[deprecated("Use the ghc::filesystem::path version")]]
|
||||
GEODE_DLL Result<> createDirectoryAll(std::string const& path);
|
||||
GEODE_DLL Result<> createDirectoryAll(ghc::filesystem::path const& path);
|
||||
GEODE_DLL Result<std::vector<std::string>> listFiles(std::string const& path);
|
||||
GEODE_DLL Result<std::vector<std::string>> listFilesRecursively(std::string const& path);
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
#include <Geode/utils/string.hpp>
|
||||
#include <Geode/utils/vector.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <Geode/utils/general.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/binding/FLAlertLayer.hpp>
|
||||
|
||||
#define GITHUB_DONT_RATE_LIMIT_ME_PLS 0
|
||||
|
||||
|
@ -36,7 +40,6 @@ static PlatformID platformFromString(std::string const& str) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Index* Index::get() {
|
||||
static auto ret = new Index();
|
||||
return ret;
|
||||
|
@ -232,12 +235,7 @@ void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto readModJson = readJSON<ModJson>(dir / "mod.json");
|
||||
if (!readModJson) {
|
||||
log::warn("Error reading mod.json: {}, skipping", readModJson.error());
|
||||
return;
|
||||
}
|
||||
auto info = ModInfo::create(readModJson.value());
|
||||
auto info = ModInfo::createFromFile(dir / "mod.json");
|
||||
if (!info) {
|
||||
log::warn("{}: {}, skipping", dir, info.error());
|
||||
return;
|
||||
|
@ -416,7 +414,7 @@ Result<std::vector<std::string>> Index::checkDependenciesForItem(
|
|||
}
|
||||
}
|
||||
|
||||
Result<InstallItems> Index::installItems(
|
||||
Result<InstallHandle> Index::installItems(
|
||||
std::vector<IndexItem> const& items
|
||||
) {
|
||||
std::vector<std::string> ids {};
|
||||
|
@ -451,31 +449,34 @@ Result<InstallItems> Index::installItems(
|
|||
}
|
||||
utils::vector::push(ids, list.value());
|
||||
}
|
||||
return Ok(InstallItems(ids));
|
||||
auto ret = std::make_shared<InstallItems>(
|
||||
std::unordered_set(ids.begin(), ids.end())
|
||||
);
|
||||
m_installations.insert(ret);
|
||||
return Ok(ret);
|
||||
}
|
||||
|
||||
Result<InstallItems> Index::installItem(
|
||||
Result<InstallHandle> Index::installItem(
|
||||
IndexItem const& item
|
||||
) {
|
||||
return this->installItems({ item });
|
||||
}
|
||||
|
||||
bool Index::isUpdateAvailableForItem(std::string const& id) const {
|
||||
if (!Loader::get()->isModInstalled(id)) {
|
||||
return false;
|
||||
}
|
||||
if (!this->isKnownItem(id)) {
|
||||
return false;
|
||||
}
|
||||
return
|
||||
this->getKnownItem(id).m_info.m_version >
|
||||
Loader::get()->getInstalledMod(id)->getVersion();
|
||||
return this->isUpdateAvailableForItem(this->getKnownItem(id));
|
||||
}
|
||||
|
||||
bool Index::isUpdateAvailableForItem(IndexItem const& item) const {
|
||||
if (!Loader::get()->isModInstalled(item.m_info.m_id)) {
|
||||
return false;
|
||||
}
|
||||
// has the mod been updated (but update not yet been applied by restarting game)
|
||||
if (m_updated.count(item.m_info.m_id)) {
|
||||
return false;
|
||||
}
|
||||
return
|
||||
item.m_info.m_version >
|
||||
Loader::get()->getInstalledMod(item.m_info.m_id)->getVersion();
|
||||
|
@ -490,7 +491,7 @@ bool Index::areUpdatesAvailable() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
Result<InstallItems> Index::installAllUpdates() {
|
||||
Result<InstallHandle> Index::installAllUpdates() {
|
||||
// find items that need updating
|
||||
std::vector<IndexItem> itemsToUpdate {};
|
||||
for (auto& item : m_items) {
|
||||
|
@ -501,115 +502,219 @@ Result<InstallItems> Index::installAllUpdates() {
|
|||
return this->installItems(itemsToUpdate);
|
||||
}
|
||||
|
||||
InstallHandles Index::getRunningInstallations() const {
|
||||
return map::getValues(m_installations);
|
||||
std::vector<InstallHandle> Index::getRunningInstallations() const {
|
||||
return std::vector<InstallHandle>(
|
||||
m_installations.begin(),
|
||||
m_installations.end()
|
||||
);
|
||||
}
|
||||
|
||||
InstallHandle Index::isInstallingItem(std::string const& id) {
|
||||
if (m_installations.count(id)) {
|
||||
return m_installations.at(id);
|
||||
for (auto& inst : m_installations) {
|
||||
if (inst->m_toInstall.count(id)) {
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> InstallItems::toInstall() const {
|
||||
std::unordered_set<std::string> InstallItems::toInstall() const {
|
||||
return m_toInstall;
|
||||
}
|
||||
|
||||
InstallHandles InstallItems::begin(ItemInstallCallback callback) const {
|
||||
InstallHandles res {};
|
||||
InstallItems::CallbackID InstallItems::join(ItemInstallCallback callback) {
|
||||
// already finished?
|
||||
if (m_started && this->finished()) {
|
||||
callback(shared_from_this(), UpdateStatus::Finished, "", 100);
|
||||
return 0;
|
||||
}
|
||||
// start at one because 0 means invalid callback
|
||||
static CallbackID COUNTER = 1;
|
||||
if (callback) {
|
||||
auto id = COUNTER++;
|
||||
m_callbacks.insert({ id, callback });
|
||||
return id;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void InstallItems::leave(InstallItems::CallbackID id) {
|
||||
m_callbacks.erase(id);
|
||||
}
|
||||
|
||||
void InstallItems::post(
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t progress
|
||||
) {
|
||||
for (auto& [_, cb] : m_callbacks) {
|
||||
cb(shared_from_this(), status, info, progress);
|
||||
}
|
||||
}
|
||||
|
||||
void InstallItems::progress(
|
||||
std::string const& info,
|
||||
uint8_t progress
|
||||
) {
|
||||
this->post(UpdateStatus::Progress, info, progress);
|
||||
}
|
||||
|
||||
void InstallItems::error(std::string const& info) {
|
||||
this->post(UpdateStatus::Failed, info, 0);
|
||||
}
|
||||
|
||||
void InstallItems::finish(bool replaceFiles) {
|
||||
// move files from temp dir to geode directory
|
||||
auto tempDir = Loader::get()->getGeodeDirectory() / "index" / "temp";
|
||||
for (auto& file : ghc::filesystem::directory_iterator(tempDir)) {
|
||||
try {
|
||||
auto modDir = Loader::get()->getGeodeDirectory() / "mods";
|
||||
auto targetFile = modDir / file.path().filename();
|
||||
auto targetName = file.path().stem();
|
||||
|
||||
if (!replaceFiles) {
|
||||
// find valid filename that doesn't exist yet
|
||||
auto filename = ghc::filesystem::path(targetName)
|
||||
.replace_extension("")
|
||||
.string();
|
||||
|
||||
size_t number = 0;
|
||||
while (ghc::filesystem::exists(targetFile)) {
|
||||
targetFile = modDir /
|
||||
(filename + std::to_string(number) + ".geode");
|
||||
number++;
|
||||
}
|
||||
}
|
||||
|
||||
// move file
|
||||
ghc::filesystem::rename(file, targetFile);
|
||||
|
||||
} catch(std::exception& e) {
|
||||
try { ghc::filesystem::remove_all(tempDir); } catch(...) {}
|
||||
return this->error(
|
||||
"Unable to move downloaded file to mods directory: \"" +
|
||||
std::string(e.what()) + " \" "
|
||||
"(This might be due to insufficient permissions to "
|
||||
"write files under SteamLibrary, try running GD as "
|
||||
"administrator)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// load mods
|
||||
Loader::get()->refreshMods();
|
||||
|
||||
// finished
|
||||
this->post(UpdateStatus::Finished, "", 100);
|
||||
|
||||
// let index know these mods have been updated
|
||||
for (auto& inst : m_toInstall) {
|
||||
Index::get()->m_updated.insert(inst);
|
||||
}
|
||||
|
||||
// if no one is listening, show a popup anyway
|
||||
if (!m_callbacks.size()) {
|
||||
FLAlertLayer::create(
|
||||
"Mods installed",
|
||||
"The following <cy>mods</c> have been installed: " +
|
||||
container::join(m_toInstall, ",") + "\n"
|
||||
"Please <cr>restart the game</c> to apply",
|
||||
"OK"
|
||||
)->show();
|
||||
}
|
||||
|
||||
// no longer need to ensure aliveness
|
||||
Index::get()->m_installations.erase(shared_from_this());
|
||||
}
|
||||
|
||||
InstallItems::CallbackID InstallItems::start(
|
||||
ItemInstallCallback callback,
|
||||
bool replaceFiles
|
||||
) {
|
||||
auto id = this->join(callback);
|
||||
|
||||
// check if started already, if so, behave like join
|
||||
if (m_started) return id;
|
||||
m_started = true;
|
||||
|
||||
for (auto& inst : m_toInstall) {
|
||||
// by virtue of running this function we know item must be valid
|
||||
auto item = Index::get()->getKnownItem(inst);
|
||||
|
||||
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
|
||||
auto tempFile = indexDir / item.m_download.m_filename;
|
||||
file::createDirectoryAll(indexDir / "temp");
|
||||
auto tempFile = indexDir / "temp" / item.m_download.m_filename;
|
||||
|
||||
m_downloaded.push_back(tempFile);
|
||||
|
||||
auto handle = web::AsyncWebRequest()
|
||||
.join("install_mod_" + inst)
|
||||
.fetch(item.m_download.m_url)
|
||||
.into(tempFile)
|
||||
.then([callback, item, inst, indexDir, tempFile](auto) {
|
||||
.then([this, replaceFiles, item, inst, indexDir, tempFile](auto) {
|
||||
// check for 404
|
||||
auto notFound = utils::file::readString(tempFile);
|
||||
if (notFound && notFound.value() == "Not Found") {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Failed,
|
||||
return this->error(
|
||||
"Binary file download returned \"Not found\". Report "
|
||||
"this to the Geode development team.",
|
||||
0
|
||||
"this to the Geode development team."
|
||||
);
|
||||
}
|
||||
|
||||
// verify checksum
|
||||
if (callback) callback(inst, UpdateStatus::Progress, "Verifying", 100);
|
||||
this->progress("Verifying", 100);
|
||||
if (::calculateHash(tempFile.string()) != item.m_download.m_hash) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Failed,
|
||||
return this->error(
|
||||
"Checksum mismatch! (Downloaded file did not match what "
|
||||
"was expected. Try again, and if the download fails another time, "
|
||||
"report this to the Geode development team.",
|
||||
0
|
||||
"report this to the Geode development team."
|
||||
);
|
||||
}
|
||||
|
||||
// move temp file to geode directory
|
||||
try {
|
||||
auto modDir = Loader::get()->getGeodeDirectory() / "mods";
|
||||
auto targetFile = modDir / item.m_download.m_filename;
|
||||
|
||||
// find valid filename that doesn't exist yet
|
||||
auto filename = ghc::filesystem::path(
|
||||
item.m_download.m_filename
|
||||
).replace_extension("").string();
|
||||
|
||||
size_t number = 0;
|
||||
while (ghc::filesystem::exists(targetFile)) {
|
||||
targetFile = modDir /
|
||||
(filename + std::to_string(number) + ".geode");
|
||||
number++;
|
||||
}
|
||||
|
||||
// move file
|
||||
ghc::filesystem::rename(tempFile, targetFile);
|
||||
} catch(std::exception& e) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Failed,
|
||||
"Unable to move downloaded file to mods directory: \"" +
|
||||
std::string(e.what()) + " \" "
|
||||
"(This might be due to insufficient permissions to "
|
||||
"write files under SteamLibrary, try running GD as "
|
||||
"administrator)",
|
||||
0
|
||||
);
|
||||
// finished() just checks if the web requests are done
|
||||
if (this->finished()) {
|
||||
this->finish(replaceFiles);
|
||||
}
|
||||
|
||||
// finished
|
||||
if (callback) callback(inst, UpdateStatus::Finished, "", 100);
|
||||
})
|
||||
.expect([inst, callback](std::string const& error) {
|
||||
if (callback) callback(inst, UpdateStatus::Failed, error, 0);
|
||||
Index::get()->m_installations.erase(inst);
|
||||
.expect([this, inst](std::string const& error) {
|
||||
this->error(error);
|
||||
this->cancel();
|
||||
})
|
||||
.cancelled([inst](auto&) {
|
||||
Index::get()->m_installations.erase(inst);
|
||||
.cancelled([this, item](auto&) {
|
||||
this->cancel();
|
||||
})
|
||||
.progress([inst, callback](web::SentAsyncWebRequest&, double now, double total) {
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Progress,
|
||||
.progress([this, inst](web::SentAsyncWebRequest&, double now, double total) {
|
||||
this->progress(
|
||||
"Downloading binary",
|
||||
static_cast<uint8_t>(now / total * 100.0)
|
||||
);
|
||||
})
|
||||
.send();
|
||||
|
||||
res.push_back(handle);
|
||||
Index::get()->m_installations.insert({ inst, handle });
|
||||
m_handles.push_back(handle);
|
||||
}
|
||||
|
||||
return res;
|
||||
// manage installation in the index until it's finished so
|
||||
// even if no one listens to it it doesn't get freed from
|
||||
// memory
|
||||
Index::get()->m_installations.insert(shared_from_this());
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
bool InstallItems::finished() const {
|
||||
for (auto& inst : m_handles) {
|
||||
if (!inst->finished()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallItems::cancel() {
|
||||
for (auto& inst : m_handles) {
|
||||
inst->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,15 @@
|
|||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <Geode/utils/fetch.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
class Index;
|
||||
struct ModInstallUpdate;
|
||||
struct InstallItems;
|
||||
|
||||
using InstallHandle = std::shared_ptr<InstallItems>;
|
||||
|
||||
// todo: make index use events
|
||||
|
||||
|
@ -17,11 +21,8 @@ enum class UpdateStatus {
|
|||
Finished,
|
||||
};
|
||||
|
||||
using InstallHandle = web::SentAsyncWebRequestHandle;
|
||||
using InstallHandles = std::vector<InstallHandle>;
|
||||
|
||||
using ItemInstallCallback = std::function<void(
|
||||
std::string const&, UpdateStatus, std::string const&, uint8_t
|
||||
InstallHandle, UpdateStatus, std::string const&, uint8_t
|
||||
)>;
|
||||
using IndexUpdateCallback = std::function<void(
|
||||
UpdateStatus, std::string const&, uint8_t
|
||||
|
@ -41,20 +42,38 @@ struct IndexItem {
|
|||
std::unordered_set<std::string> m_categories;
|
||||
};
|
||||
|
||||
struct InstallItems final {
|
||||
private:
|
||||
std::vector<std::string> m_toInstall;
|
||||
struct InstallItems final : public std::enable_shared_from_this<InstallItems> {
|
||||
public:
|
||||
using CallbackID = size_t;
|
||||
|
||||
inline InstallItems(
|
||||
std::vector<std::string> const& toInstall
|
||||
) : m_toInstall(toInstall) {}
|
||||
private:
|
||||
bool m_started = false;
|
||||
std::unordered_set<std::string> m_toInstall;
|
||||
std::vector<web::SentAsyncWebRequestHandle> m_handles;
|
||||
std::unordered_map<CallbackID, ItemInstallCallback> m_callbacks;
|
||||
std::vector<ghc::filesystem::path> m_downloaded;
|
||||
|
||||
void post(UpdateStatus status, std::string const& info, uint8_t progress);
|
||||
void progress(std::string const& info, uint8_t progress);
|
||||
void error(std::string const& info);
|
||||
void finish(bool replaceFiles);
|
||||
|
||||
friend class Index;
|
||||
|
||||
public:
|
||||
std::vector<std::string> toInstall() const;
|
||||
std::unordered_set<std::string> toInstall() const;
|
||||
|
||||
InstallHandles begin(ItemInstallCallback callback) const;
|
||||
inline InstallItems(
|
||||
std::unordered_set<std::string> const& toInstall
|
||||
) : m_toInstall(toInstall) {}
|
||||
|
||||
void cancel();
|
||||
bool finished() const;
|
||||
|
||||
CallbackID join(ItemInstallCallback callback);
|
||||
void leave(CallbackID id);
|
||||
|
||||
CallbackID start(ItemInstallCallback callback, bool replaceFiles = true);
|
||||
};
|
||||
|
||||
class Index {
|
||||
|
@ -63,10 +82,11 @@ protected:
|
|||
bool m_updating = false;
|
||||
mutable std::mutex m_callbacksMutex;
|
||||
std::vector<IndexItem> m_items;
|
||||
std::unordered_map<std::string, InstallHandle> m_installations;
|
||||
std::unordered_set<InstallHandle> m_installations;
|
||||
mutable std::mutex m_ticketsMutex;
|
||||
std::unordered_set<std::string> m_featured;
|
||||
std::unordered_set<std::string> m_categories;
|
||||
std::unordered_set<std::string> m_updated;
|
||||
|
||||
void addIndexItemFromFolder(ghc::filesystem::path const& dir);
|
||||
Result<> updateIndexFromLocalCache();
|
||||
|
@ -88,15 +108,15 @@ public:
|
|||
std::vector<IndexItem> getFeaturedItems() const;
|
||||
bool isFeaturedItem(std::string const& item) const;
|
||||
|
||||
Result<InstallItems> installItems(std::vector<IndexItem> const& item);
|
||||
Result<InstallItems> installItem(IndexItem const& item);
|
||||
InstallHandles getRunningInstallations() const;
|
||||
Result<InstallHandle> installItems(std::vector<IndexItem> const& item);
|
||||
Result<InstallHandle> installItem(IndexItem const& item);
|
||||
std::vector<InstallHandle> getRunningInstallations() const;
|
||||
InstallHandle isInstallingItem(std::string const& id);
|
||||
|
||||
bool isUpdateAvailableForItem(std::string const& id) const;
|
||||
bool isUpdateAvailableForItem(IndexItem const& item) const;
|
||||
bool areUpdatesAvailable() const;
|
||||
Result<InstallItems> installAllUpdates();
|
||||
Result<InstallHandle> installAllUpdates();
|
||||
|
||||
bool isIndexUpdated() const;
|
||||
void updateIndex(IndexUpdateCallback callback, bool force = false);
|
||||
|
|
|
@ -39,19 +39,21 @@ bool InternalLoader::setup() {
|
|||
|
||||
void InternalLoader::queueInGDThread(ScheduledFunction func) {
|
||||
std::lock_guard<std::mutex> lock(m_gdThreadMutex);
|
||||
this->m_gdThreadQueue.push_back(func);
|
||||
m_gdThreadQueue.push_back(func);
|
||||
}
|
||||
|
||||
void InternalLoader::executeGDThreadQueue() {
|
||||
// copy queue to avoid locking mutex if someone is
|
||||
// running addToGDThread inside their function
|
||||
m_gdThreadMutex.lock();
|
||||
auto queue = std::move(m_gdThreadQueue);
|
||||
auto queue = m_gdThreadQueue;
|
||||
m_gdThreadQueue.clear();
|
||||
m_gdThreadMutex.unlock();
|
||||
|
||||
// call queue
|
||||
for (auto const& func : queue) {
|
||||
func();
|
||||
}
|
||||
m_gdThreadMutex.lock();
|
||||
m_gdThreadQueue.clear();
|
||||
m_gdThreadMutex.unlock();
|
||||
}
|
||||
|
||||
void InternalLoader::logConsoleMessage(std::string const& msg) {
|
||||
|
|
|
@ -267,11 +267,7 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
|
|||
|
||||
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
|
||||
|
||||
std::ofstream file;
|
||||
file.open(
|
||||
crashlog::getCrashLogDirectory() + "/" + getDateString(true) + ".log",
|
||||
std::ios::app
|
||||
);
|
||||
std::stringstream file;
|
||||
|
||||
// init symbols so we can get some juicy debug info
|
||||
g_symbolsInitialized = SymInitialize(
|
||||
|
@ -315,6 +311,20 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
|
|||
file << "\n== Installed Mods ==\n";
|
||||
printMods(file);
|
||||
|
||||
// show message box on debug mode
|
||||
#ifdef GEODE_DEBUG
|
||||
MessageBoxA(nullptr, file.str().c_str(), "Geode Crashed", MB_ICONERROR);
|
||||
#endif
|
||||
|
||||
// save actual file
|
||||
std::ofstream actualFile;
|
||||
actualFile.open(
|
||||
crashlog::getCrashLogDirectory() + "/" + getDateString(true) + ".log",
|
||||
std::ios::app
|
||||
);
|
||||
actualFile << file.rdbuf() << std::flush;
|
||||
actualFile.close();
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,8 @@ void Loader::createDirectories() {
|
|||
}
|
||||
|
||||
void Loader::updateResourcePaths() {
|
||||
log::debug("Updating resources paths");
|
||||
|
||||
// add own geode/resources directory
|
||||
CCFileUtils::sharedFileUtils()->addSearchPath(
|
||||
(this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY).string().c_str()
|
||||
|
@ -61,11 +63,20 @@ void Loader::updateResourcePaths() {
|
|||
|
||||
// add geode/temp/mod.id/resources for accessing additional resources in mods
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
if (mod->m_addResourcesToSearchPath) {
|
||||
CCFileUtils::sharedFileUtils()->addSearchPath(
|
||||
(tempDir / mod->getID() / "resources").string().c_str()
|
||||
);
|
||||
}
|
||||
this->updateModResourcePaths(mod);
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::updateModResourcePaths(Mod* mod) {
|
||||
if (mod->m_addResourcesToSearchPath) {
|
||||
CCFileUtils::sharedFileUtils()->addSearchPath(
|
||||
(this->getGeodeDirectory() /
|
||||
GEODE_TEMP_DIRECTORY /
|
||||
mod->getID() /
|
||||
"resources"
|
||||
).string().c_str()
|
||||
);
|
||||
log::debug("Added resources path for {}", mod->getID());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,11 +99,15 @@ void Loader::updateModResources(Mod* mod) {
|
|||
CCTextureCache::sharedTextureCache()->addImage(png.c_str(), false);
|
||||
CCSpriteFrameCache::sharedSpriteFrameCache()
|
||||
->addSpriteFramesWithFile(plist.c_str());
|
||||
|
||||
log::debug("Added resources for {}", mod->getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::updateResources() {
|
||||
log::debug("Adding mod resources");
|
||||
|
||||
// add own spritesheets
|
||||
this->updateModResources(InternalMod::get());
|
||||
|
||||
|
@ -326,6 +341,12 @@ bool Loader::setup() {
|
|||
if (m_isSetup)
|
||||
return true;
|
||||
|
||||
if (crashlog::setupPlatformHandler()) {
|
||||
log::debug("Set up platform crash logger");
|
||||
} else {
|
||||
log::debug("Unable to set up platform crash logger");
|
||||
}
|
||||
|
||||
log::debug("Setting up Loader...");
|
||||
|
||||
this->createDirectories();
|
||||
|
@ -335,14 +356,9 @@ bool Loader::setup() {
|
|||
// add resources on startup
|
||||
this->queueInGDThread([]() {
|
||||
Loader::get()->updateResourcePaths();
|
||||
Loader::get()->updateResources();
|
||||
});
|
||||
|
||||
if (crashlog::setupPlatformHandler()) {
|
||||
log::debug("Set up platform crash logger");
|
||||
} else {
|
||||
log::debug("Unable to set up platform crash logger");
|
||||
}
|
||||
|
||||
m_isSetup = true;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
static std::string sanitizeDetailsData(unsigned char* start, unsigned char* end) {
|
||||
static std::string sanitizeDetailsData(std::string const& str) {
|
||||
// delete CRLF
|
||||
return utils::string::replace(std::string(start, end), "\r", "");
|
||||
return utils::string::replace(str, "\r", "");
|
||||
}
|
||||
|
||||
Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
||||
|
@ -194,6 +194,9 @@ Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) {
|
|||
if (!res) return res;
|
||||
auto info = res.value();
|
||||
info.m_path = path;
|
||||
if (path.has_parent_path()) {
|
||||
info.addSpecialFiles(path.parent_path());
|
||||
}
|
||||
return Ok(info);
|
||||
} catch(std::exception& e) {
|
||||
return Err("Unable to parse mod.json: " + std::string(e.what()));
|
||||
|
@ -241,24 +244,51 @@ Result<ModInfo> ModInfo::createFromGeodeFile(ghc::filesystem::path const& path)
|
|||
}
|
||||
auto info = res.value();
|
||||
info.m_path = path;
|
||||
|
||||
info.addSpecialFiles(unzip);
|
||||
|
||||
return Ok(info);
|
||||
}
|
||||
|
||||
Result<> ModInfo::addSpecialFiles(ZipFile& unzip) {
|
||||
// unzip known MD files
|
||||
using God = std::initializer_list<std::pair<std::string, std::optional<std::string>*>>;
|
||||
for (auto [file, target] : God {
|
||||
{ "about.md", &info.m_details },
|
||||
{ "changelog.md", &info.m_changelog },
|
||||
{ "support.md", &info.m_supportInfo },
|
||||
}) {
|
||||
for (auto& [file, target] : getSpecialFiles()) {
|
||||
if (unzip.fileExists(file)) {
|
||||
unsigned long readSize = 0;
|
||||
auto fileData = unzip.getFileData(file, &readSize);
|
||||
if (!fileData || !readSize) {
|
||||
return Err("Unable to read \"" + path.string() + "\"/" + file);
|
||||
return Err("Unable to read \"" + file + "\"");
|
||||
} else {
|
||||
*target = sanitizeDetailsData(fileData, fileData + readSize);
|
||||
*target = sanitizeDetailsData(
|
||||
std::string(fileData, fileData + readSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(info);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) {
|
||||
// unzip known MD files
|
||||
for (auto& [file, target] : getSpecialFiles()) {
|
||||
if (ghc::filesystem::exists(dir / file)) {
|
||||
auto data = file::readString(dir / file);
|
||||
if (!data) {
|
||||
return Err("Unable to read \"" + file + "\": " + data.error());
|
||||
}
|
||||
*target = sanitizeDetailsData(data.value());
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
std::vector<std::pair<
|
||||
std::string,
|
||||
std::optional<std::string>*
|
||||
>> ModInfo::getSpecialFiles() {
|
||||
return {
|
||||
{ "about.md", &m_details },
|
||||
{ "changelog.md", &m_changelog },
|
||||
{ "support.md", &m_supportInfo },
|
||||
};
|
||||
}
|
||||
|
|
|
@ -46,8 +46,18 @@ Result<Mod*> Loader::loadModFromFile(std::string const& path) {
|
|||
|
||||
// enable mod if needed
|
||||
mod->m_enabled = Loader::get()->shouldLoadMod(mod->m_info.m_id);
|
||||
this->m_mods.insert({ res.value().m_id, mod });
|
||||
m_mods.insert({ res.value().m_id, mod });
|
||||
mod->updateDependencyStates();
|
||||
|
||||
return Ok<>(mod);
|
||||
log::debug("loaded mod, adding resources");
|
||||
|
||||
// add mod resources
|
||||
this->queueInGDThread([this, mod]() {
|
||||
log::debug("steve :pleading_face:");
|
||||
|
||||
this->updateModResourcePaths(mod);
|
||||
this->updateModResources(mod);
|
||||
});
|
||||
|
||||
return Ok(mod);
|
||||
}
|
||||
|
|
|
@ -265,7 +265,6 @@ bool ModInfoLayer::init(ModObject* obj, ModListView* list) {
|
|||
m_buttonMenu->addChild(issuesBtn);
|
||||
}
|
||||
|
||||
|
||||
if (isInstalledMod) {
|
||||
auto settingsSpr = CCSprite::createWithSpriteFrameName(
|
||||
"GJ_optionsBtn_001.png"
|
||||
|
@ -418,10 +417,12 @@ bool ModInfoLayer::init(ModObject* obj, ModListView* list) {
|
|||
);
|
||||
m_installStatus->setVisible(false);
|
||||
m_mainLayer->addChild(m_installStatus);
|
||||
}
|
||||
|
||||
if (auto handle = Index::get()->isInstallingItem(m_info.m_id)) {
|
||||
m_installations.push_back(handle);
|
||||
}
|
||||
// check if this mod is being installed/updated, and if so, update UI
|
||||
if (auto handle = Index::get()->isInstallingItem(m_info.m_id)) {
|
||||
m_installation = handle;
|
||||
this->install();
|
||||
}
|
||||
|
||||
auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
|
||||
|
@ -516,36 +517,16 @@ void ModInfoLayer::onInstallMod(CCObject*) {
|
|||
"OK"
|
||||
)->show();
|
||||
}
|
||||
auto items = ticketRes.value();
|
||||
m_installation = ticketRes.value();
|
||||
|
||||
createQuickPopup(
|
||||
"Install",
|
||||
"The following <cb>mods</c> will be installed: " +
|
||||
utils::vector::join(items.toInstall(), ",") + ".",
|
||||
utils::container::join(m_installation->toInstall(), ",") + ".",
|
||||
"Cancel", "OK",
|
||||
[this, items](FLAlertLayer*, bool btn2) {
|
||||
[this](FLAlertLayer*, bool btn2) {
|
||||
if (btn2) {
|
||||
if (m_updateVersionLabel) {
|
||||
m_updateVersionLabel->setVisible(false);
|
||||
}
|
||||
this->updateInstallStatus("Starting install", 0);
|
||||
|
||||
m_installBtn->setTarget(
|
||||
this, menu_selector(ModInfoLayer::onCancelInstall)
|
||||
);
|
||||
m_installBtnSpr->setString("Cancel");
|
||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
||||
|
||||
this->retain();
|
||||
|
||||
m_installations = items.begin([this](
|
||||
std::string const& mod,
|
||||
UpdateStatus status,
|
||||
std::string const& value,
|
||||
uint8_t progress
|
||||
) {
|
||||
this->modInstallProgress(mod, status, value, progress);
|
||||
});
|
||||
this->install();
|
||||
} else {
|
||||
this->updateInstallStatus("", 0);
|
||||
}
|
||||
|
@ -556,10 +537,8 @@ void ModInfoLayer::onInstallMod(CCObject*) {
|
|||
void ModInfoLayer::onCancelInstall(CCObject*) {
|
||||
m_installBtn->setEnabled(false);
|
||||
m_installBtnSpr->setString("Cancelling");
|
||||
for (auto& inst : m_installations) {
|
||||
inst->cancel();
|
||||
}
|
||||
m_installations.clear();
|
||||
m_installation->cancel();
|
||||
m_installation = nullptr;
|
||||
if (m_updateVersionLabel) {
|
||||
m_updateVersionLabel->setVisible(true);
|
||||
}
|
||||
|
@ -620,7 +599,7 @@ void ModInfoLayer::updateInstallStatus(
|
|||
}
|
||||
|
||||
void ModInfoLayer::modInstallProgress(
|
||||
std::string const& mod,
|
||||
InstallHandle,
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t percentage
|
||||
|
@ -639,22 +618,10 @@ void ModInfoLayer::modInstallProgress(
|
|||
m_installBtnSpr->setString("Install");
|
||||
m_installBtnSpr->setBG("GE_button_01.png"_spr, false);
|
||||
|
||||
for (auto& inst : m_installations) {
|
||||
inst->cancel();
|
||||
}
|
||||
m_installations.clear();
|
||||
this->release();
|
||||
m_installation = nullptr;
|
||||
} break;
|
||||
|
||||
case UpdateStatus::Finished: {
|
||||
// if some installations are still running, keep going
|
||||
for (auto& inst : m_installations) {
|
||||
if (!inst->finished()) return;
|
||||
}
|
||||
|
||||
// load mods
|
||||
Loader::get()->refreshMods();
|
||||
|
||||
this->updateInstallStatus("", 100);
|
||||
|
||||
FLAlertLayer::create(
|
||||
|
@ -665,12 +632,10 @@ void ModInfoLayer::modInstallProgress(
|
|||
"OK"
|
||||
)->show();
|
||||
|
||||
m_installations.clear();
|
||||
m_installation = nullptr;
|
||||
|
||||
if (m_list) m_list->refreshList();
|
||||
this->onClose(nullptr);
|
||||
|
||||
this->release();
|
||||
} break;
|
||||
|
||||
default: {
|
||||
|
@ -679,6 +644,25 @@ void ModInfoLayer::modInstallProgress(
|
|||
}
|
||||
}
|
||||
|
||||
void ModInfoLayer::install() {
|
||||
if (m_updateVersionLabel) {
|
||||
m_updateVersionLabel->setVisible(false);
|
||||
}
|
||||
this->updateInstallStatus("Starting install", 0);
|
||||
|
||||
m_installBtn->setTarget(
|
||||
this, menu_selector(ModInfoLayer::onCancelInstall)
|
||||
);
|
||||
m_installBtnSpr->setString("Cancel");
|
||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
||||
|
||||
m_callbackID = m_installation->start(std::bind(
|
||||
&ModInfoLayer::modInstallProgress, this,
|
||||
std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3, std::placeholders::_4
|
||||
));
|
||||
}
|
||||
|
||||
void ModInfoLayer::uninstall() {
|
||||
auto res = m_mod->uninstall();
|
||||
if (!res) {
|
||||
|
@ -753,7 +737,9 @@ void ModInfoLayer::keyDown(enumKeyCodes key) {
|
|||
void ModInfoLayer::onClose(CCObject* pSender) {
|
||||
this->setKeyboardEnabled(false);
|
||||
this->removeFromParentAndCleanup(true);
|
||||
m_installations.clear();
|
||||
if (m_installation) {
|
||||
m_installation->leave(m_callbackID);
|
||||
}
|
||||
};
|
||||
|
||||
ModInfoLayer* ModInfoLayer::create(Mod* mod, ModListView* list) {
|
||||
|
@ -827,8 +813,8 @@ CCNode* ModInfoLayer::createLogoSpr(IndexItem const& item) {
|
|||
if (Index::get()->isFeaturedItem(item.m_info.m_id)) {
|
||||
auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr);
|
||||
spr->setPosition(
|
||||
logoGlow->getContentSize().width / 2 + 1.f,
|
||||
logoGlow->getContentSize().height / 2 - .6f
|
||||
logoGlow->getContentSize().width / 2,
|
||||
logoGlow->getContentSize().height / 2
|
||||
);
|
||||
logoGlow->setContentSize(spr->getContentSize());
|
||||
logoGlow->addChild(spr);
|
||||
|
@ -838,7 +824,6 @@ CCNode* ModInfoLayer::createLogoSpr(IndexItem const& item) {
|
|||
return spr;
|
||||
}
|
||||
|
||||
|
||||
void ModInfoLayer::showIssueReportPopup(ModInfo const& info) {
|
||||
if (info.m_issues) {
|
||||
MDPopup::create(
|
||||
|
|
|
@ -38,7 +38,8 @@ protected:
|
|||
IconButtonSprite* m_installBtnSpr;
|
||||
CCMenuItemSpriteExtra* m_installBtn;
|
||||
CCLabelBMFont* m_updateVersionLabel = nullptr;
|
||||
InstallHandles m_installations;
|
||||
InstallHandle m_installation;
|
||||
InstallItems::CallbackID m_callbackID;
|
||||
MDTextArea* m_detailsArea;
|
||||
MDTextArea* m_changelogArea;
|
||||
Scrollbar* m_scrollbar;
|
||||
|
@ -61,7 +62,7 @@ protected:
|
|||
void updateInstallStatus(std::string const& status, uint8_t progress);
|
||||
|
||||
void modInstallProgress(
|
||||
std::string const& mod,
|
||||
InstallHandle handle,
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t percentage
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include <Geode/binding/StatsCell.hpp>
|
||||
#include <Geode/binding/ButtonSprite.hpp>
|
||||
#include <Geode/binding/TableView.hpp>
|
||||
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
|
||||
template<class T>
|
||||
static bool tryOrAlert(Result<T> const& res, const char* title) {
|
||||
|
|
|
@ -173,14 +173,38 @@ Result<> utils::file::writeBinary(ghc::filesystem::path const& path, byte_array
|
|||
}
|
||||
|
||||
Result<> utils::file::createDirectory(std::string const& path) {
|
||||
if (ghc::filesystem::create_directory(path))
|
||||
return Ok<>();
|
||||
try {
|
||||
if (ghc::filesystem::create_directory(path)) {
|
||||
return Ok<>();
|
||||
}
|
||||
} catch(...) {}
|
||||
return Err<>("Unable to create directory");
|
||||
}
|
||||
|
||||
Result<> utils::file::createDirectory(ghc::filesystem::path const& path) {
|
||||
try {
|
||||
if (ghc::filesystem::create_directory(path)) {
|
||||
return Ok<>();
|
||||
}
|
||||
} catch(...) {}
|
||||
return Err<>("Unable to create directory");
|
||||
}
|
||||
|
||||
Result<> utils::file::createDirectoryAll(std::string const& path) {
|
||||
if (ghc::filesystem::create_directories(path))
|
||||
return Ok<>();
|
||||
try {
|
||||
if (ghc::filesystem::create_directories(path)) {
|
||||
return Ok<>();
|
||||
}
|
||||
} catch(...) {}
|
||||
return Err<>("Unable to create directories");
|
||||
}
|
||||
|
||||
Result<> utils::file::createDirectoryAll(ghc::filesystem::path const& path) {
|
||||
try {
|
||||
if (ghc::filesystem::create_directories(path)) {
|
||||
return Ok<>();
|
||||
}
|
||||
} catch(...) {}
|
||||
return Err<>("Unable to create directories");
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue