feat: add support for version older than 1.13

This commit is contained in:
HerozDotExe 2026-02-26 19:24:41 +01:00
parent 697ae0d040
commit 4b0410f4f3
8 changed files with 141 additions and 60 deletions

View file

@ -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,
};
}

View file

@ -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,

View file

@ -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),

View file

@ -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("<LegacyXMLLayout />", ""))
}
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<string | null> {
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
}

View file

@ -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<InstanceEvents> {
try {
const nativesDownloader = await core.NativesDownloader(
this.instanceLocation,
path.join(this.instanceLocation, "natives"),
this.versionManifest,
);
nativesDownloader.on("completed", () => {

View file

@ -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 };

View file

@ -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;

View file

@ -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<void>((res) => {
p.on("error", (d: Buffer) => {
console.log(d.toString());
res();
});
p.on("close", () => {
console.log("closed");
res();
});
});
});