Refactor some stuff in Loader (#420)

* move console stuff from loader

* compile platform sources only per platform

* move ipc from loader

* move updater from loader

* remove Loader::didLastLaunchCrash

* remove platformdata from loader

* move updaters events and filters too
This commit is contained in:
ConfiG 2024-01-14 14:23:34 +03:00 committed by GitHub
parent 05064eb4d5
commit be7ee3ef18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 710 additions and 811 deletions

View file

@ -60,9 +60,6 @@ file(GLOB SOURCES CONFIGURE_DEPENDS
src/hooks/*.cpp
src/ids/*.cpp
src/internal/*.cpp
src/platform/mac/*.cpp
src/platform/ios/*.cpp
src/platform/android/*.cpp
src/loader/*.cpp
src/load.cpp
src/utils/*.cpp

View file

@ -4,9 +4,9 @@
#include "Loader.hpp"
#include <matjson.hpp>
namespace geode {
namespace geode::ipc {
#ifdef GEODE_IS_WINDOWS
constexpr char const* IPC_PIPE_NAME = "\\\\.\\pipe\\GeodeIPCPipe";
constexpr char const* IPC_PIPE_NAME = R"(\\.\pipe\GeodeIPCPipe)";
#endif
#ifdef GEODE_IS_MACOS
@ -57,12 +57,12 @@ namespace geode {
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, IPCEvent* event);
IPCFilter(
IPCFilter(
std::string const& modID,
std::string const& messageID
);
IPCFilter(IPCFilter const&) = default;
};
std::monostate listenForIPC(std::string const& messageID, matjson::Value(*callback)(IPCEvent*));
std::monostate listen(std::string const& messageID, matjson::Value(*callback)(IPCEvent*));
}

View file

@ -85,8 +85,6 @@ namespace geode {
void queueInMainThread(ScheduledFunction func);
bool didLastLaunchCrash() const;
friend class LoaderImpl;
friend Mod* takeNextLoaderMod();

View file

@ -1,10 +1,11 @@
#include <Geode/modify/LoadingLayer.hpp>
#include <Geode/modify/CCLayer.hpp>
#include <Geode/utils/cocos.hpp>
#include <array>
#include <fmt/format.h>
#include <loader/LoaderImpl.hpp>
#include <loader/console.hpp>
#include <loader/updater.hpp>
using namespace geode::prelude;
@ -77,23 +78,23 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
this->setSmallText("Verifying Loader Resources");
// verify loader resources
Loader::get()->queueInMainThread([&]() {
if (!LoaderImpl::get()->verifyLoaderResources()) {
if (!updater::verifyLoaderResources()) {
log::debug("Downloading Loader Resources");
this->setSmallText("Downloading Loader Resources");
this->addChild(EventListenerNode<ResourceDownloadFilter>::create(
this->addChild(EventListenerNode<updater::ResourceDownloadFilter>::create(
this, &CustomLoadingLayer::updateResourcesProgress
));
}
else {
log::debug("Loading Loader Resources");
this->setSmallText("Loading Loader Resources");
LoaderImpl::get()->updateSpecialFiles();
updater::updateSpecialFiles();
this->continueLoadAssets();
}
});
}
void updateResourcesProgress(ResourceDownloadEvent* event) {
void updateResourcesProgress(updater::ResourceDownloadEvent* event) {
std::visit(makeVisitor {
[&](UpdateProgress const& progress) {
this->setSmallText(fmt::format(
@ -107,7 +108,7 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
},
[&](UpdateFailed const& error) {
log::debug("Failed Loader Resources");
LoaderImpl::get()->platformMessageBox(
console::messageBox(
"Error updating resources",
error + ".\n"
"You will have to install resources manually by downloading resources.zip "

View file

@ -1,4 +1,3 @@
#include "../ui/internal/list/ModListLayer.hpp"
#include <Geode/loader/Index.hpp>
@ -7,13 +6,13 @@
#include <Geode/modify/IDManager.hpp>
#include <Geode/utils/NodeIDs.hpp>
#include <Geode/ui/BasedButtonSprite.hpp>
#include <Geode/ui/GeodeUI.hpp>
#include <Geode/ui/Notification.hpp>
#include <Geode/ui/Popup.hpp>
#include <Geode/ui/MDPopup.hpp>
#include <Geode/utils/cocos.hpp>
#include <loader/ModImpl.hpp>
#include <loader/LoaderImpl.hpp>
#include <loader/updater.hpp>
using namespace geode::prelude;
@ -129,7 +128,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
// show auto update message
static bool shownUpdateInfo = false;
if (LoaderImpl::get()->isNewUpdateDownloaded() && !shownUpdateInfo) {
if (updater::isNewUpdateDownloaded() && !shownUpdateInfo) {
shownUpdateInfo = true;
auto popup = FLAlertLayer::create(
"Update downloaded",
@ -147,7 +146,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
// show crash info
static bool shownLastCrash = false;
if (
Loader::get()->didLastLaunchCrash() &&
crashlog::didLastLaunchCrash() &&
!shownLastCrash &&
!Mod::get()->template getSettingValue<bool>("disable-last-crashed-popup")
) {

View file

@ -1,4 +1,7 @@
#include <loader/LoaderImpl.hpp>
#include <loader/console.hpp>
#include <loader/IPC.hpp>
#include <loader/updater.hpp>
#include <Geode/loader/IPC.hpp>
#include <Geode/loader/Loader.hpp>
@ -18,22 +21,22 @@ using namespace geode::prelude;
$execute {
listenForSettingChanges("show-platform-console", +[](bool value) {
if (value) {
LoaderImpl::get()->openPlatformConsole();
console::open();
}
else {
LoaderImpl::get()->closePlatformConsole();
console::close();
}
});
listenForIPC("ipc-test", [](IPCEvent* event) -> matjson::Value {
ipc::listen("ipc-test", [](ipc::IPCEvent* event) -> matjson::Value {
return "Hello from Geode!";
});
listenForIPC("loader-info", [](IPCEvent* event) -> matjson::Value {
ipc::listen("loader-info", [](ipc::IPCEvent* event) -> matjson::Value {
return Mod::get()->getMetadata();
});
listenForIPC("list-mods", [](IPCEvent* event) -> matjson::Value {
ipc::listen("list-mods", [](ipc::IPCEvent* event) -> matjson::Value {
std::vector<matjson::Value> res;
auto args = *event->messageData;
@ -76,7 +79,7 @@ void tryShowForwardCompat() {
return;
// TODO: change text later
LoaderImpl::get()->platformMessageBox(
console::messageBox(
"Forward Compatibility Warning",
"Geode is running in a newer version of GD than Geode targets.\n"
"UI is going to be disabled, platform console is forced on and crashes can be more common.\n"
@ -116,7 +119,7 @@ int geodeEntry(void* platformData) {
auto internalSetupRes = LoaderImpl::get()->setupInternalMod();
log::popNest();
if (!internalSetupRes) {
LoaderImpl::get()->platformMessageBox(
console::messageBox(
"Unable to Load Geode!",
"There was a fatal error setting up "
"the internal mod and Geode can not be loaded: " + internalSetupRes.unwrapErr()
@ -131,7 +134,7 @@ int geodeEntry(void* platformData) {
if (LoaderImpl::get()->isForwardCompatMode() ||
Mod::get()->getSettingValue<bool>("show-platform-console")) {
log::debug("Opening console");
LoaderImpl::get()->openPlatformConsole();
console::open();
}
// set up loader, load mods, etc.
@ -140,7 +143,7 @@ int geodeEntry(void* platformData) {
auto setupRes = LoaderImpl::get()->setup();
log::popNest();
if (!setupRes) {
LoaderImpl::get()->platformMessageBox(
console::messageBox(
"Unable to Load Geode!",
"There was an unknown fatal error setting up "
"the loader and Geode can not be loaded. "
@ -152,12 +155,15 @@ int geodeEntry(void* platformData) {
crashlog::setupPlatformHandlerPost();
log::info("Set up loader");
log::debug("Setting up IPC");
log::pushNest();
ipc::setup();
log::popNest();
// download and install new loader update in the background
if (Mod::get()->getSettingValue<bool>("auto-check-updates")) {
log::info("Starting loader update check");
LoaderImpl::get()->checkForLoaderUpdates();
updater::checkForLoaderUpdates();
}
else {
log::info("Skipped loader update check");

View file

@ -1,16 +1,17 @@
#include <Geode/loader/IPC.hpp>
#include "IPC.hpp"
#include <matjson.hpp>
using namespace geode::prelude;
std::monostate geode::listenForIPC(std::string const& messageID, matjson::Value(*callback)(IPCEvent*)) {
std::monostate ipc::listen(std::string const& messageID, matjson::Value(*callback)(IPCEvent*)) {
(void) new EventListener(
callback, IPCFilter(getMod()->getID(), messageID)
);
return std::monostate();
}
IPCEvent::IPCEvent(
ipc::IPCEvent::IPCEvent(
void* rawPipeHandle,
std::string const& targetModID,
std::string const& messageID,
@ -22,9 +23,9 @@ IPCEvent::IPCEvent(
replyData(replyData),
messageData(std::make_unique<matjson::Value>(messageData)) {}
IPCEvent::~IPCEvent() {}
ipc::IPCEvent::~IPCEvent() {}
ListenerResult IPCFilter::handle(utils::MiniFunction<Callback> fn, IPCEvent* event) {
ListenerResult ipc::IPCFilter::handle(utils::MiniFunction<Callback> fn, IPCEvent* event) {
if (event->targetModID == m_modID && event->messageID == m_messageID) {
event->replyData = fn(event);
return ListenerResult::Stop;
@ -32,5 +33,34 @@ ListenerResult IPCFilter::handle(utils::MiniFunction<Callback> fn, IPCEvent* eve
return ListenerResult::Propagate;
}
IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID) :
ipc::IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID) :
m_modID(modID), m_messageID(messageID) {}
matjson::Value ipc::processRaw(void* rawHandle, std::string const& buffer) {
matjson::Value reply;
matjson::Value json;
try {
json = matjson::parse(buffer);
} catch (...) {
log::warn("Received IPC message that isn't valid JSON");
return reply;
}
if (!json.contains("mod") || !json["mod"].is_string()) {
log::warn("Received IPC message without 'mod' field");
return reply;
}
if (!json.contains("message") || !json["message"].is_string()) {
log::warn("Received IPC message without 'message' field");
return reply;
}
matjson::Value data;
if (json.contains("data")) {
data = json["data"];
}
// log::debug("Posting IPC event");
// ! warning: if the event system is ever made asynchronous this will break!
IPCEvent(rawHandle, json["mod"].as_string(), json["message"].as_string(), data, reply).post();
return reply;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <string>
#include <matjson.hpp>
namespace geode::ipc {
void setup();
matjson::Value processRaw(void* rawHandle, std::string const& buffer);
}

View file

@ -73,10 +73,6 @@ void Loader::queueInMainThread(ScheduledFunction func) {
return m_impl->queueInMainThread(std::move(func));
}
bool Loader::didLastLaunchCrash() const {
return m_impl->didLastLaunchCrash();
}
Mod* Loader::takeNextMod() {
return m_impl->takeNextMod();
}

View file

@ -4,6 +4,7 @@
#include "ModImpl.hpp"
#include "ModMetadataImpl.hpp"
#include "LogImpl.hpp"
#include "console.hpp"
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/IPC.hpp>
@ -82,11 +83,6 @@ Result<> Loader::Impl::setup() {
}
log::popNest();
log::debug("Setting up IPC");
log::pushNest();
this->setupIPC();
log::popNest();
log::debug("Setting up directories");
log::pushNest();
this->createDirectories();
@ -663,12 +659,8 @@ std::vector<LoadProblem> Loader::Impl::getProblems() const {
return m_problems;
}
bool Loader::Impl::didLastLaunchCrash() const {
return crashlog::didLastLaunchCrash();
}
void Loader::Impl::forceReset() {
this->closePlatformConsole();
console::close();
for (auto& [_, mod] : m_mods) {
delete mod;
}
@ -719,356 +711,6 @@ void Loader::Impl::executeGDThreadQueue() {
}
}
void Loader::Impl::logConsoleMessage(std::string const& msg) {
if (m_platformConsoleOpen) {
// TODO: make flushing optional
std::cout << msg << '\n' << std::flush;
}
}
bool Loader::Impl::platformConsoleOpen() const {
return m_platformConsoleOpen;
}
void Loader::Impl::fetchLatestGithubRelease(
utils::MiniFunction<void(matjson::Value const&)> then,
utils::MiniFunction<void(std::string const&)> expect
) {
if (m_latestGithubRelease) {
return then(m_latestGithubRelease.value());
}
// TODO: add header to not get rate limited
web::AsyncWebRequest()
.join("loader-auto-update-check")
.userAgent("github_api/1.0")
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
.json()
.then([this, then](matjson::Value const& json) {
m_latestGithubRelease = json;
then(json);
})
.expect(expect);
}
void Loader::Impl::tryDownloadLoaderResources(
std::string const& url,
bool tryLatestOnError
) {
auto tempResourcesZip = dirs::getTempDir() / "new.zip";
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
web::AsyncWebRequest()
// use the url as a join handle
.join(url)
.fetch(url)
.into(tempResourcesZip)
.then([tempResourcesZip, resourcesDir, this](auto) {
// unzip resources zip
auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
if (!unzip) {
ResourceDownloadEvent(
UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr())
).post();
return;
}
this->updateSpecialFiles();
ResourceDownloadEvent(UpdateFinished()).post();
})
.expect([this, tryLatestOnError](std::string const& info, int code) {
// if the url was not found, try downloading latest release instead
// (for development versions)
if (code == 404) {
log::warn("Unable to download resources: {}", info);
}
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
})
.progress([](auto&, double now, double total) {
ResourceDownloadEvent(
UpdateProgress(
static_cast<uint8_t>(now / total * 100.0),
"Downloading resources"
)
).post();
});
}
void Loader::Impl::updateSpecialFiles() {
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
auto res = ModMetadataImpl::getImpl(ModImpl::get()->m_metadata).addSpecialFiles(resourcesDir);
if (res.isErr()) {
log::warn("Unable to add special files: {}", res.unwrapErr());
}
}
void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
web::AsyncWebRequest()
.join("loader-tag-exists-check")
.userAgent("github_api/1.0")
.fetch(fmt::format(
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
this->getVersion().toString()
))
.json()
.then([this](matjson::Value const& json) {
this->tryDownloadLoaderResources(fmt::format(
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip",
this->getVersion().toString()
), true);
})
.expect([=, this](std::string const& info, int code) {
if (code == 404) {
if (useLatestRelease) {
log::debug("Loader version {} does not exist on Github, downloading latest resources", this->getVersion().toString());
fetchLatestGithubRelease(
[this](matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").template get<std::string>() == "resources.zip") {
this->tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
false
);
return;
}
}
ResourceDownloadEvent(
UpdateFailed("Unable to find resources in latest GitHub release")
).post();
},
[this](std::string const& info) {
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
}
);
return;
}
else {
log::debug("Loader version {} does not exist on Github, not downloading the resources", this->getVersion().toString());
}
ResourceDownloadEvent(
UpdateFinished()
).post();
}
else {
ResourceDownloadEvent(
UpdateFailed("Unable to check if tag exists: " + info)
).post();
}
});
}
bool Loader::Impl::verifyLoaderResources() {
static std::optional<bool> CACHED = std::nullopt;
if (CACHED.has_value()) {
return CACHED.value();
}
// geode/resources/geode.loader
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
// if the resources dir doesn't exist, then it's probably incorrect
if (!(
ghc::filesystem::exists(resourcesDir) &&
ghc::filesystem::is_directory(resourcesDir)
)) {
log::debug("Resources directory does not exist");
this->downloadLoaderResources(true);
return false;
}
// TODO: actually have a proper way to disable checking resources
// for development builds
if (ghc::filesystem::exists(resourcesDir / "dont-update.txt")) {
// this is kind of a hack, but it's the easiest way to prevent
// auto update while developing
log::debug("Not updating resources since dont-update.txt exists");
return true;
}
// make sure every file was covered
size_t coverage = 0;
// verify hashes
for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
auto name = file.path().filename().string();
// skip unknown files
if (!LOADER_RESOURCE_HASHES.count(name)) {
continue;
}
// verify hash
auto hash = calculateSHA256(file.path());
auto expected = LOADER_RESOURCE_HASHES.at(name);
if (hash != expected) {
log::debug("Resource hash mismatch: {} ({}, {})", name, hash.substr(0, 7), expected.substr(0, 7));
this->downloadLoaderResources();
return false;
}
coverage += 1;
}
// make sure every file was found
if (coverage != LOADER_RESOURCE_HASHES.size()) {
log::debug("Resource coverage mismatch");
this->downloadLoaderResources();
return false;
}
return true;
}
void Loader::Impl::downloadLoaderUpdate(std::string const& url) {
auto updateZip = dirs::getTempDir() / "loader-update.zip";
auto targetDir = dirs::getGeodeDir() / "update";
web::AsyncWebRequest()
.join("loader-update-download")
.fetch(url)
.into(updateZip)
.then([this, updateZip, targetDir](auto) {
// unzip resources zip
auto unzip = file::Unzip::intoDir(updateZip, targetDir, true);
if (!unzip) {
LoaderUpdateEvent(
UpdateFailed("Unable to unzip update: " + unzip.unwrapErr())
).post();
return;
}
m_isNewUpdateDownloaded = true;
LoaderUpdateEvent(UpdateFinished()).post();
})
.expect([](std::string const& info) {
LoaderUpdateEvent(
UpdateFailed("Unable to download update: " + info)
).post();
})
.progress([](auto&, double now, double total) {
LoaderUpdateEvent(
UpdateProgress(
static_cast<uint8_t>(now / total * 100.0),
"Downloading update"
)
).post();
});
}
void Loader::Impl::checkForLoaderUpdates() {
// Check for updates in the background
fetchLatestGithubRelease(
[this](matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
VersionInfo ver { 0, 0, 0 };
root.needs("tag_name").into(ver);
// make sure release is newer
if (ver <= this->getVersion()) {
return;
}
// don't auto-update major versions
if (ver.getMajor() > this->getVersion().getMajor()) {
return;
}
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (string::endsWith(
obj.needs("name").template get<std::string>(),
GEODE_PLATFORM_SHORT_IDENTIFIER ".zip"
)) {
this->downloadLoaderUpdate(
obj.needs("browser_download_url").template get<std::string>()
);
return;
}
}
LoaderUpdateEvent(
UpdateFailed("Unable to find release asset for " GEODE_PLATFORM_NAME)
).post();
},
[](std::string const& info) {
LoaderUpdateEvent(
UpdateFailed("Unable to check for updates: " + info)
).post();
}
);
}
bool Loader::Impl::isNewUpdateDownloaded() const {
return m_isNewUpdateDownloaded;
}
matjson::Value Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) {
matjson::Value reply;
matjson::Value json;
try {
json = matjson::parse(buffer);
} catch (...) {
log::warn("Received IPC message that isn't valid JSON");
return reply;
}
if (!json.contains("mod") || !json["mod"].is_string()) {
log::warn("Received IPC message without 'mod' field");
return reply;
}
if (!json.contains("message") || !json["message"].is_string()) {
log::warn("Received IPC message without 'message' field");
return reply;
}
matjson::Value data;
if (json.contains("data")) {
data = json["data"];
}
// log::debug("Posting IPC event");
// ! warning: if the event system is ever made asynchronous this will break!
IPCEvent(rawHandle, json["mod"].as_string(), json["message"].as_string(), data, reply).post();
return reply;
}
ResourceDownloadEvent::ResourceDownloadEvent(
UpdateStatus const& status
) : status(status) {}
ListenerResult ResourceDownloadFilter::handle(
utils::MiniFunction<Callback> fn,
ResourceDownloadEvent* event
) {
fn(event);
return ListenerResult::Propagate;
}
ResourceDownloadFilter::ResourceDownloadFilter() {}
LoaderUpdateEvent::LoaderUpdateEvent(
UpdateStatus const& status
) : status(status) {}
ListenerResult LoaderUpdateFilter::handle(
utils::MiniFunction<Callback> fn,
LoaderUpdateEvent* event
) {
fn(event);
return ListenerResult::Propagate;
}
LoaderUpdateFilter::LoaderUpdateFilter() {}
void Loader::Impl::provideNextMod(Mod* mod) {
m_nextModLock.lock();
if (mod) {

View file

@ -25,32 +25,6 @@
// TODO: Find a file convention for impl headers
namespace geode {
struct ResourceDownloadEvent : public Event {
const UpdateStatus status;
ResourceDownloadEvent(UpdateStatus const& status);
};
class ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
public:
using Callback = void(ResourceDownloadEvent*);
ListenerResult handle(utils::MiniFunction<Callback> fn, ResourceDownloadEvent* event);
ResourceDownloadFilter();
};
struct LoaderUpdateEvent : public Event {
const UpdateStatus status;
LoaderUpdateEvent(UpdateStatus const& status);
};
class LoaderUpdateFilter : public EventFilter<LoaderUpdateEvent> {
public:
using Callback = void(LoaderUpdateEvent*);
ListenerResult handle(utils::MiniFunction<Callback> fn, LoaderUpdateEvent* event);
LoaderUpdateFilter();
};
class Loader::Impl {
public:
mutable std::mutex m_mutex;
@ -66,11 +40,6 @@ namespace geode {
std::vector<ghc::filesystem::path> m_texturePaths;
bool m_isSetup = false;
// cache for the json of the latest github release to avoid hitting
// the github api too much
std::optional<matjson::Value> m_latestGithubRelease;
bool m_isNewUpdateDownloaded = false;
LoadingState m_loadingState;
std::vector<utils::MiniFunction<void(void)>> m_mainThreadQueue;
@ -78,9 +47,6 @@ namespace geode {
std::vector<std::pair<Hook*, Mod*>> m_uninitializedHooks;
bool m_readyToHook = false;
bool m_platformConsoleOpen = false;
void* m_platformData = nullptr;
std::mutex m_nextModMutex;
std::unique_lock<std::mutex> m_nextModLock = std::unique_lock<std::mutex>(m_nextModMutex, std::defer_lock);
std::condition_variable m_nextModCV;
@ -109,17 +75,7 @@ namespace geode {
Result<tulip::hook::HandlerHandle> getHandler(void* address);
Result<> removeHandler(void* address);
void updateSpecialFiles();
void tryDownloadLoaderResources(std::string const& url, bool tryLatestOnError = true);
void downloadLoaderResources(bool useLatestRelease = false);
void downloadLoaderUpdate(std::string const& url);
void fetchLatestGithubRelease(
utils::MiniFunction<void(matjson::Value const&)> then,
utils::MiniFunction<void(std::string const&)> expect
);
bool loadHooks();
void setupIPC();
Impl();
~Impl();
@ -157,25 +113,9 @@ namespace geode {
void updateResources(bool forceReload);
bool didLastLaunchCrash() const;
matjson::Value processRawIPC(void* rawHandle, std::string const& buffer);
void queueInMainThread(ScheduledFunction func);
void executeGDThreadQueue();
void logConsoleMessage(std::string const& msg);
void logConsoleMessageWithSeverity(std::string const& msg, Severity severity);
bool platformConsoleOpen() const;
void openPlatformConsole();
void closePlatformConsole();
void platformMessageBox(char const* title, std::string const& info, Severity severity = Severity::Error);
bool verifyLoaderResources();
void checkForLoaderUpdates();
bool isNewUpdateDownloaded() const;
bool isReadyToHook() const;
void addUninitializedHook(Hook* hook, Mod* mod);

View file

@ -1,4 +1,4 @@
#include "LoaderImpl.hpp"
#include "console.hpp"
#include "LogImpl.hpp"
#include <Geode/loader/Dirs.hpp>
@ -198,7 +198,7 @@ void Logger::push(Severity sev, Mod* mod, std::string&& content) {
auto& log = m_logs.emplace_back(sev, mod, std::move(content));
auto const logStr = log.toString(true, m_nestLevel);
LoaderImpl::get()->logConsoleMessageWithSeverity(logStr, log.getSeverity());
console::log(logStr, log.getSeverity());
m_logStream << logStr << std::endl;
}
}

View file

@ -2,6 +2,7 @@
#include "LoaderImpl.hpp"
#include "ModMetadataImpl.hpp"
#include "about.hpp"
#include "console.hpp"
#include <hash/hash.hpp>
#include <Geode/loader/Dirs.hpp>
@ -686,7 +687,7 @@ Mod* Loader::Impl::getInternalMod() {
}
auto infoRes = getModImplInfo();
if (!infoRes) {
LoaderImpl::get()->platformMessageBox(
console::messageBox(
"Fatal Internal Error",
"Unable to create internal mod info: \"" + infoRes.unwrapErr() +
"\"\n"

View file

@ -0,0 +1,10 @@
#pragma once
#include <string>
namespace geode::console {
void open();
void close();
void log(std::string const& msg, Severity severity);
void messageBox(char const* title, std::string const& info, Severity severity = Severity::Error);
}

View file

@ -0,0 +1,325 @@
#include "updater.hpp"
#include <Geode/utils/web.hpp>
#include <Geode/loader/Index.hpp>
#include <resources.hpp>
#include <hash.hpp>
#include <utility>
#include "LoaderImpl.hpp"
#include "ModMetadataImpl.hpp"
using namespace geode::prelude;
updater::ResourceDownloadEvent::ResourceDownloadEvent(
UpdateStatus status
) : status(std::move(status)) {}
ListenerResult updater::ResourceDownloadFilter::handle(
const utils::MiniFunction<Callback>& fn,
ResourceDownloadEvent* event
) {
fn(event);
return ListenerResult::Propagate;
}
updater::ResourceDownloadFilter::ResourceDownloadFilter() = default;
updater::LoaderUpdateEvent::LoaderUpdateEvent(
UpdateStatus status
) : status(std::move(status)) {}
ListenerResult updater::LoaderUpdateFilter::handle(
const utils::MiniFunction<Callback>& fn,
LoaderUpdateEvent* event
) {
fn(event);
return ListenerResult::Propagate;
}
updater::LoaderUpdateFilter::LoaderUpdateFilter() = default;
// cache for the json of the latest github release to avoid hitting
// the github api too much
std::optional<matjson::Value> s_latestGithubRelease;
bool s_isNewUpdateDownloaded = false;
void updater::fetchLatestGithubRelease(
const utils::MiniFunction<void(matjson::Value const&)>& then,
utils::MiniFunction<void(std::string const&)> expect
) {
if (s_latestGithubRelease) {
return then(s_latestGithubRelease.value());
}
// TODO: add header to not get rate limited
web::AsyncWebRequest()
.join("loader-auto-update-check")
.userAgent("github_api/1.0")
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
.json()
.then([then](matjson::Value const& json) {
s_latestGithubRelease = json;
then(json);
})
.expect(std::move(expect));
}
void updater::tryDownloadLoaderResources(
std::string const& url,
bool tryLatestOnError
) {
auto tempResourcesZip = dirs::getTempDir() / "new.zip";
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
web::AsyncWebRequest()
// use the url as a join handle
.join(url)
.fetch(url)
.into(tempResourcesZip)
.then([tempResourcesZip, resourcesDir](auto) {
// unzip resources zip
auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
if (!unzip) {
ResourceDownloadEvent(
UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr())
).post();
return;
}
updater::updateSpecialFiles();
ResourceDownloadEvent(UpdateFinished()).post();
})
.expect([tryLatestOnError](std::string const& info, int code) {
// if the url was not found, try downloading latest release instead
// (for development versions)
if (code == 404) {
log::warn("Unable to download resources: {}", info);
}
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
})
.progress([](auto&, double now, double total) {
ResourceDownloadEvent(
UpdateProgress(
static_cast<uint8_t>(now / total * 100.0),
"Downloading resources"
)
).post();
});
}
void updater::updateSpecialFiles() {
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
auto res = ModMetadataImpl::getImpl(ModImpl::get()->m_metadata).addSpecialFiles(resourcesDir);
if (res.isErr()) {
log::warn("Unable to add special files: {}", res.unwrapErr());
}
}
void updater::downloadLoaderResources(bool useLatestRelease) {
web::AsyncWebRequest()
.join("loader-tag-exists-check")
.userAgent("github_api/1.0")
.fetch(fmt::format(
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
Loader::get()->getVersion().toString()
))
.json()
.then([](matjson::Value const& json) {
updater::tryDownloadLoaderResources(fmt::format(
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip",
Loader::get()->getVersion().toString()
), true);
})
.expect([=](std::string const& info, int code) {
if (code == 404) {
if (useLatestRelease) {
log::debug("Loader version {} does not exist on Github, downloading latest resources", Loader::get()->getVersion().toString());
fetchLatestGithubRelease(
[](matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").template get<std::string>() == "resources.zip") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
false
);
return;
}
}
ResourceDownloadEvent(
UpdateFailed("Unable to find resources in latest GitHub release")
).post();
},
[](std::string const& info) {
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
}
);
return;
}
else {
log::debug("Loader version {} does not exist on GitHub, not downloading the resources", Loader::get()->getVersion().toString());
}
ResourceDownloadEvent(
UpdateFinished()
).post();
}
else {
ResourceDownloadEvent(
UpdateFailed("Unable to check if tag exists: " + info)
).post();
}
});
}
bool updater::verifyLoaderResources() {
static std::optional<bool> CACHED = std::nullopt;
if (CACHED.has_value()) {
return CACHED.value();
}
// geode/resources/geode.loader
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
// if the resources dir doesn't exist, then it's probably incorrect
if (!(
ghc::filesystem::exists(resourcesDir) &&
ghc::filesystem::is_directory(resourcesDir)
)) {
log::debug("Resources directory does not exist");
updater::downloadLoaderResources(true);
return false;
}
// TODO: actually have a proper way to disable checking resources
// for development builds
if (ghc::filesystem::exists(resourcesDir / "dont-update.txt")) {
// this is kind of a hack, but it's the easiest way to prevent
// auto update while developing
log::debug("Not updating resources since dont-update.txt exists");
return true;
}
// make sure every file was covered
size_t coverage = 0;
// verify hashes
for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
auto name = file.path().filename().string();
// skip unknown files
if (!LOADER_RESOURCE_HASHES.count(name)) {
continue;
}
// verify hash
auto hash = calculateSHA256(file.path());
const auto& expected = LOADER_RESOURCE_HASHES.at(name);
if (hash != expected) {
log::debug("Resource hash mismatch: {} ({}, {})", name, hash.substr(0, 7), expected.substr(0, 7));
updater::downloadLoaderResources();
return false;
}
coverage += 1;
}
// make sure every file was found
if (coverage != LOADER_RESOURCE_HASHES.size()) {
log::debug("Resource coverage mismatch");
updater::downloadLoaderResources();
return false;
}
return true;
}
void updater::downloadLoaderUpdate(std::string const& url) {
auto updateZip = dirs::getTempDir() / "loader-update.zip";
auto targetDir = dirs::getGeodeDir() / "update";
web::AsyncWebRequest()
.join("loader-update-download")
.fetch(url)
.into(updateZip)
.then([updateZip, targetDir](auto) {
// unzip resources zip
auto unzip = file::Unzip::intoDir(updateZip, targetDir, true);
if (!unzip) {
LoaderUpdateEvent(
UpdateFailed("Unable to unzip update: " + unzip.unwrapErr())
).post();
return;
}
s_isNewUpdateDownloaded = true;
LoaderUpdateEvent(UpdateFinished()).post();
})
.expect([](std::string const& info) {
LoaderUpdateEvent(
UpdateFailed("Unable to download update: " + info)
).post();
})
.progress([](auto&, double now, double total) {
LoaderUpdateEvent(
UpdateProgress(
static_cast<uint8_t>(now / total * 100.0),
"Downloading update"
)
).post();
});
}
void updater::checkForLoaderUpdates() {
// Check for updates in the background
fetchLatestGithubRelease(
[](matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
VersionInfo ver { 0, 0, 0 };
root.needs("tag_name").into(ver);
// make sure release is newer
if (ver <= Loader::get()->getVersion()) {
return;
}
// don't auto-update major versions
if (ver.getMajor() > Loader::get()->getVersion().getMajor()) {
return;
}
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (string::endsWith(
obj.needs("name").template get<std::string>(),
GEODE_PLATFORM_SHORT_IDENTIFIER ".zip"
)) {
updater::downloadLoaderUpdate(
obj.needs("browser_download_url").template get<std::string>()
);
return;
}
}
LoaderUpdateEvent(
UpdateFailed("Unable to find release asset for " GEODE_PLATFORM_NAME)
).post();
},
[](std::string const& info) {
LoaderUpdateEvent(
UpdateFailed("Unable to check for updates: " + info)
).post();
}
);
}
bool updater::isNewUpdateDownloaded() {
return s_isNewUpdateDownloaded;
}

View file

@ -0,0 +1,46 @@
#pragma once
#include <string>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/loader/Index.hpp>
namespace geode::updater {
struct ResourceDownloadEvent : public Event {
const UpdateStatus status;
explicit ResourceDownloadEvent(UpdateStatus status);
};
class ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
public:
using Callback = void(ResourceDownloadEvent*);
static ListenerResult handle(const utils::MiniFunction<Callback>& fn, ResourceDownloadEvent* event);
ResourceDownloadFilter();
};
struct LoaderUpdateEvent : public Event {
const UpdateStatus status;
explicit LoaderUpdateEvent(UpdateStatus status);
};
class LoaderUpdateFilter : public EventFilter<LoaderUpdateEvent> {
public:
using Callback = void(LoaderUpdateEvent*);
static ListenerResult handle(const utils::MiniFunction<Callback>& fn, LoaderUpdateEvent* event);
LoaderUpdateFilter();
};
void updateSpecialFiles();
void tryDownloadLoaderResources(std::string const& url, bool tryLatestOnError = true);
void downloadLoaderResources(bool useLatestRelease = false);
void downloadLoaderUpdate(std::string const& url);
void fetchLatestGithubRelease(
const utils::MiniFunction<void(matjson::Value const&)>& then,
utils::MiniFunction<void(std::string const&)> expect
);
bool verifyLoaderResources();
void checkForLoaderUpdates();
bool isNewUpdateDownloaded();
}

View file

@ -1,7 +1,5 @@
#include <FileWatcher.hpp>
#ifdef GEODE_IS_ANDROID
FileWatcher::FileWatcher(
ghc::filesystem::path const& file, FileWatchCallback callback, ErrorCallback error
) {
@ -23,5 +21,3 @@ void FileWatcher::watch() {
bool FileWatcher::watching() const {
return false;
}
#endif

View file

@ -0,0 +1,7 @@
#include <loader/IPC.hpp>
using namespace geode::prelude;
void ipc::setup() {
log::debug("IPC is not supported on this platform!");
}

View file

@ -1,29 +1,9 @@
#include <Geode/loader/IPC.hpp>
#include <Geode/loader/Log.hpp>
#include <loader/ModImpl.hpp>
#include <iostream>
#include <loader/LoaderImpl.hpp>
#include <Geode/utils/string.hpp>
#include <Geode/cocos/platform/android/jni/JniHelper.h>
using namespace geode::prelude;
#ifdef GEODE_IS_ANDROID
#include <Geode/cocos/platform/android/jni/JniHelper.h>
#include <android/log.h>
namespace {
android_LogPriority getLogSeverityForSeverity(Severity severity) {
switch (severity) {
case Severity::Debug: return ANDROID_LOG_DEBUG;
case Severity::Info: return ANDROID_LOG_INFO;
case Severity::Warning: return ANDROID_LOG_WARN;
case Severity::Error: return ANDROID_LOG_ERROR;
default: return ANDROID_LOG_DEFAULT;
}
}
}
std::string Loader::Impl::getGameVersion() {
if (m_gdVersion.empty()) {
/*
@ -49,33 +29,6 @@ std::string Loader::Impl::getGameVersion() {
return m_gdVersion;
}
void Loader::Impl::platformMessageBox(char const* title, std::string const& info, Severity severity) {
cocos2d::CCMessageBox(info.c_str(), title);
}
void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) {
__android_log_print(
getLogSeverityForSeverity(severity),
"Geode",
"%s",
msg.c_str()
);
}
void Loader::Impl::openPlatformConsole() {
return;
}
void Loader::Impl::closePlatformConsole() {
return;
}
void Loader::Impl::setupIPC() {
log::warn("IPC is not supported on this platform!");
}
bool Loader::Impl::userTriedToLoadDLLs() const {
return false;
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_ANDROID
#include <Geode/loader/Mod.hpp>
#include <loader/ModImpl.hpp>
@ -34,5 +32,3 @@ Result<> Mod::Impl::unloadPlatformBinary() {
return Err("Unable to free library");
}
}
#endif

View file

@ -0,0 +1,39 @@
#include <loader/console.hpp>
#include <iostream>
#include <Geode/loader/Log.hpp>
#include <android/log.h>
using namespace geode::prelude;
namespace {
android_LogPriority getLogSeverityForSeverity(Severity severity) {
switch (severity) {
case Severity::Debug: return ANDROID_LOG_DEBUG;
case Severity::Info: return ANDROID_LOG_INFO;
case Severity::Warning: return ANDROID_LOG_WARN;
case Severity::Error: return ANDROID_LOG_ERROR;
default: return ANDROID_LOG_DEFAULT;
}
}
}
void console::open() {
return;
}
void console::close() {
return;
}
void console::log(std::string const& msg, Severity severity) {
__android_log_print(
getLogSeverityForSeverity(severity),
"Geode",
"%s",
msg.c_str()
);
}
void console::messageBox(char const* title, std::string const& info, Severity severity) {
cocos2d::CCMessageBox(info.c_str(), title);
}

View file

@ -1,7 +1,5 @@
#include <crashlog.hpp>
#ifdef GEODE_IS_ANDROID
using namespace geode::prelude;
#include <Geode/utils/string.hpp>
@ -302,5 +300,3 @@ void crashlog::setupPlatformHandlerPost() {
bool crashlog::didLastLaunchCrash() {
return s_lastLaunchCrashed;
}
#endif

View file

@ -1,8 +1,6 @@
#include <Geode/c++stl/gdstdlib.hpp>
#include "../../c++stl/string-impl.hpp"
#ifdef GEODE_IS_ANDROID
#if defined(GEODE_IS_ANDROID32)
static auto constexpr NEW_SYM = "_Znwj";
@ -119,5 +117,3 @@ namespace geode::stl {
// TODO: implement this, remember its copy-on-write...
}
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#if defined(GEODE_IS_ANDROID)
#include "../load.hpp"
#include <jni.h>
@ -30,5 +28,3 @@ extern "C" [[gnu::visibility("default")]] jint JNI_OnLoad(JavaVM* vm, void* rese
extern "C" [[gnu::visibility("default")]] void emptyFunction(void*) {
// empty
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_ANDROID
using namespace geode::prelude;
#include <Geode/utils/cocos.hpp>
@ -279,5 +277,3 @@ void geode::utils::game::restart() {
nullptr
), CCDirector::get()->getRunningScene(), false);
}
#endif

View file

@ -1,7 +1,5 @@
#include <FileWatcher.hpp>
#ifdef GEODE_IS_IOS
#import <UIKit/UIKit.h>
#include <fcntl.h>
#include <iostream>
@ -52,5 +50,3 @@ void FileWatcher::watch() {
bool FileWatcher::watching() const {
return m_platformHandle != NULL;
}
#endif

View file

@ -1,15 +1,13 @@
#include <loader/LoaderImpl.hpp>
#ifdef GEODE_IS_IOS
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <loader/ModImpl.hpp>
#include <iostream>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <loader/ModImpl.hpp>
#include <iostream>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
std::cout << title << ": " << info << std::endl;
@ -41,5 +39,3 @@ void Loader::Impl::setupIPC() {
bool Loader::Impl::userTriedToLoadDLLs() const {
return false;
}
#endif

View file

@ -1,10 +1,8 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_IOS
#include <Geode/loader/Mod.hpp>
#include <loader/ModImpl.hpp>
#include <dlfcn.h>
#include <Geode/loader/Mod.hpp>
#include <loader/ModImpl.hpp>
#include <dlfcn.h>
using namespace geode::prelude;
@ -43,5 +41,3 @@ Result<> Mod::Impl::unloadPlatformBinary() {
return Err("Unable to free library");
}
}
#endif

View file

@ -1,7 +1,5 @@
#include <crashlog.hpp>
#ifdef GEODE_IS_IOS
bool crashlog::setupPlatformHandler() {
return false;
}
@ -13,5 +11,3 @@ bool crashlog::didLastLaunchCrash() {
ghc::filesystem::path crashlog::getCrashLogDirectory() {
return "";
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#if defined(GEODE_IS_IOS)
#include "../load.hpp"
#include <dlfcn.h>
#include <mach-o/dyld.h>
@ -43,5 +41,3 @@ extern "C" __attribute__((visibility("default"))) void dynamicTrigger() {
// remove when we can figure out how to not remove it
auto dynamicTriggerRef = &dynamicTrigger;
#endif

View file

@ -1,9 +1,7 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_IOS
using namespace geode::prelude;
#include <Geode/loader/Dirs.hpp>
#include <UIKit/UIKit.h>
#include <iostream>
@ -36,5 +34,3 @@ ghc::filesystem::path dirs::getGameDir() {
ghc::filesystem::path dirs::getSaveDir() {
return weaklyCanonical(CCFileUtils::sharedFileUtils()->getWritablePath().c_str());
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_MACOS
#include <cocos2d.h>
using namespace cocos2d;
@ -815,5 +813,3 @@ void CCArray::acceptVisitor(CCDataVisitor &visitor)
{
visitor.visit(this);
}
#endif

View file

@ -1,10 +1,8 @@
#include <FileWatcher.hpp>
#ifdef GEODE_IS_MACOS
#import <Cocoa/Cocoa.h>
#include <fcntl.h>
#include <iostream>
#import <Cocoa/Cocoa.h>
#include <fcntl.h>
#include <iostream>
// static constexpr const auto notifyAttributes = FILE_NOTIFY_CHANGE_LAST_WRITE |
// FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE;
@ -52,5 +50,3 @@ void FileWatcher::watch() {
bool FileWatcher::watching() const {
return m_platformHandle != NULL;
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_MACOS
#include <Geode/loader/Mod.hpp>
#include <loader/ModImpl.hpp>
#include <dlfcn.h>
@ -43,5 +41,3 @@ Result<> Mod::Impl::unloadPlatformBinary() {
return Err("Unable to free library");
}
}
#endif

View file

@ -1,7 +1,5 @@
#include <crashlog.hpp>
#ifdef GEODE_IS_MACOS
#include <Geode/utils/string.hpp>
#include <array>
#include <thread>
@ -391,6 +389,3 @@ bool crashlog::didLastLaunchCrash() {
ghc::filesystem::path crashlog::getCrashLogDirectory() {
return dirs::getGeodeDir() / "crashlogs";
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/c++stl/gdstdlib.hpp>
#ifdef GEODE_IS_MACOS
namespace gd {
namespace {
static inline auto emptyInternalString() {
@ -57,5 +55,3 @@ namespace gd {
return std::string(*this) == std::string(other);
}
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#if defined(GEODE_IS_MACOS)
#import <Cocoa/Cocoa.h>
#include "../load.hpp"
#include <dlfcn.h>
@ -151,5 +149,3 @@ __attribute__((constructor)) void _entry() {
if (!loadGeode())
return;
}
#endif

View file

@ -1,8 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_MACOS
using namespace geode::prelude;
#include <Geode/loader/Dirs.hpp>
@ -304,5 +301,3 @@ Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::s
return Ok((void*)method_getImplementation(method));
}
#endif

View file

@ -2,8 +2,6 @@
#include <iostream>
#include <thread>
#ifdef GEODE_IS_WINDOWS
static constexpr auto const notifyAttributes =
FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE;
@ -100,5 +98,3 @@ bool FileWatcher::watching() const {
HANDLE handle = (HANDLE)m_platformHandle;
return handle != INVALID_HANDLE_VALUE && handle != nullptr;
}
#endif

View file

@ -0,0 +1,75 @@
#include <Geode/loader/IPC.hpp>
#include <loader/IPC.hpp>
#include <thread>
#include <optional>
#include <string>
using namespace geode::prelude;
static constexpr auto IPC_BUFFER_SIZE = 512;
void ipcPipeThread(HANDLE pipe) {
char buffer[IPC_BUFFER_SIZE * sizeof(TCHAR)];
DWORD read;
std::optional<std::string> replyID;
// log::debug("Waiting for I/O");
if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
buffer[read] = '\0';
std::string reply = ipc::processRaw((void*)pipe, buffer).dump();
DWORD written;
WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr);
}
// log::debug("Connection done");
FlushFileBuffers(pipe);
DisconnectNamedPipe(pipe);
CloseHandle(pipe);
// log::debug("Disconnected pipe");
}
void ipc::setup() {
std::thread ipcThread([]() {
while (true) {
auto pipe = CreateNamedPipeA(
ipc::IPC_PIPE_NAME,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
IPC_BUFFER_SIZE,
IPC_BUFFER_SIZE,
NMPWAIT_USE_DEFAULT_WAIT,
nullptr
);
if (pipe == INVALID_HANDLE_VALUE) {
// todo: Rn this quits IPC, but we might wanna change that later
// to just continue trying. however, I'm assuming that if
// CreateNamedPipeA fails, then it will probably fail again if
// you try right after, so changing the break; to continue; might
// just result in the console getting filled with error messages
log::warn("Unable to create pipe, quitting IPC");
break;
}
// log::debug("Waiting for pipe connections");
if (ConnectNamedPipe(pipe, nullptr)) {
// log::debug("Got connection, creating thread");
std::thread pipeThread(&ipcPipeThread, pipe);
// SetThreadDescription(pipeThread.native_handle(), L"Geode IPC Pipe");
pipeThread.detach();
}
else {
// log::debug("No connection, cleaning pipe");
CloseHandle(pipe);
}
}
});
// SetThreadDescription(ipcThread.native_handle(), L"Geode Main IPC");
ipcThread.detach();
log::debug("IPC set up");
}

View file

@ -1,19 +1,13 @@
#include <Geode/loader/IPC.hpp>
#include <Geode/loader/Log.hpp>
#include <loader/ModImpl.hpp>
#include <iostream>
#include <loader/LoaderImpl.hpp>
#include <Geode/utils/string.hpp>
#include <loader/LogImpl.hpp>
using namespace geode::prelude;
#ifdef GEODE_IS_WINDOWS
#include <Psapi.h>
static constexpr auto IPC_BUFFER_SIZE = 512;
#include "gdTimestampMap.hpp"
std::string Loader::Impl::getGameVersion() {
if (m_gdVersion.empty()) {
@ -25,161 +19,6 @@ std::string Loader::Impl::getGameVersion() {
return m_gdVersion;
}
void Loader::Impl::platformMessageBox(char const* title, std::string const& info, Severity severity) {
unsigned int icon;
switch (severity) {
case Severity::Debug:
case Severity::Info:
case Severity::Notice:
icon = MB_ICONINFORMATION;
break;
case Severity::Warning:
icon = MB_ICONWARNING;
break;
default:
icon = MB_ICONERROR;
break;
}
MessageBoxA(nullptr, info.c_str(), title, icon);
}
bool hasAnsiColorSupport = false;
void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) {
if (!m_platformConsoleOpen)
return;
if (!hasAnsiColorSupport) {
std::cout << msg << "\n" << std::flush;
return;
}
int color = 0;
switch (severity) {
case Severity::Debug:
color = 243;
break;
case Severity::Info:
color = 33;
break;
case Severity::Warning:
color = 229;
break;
case Severity::Error:
color = 9;
break;
default:
color = 7;
break;
}
auto const colorStr = fmt::format("\x1b[38;5;{}m", color);
auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 12),
msg.substr(12));
std::cout << newMsg << "\n" << std::flush;
}
void Loader::Impl::openPlatformConsole() {
if (m_platformConsoleOpen) return;
if (AllocConsole() == 0) return;
SetConsoleCP(CP_UTF8);
// redirect console output
freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout);
freopen_s(reinterpret_cast<FILE**>(stdin), "CONIN$", "r", stdin);
// Set output mode to handle ansi color sequences
auto handleStdout = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = 0;
if (GetConsoleMode(handleStdout, &consoleMode)) {
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (SetConsoleMode(handleStdout, consoleMode)) {
hasAnsiColorSupport = true;
}
}
m_platformConsoleOpen = true;
for (auto const& log : log::Logger::get()->list()) {
this->logConsoleMessageWithSeverity(log.toString(true), log.getSeverity());
}
}
void Loader::Impl::closePlatformConsole() {
if (!m_platformConsoleOpen) return;
fclose(stdin);
fclose(stdout);
FreeConsole();
m_platformConsoleOpen = false;
}
void ipcPipeThread(HANDLE pipe) {
char buffer[IPC_BUFFER_SIZE * sizeof(TCHAR)];
DWORD read;
std::optional<std::string> replyID = std::nullopt;
// log::debug("Waiting for I/O");
if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
buffer[read] = '\0';
std::string reply = LoaderImpl::get()->processRawIPC((void*)pipe, buffer).dump();
DWORD written;
WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr);
}
// log::debug("Connection done");
FlushFileBuffers(pipe);
DisconnectNamedPipe(pipe);
CloseHandle(pipe);
// log::debug("Disconnected pipe");
}
void Loader::Impl::setupIPC() {
std::thread ipcThread([]() {
while (true) {
auto pipe = CreateNamedPipeA(
IPC_PIPE_NAME,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
IPC_BUFFER_SIZE,
IPC_BUFFER_SIZE,
NMPWAIT_USE_DEFAULT_WAIT,
nullptr
);
if (pipe == INVALID_HANDLE_VALUE) {
// todo: Rn this quits IPC, but we might wanna change that later
// to just continue trying. however, I'm assuming that if
// CreateNamedPipeA fails, then it will probably fail again if
// you try right after, so changing the break; to continue; might
// just result in the console getting filled with error messages
log::warn("Unable to create pipe, quitting IPC");
break;
}
// log::debug("Waiting for pipe connections");
if (ConnectNamedPipe(pipe, nullptr)) {
// log::debug("Got connection, creating thread");
std::thread pipeThread(&ipcPipeThread, pipe);
// SetThreadDescription(pipeThread.native_handle(), L"Geode IPC Pipe");
pipeThread.detach();
}
else {
// log::debug("No connection, cleaning pipe");
CloseHandle(pipe);
}
}
});
// SetThreadDescription(ipcThread.native_handle(), L"Geode Main IPC");
ipcThread.detach();
log::debug("IPC set up");
}
bool Loader::Impl::userTriedToLoadDLLs() const {
static std::unordered_set<std::string> KNOWN_MOD_DLLS {
"betteredit-v4.0.5.dll",
@ -235,5 +74,3 @@ bool Loader::Impl::userTriedToLoadDLLs() const {
return triedToLoadDLLs;
}
#endif

View file

@ -1,7 +1,5 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_WINDOWS
#include <Geode/loader/Mod.hpp>
#include <loader/ModImpl.hpp>
@ -81,5 +79,3 @@ Result<> Mod::Impl::unloadPlatformBinary() {
return Err("Unable to free the DLL: " + getLastWinError());
}
}
#endif

View file

@ -0,0 +1,96 @@
#include <loader/console.hpp>
#include <loader/LogImpl.hpp>
#include <iostream>
using namespace geode::prelude;
bool s_isOpen = false;
bool s_hasAnsiColorSupport = false;
void console::open() {
if (s_isOpen) return;
if (AllocConsole() == 0) return;
SetConsoleCP(CP_UTF8);
// redirect console output
freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout);
freopen_s(reinterpret_cast<FILE**>(stdin), "CONIN$", "r", stdin);
// Set output mode to handle ansi color sequences
auto handleStdout = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = 0;
if (GetConsoleMode(handleStdout, &consoleMode)) {
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (SetConsoleMode(handleStdout, consoleMode)) {
s_hasAnsiColorSupport = true;
}
}
s_isOpen = true;
for (auto const& log : log::Logger::get()->list()) {
console::log(log.toString(true), log.getSeverity());
}
}
void console::close() {
if (!s_isOpen) return;
fclose(stdin);
fclose(stdout);
FreeConsole();
s_isOpen = false;
}
void console::log(std::string const& msg, Severity severity) {
if (!s_isOpen)
return;
if (!s_hasAnsiColorSupport) {
std::cout << msg << "\n" << std::flush;
return;
}
int color = 0;
switch (severity) {
case Severity::Debug:
color = 243;
break;
case Severity::Info:
color = 33;
break;
case Severity::Warning:
color = 229;
break;
case Severity::Error:
color = 9;
break;
default:
color = 7;
break;
}
auto const colorStr = fmt::format("\x1b[38;5;{}m", color);
auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 12),
msg.substr(12));
std::cout << newMsg << "\n" << std::flush;
}
void console::messageBox(char const* title, std::string const& info, Severity severity) {
unsigned int icon;
switch (severity) {
case Severity::Debug:
case Severity::Info:
case Severity::Notice:
icon = MB_ICONINFORMATION;
break;
case Severity::Warning:
icon = MB_ICONWARNING;
break;
default:
icon = MB_ICONERROR;
break;
}
MessageBoxA(nullptr, info.c_str(), title, icon);
}

View file

@ -2,8 +2,6 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_WINDOWS
#include <crashlog.hpp>
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Loader.hpp>
@ -301,5 +299,3 @@ void crashlog::setupPlatformHandlerPost() {}
ghc::filesystem::path crashlog::getCrashLogDirectory() {
return dirs::getGeodeDir() / "crashlogs";
}
#endif

View file

@ -1,7 +1,5 @@
#include "../../c++stl/string-impl.hpp"
#ifdef GEODE_IS_WINDOWS
namespace geode::stl {
void StringImpl::setEmpty() {
data.m_size = 0;
@ -43,5 +41,3 @@ namespace geode::stl {
data.m_capacity = cap;
}
}
#endif

View file

@ -1,11 +1,11 @@
#include <Geode/DefaultInclude.hpp>
#if defined(GEODE_IS_WINDOWS)
#include "../load.hpp"
#include <Windows.h>
#include "loader/LoaderImpl.hpp"
#include "loader/console.hpp"
using namespace geode::prelude;
void updateGeode() {
@ -38,7 +38,7 @@ int WINAPI gdMainHook(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd
updateGeode();
if (versionToTimestamp(GEODE_STR(GEODE_GD_VERSION)) > gdTimestamp) {
LoaderImpl::get()->platformMessageBox(
console::messageBox(
"Unable to Load Geode!",
fmt::format(
"This version of Geode is made for Geometry Dash {} "
@ -142,7 +142,7 @@ void earlyError(std::string message) {
std::ofstream fout("_geode_early_error.txt");
fout << message;
fout.close();
LoaderImpl::get()->platformMessageBox("Unable to Load Geode!", message);
console::messageBox("Unable to Load Geode!", message);
}
BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) {
@ -176,5 +176,3 @@ BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) {
return TRUE;
}
#endif

View file

@ -1,8 +1,6 @@
#include "nfdwin.hpp"
#include <Geode/utils/string.hpp>
#ifdef GEODE_IS_WINDOWS
using Path = ghc::filesystem::path;
using Paths = std::vector<ghc::filesystem::path>;
@ -296,5 +294,3 @@ Result<> nfdPick(
return Err("Unknown error");
}
#endif

View file

@ -10,8 +10,6 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_WINDOWS
#ifdef __MINGW32__
// Explicitly setting NTDDI version, this is necessary for the MinGW compiler
#define NTDDI_VERSION NTDDI_VISTA
@ -58,5 +56,3 @@ Result<> nfdPick(
file::FilePickOptions const& options,
void* result
);
#endif

View file

@ -1,9 +1,7 @@
#include <Geode/DefaultInclude.hpp>
#ifdef GEODE_IS_WINDOWS
using namespace geode::prelude;
#include <Geode/loader/Dirs.hpp>
#include "nfdwin.hpp"
#include <ghc/fs_fwd.hpp>
@ -265,5 +263,3 @@ Result<> geode::hook::addObjcMethod(std::string const& className, std::string co
Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::string const& selectorName) {
return Err("Wrong platform");
}
#endif