import { MinecraftFolder , MinecraftLocation } from "./folder" ; import { Platform , getPlatform } from "./platform" ; import { readFile } from "./utils" ; import { extname } from "path" ; interface PartialResolvedVersion extends Version { libraries : ResolvedLibrary [ ] ; arguments : { game : Version . LaunchArgument [ ] ; jvm : Version . LaunchArgument [ ] ; } ; minecraftDirectory : string ; } ; /**
 * The resolved version for launcher.
 * It could be a combination of multiple versions as there might be some inheritions.
 *
 * You can get resolved version of a Minecraft by calling  Version . parse .
 *
 *  see Version . parse export interface ResolvedVersion { /**
     * The id of the version, should be identical to the version folder.
     */ id : string ; arguments : { game : Version . LaunchArgument [ ] ; jvm : Version . LaunchArgument [ ] ; } ; /**
     * The main class full qualified name
     */ mainClass : string ; assetIndex : Version . AssetIndex ; /**
     * The asset index id of this version. Should be something like `1.14`, `1.12`
     */ assets : string ; downloads : { client : Version . Download ; server : Version . Download ; [ key ] : Version . Download ; } ; libraries : ResolvedLibrary [ ] ; minimumLauncherVersion : number ; releaseTime : string ; time : string ; type : string ; logging ? : { [ key ] : { file : Version . Download ; argument : string ; type : string ; } ; } ; /**
     * Recommended java version
     */ javaVersion : JavaVersion ; /**
     * The minecraft version of this version
     */ minecraftVersion : string ; /**
     * The minecraft directory of this version
     */ minecraftDirectory : string ; /**
     * The version inheritances of this whole resolved version.
     *
     * The first element is this version, and the last element is the root Minecraft version.
     * The dependencies of [<a>, <b>, <c>] should be <a> -> <b> -> <c>, where c is a Minecraft version.
     */ inheritances : string [ ] ; /**
     * All array of json file paths.
     *
     * It's the chain of inherits json path. The root json will be the last element of the array.
     * The first element is the user provided version.
     */ pathChain : string [ ] ; } see LibraryInfo . resolveFromPath LibraryInfo . resolve export interface LibraryInfo { readonly groupId : string ; readonly artifactId : string ; readonly version : string ; readonly isSnapshot : boolean ; /**
     * The file extension. Default is `jar`. Some files in forge are `zip`.
     */ readonly type : string ; /**
     * The classifier. Normally, this is empty. For forge, it can be like `universal`, `installer`.
     */ readonly classifier : string ; /**
     * The maven path.
     */ readonly path : string ; /**
     * The original maven name of this library
     */ readonly name : string ; } export interface BadVersionJsonError { error : "BadVersionJson" ; missing : "MainClass" | "AssetIndex" | "Downloads" ; version : string ; } export interface CorruptedVersionJsonError { error : "CorruptedVersionJson" ; version : string ; json : string ; } export interface MissingVersionJsonError { error : "MissingVersionJson" ; version : string ; path : string ; } export type VersionParseError = ( ( BadVersionJsonError | CorruptedVersionJsonError | MissingVersionJsonError ) & Error ) | Error ; export namespace LibraryInfo { param path export function resolveFromPath ( path ) { let parts = path . split ( "/" ) ; let file = parts [ parts . length - 1 ] ; let version = parts [ parts . length - 2 ] ; let artifactId = parts [ parts . length - 3 ] ; let groupId = parts . slice ( 0 , parts . length - 3 ) . join ( "." ) ; let filePrefix = `${ artifactId }-${ version }` ; let ext = extname ( file ) ; let type = ext . substring ( 1 ) ; let isSnapshot = file . startsWith ( version ) ; let classifier = file . substring ( isSnapshot ? version . length : filePrefix . length , file . length - ext . length ) ; if ( classifier . startsWith ( "-" ) ) { classifier = classifier . slice ( 1 ) ; } let name = `${ groupId }:${ artifactId }:${ version }` ; if ( classifier ) { name += `:${ classifier }` ; } if ( type !== "jar" ) { name += `@${ type }` ; } return { type , groupId , artifactId , version , classifier , name , path , isSnapshot , } ; } param lib export function resolve ( lib ) { const name = typeof lib === "string" ? lib : lib . name ; const [ body , type = "jar" ] = name . split ( "@" ) ; const [ groupId , artifactId , version , classifier = "" ] = body . split ( ":" ) ; const isSnapshot = version . endsWith ( "-SNAPSHOT" ) ; const groupPath = groupId . replace ( /\./g , "/" ) ; let base = `${ groupPath }/${ artifactId }/${ version }/${ artifactId }-${ version }` ; if ( classifier ) { base += `-${ classifier }` ; } const path = `${ base }.${ type }` ; return { type , groupId , artifactId , version , name , isSnapshot , classifier , path , } ; } } /**
 * A resolved library for launcher. It can by parsed from `LibraryInfo`.
 */ export class ResolvedLibrary implements LibraryInfo { readonly groupId : string ; readonly artifactId : string ; readonly version : string ; readonly isSnapshot : boolean ; readonly type : string ; readonly classifier : string ; readonly path : string ; constructor ( readonly name , info , readonly download , readonly checksums ? , readonly serverreq ? , readonly clientreq ? ) { const { groupId , artifactId , version , isSnapshot , type , classifier , path } = info ; this . groupId = groupId ; this . artifactId = artifactId ; this . version = version ; this . isSnapshot = isSnapshot ; this . type = type ; this . classifier = classifier ; this . path = path ; } } /**
 * Represent a native libraries provided by Minecraft
 */ export class ResolvedNative extends ResolvedLibrary { constructor ( name , info , download , readonly extractExclude ? ) { super ( name , info , download ) ; } } export namespace Version { export interface Download { readonly sha1 : string ; readonly size : number ; url : string ; } export interface AssetIndex extends Download { readonly id : string ; readonly totalSize : number ; } export interface Artifact extends Download { readonly path : string ; } export interface LoggingFile extends Download { readonly id : string ; } export interface NormalLibrary { name : string ; downloads : { artifact : Artifact ; } ; } export interface Rule { action : "allow" | "disallow" ; os ? : Partial < Platform > ; features ? : { [ feat ] : boolean ; } ; } export interface NativeLibrary { name : string ; downloads : { artifact : Artifact ; classifiers : { [ os ] : Artifact ; } ; } ; rules : Rule [ ] ; extract : { exclude : string [ ] ; } ; natives : { [ os ] : string ; } ; } export interface PlatformSpecificLibrary { name : string ; downloads : { artifact : Artifact ; } ; rules : Rule [ ] ; } export interface LegacyLibrary { name : string ; url ? : string ; clientreq ? : boolean ; serverreq ? : boolean ; checksums ? : string [ ] ; } export type Library = NormalLibrary | NativeLibrary | PlatformSpecificLibrary | LegacyLibrary ; export type LaunchArgument = string | { rules : Rule [ ] ; value : string | string [ ] ; } ; param rules param platform param features export function checkAllowed ( rules , platform = getPlatform ( ) , features = [ ] ) { if ( ! rules || rules . length === 0 ) { return true ; } let allow = false ; for ( const rule of rules ) { const action = rule . action === "allow" ; let apply = true ; if ( "os" in rule && rule . os ) { apply = false ; const osRule = rule . os ; if ( platform . name === osRule . name && ( ! osRule . version || platform . version . match ( osRule . version ) ) ) { apply = true ; } } if ( apply ) { if ( "features" in rule && rule . features ) { const featureRequire = rule . features ; apply = Object . entries ( featureRequire ) . every ( ( [ k , v ] ) => v ? features . indexOf ( k ) !== - 1 : features . indexOf ( k ) === - 1 ) ; } } if ( apply ) { allow = action ; } } return allow ; } param minecraftPath param version return throws CorruptedVersionJsonError throws MissingVersionJsonError throws BadVersionJsonError see VersionParseError export async function parse ( minecraftPath , version , platofrm = getPlatform ( ) ) { const folder = MinecraftFolder . from ( minecraftPath ) ; const hierarchy = await resolveDependency ( folder , version , platofrm ) ; return resolve ( minecraftPath , hierarchy ) ; } param minecraftPath param hierarchy throws BadVersionJsonError see VersionParseError see normalizeVersionJson see parse export function resolve ( minecraftPath , hierarchy ) { const folder = MinecraftFolder . from ( minecraftPath ) ; const rootVersion = hierarchy [ hierarchy . length - 1 ] ; const id = hierarchy [ 0 ] . id ; let assetIndex = rootVersion . assetIndex ! ; let assets = "" ; const downloadsMap = { } ; const librariesMap = { } ; const nativesMap = { } ; let mainClass = "" ; const args = { jvm : [ ] as LaunchArgument [ ] , game : [ ] as LaunchArgument [ ] } ; let minimumLauncherVersion = 0 ; let releaseTime = "" ; let time = "" ; let type = "" ; let logging ; let minecraftVersion = rootVersion . id ; let location ; let javaVersion = { majorVersion : 8 , component : "jre-legacy" } ; const chains = hierarchy . map ( ( j ) => folder . getVersionRoot ( j . id ) ) ; const inheritances = hierarchy . map ( ( j ) => j . id ) ; let json ; do { json = hierarchy . pop ( ) ! ; minimumLauncherVersion = Math . max ( json . minimumLauncherVersion || 0 , minimumLauncherVersion ) ; location = json . minecraftDirectory ; if ( ! Reflect . get ( json , "replace" ) ) { args . game . push ( ... json . arguments . game ) ; args . jvm . push ( ... json . arguments . jvm ) ; } else { args . game = json . arguments . game ; args . jvm = json . arguments . jvm ; } releaseTime = json . releaseTime || releaseTime ; time = json . time || time ; logging = json . logging || logging ; assets = json . assets || assets ; type = json . type || type ; mainClass = json . mainClass || mainClass ; assetIndex = json . assetIndex || assetIndex ; javaVersion = json . javaVersion || javaVersion ; if ( json . libraries ) { json . libraries . forEach ( ( lib ) => { const libOrgName = lib . name . substring ( 0 , lib . name . lastIndexOf ( ":" ) ) ; if ( lib instanceof ResolvedNative ) { nativesMap [ libOrgName ] = lib ; } else { librariesMap [ libOrgName ] = lib ; } } ) ; } if ( json . downloads ) { for ( const key in json . downloads ) { downloadsMap [ key ] = json . downloads [ key ] ; } } } while ( hierarchy . length !== 0 ) ; if ( ! mainClass ) { throw Object . assign ( new Error ( ) , { error : "BadVersionJson" , version : id , missing : "MainClass" , } as BadVersionJsonError ) ; } if ( ! assetIndex ) { throw Object . assign ( new Error ( ) , { error : "BadVersionJson" , version : id , missing : "AssetIndex" , } as BadVersionJsonError ) ; } if ( Object . keys ( downloadsMap ) . length === 0 ) { throw Object . assign ( new Error ( ) , { error : "BadVersionJson" , version : id , missing : "Downloads" , } as BadVersionJsonError ) ; } return { id , assetIndex , assets , minecraftVersion , inheritances , arguments : args , downloads : downloadsMap , libraries : Object . keys ( librariesMap ) . map ( ( k ) => librariesMap [ k ] ) . concat ( Object . keys ( nativesMap ) . map ( ( k ) => nativesMap [ k ] ) ) , mainClass , minimumLauncherVersion , releaseTime , time , type , logging , pathChain : chains , minecraftDirectory : location , javaVersion , } as ResolvedVersion ; } beta param id param parent param version return export function inherits ( id , parent , version ) { const launcherVersion = Math . max ( parent . minimumLauncherVersion , version . minimumLauncherVersion ) ; const libMap = { } ; parent . libraries . forEach ( ( l ) => { libMap [ l . name ] = l ; } ) ; const libraries = version . libraries . filter ( ( l ) => libMap [ l . name ] === undefined ) ; const result = { id , time : new Date ( ) . toISOString ( ) , releaseTime : new Date ( ) . toISOString ( ) , type : version . type , libraries , mainClass : version . mainClass , inheritsFrom : parent . id , minimumLauncherVersion : launcherVersion , } ; if ( typeof parent . minecraftArguments === "string" ) { if ( typeof version . arguments === "object" ) { throw new TypeError ( "Extends require two version in same format!" ) ; } result . minecraftArguments = mixinArgumentString ( parent . minecraftArguments , version . minecraftArguments || "" ) ; } else if ( typeof parent . arguments === "object" ) { if ( typeof version . minecraftArguments === "string" ) { throw new TypeError ( "Extends require two version in same format!" ) ; } result . arguments = version . arguments ; } return result ; } beta param hi param lo export function mixinArgumentString ( hi , lo ) { const arrA = hi . split ( " " ) ; const arrB = lo . split ( " " ) ; const args = { } ; for ( let i = 0 ; i < arrA . length ; i ++ ) { const element = arrA [ i ] ; if ( ! args [ element ] ) { args [ element ] = [ ] ; } if ( arrA [ i + 1 ] ) { args [ element ] . push ( arrA [ i += 1 ] ) ; } } for ( let i = 0 ; i < arrB . length ; i ++ ) { const element = arrB [ i ] ; if ( ! args [ element ] ) { args [ element ] = [ ] ; } if ( arrB [ i + 1 ] ) { args [ element ] . push ( arrB [ i += 1 ] ) ; } } const out = [ ] ; for ( const k of Object . keys ( args ) ) { switch ( k ) { case "--tweakClass" : const set = { } ; for ( const v of args [ k ] ) { set [ v ] = 0 ; } Object . keys ( set ) . forEach ( ( v ) => out . push ( k , v ) ) ; break ; default : if ( args [ k ] [ 0 ] ) { out . push ( k , args [ k ] [ 0 ] ) ; } break ; } } return out . join ( " " ) ; } param path param version returns throws CorruptedVersionJsonError throws MissingVersionJsonError export async function resolveDependency ( path , version , platform = getPlatform ( ) ) { const folder = MinecraftFolder . from ( path ) ; const stack = [ ] ; async function walk ( versionName ) { let jsonPath = folder . getVersionJson ( versionName ) ; let contentString ; try { contentString = await readFile ( jsonPath , "utf-8" ) ; } catch ( err ) { const e = err ; throw Object . assign ( new Error ( e . message ) , { error : "MissingVersionJson" , version : versionName , path : jsonPath , } as MissingVersionJsonError ) ; } let nextVersion ; try { let versionJson = normalizeVersionJson ( contentString , folder . root , platform ) ; stack . push ( versionJson ) ; nextVersion = versionJson . inheritsFrom ; } catch ( e ) { if ( e instanceof SyntaxError ) { throw Object . assign ( new Error ( e . message ) , { error : "CorruptedVersionJson" , version : versionName , json : contentString } as CorruptedVersionJsonError ) ; } throw e ; } if ( nextVersion ) { await walk ( nextVersion ) ; } } await walk ( version ) ; return stack ; } export function resolveLibrary ( lib , platform = getPlatform ( ) ) { if ( "rules" in lib && ! checkAllowed ( lib . rules , platform ) ) { return undefined ; } if ( "natives" in lib ) { if ( ! lib . natives [ platform . name ] ) { return undefined ; } const classifier = ( lib . natives [ platform . name ] ) . replace ( "${arch}" , platform . arch . substring ( 1 ) ) ; const nativeArtifact = lib . downloads . classifiers [ classifier ] ; if ( ! nativeArtifact ) { return undefined ; } return new ResolvedNative ( lib . name + ":" + classifier , LibraryInfo . resolve ( lib . name + ":" + classifier ) , nativeArtifact , lib . extract ? lib . extract . exclude ? lib . extract . exclude : undefined : undefined ) ; } const info = LibraryInfo . resolve ( lib . name ) ; if ( "downloads" in lib ) { if ( ! lib . downloads . artifact . url ) { lib . downloads . artifact . url = info . groupId === "net.minecraftforge" ? "https://files.minecraftforge.net/maven/" + lib . downloads . artifact . path : "https://libraries.minecraft.net/" + lib . downloads . artifact . path ; } return new ResolvedLibrary ( lib . name , info , lib . downloads . artifact ) ; } const maven = lib . url || "https://libraries.minecraft.net/" ; const artifact = { size : - 1 , sha1 : lib . checksums ? lib . checksums [ 0 ] : "" , path : info . path , url : maven + info . path , } ; return new ResolvedLibrary ( lib . name , info , artifact , lib . checksums , lib . serverreq , lib . clientreq ) ; } param libs param platform export function resolveLibraries ( libs , platform = getPlatform ( ) ) { return libs . map ( ( lib ) => resolveLibrary ( lib , platform ) ) . filter ( ( l ) => l !== undefined ) as ResolvedLibrary [ ] ; } param versionString param root export function normalizeVersionJson ( versionString , root , platform = getPlatform ( ) ) { function processArguments ( ar ) { return ar . filter ( ( a ) => { if ( typeof a === "object" && a . rules . every ( ( r ) => typeof r === "string" || ! ( "features" in r ) ) ) { return Version . checkAllowed ( a . rules , platform ) ; } return true ; } ) ; } ; let parsed = JSON . parse ( versionString ) ; let legacyVersionJson = ! parsed . arguments ; let libraries = Version . resolveLibraries ( parsed . libraries || [ ] , platform ) ; let args = { jvm : [ ] as Version . LaunchArgument [ ] , game : [ ] as Version . LaunchArgument [ ] , } ; if ( ! parsed . arguments ) { args . game = parsed . minecraftArguments ? parsed . minecraftArguments . split ( " " ) : [ ] ; args . jvm = [ { rules : [ { action : "allow" , os : { name : "windows" , } , } , ] , value : "-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump" , } , { rules : [ { action : "allow" , os : { name : "windows" , version : "^10\\." , } , } , ] , value : [ "-Dos.name=Windows 10" , "-Dos.version=10.0" , ] , } , "-Djava.library.path=${natives_directory}" , "-Dminecraft.launcher.brand=${launcher_name}" , "-Dminecraft.launcher.version=${launcher_version}" , "-cp" , "${classpath}" , ] ; } else { args . jvm = parsed . arguments . jvm || [ ] ; args . game = parsed . arguments . game || [ ] ; } args . jvm = processArguments ( args . jvm ) ; let partial = { ... parsed , libraries , arguments : args , minecraftDirectory : root , replace : legacyVersionJson , } ; return partial ; } } export interface JavaVersion { example component : string ; majorVersion : number ; } see Version . parse see launch export interface Version { id : string ; time : string ; type : string ; releaseTime : string ; inheritsFrom ? : string ; minimumLauncherVersion : number ; minecraftArguments ? : string ; arguments ? : { game : Version . LaunchArgument [ ] ; jvm : Version . LaunchArgument [ ] ; } ; mainClass : string ; libraries : Version . Library [ ] ; jar ? : string ; assetIndex ? : Version . AssetIndex ; assets ? : string ; downloads ? : { client : Version . Download ; server : Version . Download ; [ key ] : Version . Download ; } ; client ? : string ; server ? : string ; logging ? : { [ key ] : { file : Version . Download ; argument : string ; type : string ; } ; } ; javaVersion ? : JavaVersion ; }