make ModInfo pimpl

This commit is contained in:
altalk23 2023-01-31 16:43:17 +03:00
parent d91836993d
commit 51990ad89b
13 changed files with 478 additions and 206 deletions

View file

@ -1,10 +1,12 @@
#pragma once #pragma once
#include "Types.hpp"
#include <json.hpp>
#include "../utils/VersionInfo.hpp"
#include "../utils/Result.hpp" #include "../utils/Result.hpp"
#include "../utils/VersionInfo.hpp"
#include "Setting.hpp" #include "Setting.hpp"
#include "Types.hpp"
#include <json.hpp>
#include <memory>
namespace geode { namespace geode {
namespace utils::file { namespace utils::file {
@ -28,20 +30,34 @@ namespace geode {
* Represents all the data gatherable * Represents all the data gatherable
* from mod.json * from mod.json
*/ */
struct GEODE_DLL ModInfo { class GEODE_DLL ModInfo {
class Impl;
std::unique_ptr<Impl> m_impl;
public:
ModInfo();
ModInfo(ModInfo const& other);
ModInfo(ModInfo&& other) noexcept;
ModInfo& operator=(ModInfo const& other);
ModInfo& operator=(ModInfo&& other) noexcept;
~ModInfo();
/** /**
* Path to the mod file * Path to the mod file
*/ */
ghc::filesystem::path path; ghc::filesystem::path& path();
ghc::filesystem::path const& path() const;
/** /**
* Name of the platform binary within * Name of the platform binary within
* the mod zip * the mod zip
*/ */
std::string binaryName; std::string& binaryName();
std::string const& binaryName() const;
/** /**
* Mod Version. Should follow semver. * Mod Version. Should follow semver.
*/ */
VersionInfo version { 1, 0, 0 }; VersionInfo& version();
VersionInfo const& version() const;
/** /**
* Human-readable ID of the Mod. * Human-readable ID of the Mod.
* Recommended to be in the format * Recommended to be in the format
@ -51,14 +67,16 @@ namespace geode {
* be restricted to the ASCII * be restricted to the ASCII
* character set. * character set.
*/ */
std::string id; std::string& id();
std::string const& id() const;
/** /**
* Name of the mod. May contain * Name of the mod. May contain
* spaces & punctuation, but should * spaces & punctuation, but should
* be restricted to the ASCII * be restricted to the ASCII
* character set. * character set.
*/ */
std::string name; std::string& name();
std::string const& name() const;
/** /**
* The name of the head developer. * The name of the head developer.
* Should be a single name, like * Should be a single name, like
@ -70,61 +88,74 @@ namespace geode {
* should be named in `m_credits` * should be named in `m_credits`
* instead. * instead.
*/ */
std::string developer; std::string& developer();
std::string const& developer() const;
/** /**
* Short & concise description of the * Short & concise description of the
* mod. * mod.
*/ */
std::optional<std::string> description; std::optional<std::string>& description();
std::optional<std::string> const& description() const;
/** /**
* Detailed description of the mod, writtenin Markdown (see * Detailed description of the mod, writtenin Markdown (see
* <Geode/ui/MDTextArea.hpp>) for more info * <Geode/ui/MDTextArea.hpp>) for more info
*/ */
std::optional<std::string> details; std::optional<std::string>& details();
std::optional<std::string> const& details() const;
/** /**
* Changelog for the mod, written in Markdown (see * Changelog for the mod, written in Markdown (see
* <Geode/ui/MDTextArea.hpp>) for more info * <Geode/ui/MDTextArea.hpp>) for more info
*/ */
std::optional<std::string> changelog; std::optional<std::string>& changelog();
std::optional<std::string> const& changelog() const;
/** /**
* Support info for the mod; this means anything to show ways to * Support info for the mod; this means anything to show ways to
* support the mod's development, like donations. Written in Markdown * support the mod's development, like donations. Written in Markdown
* (see MDTextArea for more info) * (see MDTextArea for more info)
*/ */
std::optional<std::string> supportInfo; std::optional<std::string>& supportInfo();
std::optional<std::string> const& supportInfo() const;
/** /**
* Git Repository of the mod * Git Repository of the mod
*/ */
std::optional<std::string> repository; std::optional<std::string>& repository();
std::optional<std::string> const& repository() const;
/** /**
* Info about where users should report issues and request help * Info about where users should report issues and request help
*/ */
std::optional<IssuesInfo> issues; std::optional<IssuesInfo>& issues();
std::optional<IssuesInfo> const& issues() const;
/** /**
* Dependencies * Dependencies
*/ */
std::vector<Dependency> dependencies; std::vector<Dependency>& dependencies();
std::vector<Dependency> const& dependencies() const;
/** /**
* Mod spritesheet names * Mod spritesheet names
*/ */
std::vector<std::string> spritesheets; std::vector<std::string>& spritesheets();
std::vector<std::string> const& spritesheets() const;
/** /**
* Mod settings * Mod settings
* @note Not a map because insertion order must be preserved * @note Not a map because insertion order must be preserved
*/ */
std::vector<std::pair<std::string, Setting>> settings; std::vector<std::pair<std::string, Setting>>& settings();
std::vector<std::pair<std::string, Setting>> const& settings() const;
/** /**
* Whether the mod can be disabled or not * Whether the mod can be disabled or not
*/ */
bool supportsDisabling = true; bool& supportsDisabling();
bool const& supportsDisabling() const;
/** /**
* Whether the mod can be unloaded or not * Whether the mod can be unloaded or not
*/ */
bool supportsUnloading = false; bool& supportsUnloading();
bool const& supportsUnloading() const;
/** /**
* Whether this mod has to be loaded before the loading screen or not * Whether this mod has to be loaded before the loading screen or not
*/ */
bool needsEarlyLoad = false; bool& needsEarlyLoad();
bool const& needsEarlyLoad() const;
/** /**
* Create ModInfo from an unzipped .geode package * Create ModInfo from an unzipped .geode package
*/ */
@ -157,8 +188,8 @@ namespace geode {
static bool validateID(std::string const& id); static bool validateID(std::string const& id);
private: private:
std::shared_ptr<ModJson> m_rawJSON; ModJson& rawJSON();
ModJson const& rawJSON() const;
/** /**
* Version is passed for backwards * Version is passed for backwards
* compatibility if we update the mod.json * compatibility if we update the mod.json

View file

@ -381,16 +381,16 @@ void Index::updateSourceFromLocal(IndexSourceImpl* src) {
} }
auto add = addRes.unwrap(); auto add = addRes.unwrap();
// check if this major version of this item has already been added // check if this major version of this item has already been added
if (m_items[add->info.id].count(add->info.version.getMajor())) { if (m_items[add->info.id()].count(add->info.version().getMajor())) {
log::warn( log::warn(
"Item {}@{} has already been added, skipping", "Item {}@{} has already been added, skipping",
add->info.id, add->info.version add->info.id(), add->info.version()
); );
continue; continue;
} }
// add new major version of this item // add new major version of this item
m_items[add->info.id].insert({ m_items[add->info.id()].insert({
add->info.version.getMajor(), add->info.version().getMajor(),
add add
}); });
} }
@ -486,7 +486,7 @@ std::vector<IndexItemHandle> Index::getItemsByDeveloper(
std::vector<IndexItemHandle> res; std::vector<IndexItemHandle> res;
for (auto& items : map::values(m_items)) { for (auto& items : map::values(m_items)) {
if (items.size()) { if (items.size()) {
if (items.rbegin()->second->info.developer == name) { if (items.rbegin()->second->info.developer() == name) {
res.push_back(items.rbegin()->second); res.push_back(items.rbegin()->second);
} }
} }
@ -510,7 +510,7 @@ IndexItemHandle Index::getItem(
if (version) { if (version) {
// prefer most major version // prefer most major version
for (auto& [_, item] : ranges::reverse(m_items.at(id))) { for (auto& [_, item] : ranges::reverse(m_items.at(id))) {
if (version.value() == item->info.version) { if (version.value() == item->info.version()) {
return item; return item;
} }
} }
@ -530,7 +530,7 @@ IndexItemHandle Index::getItem(
if (m_items.count(id)) { if (m_items.count(id)) {
// prefer most major version // prefer most major version
for (auto& [_, item] : ranges::reverse(m_items.at(id))) { for (auto& [_, item] : ranges::reverse(m_items.at(id))) {
if (version.compare(item->info.version)) { if (version.compare(item->info.version())) {
return item; return item;
} }
} }
@ -539,7 +539,7 @@ IndexItemHandle Index::getItem(
} }
IndexItemHandle Index::getItem(ModInfo const& info) const { IndexItemHandle Index::getItem(ModInfo const& info) const {
return this->getItem(info.id, info.version); return this->getItem(info.id(), info.version());
} }
IndexItemHandle Index::getItem(Mod* mod) const { IndexItemHandle Index::getItem(Mod* mod) const {
@ -547,17 +547,17 @@ IndexItemHandle Index::getItem(Mod* mod) const {
} }
bool Index::isUpdateAvailable(IndexItemHandle item) const { bool Index::isUpdateAvailable(IndexItemHandle item) const {
auto installed = Loader::get()->getInstalledMod(item->info.id); auto installed = Loader::get()->getInstalledMod(item->info.id());
if (!installed) { if (!installed) {
return false; return false;
} }
return item->info.version > installed->getVersion(); return item->info.version() > installed->getVersion();
} }
bool Index::areUpdatesAvailable() const { bool Index::areUpdatesAvailable() const {
for (auto& mod : Loader::get()->getAllMods()) { for (auto& mod : Loader::get()->getAllMods()) {
auto item = this->getItem(mod); auto item = this->getItem(mod);
if (item && item->info.version > mod->getVersion()) { if (item && item->info.version() > mod->getVersion()) {
return true; return true;
} }
} }
@ -573,7 +573,7 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
IndexInstallList list; IndexInstallList list;
list.target = item; list.target = item;
for (auto& dep : item->info.dependencies) { for (auto& dep : item->info.dependencies()) {
if (!dep.isResolved()) { if (!dep.isResolved()) {
// check if this dep is available in the index // check if this dep is available in the index
if (auto depItem = this->getItem(dep.id, dep.version)) { if (auto depItem = this->getItem(dep.id, dep.version)) {
@ -595,7 +595,7 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
"reason is that the version of the dependency this mod " "reason is that the version of the dependency this mod "
"depends on is not available. Please let the the developer " "depends on is not available. Please let the the developer "
"({}) of the mod know!", "({}) of the mod know!",
dep.id, dep.version.toString(), item->info.developer dep.id, dep.version.toString(), item->info.developer()
); );
} }
} }
@ -610,7 +610,7 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
void Index::installNext(size_t index, IndexInstallList const& list) { void Index::installNext(size_t index, IndexInstallList const& list) {
auto postError = [this, list](std::string const& error) { auto postError = [this, list](std::string const& error) {
m_runningInstallations.erase(list.target); m_runningInstallations.erase(list.target);
ModInstallEvent(list.target->info.id, error).post(); ModInstallEvent(list.target->info.id(), error).post();
}; };
// If we're at the end of the list, move the downloaded items to mods // If we're at the end of the list, move the downloaded items to mods
@ -619,12 +619,12 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
// Move all downloaded files // Move all downloaded files
for (auto& item : list.list) { for (auto& item : list.list) {
// If the mod is already installed, delete the old .geode file // If the mod is already installed, delete the old .geode file
if (auto mod = Loader::get()->getInstalledMod(item->info.id)) { if (auto mod = Loader::get()->getInstalledMod(item->info.id())) {
auto res = mod->uninstall(); auto res = mod->uninstall();
if (!res) { if (!res) {
return postError(fmt::format( return postError(fmt::format(
"Unable to uninstall old version of {}: {}", "Unable to uninstall old version of {}: {}",
item->info.id, res.unwrapErr() item->info.id(), res.unwrapErr()
)); ));
} }
} }
@ -632,13 +632,13 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
// Move the temp file // Move the temp file
try { try {
ghc::filesystem::rename( ghc::filesystem::rename(
dirs::getTempDir() / (item->info.id + ".index"), dirs::getTempDir() / (item->info.id() + ".index"),
dirs::getModsDir() / (item->info.id + ".geode") dirs::getModsDir() / (item->info.id() + ".geode")
); );
} catch(std::exception& e) { } catch(std::exception& e) {
return postError(fmt::format( return postError(fmt::format(
"Unable to install {}: {}", "Unable to install {}: {}",
item->info.id, e.what() item->info.id(), e.what()
)); ));
} }
} }
@ -646,7 +646,7 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
// load mods // load mods
Loader::get()->refreshModsList(); Loader::get()->refreshModsList();
ModInstallEvent(list.target->info.id, UpdateFinished()).post(); ModInstallEvent(list.target->info.id(), UpdateFinished()).post();
return; return;
} }
@ -657,9 +657,9 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
}; };
auto item = list.list.at(index); auto item = list.list.at(index);
auto tempFile = dirs::getTempDir() / (item->info.id + ".index"); auto tempFile = dirs::getTempDir() / (item->info.id() + ".index");
m_runningInstallations[list.target] = web::AsyncWebRequest() m_runningInstallations[list.target] = web::AsyncWebRequest()
.join("install_item_" + item->info.id) .join("install_item_" + item->info.id())
.fetch(item->download.url) .fetch(item->download.url)
.into(tempFile) .into(tempFile)
.then([=](auto) { .then([=](auto) {
@ -669,16 +669,16 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
return postError(fmt::format( return postError(fmt::format(
"Binary file download for {} returned \"404 Not found\". " "Binary file download for {} returned \"404 Not found\". "
"Report this to the Geode development team.", "Report this to the Geode development team.",
item->info.id item->info.id()
)); ));
} }
// Verify checksum // Verify checksum
ModInstallEvent( ModInstallEvent(
list.target->info.id, list.target->info.id(),
UpdateProgress( UpdateProgress(
scaledProgress(100), scaledProgress(100),
fmt::format("Verifying {}", item->info.id) fmt::format("Verifying {}", item->info.id())
) )
).post(); ).post();
@ -687,7 +687,7 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
"Checksum mismatch with {}! (Downloaded file did not match what " "Checksum mismatch with {}! (Downloaded file did not match what "
"was expected. Try again, and if the download fails another time, " "was expected. Try again, and if the download fails another time, "
"report this to the Geode development team.)", "report this to the Geode development team.)",
item->info.id item->info.id()
)); ));
} }
@ -697,15 +697,15 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
.expect([postError, list, item](std::string const& err) { .expect([postError, list, item](std::string const& err) {
postError(fmt::format( postError(fmt::format(
"Unable to download {}: {}", "Unable to download {}: {}",
item->info.id, err item->info.id(), err
)); ));
}) })
.progress([this, item, list, scaledProgress](auto&, double now, double total) { .progress([this, item, list, scaledProgress](auto&, double now, double total) {
ModInstallEvent( ModInstallEvent(
list.target->info.id, list.target->info.id(),
UpdateProgress( UpdateProgress(
scaledProgress(now / total * 100.0), scaledProgress(now / total * 100.0),
fmt::format("Downloading {}", item->info.id) fmt::format("Downloading {}", item->info.id())
) )
).post(); ).post();
}) })
@ -740,7 +740,7 @@ void Index::install(IndexItemHandle item) {
this->install(list.unwrap()); this->install(list.unwrap());
} else { } else {
ModInstallEvent( ModInstallEvent(
item->info.id, item->info.id(),
UpdateFailed(list.unwrapErr()) UpdateFailed(list.unwrapErr())
).post(); ).post();
} }

View file

@ -187,8 +187,8 @@ Result<> Loader::Impl::loadData() {
// Mod loading // Mod loading
Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) { Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
if (m_mods.count(info.id)) { if (m_mods.count(info.id())) {
return Err(fmt::format("Mod with ID '{}' already loaded", info.id)); return Err(fmt::format("Mod with ID '{}' already loaded", info.id()));
} }
// create Mod instance // create Mod instance
@ -197,13 +197,13 @@ Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
if (!setupRes) { if (!setupRes) {
return Err(fmt::format( return Err(fmt::format(
"Unable to setup mod '{}': {}", "Unable to setup mod '{}': {}",
info.id, setupRes.unwrapErr() info.id(), setupRes.unwrapErr()
)); ));
} }
m_mods.insert({ info.id, mod }); m_mods.insert({ info.id(), mod });
mod->m_impl->m_enabled = Mod::get()->getSavedValue<bool>( mod->m_impl->m_enabled = Mod::get()->getSavedValue<bool>(
"should-load-" + info.id, true "should-load-" + info.id(), true
); );
// add mod resources // add mod resources
@ -258,7 +258,7 @@ Mod* Loader::Impl::getLoadedMod(std::string const& id) const {
} }
void Loader::Impl::updateModResources(Mod* mod) { void Loader::Impl::updateModResources(Mod* mod) {
if (!mod->m_impl->m_info.spritesheets.size()) { if (!mod->m_impl->m_info.spritesheets().size()) {
return; return;
} }
@ -267,7 +267,7 @@ void Loader::Impl::updateModResources(Mod* mod) {
log::debug("Adding resources for {}", mod->getID()); log::debug("Adding resources for {}", mod->getID());
// add spritesheets // add spritesheets
for (auto const& sheet : mod->m_impl->m_info.spritesheets) { for (auto const& sheet : mod->m_impl->m_info.spritesheets()) {
log::debug("Adding sheet {}", sheet); log::debug("Adding sheet {}", sheet);
auto png = sheet + ".png"; auto png = sheet + ".png";
auto plist = sheet + ".plist"; auto plist = sheet + ".plist";
@ -277,7 +277,7 @@ void Loader::Impl::updateModResources(Mod* mod) {
plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) { plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) {
log::warn( log::warn(
"The resource dir of \"{}\" is missing \"{}\" png and/or plist files", "The resource dir of \"{}\" is missing \"{}\" png and/or plist files",
mod->m_impl->m_info.id, sheet mod->m_impl->m_info.id(), sheet
); );
} }
else { else {
@ -312,7 +312,7 @@ void Loader::Impl::loadModsFromDirectory(
} }
// skip this entry if it's already loaded // skip this entry if it's already loaded
if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool { if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool {
return p->m_impl->m_info.path == entry.path(); return p->m_impl->m_info.path() == entry.path();
})) { })) {
continue; continue;
} }
@ -359,10 +359,10 @@ void Loader::Impl::refreshModsList() {
// load early-load mods first // load early-load mods first
for (auto& mod : m_modsToLoad) { for (auto& mod : m_modsToLoad) {
if (mod.needsEarlyLoad) { if (mod.needsEarlyLoad()) {
auto load = this->loadModFromInfo(mod); auto load = this->loadModFromInfo(mod);
if (!load) { if (!load) {
log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); log::error("Unable to load {}: {}", mod.id(), load.unwrapErr());
} }
} }
} }
@ -373,10 +373,10 @@ void Loader::Impl::refreshModsList() {
// load the rest of the mods // load the rest of the mods
for (auto& mod : m_modsToLoad) { for (auto& mod : m_modsToLoad) {
if (!mod.needsEarlyLoad) { if (!mod.needsEarlyLoad()) {
auto load = this->loadModFromInfo(mod); auto load = this->loadModFromInfo(mod);
if (!load) { if (!load) {
log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); log::error("Unable to load {}: {}", mod.id(), load.unwrapErr());
} }
} }
} }

View file

@ -28,13 +28,13 @@ Mod::Impl::~Impl() {
} }
Result<> Mod::Impl::setup() { Result<> Mod::Impl::setup() {
m_saveDirPath = dirs::getModsSaveDir() / m_info.id; m_saveDirPath = dirs::getModsSaveDir() / m_info.id();
ghc::filesystem::create_directories(m_saveDirPath); ghc::filesystem::create_directories(m_saveDirPath);
this->setupSettings(); this->setupSettings();
auto loadRes = this->loadData(); auto loadRes = this->loadData();
if (!loadRes) { if (!loadRes) {
log::warn("Unable to load data for \"{}\": {}", m_info.id, loadRes.unwrapErr()); log::warn("Unable to load data for \"{}\": {}", m_info.id(), loadRes.unwrapErr());
} }
return Ok(); return Ok();
} }
@ -46,23 +46,23 @@ ghc::filesystem::path Mod::Impl::getSaveDir() const {
} }
std::string Mod::Impl::getID() const { std::string Mod::Impl::getID() const {
return m_info.id; return m_info.id();
} }
std::string Mod::Impl::getName() const { std::string Mod::Impl::getName() const {
return m_info.name; return m_info.name();
} }
std::string Mod::Impl::getDeveloper() const { std::string Mod::Impl::getDeveloper() const {
return m_info.developer; return m_info.developer();
} }
std::optional<std::string> Mod::Impl::getDescription() const { std::optional<std::string> Mod::Impl::getDescription() const {
return m_info.description; return m_info.description();
} }
std::optional<std::string> Mod::Impl::getDetails() const { std::optional<std::string> Mod::Impl::getDetails() const {
return m_info.details; return m_info.details();
} }
ModInfo Mod::Impl::getModInfo() const { ModInfo Mod::Impl::getModInfo() const {
@ -74,15 +74,15 @@ ghc::filesystem::path Mod::Impl::getTempDir() const {
} }
ghc::filesystem::path Mod::Impl::getBinaryPath() const { ghc::filesystem::path Mod::Impl::getBinaryPath() const {
return m_tempDirName / m_info.binaryName; return m_tempDirName / m_info.binaryName();
} }
ghc::filesystem::path Mod::Impl::getPackagePath() const { ghc::filesystem::path Mod::Impl::getPackagePath() const {
return m_info.path; return m_info.path();
} }
VersionInfo Mod::Impl::getVersion() const { VersionInfo Mod::Impl::getVersion() const {
return m_info.version; return m_info.version();
} }
json::Value& Mod::Impl::getSaveContainer() { json::Value& Mod::Impl::getSaveContainer() {
@ -98,11 +98,11 @@ bool Mod::Impl::isLoaded() const {
} }
bool Mod::Impl::supportsDisabling() const { bool Mod::Impl::supportsDisabling() const {
return m_info.supportsDisabling; return m_info.supportsDisabling();
} }
bool Mod::Impl::supportsUnloading() const { bool Mod::Impl::supportsUnloading() const {
return m_info.supportsUnloading; return m_info.supportsUnloading();
} }
bool Mod::Impl::wasSuccesfullyLoaded() const { bool Mod::Impl::wasSuccesfullyLoaded() const {
@ -143,7 +143,7 @@ Result<> Mod::Impl::loadData() {
Severity::Error, Severity::Error,
m_self, m_self,
"{}: Unable to load value for setting \"{}\"", "{}: Unable to load value for setting \"{}\"",
m_info.id, m_info.id(),
key key
); );
} }
@ -229,7 +229,7 @@ Result<> Mod::Impl::saveData() {
} }
void Mod::Impl::setupSettings() { void Mod::Impl::setupSettings() {
for (auto& [key, sett] : m_info.settings) { for (auto& [key, sett] : m_info.settings()) {
if (auto value = sett.createDefaultValue()) { if (auto value = sett.createDefaultValue()) {
m_settings.emplace(key, std::move(value)); m_settings.emplace(key, std::move(value));
} }
@ -247,19 +247,19 @@ void Mod::Impl::registerCustomSetting(std::string const& key, std::unique_ptr<Se
} }
bool Mod::Impl::hasSettings() const { bool Mod::Impl::hasSettings() const {
return m_info.settings.size(); return m_info.settings().size();
} }
std::vector<std::string> Mod::Impl::getSettingKeys() const { std::vector<std::string> Mod::Impl::getSettingKeys() const {
std::vector<std::string> keys; std::vector<std::string> keys;
for (auto& [key, _] : m_info.settings) { for (auto& [key, _] : m_info.settings()) {
keys.push_back(key); keys.push_back(key);
} }
return keys; return keys;
} }
std::optional<Setting> Mod::Impl::getSettingDefinition(std::string const& key) const { std::optional<Setting> Mod::Impl::getSettingDefinition(std::string const& key) const {
for (auto& setting : m_info.settings) { for (auto& setting : m_info.settings()) {
if (setting.first == key) { if (setting.first == key) {
return setting.second; return setting.second;
} }
@ -275,7 +275,7 @@ SettingValue* Mod::Impl::getSetting(std::string const& key) const {
} }
bool Mod::Impl::hasSetting(std::string const& key) const { bool Mod::Impl::hasSetting(std::string const& key) const {
for (auto& setting : m_info.settings) { for (auto& setting : m_info.settings()) {
if (setting.first == key) { if (setting.first == key) {
return true; return true;
} }
@ -322,7 +322,7 @@ Result<> Mod::Impl::unloadBinary() {
return Ok(); return Ok();
} }
if (!m_info.supportsUnloading) { if (!m_info.supportsUnloading()) {
return Err("Mod does not support unloading"); return Err("Mod does not support unloading");
} }
@ -375,7 +375,7 @@ Result<> Mod::Impl::disable() {
if (!m_enabled) { if (!m_enabled) {
return Ok(); return Ok();
} }
if (!m_info.supportsDisabling) { if (!m_info.supportsDisabling()) {
return Err("Mod does not support disabling"); return Err("Mod does not support disabling");
} }
@ -396,15 +396,15 @@ Result<> Mod::Impl::disable() {
} }
Result<> Mod::Impl::uninstall() { Result<> Mod::Impl::uninstall() {
if (m_info.supportsDisabling) { if (m_info.supportsDisabling()) {
GEODE_UNWRAP(this->disable()); GEODE_UNWRAP(this->disable());
if (m_info.supportsUnloading) { if (m_info.supportsUnloading()) {
GEODE_UNWRAP(this->unloadBinary()); GEODE_UNWRAP(this->unloadBinary());
} }
} }
try { try {
ghc::filesystem::remove(m_info.path); ghc::filesystem::remove(m_info.path());
} }
catch (std::exception& e) { catch (std::exception& e) {
return Err( return Err(
@ -418,14 +418,14 @@ Result<> Mod::Impl::uninstall() {
} }
bool Mod::Impl::isUninstalled() const { bool Mod::Impl::isUninstalled() const {
return m_self != Mod::get() && !ghc::filesystem::exists(m_info.path); return m_self != Mod::get() && !ghc::filesystem::exists(m_info.path());
} }
// Dependencies // Dependencies
Result<> Mod::Impl::updateDependencies() { Result<> Mod::Impl::updateDependencies() {
bool hasUnresolved = false; bool hasUnresolved = false;
for (auto& dep : m_info.dependencies) { for (auto& dep : m_info.dependencies()) {
// set the dependency's loaded mod if such exists // set the dependency's loaded mod if such exists
if (!dep.mod) { if (!dep.mod) {
dep.mod = Loader::get()->getLoadedMod(dep.id); dep.mod = Loader::get()->getLoadedMod(dep.id);
@ -455,20 +455,20 @@ Result<> Mod::Impl::updateDependencies() {
} }
// load if there weren't any unresolved dependencies // load if there weren't any unresolved dependencies
if (!hasUnresolved) { if (!hasUnresolved) {
log::debug("All dependencies for {} found", m_info.id); log::debug("All dependencies for {} found", m_info.id());
if (m_enabled) { if (m_enabled) {
log::debug("Resolved & loading {}", m_info.id); log::debug("Resolved & loading {}", m_info.id());
GEODE_UNWRAP(this->loadBinary()); GEODE_UNWRAP(this->loadBinary());
} }
else { else {
log::debug("Resolved {}, however not loading it as it is disabled", m_info.id); log::debug("Resolved {}, however not loading it as it is disabled", m_info.id());
} }
} }
return Ok(); return Ok();
} }
bool Mod::Impl::hasUnresolvedDependencies() const { bool Mod::Impl::hasUnresolvedDependencies() const {
for (auto const& dep : m_info.dependencies) { for (auto const& dep : m_info.dependencies()) {
if (!dep.isResolved()) { if (!dep.isResolved()) {
return true; return true;
} }
@ -478,7 +478,7 @@ bool Mod::Impl::hasUnresolvedDependencies() const {
std::vector<Dependency> Mod::Impl::getUnresolvedDependencies() { std::vector<Dependency> Mod::Impl::getUnresolvedDependencies() {
std::vector<Dependency> unresolved; std::vector<Dependency> unresolved;
for (auto const& dep : m_info.dependencies) { for (auto const& dep : m_info.dependencies()) {
if (!dep.isResolved()) { if (!dep.isResolved()) {
unresolved.push_back(dep); unresolved.push_back(dep);
} }
@ -487,7 +487,7 @@ std::vector<Dependency> Mod::Impl::getUnresolvedDependencies() {
} }
bool Mod::Impl::depends(std::string const& id) const { bool Mod::Impl::depends(std::string const& id) const {
return utils::ranges::contains(m_info.dependencies, [id](Dependency const& t) { return utils::ranges::contains(m_info.dependencies(), [id](Dependency const& t) {
return t.id == id; return t.id == id;
}); });
} }
@ -498,7 +498,7 @@ Result<> Mod::Impl::enableHook(Hook* hook) {
auto res = hook->enable(); auto res = hook->enable();
if (res) m_hooks.push_back(hook); if (res) m_hooks.push_back(hook);
else { else {
log::error("Can't enable hook {} for mod {}: {}", m_info.id, res.unwrapErr()); log::error("Can't enable hook {} for mod {}: {}", m_info.id(), res.unwrapErr());
} }
return res; return res;
@ -583,16 +583,16 @@ Result<> Mod::Impl::createTempDir() {
} }
// Create geode/temp/mod.id // Create geode/temp/mod.id
auto tempPath = tempDir / m_info.id; auto tempPath = tempDir / m_info.id();
if (!file::createDirectoryAll(tempPath)) { if (!file::createDirectoryAll(tempPath)) {
return Err("Unable to create mod runtime directory"); return Err("Unable to create mod runtime directory");
} }
// Unzip .geode file into temp dir // Unzip .geode file into temp dir
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_info.path)); GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_info.path()));
if (!unzip.hasEntry(m_info.binaryName)) { if (!unzip.hasEntry(m_info.binaryName())) {
return Err( return Err(
fmt::format("Unable to find platform binary under the name \"{}\"", m_info.binaryName) fmt::format("Unable to find platform binary under the name \"{}\"", m_info.binaryName())
); );
} }
GEODE_UNWRAP(unzip.extractAllTo(tempPath)); GEODE_UNWRAP(unzip.extractAllTo(tempPath));
@ -604,7 +604,7 @@ Result<> Mod::Impl::createTempDir() {
} }
ghc::filesystem::path Mod::Impl::getConfigDir(bool create) const { ghc::filesystem::path Mod::Impl::getConfigDir(bool create) const {
auto dir = dirs::getModConfigDir() / m_info.id; auto dir = dirs::getModConfigDir() / m_info.id();
if (create) { if (create) {
(void)file::createDirectoryAll(dir); (void)file::createDirectoryAll(dir);
} }
@ -615,8 +615,8 @@ char const* Mod::Impl::expandSpriteName(char const* name) {
static std::unordered_map<std::string, char const*> expanded = {}; static std::unordered_map<std::string, char const*> expanded = {};
if (expanded.count(name)) return expanded[name]; if (expanded.count(name)) return expanded[name];
auto exp = new char[strlen(name) + 2 + m_info.id.size()]; auto exp = new char[strlen(name) + 2 + m_info.id().size()];
auto exps = m_info.id + "/" + name; auto exps = m_info.id() + "/" + name;
memcpy(exp, exps.c_str(), exps.size() + 1); memcpy(exp, exps.c_str(), exps.size() + 1);
expanded[name] = exp; expanded[name] = exp;
@ -679,9 +679,9 @@ static ModInfo getModImplInfo() {
return ModInfo(); return ModInfo();
} }
auto info = infoRes.unwrap(); auto info = infoRes.unwrap();
info.details = LOADER_ABOUT_MD; info.details() = LOADER_ABOUT_MD;
info.supportInfo = SUPPORT_INFO; info.supportInfo() = SUPPORT_INFO;
info.supportsDisabling = false; info.supportsDisabling() = false;
return info; return info;
} }

View file

@ -1,24 +1,18 @@
#include <Geode/loader/Loader.hpp> #include <Geode/loader/Loader.hpp>
#include <Geode/loader/Mod.hpp> #include <Geode/loader/Mod.hpp>
#include <Geode/utils/file.hpp>
#include <Geode/utils/string.hpp>
#include <json.hpp>
#include <Geode/utils/JsonValidation.hpp> #include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/VersionInfo.hpp> #include <Geode/utils/VersionInfo.hpp>
#include <Geode/utils/file.hpp>
#include <Geode/utils/string.hpp>
#include <about.hpp> #include <about.hpp>
#include <json.hpp>
USE_GEODE_NAMESPACE(); USE_GEODE_NAMESPACE();
bool Dependency::isResolved() const { bool Dependency::isResolved() const {
return return !this->required ||
!this->required || (this->mod && this->mod->isLoaded() && this->mod->isEnabled() &&
( this->version.compare(this->mod->getVersion()));
this->mod &&
this->mod->isLoaded() &&
this->mod->isEnabled() &&
this->version.compare(this->mod->getVersion())
);
} }
static std::string sanitizeDetailsData(std::string const& str) { static std::string sanitizeDetailsData(std::string const& str) {
@ -26,7 +20,50 @@ static std::string sanitizeDetailsData(std::string const& str) {
return utils::string::replace(str, "\r", ""); return utils::string::replace(str, "\r", "");
} }
bool ModInfo::validateID(std::string const& id) { class ModInfo::Impl {
public:
ghc::filesystem::path m_path;
std::string m_binaryName;
VersionInfo m_version{1, 0, 0};
std::string m_id;
std::string m_name;
std::string m_developer;
std::optional<std::string> m_description;
std::optional<std::string> m_details;
std::optional<std::string> m_changelog;
std::optional<std::string> m_supportInfo;
std::optional<std::string> m_repository;
std::optional<IssuesInfo> m_issues;
std::vector<Dependency> m_dependencies;
std::vector<std::string> m_spritesheets;
std::vector<std::pair<std::string, Setting>> m_settings;
bool m_supportsDisabling = true;
bool m_supportsUnloading = false;
bool m_needsEarlyLoad = false;
std::shared_ptr<ModJson> m_rawJSON;
static Result<ModInfo> createFromGeodeZip(utils::file::Unzip& zip);
static Result<ModInfo> createFromGeodeFile(ghc::filesystem::path const& path);
static Result<ModInfo> createFromFile(ghc::filesystem::path const& path);
static Result<ModInfo> create(ModJson const& json);
ModJson toJSON() const;
ModJson getRawJSON() const;
bool operator==(ModInfo::Impl const& other) const;
static bool validateID(std::string const& id);
static Result<ModInfo> createFromSchemaV010(ModJson const& json);
Result<> addSpecialFiles(ghc::filesystem::path const& dir);
Result<> addSpecialFiles(utils::file::Unzip& zip);
std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
};
bool ModInfo::Impl::validateID(std::string const& id) {
// ids may not be empty // ids may not be empty
if (!id.size()) return false; if (!id.size()) return false;
for (auto const& c : id) { for (auto const& c : id) {
@ -37,47 +74,49 @@ bool ModInfo::validateID(std::string const& id) {
return true; return true;
} }
Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) { Result<ModInfo> ModInfo::Impl::createFromSchemaV010(ModJson const& rawJson) {
ModInfo info; ModInfo info;
info.m_rawJSON = std::make_unique<ModJson>(rawJson); auto impl = info.m_impl.get();
JsonChecker checker(*info.m_rawJSON); impl->m_rawJSON = std::make_unique<ModJson>(rawJson);
JsonChecker checker(*impl->m_rawJSON);
auto root = checker.root("[mod.json]").obj(); auto root = checker.root("[mod.json]").obj();
root.addKnownKey("geode"); root.addKnownKey("geode");
root.addKnownKey("binary"); root.addKnownKey("binary");
root.needs("id").validate(&ModInfo::validateID).into(info.id); root.needs("id").validate(&ModInfo::validateID).into(impl->m_id);
root.needs("version").into(info.version); root.needs("version").into(impl->m_version);
root.needs("name").into(info.name); root.needs("name").into(impl->m_name);
root.needs("developer").into(info.developer); root.needs("developer").into(impl->m_developer);
root.has("description").into(info.description); root.has("description").into(impl->m_description);
root.has("repository").into(info.repository); root.has("repository").into(impl->m_repository);
root.has("toggleable").into(info.supportsDisabling); root.has("toggleable").into(impl->m_supportsDisabling);
root.has("unloadable").into(info.supportsUnloading); root.has("unloadable").into(impl->m_supportsUnloading);
root.has("early-load").into(info.needsEarlyLoad); root.has("early-load").into(impl->m_needsEarlyLoad);
for (auto& dep : root.has("dependencies").iterate()) { for (auto& dep : root.has("dependencies").iterate()) {
auto obj = dep.obj(); auto obj = dep.obj();
auto depobj = Dependency {}; auto depobj = Dependency{};
obj.needs("id").validate(&ModInfo::validateID).into(depobj.id); obj.needs("id").validate(&ModInfo::validateID).into(depobj.id);
obj.needs("version").into(depobj.version); obj.needs("version").into(depobj.version);
obj.has("required").into(depobj.required); obj.has("required").into(depobj.required);
obj.checkUnknownKeys(); obj.checkUnknownKeys();
info.dependencies.push_back(depobj); impl->m_dependencies.push_back(depobj);
} }
for (auto& [key, value] : root.has("settings").items()) { for (auto& [key, value] : root.has("settings").items()) {
GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, value)); GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, value));
info.settings.push_back({ key, sett }); impl->m_settings.push_back({key, sett});
} }
if (auto resources = root.has("resources").obj()) { if (auto resources = root.has("resources").obj()) {
for (auto& [key, _] : resources.has("spritesheets").items()) { for (auto& [key, _] : resources.has("spritesheets").items()) {
info.spritesheets.push_back(info.id + "/" + key); impl->m_spritesheets.push_back(impl->m_id + "/" + key);
} }
} }
@ -85,11 +124,11 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
IssuesInfo issuesInfo; IssuesInfo issuesInfo;
issues.needs("info").into(issuesInfo.info); issues.needs("info").into(issuesInfo.info);
issues.has("url").intoAs<std::string>(issuesInfo.url); issues.has("url").intoAs<std::string>(issuesInfo.url);
info.issues = issuesInfo; impl->m_issues = issuesInfo;
} }
// with new cli, binary name is always mod id // with new cli, binary name is always mod id
info.binaryName = info.id + GEODE_PLATFORM_EXTENSION; impl->m_binaryName = impl->m_id + GEODE_PLATFORM_EXTENSION;
// removed keys // removed keys
if (root.has("datastore")) { if (root.has("datastore")) {
@ -110,12 +149,13 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
return Ok(info); return Ok(info);
} }
Result<ModInfo> ModInfo::create(ModJson const& json) { Result<ModInfo> ModInfo::Impl::create(ModJson const& json) {
// Check mod.json target version // Check mod.json target version
auto schema = LOADER_VERSION; auto schema = LOADER_VERSION;
if (json.contains("geode") && json["geode"].is_string()) { if (json.contains("geode") && json["geode"].is_string()) {
GEODE_UNWRAP_INTO( GEODE_UNWRAP_INTO(
schema, VersionInfo::parse(json["geode"].as_string()) schema,
VersionInfo::parse(json["geode"].as_string())
.expect("[mod.json] has invalid target loader version: {error}") .expect("[mod.json] has invalid target loader version: {error}")
); );
} }
@ -159,13 +199,15 @@ Result<ModInfo> ModInfo::create(ModJson const& json) {
); );
} }
Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) { Result<ModInfo> ModInfo::Impl::createFromFile(ghc::filesystem::path const& path) {
GEODE_UNWRAP_INTO(auto read, utils::file::readString(path)); GEODE_UNWRAP_INTO(auto read, utils::file::readString(path));
try { try {
GEODE_UNWRAP_INTO(auto info, ModInfo::create(json::parse(read))); GEODE_UNWRAP_INTO(auto info, ModInfo::create(json::parse(read)));
info.path = path; auto impl = info.m_impl.get();
impl->m_path = path;
if (path.has_parent_path()) { if (path.has_parent_path()) {
GEODE_UNWRAP(info.addSpecialFiles(path.parent_path())); GEODE_UNWRAP(info.addSpecialFiles(path.parent_path()));
} }
@ -176,12 +218,12 @@ Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) {
} }
} }
Result<ModInfo> ModInfo::createFromGeodeFile(ghc::filesystem::path const& path) { Result<ModInfo> ModInfo::Impl::createFromGeodeFile(ghc::filesystem::path const& path) {
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(path)); GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(path));
return ModInfo::createFromGeodeZip(unzip); return ModInfo::createFromGeodeZip(unzip);
} }
Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) { Result<ModInfo> ModInfo::Impl::createFromGeodeZip(file::Unzip& unzip) {
// Check if mod.json exists in zip // Check if mod.json exists in zip
if (!unzip.hasEntry("mod.json")) { if (!unzip.hasEntry("mod.json")) {
return Err("\"" + unzip.getPath().string() + "\" is missing mod.json"); return Err("\"" + unzip.getPath().string() + "\" is missing mod.json");
@ -206,16 +248,17 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) {
return Err("\"" + unzip.getPath().string() + "\" - " + res.unwrapErr()); return Err("\"" + unzip.getPath().string() + "\" - " + res.unwrapErr());
} }
auto info = res.unwrap(); auto info = res.unwrap();
info.path = unzip.getPath(); auto impl = info.m_impl.get();
impl->m_path = unzip.getPath();
GEODE_UNWRAP(info.addSpecialFiles(unzip).expect("Unable to add extra files: {error}")); GEODE_UNWRAP(info.addSpecialFiles(unzip).expect("Unable to add extra files: {error}"));
return Ok(info); return Ok(info);
} }
Result<> ModInfo::addSpecialFiles(file::Unzip& unzip) { Result<> ModInfo::Impl::addSpecialFiles(file::Unzip& unzip) {
// unzip known MD files // unzip known MD files
for (auto& [file, target] : getSpecialFiles()) { for (auto& [file, target] : this->getSpecialFiles()) {
if (unzip.hasEntry(file)) { if (unzip.hasEntry(file)) {
GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect("Unable to extract \"{}\"", file)); GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect("Unable to extract \"{}\"", file));
*target = sanitizeDetailsData(std::string(data.begin(), data.end())); *target = sanitizeDetailsData(std::string(data.begin(), data.end()));
@ -224,9 +267,9 @@ Result<> ModInfo::addSpecialFiles(file::Unzip& unzip) {
return Ok(); return Ok();
} }
Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) { Result<> ModInfo::Impl::addSpecialFiles(ghc::filesystem::path const& dir) {
// unzip known MD files // unzip known MD files
for (auto& [file, target] : getSpecialFiles()) { for (auto& [file, target] : this->getSpecialFiles()) {
if (ghc::filesystem::exists(dir / file)) { if (ghc::filesystem::exists(dir / file)) {
auto data = file::readString(dir / file); auto data = file::readString(dir / file);
if (!data) { if (!data) {
@ -238,25 +281,223 @@ Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) {
return Ok(); return Ok();
} }
std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::getSpecialFiles() { std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::Impl::getSpecialFiles() {
return { return {
{ "about.md", &this->details }, {"about.md", &this->m_details},
{ "changelog.md", &this->changelog }, {"changelog.md", &this->m_changelog},
{ "support.md", &this->supportInfo }, {"support.md", &this->m_supportInfo},
}; };
} }
ModJson ModInfo::toJSON() const { ModJson ModInfo::Impl::toJSON() const {
auto json = *m_rawJSON; auto json = *m_rawJSON;
json["path"] = this->path.string(); json["path"] = this->m_path.string();
json["binary"] = this->binaryName; json["binary"] = this->m_binaryName;
return json; return json;
} }
ModJson ModInfo::getRawJSON() const { ModJson ModInfo::Impl::getRawJSON() const {
return *m_rawJSON; return *m_rawJSON;
} }
bool ModInfo::operator==(ModInfo const& other) const { bool ModInfo::Impl::operator==(ModInfo::Impl const& other) const {
return this->id == other.id; return this->m_id == other.m_id;
} }
ghc::filesystem::path& ModInfo::path() {
return m_impl->m_path;
}
ghc::filesystem::path const& ModInfo::path() const {
return m_impl->m_path;
}
std::string& ModInfo::binaryName() {
return m_impl->m_binaryName;
}
std::string const& ModInfo::binaryName() const {
return m_impl->m_binaryName;
}
VersionInfo& ModInfo::version() {
return m_impl->m_version;
}
VersionInfo const& ModInfo::version() const {
return m_impl->m_version;
}
std::string& ModInfo::id() {
return m_impl->m_id;
}
std::string const& ModInfo::id() const {
return m_impl->m_id;
}
std::string& ModInfo::name() {
return m_impl->m_name;
}
std::string const& ModInfo::name() const {
return m_impl->m_name;
}
std::string& ModInfo::developer() {
return m_impl->m_developer;
}
std::string const& ModInfo::developer() const {
return m_impl->m_developer;
}
std::optional<std::string>& ModInfo::description() {
return m_impl->m_description;
}
std::optional<std::string> const& ModInfo::description() const {
return m_impl->m_description;
}
std::optional<std::string>& ModInfo::details() {
return m_impl->m_details;
}
std::optional<std::string> const& ModInfo::details() const {
return m_impl->m_details;
}
std::optional<std::string>& ModInfo::changelog() {
return m_impl->m_changelog;
}
std::optional<std::string> const& ModInfo::changelog() const {
return m_impl->m_changelog;
}
std::optional<std::string>& ModInfo::supportInfo() {
return m_impl->m_supportInfo;
}
std::optional<std::string> const& ModInfo::supportInfo() const {
return m_impl->m_supportInfo;
}
std::optional<std::string>& ModInfo::repository() {
return m_impl->m_repository;
}
std::optional<std::string> const& ModInfo::repository() const {
return m_impl->m_repository;
}
std::optional<IssuesInfo>& ModInfo::issues() {
return m_impl->m_issues;
}
std::optional<IssuesInfo> const& ModInfo::issues() const {
return m_impl->m_issues;
}
std::vector<Dependency>& ModInfo::dependencies() {
return m_impl->m_dependencies;
}
std::vector<Dependency> const& ModInfo::dependencies() const {
return m_impl->m_dependencies;
}
std::vector<std::string>& ModInfo::spritesheets() {
return m_impl->m_spritesheets;
}
std::vector<std::string> const& ModInfo::spritesheets() const {
return m_impl->m_spritesheets;
}
std::vector<std::pair<std::string, Setting>>& ModInfo::settings() {
return m_impl->m_settings;
}
std::vector<std::pair<std::string, Setting>> const& ModInfo::settings() const {
return m_impl->m_settings;
}
bool& ModInfo::supportsDisabling() {
return m_impl->m_supportsDisabling;
}
bool const& ModInfo::supportsDisabling() const {
return m_impl->m_supportsDisabling;
}
bool& ModInfo::supportsUnloading() {
return m_impl->m_supportsUnloading;
}
bool const& ModInfo::supportsUnloading() const {
return m_impl->m_supportsUnloading;
}
bool& ModInfo::needsEarlyLoad() {
return m_impl->m_needsEarlyLoad;
}
bool const& ModInfo::needsEarlyLoad() const {
return m_impl->m_needsEarlyLoad;
}
Result<ModInfo> ModInfo::createFromGeodeZip(utils::file::Unzip& zip) {
return Impl::createFromGeodeZip(zip);
}
Result<ModInfo> ModInfo::createFromGeodeFile(ghc::filesystem::path const& path) {
return Impl::createFromGeodeFile(path);
}
Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) {
return Impl::createFromFile(path);
}
Result<ModInfo> ModInfo::create(ModJson const& json) {
return Impl::create(json);
}
ModJson ModInfo::toJSON() const {
return m_impl->toJSON();
}
ModJson ModInfo::getRawJSON() const {
return m_impl->getRawJSON();
}
bool ModInfo::operator==(ModInfo const& other) const {
return m_impl->operator==(*other.m_impl);
}
bool ModInfo::validateID(std::string const& id) {
return Impl::validateID(id);
}
ModJson& ModInfo::rawJSON() {
return *m_impl->m_rawJSON;
}
ModJson const& ModInfo::rawJSON() const {
return *m_impl->m_rawJSON;
}
Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& json) {
return Impl::createFromSchemaV010(json);
}
Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) {
return m_impl->addSpecialFiles(dir);
}
Result<> ModInfo::addSpecialFiles(utils::file::Unzip& zip) {
return m_impl->addSpecialFiles(zip);
}
std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::getSpecialFiles() {
return m_impl->getSpecialFiles();
}
ModInfo::ModInfo() : m_impl() {}
ModInfo::ModInfo(ModInfo const& other) : m_impl(std::make_unique<Impl>(*other.m_impl)) {}
ModInfo::ModInfo(ModInfo&& other) noexcept : m_impl(std::move(other.m_impl)) {}
ModInfo& ModInfo::operator=(ModInfo const& other) {
m_impl = std::make_unique<Impl>(*other.m_impl);
return *this;
}
ModInfo& ModInfo::operator=(ModInfo&& other) noexcept {
m_impl = std::move(other.m_impl);
return *this;
}
ModInfo::~ModInfo() {}

View file

@ -19,7 +19,7 @@ T findSymbolOrMangled(void* dylib, char const* name, char const* mangled) {
Result<> Mod::Impl::loadPlatformBinary() { Result<> Mod::Impl::loadPlatformBinary() {
auto dylib = auto dylib =
dlopen((m_tempDirName / m_info.binaryName).string().c_str(), RTLD_LAZY); dlopen((m_tempDirName / m_info.binaryName()).string().c_str(), RTLD_LAZY);
if (dylib) { if (dylib) {
if (m_platformInfo) { if (m_platformInfo) {
delete m_platformInfo; delete m_platformInfo;

View file

@ -19,7 +19,7 @@ T findSymbolOrMangled(void* dylib, char const* name, char const* mangled) {
Result<> Mod::Impl::loadPlatformBinary() { Result<> Mod::Impl::loadPlatformBinary() {
auto dylib = auto dylib =
dlopen((m_tempDirName / m_info.binaryName).string().c_str(), RTLD_LAZY); dlopen((m_tempDirName / m_info.binaryName()).string().c_str(), RTLD_LAZY);
if (dylib) { if (dylib) {
if (m_platformInfo) { if (m_platformInfo) {
delete m_platformInfo; delete m_platformInfo;

View file

@ -73,7 +73,7 @@ std::string getLastWinError() {
} }
Result<> Mod::Impl::loadPlatformBinary() { Result<> Mod::Impl::loadPlatformBinary() {
auto load = LoadLibraryW((m_tempDirName / m_info.binaryName).wstring().c_str()); auto load = LoadLibraryW((m_tempDirName / m_info.binaryName()).wstring().c_str());
if (load) { if (load) {
if (m_platformInfo) { if (m_platformInfo) {
delete m_platformInfo; delete m_platformInfo;

View file

@ -14,19 +14,19 @@ void geode::openModsList() {
} }
void geode::openIssueReportPopup(Mod* mod) { void geode::openIssueReportPopup(Mod* mod) {
if (mod->getModInfo().issues) { if (mod->getModInfo().issues()) {
MDPopup::create( MDPopup::create(
"Issue Report", "Issue Report",
mod->getModInfo().issues.value().info + mod->getModInfo().issues().value().info +
"\n\n" "\n\n"
"If your issue relates to a <cr>game crash</c>, <cb>please include</c> the " "If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
"latest crash log(s) from `" + "latest crash log(s) from `" +
dirs::getCrashlogsDir().string() + "`", dirs::getCrashlogsDir().string() + "`",
"OK", (mod->getModInfo().issues.value().url ? "Open URL" : ""), "OK", (mod->getModInfo().issues().value().url ? "Open URL" : ""),
[mod](bool btn2) { [mod](bool btn2) {
if (btn2) { if (btn2) {
web::openLinkInBrowser( web::openLinkInBrowser(
mod->getModInfo().issues.value().url.value() mod->getModInfo().issues().value().url.value()
); );
} }
} }

View file

@ -25,7 +25,7 @@ bool DevProfilePopup::setup(std::string const& developer) {
// index mods // index mods
for (auto& item : Index::get()->getItemsByDeveloper(developer)) { for (auto& item : Index::get()->getItemsByDeveloper(developer)) {
if (Loader::get()->isModInstalled(item->info.id)) { if (Loader::get()->isModInstalled(item->info.id())) {
continue; continue;
} }
items->addObject(IndexItemCell::create( items->addObject(IndexItemCell::create(

View file

@ -49,7 +49,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
constexpr float logoSize = 40.f; constexpr float logoSize = 40.f;
constexpr float logoOffset = 10.f; constexpr float logoOffset = 10.f;
auto nameLabel = CCLabelBMFont::create(info.name.c_str(), "bigFont.fnt"); auto nameLabel = CCLabelBMFont::create(info.name().c_str(), "bigFont.fnt");
nameLabel->setAnchorPoint({ .0f, .5f }); nameLabel->setAnchorPoint({ .0f, .5f });
nameLabel->limitLabelWidth(200.f, .7f, .1f); nameLabel->limitLabelWidth(200.f, .7f, .1f);
m_mainLayer->addChild(nameLabel, 2); m_mainLayer->addChild(nameLabel, 2);
@ -57,7 +57,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
auto logoSpr = this->createLogo({logoSize, logoSize}); auto logoSpr = this->createLogo({logoSize, logoSize});
m_mainLayer->addChild(logoSpr); m_mainLayer->addChild(logoSpr);
auto developerStr = "by " + info.developer; auto developerStr = "by " + info.developer();
auto developerLabel = CCLabelBMFont::create(developerStr.c_str(), "goldFont.fnt"); auto developerLabel = CCLabelBMFont::create(developerStr.c_str(), "goldFont.fnt");
developerLabel->setScale(.5f); developerLabel->setScale(.5f);
developerLabel->setAnchorPoint({.0f, .5f}); developerLabel->setAnchorPoint({.0f, .5f});
@ -78,7 +78,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
); );
auto versionLabel = CCLabelBMFont::create( auto versionLabel = CCLabelBMFont::create(
info.version.toString().c_str(), info.version().toString().c_str(),
"bigFont.fnt" "bigFont.fnt"
); );
versionLabel->setAnchorPoint({ .0f, .5f }); versionLabel->setAnchorPoint({ .0f, .5f });
@ -94,7 +94,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
this->registerWithTouchDispatcher(); this->registerWithTouchDispatcher();
m_detailsArea = MDTextArea::create( m_detailsArea = MDTextArea::create(
(info.details ? info.details.value() : "### No description provided."), (info.details() ? info.details().value() : "### No description provided."),
{ 350.f, 137.5f } { 350.f, 137.5f }
); );
m_detailsArea->setPosition( m_detailsArea->setPosition(
@ -111,8 +111,8 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
m_mainLayer->addChild(m_scrollbar); m_mainLayer->addChild(m_scrollbar);
// changelog // changelog
if (info.changelog) { if (info.changelog()) {
m_changelogArea = MDTextArea::create(info.changelog.value(), { 350.f, 137.5f }); m_changelogArea = MDTextArea::create(info.changelog().value(), { 350.f, 137.5f });
m_changelogArea->setPosition( m_changelogArea->setPosition(
-5000.f, winSize.height / 2 - m_changelogArea->getScaledContentSize().height / 2 - 20.f -5000.f, winSize.height / 2 - m_changelogArea->getScaledContentSize().height / 2 - 20.f
); );
@ -155,7 +155,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
m_buttonMenu->addChild(m_infoBtn); m_buttonMenu->addChild(m_infoBtn);
// repo button // repo button
if (info.repository) { if (info.repository()) {
auto repoBtn = CCMenuItemSpriteExtra::create( auto repoBtn = CCMenuItemSpriteExtra::create(
CCSprite::createWithSpriteFrameName("github.png"_spr), CCSprite::createWithSpriteFrameName("github.png"_spr),
this, this,
@ -166,7 +166,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
} }
// support button // support button
if (info.supportInfo) { if (info.supportInfo()) {
auto supportBtn = CCMenuItemSpriteExtra::create( auto supportBtn = CCMenuItemSpriteExtra::create(
CCSprite::createWithSpriteFrameName("gift.png"_spr), CCSprite::createWithSpriteFrameName("gift.png"_spr),
this, this,
@ -192,30 +192,30 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
void ModInfoPopup::onSupport(CCObject*) { void ModInfoPopup::onSupport(CCObject*) {
MDPopup::create( MDPopup::create(
"Support " + this->getModInfo().name, "Support " + this->getModInfo().name(),
this->getModInfo().supportInfo.value(), this->getModInfo().supportInfo().value(),
"OK" "OK"
)->show(); )->show();
} }
void ModInfoPopup::onRepository(CCObject*) { void ModInfoPopup::onRepository(CCObject*) {
web::openLinkInBrowser(this->getModInfo().repository.value()); web::openLinkInBrowser(this->getModInfo().repository().value());
} }
void ModInfoPopup::onInfo(CCObject*) { void ModInfoPopup::onInfo(CCObject*) {
auto info = this->getModInfo(); auto info = this->getModInfo();
FLAlertLayer::create( FLAlertLayer::create(
nullptr, nullptr,
("About " + info.name).c_str(), ("About " + info.name()).c_str(),
fmt::format( fmt::format(
"<cr>ID: {}</c>\n" "<cr>ID: {}</c>\n"
"<cg>Version: {}</c>\n" "<cg>Version: {}</c>\n"
"<cp>Developer: {}</c>\n" "<cp>Developer: {}</c>\n"
"<cb>Path: {}</c>\n", "<cb>Path: {}</c>\n",
info.id, info.id(),
info.version.toString(), info.version().toString(),
info.developer, info.developer(),
info.path.string() info.path().string()
), ),
"OK", "OK",
nullptr, nullptr,
@ -357,7 +357,7 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
m_mainLayer->addChild(m_installStatus); m_mainLayer->addChild(m_installStatus);
m_updateVersionLabel = CCLabelBMFont::create( m_updateVersionLabel = CCLabelBMFont::create(
("Available: " + indexItem->info.version.toString()).c_str(), ("Available: " + indexItem->info.version().toString()).c_str(),
"bigFont.fnt" "bigFont.fnt"
); );
m_updateVersionLabel->setScale(.35f); m_updateVersionLabel->setScale(.35f);
@ -369,7 +369,7 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
} }
// issue report button // issue report button
if (mod->getModInfo().issues) { if (mod->getModInfo().issues()) {
auto issuesBtnSpr = ButtonSprite::create( auto issuesBtnSpr = ButtonSprite::create(
"Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f "Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f
); );
@ -526,7 +526,7 @@ IndexItemInfoPopup::IndexItemInfoPopup()
bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) { bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
m_item = item; m_item = item;
m_installListener.setFilter(m_item->info.id); m_installListener.setFilter(m_item->info.id());
auto winSize = CCDirector::sharedDirector()->getWinSize(); auto winSize = CCDirector::sharedDirector()->getWinSize();
@ -613,7 +613,7 @@ void IndexItemInfoPopup::onInstall(CCObject*) {
[](IndexItemHandle handle) { [](IndexItemHandle handle) {
return fmt::format( return fmt::format(
" - <cr>{}</c> (<cy>{}</c>)", " - <cr>{}</c> (<cy>{}</c>)",
handle->info.name, handle->info.id handle->info.name(), handle->info.id()
); );
} }
), ),

View file

@ -45,9 +45,9 @@ void ModListCell::setupInfo(
bool hasDesc = bool hasDesc =
display == ModListDisplay::Expanded && display == ModListDisplay::Expanded &&
info.description.has_value(); info.description().has_value();
auto titleLabel = CCLabelBMFont::create(info.name.c_str(), "bigFont.fnt"); auto titleLabel = CCLabelBMFont::create(info.name().c_str(), "bigFont.fnt");
titleLabel->setAnchorPoint({ .0f, .5f }); titleLabel->setAnchorPoint({ .0f, .5f });
titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f); titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
if (hasDesc && spaceForTags) { if (hasDesc && spaceForTags) {
@ -66,7 +66,7 @@ void ModListCell::setupInfo(
this->addChild(titleLabel); this->addChild(titleLabel);
auto versionLabel = CCLabelBMFont::create( auto versionLabel = CCLabelBMFont::create(
info.version.toString(false).c_str(), info.version().toString(false).c_str(),
"bigFont.fnt" "bigFont.fnt"
); );
versionLabel->setAnchorPoint({ .0f, .5f }); versionLabel->setAnchorPoint({ .0f, .5f });
@ -78,7 +78,7 @@ void ModListCell::setupInfo(
versionLabel->setColor({ 0, 255, 0 }); versionLabel->setColor({ 0, 255, 0 });
this->addChild(versionLabel); this->addChild(versionLabel);
if (auto tag = info.version.getTag()) { if (auto tag = info.version().getTag()) {
auto tagLabel = TagNode::create( auto tagLabel = TagNode::create(
versionTagToString(tag.value()).c_str() versionTagToString(tag.value()).c_str()
); );
@ -92,7 +92,7 @@ void ModListCell::setupInfo(
this->addChild(tagLabel); this->addChild(tagLabel);
} }
auto creatorStr = "by " + info.developer; auto creatorStr = "by " + info.developer();
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt"); auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
creatorLabel->setScale(.43f); creatorLabel->setScale(.43f);
@ -131,7 +131,7 @@ void ModListCell::setupInfo(
descBG->setScale(.25f); descBG->setScale(.25f);
this->addChild(descBG); this->addChild(descBG);
m_description = CCLabelBMFont::create(info.description.value().c_str(), "chatFont.fnt"); m_description = CCLabelBMFont::create(info.description().value().c_str(), "chatFont.fnt");
m_description->setAnchorPoint({ .0f, .5f }); m_description->setAnchorPoint({ .0f, .5f });
m_description->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY()); m_description->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
m_description->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f); m_description->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
@ -354,7 +354,7 @@ bool IndexItemCell::init(
void IndexItemCell::updateState() {} void IndexItemCell::updateState() {}
std::string IndexItemCell::getDeveloper() const { std::string IndexItemCell::getDeveloper() const {
return m_item->info.developer; return m_item->info.developer();
} }
CCNode* IndexItemCell::createLogo(CCSize const& size) { CCNode* IndexItemCell::createLogo(CCSize const& size) {

View file

@ -52,11 +52,11 @@ static std::optional<int> queryMatchKeywords(
// fuzzy match keywords // fuzzy match keywords
if (query.keywords) { if (query.keywords) {
bool someMatched = false; bool someMatched = false;
WEIGHTED_MATCH(info.name, 2); WEIGHTED_MATCH(info.name(), 2);
WEIGHTED_MATCH(info.id, 1.5); WEIGHTED_MATCH(info.id(), 1.5);
WEIGHTED_MATCH(info.developer, 1); WEIGHTED_MATCH(info.developer(), 1);
WEIGHTED_MATCH(info.details.value_or(""), 2); WEIGHTED_MATCH(info.details().value_or(""), 2);
WEIGHTED_MATCH(info.description.value_or(""), 1); WEIGHTED_MATCH(info.description().value_or(""), 1);
if (!someMatched) { if (!someMatched) {
return std::nullopt; return std::nullopt;
} }
@ -66,7 +66,7 @@ static std::optional<int> queryMatchKeywords(
// sorted, at least enough so that if you're scrolling it based on // sorted, at least enough so that if you're scrolling it based on
// alphabetical order you will find the part you're looking for easily // alphabetical order you will find the part you're looking for easily
// so it's fine // so it's fine
weighted = -tolower(info.name[0]); weighted = -tolower(info.name()[0]);
} }
// empty keywords always match // empty keywords always match
@ -83,7 +83,7 @@ static std::optional<int> queryMatch(ModListQuery const& query, Mod* mod) {
static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle item) { static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle item) {
// if no force visibility was provided and item is already installed, don't // if no force visibility was provided and item is already installed, don't
// show it // show it
if (!query.forceVisibility && Loader::get()->isModInstalled(item->info.id)) { if (!query.forceVisibility && Loader::get()->isModInstalled(item->info.id())) {
return std::nullopt; return std::nullopt;
} }
// make sure all tags match // make sure all tags match