Add support for processing 1.12.2 Forge compiled with Forge Gradle 3 (#8).

Changed the resolver names to match the Forge Gradle versions, since that software version
determines the structure of the jar file and how it needs to be parsed.
Refactored the ForgeGradle3 resolver to conditionally execute the installer only when we
need artifacts generated by the installer. This allows the new 1.12.2 builds to be processed
without running the installer.
Changed the VersionSegemented interface to accept a libraryVersion, so we can segment within
a minecraft version.

This change is pending verification with Helios.
This commit is contained in:
Daniel Scalzi
2020-06-02 17:16:15 -04:00
parent 6e94883f23
commit 672424b973
14 changed files with 444 additions and 185 deletions

38
package-lock.json generated
View File

@@ -103,9 +103,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "12.12.42", "version": "12.12.43",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.42.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.43.tgz",
"integrity": "sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg==", "integrity": "sha512-KUyZdkGCnVPuXfsKmDUu2XLui65LZIJ2s0M57noy5e+ixUT2oK33ep7zlvgzI8LElcWqbf8AR+o/3GqAPac2zA==",
"dev": true "dev": true
}, },
"@types/yargs": { "@types/yargs": {
@@ -124,12 +124,12 @@
"dev": true "dev": true
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "3.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.1.0.tgz",
"integrity": "sha512-ER3bSS/A/pKQT/hjMGCK8UQzlL0yLjuCZ/G8CDFJFVTfl3X65fvq2lNYqOG8JPTfrPa2RULCdwfOyFjZEMNExQ==", "integrity": "sha512-D52KwdgkjYc+fmTZKW7CZpH5ZBJREJKZXRrveMiRCmlzZ+Rw9wRVJ1JAmHQ9b/+Ehy1ZeaylofDB9wwXUt83wg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/experimental-utils": "3.0.2", "@typescript-eslint/experimental-utils": "3.1.0",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0", "regexpp": "^3.0.0",
"semver": "^7.3.2", "semver": "^7.3.2",
@@ -137,33 +137,33 @@
} }
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "3.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.1.0.tgz",
"integrity": "sha512-4Wc4EczvoY183SSEnKgqAfkj1eLtRgBQ04AAeG+m4RhTVyaazxc1uI8IHf0qLmu7xXe9j1nn+UoDJjbmGmuqXQ==", "integrity": "sha512-Zf8JVC2K1svqPIk1CB/ehCiWPaERJBBokbMfNTNRczCbQSlQXaXtO/7OfYz9wZaecNvdSvVADt6/XQuIxhC79w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json-schema": "^7.0.3", "@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "3.0.2", "@typescript-eslint/typescript-estree": "3.1.0",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0" "eslint-utils": "^2.0.0"
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "3.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.0.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.1.0.tgz",
"integrity": "sha512-80Z7s83e8QXHNUspqVlWwb4t5gdz/1bBBmafElbK1wwAwiD/yvJsFyHRxlEpNrt4rdK6eB3p+2WEFkEDHAKk9w==", "integrity": "sha512-NcDSJK8qTA2tPfyGiPes9HtVKLbksmuYjlgGAUs7Ld2K0swdWibnCq9IJx9kJN8JJdgUJSorFiGaPHBgH81F/Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/eslint-visitor-keys": "^1.0.0", "@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "3.0.2", "@typescript-eslint/experimental-utils": "3.1.0",
"@typescript-eslint/typescript-estree": "3.0.2", "@typescript-eslint/typescript-estree": "3.1.0",
"eslint-visitor-keys": "^1.1.0" "eslint-visitor-keys": "^1.1.0"
} }
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "3.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.2.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.1.0.tgz",
"integrity": "sha512-cs84mxgC9zQ6viV8MEcigfIKQmKtBkZNDYf8Gru2M+MhnA6z9q0NFMZm2IEzKqAwN8lY5mFVd1Z8DiHj6zQ3Tw==", "integrity": "sha512-+4nfYauqeQvK55PgFrmBWFVYb6IskLyOosYEmhH3mSVhfBp9AIJnjExdgDmKWoOBHRcPM8Ihfm2BFpZf0euUZQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"debug": "^4.1.1", "debug": "^4.1.1",

View File

@@ -28,10 +28,10 @@
"devDependencies": { "devDependencies": {
"@types/adm-zip": "^0.4.33", "@types/adm-zip": "^0.4.33",
"@types/fs-extra": "^9.0.1", "@types/fs-extra": "^9.0.1",
"@types/node": "^12.12.42", "@types/node": "^12.12.43",
"@types/yargs": "^15.0.5", "@types/yargs": "^15.0.5",
"@typescript-eslint/eslint-plugin": "^3.0.2", "@typescript-eslint/eslint-plugin": "^3.1.0",
"@typescript-eslint/parser": "^3.0.2", "@typescript-eslint/parser": "^3.1.0",
"eslint": "^7.1.0", "eslint": "^7.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^3.9.3" "typescript": "^3.9.3"

View File

@@ -16,7 +16,7 @@ export abstract class BaseForgeModStructure extends ModuleStructure implements V
super(absoluteRoot, relativeRoot, 'forgemods', baseUrl, Type.ForgeMod) super(absoluteRoot, relativeRoot, 'forgemods', baseUrl, Type.ForgeMod)
} }
public abstract isForVersion(version: MinecraftVersion): boolean public abstract isForVersion(version: MinecraftVersion, libraryVersion: string): boolean
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> { protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {

View File

@@ -11,7 +11,8 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
public static readonly IMPLEMENTATION_VERSION_REGEX = /^Implementation-Version: (.+)[\r\n]/ public static readonly IMPLEMENTATION_VERSION_REGEX = /^Implementation-Version: (.+)[\r\n]/
public static isForVersion(version: MinecraftVersion): boolean { // eslint-disable-next-line @typescript-eslint/no-unused-vars
public static isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
return VersionUtil.isVersionAcceptable(version, [13, 14, 15]) return VersionUtil.isVersionAcceptable(version, [13, 14, 15])
} }
@@ -25,8 +26,8 @@ export class ForgeModStructure113 extends BaseForgeModStructure {
super(absoluteRoot, relativeRoot, baseUrl) super(absoluteRoot, relativeRoot, baseUrl)
} }
public isForVersion(version: MinecraftVersion): boolean { public isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
return ForgeModStructure113.isForVersion(version) return ForgeModStructure113.isForVersion(version, libraryVersion)
} }
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> { protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {

View File

@@ -9,7 +9,8 @@ import { MinecraftVersion } from '../../../../../util/MinecraftVersion'
export class ForgeModStructure17 extends BaseForgeModStructure { export class ForgeModStructure17 extends BaseForgeModStructure {
public static isForVersion(version: MinecraftVersion): boolean { // eslint-disable-next-line @typescript-eslint/no-unused-vars
public static isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12]) return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12])
} }
@@ -23,8 +24,8 @@ export class ForgeModStructure17 extends BaseForgeModStructure {
super(absoluteRoot, relativeRoot, baseUrl) super(absoluteRoot, relativeRoot, baseUrl)
} }
public isForVersion(version: MinecraftVersion): boolean { public isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
return ForgeModStructure17.isForVersion(version) return ForgeModStructure17.isForVersion(version, libraryVersion)
} }
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> { protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {

View File

@@ -50,6 +50,7 @@ export class ServerStructure extends BaseModelStructure<Server> {
if (options.forgeVersion != null) { if (options.forgeVersion != null) {
const fms = VersionSegmentedRegistry.getForgeModStruct( const fms = VersionSegmentedRegistry.getForgeModStruct(
minecraftVersion, minecraftVersion,
options.forgeVersion,
absoluteServerRoot, absoluteServerRoot,
relativeServerRoot, relativeServerRoot,
this.baseUrl this.baseUrl
@@ -123,6 +124,7 @@ export class ServerStructure extends BaseModelStructure<Server> {
const forgeModStruct = VersionSegmentedRegistry.getForgeModStruct( const forgeModStruct = VersionSegmentedRegistry.getForgeModStruct(
minecraftVersion, minecraftVersion,
serverMeta.forgeVersion,
absoluteServerRoot, absoluteServerRoot,
relativeServerRoot, relativeServerRoot,
this.baseUrl this.baseUrl

View File

@@ -50,20 +50,24 @@ export abstract class BaseMavenRepo extends BaseFileStructure {
private async downloadArtifactBase(url: string, relative: string): Promise<void> { private async downloadArtifactBase(url: string, relative: string): Promise<void> {
const resolvedURL = resolveURL(url, relative).toString() const resolvedURL = resolveURL(url, relative).toString()
console.debug(`Downloading ${resolvedURL}..`) return this.downloadArtifactDirect(resolvedURL, relative)
}
public async downloadArtifactDirect(url: string, path: string): Promise<void> {
console.debug(`Downloading ${url}..`)
const response = await axios({ const response = await axios({
method: 'get', method: 'get',
url: resolvedURL, url,
responseType: 'stream' responseType: 'stream'
}) })
const localPath = resolve(this.containerDirectory, relative) const localPath = resolve(this.containerDirectory, path)
await mkdirs(dirname(localPath)) await mkdirs(dirname(localPath))
const writer = createWriteStream(localPath) const writer = createWriteStream(localPath)
response.data.pipe(writer) response.data.pipe(writer)
// tslint:disable-next-line: no-shadowed-variable // tslint:disable-next-line: no-shadowed-variable
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
writer.on('finish', () => { writer.on('finish', () => {
console.debug(`Completed download of ${resolvedURL}.`) console.debug(`Completed download of ${url}.`)
resolve() resolve()
}) })
writer.on('error', reject) writer.on('error', reject)

View File

@@ -12,6 +12,6 @@ export abstract class BaseResolver implements Resolver, VersionSegmented {
) {} ) {}
public abstract getModule(): Promise<Module> public abstract getModule(): Promise<Module>
public abstract isForVersion(version: MinecraftVersion): boolean public abstract isForVersion(version: MinecraftVersion, libraryVersion: string): boolean
} }

View File

@@ -15,12 +15,13 @@ type ArrayElement<A> = A extends readonly (infer T)[] ? T : never
export class ForgeGradle2Adapter extends ForgeResolver { export class ForgeGradle2Adapter extends ForgeResolver {
public static isForVersion(version: MinecraftVersion): boolean { public static isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
if(version.getMinor() === 12 && !VersionUtil.isOneDotTwelveFG2(libraryVersion)) {
return false
}
return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12]) return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12])
} }
protected readonly MOJANG_REMOTE_REPOSITORY = 'https://libraries.minecraft.net/'
constructor( constructor(
absoluteRoot: string, absoluteRoot: string,
relativeRoot: string, relativeRoot: string,
@@ -35,8 +36,8 @@ export class ForgeGradle2Adapter extends ForgeResolver {
return this.getForgeByVersion() return this.getForgeByVersion()
} }
public isForVersion(version: MinecraftVersion): boolean { public isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
return ForgeGradle2Adapter.isForVersion(version) return ForgeGradle2Adapter.isForVersion(version, libraryVersion)
} }
public async getForgeByVersion(): Promise<Module> { public async getForgeByVersion(): Promise<Module> {

View File

@@ -1,20 +1,39 @@
import { spawn } from 'child_process' import AdmZip from 'adm-zip'
import { copy, lstat, mkdirs, move, pathExists, readFile, remove, writeFile } from 'fs-extra'
import { Module, Type } from 'helios-distribution-types'
import { basename, dirname, join } from 'path'
import { VersionManifestFG3 } from '../../../model/forge/VersionManifestFG3'
import { LibRepoStructure } from '../../../model/struct/repo/librepo.struct'
import { JavaUtil } from '../../../util/javautil'
import { MavenUtil } from '../../../util/maven'
import { VersionUtil } from '../../../util/versionutil'
import { ForgeResolver } from '../forge.resolver' import { ForgeResolver } from '../forge.resolver'
import { MinecraftVersion } from '../../../util/MinecraftVersion' import { MinecraftVersion } from '../../../util/MinecraftVersion'
import { VersionUtil } from '../../../util/versionutil'
import { Module, Type } from 'helios-distribution-types'
import { LibRepoStructure } from '../../../model/struct/repo/librepo.struct'
import { pathExists, remove, mkdirs, copy, writeFile, readFile, lstat, move, writeJson } from 'fs-extra'
import { join, basename, dirname } from 'path'
import { spawn } from 'child_process'
import { JavaUtil } from '../../../util/javautil'
import { VersionManifestFG3 } from '../../../model/forge/VersionManifestFG3'
import { MavenUtil } from '../../../util/maven'
import { createHash } from 'crypto'
interface GeneratedFile {
name: string
group: string
artifact: string
version: string
classifiers: string[] | [undefined]
skipIfNotPresent?: boolean
}
export class ForgeGradle3Adapter extends ForgeResolver { export class ForgeGradle3Adapter extends ForgeResolver {
public static isForVersion(version: MinecraftVersion): boolean { private static readonly WILDCARD_MCP_VERSION = '${mcpVersion}'
return VersionUtil.isVersionAcceptable(version, [13, 14, 15])
public static isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
if(version.getMinor() === 12 && VersionUtil.isOneDotTwelveFG2(libraryVersion)) {
return false
} }
return VersionUtil.isVersionAcceptable(version, [12, 13, 14, 15])
}
private generatedFiles: GeneratedFile[] | undefined
private wildcardsInUse: string[] | undefined
constructor( constructor(
absoluteRoot: string, absoluteRoot: string,
@@ -24,138 +43,13 @@ export class ForgeGradle3Adapter extends ForgeResolver {
forgeVersion: string forgeVersion: string
) { ) {
super(absoluteRoot, relativeRoot, baseUrl, minecraftVersion, forgeVersion) super(absoluteRoot, relativeRoot, baseUrl, minecraftVersion, forgeVersion)
this.configure()
} }
public async getModule(): Promise<Module> { private configure(): void {
return this.process() // Configure for 13, 14, 15
} if(VersionUtil.isVersionAcceptable(this.minecraftVersion, [13, 14, 15])) {
this.generatedFiles = [
public isForVersion(version: MinecraftVersion): boolean {
return ForgeGradle3Adapter.isForVersion(version)
}
private async process(): Promise<Module> {
const libRepo = this.repoStructure.getLibRepoStruct()
const installerPath = libRepo.getLocalForge(this.artifactVersion, 'installer')
console.debug(`Checking for forge installer at ${installerPath}..`)
if (!await libRepo.artifactExists(installerPath)) {
console.debug('Forge installer not found locally, initializing download..')
await libRepo.downloadArtifactByComponents(
this.REMOTE_REPOSITORY,
LibRepoStructure.FORGE_GROUP,
LibRepoStructure.FORGE_ARTIFACT,
this.artifactVersion, 'installer', 'jar'
)
} else {
console.debug('Using locally discovered forge installer.')
}
console.debug(`Beginning processing of Forge v${this.forgeVersion} (Minecraft ${this.minecraftVersion})`)
const workDir = this.repoStructure.getWorkDirectory()
if (await pathExists(workDir)) {
await remove(workDir)
}
await mkdirs(workDir)
const workingInstaller = join(workDir, basename(installerPath))
await copy(installerPath, workingInstaller)
// Required for the installer to function.
await writeFile(join(workDir, 'launcher_profiles.json'), JSON.stringify({}))
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 VersionManifestFG3
console.debug('Processing generated forge files.')
const forgeModule = await this.processForgeModule(versionManifest)
// 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: VersionManifestFG3): Promise<Module[]> {
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(versionManifest: VersionManifestFG3): Promise<Module> {
const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries')
const mcpVersion = this.getMCPVersion(versionManifest.arguments.game)
const generatedFiles = [
{ {
name: 'base jar', name: 'base jar',
group: LibRepoStructure.FORGE_GROUP, group: LibRepoStructure.FORGE_GROUP,
@@ -209,14 +103,166 @@ export class ForgeGradle3Adapter extends ForgeResolver {
name: 'client srg', name: 'client srg',
group: LibRepoStructure.MINECRAFT_GROUP, group: LibRepoStructure.MINECRAFT_GROUP,
artifact: LibRepoStructure.MINECRAFT_CLIENT_ARTIFACT, artifact: LibRepoStructure.MINECRAFT_CLIENT_ARTIFACT,
version: `${this.minecraftVersion}-${mcpVersion}`, version: `${this.minecraftVersion}-${ForgeGradle3Adapter.WILDCARD_MCP_VERSION}`,
classifiers: ['srg'] classifiers: ['srg']
} }
] ]
this.wildcardsInUse = [
ForgeGradle3Adapter.WILDCARD_MCP_VERSION
]
return
}
// Configure for 12
if(VersionUtil.isVersionAcceptable(this.minecraftVersion, [12])) {
// NOTHING TO CONFIGURE
return
}
}
public async getModule(): Promise<Module> {
return this.process()
}
public isForVersion(version: MinecraftVersion, libraryVersion: string): boolean {
return ForgeGradle3Adapter.isForVersion(version, libraryVersion)
}
private async process(): Promise<Module> {
const libRepo = this.repoStructure.getLibRepoStruct()
// Get Installer
const installerPath = libRepo.getLocalForge(this.artifactVersion, 'installer')
console.debug(`Checking for forge installer at ${installerPath}..`)
if (!await libRepo.artifactExists(installerPath)) {
console.debug('Forge installer not found locally, initializing download..')
await libRepo.downloadArtifactByComponents(
this.REMOTE_REPOSITORY,
LibRepoStructure.FORGE_GROUP,
LibRepoStructure.FORGE_ARTIFACT,
this.artifactVersion, 'installer', 'jar'
)
} else {
console.debug('Using locally discovered forge installer.')
}
console.debug(`Beginning processing of Forge v${this.forgeVersion} (Minecraft ${this.minecraftVersion})`)
if(this.generatedFiles != null && this.generatedFiles.length > 0) {
// Run installer
return this.processWithInstaller(installerPath)
} else {
// Installer not required
return this.processWithoutInstaller(installerPath)
}
}
private async processWithInstaller(installerPath: string): Promise<Module> {
const workDir = this.repoStructure.getWorkDirectory()
if (await pathExists(workDir)) {
await remove(workDir)
}
await mkdirs(workDir)
const workingInstaller = join(workDir, basename(installerPath))
await copy(installerPath, workingInstaller)
// Required for the installer to function.
await writeFile(join(workDir, 'launcher_profiles.json'), JSON.stringify({}))
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 VersionManifestFG3
console.debug('Processing generated forge files.')
const forgeModule = await this.processForgeModule(versionManifest)
// 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 processVersionManifest(): Promise<[VersionManifestFG3, Module]> {
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 VersionManifestFG3
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 = versionRepo.getVersionManifest(
this.minecraftVersion,
this.forgeVersion
)
await move(versionManifestPath, destination, {overwrite: true})
return [versionManifest, versionManifestModule]
}
private async processForgeModule(versionManifest: VersionManifestFG3): Promise<Module> {
const libDir = join(this.repoStructure.getWorkDirectory(), 'libraries')
if(this.wildcardsInUse) {
if(this.wildcardsInUse.indexOf(ForgeGradle3Adapter.WILDCARD_MCP_VERSION) > -1) {
const mcpVersion = this.getMCPVersion(versionManifest.arguments.game)
if(mcpVersion == null) {
throw new Error('MCP Version not found.. did forge change their format?')
}
this.generatedFiles = this.generatedFiles!.map(f => {
if(f.version.indexOf(ForgeGradle3Adapter.WILDCARD_MCP_VERSION) > -1) {
return {
...f,
version: f.version.replace(ForgeGradle3Adapter.WILDCARD_MCP_VERSION, mcpVersion)
}
}
return f
})
}
}
const mdls: Module[] = [] const mdls: Module[] = []
for (const entry of generatedFiles) { for (const entry of this.generatedFiles!) {
const targetLocations: string[] = [] const targetLocations: string[] = []
let located = false let located = false
@@ -286,34 +332,58 @@ export class ForgeGradle3Adapter extends ForgeResolver {
return forgeModule return forgeModule
} }
private async processVersionManifest(): Promise<[VersionManifestFG3, Module]> { private async processLibraries(manifest: VersionManifestFG3): Promise<Module[]> {
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 libDir = join(this.repoStructure.getWorkDirectory(), 'libraries')
const versionManifest = JSON.parse(versionManifestBuf.toString()) as VersionManifestFG3 const libRepo = this.repoStructure.getLibRepoStruct()
const versionManifestModule: Module = { const mdls: Module[] = []
id: this.artifactVersion,
name: 'Minecraft Forge (version.json)', for (const entry of manifest.libraries) {
type: Type.VersionManifest, const artifact = entry.downloads.artifact
artifact: this.generateArtifact( if (artifact.url) {
versionManifestBuf,
await lstat(versionManifestPath), const targetLocalPath = join(libDir, artifact.path)
versionRepo.getVersionManifestURL(this.baseUrl, this.minecraftVersion, this.forgeVersion)
) if (!await pathExists(targetLocalPath)) {
throw new Error(`Expected library ${entry.name} not found!`)
} }
const destination = this.repoStructure.getVersionRepoStruct().getVersionManifest( const components = MavenUtil.getMavenComponents(entry.name)
this.minecraftVersion,
this.forgeVersion 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(versionManifestPath, destination, {overwrite: true}) await move(targetLocalPath, destination, {overwrite: true})
}
}
return mdls
return [versionManifest, versionManifestModule]
} }
private executeInstaller(installerExec: string): Promise<void> { private executeInstaller(installerExec: string): Promise<void> {
@@ -342,4 +412,169 @@ export class ForgeGradle3Adapter extends ForgeResolver {
return null return null
} }
private async processWithoutInstaller(installerPath: string): Promise<Module> {
// Extract version.json from installer.
const forgeInstallerBuffer = await readFile(installerPath)
const zip = new AdmZip(forgeInstallerBuffer)
const zipEntries = zip.getEntries()
let versionManifest
for (const entry of zipEntries) {
if (entry.entryName === 'version.json') {
versionManifest = zip.readAsText(entry)
break
}
}
if (!versionManifest) {
throw new Error('Failed to find version.json in forge installer jar.')
}
versionManifest = JSON.parse(versionManifest) as VersionManifestFG3
// Save Version Manifest
const versionManifestDest = this.repoStructure.getVersionRepoStruct().getVersionManifest(
this.minecraftVersion,
this.forgeVersion
)
await mkdirs(dirname(versionManifestDest))
await writeJson(versionManifestDest, versionManifest, { spaces: 4 })
const libRepo = this.repoStructure.getLibRepoStruct()
const universalLocalPath = libRepo.getLocalForge(this.artifactVersion, 'universal')
console.debug(`Checking for Forge Universal jar at ${universalLocalPath}..`)
const forgeMdl = versionManifest.libraries.find(val => val.name.startsWith('net.minecraftforge:forge:'))
if(forgeMdl == null) {
throw new Error('Forge entry not found in version.json!')
}
let forgeUniversalBuffer
// Check for local universal jar.
if (await libRepo.artifactExists(universalLocalPath)) {
const localUniBuf = await readFile(universalLocalPath)
const sha1 = createHash('sha1').update(localUniBuf).digest('hex')
if(sha1 !== forgeMdl.downloads.artifact.sha1) {
console.debug('SHA-1 of local universal jar does not match version.json entry.')
console.debug('Redownloading Forge Universal jar..')
} else {
console.debug('Using locally discovered forge.')
forgeUniversalBuffer = localUniBuf
}
} else {
console.debug('Forge Universal jar not found locally, initializing download..')
}
// Download if local is missing or corrupt
if(!forgeUniversalBuffer) {
await libRepo.downloadArtifactByComponents(
this.REMOTE_REPOSITORY,
LibRepoStructure.FORGE_GROUP,
LibRepoStructure.FORGE_ARTIFACT,
this.artifactVersion, 'universal', 'jar')
forgeUniversalBuffer = await readFile(universalLocalPath)
}
console.debug(`Beginning processing of Forge v${this.forgeVersion} (Minecraft ${this.minecraftVersion})`)
const forgeModule: Module = {
id: MavenUtil.mavenComponentsToIdentifier(
LibRepoStructure.FORGE_GROUP,
LibRepoStructure.FORGE_ARTIFACT,
this.artifactVersion, 'universal'
),
name: 'Minecraft Forge',
type: Type.ForgeHosted,
artifact: this.generateArtifact(
forgeUniversalBuffer,
await lstat(universalLocalPath),
libRepo.getArtifactUrlByComponents(
this.baseUrl,
LibRepoStructure.FORGE_GROUP,
LibRepoStructure.FORGE_ARTIFACT,
this.artifactVersion, 'universal'
)
),
subModules: []
}
// Attach Version Manifest module.
forgeModule.subModules?.push({
id: this.artifactVersion,
name: 'Minecraft Forge (version.json)',
type: Type.VersionManifest,
artifact: this.generateArtifact(
await readFile(versionManifestDest),
await lstat(versionManifestDest),
this.repoStructure.getVersionRepoStruct().getVersionManifestURL(
this.baseUrl, this.minecraftVersion, this.forgeVersion)
)
})
for(const lib of versionManifest.libraries) {
if (lib.name.startsWith('net.minecraftforge:forge:')) {
// We've already processed forge.
continue
}
console.debug(`Processing ${lib.name}..`)
const extension = 'jar'
const localPath = libRepo.getArtifactById(lib.name, extension)
let queueDownload = !await libRepo.artifactExists(localPath)
let libBuf
if (!queueDownload) {
libBuf = await readFile(localPath)
const sha1 = createHash('sha1').update(libBuf).digest('hex')
if (sha1 !== lib.downloads.artifact.sha1) {
console.debug('Hashes do not match, redownloading..')
queueDownload = true
}
} else {
console.debug('Not found locally, downloading..')
queueDownload = true
}
if (queueDownload) {
await libRepo.downloadArtifactDirect(lib.downloads.artifact.url, lib.downloads.artifact.path)
libBuf = await readFile(localPath)
} else {
console.debug('Using local copy.')
}
const stats = await lstat(localPath)
const mavenComponents = MavenUtil.getMavenComponents(lib.name)
const properId = MavenUtil.mavenComponentsToIdentifier(
mavenComponents.group, mavenComponents.artifact, mavenComponents.version,
mavenComponents.classifier, extension
)
forgeModule.subModules?.push({
id: properId,
name: `Minecraft Forge (${mavenComponents?.artifact})`,
type: Type.Library,
artifact: this.generateArtifact(
libBuf as Buffer,
stats,
libRepo.getArtifactUrlByComponents(
this.baseUrl,
mavenComponents.group, mavenComponents.artifact,
mavenComponents.version, mavenComponents.classifier, extension
)
)
})
}
return forgeModule
}
} }

View File

@@ -7,6 +7,7 @@ import { MinecraftVersion } from '../../util/MinecraftVersion'
export abstract class ForgeResolver extends BaseResolver { export abstract class ForgeResolver extends BaseResolver {
protected readonly MOJANG_REMOTE_REPOSITORY = 'https://libraries.minecraft.net/'
protected readonly REMOTE_REPOSITORY = 'https://files.minecraftforge.net/maven/' protected readonly REMOTE_REPOSITORY = 'https://files.minecraftforge.net/maven/'
protected repoStructure: RepoStructure protected repoStructure: RepoStructure

View File

@@ -2,6 +2,6 @@ import { MinecraftVersion } from './MinecraftVersion'
export interface VersionSegmented { export interface VersionSegmented {
isForVersion(version: MinecraftVersion): boolean isForVersion(version: MinecraftVersion, libraryVersion: string): boolean
} }

View File

@@ -26,7 +26,7 @@ export class VersionSegmentedRegistry {
baseURL: string baseURL: string
): ForgeResolver { ): ForgeResolver {
for (const impl of VersionSegmentedRegistry.FORGE_ADAPTER_IMPL) { for (const impl of VersionSegmentedRegistry.FORGE_ADAPTER_IMPL) {
if (impl.isForVersion(minecraftVersion)) { if (impl.isForVersion(minecraftVersion, forgeVersion)) {
return new impl(absoluteRoot, relativeRoot, baseURL, minecraftVersion, forgeVersion) return new impl(absoluteRoot, relativeRoot, baseURL, minecraftVersion, forgeVersion)
} }
} }
@@ -35,12 +35,13 @@ export class VersionSegmentedRegistry {
public static getForgeModStruct( public static getForgeModStruct(
minecraftVersion: MinecraftVersion, minecraftVersion: MinecraftVersion,
forgeVersion: string,
absoluteRoot: string, absoluteRoot: string,
relativeRoot: string, relativeRoot: string,
baseUrl: string baseUrl: string
): BaseForgeModStructure { ): BaseForgeModStructure {
for (const impl of VersionSegmentedRegistry.FORGEMOD_STRUCT_IML) { for (const impl of VersionSegmentedRegistry.FORGEMOD_STRUCT_IML) {
if (impl.isForVersion(minecraftVersion)) { if (impl.isForVersion(minecraftVersion, forgeVersion)) {
return new impl(absoluteRoot, relativeRoot, baseUrl) return new impl(absoluteRoot, relativeRoot, baseUrl)
} }
} }

View File

@@ -18,6 +18,19 @@ export class VersionUtil {
return false return false
} }
public static isOneDotTwelveFG2(libraryVersion: string): boolean {
const maxFG2 = [14, 23, 5, 2847]
const verSplit = libraryVersion.split('.').map(v => Number(v))
for(let i=0; i<maxFG2.length; i++) {
if(verSplit[i] > maxFG2[i]) {
return false
}
}
return true
}
public static isPromotionVersion(version: string): boolean { public static isPromotionVersion(version: string): boolean {
return VersionUtil.PROMOTION_TYPE.indexOf(version.toLowerCase()) > -1 return VersionUtil.PROMOTION_TYPE.indexOf(version.toLowerCase()) > -1
} }