diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3a8e4c8..4d03704 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,7 +9,10 @@ "tsconfig": "tsconfig.json", "problemMatcher": [ "$tsc" - ] + ], + "presentation": { + "reveal": "silent" + } } ] } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 39d8f9b..ce1fea2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -178,7 +178,8 @@ const testCommand: yargs.CommandModule = { 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') if (resolver != null) { - await resolver.getModule() + const mdl = await resolver.getModule() + console.log(mdl) } } } diff --git a/src/model/forge/versionmanifest.ts b/src/model/forge/versionmanifest.ts new file mode 100644 index 0000000..bbf54f3 --- /dev/null +++ b/src/model/forge/versionmanifest.ts @@ -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 + }> + +} diff --git a/src/model/struct/model/module/module.struct.ts b/src/model/struct/model/module/module.struct.ts index b1bd835..5b1b623 100644 --- a/src/model/struct/model/module/module.struct.ts +++ b/src/model/struct/model/module/module.struct.ts @@ -43,8 +43,8 @@ export abstract class ModuleStructure extends BaseModelStructure { for (const file of files) { const filePath = resolve(this.containerDirectory, file) const stats = await lstat(filePath) - const buf = await readFile(filePath) if (stats.isFile()) { + const buf = await readFile(filePath) const mdl: Module = { id: await this.getModuleId(file, filePath, stats, buf), name: await this.getModuleName(file, filePath, stats, buf), diff --git a/src/model/struct/repo/BaseMavenRepo.ts b/src/model/struct/repo/BaseMavenRepo.ts index f1804bd..e9a4f18 100644 --- a/src/model/struct/repo/BaseMavenRepo.ts +++ b/src/model/struct/repo/BaseMavenRepo.ts @@ -15,8 +15,8 @@ export abstract class BaseMavenRepo extends BaseFileStructure { super(absoluteRoot, relativeRoot, structRoot) } - public getArtifactById(mavenIdentifier: string): string | null { - const resolved = MavenUtil.mavenIdentifierToString(mavenIdentifier) + public getArtifactById(mavenIdentifier: string, extension?: string): string | null { + const resolved = MavenUtil.mavenIdentifierToString(mavenIdentifier, extension) return resolved == null ? null : resolve(this.containerDirectory, resolved) } @@ -30,9 +30,17 @@ export abstract class BaseMavenRepo extends BaseFileStructure { return pathExists(path) } - public async downloadArtifact(url: string, group: string, artifact: string, version: string, - classifier?: string, extension?: string) { - const relative = MavenUtil.mavenComponentsToString(group, artifact, version, classifier, extension) + public async downloadArtifactById(url: string, mavenIdentifier: string, extension?: string) { + return this.downloadArtifactBase(url, MavenUtil.mavenIdentifierToString(mavenIdentifier, extension) as string) + } + + 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() console.debug(`Downloading ${resolvedURL}..`) const response = await axios({ diff --git a/src/resolver/forge/adapter/forge18.resolver.ts b/src/resolver/forge/adapter/forge18.resolver.ts index 43792e2..cc429eb 100644 --- a/src/resolver/forge/adapter/forge18.resolver.ts +++ b/src/resolver/forge/adapter/forge18.resolver.ts @@ -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 { Type } from '../../../model/spec/type' import { ForgeRepoStructure } from '../../../model/struct/repo/forgerepo.struct' +import { MavenUtil } from '../../../util/maven' import { ForgeResolver } from '../forge.resolver' export class Forge18Adapter extends ForgeResolver { @@ -18,8 +25,7 @@ export class Forge18Adapter extends ForgeResolver { } public async getModule(): Promise { - await this.getForgeByVersion() - return null as unknown as Module + return this.getForgeByVersion() } public isForVersion(version: string) { @@ -33,7 +39,7 @@ export class Forge18Adapter extends ForgeResolver { console.debug(`Checking for forge version at ${targetLocalPath}..`) if (!await forgeRepo.artifactExists(targetLocalPath)) { console.debug(`Forge not found locally, initializing download..`) - await forgeRepo.downloadArtifact( + await forgeRepo.downloadArtifactByComponents( this.REMOTE_REPOSITORY, ForgeRepoStructure.FORGE_GROUP, ForgeRepoStructure.FORGE_ARTIFACT, @@ -42,11 +48,81 @@ export class Forge18Adapter extends ForgeResolver { console.debug('Using locally discovered forge.') } 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 - // extract manifest - // parse manifest - // return module + private generateArtifact(buf: Buffer, stats: Stats): Artifact { + return { + size: stats.size, + MD5: createHash('md5').update(buf).digest('hex'), + url: 'TODO' + } + } + + private determineExtension(checksums: string[] | undefined) { + return checksums != null && checksums.length > 1 ? 'jar.pack.xz' : 'jar' + } } diff --git a/src/util/maven.ts b/src/util/maven.ts index 4383305..80e667f 100644 --- a/src/util/maven.ts +++ b/src/util/maven.ts @@ -6,11 +6,16 @@ export class MavenUtil { public static readonly ID_REGEX = /(.+):(.+):([^@]+)()(?:@{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) } - public static mavenIdentifierToString(id: string, extension = 'jar') { + public static getMavenComponents(id: string, extension = 'jar') { if (!MavenUtil.isMavenIdentifier(id)) { return null } @@ -24,17 +29,29 @@ export class MavenUtil { } if (result != null) { - const group = result[1] - const artifact = result[2] - const version = result[3] - const classifier = result[4] || undefined - const ext = result[5] || extension - - return MavenUtil.mavenComponentsToString(group, artifact, version, classifier, ext) + return { + group: result[1], + artifact: result[2], + version: result[3], + classifier: result[4] || undefined, + extension: result[5] || extension + } } + 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, classifier?: string, extension = 'jar') { return `${group.replace(/\./g, '/')}/${artifact}/${version}/${artifact}-${version}${classifier != null ? `-${classifier}` : ''}.${extension}`