Add support for reading mods.toml file from 1.13+ forge mods.

This commit is contained in:
Daniel Scalzi
2020-01-19 11:59:11 -05:00
parent 4d342b7b0a
commit 1ff02edc71
15 changed files with 334 additions and 135 deletions

View File

@@ -0,0 +1,29 @@
// https://github.com/MinecraftForge/MinecraftForge/blob/1.15.x/mdk/src/main/resources/META-INF/mods.toml
export interface ModsToml {
modLoader: string,
loaderVersion: string
issueTrackerURL?: string
mods: Array<{
modId: string
version: string
displayName: string
updateJSONURL?: string
displayURL?: string
logoFile?: string
credits?: string
authors?: string
description: string
}>
dependencies?: {[modId: string]: {
modId: string,
mandatory: boolean,
versionRange: string,
ordering?: 'NONE' | 'BEFORE' | 'AFTER'
side: 'BOTH' | 'CLIENT' | 'SERVER'
}}
}

View File

@@ -1,16 +1,11 @@
import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra'
import { join } from 'path'
import { resolve } from 'url'
import { capitalize } from '../../../../util/stringutils'
import { McModInfo } from '../../../forge/mcmodinfo'
import { McModInfoList } from '../../../forge/mcmodinfolist'
import { VersionSegmented } from '../../../../util/VersionSegmented'
import { Type } from '../../../spec/type'
import { ModuleStructure } from './module.struct'
export class ForgeModStructure extends ModuleStructure {
private forgeModMetadata: {[property: string]: McModInfo | undefined} = {}
export abstract class BaseForgeModStructure extends ModuleStructure implements VersionSegmented {
constructor(
absoluteRoot: string,
@@ -20,13 +15,8 @@ export class ForgeModStructure extends ModuleStructure {
super(absoluteRoot, relativeRoot, 'forgemods', baseUrl, Type.ForgeMod)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const fmData = this.getForgeModMetadata(buf, name)
return this.generateMavenIdentifier(fmData.modid, fmData.version)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize((this.getForgeModMetadata(buf, name)).name)
}
public abstract isForVersion(version: string): boolean
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
return resolve(this.baseUrl, join(this.relativeRoot, name))
}
@@ -34,75 +24,4 @@ export class ForgeModStructure extends ModuleStructure {
return null
}
private getForgeModMetadata(buf: Buffer, name: string): McModInfo {
if (!this.forgeModMetadata.hasOwnProperty(name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
// Optifine is a tweak that can be loaded as a forge mod. It does not
// appear to contain a mcmod.info class. This a special case we will
// account for.
if (name.toLowerCase().indexOf('optifine') > -1) {
// Read zip for changelog.txt
let rawChangelog
for (const entry of zipEntries) {
if (entry.entryName === 'changelog.txt') {
rawChangelog = zip.readAsText(entry)
break
}
}
if (!rawChangelog) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = rawChangelog.split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
name: info,
version,
mcversion: version.substring(0, version.indexOf('_'))
}) as unknown as McModInfo
return this.forgeModMetadata[name] as McModInfo
}
let raw
for (const entry of zipEntries) {
if (entry.entryName === 'mcmod.info') {
raw = zip.readAsText(entry)
break
}
}
let createDefault = false
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const resolved = JSON.parse(raw) as object
if (resolved.hasOwnProperty('modListVersion')) {
this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0]
} else {
this.forgeModMetadata[name] = (resolved as McModInfo[])[0]
}
} catch (err) {
console.error(`ForgeMod ${name} contains an invalid mcmod.info file.`)
createDefault = true
}
} else {
console.error(`ForgeMod ${name} does not contain mcmod.info file.`)
createDefault = true
}
if (createDefault) {
this.forgeModMetadata[name] = ({
modid: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
name,
version: '0.0.0'
}) as unknown as McModInfo
}
}
return this.forgeModMetadata[name] as McModInfo
}
}

View File

@@ -0,0 +1,104 @@
import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra'
import toml from 'toml'
import { capitalize } from '../../../../../util/stringutils'
import { VersionUtil } from '../../../../../util/versionutil'
import { ModsToml } from '../../../../forge/modstoml'
import { BaseForgeModStructure } from '../forgemod.struct'
export class ForgeModStructure113 extends BaseForgeModStructure {
public static isForVersion(version: string) {
return VersionUtil.isVersionAcceptable(version, [13, 14, 15])
}
private forgeModMetadata: {[property: string]: ModsToml | undefined} = {}
constructor(
absoluteRoot: string,
relativeRoot: string,
baseUrl: string
) {
super(absoluteRoot, relativeRoot, baseUrl)
}
public isForVersion(version: string): boolean {
return ForgeModStructure113.isForVersion(version)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const fmData = this.getForgeModMetadata(buf, name)
return this.generateMavenIdentifier(fmData.mods[0].modId, fmData.mods[0].version)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize((this.getForgeModMetadata(buf, name)).mods[0].displayName)
}
private getForgeModMetadata(buf: Buffer, name: string): ModsToml {
if (!this.forgeModMetadata.hasOwnProperty(name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
// Optifine is a tweak that can be loaded as a forge mod. It does not
// appear to contain a mcmod.info class. This a special case we will
// account for.
if (name.toLowerCase().indexOf('optifine') > -1) {
// Read zip for changelog.txt
let rawChangelog
for (const entry of zipEntries) {
if (entry.entryName === 'changelog.txt') {
rawChangelog = zip.readAsText(entry)
break
}
}
if (!rawChangelog) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = rawChangelog.split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
name: info,
version,
mcversion: version.substring(0, version.indexOf('_'))
}) as unknown as ModsToml
return this.forgeModMetadata[name] as ModsToml
}
const raw = zip.readAsText('META-INF/mods.toml')
let createDefault = false
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
this.forgeModMetadata[name] = toml.parse(raw) as ModsToml
} catch (err) {
console.error(`ForgeMod ${name} contains an invalid mods.toml file.`)
createDefault = true
}
} else {
console.error(`ForgeMod ${name} does not contain mods.toml file.`)
createDefault = true
}
if (createDefault) {
this.forgeModMetadata[name] = ({
modLoader: 'javafml',
loaderVersion: '',
mods: [{
modId: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
version: '0.0.0',
displayName: name,
description: ''
}]
})
}
}
return this.forgeModMetadata[name] as ModsToml
}
}

View File

@@ -0,0 +1,108 @@
import AdmZip from 'adm-zip'
import { Stats } from 'fs-extra'
import { capitalize } from '../../../../../util/stringutils'
import { VersionUtil } from '../../../../../util/versionutil'
import { McModInfo } from '../../../../forge/mcmodinfo'
import { McModInfoList } from '../../../../forge/mcmodinfolist'
import { BaseForgeModStructure } from '../forgemod.struct'
export class ForgeModStructure17 extends BaseForgeModStructure {
public static isForVersion(version: string) {
return VersionUtil.isVersionAcceptable(version, [7, 8, 9, 10, 11, 12])
}
private forgeModMetadata: {[property: string]: McModInfo | undefined} = {}
constructor(
absoluteRoot: string,
relativeRoot: string,
baseUrl: string
) {
super(absoluteRoot, relativeRoot, baseUrl)
}
public isForVersion(version: string): boolean {
return ForgeModStructure17.isForVersion(version)
}
protected async getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
const fmData = this.getForgeModMetadata(buf, name)
return this.generateMavenIdentifier(fmData.modid, fmData.version)
}
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
return capitalize((this.getForgeModMetadata(buf, name)).name)
}
private getForgeModMetadata(buf: Buffer, name: string): McModInfo {
if (!this.forgeModMetadata.hasOwnProperty(name)) {
const zip = new AdmZip(buf)
const zipEntries = zip.getEntries()
// Optifine is a tweak that can be loaded as a forge mod. It does not
// appear to contain a mcmod.info class. This a special case we will
// account for.
if (name.toLowerCase().indexOf('optifine') > -1) {
// Read zip for changelog.txt
let rawChangelog
for (const entry of zipEntries) {
if (entry.entryName === 'changelog.txt') {
rawChangelog = zip.readAsText(entry)
break
}
}
if (!rawChangelog) {
throw new Error('Failed to read OptiFine changelog.')
}
const info = rawChangelog.split('\n')[0].trim()
const version = info.split(' ')[1]
this.forgeModMetadata[name] = ({
modid: 'optifine',
name: info,
version,
mcversion: version.substring(0, version.indexOf('_'))
}) as unknown as McModInfo
return this.forgeModMetadata[name] as McModInfo
}
let raw
for (const entry of zipEntries) {
if (entry.entryName === 'mcmod.info') {
raw = zip.readAsText(entry)
break
}
}
let createDefault = false
if (raw) {
// Assuming the main mod will be the first entry in this file.
try {
const resolved = JSON.parse(raw) as object
if (resolved.hasOwnProperty('modListVersion')) {
this.forgeModMetadata[name] = (resolved as McModInfoList).modList[0]
} else {
this.forgeModMetadata[name] = (resolved as McModInfo[])[0]
}
} catch (err) {
console.error(`ForgeMod ${name} contains an invalid mcmod.info file.`)
createDefault = true
}
} else {
console.error(`ForgeMod ${name} does not contain mcmod.info file.`)
createDefault = true
}
if (createDefault) {
this.forgeModMetadata[name] = ({
modid: name.substring(0, name.lastIndexOf('.')).toLowerCase(),
name,
version: '0.0.0'
}) as unknown as McModInfo
}
}
return this.forgeModMetadata[name] as McModInfo
}
}

View File

@@ -1,12 +1,11 @@
import { lstat, mkdirs, pathExists, readdir, readFile, writeFile } from 'fs-extra'
import { dirname, join, resolve as resolvePath } from 'path'
import { resolve as resolveUrl } from 'url'
import { ResolverRegistry } from '../../../resolver/ResolverRegistry'
import { VersionSegmentedRegistry } from '../../../util/VersionSegmentedRegistry'
import { ServerMeta } from '../../nebula/servermeta'
import { Server } from '../../spec/server'
import { BaseModelStructure } from './basemodel.struct'
import { MiscFileStructure } from './module/file.struct'
import { ForgeModStructure } from './module/forgemod.struct'
import { LiteModStructure } from './module/litemod.struct'
export class ServerStructure extends BaseModelStructure<Server> {
@@ -47,7 +46,12 @@ export class ServerStructure extends BaseModelStructure<Server> {
await mkdirs(absoluteServerRoot)
if (options.forgeVersion != null) {
const fms = new ForgeModStructure(absoluteServerRoot, relativeServerRoot, this.baseUrl)
const fms = VersionSegmentedRegistry.getForgeModStruct(
minecraftVersion,
absoluteServerRoot,
relativeServerRoot,
this.baseUrl
)
await fms.init()
const serverMeta: ServerMeta = {
forgeVersion: options.forgeVersion
@@ -99,24 +103,25 @@ export class ServerStructure extends BaseModelStructure<Server> {
// Read server meta
const serverMeta: ServerMeta = JSON.parse(await readFile(resolvePath(absoluteServerRoot, 'servermeta.json'), 'utf-8'))
const minecraftVersion = match[2]
const forgeResolver = ResolverRegistry.getForgeResolver(
match[2],
const forgeResolver = VersionSegmentedRegistry.getForgeResolver(
minecraftVersion,
serverMeta.forgeVersion,
dirname(this.containerDirectory),
'',
this.baseUrl
)
if (forgeResolver == null) {
console.error(`No forge resolver found for Minecraft ${match[2]}, aborting.`)
throw new Error(`No forge resolver found for Minecraft ${match[2]}!`)
}
// Resolve forge
const forgeItselfModule = await forgeResolver.getModule()
const forgeModStruct = new ForgeModStructure(absoluteServerRoot, relativeServerRoot, this.baseUrl)
const forgeModStruct = VersionSegmentedRegistry.getForgeModStruct(
minecraftVersion,
absoluteServerRoot,
relativeServerRoot,
this.baseUrl
)
const forgeModModules = await forgeModStruct.getSpecModel()
const liteModStruct = new LiteModStructure(absoluteServerRoot, relativeServerRoot, this.baseUrl)