// AI-authored (Claude). #include "launchers.hpp" #include "fs.hpp" #include "json.hpp" #include #include #include #include #include #include namespace packutils::launchers { namespace { /// `mmc-pack.json` is a list of components; a large one is a few kilobytes. constexpr std::size_t pack_limit = std::size_t{1} << 20U; struct LoaderUid { std::string_view uid; std::string_view loader; }; constexpr std::array loader_uids{{ {.uid = "net.fabricmc.fabric-loader", .loader = "fabric"}, {.uid = "org.quiltmc.quilt-loader", .loader = "quilt"}, {.uid = "net.neoforged", .loader = "neoforge"}, {.uid = "net.minecraftforge", .loader = "forge"}, }}; /// Reads the Minecraft and loader components; a file that is missing or does not parse says /// nothing, since the launcher will report it itself. Result read_components(const std::string &directory, Instance &instance) { auto bytes = fs::read(fs::join(directory, "mmc-pack.json"), pack_limit); if (!bytes) { return std::unexpected(bytes.error()); } if (!*bytes) { return {}; } auto document = json::parse(**bytes); const json::Value *components = document ? document->get("components") : nullptr; if (components == nullptr || !components->is(json::Value::Type::array)) { return {}; } for (const json::Value &component : components->items) { const auto uid = component.string_at("uid"); const auto version = component.string_at("version"); if (!uid || !version) { continue; } if (*uid == "net.minecraft") { instance.minecraft = *version; continue; } for (const LoaderUid &known : loader_uids) { if (*uid == known.uid && instance.loader.empty()) { instance.loader = known.loader; instance.loader_version = *version; } } } return {}; } } // namespace Result> detect(const std::string &directory) { auto config = fs::state(fs::join(directory, "instance.cfg")); if (!config) { return std::unexpected(config.error()); } if (*config != fs::State::file) { return std::optional{}; } Instance instance{ .launcher = "prism", .game = {}, .minecraft = {}, .loader = {}, .loader_version = {}}; for (const std::string_view name : {".minecraft", "minecraft"}) { const std::string game = fs::join(directory, name); auto at = fs::state(game); if (!at) { return std::unexpected(at.error()); } if (*at == fs::State::directory) { instance.game = game; break; } } if (instance.game.empty()) { return std::unexpected(Failure::of( PU_KIND_INVALID_REQUEST, "InstanceInvalid", "the launcher instance " + directory + " has no game directory yet (.minecraft); start it once from the launcher")); } if (auto read = read_components(directory, instance); !read) { return std::unexpected(read.error()); } return std::optional{std::move(instance)}; } } // namespace packutils::launchers