UNPKG

minecraft-java-core

Version:

A library starting minecraft game NW.js and Electron.js

365 lines 17.3 kB
"use strict"; /** * @author Luuxis * Luuxis License v1.0 (voir fichier LICENSE pour les détails en FR/EN) */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const os_1 = __importDefault(require("os")); const semver_1 = __importDefault(require("semver")); const Index_js_1 = require("../utils/Index.js"); /** Maps Node.js platforms to Mojang's library folders */ const MOJANG_LIBRARY_MAP = { win32: 'windows', darwin: 'osx', linux: 'linux' }; /** * Builds and organizes JVM and game arguments required to launch Minecraft. */ class MinecraftArguments { constructor(options) { this.options = options; this.authenticator = options.authenticator; } /** * Gathers all arguments (game, JVM, classpath) and returns them for launching. */ async GetArguments(versionJson, loaderJson) { const gameArguments = await this.GetGameArguments(versionJson, loaderJson); const jvmArguments = await this.GetJVMArguments(versionJson); const classpathData = await this.GetClassPath(versionJson, loaderJson); return { game: gameArguments, jvm: jvmArguments, classpath: classpathData.classpath, mainClass: classpathData.mainClass }; } /** * Builds the Minecraft game arguments, injecting authentication tokens, * user info, and any loader arguments if present. * @param versionJson The Minecraft version JSON. * @param loaderJson The loader JSON (e.g., Forge) if applicable. */ async GetGameArguments(versionJson, loaderJson) { // For older MC versions, arguments may be in `minecraftArguments` instead of `arguments.game` let gameArgs = versionJson.minecraftArguments ? versionJson.minecraftArguments.split(' ') : versionJson.arguments?.game ?? []; // Merge loader's Minecraft arguments if provided if (loaderJson) { const loaderGameArgs = loaderJson.minecraftArguments ? loaderJson.minecraftArguments.split(' ') : []; gameArgs = gameArgs.concat(loaderGameArgs); // Remove duplicate arguments gameArgs = gameArgs.filter((item, index, self) => index === self.findIndex(arg => arg === item)); } // Determine user type (e.g. 'msa' or 'Xbox') depending on version and authenticator let userType = 'msa'; if (versionJson.id.startsWith('1.16')) { userType = 'Xbox'; } else { userType = this.authenticator.meta.type === 'Xbox' ? 'msa' : this.authenticator.meta.type; } // Map of placeholders to actual values const assetsIndexName = versionJson.assetIndex?.id ?? versionJson.assets ?? versionJson.id; const placeholderMap = { '${auth_access_token}': this.authenticator.access_token, '${auth_session}': this.authenticator.access_token, '${auth_player_name}': this.authenticator.name, '${auth_uuid}': this.authenticator.uuid, '${auth_xuid}': this.authenticator?.xboxAccount?.xuid || this.authenticator.access_token, '${user_properties}': this.authenticator.user_properties, '${user_type}': userType, '${version_name}': loaderJson ? loaderJson.id || versionJson.id : versionJson.id, '${assets_index_name}': assetsIndexName, '${game_directory}': this.options.instance ? `${this.options.path}/instances/${this.options.instance}` : this.options.path, '${assets_root}': (0, Index_js_1.isold)(versionJson) ? `${this.options.path}/resources` : `${this.options.path}/assets`, '${game_assets}': (0, Index_js_1.isold)(versionJson) ? `${this.options.path}/resources` : `${this.options.path}/assets`, '${version_type}': versionJson.type, '${clientid}': this.authenticator.clientId || this.authenticator.client_token || this.authenticator.access_token }; // Replace placeholders in the game arguments for (let i = 0; i < gameArgs.length; i++) { if (typeof gameArgs[i] === 'object') { gameArgs.splice(i, 1); i--; continue; } const arg = gameArgs[i]; if (placeholderMap[arg]) { gameArgs[i] = placeholderMap[arg]; } } // If screen options are provided, add them if (this.options.screen) { const { width, height } = this.options.screen; if (width && height) { gameArgs.push('--width', String(width), '--height', String(height)); } } // Add any extra game arguments from user config gameArgs.push(...this.options.GAME_ARGS); // Filter out any remaining unexpected objects return gameArgs.filter(item => typeof item === 'string'); } /** * Evaluates rules for arguments (OS, version, features). * @param rules Array of rules to evaluate. * @returns true if all rules pass, false otherwise. */ EvaluateRules(rules) { if (!rules || rules.length === 0) return true; for (const rule of rules) { const action = rule.action || 'allow'; let matches = true; // Check OS rules if (rule.os) { const osName = MOJANG_LIBRARY_MAP[process.platform]; if (rule.os.name && rule.os.name !== osName) { matches = false; } // Check OS version range (for Windows) if (matches && rule.os.versionRange) { const osRelease = os_1.default.release(); // Simple version comparison for Windows if (rule.os.versionRange.min) { // For simplicity, we'll allow the rule if on Windows // A more complete implementation would parse and compare versions matches = process.platform === 'win32'; } if (rule.os.versionRange.max) { // Similar simple check matches = process.platform === 'win32'; } } } // If action is 'allow' and matches, or action is 'disallow' and doesn't match if ((action === 'allow' && !matches) || (action === 'disallow' && matches)) { return false; } } return true; } /** * Extracts the key from a JVM argument for comparison. * @param arg The JVM argument to extract the key from. */ getArgKey(arg) { if (arg.includes('=')) return arg.split('=')[0]; if (arg.match(/^-XX:[+-]/)) return arg; const match = arg.match(/^(.+?)(\d+[GMKgmkBb]?)?$/); return match ? match[1] : arg; } /** * Processes default-user-jvm arguments from the version JSON. * @param versionJson The Minecraft version JSON. * @param existingArgs Existing JVM arguments to check for duplicates. * @returns Array of default JVM arguments to add. */ ProcessDefaultUserJVMArgs(versionJson, existingArgs) { const defaultUserJVM = versionJson.arguments?.['default-user-jvm']; if (!defaultUserJVM || !Array.isArray(defaultUserJVM)) { return []; } // Créer un Set des clés existantes (jvmArgs + JVM_ARGS utilisateur) const allExistingArgs = [...existingArgs, ...this.options.JVM_ARGS]; const existingKeys = new Set(allExistingArgs.map(arg => this.getArgKey(arg))); const defaultArgs = []; for (const argEntry of defaultUserJVM) { // Check if rules exist and evaluate them if (argEntry.rules && !this.EvaluateRules(argEntry.rules)) continue; // Extract values let values = []; if (typeof argEntry.value === 'string') values = [argEntry.value]; else if (Array.isArray(argEntry.value)) values = argEntry.value; // Add values if they don't already exist for (const value of values) { if (typeof value === 'string') { const key = this.getArgKey(value); // Ajouter seulement si la clé n'existe pas déjà if (!existingKeys.has(key)) { defaultArgs.push(value); existingKeys.add(key); // Éviter les doublons dans defaultUserJVM lui-même } } } } return defaultArgs; } /** * Builds the JVM arguments needed by Minecraft. This includes memory settings, * OS-specific options, and any additional arguments supplied by the user. * @param versionJson The Minecraft version JSON. */ async GetJVMArguments(versionJson) { // Some OS-specific defaults const osSpecificOpts = { win32: '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump', darwin: '-XstartOnFirstThread', linux: '-Xss1M' }; // Core JVM arguments const jvmArgs = [ `-Xms${this.options.memory.min}`, `-Xmx${this.options.memory.max}`, '-XX:+UnlockExperimentalVMOptions', '-XX:G1NewSizePercent=20', '-XX:G1ReservePercent=20', '-XX:MaxGCPauseMillis=50', '-XX:G1HeapRegionSize=32M', '-Dfml.ignoreInvalidMinecraftCertificates=true', `-Djna.tmpdir=${this.options.path}/versions/${versionJson.id}/natives`, `-Dorg.lwjgl.system.SharedLibraryExtractPath=${this.options.path}/versions/${versionJson.id}/natives`, `-Dio.netty.native.workdir=${this.options.path}/versions/${versionJson.id}/natives` ]; // For newer MC versions that use "arguments.game" instead of "minecraftArguments", // we add OS-specific arguments (e.g., Mac uses -XstartOnFirstThread). if (!versionJson.minecraftArguments) { const opt = osSpecificOpts[process.platform]; if (opt) { jvmArgs.push(opt); } } // bypass offline mode multiplayer if (this.options?.bypassOffline) { jvmArgs.push('-Dminecraft.api.auth.host=https://nope.invalid/'); jvmArgs.push('-Dminecraft.api.account.host=https://nope.invalid/'); jvmArgs.push('-Dminecraft.api.session.host=https://nope.invalid/'); jvmArgs.push('-Dminecraft.api.services.host=https://nope.invalid/'); } // If natives are specified, add the native library path if (versionJson.nativesList) { jvmArgs.push(`-Djava.library.path=${this.options.path}/versions/${versionJson.id}/natives`); } // Special handling for macOS (setting dock icon) if (os_1.default.platform() === 'darwin') { const assetsPath = `${this.options.path}/assets/indexes/${versionJson.assets}.json`; const assetsContent = fs_1.default.readFileSync(assetsPath, 'utf-8'); const assetsJson = JSON.parse(assetsContent); // Retrieve the hash of the minecraft.icns file const iconHash = assetsJson.objects['icons/minecraft.icns']?.hash; if (iconHash) { jvmArgs.push('-Xdock:name=Minecraft'); jvmArgs.push(`-Xdock:icon=${this.options.path}/assets/objects/${iconHash.substring(0, 2)}/${iconHash}`); } } if (versionJson.logging && versionJson.logging.client && !this.options.ignore_log4j) { const logConfig = versionJson.logging.client; const logConfigPath = `${this.options.path}/assets/log_configs/${logConfig.file.id}`; jvmArgs.push(logConfig.argument.replace('${path}', logConfigPath)); } // Process and add default-user-jvm arguments from version JSON // These are Mojang's recommended JVM arguments for this version // Pass existing jvmArgs to avoid duplicates with hardcoded arguments const defaultUserJVMArgs = this.ProcessDefaultUserJVMArgs(versionJson, jvmArgs); jvmArgs.push(...defaultUserJVMArgs); // Append any user-supplied JVM arguments // User arguments come last so they can override defaults jvmArgs.push(...this.options.JVM_ARGS); return jvmArgs; } /** * Constructs the classpath (including libraries) that Minecraft requires * to launch, and identifies the main class. Optionally merges loader libraries. * @param versionJson The Minecraft version JSON. * @param loaderJson The loader JSON (e.g., Forge, Fabric) if applicable. */ async GetClassPath(versionJson, loaderJson) { let combinedLibraries = versionJson.libraries ?? []; // If a loader JSON is provided, merge its libraries with the base MC version if (loaderJson?.libraries) { combinedLibraries = loaderJson.libraries.concat(combinedLibraries); } const map = new Map(); for (const dep of combinedLibraries) { const parts = (0, Index_js_1.getPathLibraries)(dep.name); const version = semver_1.default.valid(semver_1.default.coerce(parts.version)); if (!version) continue; const pathParts = parts.path.split('/'); const basePath = pathParts.slice(0, -1).join('/'); const key = `${basePath}/${parts.name.replace(`-${parts.version}`, '')}`; const current = map.get(key); const currentVersion = semver_1.default.valid(semver_1.default.coerce(this.options.version)); const isSupportedVersion = currentVersion ? semver_1.default.satisfies(currentVersion, '1.14.4 - 1.18.2') : false; const isWindows = process.platform === 'win32'; if (!current || semver_1.default.gt(version, current.version) && (isSupportedVersion && isWindows)) { map.set(key, { ...dep, version }); } } const latest = Object.fromEntries(Array.from(map.entries()).map(([key, value]) => [key, value])); const librariesList = []; for (const lib of Object.values(latest)) { // Skip certain logging libraries if flagged (e.g., in Forge's "loader" property) if (lib.loader && lib.name.startsWith('org.apache.logging.log4j:log4j-slf4j2-impl')) continue; // Check if the library has native bindings if (lib.natives) { const nativeName = lib.natives[MOJANG_LIBRARY_MAP[process.platform]] || lib.natives[process.platform]; if (!nativeName) continue; } else if (lib.rules && lib.rules[0].os) { // Some libraries only apply to specific OS platforms if (lib.rules[0].os.name !== MOJANG_LIBRARY_MAP[process.platform]) continue; } // Build the path for this library const libPath = (0, Index_js_1.getPathLibraries)(lib.name); if (lib.loader) { // If the loader uses a specific library path librariesList.push(`${lib.loader}/libraries/${libPath.path}/${libPath.name}`); } else { librariesList.push(`${this.options.path}/libraries/${libPath.path}/${libPath.name}`); } } // Add the main Minecraft JAR (or special jar if using old Forge or MCP) if (loaderJson?.isOldForge && loaderJson.jarPath) { librariesList.push(loaderJson.jarPath); } else if (this.options.mcp) { librariesList.push(this.options.mcp); } else { librariesList.push(`${this.options.path}/versions/${versionJson.id}/${versionJson.id}.jar`); } // Filter out duplicates in the final library paths const uniquePaths = []; for (const libPath of librariesList) { // We only check if we've already used the exact file name const fileName = libPath.split('/').pop(); if (fileName && !uniquePaths.includes(fileName)) { uniquePaths.push(libPath); } } // The final classpath argument is OS-dependent (':' on Unix, ';' on Windows) const cpSeparator = process.platform === 'win32' ? ';' : ':'; const cpArgument = uniquePaths.length > 0 ? uniquePaths.join(cpSeparator) : ''; return { classpath: ['-cp', cpArgument], mainClass: loaderJson ? loaderJson.mainClass : versionJson.mainClass }; } } exports.default = MinecraftArguments; //# sourceMappingURL=Minecraft-Arguments.js.map