@sattori/touhou-replay-parser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/byte-reader.d.ts +48 -0
- package/dist/byte-reader.d.ts.map +1 -0
- package/dist/byte-reader.js +129 -0
- package/dist/byte-reader.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/game-ids.d.ts +13 -0
- package/dist/game-ids.d.ts.map +1 -0
- package/dist/game-ids.js +56 -0
- package/dist/game-ids.js.map +1 -0
- package/dist/games/modern-body.d.ts +16 -0
- package/dist/games/modern-body.d.ts.map +1 -0
- package/dist/games/modern-body.js +21 -0
- package/dist/games/modern-body.js.map +1 -0
- package/dist/games/th06.d.ts +8 -0
- package/dist/games/th06.d.ts.map +1 -0
- package/dist/games/th06.js +85 -0
- package/dist/games/th06.js.map +1 -0
- package/dist/games/th07.d.ts +12 -0
- package/dist/games/th07.d.ts.map +1 -0
- package/dist/games/th07.js +138 -0
- package/dist/games/th07.js.map +1 -0
- package/dist/games/th08.d.ts +6 -0
- package/dist/games/th08.d.ts.map +1 -0
- package/dist/games/th08.js +118 -0
- package/dist/games/th08.js.map +1 -0
- package/dist/games/th09.d.ts +10 -0
- package/dist/games/th09.d.ts.map +1 -0
- package/dist/games/th09.js +110 -0
- package/dist/games/th09.js.map +1 -0
- package/dist/games/th095.d.ts +7 -0
- package/dist/games/th095.d.ts.map +1 -0
- package/dist/games/th095.js +39 -0
- package/dist/games/th095.js.map +1 -0
- package/dist/games/th10.d.ts +4 -0
- package/dist/games/th10.d.ts.map +1 -0
- package/dist/games/th10.js +50 -0
- package/dist/games/th10.js.map +1 -0
- package/dist/games/th11.d.ts +4 -0
- package/dist/games/th11.d.ts.map +1 -0
- package/dist/games/th11.js +49 -0
- package/dist/games/th11.js.map +1 -0
- package/dist/games/th12.d.ts +4 -0
- package/dist/games/th12.d.ts.map +1 -0
- package/dist/games/th12.js +58 -0
- package/dist/games/th12.js.map +1 -0
- package/dist/games/th125.d.ts +6 -0
- package/dist/games/th125.d.ts.map +1 -0
- package/dist/games/th125.js +39 -0
- package/dist/games/th125.js.map +1 -0
- package/dist/games/th128.d.ts +9 -0
- package/dist/games/th128.d.ts.map +1 -0
- package/dist/games/th128.js +79 -0
- package/dist/games/th128.js.map +1 -0
- package/dist/games/th13-14.d.ts +17 -0
- package/dist/games/th13-14.d.ts.map +1 -0
- package/dist/games/th13-14.js +110 -0
- package/dist/games/th13-14.js.map +1 -0
- package/dist/games/th143.d.ts +7 -0
- package/dist/games/th143.d.ts.map +1 -0
- package/dist/games/th143.js +39 -0
- package/dist/games/th143.js.map +1 -0
- package/dist/games/th15.d.ts +4 -0
- package/dist/games/th15.d.ts.map +1 -0
- package/dist/games/th15.js +49 -0
- package/dist/games/th15.js.map +1 -0
- package/dist/games/th16.d.ts +4 -0
- package/dist/games/th16.d.ts.map +1 -0
- package/dist/games/th16.js +51 -0
- package/dist/games/th16.js.map +1 -0
- package/dist/games/th17.d.ts +4 -0
- package/dist/games/th17.d.ts.map +1 -0
- package/dist/games/th17.js +49 -0
- package/dist/games/th17.js.map +1 -0
- package/dist/games/th18.d.ts +4 -0
- package/dist/games/th18.d.ts.map +1 -0
- package/dist/games/th18.js +58 -0
- package/dist/games/th18.js.map +1 -0
- package/dist/games/th20.d.ts +22 -0
- package/dist/games/th20.d.ts.map +1 -0
- package/dist/games/th20.js +41 -0
- package/dist/games/th20.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/lzss.d.ts +16 -0
- package/dist/lzss.d.ts.map +1 -0
- package/dist/lzss.js +147 -0
- package/dist/lzss.js.map +1 -0
- package/dist/types.d.ts +125 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/dist/userdata.d.ts +24 -0
- package/dist/userdata.d.ts.map +1 -0
- package/dist/userdata.js +52 -0
- package/dist/userdata.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"th18.d.ts","sourceRoot":"","sources":["../../src/games/th18.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAIjH,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAwD5D"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ByteReader } from "../byte-reader.js";
|
|
2
|
+
import { readBufferedUint32LE } from "../lzss.js";
|
|
3
|
+
import { readModernUserdata } from "../userdata.js";
|
|
4
|
+
import { emptySplit, normalizeText, resourceCount } from "../types.js";
|
|
5
|
+
import { REPLAY_GAME_TITLES } from "../game-ids.js";
|
|
6
|
+
import { decodeModernBody } from "./modern-body.js";
|
|
7
|
+
/** t18r (東方虹龍洞, UM) decoder. Ported from Read_t18r in threplay. */
|
|
8
|
+
export function parseTh18(original) {
|
|
9
|
+
const decodedata = decodeModernBody(original, { blockSize: 0x400, base: 0x5c, add: 0xe1 }, { blockSize: 0x100, base: 0x7d, add: 0x3a });
|
|
10
|
+
const splits = [];
|
|
11
|
+
let stageOffset = 0xc8;
|
|
12
|
+
let frameCount = 0;
|
|
13
|
+
const stageCount = Math.min(decodedata[0xa8] ?? 0, 6);
|
|
14
|
+
for (let i = 0; i < stageCount; i++) {
|
|
15
|
+
const split = emptySplit();
|
|
16
|
+
split.stage = decodedata[stageOffset] ?? null;
|
|
17
|
+
split.score = readBufferedUint32LE(decodedata, stageOffset + 0x88) * 10;
|
|
18
|
+
split.power = (readBufferedUint32LE(decodedata, stageOffset + 0xc4) / 100).toFixed(2);
|
|
19
|
+
split.piv = readBufferedUint32LE(decodedata, stageOffset + 0xbc);
|
|
20
|
+
const lives = decodedata[stageOffset + 0xd4] ?? 0;
|
|
21
|
+
const livePieces = decodedata[stageOffset + 0xd8] ?? 0;
|
|
22
|
+
const bombs = decodedata[stageOffset + 0xe4] ?? 0;
|
|
23
|
+
const bombPieces = decodedata[stageOffset + 0xe8] ?? 0;
|
|
24
|
+
const cards = [];
|
|
25
|
+
for (let cardByteOffset = 0x160; cardByteOffset < 1376; cardByteOffset += 4) {
|
|
26
|
+
const cardId = readBufferedUint32LE(decodedata, stageOffset + cardByteOffset);
|
|
27
|
+
if (cardId === 0xffffffff)
|
|
28
|
+
break;
|
|
29
|
+
cards.push(cardId);
|
|
30
|
+
}
|
|
31
|
+
const active = readBufferedUint32LE(decodedata, stageOffset + 2400);
|
|
32
|
+
split.additional = { cards, active };
|
|
33
|
+
split.lives = resourceCount(lives, livePieces, 3);
|
|
34
|
+
split.graze = 0;
|
|
35
|
+
split.bombs = resourceCount(bombs, bombPieces, 3);
|
|
36
|
+
const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
|
|
37
|
+
split.frameCount = stageFrameCount;
|
|
38
|
+
splits.push(split);
|
|
39
|
+
frameCount += stageFrameCount;
|
|
40
|
+
stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0x126c;
|
|
41
|
+
}
|
|
42
|
+
const userdata = readModernUserdata(new ByteReader(original));
|
|
43
|
+
return {
|
|
44
|
+
game: "th18",
|
|
45
|
+
gameTitle: REPLAY_GAME_TITLES.th18,
|
|
46
|
+
formatVersion: null,
|
|
47
|
+
player: normalizeText(userdata.name),
|
|
48
|
+
date: normalizeText(userdata.date),
|
|
49
|
+
character: normalizeText(userdata.character),
|
|
50
|
+
difficulty: normalizeText(userdata.difficulty),
|
|
51
|
+
stage: normalizeText(userdata.stage),
|
|
52
|
+
score: userdata.score,
|
|
53
|
+
cleared: userdata.stage.includes("Clear"),
|
|
54
|
+
splits,
|
|
55
|
+
frameCount: stageCount > 0 ? frameCount : null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=th18.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"th18.js","sourceRoot":"","sources":["../../src/games/th18.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAA4C,MAAM,aAAa,CAAC;AACjH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,mEAAmE;AACnE,MAAM,UAAU,SAAS,CAAC,QAAoB;IAC5C,MAAM,UAAU,GAAG,gBAAgB,CACjC,QAAQ,EACR,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAC3C,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAC5C,CAAC;IAEF,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,IAAI,WAAW,GAAG,IAAI,CAAC;IACvB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC;QAC9C,KAAK,CAAC,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxE,KAAK,CAAC,KAAK,GAAG,CAAC,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtF,KAAK,CAAC,GAAG,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QAEvD,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,cAAc,GAAG,KAAK,EAAE,cAAc,GAAG,IAAI,EAAE,cAAc,IAAI,CAAC,EAAE,CAAC;YAC5E,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,cAAc,CAAC,CAAC;YAC9E,IAAI,MAAM,KAAK,UAAU;gBAAE,MAAM;YACjC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,CAAC;QACpE,KAAK,CAAC,UAAU,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACrC,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;QAClD,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;QAChB,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;QAClD,MAAM,eAAe,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,GAAG,CAAC,CAAC;QAC5E,KAAK,CAAC,UAAU,GAAG,eAAe,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,UAAU,IAAI,eAAe,CAAC;QAC9B,WAAW,IAAI,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC;IAC9E,CAAC;IAED,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE9D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,kBAAkB,CAAC,IAAI;QAClC,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpC,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC5C,UAAU,EAAE,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC9C,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;QACzC,MAAM;QACN,UAAU,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;KAC/C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ParsedReplay } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* t20r (東方錦上京, FW) decoder.
|
|
4
|
+
*
|
|
5
|
+
* threplay (raviddog/threplay, the upstream this package was ported from)
|
|
6
|
+
* only covers up to th18; th20 is implemented based on this package's own
|
|
7
|
+
* investigation. The USER section layout (player name, date, character,
|
|
8
|
+
* difficulty, stage, score after JumpToUser(12)) has been confirmed to be
|
|
9
|
+
* identical to th10-th18 by cross-checking real replays
|
|
10
|
+
* (`touhou-recorder/games/th20/replay/*.rpy`) against screenshots.
|
|
11
|
+
*
|
|
12
|
+
* On the other hand, the "per-stage breakdown via XOR decoding + LZSS
|
|
13
|
+
* decompression using the length/dlength at header offset 0x1c/0x20" present
|
|
14
|
+
* in th10-th18 does not appear to carry the same meaning in th20: across all
|
|
15
|
+
* 3 samples on hand, the decompressed size stays constant (256 bytes)
|
|
16
|
+
* regardless of progress (likely because a format change moved that data
|
|
17
|
+
* elsewhere). Since the stage-breakdown structure has not been analyzed yet,
|
|
18
|
+
* `splits` always returns an empty array (name/date/character/difficulty/stage/score
|
|
19
|
+
* can still be obtained).
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseTh20(original: Uint8Array): ParsedReplay;
|
|
22
|
+
//# sourceMappingURL=th20.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"th20.d.ts","sourceRoot":"","sources":["../../src/games/th20.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAiB5D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ByteReader } from "../byte-reader.js";
|
|
2
|
+
import { readModernUserdata } from "../userdata.js";
|
|
3
|
+
import { normalizeText } from "../types.js";
|
|
4
|
+
import { REPLAY_GAME_TITLES } from "../game-ids.js";
|
|
5
|
+
/**
|
|
6
|
+
* t20r (東方錦上京, FW) decoder.
|
|
7
|
+
*
|
|
8
|
+
* threplay (raviddog/threplay, the upstream this package was ported from)
|
|
9
|
+
* only covers up to th18; th20 is implemented based on this package's own
|
|
10
|
+
* investigation. The USER section layout (player name, date, character,
|
|
11
|
+
* difficulty, stage, score after JumpToUser(12)) has been confirmed to be
|
|
12
|
+
* identical to th10-th18 by cross-checking real replays
|
|
13
|
+
* (`touhou-recorder/games/th20/replay/*.rpy`) against screenshots.
|
|
14
|
+
*
|
|
15
|
+
* On the other hand, the "per-stage breakdown via XOR decoding + LZSS
|
|
16
|
+
* decompression using the length/dlength at header offset 0x1c/0x20" present
|
|
17
|
+
* in th10-th18 does not appear to carry the same meaning in th20: across all
|
|
18
|
+
* 3 samples on hand, the decompressed size stays constant (256 bytes)
|
|
19
|
+
* regardless of progress (likely because a format change moved that data
|
|
20
|
+
* elsewhere). Since the stage-breakdown structure has not been analyzed yet,
|
|
21
|
+
* `splits` always returns an empty array (name/date/character/difficulty/stage/score
|
|
22
|
+
* can still be obtained).
|
|
23
|
+
*/
|
|
24
|
+
export function parseTh20(original) {
|
|
25
|
+
const userdata = readModernUserdata(new ByteReader(original));
|
|
26
|
+
return {
|
|
27
|
+
game: "th20",
|
|
28
|
+
gameTitle: REPLAY_GAME_TITLES.th20,
|
|
29
|
+
formatVersion: null,
|
|
30
|
+
player: normalizeText(userdata.name),
|
|
31
|
+
date: normalizeText(userdata.date),
|
|
32
|
+
character: normalizeText(userdata.character),
|
|
33
|
+
difficulty: normalizeText(userdata.difficulty),
|
|
34
|
+
stage: normalizeText(userdata.stage),
|
|
35
|
+
score: userdata.score,
|
|
36
|
+
cleared: userdata.stage.includes("Clear"),
|
|
37
|
+
splits: [],
|
|
38
|
+
frameCount: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=th20.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"th20.js","sourceRoot":"","sources":["../../src/games/th20.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,SAAS,CAAC,QAAoB;IAC5C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE9D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,kBAAkB,CAAC,IAAI;QAClC,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpC,IAAI,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC5C,UAAU,EAAE,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC9C,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;QACzC,MAAM,EAAE,EAAE;QACV,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReplayParseResult } from "./types.js";
|
|
2
|
+
export * from "./game-ids.js";
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
export { ReplayCorruptError } from "./errors.js";
|
|
5
|
+
export declare function parseReplay(data: Uint8Array): ReplayParseResult;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAElE,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA6BjD,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,iBAAiB,CAyB/D"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ReplayCorruptError } from "./errors.js";
|
|
2
|
+
import { parseTh06 } from "./games/th06.js";
|
|
3
|
+
import { parseTh07 } from "./games/th07.js";
|
|
4
|
+
import { parseTh08 } from "./games/th08.js";
|
|
5
|
+
import { parseTh09 } from "./games/th09.js";
|
|
6
|
+
import { parseTh095 } from "./games/th095.js";
|
|
7
|
+
import { parseTh125 } from "./games/th125.js";
|
|
8
|
+
import { parseTh128 } from "./games/th128.js";
|
|
9
|
+
import { parseTh143Family } from "./games/th143.js";
|
|
10
|
+
import { parseTh10 } from "./games/th10.js";
|
|
11
|
+
import { parseTh11 } from "./games/th11.js";
|
|
12
|
+
import { parseTh12 } from "./games/th12.js";
|
|
13
|
+
import { parseTh1314 } from "./games/th13-14.js";
|
|
14
|
+
import { parseTh15 } from "./games/th15.js";
|
|
15
|
+
import { parseTh16 } from "./games/th16.js";
|
|
16
|
+
import { parseTh17 } from "./games/th17.js";
|
|
17
|
+
import { parseTh18 } from "./games/th18.js";
|
|
18
|
+
import { parseTh20 } from "./games/th20.js";
|
|
19
|
+
export * from "./game-ids.js";
|
|
20
|
+
export * from "./types.js";
|
|
21
|
+
export { ReplayCorruptError } from "./errors.js";
|
|
22
|
+
function magicOf(buffer) {
|
|
23
|
+
return Array.from(buffer.subarray(0, 4), (b) => String.fromCharCode(b)).join("");
|
|
24
|
+
}
|
|
25
|
+
const DECODERS = {
|
|
26
|
+
T6RP: parseTh06,
|
|
27
|
+
T7RP: parseTh07,
|
|
28
|
+
T8RP: parseTh08,
|
|
29
|
+
T9RP: parseTh09,
|
|
30
|
+
t95r: parseTh095,
|
|
31
|
+
t125: parseTh125,
|
|
32
|
+
"128r": parseTh128,
|
|
33
|
+
t143: (buffer) => parseTh143Family(buffer, "th143"),
|
|
34
|
+
t156: (buffer) => parseTh143Family(buffer, "th165"),
|
|
35
|
+
t10r: parseTh10,
|
|
36
|
+
t11r: parseTh11,
|
|
37
|
+
t12r: parseTh12,
|
|
38
|
+
t13r: parseTh1314,
|
|
39
|
+
t15r: parseTh15,
|
|
40
|
+
t16r: parseTh16,
|
|
41
|
+
t17r: parseTh17,
|
|
42
|
+
t18r: parseTh18,
|
|
43
|
+
t20r: parseTh20,
|
|
44
|
+
};
|
|
45
|
+
export function parseReplay(data) {
|
|
46
|
+
if (data.length < 4) {
|
|
47
|
+
return { ok: false, error: { code: "too_short", message: "file is shorter than the 4-byte magic header" } };
|
|
48
|
+
}
|
|
49
|
+
const magic = magicOf(data);
|
|
50
|
+
const decode = DECODERS[magic];
|
|
51
|
+
if (!decode) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error: { code: "unknown_magic", message: `unrecognized replay magic: ${JSON.stringify(magic)}` },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const replay = decode(data);
|
|
59
|
+
return { ok: true, replay };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof ReplayCorruptError) {
|
|
63
|
+
return { ok: false, error: { code: "corrupt", message: error.message } };
|
|
64
|
+
}
|
|
65
|
+
// Fail safe on unknown exceptions too — never throw.
|
|
66
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
67
|
+
return { ok: false, error: { code: "corrupt", message: `unexpected error while parsing: ${message}` } };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lzss.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function assertSaneAllocSize(n: number, what: string): void;
|
|
2
|
+
/**
|
|
3
|
+
* An LZSS-family decompressor (13-bit dictionary / 4-bit run length) ported
|
|
4
|
+
* from decompress() in threp (https://github.com/Fluorohydride/threp/blob/master/common.cpp).
|
|
5
|
+
* `length` is the input (compressed) byte count, `outLength` is the expected decompressed byte count.
|
|
6
|
+
*/
|
|
7
|
+
export declare function decompress(buffer: Uint8Array, length: number, outLength: number): Uint8Array;
|
|
8
|
+
/**
|
|
9
|
+
* Ported from threp's decode() (block-wise XOR deobfuscation).
|
|
10
|
+
* Rewrites the first `length` bytes of `buffer` in place.
|
|
11
|
+
*/
|
|
12
|
+
export declare function xorBlockDecode(buffer: Uint8Array, length: number, blockSizeInit: number, baseInit: number, add: number): void;
|
|
13
|
+
/** Decodes the simple additive-key scheme used by the older format (th06-09). */
|
|
14
|
+
export declare function additiveKeyDecode(buffer: Uint8Array, start: number, initialKey: number, increment: number): void;
|
|
15
|
+
export declare function readBufferedUint32LE(buffer: Uint8Array, offset: number): number;
|
|
16
|
+
//# sourceMappingURL=lzss.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lzss.d.ts","sourceRoot":"","sources":["../src/lzss.ts"],"names":[],"mappings":"AASA,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAIjE;AAoCD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CA0C5F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAqC7H;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAMhH;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAO/E"}
|
package/dist/lzss.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ReplayCorruptError } from "./errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Upper bound (64MiB) used to sanity-check decompressed sizes.
|
|
4
|
+
* The header's length/dlength fields are untrusted external input, so
|
|
5
|
+
* without this guard an invalid huge value would attempt an unbounded allocation.
|
|
6
|
+
*/
|
|
7
|
+
const MAX_ALLOC_BYTES = 64 * 1024 * 1024;
|
|
8
|
+
export function assertSaneAllocSize(n, what) {
|
|
9
|
+
if (!Number.isFinite(n) || n < 0 || n > MAX_ALLOC_BYTES) {
|
|
10
|
+
throw new ReplayCorruptError(`implausible ${what}: ${n}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function getBit(buffer, length, cursor, bitLength) {
|
|
14
|
+
// https://github.com/Fluorohydride/thprac/blob/master/common.cpp (ported from get_bit in common.cpp)
|
|
15
|
+
let result = 0;
|
|
16
|
+
for (let i = 0; i < bitLength; i++) {
|
|
17
|
+
result <<= 1;
|
|
18
|
+
if (cursor.pointer >= length) {
|
|
19
|
+
// The original implementation can read past the buffer boundary, but this
|
|
20
|
+
// is harmless because the caller immediately checks `pointer >= length`
|
|
21
|
+
// and stops. Here we fail safe by zero-filling.
|
|
22
|
+
cursor.filter >>= 1;
|
|
23
|
+
if (cursor.filter === 0) {
|
|
24
|
+
cursor.pointer++;
|
|
25
|
+
cursor.filter = 0x80;
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const current = buffer[cursor.pointer];
|
|
30
|
+
if ((current & cursor.filter) !== 0) {
|
|
31
|
+
result |= 1;
|
|
32
|
+
}
|
|
33
|
+
cursor.filter >>= 1;
|
|
34
|
+
if (cursor.filter === 0) {
|
|
35
|
+
cursor.pointer++;
|
|
36
|
+
cursor.filter = 0x80;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result >>> 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* An LZSS-family decompressor (13-bit dictionary / 4-bit run length) ported
|
|
43
|
+
* from decompress() in threp (https://github.com/Fluorohydride/threp/blob/master/common.cpp).
|
|
44
|
+
* `length` is the input (compressed) byte count, `outLength` is the expected decompressed byte count.
|
|
45
|
+
*/
|
|
46
|
+
export function decompress(buffer, length, outLength) {
|
|
47
|
+
assertSaneAllocSize(outLength, "decompressed length");
|
|
48
|
+
// length is an untrusted value read from the header. A value exceeding the
|
|
49
|
+
// buffer's actual size indicates corrupt data, and using it as-is would let
|
|
50
|
+
// the loop run for an unreasonably long time relative to the actual data
|
|
51
|
+
// size (a potential DoS), so we clamp it here to a physically possible value.
|
|
52
|
+
const boundedLength = Math.min(length, buffer.length);
|
|
53
|
+
const decoded = new Uint8Array(outLength);
|
|
54
|
+
const dict = new Uint8Array(0x2010);
|
|
55
|
+
const cursor = { pointer: 0, filter: 0x80 };
|
|
56
|
+
let dest = 0;
|
|
57
|
+
while (cursor.pointer < boundedLength && dest < outLength) {
|
|
58
|
+
const flag = getBit(buffer, boundedLength, cursor, 1);
|
|
59
|
+
if (cursor.pointer >= boundedLength)
|
|
60
|
+
break;
|
|
61
|
+
if (flag !== 0) {
|
|
62
|
+
const byte = getBit(buffer, boundedLength, cursor, 8);
|
|
63
|
+
if (cursor.pointer >= boundedLength)
|
|
64
|
+
break;
|
|
65
|
+
decoded[dest] = byte;
|
|
66
|
+
dict[dest & 0x1fff] = byte;
|
|
67
|
+
dest++;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const indexBits = getBit(buffer, boundedLength, cursor, 13);
|
|
71
|
+
if (cursor.pointer >= boundedLength)
|
|
72
|
+
break;
|
|
73
|
+
const index = indexBits - 1;
|
|
74
|
+
let runLength = getBit(buffer, boundedLength, cursor, 4);
|
|
75
|
+
if (cursor.pointer >= boundedLength)
|
|
76
|
+
break;
|
|
77
|
+
runLength += 3;
|
|
78
|
+
for (let i = 0; i < runLength && dest < outLength; i++) {
|
|
79
|
+
// index can be negative or out of range for invalid input, but
|
|
80
|
+
// out-of-range TypedArray read/write does not throw and is
|
|
81
|
+
// coerced to undefined -> 0, so this is safe.
|
|
82
|
+
const value = dict[(index + i) & 0x1fff] ?? 0;
|
|
83
|
+
dict[dest & 0x1fff] = value;
|
|
84
|
+
decoded[dest] = value;
|
|
85
|
+
dest++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return decoded;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Ported from threp's decode() (block-wise XOR deobfuscation).
|
|
93
|
+
* Rewrites the first `length` bytes of `buffer` in place.
|
|
94
|
+
*/
|
|
95
|
+
export function xorBlockDecode(buffer, length, blockSizeInit, baseInit, add) {
|
|
96
|
+
assertSaneAllocSize(length, "xorBlockDecode length");
|
|
97
|
+
if (length > buffer.length) {
|
|
98
|
+
throw new ReplayCorruptError(`xorBlockDecode: length ${length} exceeds buffer size ${buffer.length}`);
|
|
99
|
+
}
|
|
100
|
+
const tbuf = buffer.slice(0, length);
|
|
101
|
+
let blockSize = blockSizeInit;
|
|
102
|
+
let p = 0;
|
|
103
|
+
let left = length;
|
|
104
|
+
if (left % blockSize < Math.floor(blockSize / 4)) {
|
|
105
|
+
left -= left % blockSize;
|
|
106
|
+
}
|
|
107
|
+
left -= length & 1;
|
|
108
|
+
let base = baseInit & 0xff;
|
|
109
|
+
while (left !== 0) {
|
|
110
|
+
if (left < blockSize)
|
|
111
|
+
blockSize = left;
|
|
112
|
+
let tp1 = p + blockSize - 1;
|
|
113
|
+
let tp2 = p + blockSize - 2;
|
|
114
|
+
let half = (blockSize + (blockSize & 1)) >> 1;
|
|
115
|
+
for (let i = 0; i < half; i++, p++) {
|
|
116
|
+
if (tp1 >= 0 && tp1 < buffer.length && p < tbuf.length) {
|
|
117
|
+
buffer[tp1] = (tbuf[p] ^ base) & 0xff;
|
|
118
|
+
}
|
|
119
|
+
base = (base + add) & 0xff;
|
|
120
|
+
tp1 -= 2;
|
|
121
|
+
}
|
|
122
|
+
half = blockSize >> 1;
|
|
123
|
+
for (let i = 0; i < half; i++, p++) {
|
|
124
|
+
if (tp2 >= 0 && tp2 < buffer.length && p < tbuf.length) {
|
|
125
|
+
buffer[tp2] = (tbuf[p] ^ base) & 0xff;
|
|
126
|
+
}
|
|
127
|
+
base = (base + add) & 0xff;
|
|
128
|
+
tp2 -= 2;
|
|
129
|
+
}
|
|
130
|
+
left -= blockSize;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Decodes the simple additive-key scheme used by the older format (th06-09). */
|
|
134
|
+
export function additiveKeyDecode(buffer, start, initialKey, increment) {
|
|
135
|
+
let key = initialKey & 0xff;
|
|
136
|
+
for (let i = start; i < buffer.length; i++) {
|
|
137
|
+
buffer[i] = (buffer[i] - key) & 0xff;
|
|
138
|
+
key = (key + increment) & 0xff;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export function readBufferedUint32LE(buffer, offset) {
|
|
142
|
+
if (offset < 0 || offset + 4 > buffer.length) {
|
|
143
|
+
throw new ReplayCorruptError(`readBufferedUint32LE out of range at ${offset} (length ${buffer.length})`);
|
|
144
|
+
}
|
|
145
|
+
return ((buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24)) >>> 0);
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=lzss.js.map
|
package/dist/lzss.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lzss.js","sourceRoot":"","sources":["../src/lzss.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { ReplayGameId } from "./game-ids.js";
|
|
2
|
+
/**
|
|
3
|
+
* A resource count made up of a "whole unit count" plus "fragments toward the
|
|
4
|
+
* next unit," as used for lives, bombs, etc. `pieces`/`maxPieces` are null for
|
|
5
|
+
* games without a fragment system, or when the data cannot be obtained for
|
|
6
|
+
* that game.
|
|
7
|
+
*
|
|
8
|
+
* Exception: th128 (妖精大戦争, GFW) uses this field as a percentage gauge —
|
|
9
|
+
* `count` holds the percentage (0-100+), `maxPieces` is always 100, and
|
|
10
|
+
* `pieces` is always null (see the comments in that file).
|
|
11
|
+
*/
|
|
12
|
+
export interface ReplayResourceCount {
|
|
13
|
+
/** The whole unit count (or a percentage, for th128 only). */
|
|
14
|
+
count: number;
|
|
15
|
+
/** Number of fragments collected toward the next unit. */
|
|
16
|
+
pieces: number | null;
|
|
17
|
+
/** The fragment maximum (denominator). Null if it cannot be determined. */
|
|
18
|
+
maxPieces: number | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A per-stage record. Games track different fields, so fields not tracked by
|
|
22
|
+
* a given game are null.
|
|
23
|
+
*/
|
|
24
|
+
export interface ReplayStageSplit {
|
|
25
|
+
/**
|
|
26
|
+
* Stage number (null if it cannot be determined). Most games record this
|
|
27
|
+
* as a snapshot taken "at the start of the stage," so fields like `score`
|
|
28
|
+
* effectively reflect the value "at the end of the previous stage"
|
|
29
|
+
* (this matches the value shown on the "Stage N" line of the original
|
|
30
|
+
* game's replay selection screen).
|
|
31
|
+
*/
|
|
32
|
+
stage: number | null;
|
|
33
|
+
/** Score at the time of this snapshot. */
|
|
34
|
+
score: number | null;
|
|
35
|
+
/** Power (kept as a string since notation differs by game, e.g. "1.00", "128"). */
|
|
36
|
+
power: string | null;
|
|
37
|
+
/** Game-specific score metric such as PIV (Point of Item Value). */
|
|
38
|
+
piv: number | null;
|
|
39
|
+
/** Lives. */
|
|
40
|
+
lives: ReplayResourceCount | null;
|
|
41
|
+
/** Bomb count. */
|
|
42
|
+
bombs: ReplayResourceCount | null;
|
|
43
|
+
/** Graze count. */
|
|
44
|
+
graze: number | null;
|
|
45
|
+
/**
|
|
46
|
+
* Game-specific extra info such as UFO color, trance, season, etc. Key
|
|
47
|
+
* names and value shapes differ by game (see the comments in each game's
|
|
48
|
+
* decoder implementation). Null for games without such data.
|
|
49
|
+
*/
|
|
50
|
+
additional: Record<string, number | string | (number | string)[]> | null;
|
|
51
|
+
/**
|
|
52
|
+
* Number of in-game frames played during this stage/segment (i.e. from
|
|
53
|
+
* this checkpoint up to the next one, or to the end of the replay for the
|
|
54
|
+
* last split). See `ParsedReplay.frameCount` for the fixed-60fps
|
|
55
|
+
* conversion to seconds and the games this is currently populated for.
|
|
56
|
+
* Null for games where this package does not (yet) know how to locate the
|
|
57
|
+
* per-frame input log.
|
|
58
|
+
*/
|
|
59
|
+
frameCount: number | null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The full set of information extractable from a `.rpy` file. `ReplayInfo` in
|
|
63
|
+
* `packages/shared` corresponds to a subset of this type (only the fields
|
|
64
|
+
* needed for Sattori's recording metadata display).
|
|
65
|
+
*/
|
|
66
|
+
export interface ParsedReplay {
|
|
67
|
+
game: ReplayGameId;
|
|
68
|
+
gameTitle: string;
|
|
69
|
+
/**
|
|
70
|
+
* The raw format/version byte read from the header (its meaning and offset
|
|
71
|
+
* differ by game). Can be used as a clue to identify replays recorded with
|
|
72
|
+
* an incompatible version of the original game (used by Sattori for #16
|
|
73
|
+
* detection). Null for games where this cannot be determined.
|
|
74
|
+
*/
|
|
75
|
+
formatVersion: number | null;
|
|
76
|
+
player: string | null;
|
|
77
|
+
/** Recording date/time, kept verbatim as it appears in the source data (e.g. "25/12/31"). */
|
|
78
|
+
date: string | null;
|
|
79
|
+
character: string | null;
|
|
80
|
+
difficulty: string | null;
|
|
81
|
+
/** The reached/recorded stage or scene notation (e.g. "Stage 6", "Extra"). */
|
|
82
|
+
stage: string | null;
|
|
83
|
+
score: number | null;
|
|
84
|
+
/**
|
|
85
|
+
* True/false only when a full clear (equivalent to "Player Wins") could be
|
|
86
|
+
* detected. Null for games/replay types with no way to determine this.
|
|
87
|
+
*/
|
|
88
|
+
cleared: boolean | null;
|
|
89
|
+
/** Per-stage records (empty array for games where this cannot be determined). */
|
|
90
|
+
splits: ReplayStageSplit[];
|
|
91
|
+
/**
|
|
92
|
+
* Total number of in-game frames the replay plays back (all recorded
|
|
93
|
+
* stages/segments summed). The main-series games run gameplay logic at a
|
|
94
|
+
* fixed 60 frames/sec, so dividing by 60 gives the playback duration in
|
|
95
|
+
* seconds. This is the number of frames the game itself will replay, and
|
|
96
|
+
* does not include any recording-pipeline overhead (menu automation,
|
|
97
|
+
* end-of-replay detection lag, etc.) on top of that.
|
|
98
|
+
* Null for games/replay types where this package does not (yet) know how
|
|
99
|
+
* to locate the per-frame input log.
|
|
100
|
+
*/
|
|
101
|
+
frameCount: number | null;
|
|
102
|
+
}
|
|
103
|
+
export type ReplayParseErrorCode =
|
|
104
|
+
/** The file is too short to even read the magic bytes. */
|
|
105
|
+
"too_short"
|
|
106
|
+
/** The first 4 bytes don't match any known Touhou replay magic. */
|
|
107
|
+
| "unknown_magic"
|
|
108
|
+
/** The magic is a known format, but the data that follows is invalid and cannot be safely parsed further. */
|
|
109
|
+
| "corrupt";
|
|
110
|
+
export interface ReplayParseError {
|
|
111
|
+
code: ReplayParseErrorCode;
|
|
112
|
+
message: string;
|
|
113
|
+
}
|
|
114
|
+
export type ReplayParseResult = {
|
|
115
|
+
ok: true;
|
|
116
|
+
replay: ParsedReplay;
|
|
117
|
+
} | {
|
|
118
|
+
ok: false;
|
|
119
|
+
error: ReplayParseError;
|
|
120
|
+
};
|
|
121
|
+
/** Strips padding whitespace from fixed-length fields. An empty string is treated as null. */
|
|
122
|
+
export declare function normalizeText(value: string): string | null;
|
|
123
|
+
export declare function emptySplit(): ReplayStageSplit;
|
|
124
|
+
export declare function resourceCount(count: number, pieces?: number | null, maxPieces?: number | null): ReplayResourceCount;
|
|
125
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,oEAAoE;IACpE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,aAAa;IACb,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClC,kBAAkB;IAClB,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClC,mBAAmB;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACzE;;;;;;;OAOG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,6FAA6F;IAC7F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,8EAA8E;IAC9E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;OAGG;IACH,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,iFAAiF;IACjF,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B;;;;;;;;;OASG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,MAAM,oBAAoB;AAC9B,0DAA0D;AACxD,WAAW;AACb,mEAAmE;GACjE,eAAe;AACjB,6GAA6G;GAC3G,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE5G,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG1D;AAED,wBAAgB,UAAU,IAAI,gBAAgB,CAY7C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,EAAE,SAAS,GAAE,MAAM,GAAG,IAAW,GAAG,mBAAmB,CAE/H"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Strips padding whitespace from fixed-length fields. An empty string is treated as null. */
|
|
2
|
+
export function normalizeText(value) {
|
|
3
|
+
const trimmed = value.trim();
|
|
4
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
5
|
+
}
|
|
6
|
+
export function emptySplit() {
|
|
7
|
+
return {
|
|
8
|
+
stage: null,
|
|
9
|
+
score: null,
|
|
10
|
+
power: null,
|
|
11
|
+
piv: null,
|
|
12
|
+
lives: null,
|
|
13
|
+
bombs: null,
|
|
14
|
+
graze: null,
|
|
15
|
+
additional: null,
|
|
16
|
+
frameCount: null,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function resourceCount(count, pieces = null, maxPieces = null) {
|
|
20
|
+
return { count, pieces, maxPieces };
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAyHA,8FAA8F;AAC9F,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI;QACX,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,MAAM,GAAkB,IAAI,EAAE,SAAS,GAAkB,IAAI;IACxG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ByteReader } from "./byte-reader.js";
|
|
2
|
+
/**
|
|
3
|
+
* Equivalent to JumpToUser in threplay. Reads the 4-byte offset value at
|
|
4
|
+
* `pointerFieldOffset`, confirms a "USER" marker exists at that position, and
|
|
5
|
+
* then advances the cursor just past it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function jumpToUser(reader: ByteReader, pointerFieldOffset: number): void;
|
|
8
|
+
/** Equivalent to `long.TryParse(text + "0", ...)`. Restores the omitted trailing digit before converting to a number. */
|
|
9
|
+
export declare function parseScoreWithTrailingZero(text: string): number | null;
|
|
10
|
+
export declare function parseIntStrict(text: string): number | null;
|
|
11
|
+
export interface CommonUserdataFields {
|
|
12
|
+
name: string;
|
|
13
|
+
date: string;
|
|
14
|
+
character: string;
|
|
15
|
+
difficulty: string;
|
|
16
|
+
stage: string;
|
|
17
|
+
score: number | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The USER section layout shared by th10 (東方風神録, MoF) through
|
|
21
|
+
* th18 (東方虹龍洞, UM) (ported from threplay's `Read_Userdata`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function readModernUserdata(reader: ByteReader): CommonUserdataFields;
|
|
24
|
+
//# sourceMappingURL=userdata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userdata.d.ts","sourceRoot":"","sources":["../src/userdata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAK9C;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAQ/E;AAED,yHAAyH;AACzH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEtE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK1D;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,oBAAoB,CAkB3E"}
|