diff --git a/src/model/forge/versionmanifest113.ts b/src/model/forge/versionmanifest113.ts new file mode 100644 index 0000000..97b8de1 --- /dev/null +++ b/src/model/forge/versionmanifest113.ts @@ -0,0 +1,25 @@ +export interface VersionManifest113 { + + id: string + time: string + releaseTime: string + type: string + mainClass: string + inheritsFrom: string + logging: any + arguments: { + game: string[] + } + libraries: Array<{ + name: string, + downloads: { + artifact: { + path: string, + url: string, + sha1: string, + size: number + } + } + }> + +} diff --git a/src/model/forge/versionmanifest.ts b/src/model/forge/versionmanifest17.ts similarity index 91% rename from src/model/forge/versionmanifest.ts rename to src/model/forge/versionmanifest17.ts index b029c16..3e39f21 100644 --- a/src/model/forge/versionmanifest.ts +++ b/src/model/forge/versionmanifest17.ts @@ -1,4 +1,4 @@ -export interface VersionManifest { +export interface VersionManifest17 { id: string time: string diff --git a/src/model/struct/repo/librepo.struct.ts b/src/model/struct/repo/librepo.struct.ts index f0fa629..7d2cac9 100644 --- a/src/model/struct/repo/librepo.struct.ts +++ b/src/model/struct/repo/librepo.struct.ts @@ -2,6 +2,9 @@ import { BaseMavenRepo } from './BaseMavenRepo' export class LibRepoStructure extends BaseMavenRepo { + public static readonly MINECRAFT_GROUP = 'net.minecraft' + public static readonly MINECRAFT_CLIENT_ARTIFACT = 'client' + constructor( absoluteRoot: string, relativeRoot: string diff --git a/src/model/struct/repo/repo.struct.ts b/src/model/struct/repo/repo.struct.ts index eb6d5c4..bc43bbe 100644 --- a/src/model/struct/repo/repo.struct.ts +++ b/src/model/struct/repo/repo.struct.ts @@ -3,12 +3,14 @@ import { BaseFileStructure } from '../BaseFileStructure' import { ForgeRepoStructure } from './forgerepo.struct' import { LibRepoStructure } from './librepo.struct' import { LiteLoaderRepoStructure } from './liteloaderrepo.struct' +import { VersionRepoStructure } from './versionrepo.struct' export class RepoStructure extends BaseFileStructure { private forgeRepoStruct: ForgeRepoStructure private liteloaderRepoStruct: LiteLoaderRepoStructure private libRepoStruct: LibRepoStructure + private versionRepoStruct: VersionRepoStructure constructor( absoluteRoot: string, @@ -18,6 +20,7 @@ export class RepoStructure extends BaseFileStructure { this.forgeRepoStruct = new ForgeRepoStructure(this.containerDirectory, this.relativeRoot) this.liteloaderRepoStruct = new LiteLoaderRepoStructure(this.containerDirectory, this.relativeRoot) this.libRepoStruct = new LibRepoStructure(this.containerDirectory, this.relativeRoot) + this.versionRepoStruct = new VersionRepoStructure(this.containerDirectory, this.relativeRoot) } public async init() { @@ -25,6 +28,7 @@ export class RepoStructure extends BaseFileStructure { await this.forgeRepoStruct.init() await this.liteloaderRepoStruct.init() await this.libRepoStruct.init() + await this.versionRepoStruct.init() } public getForgeRepoStruct() { @@ -39,6 +43,10 @@ export class RepoStructure extends BaseFileStructure { return this.libRepoStruct } + public getVersionRepoStruct() { + return this.versionRepoStruct + } + public getTempDirectory() { return join(this.absoluteRoot, 'temp') } diff --git a/src/model/struct/repo/versionrepo.struct.ts b/src/model/struct/repo/versionrepo.struct.ts new file mode 100644 index 0000000..08d6b70 --- /dev/null +++ b/src/model/struct/repo/versionrepo.struct.ts @@ -0,0 +1,28 @@ +import { join } from 'path' +import { resolve as resolveURL } from 'url' +import { BaseFileStructure } from '../BaseFileStructure' + +export class VersionRepoStructure extends BaseFileStructure { + + constructor( + absoluteRoot: string, + relativeRoot: string + ) { + super(absoluteRoot, relativeRoot, 'versions') + } + + public getFileName(minecraftVersion: string, forgeVersion: string) { + return `${minecraftVersion}-forge-${forgeVersion}` + } + + public getVersionManifest(minecraftVersion: string, forgeVersion: string) { + const fileName = this.getFileName(minecraftVersion, forgeVersion) + return join(this.containerDirectory, fileName, `${fileName}.json`) + } + + public getVersionManifestURL(url: string, minecraftVersion: string, forgeVersion: string) { + const fileName = this.getFileName(minecraftVersion, forgeVersion) + return resolveURL(url, join(this.relativeRoot, fileName, `${fileName}.json`)) + } + +} diff --git a/src/resolver/ResolverRegistry.ts b/src/resolver/ResolverRegistry.ts index a6bcb7b..1550d40 100644 --- a/src/resolver/ResolverRegistry.ts +++ b/src/resolver/ResolverRegistry.ts @@ -1,11 +1,11 @@ import { Forge113Adapter } from './forge/adapter/forge113.resolver' -import { Forge18Adapter } from './forge/adapter/forge18.resolver' +import { Forge17Adapter } from './forge/adapter/forge17.resolver' import { ForgeResolver } from './forge/forge.resolver' export class ResolverRegistry { public static readonly FORGE_ADAPTER_IMPL = [ - Forge18Adapter, + Forge17Adapter, Forge113Adapter ] diff --git a/src/resolver/forge/adapter/forge113.resolver.ts b/src/resolver/forge/adapter/forge113.resolver.ts index fc8abc4..9cf3336 100644 --- a/src/resolver/forge/adapter/forge113.resolver.ts +++ b/src/resolver/forge/adapter/forge113.resolver.ts @@ -1,9 +1,13 @@ import { spawn } from 'child_process' -import { copy, mkdirs, pathExists, remove, writeFile } from 'fs-extra' -import { basename, join } from 'path' +import { copy, lstat, mkdirs, move, pathExists, readFile, remove, writeFile } from 'fs-extra' +import { basename, dirname, join } from 'path' +import { VersionManifest113 } from '../../../model/forge/versionmanifest113' import { Module } from '../../../model/spec/module' +import { Type } from '../../../model/spec/type' import { ForgeRepoStructure } from '../../../model/struct/repo/forgerepo.struct' +import { LibRepoStructure } from '../../../model/struct/repo/librepo.struct' import { JavaUtil } from '../../../util/javautil' +import { MavenUtil } from '../../../util/maven' import { ForgeResolver } from '../forge.resolver' export class Forge113Adapter extends ForgeResolver { @@ -23,8 +27,7 @@ export class Forge113Adapter extends ForgeResolver { } public async getModule(): Promise { - await this.process() - return {} as unknown as Module + return this.process() } public isForVersion(version: string): boolean { @@ -64,6 +67,226 @@ export class Forge113Adapter extends ForgeResolver { console.debug(`Spawning forge installer`) + console.log('============== [ IMPORTANT ] ==============') + console.log('When the installer opens please set the client installation directory to:') + console.log(workDir) + console.log('===========================================') + + await this.executeInstaller(workingInstaller) + + console.debug('Installer finished, beginning processing..') + + console.debug('Processing Version Manifest') + const versionManifestTuple = await this.processVersionManifest() + const versionManifest = versionManifestTuple[0] as VersionManifest113 + + console.debug('Processing generated forge files.') + const forgeModule = await this.processForgeModule() + + // Attach version.json module. + forgeModule.subModules?.unshift(versionManifestTuple[1] as Module) + + console.debug('Processing Libraries') + const libs = await this.processLibraries(versionManifest) + + forgeModule.subModules = forgeModule.subModules?.concat(libs) + + await remove(workDir) + + return forgeModule + } + + private async processLibraries(manifest: VersionManifest113) { + + const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries') + const libRepo = this.repoStructure.getLibRepoStruct() + + const mdls: Module[] = [] + + for (const entry of manifest.libraries) { + const artifact = entry.downloads.artifact + if (artifact.url) { + + const targetLocalPath = join(libDir, artifact.path) + + if (!await pathExists(targetLocalPath)) { + throw new Error(`Expected library ${entry.name} not found!`) + } + + const components = MavenUtil.getMavenComponents(entry.name) + + mdls.push({ + id: entry.name, + name: `Minecraft Forge (${components.artifact})`, + type: Type.Library, + artifact: this.generateArtifact( + await readFile(targetLocalPath), + await lstat(targetLocalPath), + libRepo.getArtifactUrlByComponents( + this.baseUrl, + components.group, + components.artifact, + components.version, + components.classifier, + components.extension + ) + ) + }) + + const destination = libRepo.getArtifactByComponents( + components.group, + components.artifact, + components.version, + components.classifier, + components.extension + ) + + await move(targetLocalPath, destination, {overwrite: true}) + + } + } + + return mdls + + } + + private async processForgeModule() { + + const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries') + + const generatedFiles = [ + { + name: 'base jar', + group: ForgeRepoStructure.FORGE_GROUP, + artifact: ForgeRepoStructure.FORGE_ARTIFACT, + version: this.artifactVersion, + classifier: undefined + }, + { + name: 'universal jar', + group: ForgeRepoStructure.FORGE_GROUP, + artifact: ForgeRepoStructure.FORGE_ARTIFACT, + version: this.artifactVersion, + classifier: 'universal' + }, + { + name: 'client jar', + group: ForgeRepoStructure.FORGE_GROUP, + artifact: ForgeRepoStructure.FORGE_ARTIFACT, + version: this.artifactVersion, + classifier: 'client' + }, + { + name: 'client slim', + group: LibRepoStructure.MINECRAFT_GROUP, + artifact: LibRepoStructure.MINECRAFT_CLIENT_ARTIFACT, + version: this.minecraftVersion, + classifier: 'slim' + }, + { + name: 'client data', + group: LibRepoStructure.MINECRAFT_GROUP, + artifact: LibRepoStructure.MINECRAFT_CLIENT_ARTIFACT, + version: this.minecraftVersion, + classifier: 'data', + skipIfNotPresent: true + }, + { + name: 'client extra', + group: LibRepoStructure.MINECRAFT_GROUP, + artifact: LibRepoStructure.MINECRAFT_CLIENT_ARTIFACT, + version: this.minecraftVersion, + classifier: 'extra' + } + ] + + const mdls: Module[] = [] + + for (const entry of generatedFiles) { + + const targetLocalPath = join( + libDir, + MavenUtil.mavenComponentsToPath(entry.group, entry.artifact, entry.version, entry.classifier) + ) + + const exists = await pathExists(targetLocalPath) + if (exists) { + + mdls.push({ + id: MavenUtil.mavenComponentsToIdentifier( + entry.group, + entry.artifact, + entry.version, + entry.classifier + ), + name: `Minecraft Forge (${entry.name})`, + type: Type.Library, + artifact: this.generateArtifact( + await readFile(targetLocalPath), + await lstat(targetLocalPath), + this.repoStructure.getLibRepoStruct().getArtifactUrlByComponents( + this.baseUrl, + entry.group, + entry.artifact, + entry.version, + entry.classifier + ) + ), + subModules: [] + }) + + const destination = this.repoStructure.getLibRepoStruct().getArtifactByComponents( + entry.group, + entry.artifact, + entry.version, + entry.classifier + ) + + await move(targetLocalPath, destination, {overwrite: true}) + + } else { + if (!entry.skipIfNotPresent) { + throw new Error(`Required file ${entry.name} not found at expected location ${targetLocalPath}!`) + } + } + + } + + const forgeModule = mdls.shift() as Module + forgeModule.type = Type.ForgeHosted + forgeModule.subModules = mdls + + return forgeModule + } + + private async processVersionManifest() { + const workDir = this.repoStructure.getWorkDirectory() + const versionRepo = this.repoStructure.getVersionRepoStruct() + const versionName = versionRepo.getFileName(this.minecraftVersion, this.forgeVersion) + const versionManifestPath = join(workDir, 'versions', versionName, `${versionName}.json`) + + const versionManifestBuf = await readFile(versionManifestPath) + const versionManifest = JSON.parse(versionManifestBuf.toString()) as VersionManifest113 + + const versionManifestModule: Module = { + id: this.artifactVersion, + name: 'Minecraft Forge (version.json)', + type: Type.VersionManifest, + artifact: this.generateArtifact( + versionManifestBuf, + await lstat(versionManifestPath), + versionRepo.getVersionManifestURL(this.baseUrl, this.minecraftVersion, this.forgeVersion) + ) + } + + const destination = this.repoStructure.getVersionRepoStruct().getVersionManifest( + this.minecraftVersion, + this.forgeVersion + ) + + await move(versionManifestPath, destination, {overwrite: true}) + + return [versionManifest, versionManifestModule] } private executeInstaller(installerExec: string) { @@ -71,9 +294,11 @@ export class Forge113Adapter extends ForgeResolver { const child = spawn(JavaUtil.getJavaExecutable(), [ '-jar', installerExec - ]) - child.stdout.on('data', (data) => console.log('[Forge Installer]', data.toString('utf8'))) - child.stderr.on('data', (data) => console.error('[Forge Installer]', data.toString('utf8'))) + ], { + cwd: dirname(installerExec) + }) + child.stdout.on('data', (data) => console.log('[Forge Installer]', data.toString('utf8').trim())) + child.stderr.on('data', (data) => console.error('[Forge Installer]', data.toString('utf8').trim())) child.on('close', (code, signal) => { console.log('[Forge Installer]', 'Exited with code', code) resolve() diff --git a/src/resolver/forge/adapter/forge18.resolver.ts b/src/resolver/forge/adapter/forge17.resolver.ts similarity index 92% rename from src/resolver/forge/adapter/forge18.resolver.ts rename to src/resolver/forge/adapter/forge17.resolver.ts index 37adbd9..881fa45 100644 --- a/src/resolver/forge/adapter/forge18.resolver.ts +++ b/src/resolver/forge/adapter/forge17.resolver.ts @@ -1,9 +1,8 @@ import AdmZip from 'adm-zip' import { createHash } from 'crypto' -import { copy, lstat, mkdirs, pathExists, readFile, remove, Stats } from 'fs-extra' +import { copy, lstat, mkdirs, pathExists, readFile, remove } from 'fs-extra' import { basename, join } from 'path' -import { VersionManifest } from '../../../model/forge/versionmanifest' -import { Artifact } from '../../../model/spec/artifact' +import { VersionManifest17 } from '../../../model/forge/versionmanifest17' import { Module } from '../../../model/spec/module' import { Type } from '../../../model/spec/type' import { ForgeRepoStructure } from '../../../model/struct/repo/forgerepo.struct' @@ -11,10 +10,10 @@ import { MavenUtil } from '../../../util/maven' import { PackXZExtractWrapper } from '../../../util/PackXZExtractWrapper' import { ForgeResolver } from '../forge.resolver' -export class Forge18Adapter extends ForgeResolver { +export class Forge17Adapter extends ForgeResolver { public static isForVersion(version: string) { - return Forge18Adapter.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12]) + return Forge17Adapter.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12]) } constructor( @@ -32,7 +31,7 @@ export class Forge18Adapter extends ForgeResolver { } public isForVersion(version: string) { - return Forge18Adapter.isForVersion(version) + return Forge17Adapter.isForVersion(version) } public async getForgeByVersion() { @@ -68,7 +67,7 @@ export class Forge18Adapter extends ForgeResolver { throw new Error('Failed to find version.json in forge universal jar.') } - versionManifest = JSON.parse(versionManifest) as VersionManifest + versionManifest = JSON.parse(versionManifest) as VersionManifest17 const forgeModule: Module = { id: MavenUtil.mavenComponentsToIdentifier( @@ -177,14 +176,6 @@ export class Forge18Adapter extends ForgeResolver { return forgeModule } - private generateArtifact(buf: Buffer, stats: Stats, url: string): Artifact { - return { - size: stats.size, - MD5: createHash('md5').update(buf).digest('hex'), - url - } - } - private determineExtension(checksums: string[] | undefined) { return checksums != null && checksums.length > 1 ? 'jar.pack.xz' : 'jar' } diff --git a/src/resolver/forge/forge.resolver.ts b/src/resolver/forge/forge.resolver.ts index 41343c5..8938f51 100644 --- a/src/resolver/forge/forge.resolver.ts +++ b/src/resolver/forge/forge.resolver.ts @@ -1,3 +1,6 @@ +import { createHash } from 'crypto' +import { Stats } from 'fs-extra' +import { Artifact } from '../../model/spec/artifact' import { RepoStructure } from '../../model/struct/repo/repo.struct' import { BaseResolver } from '../baseresolver' @@ -68,4 +71,12 @@ export abstract class ForgeResolver extends BaseResolver { return version } + protected generateArtifact(buf: Buffer, stats: Stats, url: string): Artifact { + return { + size: stats.size, + MD5: createHash('md5').update(buf).digest('hex'), + url + } + } + } diff --git a/src/util/PackXZExtractWrapper.ts b/src/util/PackXZExtractWrapper.ts index 69164fb..60696dd 100644 --- a/src/util/PackXZExtractWrapper.ts +++ b/src/util/PackXZExtractWrapper.ts @@ -28,8 +28,8 @@ export class PackXZExtractWrapper { command, paths.join(',') ]) - child.stdout.on('data', (data) => console.log('[PackXZExtract]', data.toString('utf8'))) - child.stderr.on('data', (data) => console.error('[PackXZExtract]', data.toString('utf8'))) + child.stdout.on('data', (data) => console.log('[PackXZExtract]', data.toString('utf8').trim())) + child.stderr.on('data', (data) => console.error('[PackXZExtract]', data.toString('utf8').trim())) child.on('close', (code, signal) => { console.log('[PackXZExtract]', 'Exited with code', code) resolve()