mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-26 03:24:18 +00:00
Parse mi files (#935)
* Add more maki files found online * Add parser for .mi files and confirm it can derive the objects we have today
This commit is contained in:
parent
1215beec76
commit
460ea396ec
6 changed files with 2822 additions and 0 deletions
|
|
@ -0,0 +1,189 @@
|
|||
Nullsoft Database Engine Format Specifications v1.0
|
||||
---------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1. Tables
|
||||
---------
|
||||
|
||||
Tables are saved using two files, one containing the data, and one containing the indexes :
|
||||
|
||||
Data file : <filename>.DAT
|
||||
Index file : <filename>.IDX
|
||||
|
||||
|
||||
2. Index File Structure
|
||||
-----------------------
|
||||
|
||||
Note that string types are not zero terminated, they are either of fixed size (indicated in the following tables)
|
||||
or their size is determined by another field.
|
||||
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 STRING 8 "NDEINDEX"
|
||||
8 INT 4 Number of records
|
||||
12 INDEX 4*NumberOfRecords Primary Index (insertion order)
|
||||
|
||||
Optional :
|
||||
|
||||
4*NumberOfRecords+12 INDEX 4*NumberOfRecords First Secondary Index
|
||||
2*(4*NumberOfRecords)+12 INDEX 4*NumberOfRecords Second Secondary Index
|
||||
...
|
||||
==================================================================================================
|
||||
|
||||
|
||||
The INDEX type is a simple array of INT (4 bytes) where each entry represents the offset of the record in the datafile.
|
||||
Thus, INDEX[0] gives you the offset for the first record of that index.
|
||||
|
||||
|
||||
3. Data File Structure
|
||||
----------------------
|
||||
|
||||
The data file is a simple data pool from which structured data is pulled. The file begins with a 8 bytes long
|
||||
string signature that reads "NDETABLE", there rest is the data.
|
||||
|
||||
Records are fetched directly from the data pool using the offset stored in each appropriate index, using the
|
||||
beginning of the file as offset 0.
|
||||
|
||||
4. Records Structure
|
||||
--------------------
|
||||
|
||||
A record is a linked list made of 'field' elements, and so, when you retrieve a record, you actually retrieve the
|
||||
first field of a linked list, and then add each new field to the record structure (or class) as you read more of them,
|
||||
following the chain.
|
||||
|
||||
Record 0 is a list of the columns that make our table.
|
||||
Record 1 is a list of the indexes that are found in the index file.
|
||||
Record 2 is the first record of the table, it is a list of fields of various types (ie, int, string, etc)
|
||||
|
||||
|
||||
5. Fields Structures
|
||||
--------------------
|
||||
|
||||
The following structures indicate offsets relative to the beginning of the (sub)structures.
|
||||
|
||||
Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 UCHAR 1 Column ID
|
||||
1 UCHAR 1 Field Type
|
||||
2 INT 4 Size of field data
|
||||
6 INT 4 Next field position in table data pool
|
||||
10 INT 4 Previous field position in table data pool
|
||||
14 FIELDDATA SizeOfFieldData Field data
|
||||
==================================================================================================
|
||||
|
||||
Field Types :
|
||||
|
||||
FIELD_UNDEFINED = 255
|
||||
FIELD_COLUMN = 0
|
||||
FIELD_INDEX = 1
|
||||
FIELD_REDIRECTOR = 2
|
||||
FIELD_STRING = 3
|
||||
FIELD_INTEGER = 4
|
||||
FIELD_BOOLEAN = 5
|
||||
FIELD_BINARY = 6
|
||||
FIELD_GUID = 7
|
||||
FIELD_FLOAT = 9
|
||||
FIELD_DATETIME = 10
|
||||
FIELD_LENGTH = 11
|
||||
|
||||
Field Data Types :
|
||||
|
||||
* Column Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 UCHAR 1 Column Field Type (ie, FIELD_INTEGER)
|
||||
1 UCHAR 1 Index unique values (0/1)
|
||||
2 UCHAR 1 Size of column name string
|
||||
3 STRING SizeOfColumnName Public name of the column
|
||||
==================================================================================================
|
||||
|
||||
* Index Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 INT 4 Offset of this index in the index file
|
||||
4 INT 4 Type of the field on which this index sorts (FIELD_UNDEFINED for the primary index)
|
||||
8 UCHAR 1 Size of index name string
|
||||
9 STRING SizeOfIndexName Public name of the index
|
||||
==================================================================================================
|
||||
|
||||
|
||||
* Redirector Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 INT 4 Redirection offset in the data pool
|
||||
==================================================================================================
|
||||
|
||||
If you read a redirector field, you should move to the appropriate offset and read a new field there.
|
||||
|
||||
* String Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 USHORT 2 Size of string
|
||||
2 STRING SizeOfString String
|
||||
==================================================================================================
|
||||
|
||||
* Integer Field, DateTime Field, Length Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 INT 4 Integer value
|
||||
==================================================================================================
|
||||
|
||||
Length fields are exactly the same as integer fields, but instead of holding some unknown quantity, they have been
|
||||
marked as lengths by the application.
|
||||
|
||||
For DateTime fields, the value is to be interpreted as a standard C time_t value.
|
||||
|
||||
* Boolean Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 UCHAR 1 Boolean value
|
||||
==================================================================================================
|
||||
|
||||
* Binary Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 USHORT 2 Size of binary data
|
||||
2 BINARY SizeOfBinaryData Binary data
|
||||
==================================================================================================
|
||||
|
||||
* GUID Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 GUID 16 Guid data
|
||||
==================================================================================================
|
||||
|
||||
* Float Field :
|
||||
|
||||
==================================================================================================
|
||||
Offset Data Type Size Field
|
||||
==================================================================================================
|
||||
0 FLOAT 4 Float value
|
||||
==================================================================================================
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef __CONFIG_MI
|
||||
#define __CONFIG_MI
|
||||
|
||||
extern class @{593DBA22 - D077 - 4976 - B952 - F4713655400B} @Object _predecl Config;
|
||||
extern class @{D4030282 - 3AAB - 4d87 - 878D - 12326FADFCD5} @Object ConfigItem;
|
||||
extern class @{24DEC283 - B76E - 4a36 - 8CCC - 9E24C46B6C73} @Object ConfigAttribute;
|
||||
|
||||
extern ConfigItem Config.getItem(String item_name);
|
||||
extern ConfigItem Config.getItemByGuid(String item_guid);
|
||||
extern ConfigItem Config.newItem(String item_name, String item_guid);
|
||||
|
||||
extern ConfigAttribute ConfigItem.getAttribute(String attr_name);
|
||||
extern ConfigAttribute ConfigItem.newAttribute(String attr_name, String default_val);
|
||||
extern String ConfigItem.getGuid(String attr_name);
|
||||
|
||||
extern ConfigAttribute.setData(String value);
|
||||
extern String ConfigAttribute.getData();
|
||||
extern ConfigAttribute.onDataChanged();
|
||||
extern ConfigItem ConfigAttribute.getParentItem();
|
||||
extern String ConfigAttribute.getAttributeName();
|
||||
|
||||
#endif
|
||||
2472
modern/resources/maki_compiler/Unknown (Winamp 5.03)/lib/std.mi
Normal file
2472
modern/resources/maki_compiler/Unknown (Winamp 5.03)/lib/std.mi
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,7 @@
|
|||
Scraped from https://web.archive.org/web/20051122102353/http://bluemars.planet-d.net/sdk/ wich was discovered via a link in Objects.pm from the decompiler.
|
||||
|
||||
The server had a number of binary files as well, which sadly the Internet Archvie did not capture.
|
||||
|
||||
wasabi-sdk500-b3.exe
|
||||
wasabi-sdk499g8.exe
|
||||
maki/mc.exe
|
||||
66
modern/src/maki-interpreter/tools/parse-mi.js
Normal file
66
modern/src/maki-interpreter/tools/parse-mi.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Based on extract_objects.pl from Ralf Engels<ralf.engels@gmx.de> Maki decompiler
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
|
||||
function parseFile(filePath) {
|
||||
const mi = fs.readFileSync(filePath, "utf8");
|
||||
const lines = mi.split("\n");
|
||||
|
||||
const objects = {};
|
||||
lines.forEach((line, lineNumber) => {
|
||||
const classDefinitionMatch = /\s*extern\s+class\s*\@\{(........\s?-\s?....\s?-\s?....\s?-\s?....\s?-\s?............)\}\s*\@\s*(.*?)\s+(.*?);/.exec(
|
||||
line
|
||||
);
|
||||
if (classDefinitionMatch) {
|
||||
const id = classDefinitionMatch[1].replace(/[-\s]/g, "");
|
||||
const parent = classDefinitionMatch[2];
|
||||
const name = classDefinitionMatch[3]
|
||||
.replace(/^_predecl /, "")
|
||||
.replace(/^&/, "");
|
||||
objects[name.toLowerCase()] = { id, name, parent, functions: [] };
|
||||
}
|
||||
|
||||
const methodMatch = /\s*extern(\s+.*)?\s+(.*)\.(.*)\((.*)\);/.exec(line);
|
||||
|
||||
if (methodMatch) {
|
||||
const result = methodMatch[1] == null ? "" : methodMatch[1].trim();
|
||||
const className = methodMatch[2].toLowerCase();
|
||||
const name = methodMatch[3];
|
||||
const rawArgs = methodMatch[4].split(/\s*,\s*/);
|
||||
const parameters = rawArgs.filter(Boolean).map(rawArg => {
|
||||
const argMatch = /^\s*(.*\s+)?(.*)\s*/.exec(rawArg);
|
||||
if (argMatch == null) {
|
||||
throw new Error(`Could not find args in ${rawArg} in ${line}`);
|
||||
}
|
||||
const type = argMatch[1];
|
||||
if (type == null) {
|
||||
// console.warn(`Could not find args name in ${fileName}:${lineNum} "${line}"`);
|
||||
return [argMatch[2], "unknown_arg_name"];
|
||||
}
|
||||
return [type.trim(), argMatch[2]];
|
||||
});
|
||||
if (objects[className] == null) {
|
||||
throw new Error(
|
||||
`"${className} not defined in ${filePath}:${lineNumber}. I have ${JSON.stringify(
|
||||
Object.keys(objects)
|
||||
)}`
|
||||
);
|
||||
}
|
||||
objects[className].functions.push({ result, name, parameters });
|
||||
}
|
||||
});
|
||||
|
||||
const objectIds = {};
|
||||
Object.keys(objects).forEach(normalizedName => {
|
||||
const { id, parent, functions, name } = objects[normalizedName];
|
||||
objectIds[id] = { parent, functions, name };
|
||||
});
|
||||
|
||||
return objectIds;
|
||||
}
|
||||
|
||||
module.exports = { parseFile };
|
||||
66
modern/src/maki-interpreter/tools/parse-mi.test.js
Normal file
66
modern/src/maki-interpreter/tools/parse-mi.test.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { objects } from "../objects";
|
||||
import { parseFile } from "../tools/parse-mi";
|
||||
import path from "path";
|
||||
|
||||
// Between myself and the author of the decompiler, a number of manual tweaks
|
||||
// have been made to our current object definitions. This function recreates
|
||||
// those tweaks so we can have an apples to apples comparison.
|
||||
function applyPatches(parsedObjects) {
|
||||
/*
|
||||
* From object.js
|
||||
*
|
||||
* > The std.mi has this set as void, but we checked in Winamp and confirmed it
|
||||
* > returns 0/1
|
||||
*/
|
||||
parsedObjects["5D0C5BB67DE14b1fA70F8D1659941941"].functions[5].result =
|
||||
"boolean";
|
||||
|
||||
/*
|
||||
* From Object.pm
|
||||
*
|
||||
* > note, std.mi does not have this parameter!
|
||||
*/
|
||||
parsedObjects.B4DCCFFF81FE4bcc961B720FD5BE0FFF.functions[0].parameters[0][1] =
|
||||
"onoff";
|
||||
|
||||
/*
|
||||
* From Object.pm
|
||||
*
|
||||
* > note, my std.mi did not contain this!
|
||||
*/
|
||||
parsedObjects.B4DCCFFF81FE4bcc961B720FD5BE0FFF.functions.push({
|
||||
parameters: [],
|
||||
name: "getCurCfgVal",
|
||||
result: "Int",
|
||||
});
|
||||
}
|
||||
|
||||
// In order to do traversal objects.js adds parent refrences.
|
||||
// For comparison, we don't want these.
|
||||
function orphan(objs) {
|
||||
const newObjs = {};
|
||||
Object.keys(objs).forEach(key => {
|
||||
const { parentClass, ...rest } = objs[key];
|
||||
newObjs[key] = rest;
|
||||
});
|
||||
return newObjs;
|
||||
}
|
||||
|
||||
test("parseFile()", () => {
|
||||
const compilers = path.join(__dirname, "../../../resources/maki_compiler/");
|
||||
|
||||
const lib30full = path.join(compilers, "v1.1.1.b3 (Winamp 3.0 full)/Lib/");
|
||||
const lib502 = path.join(compilers, "v1.1.13 (Winamp 5.02)/lib/");
|
||||
// const lib566 = path.join(compilers, "v1.2.0 (Winamp 5.66)/lib/");
|
||||
const libUnknown = path.join(compilers, "Unknown (Winamp 5.03)/lib/");
|
||||
|
||||
const parsedObjects = {
|
||||
...parseFile(path.join(lib502, "std.mi")),
|
||||
...parseFile(path.join(lib30full, "pldir.mi")),
|
||||
...parseFile(path.join(libUnknown, "config.mi")),
|
||||
};
|
||||
|
||||
applyPatches(parsedObjects);
|
||||
|
||||
expect(parsedObjects).toEqual(orphan(objects));
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue