Skip to content

Commit

Permalink
wip: automatic mod updates
Browse files Browse the repository at this point in the history
  • Loading branch information
BenMcAvoy committed Dec 16, 2024
1 parent 8b34dc5 commit cac2665
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 293 deletions.
48 changes: 18 additions & 30 deletions CarbonLauncher/include/repomanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <vector>
#include <string>
#include <mutex>

constexpr const char* REPOS_URL = "https://github.com/ScrappySM/CarbonLauncher/raw/refs/heads/main/repos.json";

Expand All @@ -18,33 +19,14 @@ namespace Carbon {
// A short description of the mod
std::string description;

// The link to the GitHub repo of the mod
std::string repo;

// A list of all the dependencies of the mod
std::vector<std::string> dependencies;

// The git repo tag to download
std::string tag;

// A list of all the files to download from the GitHub releases
std::vector<std::string> files;

// The supported game version
std::string supported;
// The link to the mods GitHub page
std::string user;
std::string repoName;

bool installed = false;
};

struct Repo {
// The name of the repository (e.g. ScrappySM, Scrap-Mods)
std::string name;

// The link to the repositories website
std::string link;

// A list of all the mods
std::vector<Mod> mods;
void Install();
void Uninstall();
};

class RepoManager {
Expand All @@ -55,15 +37,21 @@ namespace Carbon {

// Converts a JSON object to a Repo object
// @param json The JSON object to convert
// @return The converted Repo object (`json` -> `Repo`)
std::vector<Repo> URLToRepos(const std::string& url);
// @return The converted Repo object (`json` -> `Mod`)
std::vector<Mod> URLToMods(const std::string& url);

// Gets all the repos
// @return A vector of all the repos
std::vector<Repo>& GetRepos() { return repos; }
// @return A vector of all the mods
std::vector<Mod>& GetMods() {
std::lock_guard<std::mutex> lock(this->modMutex);
return this->mods;
}

bool hasLoaded = false;

private:
Repo JSONToRepo(nlohmann::json json);
std::vector<Repo> repos = {};
Mod JSONToMod(nlohmann::json jMod);
std::vector<Mod> mods = {};
std::mutex modMutex;
};
}; // namespace Carbon
4 changes: 3 additions & 1 deletion CarbonLauncher/src/gamemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ std::vector<std::string> GameManager::GetLoadedCustomModules() {
// We need to go through all repos and all their mods, incrementing module count
// if one of their files is found in the target process
std::vector<std::string> loadedModules;

/* TODO: Implement this
for (auto& repo : C.repoManager.GetRepos()) {
for (auto& mod : repo.mods) {
for (auto& file : mod.files) {
Expand All @@ -242,7 +244,7 @@ std::vector<std::string> GameManager::GetLoadedCustomModules() {
}
}
}
}
}*/

return loadedModules;
}
210 changes: 75 additions & 135 deletions CarbonLauncher/src/guimanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,62 +157,54 @@ void _GUI() {
if (ImGui::BeginTabBar("CarbonTabs", ImGuiTabBarFlags_None)) {
// Begin the first tab
if (ImGui::BeginTabItem("Home")) {
// Show each installed mod in a child window that spans the entire width of the window
for (auto& repo : C.repoManager.GetRepos()) {
for (auto& mod : repo.mods) {
if (mod.installed) {
ImGui::BeginChild(mod.name.c_str(), ImVec2(0, 150), true);
ImGui::TextWrapped(mod.name.c_str());
ImGui::Separator();
ImGui::TextWrapped(mod.description.c_str());
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);

std::string authText = mod.authors.size() > 1 ? "Authors: " : "Author: ";
ImGui::TextWrapped(authText.c_str());
ImGui::SameLine();
for (auto& author : mod.authors) {
std::string link = fmt::format("https://github.com/{}", author);
if (ImGui::Button(fmt::format("@{} ", author).c_str())) {
ShellExecute(NULL, L"open", std::wstring(link.begin(), link.end()).c_str(), NULL, NULL, SW_SHOWNORMAL);
}

if (author != mod.authors.back()) {
ImGui::SameLine();
}
}
if (!C.repoManager.hasLoaded) {
ImGui::TextWrapped("Loading mods...");
ImGui::EndTabItem();
ImGui::End();
return;
}

// Go to bottom of child window
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y);
// Show each installed mod in a child window that spans the entire width of the window
for (auto& mod : C.repoManager.GetMods()) {
if (mod.installed) {
ImGui::BeginChild(mod.name.c_str(), ImVec2(0, 150), true);
ImGui::TextWrapped(mod.name.c_str());
ImGui::Separator();
ImGui::TextWrapped(mod.description.c_str());
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);

int frameWidth = (int)ImGui::GetContentRegionAvail().x;
if (ImGui::Button("Uninstall", ImVec2((float)frameWidth, 0))) {
if (C.gameManager.IsGameRunning()) {
spdlog::error("TODO: Unload the mod from the game (ctx: tried to uninstall mod while game was running)");
return;
}
std::string authText = mod.authors.size() > 1 ? "Authors: " : "Author: ";
ImGui::TextWrapped(authText.c_str());
ImGui::SameLine();
for (auto& author : mod.authors) {
std::string link = fmt::format("https://github.com/{}", author);
if (ImGui::Button(fmt::format("@{} ", author).c_str())) {
ShellExecute(NULL, L"open", std::wstring(link.begin(), link.end()).c_str(), NULL, NULL, SW_SHOWNORMAL);
}

std::string modulesDir = Utils::GetCurrentModuleDir() + "modules\\";
std::string modFile = modulesDir + repo.name + "\\" + mod.files[0];
std::string tagFile = modulesDir + repo.name + "\\" + mod.files[0].substr(0, mod.files[0].size() - 3) + "tag";
if (author != mod.authors.back()) {
ImGui::SameLine();
}
}

std::filesystem::remove(modFile);
std::filesystem::remove(tagFile);
// Go to bottom of child window
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y);

mod.installed = false;
int frameWidth = (int)ImGui::GetContentRegionAvail().x;
if (ImGui::Button("Uninstall", ImVec2((float)frameWidth, 0))) {
if (C.gameManager.IsGameRunning()) {
spdlog::error("TODO: Unload the mod from the game (ctx: tried to uninstall mod while game was running)");
return;
}

ImGui::EndChild();
mod.Uninstall();
}

ImGui::EndChild();
}
}

// If no mods are installed, show a message
if (std::all_of(C.repoManager.GetRepos().begin(), C.repoManager.GetRepos().end(), [](const Repo& repo) {
return std::all_of(repo.mods.begin(), repo.mods.end(), [](const Mod& mod) {
return !mod.installed;
});
})) {

if (std::all_of(C.repoManager.GetMods().begin(), C.repoManager.GetMods().end(), [](const Mod& mod) { return !mod.installed; })) {
ImGui::TextWrapped("No mods installed :(");
ImGui::TextWrapped("Check out the public mods tab to get started!");
}
Expand All @@ -237,113 +229,61 @@ void _GUI() {
}

if (ImGui::BeginTabItem("Public mods")) {
ImGui::Columns(3, "modColumns", false);
for (auto& repo : C.repoManager.GetRepos()) {
// Layout:
// | mod | mod | mod |
// | mod |

for (auto& mod : repo.mods) {
ImGui::BeginChild(mod.name.c_str(), ImVec2(0, 300), true);

ImGui::TextWrapped(mod.name.c_str());
ImGui::Separator();
ImGui::TextWrapped(mod.description.c_str());
for (auto& mod : C.repoManager.GetMods()) {
ImGui::BeginChild(mod.name.c_str(), ImVec2(0, 300), true);

ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);

if (mod.authors.size() > 1) {
ImGui::TextWrapped("Authors: ");
ImGui::SameLine();
ImGui::TextWrapped(mod.name.c_str());
ImGui::Separator();
ImGui::TextWrapped(mod.description.c_str());

for (auto& author : mod.authors) {
std::string link = "https://github.com/" + author;
std::string text = "@" + author + " ";
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);

if (ImGui::Button(text.c_str())) {
ShellExecute(NULL, L"open", std::wstring(link.begin(), link.end()).c_str(), NULL, NULL, SW_SHOWNORMAL);
}
if (mod.authors.size() > 1) {
ImGui::TextWrapped("Authors: ");
ImGui::SameLine();

ImGui::PopStyleColor();
for (auto& author : mod.authors) {
std::string link = "https://github.com/" + author;
std::string text = "@" + author + " ";

if (author != mod.authors.back()) {
ImGui::SameLine();
}
if (ImGui::Button(text.c_str())) {
ShellExecute(NULL, L"open", std::wstring(link.begin(), link.end()).c_str(), NULL, NULL, SW_SHOWNORMAL);
}
}
else {
ImGui::TextWrapped("Author: ");
ImGui::SameLine();

std::string link = "https://github.com/" + mod.authors[0];
std::string text = "@" + mod.authors[0] + " ";
ImGui::PopStyleColor();

if (ImGui::Button(text.c_str())) {
ShellExecute(NULL, L"open", std::wstring(link.begin(), link.end()).c_str(), NULL, NULL, SW_SHOWNORMAL);
if (author != mod.authors.back()) {
ImGui::SameLine();
}
}

// Set ImGui cursor y pos to bottom of child window
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y);
}
else {
ImGui::TextWrapped("Author: ");
ImGui::SameLine();

if (ImGui::Button(mod.installed ? "Uninstall" : "Install")) {
if (mod.installed) {
if (C.gameManager.IsGameRunning()) {
spdlog::error("TODO: Unload the mod from the game (ctx: tried to uninstall mod while game was running)");
return;
}
std::string link = "https://github.com/" + mod.authors[0];
std::string text = "@" + mod.authors[0] + " ";

std::string modulesDir = Utils::GetCurrentModuleDir() + "modules\\";
std::string modFile = modulesDir + repo.name + "\\" + mod.files[0];
std::string tagFile = modulesDir + repo.name + "\\" + mod.files[0].substr(0, mod.files[0].size() - 3) + "tag";
if (ImGui::Button(text.c_str())) {
ShellExecute(NULL, L"open", std::wstring(link.begin(), link.end()).c_str(), NULL, NULL, SW_SHOWNORMAL);
}
}

std::filesystem::remove(modFile);
std::filesystem::remove(tagFile);
// Set ImGui cursor y pos to bottom of child window
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y);

mod.installed = false;
}
else {
// Set the mod to installed for now... (we will set it to false if the download fails)
// This is so the UI feels responsive, it shouldn't cause any issues
mod.installed = true;

std::filesystem::create_directory(Utils::GetCurrentModuleDir() + "modules");
std::filesystem::create_directory(Utils::GetCurrentModuleDir() + "modules\\" + repo.name);

std::thread([&]() {
// Download the mods
// TODO: error handling in http
for (auto& file : mod.files) {
std::string url = mod.repo + "/releases/download/" + mod.tag + "/" + file;
std::string path = Utils::GetCurrentModuleDir() + "modules\\" + repo.name + "\\" + file;
cpr::Response response = cpr::Get(cpr::Url{ url });
if (response.status_code != 200) {
mod.installed = false;
return;
}

std::ofstream out(path, std::ios::binary);
out << response.text;
out.close();

std::string fileNoExt = file.substr(0, file.find_last_of('.'));
std::string tagPath = Utils::GetCurrentModuleDir() + "modules\\" + repo.name + "\\" + fileNoExt + ".tag";
std::ofstream tagOut(tagPath);

tagOut << mod.tag;
tagOut.close();
}

// Set the mod as installed
mod.installed = true;
}).detach();
}
if (ImGui::Button(mod.installed ? "Uninstall" : "Install")) {
if (mod.installed) {
mod.Uninstall();
}
else {
mod.Install();
}
}

ImGui::EndChild();
ImGui::EndChild();

ImGui::NextColumn();
}
ImGui::NextColumn();
}

ImGui::Columns(1);
Expand Down
Loading

0 comments on commit cac2665

Please sign in to comment.