Fix auth, add modrinth export (untested)

This commit is contained in:
Johannes Frohnmeyer 2022-09-18 18:56:44 +02:00
parent f7a3d5be53
commit d2c0979d16
Signed by: Johannes
GPG Key ID: E76429612C2929F4
16 changed files with 313 additions and 165 deletions

View File

@ -9,14 +9,12 @@ For documentation on how to use or install Inceptum, please head to the [wiki](h
Inceptum utilizes code/libraries/assets from:
- [ATLauncher](https://github.com/ATLauncher/ATLauncher): Microsoft authentication
- [JLHTTP](https://www.freeutils.net/source/jlhttp/): Used in MS Auth
- [MultiMC](https://github.com/MultiMC/Launcher): Used as a reference for some implementations
- [imgui-java](https://github.com/SpaiR/imgui-java): The library used for UI
- [Dear ImGui](https://github.com/ocornut/imgui): Included and wrapped in imgui-java, UI library
- [LWJGL](https://github.com/LWJGL/lwjgl3): Used as a backend for imgui-java
- [gson](https://github.com/google/gson): Used for interacting with various APIs and configs
- [slf4j](https://github.com/qos-ch/slf4j): Used for logging
- [logback](https://github.com/qos-ch/logback): An implementation of slf4j
- [JGit](https://www.eclipse.org/jgit/): Used to allow syncing repositories
- [Ubuntu](https://design.ubuntu.com/font/): Used with nerd font symbols as the font
- [meteor-client](https://github.com/MeteorDevelopment/meteor-client): A simple HTTP client
- Several of [my other projects](https://gitlab.com/jfmods)

View File

@ -28,8 +28,8 @@ println("Using Inceptum Build Script $version")
val lwjglVersion by extra("3.3.1")
val imguiVersion by extra("1.86.4")
val jfCommonsVersion by extra("2022.9.18+12-38-21")
val jgitVersion by extra("6.2.0.202206071550-r")
val jfCommonsVersion by extra("2022.9.18+16-50-22")
val jlhttpVersion by extra("2.6")
val flavorProp: String by extra(if (project.hasProperty("flavor")) "${project.property("flavor")}" else "custom")
if (flavorProp != "custom" && flavorProp != "maven" && flavorProp != "fat" && flavorProp != "windows" && flavorProp != "linux" && flavorProp != "macos")
throw IllegalStateException("Unsupported flavor: $flavorProp")

View File

@ -11,11 +11,11 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
//TODO modrinth
public class ExportCommand extends BaseInstanceCommand {
public ExportCommand() {
this(List.of("export"), List.of(
new ExportCommand(List.of("curseforge", "cf"), List.of()),
new ModrinthExportCommand(List.of("modrinth", "mr"), List.of()),
new MultiMCExportCommand(List.of("multimc", "mmc"), List.of())
));
}
@ -50,4 +50,21 @@ public class ExportCommand extends BaseInstanceCommand {
Exporters.MULTI_MC.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), "1.0");
}
}
private static class ModrinthExportCommand extends BaseInstanceCommand {
public ModrinthExportCommand(List<String> aliases, List<Command> subCommands) {
super("Export a Modrinth instance", "<export path> <version>", aliases, subCommands);
}
@Override
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception {
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
if (args.length == 1) throw new IllegalAccessException("You must specify a version number");
if (args.length != 2) throw new IllegalAccessException("Too many arguments");
ProcessState state = new ProcessState();
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
mds.runOnce(R::nop);
Exporters.MODRINTH.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), args.get(1));
}
}
}

View File

@ -1,24 +1,14 @@
package io.gitlab.jfronny.inceptum.imgui.window;
import com.sun.net.httpserver.HttpServer;
import imgui.ImGui;
import io.gitlab.jfronny.commons.serialize.Serializer;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.api.account.*;
import io.gitlab.jfronny.inceptum.launcher.model.microsoft.*;
import java.io.OutputStream;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
import java.net.URI;
import java.net.URISyntaxException;
public class MicrosoftLoginWindow extends Window {
private static HttpServer server;
private static final String MICROSOFT_LOGIN_URL = "https://login.live.com/oauth20_authorize.srf?client_id=90890812-00d1-48a8-8d3f-38465ef43b58&prompt=select_account&cobrandid=8058f65d-ce06-4c30-9559-473c9275a65d&response_type=code&scope=XboxLive.signin%20XboxLive.offline_access&redirect_uri=http%3A%2F%2F127.0.0.1%3A28562";
private final MicrosoftAccount account;
private final MicrosoftAuthServer server;
public MicrosoftLoginWindow() {
this(null);
@ -27,9 +17,9 @@ public class MicrosoftLoginWindow extends Window {
public MicrosoftLoginWindow(MicrosoftAccount account) {
super("Microsoft Login");
this.account = account;
this.server = new MicrosoftAuthServer(account);
try {
startServer();
this.server.start();
} catch (Exception e) {
Utils.LOGGER.error("Could not start mc login server", e);
}
@ -41,141 +31,16 @@ public class MicrosoftLoginWindow extends Window {
ImGui.text("Click the button below to begin");
if (ImGui.button("Open in Browser")) {
try {
Utils.openWebBrowser(new URI(MICROSOFT_LOGIN_URL));
Utils.openWebBrowser(new URI(MicrosoftAuthAPI.MICROSOFT_LOGIN_URL));
} catch (URISyntaxException e) {
Utils.LOGGER.error("Could not open browser", e);
}
}
}
private void startServer() throws Exception {
if (server != null) return;
server = HttpServer.create(new InetSocketAddress("127.0.0.1", MicrosoftAuthAPI.MICROSOFT_LOGIN_REDIRECT_PORT), 0);
server.createContext("/", req -> {
if (req.getRequestMethod().equals("GET")) {
Map<String, String> query = new LinkedHashMap<>();
for (String pair : req.getRequestURI().getQuery().split("&")) {
int index = pair.indexOf('=');
query.put(URLDecoder.decode(pair.substring(0, index), StandardCharsets.UTF_8), URLDecoder.decode(pair.substring(index + 1), StandardCharsets.UTF_8));
}
int respCode;
String respStr;
if (query.containsKey("error")) {
respCode = 500;
respStr = "Error logging in. Check console for more information";
Utils.LOGGER.error("Error logging into Microsoft account: " + URLDecoder
.decode(query.get("error_description"), StandardCharsets.UTF_8.toString()));
} else if (query.containsKey("code")) {
try {
acquireAccessToken(query.get("code"));
respCode = 200;
respStr = "Login complete. You can now close this window and go back to Inceptum";
} catch (Exception e) {
Utils.LOGGER.error("Error acquiring accessToken", e);
respCode = 500;
respStr = "Error logging in. Check console for more information";
}
} else {
respCode = 400;
respStr = "Code is missing";
}
OutputStream os = req.getResponseBody();
req.sendResponseHeaders(respCode, respStr.length());
os.write(respStr.getBytes(StandardCharsets.UTF_8));
os.flush();
os.close();
stopServer();
}
});
server.start();
}
private void stopServer() {
if (server == null) return;
server.stop(0);
server = null;
}
private void acquireAccessToken(String authcode) throws Exception {
OauthTokenResponse oauthTokenResponse = MicrosoftAuthAPI.tradeCodeForAccessToken(authcode);
acquireXBLToken(oauthTokenResponse);
}
private void acquireXBLToken(OauthTokenResponse oauthTokenResponse) throws Exception {
XboxLiveAuthResponse xblAuthResponse = MicrosoftAuthAPI.getXBLToken(oauthTokenResponse.accessToken);
acquireXsts(oauthTokenResponse, xblAuthResponse.token);
}
private void acquireXsts(OauthTokenResponse oauthTokenResponse, String xblToken) throws Exception {
XboxLiveAuthResponse xstsAuthResponse = MicrosoftAuthAPI.getXstsToken(xblToken);
if (xstsAuthResponse != null) {
acquireMinecraftToken(oauthTokenResponse, xstsAuthResponse);
}
}
private void acquireMinecraftToken(OauthTokenResponse oauthTokenResponse, XboxLiveAuthResponse xstsAuthResponse) throws Exception {
String xblUhs = xstsAuthResponse.displayClaims.xui.get(0).uhs;
String xblXsts = xstsAuthResponse.token;
LoginResponse loginResponse = MicrosoftAuthAPI.loginToMinecraft("XBL3.0 x=" + xblUhs + ";" + xblXsts);
Store store = MicrosoftAuthAPI.getMcEntitlements(loginResponse.accessToken);
Utils.LOGGER.info(Serializer.getInstance().serialize(store));
if (!(store.items.stream().anyMatch(i -> i.name.equalsIgnoreCase("product_minecraft"))
&& store.items.stream().anyMatch(i -> i.name.equalsIgnoreCase("game_minecraft")))) {
LauncherEnv.showError("This account doesn't have a valid purchase of Minecraft.\nPlease make sure you've bought the Java edition of Minecraft and then try again.", "Doesn't own Minecraft");
throw new Exception("Account does not own Minecraft");
}
Profile profile = MicrosoftAuthAPI.getMcProfile(loginResponse.accessToken);
if (profile == null) {
throw new Exception("Failed to get Minecraft profile");
}
// add the account
addAccount(oauthTokenResponse, xstsAuthResponse, loginResponse, profile);
close();
LauncherEnv.showInfo("The account \"" + profile.name + "\" was added successfully", "Account added");
}
private void addAccount(OauthTokenResponse oauthTokenResponse, XboxLiveAuthResponse xstsAuthResponse, LoginResponse loginResponse, Profile profile) {
if (account != null || AccountManager.isAccountByName(loginResponse.username)) {
MicrosoftAccount account = (MicrosoftAccount) AccountManager.getAccountByName(loginResponse.username);
if (account == null) {
return;
}
// if forced to relogin, then make sure they logged into correct account
if (this.account != null && !Objects.equals(account.accountId, this.account.accountId)) {
LauncherEnv.showError("Logged into incorrect account. Please login again on the Accounts tab", "Incorrect account");
return;
}
account.update(oauthTokenResponse, xstsAuthResponse, loginResponse, profile);
AccountManager.saveAccounts();
} else {
MicrosoftAccount account = new MicrosoftAccount(oauthTokenResponse, xstsAuthResponse, loginResponse, profile);
AccountManager.addAccount(account);
}
}
@Override
public void close() {
super.close();
stopServer();
server.close();
}
}

View File

@ -40,7 +40,7 @@ public class NewInstanceWindow extends Window {
ImGui.text("Using git to manage it is recommended if you do so");
ImGui.spacing();
ImGui.text("Importing CurseForge or Modrinth packs is not yet implemented");
//TODO generic importer based on zip contents and file name ("mrpack")
//TODO generic importer based on zip contents and file name ("zip", "mrpack")
ImGui.endTabItem();
}
ImGui.endTabBar();

View File

@ -5,6 +5,7 @@ import imgui.type.ImBoolean;
import io.gitlab.jfronny.inceptum.imgui.GuiMain;
import java.io.Closeable;
import java.io.IOException;
public abstract class Window implements Closeable {
private final String name;

View File

@ -3,6 +3,9 @@ plugins {
}
dependencies {
val jlhttpVersion: String by rootProject.extra
api(project(":common"))
implementation("net.freeutils:jlhttp:$jlhttpVersion")
compileOnly("org.jetbrains:annotations:23.0.0")
}

View File

@ -23,9 +23,9 @@ public class MicrosoftAuthAPI {
public static final String MICROSOFT_AUTH_TOKEN_URL = "https://login.live.com/oauth20_token.srf";
public static final String MICROSOFT_XBL_AUTH_TOKEN_URL = "https://user.auth.xboxlive.com/user/authenticate";
public static final String MICROSOFT_XSTS_AUTH_TOKEN_URL = "https://xsts.auth.xboxlive.com/xsts/authorize";
public static final String MICROSOFT_MINECRAFT_LOGIN_URL = "https://api.minecraftservices.com/authentication/login_with_xbox";
public static final String MICROSOFT_MINECRAFT_STORE_URL = "https://api.minecraftservices.com/entitlements/mcstore";
public static final String MICROSOFT_MINECRAFT_LOGIN_URL = "https://api.minecraftservices.com/launcher/login";
public static final String MICROSOFT_MINECRAFT_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile";
public static final String MICROSOFT_MINECRAFT_ENTITLEMENTS_URL = "https://api.minecraftservices.com/entitlements/license?requestId=";
public static OauthTokenResponse tradeCodeForAccessToken(String code) throws IOException, URISyntaxException {
return HttpUtils.post(MICROSOFT_AUTH_TOKEN_URL)
@ -83,16 +83,17 @@ public class MicrosoftAuthAPI {
}
public static LoginResponse loginToMinecraft(String xstsToken) throws IOException, URISyntaxException {
Map<Object, Object> data = new HashMap<Object, Object>();
data.put("identityToken", xstsToken);
Map<Object, Object> data = new HashMap<>();
data.put("xtoken", xstsToken);
data.put("platform", "PC_LAUNCHER");
return HttpUtils.post(MICROSOFT_MINECRAFT_LOGIN_URL)
.bodySerialized(data)
.sendSerialized(LoginResponse.class);
}
public static Store getMcEntitlements(String accessToken) throws IOException, URISyntaxException {
return HttpUtils.get(MICROSOFT_MINECRAFT_STORE_URL).bearer(accessToken).sendSerialized(Store.class);
public static Entitlements getEntitlements(String accessToken) throws IOException, URISyntaxException {
return HttpUtils.get(MICROSOFT_MINECRAFT_ENTITLEMENTS_URL + UUID.randomUUID()).bearer(accessToken).sendSerialized(Entitlements.class);
}
public static Profile getMcProfile(String accessToken) throws IOException, URISyntaxException {

View File

@ -0,0 +1,149 @@
package io.gitlab.jfronny.inceptum.launcher.api.account;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.launcher.model.microsoft.*;
import net.freeutils.httpserver.HTTPServer;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class MicrosoftAuthServer implements Closeable {
private static final HTTPServer server = new HTTPServer(MicrosoftAuthAPI.MICROSOFT_LOGIN_REDIRECT_PORT);
private static final HTTPServer.VirtualHost host = server.getVirtualHost(null);
private final MicrosoftAccount previous;
public MicrosoftAuthServer(@Nullable MicrosoftAccount previous) {
this.previous = previous;
}
public void start() throws IOException {
host.addContext("/", (req, res) -> {
if (req.getParams().containsKey("error")) {
res.getHeaders().add("Content-Type", "text/plain");
res.send(500, "Error logging in. Check console for more information");
Utils.LOGGER.error("Error logging into Microsoft account: " + URLDecoder
.decode(req.getParams().get("error_description"), StandardCharsets.UTF_8));
close();
return 0;
}
if (!req.getParams().containsKey("code")) {
res.getHeaders().add("Content-Type", "text/plain");
res.send(400, "Code is missing");
close();
return 0;
}
try {
acquireAccessToken(req.getParams().get("code"));
} catch (Exception e) {
Utils.LOGGER.error("Error acquiring accessToken", e);
res.getHeaders().add("Content-Type", "text/html");
res.send(500, "Error logging in. Check console for more information");
close();
return 0;
}
res.getHeaders().add("Content-Type", "text/plain");
// #. {0} is the name of the launcher
res.send(200, "Login complete. You can now close this window and go back to the Launcher");
close();
return 0;
}, "GET");
server.start();
}
private void addAccount(OauthTokenResponse oauthTokenResponse, XboxLiveAuthResponse xstsAuthResponse, LoginResponse loginResponse, Profile profile) {
if (this.previous != null || AccountManager.isAccountByName(loginResponse.username)) {
MicrosoftAccount account = (MicrosoftAccount) AccountManager.getAccountByName(loginResponse.username);
if (account == null) {
return;
}
// if forced to relogin, then make sure they logged into correct account
if (this.previous != null && !Objects.equals(account.accountId, this.previous.accountId)) {
LauncherEnv.showError("Logged into incorrect account. Please login again on the Accounts tab", "Incorrect account");
return;
}
account.update(oauthTokenResponse, xstsAuthResponse, loginResponse, profile);
AccountManager.saveAccounts();
} else {
MicrosoftAccount account = new MicrosoftAccount(oauthTokenResponse, xstsAuthResponse, loginResponse, profile);
AccountManager.addAccount(account);
}
}
private void acquireAccessToken(String authcode) throws Exception {
OauthTokenResponse oauthTokenResponse = MicrosoftAuthAPI.tradeCodeForAccessToken(authcode);
acquireXBLToken(oauthTokenResponse);
}
private void acquireXBLToken(OauthTokenResponse oauthTokenResponse) throws Exception {
XboxLiveAuthResponse xblAuthResponse = MicrosoftAuthAPI.getXBLToken(oauthTokenResponse.accessToken);
acquireXsts(oauthTokenResponse, xblAuthResponse.token);
}
private void acquireXsts(OauthTokenResponse oauthTokenResponse, String xblToken) throws Exception {
XboxLiveAuthResponse xstsAuthResponse = MicrosoftAuthAPI.getXstsToken(xblToken);
if (xstsAuthResponse != null) {
acquireMinecraftToken(oauthTokenResponse, xstsAuthResponse);
}
}
private void acquireMinecraftToken(OauthTokenResponse oauthTokenResponse, XboxLiveAuthResponse xstsAuthResponse) throws Exception {
String xblUhs = xstsAuthResponse.displayClaims.xui.get(0).uhs;
String xblXsts = xstsAuthResponse.token;
LoginResponse loginResponse = MicrosoftAuthAPI.loginToMinecraft("XBL3.0 x=" + xblUhs + ";" + xblXsts);
if (loginResponse == null) {
throw new Exception("Failed to login to Minecraft");
}
Entitlements entitlements = MicrosoftAuthAPI.getEntitlements(loginResponse.accessToken);
if (!(entitlements.items.stream().anyMatch(i -> i.name.equalsIgnoreCase("product_minecraft"))
&& entitlements.items.stream().anyMatch(i -> i.name.equalsIgnoreCase("game_minecraft")))) {
LauncherEnv.showError("This account doesn't have a valid purchase of Minecraft.\nPlease make sure you've bought the Java edition of Minecraft and then try again.", "Doesn't own Minecraft");
throw new Exception("Account does not own Minecraft");
}
Profile profile = null;
try {
profile = MicrosoftAuthAPI.getMcProfile(loginResponse.accessToken);
} catch (Exception e) {
LauncherEnv.showError("""
No Minecraft profiles were found for this account. Have you purchased Minecraft?
Please make sure you've bought the Java edition of Minecraft and then try again.
If you're an Xbox Game Pass subscriber, make sure to login and play through the Minecraft
Launcher once in order to create your Minecraft profile, then try logging in again.""",
"Minecraft Profile Not Found");
throw new Exception("Minecraft Profile not found", e);
}
if (profile == null) {
throw new Exception("Failed to get Minecraft profile");
}
// add the account
addAccount(oauthTokenResponse, xstsAuthResponse, loginResponse, profile);
}
@Override
public void close() {
server.stop();
}
}

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.inceptum.launcher.model.microsoft;
import java.util.List;
public class Store {
public class Entitlements {
public List<StoreItem> items;
public String signature;

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.inceptum.launcher.model.modrinth;
public class ModrinthHashes { //TODO ensure this can parse with additional hashes
public String sha1;
public String sha512;
}

View File

@ -0,0 +1,35 @@
package io.gitlab.jfronny.inceptum.launcher.model.modrinth;
import io.gitlab.jfronny.gson.annotations.SerializedName;
import java.util.List;
public class ModrinthModpackManifest {
public Integer formatVersion; // 1
public String game; // "minecraft"
public String versionId;
public String name;
public String summary; // optional
public List<File> files;
public Dependencies dependencies;
public static class File {
public String path;
public ModrinthHashes hashes;
public Env env; // optional
public List<String> downloads;
public Long fileSize;
public static class Env {
public ModrinthDependencyType client;
public ModrinthDependencyType server;
}
}
public static class Dependencies { // All are nullable
public String minecraft;
public String forge;
@SerializedName("fabric-loader") public String fabricLoader;
@SerializedName("quilt-loader") public String quiltLoader;
}
}

View File

@ -25,15 +25,10 @@ public class ModrinthVersion {
}
public static class File {
public Hashes hashes;
public ModrinthHashes hashes;
public String url;
public String filename;
public Boolean primary;
public static class Hashes {
public String sha1;
public String sha512;
}
}
public static class Dependency {

View File

@ -6,6 +6,7 @@ import java.util.List;
public class Exporters {
public static final int STEP_COUNT = 4;
public static final CurseForgeExporter CURSE_FORGE = new CurseForgeExporter();
public static final ModrinthExporter MODRINTH = new ModrinthExporter();
public static final MultiMCExporter MULTI_MC = new MultiMCExporter();
public static final List<Exporter<?>> EXPORTERS = List.of(CURSE_FORGE, MULTI_MC);
public static final List<Exporter<?>> EXPORTERS = List.of(CURSE_FORGE, MODRINTH, MULTI_MC);
}

View File

@ -0,0 +1,66 @@
package io.gitlab.jfronny.inceptum.launcher.system.export;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.ModrinthModpackManifest;
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import io.gitlab.jfronny.inceptum.launcher.system.source.ModrinthModSource;
import io.gitlab.jfronny.inceptum.launcher.util.ModPath;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Set;
public class ModrinthExporter extends Exporter<ModrinthModpackManifest> {
public ModrinthExporter() {
super("Modrinth", "mrpack", "overrides");
}
@Override
protected ModrinthModpackManifest generateManifests(Path root, InstanceMeta instance, String name, String version) throws IOException {
ModrinthModpackManifest manifest = new ModrinthModpackManifest();
manifest.formatVersion = 1;
manifest.game = "minecraft";
manifest.versionId = version;
manifest.name = name;
manifest.files = new ArrayList<>();
manifest.dependencies = new ModrinthModpackManifest.Dependencies();
manifest.dependencies.minecraft = instance.getMinecraftVersion();
if (instance.isFabric()) {
manifest.dependencies.fabricLoader = instance.getLoaderVersion();
}
JFiles.writeObject(root.resolve("modrinth.index.json"), manifest);
return manifest;
}
@Override
protected void addMods(Path root, InstanceMeta instance, Iterable<IWModDescription> mods, ModrinthModpackManifest manifest, Path modsOverrides) throws IOException {
modsLoop: for(IWModDescription mod : mods) {
if (ModPath.isImod(mod.path())) {
Set<ModSource> sources = mod.mod().orElseThrow().sources.keySet();
for (ModSource source : sources) {
if (source instanceof ModrinthModSource cms) {
manifest.files.add(cms.toManifest());
continue modsLoop;
}
}
// Not available on modrinth
for (ModSource source : sources) {
Files.createDirectories(modsOverrides);
Files.copy(source.getJarPath(), modsOverrides.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
} else {
Files.createDirectories(modsOverrides);
Files.copy(mod.path(), modsOverrides.resolve(mod.path().getFileName().toString()));
continue modsLoop;
}
throw new FileNotFoundException("Could not find mod file for " + mod.path());
}
JFiles.writeObject(root.resolve("modrinth.index.json"), manifest);
}
}

View File

@ -5,8 +5,7 @@ import io.gitlab.jfronny.commons.cache.MemoryOperationResultCache;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.inceptum.common.Net;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.ModrinthProject;
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.ModrinthVersion;
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.*;
import io.gitlab.jfronny.inceptum.launcher.api.ModrinthApi;
import java.io.IOException;
@ -112,4 +111,16 @@ public final class ModrinthModSource implements ModSource {
public String getModId() {
return current.project_id;
}
public ModrinthModpackManifest.File toManifest() throws IOException {
ModrinthVersion.File orig = current.files.get(0);
ModrinthModpackManifest.File f = new ModrinthModpackManifest.File();
f.path = "mods/" + orig.filename;
f.hashes = new ModrinthHashes();
f.hashes.sha1 = orig.hashes.sha1;
f.hashes.sha512 = orig.hashes.sha512;
f.downloads = List.of(orig.url);
f.fileSize = Files.size(getJarPath());
return f;
}
}