Added metadata readers for litemods and forgemods.
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -24,6 +24,15 @@
|
|||||||
"js-tokens": "^4.0.0"
|
"js-tokens": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/adm-zip": {
|
||||||
|
"version": "0.4.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.32.tgz",
|
||||||
|
"integrity": "sha512-hv1O7ySn+XvP5OeDQcJFWwVb2v+GFGO1A9aMTQ5B/bzxb7WW21O8iRhVdsKKr8QwuiagzGmPP+gsUAYZ6bRddQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/fs-extra": {
|
"@types/fs-extra": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.0.0.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/dscalzi/Nebula#readme",
|
"homepage": "https://github.com/dscalzi/Nebula#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/adm-zip": "^0.4.32",
|
||||||
"@types/fs-extra": "^8.0.0",
|
"@types/fs-extra": "^8.0.0",
|
||||||
"@types/node": "^12.7.2",
|
"@types/node": "^12.7.2",
|
||||||
"@types/yargs": "^13.0.2",
|
"@types/yargs": "^13.0.2",
|
||||||
|
|||||||
22
src/model/forge/mcmodinfo.ts
Normal file
22
src/model/forge/mcmodinfo.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// https://mcforge.readthedocs.io/en/latest/gettingstarted/structuring/#the-mcmodinfo-file
|
||||||
|
export interface McModInfo {
|
||||||
|
|
||||||
|
modid: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
version: string
|
||||||
|
mcversion: string
|
||||||
|
url: string
|
||||||
|
updateUrl?: string
|
||||||
|
updateJSON: string
|
||||||
|
authorList: string[]
|
||||||
|
credits: string
|
||||||
|
logoFile: string
|
||||||
|
screenshots: string[]
|
||||||
|
parent: string
|
||||||
|
useDependencyInformation: boolean
|
||||||
|
requiredMods: string[]
|
||||||
|
dependencies: string[]
|
||||||
|
dependants: string[] // Spelled as dependants on forge's wiki.
|
||||||
|
|
||||||
|
}
|
||||||
7
src/model/forge/mcmodinfolist.ts
Normal file
7
src/model/forge/mcmodinfolist.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { McModInfo } from './mcmodinfo'
|
||||||
|
|
||||||
|
export interface McModInfoList {
|
||||||
|
|
||||||
|
modListVersion: number
|
||||||
|
modList: McModInfo[]
|
||||||
|
}
|
||||||
10
src/model/liteloader/litemod.ts
Normal file
10
src/model/liteloader/litemod.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface LiteMod {
|
||||||
|
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
mcversion: string
|
||||||
|
revision: string
|
||||||
|
description: string
|
||||||
|
author?: string
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Stats } from 'fs'
|
import { Stats } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { resolve } from 'url'
|
||||||
import { Type } from '../../spec/type'
|
import { Type } from '../../spec/type'
|
||||||
import { ModuleStructure } from './module.struct'
|
import { ModuleStructure } from './module.struct'
|
||||||
|
|
||||||
@@ -18,11 +20,11 @@ export class FileStructure extends ModuleStructure {
|
|||||||
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
|
||||||
return 'TODO'
|
return resolve(this.baseUrl, join(this.relativeRoot, name))
|
||||||
}
|
}
|
||||||
protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
|
||||||
return 'TODO'
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
|
import AdmZip from 'adm-zip'
|
||||||
import { Stats } from 'fs-extra'
|
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 { Type } from '../../spec/type'
|
import { Type } from '../../spec/type'
|
||||||
import { ModuleStructure } from './module.struct'
|
import { ModuleStructure } from './module.struct'
|
||||||
|
|
||||||
export class ForgeModStructure extends ModuleStructure {
|
export class ForgeModStructure extends ModuleStructure {
|
||||||
|
|
||||||
|
private forgeModMetadata: {[property: string]: McModInfo | undefined} = {}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
absoluteRoot: string,
|
absoluteRoot: string,
|
||||||
relativeRoot: string,
|
relativeRoot: string,
|
||||||
@@ -13,16 +21,77 @@ export class ForgeModStructure extends ModuleStructure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
return name
|
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> {
|
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||||
return name
|
return capitalize((this.getForgeModMetadata(buf, name)).name)
|
||||||
}
|
}
|
||||||
protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
|
||||||
return 'TODO'
|
return resolve(this.baseUrl, join(this.relativeRoot, name))
|
||||||
}
|
}
|
||||||
protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
|
||||||
return 'TODO'
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw) {
|
||||||
|
// Assuming the main mod will be the first entry in this file.
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`ForgeMod ${name} does not contain mcmod.info file.`)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import AdmZip from 'adm-zip'
|
||||||
import { Stats } from 'fs-extra'
|
import { Stats } from 'fs-extra'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import { resolve } from 'url'
|
||||||
|
import { capitalize } from '../../../util/stringutils'
|
||||||
|
import { LiteMod } from '../../liteloader/litemod'
|
||||||
import { Type } from '../../spec/type'
|
import { Type } from '../../spec/type'
|
||||||
import { ModuleStructure } from './module.struct'
|
import { ModuleStructure } from './module.struct'
|
||||||
|
|
||||||
export class LiteModStructure extends ModuleStructure {
|
export class LiteModStructure extends ModuleStructure {
|
||||||
|
|
||||||
|
private liteModMetadata: {[property: string]: LiteMod | undefined} = {}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
absoluteRoot: string,
|
absoluteRoot: string,
|
||||||
relativeRoot: string,
|
relativeRoot: string,
|
||||||
@@ -14,16 +20,40 @@ export class LiteModStructure extends ModuleStructure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
return name
|
const liteModData = this.getLiteModMetadata(buf, name)
|
||||||
|
return this.generateMavenIdentifier(liteModData.name, `${liteModData.version}-${liteModData.mcversion}`)
|
||||||
}
|
}
|
||||||
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
||||||
return name
|
return capitalize(this.getLiteModMetadata(buf, name).name)
|
||||||
}
|
}
|
||||||
protected async getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModuleUrl(name: string, path: string, stats: Stats): Promise<string> {
|
||||||
return 'TODO'
|
return resolve(this.baseUrl, join(this.relativeRoot, name))
|
||||||
}
|
}
|
||||||
protected async getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string> {
|
protected async getModulePath(name: string, path: string, stats: Stats): Promise<string | null> {
|
||||||
return 'TODO'
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLiteModMetadata(buf: Buffer, name: string): LiteMod {
|
||||||
|
if (!this.liteModMetadata.hasOwnProperty(name)) {
|
||||||
|
const zip = new AdmZip(buf)
|
||||||
|
const zipEntries = zip.getEntries()
|
||||||
|
|
||||||
|
let raw
|
||||||
|
for (const entry of zipEntries) {
|
||||||
|
if (entry.entryName === 'litemod.json') {
|
||||||
|
raw = zip.readAsText(entry)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw) {
|
||||||
|
this.liteModMetadata[name] = JSON.parse(raw) as LiteMod
|
||||||
|
} else {
|
||||||
|
throw new Error(`Litemod ${name} does not contain litemod.json file.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.liteModMetadata[name] as LiteMod
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createHash } from 'crypto'
|
|||||||
import { lstat, pathExists, readdir, readFile, Stats } from 'fs-extra'
|
import { lstat, pathExists, readdir, readFile, Stats } from 'fs-extra'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { Module } from '../../spec/module'
|
import { Module } from '../../spec/module'
|
||||||
import { Type } from '../../spec/type'
|
import { Type, TypeMetadata } from '../../spec/type'
|
||||||
import { BaseModelStructure } from '../basemodel.struct'
|
import { BaseModelStructure } from '../basemodel.struct'
|
||||||
|
|
||||||
export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
||||||
@@ -25,10 +25,14 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
|||||||
return this.resolvedModels
|
return this.resolvedModels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected generateMavenIdentifier(name: string, version: string) {
|
||||||
|
return `generated.${this.type.toLowerCase()}:${name}-${version}@${TypeMetadata[this.type].defaultExtension}`
|
||||||
|
}
|
||||||
|
|
||||||
protected async abstract getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
protected async abstract getModuleId(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
||||||
protected async abstract getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
protected async abstract getModuleName(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
||||||
protected async abstract getModuleUrl(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
protected async abstract getModuleUrl(name: string, path: string, stats: Stats): Promise<string>
|
||||||
protected async abstract getModulePath(name: string, path: string, stats: Stats, buf: Buffer): Promise<string>
|
protected async abstract getModulePath(name: string, path: string, stats: Stats): Promise<string | null>
|
||||||
|
|
||||||
private async _doModuleRetrieval(): Promise<Module[]> {
|
private async _doModuleRetrieval(): Promise<Module[]> {
|
||||||
|
|
||||||
@@ -41,7 +45,7 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
|||||||
const stats = await lstat(filePath)
|
const stats = await lstat(filePath)
|
||||||
const buf = await readFile(filePath)
|
const buf = await readFile(filePath)
|
||||||
if (stats.isFile()) {
|
if (stats.isFile()) {
|
||||||
accumulator.push({
|
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),
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@@ -52,10 +56,14 @@ export abstract class ModuleStructure extends BaseModelStructure<Module> {
|
|||||||
artifact: {
|
artifact: {
|
||||||
size: stats.size,
|
size: stats.size,
|
||||||
MD5: createHash('md5').update(buf).digest('hex'),
|
MD5: createHash('md5').update(buf).digest('hex'),
|
||||||
url: await this.getModuleUrl(file, filePath, stats, buf),
|
url: await this.getModuleUrl(file, filePath, stats)
|
||||||
path: await this.getModulePath(file, filePath, stats, buf)
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
const pth = await this.getModulePath(file, filePath, stats)
|
||||||
|
if (pth) {
|
||||||
|
mdl.artifact.path = pth
|
||||||
|
}
|
||||||
|
accumulator.push(mdl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/util/stringutils.ts
Normal file
6
src/util/stringutils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function capitalize(str: string) {
|
||||||
|
if (!str) {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user