diff --git a/src/core/arguments.ts b/src/core/arguments.ts index 85dd331..9382f94 100644 --- a/src/core/arguments.ts +++ b/src/core/arguments.ts @@ -1,17 +1,18 @@ import path from "path"; import { isNeeded } from "../utils/rules"; -import { Argument, Auth, PoolFile, Version } from "../utils/types"; +import { Argument, Auth, Version } from "../utils/types"; import { getLibraries } from "./libraries"; import { version as packageVersion } from "../../package.json"; import { getArgument } from "./log4j"; +// Fill templates function fillArguments( arg: string, versionManifest: Version, assetsPath: string, instancePath: string, librariesPath: string, - classPaths: PoolFile[], + classPaths: string, auth: Auth, ) { const argumentsToFill = { @@ -25,6 +26,7 @@ function fillArguments( "${version_type}": versionManifest.type, "${assets_index_name}": versionManifest.assets, "${assets_root}": assetsPath, + "${game_assets}": assetsPath, "${auth_access_token}": auth.access_token, "${auth_session}": auth.access_token, "${auth_player_name}": auth.name, @@ -44,11 +46,10 @@ function fillArguments( } } - console.log(arg) - return arg; } +// Check if each arg is needed and fill templates function parseArg( this: string[], arg: Argument, @@ -56,7 +57,7 @@ function parseArg( assetsPath: string, instancePath: string, librariesPath: string, - classPaths: PoolFile[], + classPaths: string, auth: Auth, ) { if (isNeeded(arg)) { @@ -133,16 +134,15 @@ export async function generateLaunchArguments( customGameArgs?: string; }, ) { - // Log4j /!\ - // Replace templates /!\ - const jvm: string[] = []; - const game: string[] = []; + let game: string[] = []; + + const versionJar = path.join(versionRoot, `${versionManifest.id}.jar`); const classPaths = generateClassPaths( versionManifest, librariesPath, - path.join(versionRoot, `${versionManifest.id}.jar`), + versionJar, ); function p(args: string[], arg: Argument) { @@ -157,11 +157,33 @@ export async function generateLaunchArguments( ]); } - for (const arg of versionManifest.arguments.jvm) { - p(jvm, arg); - } - for (const arg of versionManifest.arguments.game) { - p(game, arg); + if (versionManifest.arguments) { + for (const arg of versionManifest.arguments.jvm) { + p(jvm, arg); + } + for (const arg of versionManifest.arguments.game) { + p(game, arg); + } + } else { + console.log(versionManifest.minecraftArguments) + game = fillArguments( + versionManifest.minecraftArguments, + versionManifest, + assetsPath, + instancePath, + librariesPath, + classPaths, + auth, + ).split(" "); + + jvm.push(`-Djava.library.path=${path.join(instancePath, "natives")}`); + jvm.push(`-Dminecraft.launcher.brand=nlk`); + jvm.push(`-Dminecraft.launcher.version=${packageVersion}`); + jvm.push( + `-Dminecraft.client.jar=${path.join(versionRoot, `${versionManifest.id}.jar`)}`, + ); + jvm.push("-cp"); + jvm.push(classPaths) } if (options.customJvmArgs !== "") { @@ -178,21 +200,26 @@ export async function generateLaunchArguments( const log4j = await getArgument(versionManifest, versionRoot); + let launchArguments = [ + ...jvm, + `-Xms${options.minRam || "2G"}`, + `-Xmx${options.maxRam || "2G"}`, + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=32M", + ] + + if (log4j) { + launchArguments.push(log4j) + } + + launchArguments = [...launchArguments, versionManifest.mainClass, ...game,] + return { command: javaExecutable, - args: [ - ...jvm, - `-Xms${options.minRam || "2G"}`, - `-Xmx${options.maxRam || "2G"}`, - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=32M", - log4j, - versionManifest.mainClass, - ...game, - ], + args: launchArguments, }; } diff --git a/src/core/launch.ts b/src/core/launch.ts index 4c14239..b199dd9 100644 --- a/src/core/launch.ts +++ b/src/core/launch.ts @@ -1,5 +1,5 @@ import { version as packageVersion } from "../../package.json"; -import { spawn } from "child_process"; +import { spawn, spawnSync } from "child_process"; import { LaunchArguments } from "../utils/types"; export function launch( @@ -11,6 +11,7 @@ export function launch( console.log( `[nlk ${packageVersion}] Launching command : ${launchArguments.command} ${launchArguments.args.join(" ")}`, ); + const process = spawn(launchArguments.command, launchArguments.args, { detached, cwd: gameRoot, diff --git a/src/core/libraries.ts b/src/core/libraries.ts index 5f87afe..3a04be9 100644 --- a/src/core/libraries.ts +++ b/src/core/libraries.ts @@ -9,7 +9,7 @@ export function getLibraries(versionManifest: Version, librariesRoot: string) { for (const key in versionManifest.libraries) { if (Object.prototype.hasOwnProperty.call(versionManifest.libraries, key)) { const library = versionManifest.libraries[key]; - if (isNeeded(library)) { + if (isNeeded(library) && library.downloads.artifact) { libs.push({ url: library.downloads.artifact.url, path: path.join(librariesRoot, library.downloads.artifact.path), diff --git a/src/core/log4j.ts b/src/core/log4j.ts index fedc2fe..5778564 100644 --- a/src/core/log4j.ts +++ b/src/core/log4j.ts @@ -5,37 +5,42 @@ import { exists } from "../utils/fs"; import { hash } from "crypto"; import fs from "fs/promises"; -async function patchFileLog4j(versionManifest: Version, xmlDestination: string) { - const original = await fs.readFile(xmlDestination, {encoding: "utf-8"}) +async function patchFileLog4j(xmlDestination: string) { + const original = await fs.readFile(xmlDestination, { encoding: "utf-8" }) await fs.writeFile(xmlDestination, original.replace("", "")) } export async function getArgument( versionManifest: Version, destination: string, -) { - const xmlDestination = path.join( - destination, - versionManifest.logging.client.file.id, - ); - if (!(await exists(xmlDestination))) { - await downloadFile({ - url: versionManifest.logging.client.file.url, - path: xmlDestination, - }); +): Promise { + if (versionManifest.logging?.client) { + const xmlDestination = path.join( + destination, + versionManifest.logging.client.file.id, + ); + if (!(await exists(xmlDestination))) { + await downloadFile({ + url: versionManifest.logging.client.file.url, + path: xmlDestination, + }); - if ( - hash("sha1", await fs.readFile(xmlDestination, { encoding: "utf-8" })) !== - versionManifest.logging.client.file.sha1 - ) { - throw new Error("Wrong hash for log4j's xml config"); + if ( + hash("sha1", await fs.readFile(xmlDestination, { encoding: "utf-8" })) !== + versionManifest.logging.client.file.sha1 + ) { + throw new Error("Wrong hash for log4j's xml config"); + } + + await patchFileLog4j(xmlDestination) + + return versionManifest.logging.client.argument.replace( + "${path}", + xmlDestination, + ); } - - await patchFileLog4j(versionManifest, xmlDestination) } - - return versionManifest.logging.client.argument.replace( - "${path}", - xmlDestination, - ); + + // if logging is not present it means that the verson is older than 1.7 hence the fix for log4j isn't needed + return null } diff --git a/src/launcher/instance.ts b/src/launcher/instance.ts index 76b2079..38fafe5 100644 --- a/src/launcher/instance.ts +++ b/src/launcher/instance.ts @@ -8,7 +8,6 @@ import { } from "../utils/types"; import * as core from "../core"; import path from "path"; -import { os, arch } from "../utils/systemInfo"; import { EventEmitter } from "node:events"; import { InstallError, LaunchError } from "../utils/errors"; import { installForge } from "../core/modloaders"; @@ -149,7 +148,7 @@ export class Instance extends EventEmitter { try { const nativesDownloader = await core.NativesDownloader( - this.instanceLocation, + path.join(this.instanceLocation, "natives"), this.versionManifest, ); nativesDownloader.on("completed", () => { diff --git a/src/utils/types.ts b/src/utils/types.ts index 352486c..28664e8 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -116,10 +116,11 @@ export type Library = { }; export type Version = { - arguments: { + arguments?: { game: Argument[]; jvm: Argument[]; }; + minecraftArguments: string; assetIndex: { id: string; sha1: string; @@ -141,7 +142,7 @@ export type Version = { majorVersion: number; }; libraries: Library[]; - logging: { + logging?: { client: { argument: string; file: { id: string; sha1: string; size: number; url: string }; diff --git a/src/utils/unzip.ts b/src/utils/unzip.ts index e30e799..711cf12 100644 --- a/src/utils/unzip.ts +++ b/src/utils/unzip.ts @@ -11,7 +11,6 @@ export function unzipAll(from: string, to: string, filters: string[]) { for (const filter of filters) { if (entry.entryName.includes(filter)) { shouldSkip = true; - console.log("skipping", entry.entryName); } } if (shouldSkip) continue; diff --git a/tests/instance/modloaders/oldForge.test.ts b/tests/instance/modloaders/oldForge.test.ts new file mode 100644 index 0000000..2bb6334 --- /dev/null +++ b/tests/instance/modloaders/oldForge.test.ts @@ -0,0 +1,49 @@ +import { test } from "vitest"; +import { Instance, offlineAuth, RuntimeManager, getJavaComponent } from "../../../dist/index.js"; +import path from "path"; +import fs from "fs/promises"; + +const gameRoot = path.join(import.meta.dirname, "..", "temp"); +await fs.rm(gameRoot, { recursive: true, force: true }); +await fs.mkdir(gameRoot, { recursive: true }); + +test("launch game", { timeout: 0 }, async () => { + const instance = new Instance(); + const javaManager = new RuntimeManager(path.join(gameRoot, "java")); + + javaManager.on("progress", console.log) + + const java = await javaManager.use(await getJavaComponent("1.12.2")) + + const auth = offlineAuth("player"); + + instance.setVersion("1.12.2"); + instance.setPaths(gameRoot); + instance.setAuth(auth); + instance.setJavaExecutable(java) + instance.setModLoader("forge", "14.23.5.2864"); + + instance.on("progress", console.log); + + await instance.install(); + const p = await instance.launch(); + + p.stdout.on("data", (d: Buffer) => { + console.log(d.toString()); + }); + + p.stderr.on("data", (d: Buffer) => { + console.log(d.toString()); + }); + + await new Promise((res) => { + p.on("error", (d: Buffer) => { + console.log(d.toString()); + res(); + }); + p.on("close", () => { + console.log("closed"); + res(); + }); + }); +});