import { LibraryInfo , MinecraftFolder , MinecraftLocation , Version as VersionJson } from "@xmcl/core" ; import { parse as parseForge } from "@xmcl/forge-site-parser" ; import { Task , task } from "@xmcl/task" ; import { filterEntries , open , openEntryReadStream , readEntry } from "@xmcl/unzip" ; import { createWriteStream } from "fs" ; import { dirname , join , posix , sep } from "path" ; import { Entry , ZipFile } from "yauzl" ; import { DownloadTask } from "./downloadTask" ; import { withAgents } from "./http/agents" ; import { getAndParseIfUpdate , Timestamped } from "./http/fetch" ; import { joinUrl } from "./http/utils" ; import { ZipValidator } from "./http/validator" ; import { LibraryOptions , resolveLibraryDownloadUrls } from "./minecraft" ; import { installByProfileTask , InstallProfile , InstallProfileOption } from "./profile" ; import { ensureFile , InstallOptions as InstallOptionsBase , normalizeArray , pipeline , writeFile } from "./utils" ; export interface ForgeVersionList extends Timestamped { mcversion : string ; versions : ForgeVersion [ ] ; } /**
 * The forge version metadata to download a forge
 */ export interface ForgeVersion { /**
     * The installer info
     */ installer : { md5 : string ; sha1 : string ; /**
         * The url path to concat with forge maven
         */ path : string ; } ; universal : { md5 : string ; sha1 : string ; /**
         * The url path to concat with forge maven
         */ path : string ; } ; /**
     * The minecraft version
     */ mcversion : string ; /**
     * The forge version (without minecraft version)
     */ version : string ; type : "buggy" | "recommended" | "common" | "latest" ; } /**
 * All the useful entries in forge installer jar
 */ export interface ForgeInstallerEntries { /**
     *  maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}.jar
     */ forgeJar ? : Entry ; /**
     *  maven/net/minecraftforge/forge/${forgeVersion}/forge-${forgeVersion}-universal.jar
     */ forgeUniversalJar ? : Entry ; /**
     * data/client.lzma
     */ clientLzma ? : Entry ; /**
     * data/server.lzma
     */ serverLzma ? : Entry ; /**
     * install_profile.json
     */ installProfileJson ? : Entry ; /**
     * version.json
     */ versionJson ? : Entry ; /**
     * forge-${forgeVersion}-universal.jar
     */ legacyUniversalJar ? : Entry ; /**
     * data/run.sh
     */ runSh ? : Entry ; /**
     * data/run.bat
     */ runBat ? : Entry ; /**
     * data/unix_args.txt
     */ unixArgs ? : Entry ; /**
     * data/user_jvm_args.txt
     */ userJvmArgs ? : Entry ; /**
     * data/win_args.txt
     */ winArgs ? : Entry ; } export type ForgeInstallerEntriesPattern = ForgeInstallerEntries & Required < Pick < ForgeInstallerEntries , "versionJson" | "installProfileJson" > > ; export type ForgeLegacyInstallerEntriesPattern = Required < Pick < ForgeInstallerEntries , "installProfileJson" | "legacyUniversalJar" > > ; type RequiredVersion = { /**
     * The installer info.
     *
     * If this is not presented, it will genreate from mcversion and forge version.
     */ installer ? : { sha1 ? : string ; /**
         * The url path to concat with forge maven
         */ path : string ; } ; /**
     * The minecraft version
     */ mcversion : string ; /**
     * The forge version (without minecraft version)
     */ version : string ; } ; export const DEFAULT_FORGE_MAVEN = "http://files.minecraftforge.net/maven" ; /**
 * The options to install forge.
 */ export interface InstallForgeOptions extends LibraryOptions , InstallOptionsBase , InstallProfileOption { } export class DownloadForgeInstallerTask extends DownloadTask { readonly installJarPath : string ; constructor ( forgeVersion , installer , minecraft , options ) { const path = installer ? installer . path : `net/minecraftforge/forge/${ forgeVersion }/forge-${ forgeVersion }-installer.jar` ; const forgeMavenPath = path . replace ( "/maven" , "" ) . replace ( "maven" , "" ) ; const library = VersionJson . resolveLibrary ( { name : `net.minecraftforge:forge:${ forgeVersion }:installer` , downloads : { artifact : { url : joinUrl ( DEFAULT_FORGE_MAVEN , forgeMavenPath ) , path : `net/minecraftforge/forge/${ forgeVersion }/forge-${ forgeVersion }-installer.jar` , size : - 1 , sha1 : installer ?. sha1 || "" , } } } ) ! ; const mavenHost = options . mavenHost ? [ ... normalizeArray ( options . mavenHost ) , DEFAULT_FORGE_MAVEN ] : [ DEFAULT_FORGE_MAVEN ] ; const urls = resolveLibraryDownloadUrls ( library , { ... options , mavenHost } ) ; const installJarPath = minecraft . getLibraryByPath ( library . path ) ; super ( { url : urls , destination : installJarPath , validator : installer ?. sha1 ? { hash : installer . sha1 , algorithm : "sha1" , } : new ZipValidator ( ) , agents : options . agents , segmentPolicy : options . segmentPolicy , retryHandler : options . retryHandler , } ) ; this . installJarPath = installJarPath ; this . name = "downloadInstaller" ; this . param = { version : forgeVersion } ; } } function getLibraryPathWithoutMaven ( mc , name ) { return mc . getLibraryByPath ( name . substring ( name . indexOf ( "/" ) + 1 ) ) ; } function extractEntryTo ( zip , e , dest ) { return openEntryReadStream ( zip , e ) . then ( ( stream ) => pipeline ( stream , createWriteStream ( dest ) ) ) ; } async function installLegacyForgeFromZip ( zip , entries , profile , mc , options ) { const versionJson = profile . versionInfo ! ; versionJson . id = options . versionId || versionJson . id ; versionJson . inheritsFrom = options . inheritsFrom || versionJson . inheritsFrom ; const rootPath = mc . getVersionRoot ( versionJson . id ) ; const versionJsonPath = join ( rootPath , `${ versionJson . id }.json` ) ; await ensureFile ( versionJsonPath ) ; const library = LibraryInfo . resolve ( versionJson . libraries . find ( ( l ) => l . name . startsWith ( "net.minecraftforge:forge" ) ) ! ) ; await Promise . all ( [ writeFile ( versionJsonPath , JSON . stringify ( versionJson , undefined , 4 ) ) , extractEntryTo ( zip , entries . legacyUniversalJar , mc . getLibraryByPath ( library . path ) ) , ] ) ; return versionJson . id ; } param zip param entries param forgeVersion param profile param mc returns async function unpackForgeInstaller ( zip , entries , forgeVersion , profile , mc , jarPath , options ) { const versionJson = await readEntry ( zip , entries . versionJson ) . then ( ( b ) => b . toString ( ) ) . then ( JSON . parse ) ; versionJson . id = options . versionId || versionJson . id ; versionJson . inheritsFrom = options . inheritsFrom || versionJson . inheritsFrom ; const rootPath = mc . getVersionRoot ( versionJson . id ) ; const versionJsonPath = join ( rootPath , `${ versionJson . id }.json` ) ; const installJsonPath = join ( rootPath , "install_profile.json" ) ; const dataRoot = dirname ( jarPath ) ; const unpackData = ( entry ) => { promises . push ( extractEntryTo ( zip , entry , join ( dataRoot , entry . fileName . substring ( "data/" . length ) ) ) ) ; } ; await ensureFile ( versionJsonPath ) ; const promises = [ ] ; if ( entries . forgeUniversalJar ) { promises . push ( extractEntryTo ( zip , entries . forgeUniversalJar , getLibraryPathWithoutMaven ( mc , entries . forgeUniversalJar . fileName ) ) ) ; } if ( ! profile . data ) { profile . data = { } ; } const installerMaven = `net.minecraftforge:forge:${ forgeVersion }:installer` ; profile . data . INSTALLER = { client : `[${ installerMaven }]` , server : `[${ installerMaven }]` , } ; if ( entries . serverLzma ) { const serverMaven = `net.minecraftforge:forge:${ forgeVersion }:serverdata@lzma` ; profile . data . BINPATCH . server = `[${ serverMaven }]` ; const serverBinPath = mc . getLibraryByPath ( LibraryInfo . resolve ( serverMaven ) . path ) ; await ensureFile ( serverBinPath ) ; promises . push ( extractEntryTo ( zip , entries . serverLzma , serverBinPath ) ) ; } if ( entries . clientLzma ) { const clientMaven = `net.minecraftforge:forge:${ forgeVersion }:clientdata@lzma` ; profile . data . BINPATCH . client = `[${ clientMaven }]` ; const clientBinPath = mc . getLibraryByPath ( LibraryInfo . resolve ( clientMaven ) . path ) ; await ensureFile ( clientBinPath ) ; promises . push ( extractEntryTo ( zip , entries . clientLzma , clientBinPath ) ) ; } if ( entries . forgeJar ) { promises . push ( extractEntryTo ( zip , entries . forgeJar , getLibraryPathWithoutMaven ( mc , entries . forgeJar . fileName ) ) ) ; } if ( entries . runBat ) { unpackData ( entries . runBat ) ; } if ( entries . runSh ) { unpackData ( entries . runSh ) ; } if ( entries . winArgs ) { unpackData ( entries . winArgs ) ; } if ( entries . unixArgs ) { unpackData ( entries . unixArgs ) ; } if ( entries . userJvmArgs ) { unpackData ( entries . userJvmArgs ) ; } promises . push ( writeFile ( installJsonPath , JSON . stringify ( profile ) ) , writeFile ( versionJsonPath , JSON . stringify ( versionJson ) ) ) ; await Promise . all ( promises ) ; return versionJson . id ; } export function isLegacyForgeInstallerEntries ( entries ) { return ! ! entries . legacyUniversalJar && ! ! entries . installProfileJson ; } export function isForgeInstallerEntries ( entries ) { return ! ! entries . installProfileJson && ! ! entries . versionJson ; } param zip param forgeVersion export async function walkForgeInstallerEntries ( zip , forgeVersion ) { const [ forgeJar , forgeUniversalJar , clientLzma , serverLzma , installProfileJson , versionJson , legacyUniversalJar , runSh , runBat , unixArgs , userJvmArgs , winArgs ] = await filterEntries ( zip , [ `maven/net/minecraftforge/forge/${ forgeVersion }/forge-${ forgeVersion }.jar` , `maven/net/minecraftforge/forge/${ forgeVersion }/forge-${ forgeVersion }-universal.jar` , "data/client.lzma" , "data/server.lzma" , "install_profile.json" , "version.json" , `forge-${ forgeVersion }-universal.jar` , "data/run.sh" , "data/run.bat" , "data/unix_args.txt" , "data/user_jvm_args.txt" , "data/win_args.txt" , ] ) ; return { forgeJar , forgeUniversalJar , clientLzma , serverLzma , installProfileJson , versionJson , legacyUniversalJar , runSh , runBat , unixArgs , userJvmArgs , winArgs , } ; } export class BadForgeInstallerJarError extends Error { error = "BadForgeInstallerJar" ; constructor ( public jarPath , /**
     * What entry in jar is missing
     */ public entry ? ) { super ( entry ? `Missing entry ${ entry } in forge installer jar: ${ jarPath }` : `Bad forge installer: ${ jarPath }` ) ; } } function installByInstallerTask ( version , minecraft , options ) { return task ( "installForge" , async function ( ) { function getForgeArtifactVersion ( ) { let [ _ , minor ] = version . mcversion . split ( "." ) ; let minorVersion = Number . parseInt ( minor ) ; if ( minorVersion >= 7 && minorVersion <= 8 ) { return `${ version . mcversion }-${ version . version }-${ version . mcversion }` ; } return `${ version . mcversion }-${ version . version }` ; } const forgeVersion = getForgeArtifactVersion ( ) ; const mc = MinecraftFolder . from ( minecraft ) ; return withAgents ( options , async ( options ) => { const jarPath = await this . yield ( new DownloadForgeInstallerTask ( forgeVersion , version . installer , mc , options ) . map ( function ( ) { return this . installJarPath ; } ) ) ; const zip = await open ( jarPath , { lazyEntries : true , autoClose : false } ) ; const entries = await walkForgeInstallerEntries ( zip , forgeVersion ) ; if ( ! entries . installProfileJson ) { throw new BadForgeInstallerJarError ( jarPath , "install_profile.json" ) ; } const profile = await readEntry ( zip , entries . installProfileJson ) . then ( ( b ) => b . toString ( ) ) . then ( JSON . parse ) ; if ( isForgeInstallerEntries ( entries ) ) { const versionId = await unpackForgeInstaller ( zip , entries , forgeVersion , profile , mc , jarPath , options ) ; await this . concat ( installByProfileTask ( profile , minecraft , options ) ) ; return versionId ; } else if ( isLegacyForgeInstallerEntries ( entries ) ) { return installLegacyForgeFromZip ( zip , entries , profile , mc , options ) ; } else { throw new BadForgeInstallerJarError ( jarPath ) ; } } ) ; } ) ; } param version returns throws BadForgeInstallerJarError export function installForge ( version , minecraft , options ? ) { return installForgeTask ( version , minecraft , options ) . startAndWait ( ) ; } param version returns throws BadForgeInstallerJarError export function installForgeTask ( version , minecraft , options = { } ) { return installByInstallerTask ( version , minecraft , options ) ; } param option export async function getForgeVersionList ( option = { } ) { const mcversion = option . mcversion || "" ; const url = mcversion === "" ? "http://files.minecraftforge.net/maven/net/minecraftforge/forge/index.html" : `http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_${ mcversion }.html` ; return getAndParseIfUpdate ( url , parseForge , option . original ) ; }