@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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +166 -0
  3. package/dist/byte-reader.d.ts +48 -0
  4. package/dist/byte-reader.d.ts.map +1 -0
  5. package/dist/byte-reader.js +129 -0
  6. package/dist/byte-reader.js.map +1 -0
  7. package/dist/errors.d.ts +10 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +13 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/game-ids.d.ts +13 -0
  12. package/dist/game-ids.d.ts.map +1 -0
  13. package/dist/game-ids.js +56 -0
  14. package/dist/game-ids.js.map +1 -0
  15. package/dist/games/modern-body.d.ts +16 -0
  16. package/dist/games/modern-body.d.ts.map +1 -0
  17. package/dist/games/modern-body.js +21 -0
  18. package/dist/games/modern-body.js.map +1 -0
  19. package/dist/games/th06.d.ts +8 -0
  20. package/dist/games/th06.d.ts.map +1 -0
  21. package/dist/games/th06.js +85 -0
  22. package/dist/games/th06.js.map +1 -0
  23. package/dist/games/th07.d.ts +12 -0
  24. package/dist/games/th07.d.ts.map +1 -0
  25. package/dist/games/th07.js +138 -0
  26. package/dist/games/th07.js.map +1 -0
  27. package/dist/games/th08.d.ts +6 -0
  28. package/dist/games/th08.d.ts.map +1 -0
  29. package/dist/games/th08.js +118 -0
  30. package/dist/games/th08.js.map +1 -0
  31. package/dist/games/th09.d.ts +10 -0
  32. package/dist/games/th09.d.ts.map +1 -0
  33. package/dist/games/th09.js +110 -0
  34. package/dist/games/th09.js.map +1 -0
  35. package/dist/games/th095.d.ts +7 -0
  36. package/dist/games/th095.d.ts.map +1 -0
  37. package/dist/games/th095.js +39 -0
  38. package/dist/games/th095.js.map +1 -0
  39. package/dist/games/th10.d.ts +4 -0
  40. package/dist/games/th10.d.ts.map +1 -0
  41. package/dist/games/th10.js +50 -0
  42. package/dist/games/th10.js.map +1 -0
  43. package/dist/games/th11.d.ts +4 -0
  44. package/dist/games/th11.d.ts.map +1 -0
  45. package/dist/games/th11.js +49 -0
  46. package/dist/games/th11.js.map +1 -0
  47. package/dist/games/th12.d.ts +4 -0
  48. package/dist/games/th12.d.ts.map +1 -0
  49. package/dist/games/th12.js +58 -0
  50. package/dist/games/th12.js.map +1 -0
  51. package/dist/games/th125.d.ts +6 -0
  52. package/dist/games/th125.d.ts.map +1 -0
  53. package/dist/games/th125.js +39 -0
  54. package/dist/games/th125.js.map +1 -0
  55. package/dist/games/th128.d.ts +9 -0
  56. package/dist/games/th128.d.ts.map +1 -0
  57. package/dist/games/th128.js +79 -0
  58. package/dist/games/th128.js.map +1 -0
  59. package/dist/games/th13-14.d.ts +17 -0
  60. package/dist/games/th13-14.d.ts.map +1 -0
  61. package/dist/games/th13-14.js +110 -0
  62. package/dist/games/th13-14.js.map +1 -0
  63. package/dist/games/th143.d.ts +7 -0
  64. package/dist/games/th143.d.ts.map +1 -0
  65. package/dist/games/th143.js +39 -0
  66. package/dist/games/th143.js.map +1 -0
  67. package/dist/games/th15.d.ts +4 -0
  68. package/dist/games/th15.d.ts.map +1 -0
  69. package/dist/games/th15.js +49 -0
  70. package/dist/games/th15.js.map +1 -0
  71. package/dist/games/th16.d.ts +4 -0
  72. package/dist/games/th16.d.ts.map +1 -0
  73. package/dist/games/th16.js +51 -0
  74. package/dist/games/th16.js.map +1 -0
  75. package/dist/games/th17.d.ts +4 -0
  76. package/dist/games/th17.d.ts.map +1 -0
  77. package/dist/games/th17.js +49 -0
  78. package/dist/games/th17.js.map +1 -0
  79. package/dist/games/th18.d.ts +4 -0
  80. package/dist/games/th18.d.ts.map +1 -0
  81. package/dist/games/th18.js +58 -0
  82. package/dist/games/th18.js.map +1 -0
  83. package/dist/games/th20.d.ts +22 -0
  84. package/dist/games/th20.d.ts.map +1 -0
  85. package/dist/games/th20.js +41 -0
  86. package/dist/games/th20.js.map +1 -0
  87. package/dist/index.d.ts +6 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +70 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/lzss.d.ts +16 -0
  92. package/dist/lzss.d.ts.map +1 -0
  93. package/dist/lzss.js +147 -0
  94. package/dist/lzss.js.map +1 -0
  95. package/dist/types.d.ts +125 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +22 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/userdata.d.ts +24 -0
  100. package/dist/userdata.d.ts.map +1 -0
  101. package/dist/userdata.js +52 -0
  102. package/dist/userdata.js.map +1 -0
  103. package/package.json +41 -0
@@ -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
+ const UFO_COLORS = ["None", "Red", "Blue", "Green"];
8
+ /** t12r (東方星蓮船, UFO) decoder. Ported from Read_t12r in threplay. */
9
+ export function parseTh12(original) {
10
+ const decodedata = decodeModernBody(original, { blockSize: 0x800, base: 0x5e, add: 0xe1 }, { blockSize: 0x40, base: 0x7d, add: 0x3a });
11
+ const splits = [];
12
+ let stageOffset = 0x70;
13
+ let frameCount = 0;
14
+ const stageCount = Math.min(decodedata[0x58] ?? 0, 6);
15
+ for (let i = 0; i < stageCount; i++) {
16
+ const split = emptySplit();
17
+ split.stage = decodedata[stageOffset] ?? null;
18
+ split.score = readBufferedUint32LE(decodedata, stageOffset + 0xc) * 10;
19
+ split.power = (readBufferedUint32LE(decodedata, stageOffset + 0x10) / 100).toFixed(2);
20
+ split.piv = Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x14) / 1000) * 10;
21
+ const lives = decodedata[stageOffset + 0x18] ?? 0;
22
+ let livePieces = decodedata[stageOffset + 0x1a] ?? 0;
23
+ if (livePieces > 0)
24
+ livePieces -= 1;
25
+ const bombs = decodedata[stageOffset + 0x1c] ?? 0;
26
+ const bombPieces = decodedata[stageOffset + 0x1e] ?? 0;
27
+ const ufos = [];
28
+ for (let j = 0; j < 3; j++) {
29
+ const colorIndex = decodedata[stageOffset + 0x20 + j * 4] ?? 0;
30
+ ufos.push(UFO_COLORS[colorIndex] ?? "None");
31
+ }
32
+ split.additional = { ufoColors: ufos };
33
+ split.lives = resourceCount(lives, livePieces, 4);
34
+ split.bombs = resourceCount(bombs, bombPieces, 3);
35
+ split.graze = readBufferedUint32LE(decodedata, stageOffset + 0x44);
36
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
37
+ split.frameCount = stageFrameCount;
38
+ splits.push(split);
39
+ frameCount += stageFrameCount;
40
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0xa0;
41
+ }
42
+ const userdata = readModernUserdata(new ByteReader(original));
43
+ return {
44
+ game: "th12",
45
+ gameTitle: REPLAY_GAME_TITLES.th12,
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=th12.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th12.js","sourceRoot":"","sources":["../../src/games/th12.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * t125 (ダブルスポイラー ~ 東方文花帖, DS) decoder. Ported from Read_t125 in threplay.
4
+ */
5
+ export declare function parseTh125(original: Uint8Array): ParsedReplay;
6
+ //# sourceMappingURL=th125.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th125.d.ts","sourceRoot":"","sources":["../../src/games/th125.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG/D;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAgC7D"}
@@ -0,0 +1,39 @@
1
+ import { ByteReader } from "../byte-reader.js";
2
+ import { jumpToUser, parseIntStrict } from "../userdata.js";
3
+ import { normalizeText } from "../types.js";
4
+ import { REPLAY_GAME_TITLES } from "../game-ids.js";
5
+ /**
6
+ * t125 (ダブルスポイラー ~ 東方文花帖, DS) decoder. Ported from Read_t125 in threplay.
7
+ */
8
+ export function parseTh125(original) {
9
+ const reader = new ByteReader(original);
10
+ jumpToUser(reader, 12);
11
+ reader.readUint32LE();
12
+ reader.skip(4);
13
+ reader.readAnsiString();
14
+ reader.readAnsiString();
15
+ reader.skip(5);
16
+ const name = reader.readAnsiString();
17
+ reader.skip(5);
18
+ const date = reader.readAnsiString();
19
+ reader.skip(6);
20
+ const character = reader.readAnsiString();
21
+ const stage = reader.readAnsiString();
22
+ reader.skip(6);
23
+ const score = parseIntStrict(reader.readAnsiString());
24
+ return {
25
+ game: "th125",
26
+ gameTitle: REPLAY_GAME_TITLES.th125,
27
+ formatVersion: null,
28
+ player: normalizeText(name),
29
+ date: normalizeText(date),
30
+ character: normalizeText(character),
31
+ difficulty: null,
32
+ stage: normalizeText(stage),
33
+ score,
34
+ cleared: null,
35
+ splits: [],
36
+ frameCount: null,
37
+ };
38
+ }
39
+ //# sourceMappingURL=th125.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th125.js","sourceRoot":"","sources":["../../src/games/th125.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,QAAoB;IAC7C,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEvB,MAAM,CAAC,YAAY,EAAE,CAAC;IACtB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,CAAC,cAAc,EAAE,CAAC;IACxB,MAAM,CAAC,cAAc,EAAE,CAAC;IACxB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACtC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IAEtD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,SAAS,EAAE,kBAAkB,CAAC,KAAK;QACnC,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC;QAC3B,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC;QACzB,SAAS,EAAE,aAAa,CAAC,SAAS,CAAC;QACnC,UAAU,EAAE,IAAI;QAChB,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC;QAC3B,KAAK;QACL,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,EAAE;QACV,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * 128r (妖精大戦争 ~ 東方三月精, GFW) decoder. Ported from Read_128r in threplay.
4
+ * The USER section is read using the old-generation method, but the per-stage
5
+ * breakdown is a hybrid format using the same XOR block decoding + LZSS
6
+ * decompression pipeline as th10 onward.
7
+ */
8
+ export declare function parseTh128(original: Uint8Array): ParsedReplay;
9
+ //# sourceMappingURL=th128.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th128.d.ts","sourceRoot":"","sources":["../../src/games/th128.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAKjH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CA8D7D"}
@@ -0,0 +1,79 @@
1
+ import { ByteReader } from "../byte-reader.js";
2
+ import { ReplayCorruptError } from "../errors.js";
3
+ import { decompress, readBufferedUint32LE, xorBlockDecode } from "../lzss.js";
4
+ import { jumpToUser, parseScoreWithTrailingZero } from "../userdata.js";
5
+ import { emptySplit, normalizeText, resourceCount } from "../types.js";
6
+ import { REPLAY_GAME_TITLES } from "../game-ids.js";
7
+ const HEADER_SIZE = 36;
8
+ /**
9
+ * 128r (妖精大戦争 ~ 東方三月精, GFW) decoder. Ported from Read_128r in threplay.
10
+ * The USER section is read using the old-generation method, but the per-stage
11
+ * breakdown is a hybrid format using the same XOR block decoding + LZSS
12
+ * decompression pipeline as th10 onward.
13
+ */
14
+ export function parseTh128(original) {
15
+ const reader = new ByteReader(original);
16
+ jumpToUser(reader, 12);
17
+ reader.readUint32LE();
18
+ reader.skip(4);
19
+ reader.readAnsiString();
20
+ reader.readAnsiString();
21
+ reader.skip(5);
22
+ const name = reader.readAnsiString();
23
+ reader.skip(5);
24
+ const date = reader.readAnsiString();
25
+ reader.skip(6);
26
+ const stage = reader.readAnsiString();
27
+ reader.skip(5);
28
+ const difficulty = reader.readAnsiString();
29
+ reader.skip(6);
30
+ reader.readAnsiString(); // stage (duplicate, also discarded by the original implementation)
31
+ reader.skip(6);
32
+ const score = parseScoreWithTrailingZero(reader.readAnsiString());
33
+ if (original.length < HEADER_SIZE + 4) {
34
+ throw new ReplayCorruptError("file too short for 128r header");
35
+ }
36
+ const length = readBufferedUint32LE(original, 28);
37
+ const dlength = readBufferedUint32LE(original, 32);
38
+ const workBuffer = original.slice(HEADER_SIZE);
39
+ xorBlockDecode(workBuffer, length, 0x800, 0x5e, 0xe7);
40
+ xorBlockDecode(workBuffer, length, 0x80, 0x7d, 0x36);
41
+ const decodedata = decompress(workBuffer, length, dlength);
42
+ const splits = [];
43
+ let stageOffset = 0x70;
44
+ const stageCount = decodedata[0x58] ?? 0;
45
+ for (let i = 0; i < stageCount; i++) {
46
+ const split = emptySplit();
47
+ split.score = readBufferedUint32LE(decodedata, stageOffset + 0xc) * 10;
48
+ split.power = String(readBufferedUint32LE(decodedata, stageOffset + 0x10) + 1);
49
+ // th128 records lives/bombs as a percentage gauge rather than a count
50
+ // (ReplayResourceCount.count holds the percentage, maxPieces is fixed at 100).
51
+ split.lives = resourceCount(Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x80) / 100), null, 100);
52
+ split.bombs = resourceCount(Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x84) / 100), null, 100);
53
+ const freezeArea = readFloat32LE(decodedata, stageOffset + 0x88);
54
+ split.additional = { freezeAreaPercent: Math.trunc(freezeArea) };
55
+ splits.push(split);
56
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0x90;
57
+ }
58
+ return {
59
+ game: "th128",
60
+ gameTitle: REPLAY_GAME_TITLES.th128,
61
+ formatVersion: null,
62
+ player: normalizeText(name),
63
+ date: normalizeText(date),
64
+ character: null,
65
+ difficulty: normalizeText(difficulty),
66
+ stage: normalizeText(stage),
67
+ score,
68
+ cleared: null,
69
+ splits,
70
+ frameCount: null,
71
+ };
72
+ }
73
+ function readFloat32LE(buffer, offset) {
74
+ if (offset < 0 || offset + 4 > buffer.length) {
75
+ throw new ReplayCorruptError(`readFloat32LE out of range at ${offset}`);
76
+ }
77
+ return new DataView(buffer.buffer, buffer.byteOffset + offset, 4).getFloat32(0, true);
78
+ }
79
+ //# sourceMappingURL=th128.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th128.js","sourceRoot":"","sources":["../../src/games/th128.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * The t13r magic is shared by th13 (東方神霊廟, TD) and th14 (東方輝針城, DDC),
4
+ * and is distinguished by the version byte in the USER section (144 means TD,
5
+ * anything else means DDC) (ported from Read_t13r in threplay; the
6
+ * distinguishing logic follows the in-code comments).
7
+ *
8
+ * Note: threplay's Read_t13r calls `return Read_Userdata(ref replay)` at the
9
+ * end of the function, re-reading the USER section from a common layout that
10
+ * does not account for the bytes already consumed for TD/DDC detection (a
11
+ * 4-byte "which game" marker + a 1-byte version), causing name/date/character
12
+ * etc. to be overwritten from the wrong offsets (judged to be an upstream
13
+ * bug). This port instead keeps the result of the first read, which already
14
+ * reflects the TD/DDC detection logic, and does not perform this duplicate read.
15
+ */
16
+ export declare function parseTh1314(original: Uint8Array): ParsedReplay;
17
+ //# sourceMappingURL=th13-14.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th13-14.d.ts","sourceRoot":"","sources":["../../src/games/th13-14.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAIjH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAgG9D"}
@@ -0,0 +1,110 @@
1
+ import { ByteReader } from "../byte-reader.js";
2
+ import { readBufferedUint32LE } from "../lzss.js";
3
+ import { jumpToUser, parseScoreWithTrailingZero } 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
+ /**
8
+ * The t13r magic is shared by th13 (東方神霊廟, TD) and th14 (東方輝針城, DDC),
9
+ * and is distinguished by the version byte in the USER section (144 means TD,
10
+ * anything else means DDC) (ported from Read_t13r in threplay; the
11
+ * distinguishing logic follows the in-code comments).
12
+ *
13
+ * Note: threplay's Read_t13r calls `return Read_Userdata(ref replay)` at the
14
+ * end of the function, re-reading the USER section from a common layout that
15
+ * does not account for the bytes already consumed for TD/DDC detection (a
16
+ * 4-byte "which game" marker + a 1-byte version), causing name/date/character
17
+ * etc. to be overwritten from the wrong offsets (judged to be an upstream
18
+ * bug). This port instead keeps the result of the first read, which already
19
+ * reflects the TD/DDC detection logic, and does not perform this duplicate read.
20
+ */
21
+ export function parseTh1314(original) {
22
+ const reader = new ByteReader(original);
23
+ jumpToUser(reader, 12);
24
+ reader.readUint32LE();
25
+ reader.skip(4);
26
+ reader.skip(4); // which-game marker
27
+ const versionByte = reader.readUint8();
28
+ const isTd = versionByte === 144;
29
+ reader.readAnsiString();
30
+ reader.readAnsiString();
31
+ reader.skip(5);
32
+ const name = reader.readAnsiString();
33
+ reader.skip(5);
34
+ const date = reader.readAnsiString();
35
+ reader.skip(6);
36
+ const character = reader.readAnsiString();
37
+ reader.skip(5);
38
+ const difficulty = reader.readAnsiString();
39
+ const stage = reader.readAnsiString();
40
+ reader.skip(6);
41
+ const score = parseScoreWithTrailingZero(reader.readAnsiString());
42
+ const splits = [];
43
+ let frameCount = 0;
44
+ const decodedata = decodeModernBody(original, { blockSize: 0x400, base: 0x5c, add: 0xe1 }, { blockSize: 0x100, base: 0x7d, add: 0x3a });
45
+ let stageCount;
46
+ if (isTd) {
47
+ let stageOffset = 0x74;
48
+ stageCount = Math.min(decodedata[0x58] ?? 0, 6);
49
+ for (let i = 0; i < stageCount; i++) {
50
+ const split = emptySplit();
51
+ split.stage = decodedata[stageOffset] ?? null;
52
+ split.score = readBufferedUint32LE(decodedata, stageOffset + 0x1c) * 10;
53
+ split.power = (readBufferedUint32LE(decodedata, stageOffset + 0x44) / 100).toFixed(2);
54
+ split.piv = Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x38) / 1000) * 10;
55
+ const lives = decodedata[stageOffset + 0x50] ?? 0;
56
+ const livePieces = decodedata[stageOffset + 0x54] ?? 0;
57
+ const bombs = decodedata[stageOffset + 0x5c] ?? 0;
58
+ const bombPieces = decodedata[stageOffset + 0x60] ?? 0;
59
+ const trance = decodedata[stageOffset + 0x64] ?? 0;
60
+ split.additional = { trance, tranceMax: 600 };
61
+ split.lives = resourceCount(lives, livePieces, null);
62
+ split.graze = readBufferedUint32LE(decodedata, stageOffset + 0x2c);
63
+ split.bombs = resourceCount(bombs, bombPieces, 8);
64
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
65
+ split.frameCount = stageFrameCount;
66
+ splits.push(split);
67
+ frameCount += stageFrameCount;
68
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0xc4;
69
+ }
70
+ }
71
+ else {
72
+ let stageOffset = 0x94;
73
+ stageCount = Math.min(decodedata[0x78] ?? 0, 6);
74
+ for (let i = 0; i < stageCount; i++) {
75
+ const split = emptySplit();
76
+ split.stage = decodedata[stageOffset] ?? null;
77
+ split.score = readBufferedUint32LE(decodedata, stageOffset + 0x1c) * 10;
78
+ split.power = (readBufferedUint32LE(decodedata, stageOffset + 0x44) / 100).toFixed(2);
79
+ split.piv = Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x38) / 1000) * 10;
80
+ const lives = decodedata[stageOffset + 0x50] ?? 0;
81
+ const livePieces = decodedata[stageOffset + 0x54] ?? 0;
82
+ const bombs = decodedata[stageOffset + 0x5c] ?? 0;
83
+ const bombPieces = decodedata[stageOffset + 0x60] ?? 0;
84
+ split.lives = resourceCount(lives, livePieces, 3);
85
+ split.graze = readBufferedUint32LE(decodedata, stageOffset + 0x2c);
86
+ split.bombs = resourceCount(bombs, bombPieces, 8);
87
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
88
+ split.frameCount = stageFrameCount;
89
+ splits.push(split);
90
+ frameCount += stageFrameCount;
91
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0xdc;
92
+ }
93
+ }
94
+ const game = isTd ? "th13" : "th14";
95
+ return {
96
+ game,
97
+ gameTitle: REPLAY_GAME_TITLES[game],
98
+ formatVersion: versionByte,
99
+ player: normalizeText(name),
100
+ date: normalizeText(date),
101
+ character: normalizeText(character),
102
+ difficulty: normalizeText(difficulty),
103
+ stage: normalizeText(stage),
104
+ score,
105
+ cleared: stage.includes("Clear"),
106
+ splits,
107
+ frameCount: stageCount > 0 ? frameCount : null,
108
+ };
109
+ }
110
+ //# sourceMappingURL=th13-14.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th13-14.js","sourceRoot":"","sources":["../../src/games/th13-14.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * Shared decoder for t143 (弾幕アマノジャク, ISC) / t156 (秘封ナイトメアダイアリー, VD).
4
+ * Ported from Read_t143 in threplay (Read_t156 is just an alias that calls it).
5
+ */
6
+ export declare function parseTh143Family(original: Uint8Array, game: "th143" | "th165"): ParsedReplay;
7
+ //# sourceMappingURL=th143.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th143.d.ts","sourceRoot":"","sources":["../../src/games/th143.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG/D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,CA+B5F"}
@@ -0,0 +1,39 @@
1
+ import { ByteReader } from "../byte-reader.js";
2
+ import { jumpToUser, parseScoreWithTrailingZero } from "../userdata.js";
3
+ import { normalizeText } from "../types.js";
4
+ import { REPLAY_GAME_TITLES } from "../game-ids.js";
5
+ /**
6
+ * Shared decoder for t143 (弾幕アマノジャク, ISC) / t156 (秘封ナイトメアダイアリー, VD).
7
+ * Ported from Read_t143 in threplay (Read_t156 is just an alias that calls it).
8
+ */
9
+ export function parseTh143Family(original, game) {
10
+ const reader = new ByteReader(original);
11
+ jumpToUser(reader, 12);
12
+ reader.readUint32LE();
13
+ reader.skip(4);
14
+ reader.readAnsiString();
15
+ reader.readAnsiString();
16
+ reader.skip(5);
17
+ const name = reader.readAnsiString();
18
+ reader.skip(5);
19
+ const date = reader.readAnsiString();
20
+ const stagePart1 = reader.readAnsiString();
21
+ const stagePart2 = reader.readAnsiString();
22
+ reader.skip(6);
23
+ const score = parseScoreWithTrailingZero(reader.readAnsiString());
24
+ return {
25
+ game,
26
+ gameTitle: REPLAY_GAME_TITLES[game],
27
+ formatVersion: null,
28
+ player: normalizeText(name),
29
+ date: normalizeText(date),
30
+ character: null,
31
+ difficulty: null,
32
+ stage: normalizeText(`${stagePart1} ${stagePart2}`),
33
+ score,
34
+ cleared: null,
35
+ splits: [],
36
+ frameCount: null,
37
+ };
38
+ }
39
+ //# sourceMappingURL=th143.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th143.js","sourceRoot":"","sources":["../../src/games/th143.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAqB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAqB,MAAM,gBAAgB,CAAC;AAEvE;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAoB,EAAE,IAAuB;IAC5E,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEvB,MAAM,CAAC,YAAY,EAAE,CAAC;IACtB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,CAAC,cAAc,EAAE,CAAC;IACxB,MAAM,CAAC,cAAc,EAAE,CAAC;IACxB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrC,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC3C,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,KAAK,GAAG,0BAA0B,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IAElE,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,kBAAkB,CAAC,IAAoB,CAAC;QACnD,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC;QAC3B,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC;QACzB,SAAS,EAAE,IAAI;QACf,UAAU,EAAE,IAAI;QAChB,KAAK,EAAE,aAAa,CAAC,GAAG,UAAU,IAAI,UAAU,EAAE,CAAC;QACnD,KAAK;QACL,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,EAAE;QACV,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /** t15r (東方紺珠伝, LoLK) decoder. Ported from Read_t15r in threplay. */
3
+ export declare function parseTh15(original: Uint8Array): ParsedReplay;
4
+ //# sourceMappingURL=th15.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th15.d.ts","sourceRoot":"","sources":["../../src/games/th15.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAIjH,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CA+C5D"}
@@ -0,0 +1,49 @@
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
+ /** t15r (東方紺珠伝, LoLK) decoder. Ported from Read_t15r in threplay. */
8
+ export function parseTh15(original) {
9
+ const decodedata = decodeModernBody(original, { blockSize: 0x400, base: 0x5c, add: 0xe1 }, { blockSize: 0x100, base: 0x7d, add: 0x3a });
10
+ const splits = [];
11
+ let stageOffset = 0xa4;
12
+ let frameCount = 0;
13
+ const stageCount = Math.min(decodedata[0x88] ?? 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 + 0x30) * 10;
18
+ split.power = (readBufferedUint32LE(decodedata, stageOffset + 0x64) / 100).toFixed(2);
19
+ split.piv = Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x58) / 1000) * 10;
20
+ const lives = decodedata[stageOffset + 0x74] ?? 0;
21
+ const livePieces = decodedata[stageOffset + 0x78] ?? 0;
22
+ const bombs = decodedata[stageOffset + 0x80] ?? 0;
23
+ const bombPieces = decodedata[stageOffset + 0x84] ?? 0;
24
+ split.lives = resourceCount(lives, livePieces, 3);
25
+ split.graze = readBufferedUint32LE(decodedata, stageOffset + 0x40);
26
+ split.bombs = resourceCount(bombs, bombPieces, 8);
27
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
28
+ split.frameCount = stageFrameCount;
29
+ splits.push(split);
30
+ frameCount += stageFrameCount;
31
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0x238;
32
+ }
33
+ const userdata = readModernUserdata(new ByteReader(original));
34
+ return {
35
+ game: "th15",
36
+ gameTitle: REPLAY_GAME_TITLES.th15,
37
+ formatVersion: null,
38
+ player: normalizeText(userdata.name),
39
+ date: normalizeText(userdata.date),
40
+ character: normalizeText(userdata.character),
41
+ difficulty: normalizeText(userdata.difficulty),
42
+ stage: normalizeText(userdata.stage),
43
+ score: userdata.score,
44
+ cleared: userdata.stage.includes("Clear"),
45
+ splits,
46
+ frameCount: stageCount > 0 ? frameCount : null,
47
+ };
48
+ }
49
+ //# sourceMappingURL=th15.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th15.js","sourceRoot":"","sources":["../../src/games/th15.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /** t16r (東方天空璋, HSiFS) decoder. Ported from Read_t16r in threplay. */
3
+ export declare function parseTh16(original: Uint8Array): ParsedReplay;
4
+ //# sourceMappingURL=th16.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th16.d.ts","sourceRoot":"","sources":["../../src/games/th16.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAIjH,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAiD5D"}
@@ -0,0 +1,51 @@
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
+ /** t16r (東方天空璋, HSiFS) decoder. Ported from Read_t16r in threplay. */
8
+ export function parseTh16(original) {
9
+ const decodedata = decodeModernBody(original, { blockSize: 0x400, base: 0x5c, add: 0xe1 }, { blockSize: 0x100, base: 0x7d, add: 0x3a });
10
+ const splits = [];
11
+ let stageOffset = 0xa0;
12
+ let frameCount = 0;
13
+ const stageCount = Math.min(decodedata[0x80] ?? 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 + 0x34) * 10;
18
+ split.power = (readBufferedUint32LE(decodedata, stageOffset + 0x68) / 100).toFixed(2);
19
+ split.piv = Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x5c) / 1000) * 10;
20
+ const lives = decodedata[stageOffset + 0x78] ?? 0;
21
+ const bombs = decodedata[stageOffset + 0x84] ?? 0;
22
+ const bombPieces = decodedata[stageOffset + 0x88] ?? 0;
23
+ const season = readBufferedUint32LE(decodedata, stageOffset + 0x8c);
24
+ const seasonMax = readBufferedUint32LE(decodedata, stageOffset + 0x90);
25
+ split.additional = { season, seasonMax };
26
+ split.lives = resourceCount(lives);
27
+ split.graze = readBufferedUint32LE(decodedata, stageOffset + 0x44);
28
+ split.bombs = resourceCount(bombs, bombPieces, 5);
29
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
30
+ split.frameCount = stageFrameCount;
31
+ splits.push(split);
32
+ frameCount += stageFrameCount;
33
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0x294;
34
+ }
35
+ const userdata = readModernUserdata(new ByteReader(original));
36
+ return {
37
+ game: "th16",
38
+ gameTitle: REPLAY_GAME_TITLES.th16,
39
+ formatVersion: null,
40
+ player: normalizeText(userdata.name),
41
+ date: normalizeText(userdata.date),
42
+ character: normalizeText(userdata.character),
43
+ difficulty: normalizeText(userdata.difficulty),
44
+ stage: normalizeText(userdata.stage),
45
+ score: userdata.score,
46
+ cleared: userdata.stage.includes("Clear"),
47
+ splits,
48
+ frameCount: stageCount > 0 ? frameCount : null,
49
+ };
50
+ }
51
+ //# sourceMappingURL=th16.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th16.js","sourceRoot":"","sources":["../../src/games/th16.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /** t17r (東方鬼形獣, WBaWC) decoder. Ported from Read_t17r in threplay. */
3
+ export declare function parseTh17(original: Uint8Array): ParsedReplay;
4
+ //# sourceMappingURL=th17.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th17.d.ts","sourceRoot":"","sources":["../../src/games/th17.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAIjH,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CA+C5D"}
@@ -0,0 +1,49 @@
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
+ /** t17r (東方鬼形獣, WBaWC) decoder. Ported from Read_t17r in threplay. */
8
+ export function parseTh17(original) {
9
+ const decodedata = decodeModernBody(original, { blockSize: 0x400, base: 0x5c, add: 0xe1 }, { blockSize: 0x100, base: 0x7d, add: 0x3a });
10
+ const splits = [];
11
+ let stageOffset = 0xa0;
12
+ let frameCount = 0;
13
+ const stageCount = Math.min(decodedata[0x84] ?? 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 + 0x34) * 10;
18
+ split.power = (readBufferedUint32LE(decodedata, stageOffset + 0x68) / 100).toFixed(2);
19
+ split.piv = Math.trunc(readBufferedUint32LE(decodedata, stageOffset + 0x5c) / 1000) * 10;
20
+ const lives = decodedata[stageOffset + 0x78] ?? 0;
21
+ const livePieces = decodedata[stageOffset + 0x7c] ?? 0;
22
+ const bombs = decodedata[stageOffset + 0x84] ?? 0;
23
+ const bombPieces = decodedata[stageOffset + 0x88] ?? 0;
24
+ split.lives = resourceCount(lives, livePieces, 3);
25
+ split.graze = readBufferedUint32LE(decodedata, stageOffset + 0x44);
26
+ split.bombs = resourceCount(bombs, bombPieces, 3);
27
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
28
+ split.frameCount = stageFrameCount;
29
+ splits.push(split);
30
+ frameCount += stageFrameCount;
31
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0x158;
32
+ }
33
+ const userdata = readModernUserdata(new ByteReader(original));
34
+ return {
35
+ game: "th17",
36
+ gameTitle: REPLAY_GAME_TITLES.th17,
37
+ formatVersion: null,
38
+ player: normalizeText(userdata.name),
39
+ date: normalizeText(userdata.date),
40
+ character: normalizeText(userdata.character),
41
+ difficulty: normalizeText(userdata.difficulty),
42
+ stage: normalizeText(userdata.stage),
43
+ score: userdata.score,
44
+ cleared: userdata.stage.includes("Clear"),
45
+ splits,
46
+ frameCount: stageCount > 0 ? frameCount : null,
47
+ };
48
+ }
49
+ //# sourceMappingURL=th17.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th17.js","sourceRoot":"","sources":["../../src/games/th17.ts"],"names":[],"mappings":"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"}