mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
113 lines
3.5 KiB
TypeScript
113 lines
3.5 KiB
TypeScript
/** Source-built FILE records; no production decoder is used to construct them. */
|
|
export const residentMftAttribute = (
|
|
type: number,
|
|
id: number,
|
|
value: Buffer,
|
|
name = "",
|
|
): Buffer => {
|
|
const encodedName = Buffer.from(name, "utf16le");
|
|
const valueOffset = Math.ceil((24 + encodedName.length) / 8) * 8;
|
|
const attribute = Buffer.alloc(
|
|
Math.ceil((valueOffset + value.length) / 8) * 8,
|
|
);
|
|
attribute.writeUInt32LE(type);
|
|
attribute.writeUInt32LE(attribute.length, 4);
|
|
attribute.writeUInt8(name.length, 9);
|
|
attribute.writeUInt16LE(24, 10);
|
|
attribute.writeUInt16LE(id, 14);
|
|
attribute.writeUInt32LE(value.length, 16);
|
|
attribute.writeUInt16LE(valueOffset, 20);
|
|
encodedName.copy(attribute, 24);
|
|
value.copy(attribute, valueOffset);
|
|
return attribute;
|
|
};
|
|
|
|
export const mftListEntry = (
|
|
type: number,
|
|
id: number,
|
|
index: number,
|
|
sequence: number,
|
|
name = "",
|
|
lowestVcn = 0n,
|
|
): Buffer => {
|
|
const encodedName = Buffer.from(name, "utf16le");
|
|
const entry = Buffer.alloc(Math.ceil((26 + encodedName.length) / 8) * 8);
|
|
entry.writeUInt32LE(type);
|
|
entry.writeUInt16LE(entry.length, 4);
|
|
entry.writeUInt8(name.length, 6);
|
|
entry.writeUInt8(26, 7);
|
|
entry.writeBigUInt64LE(lowestVcn, 8);
|
|
entry.writeBigUInt64LE((BigInt(sequence) << 48n) | BigInt(index), 16);
|
|
entry.writeUInt16LE(id, 24);
|
|
encodedName.copy(entry, 26);
|
|
return entry;
|
|
};
|
|
|
|
export const mftFileNameAttribute = (id: number, name: string): Buffer => {
|
|
const encodedName = Buffer.from(name, "utf16le");
|
|
const value = Buffer.alloc(66 + encodedName.length);
|
|
value.writeBigUInt64LE((4n << 48n) | 9n);
|
|
value.writeBigUInt64LE(133000000000000001n, 8);
|
|
value.writeBigUInt64LE(9007199254740993n, 48);
|
|
value.writeUInt32LE(0x20, 56);
|
|
value.writeUInt8(name.length, 64);
|
|
value.writeUInt8(1, 65);
|
|
encodedName.copy(value, 66);
|
|
return residentMftAttribute(0x30, id, value);
|
|
};
|
|
|
|
export const mftFileRecord = (
|
|
index: number,
|
|
sequence: number,
|
|
attributes: readonly Buffer[],
|
|
{ baseReference = 0n, recordBytes = 1024 } = {},
|
|
): Buffer => {
|
|
const record = Buffer.alloc(recordBytes);
|
|
const sectors = recordBytes / 512;
|
|
const first = Math.ceil((48 + (sectors + 1) * 2) / 8) * 8;
|
|
record.write("FILE");
|
|
record.writeUInt16LE(48, 4);
|
|
record.writeUInt16LE(sectors + 1, 6);
|
|
record.writeUInt16LE(sequence, 16);
|
|
record.writeUInt16LE(1, 18);
|
|
record.writeUInt16LE(first, 20);
|
|
record.writeUInt16LE(1, 22);
|
|
record.writeUInt32LE(recordBytes, 28);
|
|
record.writeBigUInt64LE(baseReference, 32);
|
|
record.writeUInt16LE(
|
|
attributes.reduce(
|
|
(next, attribute) => Math.max(next, attribute.readUInt16LE(14) + 1),
|
|
0,
|
|
),
|
|
40,
|
|
);
|
|
record.writeUInt32LE(index, 44);
|
|
let cursor = first;
|
|
for (const attribute of attributes) {
|
|
if (cursor + attribute.length + 8 > recordBytes)
|
|
throw new Error("Fixture exceeds FILE record");
|
|
attribute.copy(record, cursor);
|
|
cursor += attribute.length;
|
|
}
|
|
record.writeUInt32LE(0xffffffff, cursor);
|
|
record.writeUInt32LE(cursor + 8, 24);
|
|
record.writeUInt16LE(0xa55a, 48);
|
|
for (let sector = 0; sector < sectors; sector += 1) {
|
|
const trailer = (sector + 1) * 512 - 2;
|
|
record.writeUInt16LE(record.readUInt16LE(trailer), 50 + sector * 2);
|
|
record.writeUInt16LE(0xa55a, trailer);
|
|
}
|
|
return record;
|
|
};
|
|
|
|
export const linkedMftFixture = (): Buffer =>
|
|
Buffer.concat([
|
|
mftFileRecord(0, 2, [
|
|
mftFileNameAttribute(1, "local"),
|
|
residentMftAttribute(0x20, 2, mftListEntry(0x30, 3, 1, 7)),
|
|
]),
|
|
mftFileRecord(1, 7, [mftFileNameAttribute(3, "linked")], {
|
|
baseReference: 2n << 48n,
|
|
}),
|
|
]);
|