Parse version manifest and download forge libraries.

TODO:
Integrate base url propagation.
Integrate PackXZExtract to calculate hashes  of jar.pack.xz files.
OR ammend the distribution spec to accept different hash algos (requires helioslauncher update).
This commit is contained in:
Daniel Scalzi
2020-01-12 03:42:34 -05:00
parent 419a4d5e91
commit 0674bd5808
7 changed files with 149 additions and 24 deletions

5
.vscode/tasks.json vendored
View File

@@ -9,7 +9,10 @@
"tsconfig": "tsconfig.json", "tsconfig": "tsconfig.json",
"problemMatcher": [ "problemMatcher": [
"$tsc" "$tsc"
] ],
"presentation": {
"reveal": "silent"
}
} }
] ]
} }

View File

@@ -178,7 +178,8 @@ const testCommand: yargs.CommandModule = {
console.debug(`Invoked test with mcVer ${argv.mcVer} forgeVer ${argv.forgeVer}`) console.debug(`Invoked test with mcVer ${argv.mcVer} forgeVer ${argv.forgeVer}`)
const resolver = ResolverRegistry.getForgeResolver('1.12.2', '14.23.5.2847', 'D:/TestRoot2', 'D:/TestRoot2') const resolver = ResolverRegistry.getForgeResolver('1.12.2', '14.23.5.2847', 'D:/TestRoot2', 'D:/TestRoot2')
if (resolver != null) { if (resolver != null) {
await resolver.getModule() const mdl = await resolver.getModule()
console.log(mdl)
} }
} }
} }

View File

@@ -0,0 +1,20 @@
export interface VersionManifest {
id: string
time: string
releaseTime: string
type: string
minecraftArguments: string
mainClass: string
inheritsFrom: string
jar: string
logging: any
libraries: Array<{
name: string,
url?: string,
checksums?: string[],
serverreq?: boolean,
clientreq?: boolean
}>
}

View File

@@ -43,8 +43,8 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
for (const file of files) { for (const file of files) {
const filePath = resolve(this.containerDirectory, file) const filePath = resolve(this.containerDirectory, file)
const stats = await lstat(filePath) const stats = await lstat(filePath)
const buf = await readFile(filePath)
if (stats.isFile()) { if (stats.isFile()) {
const buf = await readFile(filePath)
const mdl: Module = { const mdl: Module = {
id: await this.getModuleId(file, filePath, stats, buf), id: await this.getModuleId(file, filePath, stats, buf),
name: await this.getModuleName(file, filePath, stats, buf), name: await this.getModuleName(file, filePath, stats, buf),

View File

@@ -15,8 +15,8 @@ export abstract class BaseMavenRepo extends BaseFileStructure {
super(absoluteRoot, relativeRoot, structRoot) super(absoluteRoot, relativeRoot, structRoot)
} }
public getArtifactById(mavenIdentifier: string): string | null { public getArtifactById(mavenIdentifier: string, extension?: string): string | null {
const resolved = MavenUtil.mavenIdentifierToString(mavenIdentifier) const resolved = MavenUtil.mavenIdentifierToString(mavenIdentifier, extension)
return resolved == null ? null : resolve(this.containerDirectory, resolved) return resolved == null ? null : resolve(this.containerDirectory, resolved)
} }
@@ -30,9 +30,17 @@ export abstract class BaseMavenRepo extends BaseFileStructure {
return pathExists(path) return pathExists(path)
} }
public async downloadArtifact(url: string, group: string, artifact: string, version: string, public async downloadArtifactById(url: string, mavenIdentifier: string, extension?: string) {
classifier?: string, extension?: string) { return this.downloadArtifactBase(url, MavenUtil.mavenIdentifierToString(mavenIdentifier, extension) as string)
const relative = MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension) }
public async downloadArtifactByComponents(url: string, group: string, artifact: string, version: string,
classifier?: string, extension?: string) {
return this.downloadArtifactBase(url,
MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension))
}
private async downloadArtifactBase(url: string, relative: string) {
const resolvedURL = resolveURL(url, relative).toString() const resolvedURL = resolveURL(url, relative).toString()
console.debug(`Downloading ${resolvedURL}..`) console.debug(`Downloading ${resolvedURL}..`)
const response = await axios({ const response = await axios({

View File

@@ -1,5 +1,12 @@
import AdmZip from 'adm-zip'
import { createHash } from 'crypto'
import { lstat, readFile, Stats } from 'fs-extra'
import { VersionManifest } from '../../../model/forge/versionmanifest'
import { Artifact } from '../../../model/spec/artifact'
import { Module } from '../../../model/spec/module' import { Module } from '../../../model/spec/module'
import { Type } from '../../../model/spec/type'
import { ForgeRepoStructure } from '../../../model/struct/repo/forgerepo.struct' import { ForgeRepoStructure } from '../../../model/struct/repo/forgerepo.struct'
import { MavenUtil } from '../../../util/maven'
import { ForgeResolver } from '../forge.resolver' import { ForgeResolver } from '../forge.resolver'
export class Forge18Adapter extends ForgeResolver { export class Forge18Adapter extends ForgeResolver {
@@ -18,8 +25,7 @@ export class Forge18Adapter extends ForgeResolver {
} }
public async getModule(): Promise<Module> { public async getModule(): Promise<Module> {
await this.getForgeByVersion() return this.getForgeByVersion()
return null as unknown as Module
} }
public isForVersion(version: string) { public isForVersion(version: string) {
@@ -33,7 +39,7 @@ export class Forge18Adapter extends ForgeResolver {
console.debug(`Checking for forge version at ${targetLocalPath}..`) console.debug(`Checking for forge version at ${targetLocalPath}..`)
if (!await forgeRepo.artifactExists(targetLocalPath)) { if (!await forgeRepo.artifactExists(targetLocalPath)) {
console.debug(`Forge not found locally, initializing download..`) console.debug(`Forge not found locally, initializing download..`)
await forgeRepo.downloadArtifact( await forgeRepo.downloadArtifactByComponents(
this.REMOTE_REPOSITORY, this.REMOTE_REPOSITORY,
ForgeRepoStructure.FORGE_GROUP, ForgeRepoStructure.FORGE_GROUP,
ForgeRepoStructure.FORGE_ARTIFACT, ForgeRepoStructure.FORGE_ARTIFACT,
@@ -42,11 +48,81 @@ export class Forge18Adapter extends ForgeResolver {
console.debug('Using locally discovered forge.') console.debug('Using locally discovered forge.')
} }
console.debug(`Beginning processing of Forge v${this.forgeVersion} (Minecraft ${this.minecraftVersion})`) console.debug(`Beginning processing of Forge v${this.forgeVersion} (Minecraft ${this.minecraftVersion})`)
const forgeUniversalBuffer = await readFile(targetLocalPath)
const zip = new AdmZip(forgeUniversalBuffer)
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 universal jar.')
}
versionManifest = JSON.parse(versionManifest) as VersionManifest
const forgeModule: Module = {
id: MavenUtil.mavenComponentsToIdentifier(
ForgeRepoStructure.FORGE_GROUP,
ForgeRepoStructure.FORGE_ARTIFACT,
artifactVersion, 'universal'
),
name: 'Minecraft Forge',
type: Type.ForgeHosted,
artifact: this.generateArtifact(forgeUniversalBuffer, await lstat(targetLocalPath)),
subModules: []
}
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 libRepo = this.repoStructure.getLibRepoStruct()
const extension = this.determineExtension(lib.checksums)
const localPath = libRepo.getArtifactById(lib.name, extension) as string
if (!await libRepo.artifactExists(localPath)) {
console.debug(`Not found locally, downloading..`)
await libRepo.downloadArtifactById(lib.url || 'https://libraries.minecraft.net/', lib.name, extension)
} else {
console.debug('Using local copy.')
}
const libBuf = await readFile(localPath)
const stats = await lstat(localPath)
forgeModule.subModules?.push({
id: lib.name,
name: `Minecraft Forge (${MavenUtil.getMavenComponents(lib.name)?.artifact})`,
type: Type.Library,
artifact: this.generateArtifact(libBuf, stats)
})
}
return forgeModule
} }
// TODO private generateArtifact(buf: Buffer, stats: Stats): Artifact {
// extract manifest return {
// parse manifest size: stats.size,
// return module MD5: createHash('md5').update(buf).digest('hex'),
url: 'TODO'
}
}
private determineExtension(checksums: string[] | undefined) {
return checksums != null && checksums.length > 1 ? 'jar.pack.xz' : 'jar'
}
} }

View File

@@ -6,11 +6,16 @@ export class MavenUtil {
public static readonly ID_REGEX = /(.+):(.+):([^@]+)()(?:@{1}(.+)$)?/ public static readonly ID_REGEX = /(.+):(.+):([^@]+)()(?:@{1}(.+)$)?/
public static readonly ID_REGEX_WITH_CLASSIFIER = /(.+):(.+):(?:([^@]+)(?:-([a-zA-Z]+)))(?:@{1}(.+)$)?/ public static readonly ID_REGEX_WITH_CLASSIFIER = /(.+):(.+):(?:([^@]+)(?:-([a-zA-Z]+)))(?:@{1}(.+)$)?/
public static isMavenIdentifier(id: string) { public static mavenComponentsToIdentifier(group: string, artifact: string, version: string,
classifier?: string, extension?: string) {
return `${group}:${artifact}:${version}${classifier != null ? `-${classifier}` : ''}${extension != null ? `@${extension}` : ''}`
}
public static isMavenIdentifier(id: string): boolean {
return MavenUtil.ID_REGEX.test(id) || MavenUtil.ID_REGEX_WITH_CLASSIFIER.test(id) return MavenUtil.ID_REGEX.test(id) || MavenUtil.ID_REGEX_WITH_CLASSIFIER.test(id)
} }
public static mavenIdentifierToString(id: string, extension = 'jar') { public static getMavenComponents(id: string, extension = 'jar') {
if (!MavenUtil.isMavenIdentifier(id)) { if (!MavenUtil.isMavenIdentifier(id)) {
return null return null
} }
@@ -24,17 +29,29 @@ export class MavenUtil {
} }
if (result != null) { if (result != null) {
const group = result[1] return {
const artifact = result[2] group: result[1],
const version = result[3] artifact: result[2],
const classifier = result[4] || undefined version: result[3],
const ext = result[5] || extension classifier: result[4] || undefined,
extension: result[5] || extension
return MavenUtil.mavenComponentsToString(group, artifact, version, classifier, ext) }
} }
return null return null
} }
public static mavenIdentifierToString(id: string, extension = 'jar') {
const tmp = MavenUtil.getMavenComponents(id, extension)
if (tmp != null) {
return MavenUtil.mavenComponentsToString(tmp.group, tmp.artifact, tmp.version,
tmp.classifier, tmp.extension)
} else {
return null
}
}
public static mavenComponentsToString(group: string, artifact: string, version: string, public static mavenComponentsToString(group: string, artifact: string, version: string,
classifier?: string, extension = 'jar') { classifier?: string, extension = 'jar') {
return `${group.replace(/\./g, '/')}/${artifact}/${version}/${artifact}-${version}${classifier != null ? `-${classifier}` : ''}.${extension}` return `${group.replace(/\./g, '/')}/${artifact}/${version}/${artifact}-${version}${classifier != null ? `-${classifier}` : ''}.${extension}`