import { MinecraftFolder , MinecraftLocation , ResolvedLibrary , ResolvedVersion , Version as VersionJson } from "@xmcl/core" ; import { task , Task } from "@xmcl/task" ; import { join } from "path" ; import { withAgents } from "./http/agents" ; import { DownloadTask } from "./downloadTask" ; import { DownloadBaseOptions } from "./http/download" ; import { getAndParseIfUpdate , Timestamped } from "./http/fetch" ; import { joinUrl } from "./http/utils" ; import { ensureDir , errorToString , normalizeArray , ParallelTaskOptions , readFile } from "./utils" ; import { JsonValidator , ZipValidator } from "./http/validator" ; /**
 * The function to swap library host.
 */ export type LibraryHost = ( library : ResolvedLibrary ) => string | string [ ] | undefined ; export interface MinecraftVersionBaseInfo { /**
     * The version id, like 1.14.4
     */ id : string ; /**
     * The version json download url
     */ url : string ; } /**
 * The version metadata containing the version information, like download url
 */ export interface MinecraftVersion extends MinecraftVersionBaseInfo { /**
     * The version id, like 1.14.4
     */ id : string ; type : string ; time : string ; releaseTime : string ; /**
     * The version json download url
     */ url : string ; } export interface AssetInfo { name : string ; hash : string ; size : number ; } /**
 * Minecraft version metadata list
 */ export interface MinecraftVersionList extends Timestamped { latest : { /**
         * Snapshot version id of the Minecraft
         */ snapshot : string ; /**
         * Release version id of the Minecraft, like 1.14.2
         */ release : string ; } ; /**
     * All the vesrsion list
     */ versions : MinecraftVersion [ ] ; } /**
 * Default minecraft version manifest url.
 */ export const DEFAULT_VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json" ; /**
 * Default resource/assets url root
 */ export const DEFAULT_RESOURCE_ROOT_URL = "https://resources.download.minecraft.net" ; returns export function getVersionList ( option = { } ) { return getAndParseIfUpdate ( option . remote || DEFAULT_VERSION_MANIFEST_URL , JSON . parse , option . original ) ; } /**
 * Change the library host url
 */ export interface LibraryOptions extends DownloadBaseOptions , ParallelTaskOptions { see mavenHost libraryHost ? : LibraryHost ; /**
     * The alterative maven host to download library. It will try to use these host from the `[0]` to the `[maven.length - 1]`
     */ mavenHost ? : string | string [ ] ; /**
     * Control how many libraries download task should run at the same time.
     * It will override the `maxConcurrencyOption` if this is presented.
     *
     * This will be ignored if you have your own downloader assigned.
     */ librariesDownloadConcurrency ? : number ; } /**
 * Change the host url of assets download
 */ export interface AssetsOptions extends DownloadBaseOptions , ParallelTaskOptions { /**
     * The alternative assets host to download asset. It will try to use these host from the `[0]` to the `[assetsHost.length - 1]`
     */ assetsHost ? : string | string [ ] ; /**
     * Control how many assets download task should run at the same time.
     * It will override the `maxConcurrencyOption` if this is presented.
     *
     * This will be ignored if you have your own downloader assigned.
     */ assetsDownloadConcurrency ? : number ; /**
     * The assets index download or url replacement
     */ assetsIndexUrl ? : string | string [ ] | ( ( version : ResolvedVersion ) => string | string [ ] ) ; } export type InstallLibraryVersion = Pick < ResolvedVersion , "libraries" | "minecraftDirectory" > ; function resolveDownloadUrls < T > ( original , version , option ? ) { let result = [ original ] ; if ( typeof option === "function" ) { result . unshift ( ... normalizeArray ( option ( version ) ) ) ; } else { result . unshift ( ... normalizeArray ( option ) ) ; } return result ; } /**
 * Replace the minecraft client or server jar download
 */ export interface JarOption extends DownloadBaseOptions , ParallelTaskOptions { /**
     * The version json url replacement
     */ json ? : string | string [ ] | ( ( version : MinecraftVersionBaseInfo ) => string | string [ ] ) ; /**
     * The client jar url replacement
     */ client ? : string | string [ ] | ( ( version : ResolvedVersion ) => string | string [ ] ) ; /**
     * The server jar url replacement
     */ server ? : string | string [ ] | ( ( version : ResolvedVersion ) => string | string [ ] ) ; } export interface InstallSideOption { /**
     * The installation side
     */ side ? : "client" | "server" ; } export type Options = DownloadBaseOptions & ParallelTaskOptions & AssetsOptions & JarOption & LibraryOptions & InstallSideOption ; param versionMeta param minecraft param option export async function install ( versionMeta , minecraft , option = { } ) { return installTask ( versionMeta , minecraft , option ) . startAndWait ( ) ; } param versionMeta param minecraft export function installVersion ( versionMeta , minecraft , options = { } ) { return installVersionTask ( versionMeta , minecraft , options ) . startAndWait ( ) ; } param version param minecraft export function installDependencies ( version , options ? ) { return installDependenciesTask ( version , options ) . startAndWait ( ) ; } param version param options export function installAssets ( version , options = { } ) { return installAssetsTask ( version , options ) . startAndWait ( ) ; } param version param options export function installLibraries ( version , options = { } ) { return installLibrariesTask ( version , options ) . startAndWait ( ) ; } param libraries param minecraft param option export async function installResolvedLibraries ( libraries , minecraft , option ? ) { await installLibrariesTask ( { libraries , minecraftDirectory : typeof minecraft === "string" ? minecraft : minecraft . root } , option ) . startAndWait ( ) ; } param type param versionMeta param minecraft param options export function installTask ( versionMeta , minecraft , options = { } ) { return task ( "install" , async function ( ) { return withAgents ( options , async ( options ) => { const version = await this . yield ( installVersionTask ( versionMeta , minecraft , options ) ) ; if ( options . side !== "server" ) { await this . yield ( installDependenciesTask ( version , options ) ) ; } return version ; } ) ; } ) ; } param type param versionMeta param minecraft export function installVersionTask ( versionMeta , minecraft , options = { } ) { return task ( "version" , async function ( ) { return withAgents ( options , async ( options ) => { await this . yield ( new InstallJsonTask ( versionMeta , minecraft , options ) ) ; const version = await VersionJson . parse ( minecraft , versionMeta . id ) ; await this . yield ( new InstallJarTask ( version , minecraft , options ) ) ; return version ; } ) ; } , versionMeta ) ; } param version param minecraft export function installDependenciesTask ( version , options = { } ) { return task ( "dependencies" , async function ( ) { await withAgents ( options , ( options ) => Promise . all ( [ this . yield ( installAssetsTask ( version , options ) ) , this . yield ( installLibrariesTask ( version , options ) ) , ] ) ) ; return version ; } ) ; } param version param options export function installAssetsTask ( version , options = { } ) { return task ( "assets" , async function ( ) { const folder = MinecraftFolder . from ( version . minecraftDirectory ) ; const jsonPath = folder . getPath ( "assets" , "indexes" , version . assets + ".json" ) ; await this . yield ( new InstallAssetIndexTask ( version , options ) ) ; await ensureDir ( folder . getPath ( "assets" , "objects" ) ) ; interface AssetIndex { objects : { [ key ] : { hash : string ; size : number ; } ; } ; } const { objects } = JSON . parse ( await readFile ( jsonPath ) . then ( ( b ) => b . toString ( ) ) ) as AssetIndex ; const objectArray = Object . keys ( objects ) . map ( ( k ) => ( { name : k , ... objects [ k ] } ) ) ; await withAgents ( options , ( options ) => this . all ( objectArray . map ( ( o ) => new InstallAssetTask ( o , folder , options ) ) , { throwErrorImmediately : options . throwErrorImmediately ?? false , getErrorMessage : ( errs ) => `Errors during install Minecraft ${ version . id }'s assets at ${ version . minecraftDirectory }: ${ errs . map ( errorToString ) . join ( "\n" ) }` } ) ) ; return version ; } ) ; } param version param options export function installLibrariesTask ( version , options = { } ) { return task ( "libraries" , async function ( ) { const folder = MinecraftFolder . from ( version . minecraftDirectory ) ; await withAgents ( options , ( options ) => this . all ( version . libraries . map ( ( lib ) => new InstallLibraryTask ( lib , folder , options ) ) , { throwErrorImmediately : options . throwErrorImmediately ?? false , getErrorMessage : ( errs ) => `Errors during install libraries at ${ version . minecraftDirectory }: ${ errs . map ( errorToString ) . join ( "\n" ) }` } ) ) ; } ) ; } param libraries param minecraft param option export function installResolvedLibrariesTask ( libraries , minecraft , option ? ) { return installLibrariesTask ( { libraries , minecraftDirectory : typeof minecraft === "string" ? minecraft : minecraft . root } , option ) ; } param assets param folder param options export function installResolvedAssetsTask ( assets , folder , options = { } ) { return task ( "assets" , async function ( ) { await ensureDir ( folder . getPath ( "assets" , "objects" ) ) ; await withAgents ( options , ( options ) => this . all ( assets . map ( ( o ) => new InstallAssetTask ( o , folder , options ) ) , { throwErrorImmediately : false , getErrorMessage : ( errs ) => `Errors during install assets at ${ folder . root }:\n${ errs . map ( errorToString ) . join ( "\n" ) }` , } ) ) ; } ) ; } export class InstallJsonTask extends DownloadTask { constructor ( version , minecraft , options ) { const folder = MinecraftFolder . from ( minecraft ) ; const destination = folder . getVersionJson ( version . id ) ; const expectSha1 = version . url . split ( "/" ) [ 5 ] ; const urls = resolveDownloadUrls ( version . url , version , options . json ) ; super ( { url : urls , agents : options . agents , segmentPolicy : options . segmentPolicy , retryHandler : options . retryHandler , validator : expectSha1 ? { algorithm : "sha1" , hash : expectSha1 } : new JsonValidator ( ) , destination , } ) ; this . name = "json" ; this . param = version ; } } export class InstallJarTask extends DownloadTask { constructor ( version , minecraft , options ) { const folder = MinecraftFolder . from ( minecraft ) ; const type = options . side ?? "client" ; const destination = join ( folder . getVersionRoot ( version . id ) , type === "client" ? version . id + ".jar" : version . id + "-" + type + ".jar" ) ; const urls = resolveDownloadUrls ( version . downloads [ type ] . url , version , options [ type ] ) ; const expectSha1 = version . downloads [ type ] . sha1 ; super ( { url : urls , validator : { algorithm : "sha1" , hash : expectSha1 } , destination , agents : options . agents , segmentPolicy : options . segmentPolicy , retryHandler : options . retryHandler , } ) ; this . name = "jar" ; this . param = version ; } } export class InstallAssetIndexTask extends DownloadTask { constructor ( version , options = { } ) { const folder = MinecraftFolder . from ( version . minecraftDirectory ) ; const jsonPath = folder . getPath ( "assets" , "indexes" , version . assets + ".json" ) ; super ( { url : resolveDownloadUrls ( version . assetIndex . url , version , options . assetsIndexUrl ) , destination : jsonPath , validator : { algorithm : "sha1" , hash : version . assetIndex . sha1 , } , agents : options . agents , segmentPolicy : options . segmentPolicy , retryHandler : options . retryHandler , } ) ; this . name = "assetIndex" ; this . param = version ; } } export class InstallLibraryTask extends DownloadTask { constructor ( lib , folder , options ) { const libraryPath = lib . download . path ; const destination = join ( folder . libraries , libraryPath ) ; const urls = resolveLibraryDownloadUrls ( lib , options ) ; super ( { url : urls , validator : lib . download . sha1 === "" ? new ZipValidator ( ) : { algorithm : "sha1" , hash : lib . download . sha1 , } , destination , agents : options . agents , segmentPolicy : options . segmentPolicy , retryHandler : options . retryHandler , } ) ; this . name = "library" ; this . param = lib ; } } export class InstallAssetTask extends DownloadTask { constructor ( asset , folder , options ) { const assetsHosts = [ ... normalizeArray ( options . assetsHost ) , DEFAULT_RESOURCE_ROOT_URL , ] ; const { hash , size , name } = asset ; const head = hash . substring ( 0 , 2 ) ; const dir = folder . getPath ( "assets" , "objects" , head ) ; const file = join ( dir , hash ) ; const urls = assetsHosts . map ( ( h ) => `${ h }/${ head }/${ hash }` ) ; super ( { url : urls , destination : file , validator : { hash , algorithm : "sha1" , } , agents : options . agents , segmentPolicy : options . segmentPolicy , retryHandler : options . retryHandler , } ) ; this . _total = size ; this . name = "asset" ; this . param = asset ; } } const DEFAULT_MAVENS = [ "https://repo1.maven.org/maven2/" ] ; param library param libraryOptions export function resolveLibraryDownloadUrls ( library , libraryOptions ) { const libraryHosts = libraryOptions . libraryHost ?. ( library ) ?? [ ] ; return [ ... normalizeArray ( libraryHosts ) , ... normalizeArray ( libraryOptions . mavenHost ) . map ( ( m ) => joinUrl ( m , library . download . path ) ) , library . download . url , ... DEFAULT_MAVENS . map ( ( m ) => joinUrl ( m , library . download . path ) ) , ] ; }