Support for 1.13+ Forge Resolution.
Tested versions: 1.13.2 TODO: Test 1.14.4, 1.15.1 TODO: Read mod metadata from mods.toml in 1.13+
This commit is contained in:
25
src/model/forge/versionmanifest113.ts
Normal file
25
src/model/forge/versionmanifest113.ts
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}>
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface VersionManifest {
|
||||
export interface VersionManifest17 {
|
||||
|
||||
id: string
|
||||
time: string
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
28
src/model/struct/repo/versionrepo.struct.ts
Normal file
28
src/model/struct/repo/versionrepo.struct.ts
Normal file
@@ -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`))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
|
||||
|
||||
@@ -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<Module> {
|
||||
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()
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user