[feat] added a naive way to parse mod metadata (#855)

This commit is contained in:
silentrald
2025-04-25 19:59:09 +08:00
committed by GitHub
parent 266feae483
commit 79be1a842c
7 changed files with 143 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
import { parseMetadata } from "main/helpers/mods.helpers";
import path from "path";
const TEST_FOLDER = path.resolve(__dirname, "..", "assets", "dll");
describe("Test mods.helpers.test", () => {
test("parseMetadata of BSML.dll", async () => {
const filepath = path.join(TEST_FOLDER, "BSML.dll");
const data = await parseMetadata(filepath);
expect(data).toBeTruthy();
expect(data.name).toBe("BeatSaberMarkupLanguage");
expect(data.id).toBe("BeatSaberMarkupLanguage");
expect(data.author).toBeTruthy();
expect(data.version).toBeTruthy();
expect(data.gameVersion).toBeTruthy();
});
test("parseMetadata of Bugsmash.dll", async () => {
const filepath = path.join(TEST_FOLDER, "Bugsmash.dll");
const data = await parseMetadata(filepath);
expect(data).toBeTruthy();
expect(data.name).toBe("Bugsmash");
expect(data.id).toBe("Bugsmash");
expect(data.author).toBeTruthy();
expect(data.version).toBeTruthy();
expect(data.gameVersion).toBeTruthy();
});
test("parseMetadata of PlaylistManager.dll", async () => {
const filepath = path.join(TEST_FOLDER, "PlaylistManager.dll");
const data = await parseMetadata(filepath);
expect(data).toBeTruthy();
expect(data.name).toBe("PlaylistManager");
expect(data.id).toBe("PlaylistManager");
expect(data.author).toBeTruthy();
expect(data.version).toBeTruthy();
expect(data.gameVersion).toBeTruthy();
});
test("parseMetadata of SongCore.dll", async () => {
const filepath = path.join(TEST_FOLDER, "SongCore.dll");
const data = await parseMetadata(filepath);
expect(data).toBeTruthy();
expect(data.name).toBe("SongCore");
expect(data.id).toBe("SongCore");
expect(data.author).toBeTruthy();
expect(data.version).toBeTruthy();
expect(data.gameVersion).toBeTruthy();
});
});
+57
View File
@@ -0,0 +1,57 @@
import { readFile } from "fs-extra";
import { ModMetadata } from "shared/models/mods/mod.interface";
// NOTE: getMetadata is slow since its using a search string and object parsing.
// Ways to optimize:
// - Find a way to locate the metadata string within the pdb/dll file
// - Correct object sizing, since the first byte does not match sometimes with the size somehow
// ref: https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json
const SEARCH_ARRAY = [
0x22, 0x24, 0x73, 0x63, 0x68,
0x65, 0x6D, 0x61, 0x22
].reverse(); // '"$schema"'
export async function parseMetadata(filepath: string): Promise<ModMetadata | null> {
const data = await readFile(filepath);
// Find the word SEARCH_ARRAY from end to start of the buffer data
// NOTE: Inefficient algorithm
let start = data.length - 1;
for (let i = 0; start >= 0; --start) {
if (data[start] !== SEARCH_ARRAY[i]) {
i = 0;
} else if (++i === SEARCH_ARRAY.length) {
break;
}
}
if (start < 4) {
return null;
}
// Search for the starting left curly brace '{'
while (data[--start] !== 0x7B);
// Find the ending right curly brace '}'
// NOTE: Inefficient algorithm
let braces = 1;
let end = start + 1;
for (; end < data.length && braces > 0; ++end) {
if (data[end] === 0x7B) { // {
++braces;
} else if (data[end] === 0x7D) { // }
--braces;
}
}
// Does not work with every dll (eg. Bugsmash.dll)
// const length = (data[start - 1] << 24)
// + (data[start - 2] << 16)
// + (data[start - 3] << 8)
// + (data[start - 4]);
// const end = start + length;
return JSON.parse(data.toString(undefined, start, end)) as ModMetadata;
}
+24
View File
@@ -100,3 +100,27 @@ export interface BbmUserAPIResponse {
createdAt?: Date;
updatedAt?: Date;
}
// https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json
export interface ModMetadata {
name: string;
id: string | null;
version: string;
gameVersion: string;
description: string;
author: string;
dependsOn?: Record<string, string>;
conflictsWith?: Record<string, string>;
loadAfter?: string[];
loadBefore?: string[];
feature?: Record<string, string>;
icon?: string;
files?: string[];
links?: {
"project-home"?: string;
"project-source"?: string;
donate?: string;
};
misc?: Record<string, string>;
}