@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,12 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * T7RP (東方妖々夢, PCB) decoder. Ported from Read_T7RP in threplay.
4
+ *
5
+ * Header offset 0x07 was the clue that surfaced in production when it turned
6
+ * out some replay versions could not be played back with the recording
7
+ * binary (Issue #16, since resolved by upgrading the recording game binary).
8
+ * This package does not assume a specific meaning for the value and simply
9
+ * exposes it as the raw formatVersion.
10
+ */
11
+ export declare function parseTh07(original: Uint8Array): ParsedReplay;
12
+ //# sourceMappingURL=th07.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th07.d.ts","sourceRoot":"","sources":["../../src/games/th07.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AA2BjH;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CA8E5D"}
@@ -0,0 +1,138 @@
1
+ import { decodeAnsiText } from "../byte-reader.js";
2
+ import { ReplayCorruptError } from "../errors.js";
3
+ import { additiveKeyDecode, decompress, readBufferedUint32LE } from "../lzss.js";
4
+ import { emptySplit, normalizeText, resourceCount } from "../types.js";
5
+ import { REPLAY_GAME_TITLES } from "../game-ids.js";
6
+ const CHARACTERS = ["ReimuA", "ReimuB", "MarisaA", "MarisaB", "SakuyaA", "SakuyaB"];
7
+ const DIFFICULTIES = ["Easy", "Normal", "Hard", "Lunatic", "Extra", "Phantasm"];
8
+ const HEADER_SIZE = 0x54;
9
+ /**
10
+ * Size of the fixed per-checkpoint header preceding each stage's raw input
11
+ * log within the decompressed body (37 bytes of fields actually read by
12
+ * `readSplitCommon`, rounded up to a 4-byte-aligned 40). Combined with
13
+ * `BYTES_PER_FRAME`, this was determined by reverse-engineering the
14
+ * checked-in `test-fixtures/th07/*.rpy` fixtures: for every stage-to-stage
15
+ * checkpoint gap in a multi-stage (non-clear) replay, `(gap - 40) / 4` comes
16
+ * out to an exact integer, and applying the same formula to a single-stage
17
+ * (Extra-clear) replay whose actual recorded duration is independently known
18
+ * (`touhou-recorder` reports/11 and reports/20, both around 840-852s for
19
+ * `th7_07.rpy`) lands within that measured range. Neither threplay nor threp
20
+ * (the sources this package otherwise ports from) documents or parses this
21
+ * input log, so treat this as an empirically-derived model, not a confirmed
22
+ * upstream spec.
23
+ */
24
+ const STAGE_CHECKPOINT_HEADER_SIZE = 40;
25
+ /** See `STAGE_CHECKPOINT_HEADER_SIZE`. */
26
+ const BYTES_PER_FRAME = 4;
27
+ /**
28
+ * T7RP (東方妖々夢, PCB) decoder. Ported from Read_T7RP in threplay.
29
+ *
30
+ * Header offset 0x07 was the clue that surfaced in production when it turned
31
+ * out some replay versions could not be played back with the recording
32
+ * binary (Issue #16, since resolved by upgrading the recording game binary).
33
+ * This package does not assume a specific meaning for the value and simply
34
+ * exposes it as the raw formatVersion.
35
+ */
36
+ export function parseTh07(original) {
37
+ if (original.length < HEADER_SIZE + 4) {
38
+ throw new ReplayCorruptError("file too short for T7RP header");
39
+ }
40
+ const buffer = original.slice();
41
+ const formatVersion = buffer[0x07];
42
+ additiveKeyDecode(buffer, 16, buffer[0x0d], 7);
43
+ const length = readBufferedUint32LE(buffer, 20);
44
+ const dlength = readBufferedUint32LE(buffer, 24);
45
+ const scoreOffsets = [];
46
+ let maxStage = 0;
47
+ for (let i = 0; i < 7; i++) {
48
+ const offset = readBufferedUint32LE(buffer, 0x1c + 4 * i);
49
+ scoreOffsets.push(offset);
50
+ if (offset !== 0)
51
+ maxStage = i;
52
+ }
53
+ if (HEADER_SIZE > buffer.length) {
54
+ throw new ReplayCorruptError("T7RP body shorter than header size");
55
+ }
56
+ const shifted = buffer.slice(HEADER_SIZE);
57
+ const decodeData = decompress(shifted, length, dlength);
58
+ if (decodeData.length < 25) {
59
+ throw new ReplayCorruptError("T7RP decompressed body too short");
60
+ }
61
+ const character = CHARACTERS[decodeData[2]] ?? null;
62
+ const difficulty = DIFFICULTIES[decodeData[3]] ?? null;
63
+ const date = decodeAnsiText(decodeData.subarray(4, 9));
64
+ const name = decodeAnsiText(decodeData.subarray(10, 18));
65
+ const score = readBufferedUint32LE(decodeData, 24) * 10;
66
+ // checkpointOffsets are decodeData-relative offsets to each stage's score
67
+ // snapshot header, in the same order the stages were played. The original
68
+ // C# (threplay) used score_offsets[6] directly (without the -HEADER_SIZE
69
+ // adjustment applied to every other index) for the max_stage===6 "cleared
70
+ // in one recorded checkpoint" case (e.g. an Extra-stage clear) — an
71
+ // inconsistency that this port initially carried over as well. That bug
72
+ // caused readSplitCommon to read 0x54 bytes too far into the body, landing
73
+ // inside the raw per-frame input log instead of the checkpoint header
74
+ // (visible in the old golden fixtures as suspiciously uniform values like
75
+ // score=97/piv=97/graze=97). Applying the same -HEADER_SIZE adjustment
76
+ // uniformly here fixes that.
77
+ const checkpointOffsets = [];
78
+ const splits = [];
79
+ if (maxStage === 6) {
80
+ checkpointOffsets.push(scoreOffsets[6] - HEADER_SIZE);
81
+ }
82
+ else {
83
+ for (let i = 0; i <= maxStage; i++) {
84
+ const raw = scoreOffsets[i];
85
+ if (raw === 0)
86
+ continue;
87
+ checkpointOffsets.push(raw - HEADER_SIZE);
88
+ }
89
+ }
90
+ const stageFrameCounts = perCheckpointFrameCounts(decodeData, checkpointOffsets);
91
+ checkpointOffsets.forEach((offset, i) => {
92
+ const split = readSplitCommon(decodeData, offset, maxStage === 6 ? 7 : i + 1);
93
+ split.frameCount = stageFrameCounts[i];
94
+ splits.push(split);
95
+ });
96
+ return {
97
+ game: "th07",
98
+ gameTitle: REPLAY_GAME_TITLES.th07,
99
+ formatVersion,
100
+ player: normalizeText(name),
101
+ date: normalizeText(date),
102
+ character,
103
+ difficulty,
104
+ stage: null,
105
+ score,
106
+ cleared: maxStage === 6,
107
+ splits,
108
+ frameCount: stageFrameCounts.length === 0 ? null : stageFrameCounts.reduce((a, b) => a + b, 0),
109
+ };
110
+ }
111
+ /**
112
+ * Returns the per-frame input log length for each checkpoint-to-checkpoint
113
+ * (or last-checkpoint-to-end-of-body) span, in the same order as
114
+ * `checkpointOffsets`. See `STAGE_CHECKPOINT_HEADER_SIZE` for the model this
115
+ * is based on.
116
+ */
117
+ function perCheckpointFrameCounts(decodeData, checkpointOffsets) {
118
+ return checkpointOffsets.map((offset, i) => {
119
+ const start = offset + STAGE_CHECKPOINT_HEADER_SIZE;
120
+ const end = i + 1 < checkpointOffsets.length ? checkpointOffsets[i + 1] : decodeData.length;
121
+ return Math.max(0, Math.floor((end - start) / BYTES_PER_FRAME));
122
+ });
123
+ }
124
+ function readSplitCommon(decodeData, offset, stage) {
125
+ const split = emptySplit();
126
+ split.stage = stage;
127
+ split.score = readBufferedUint32LE(decodeData, offset);
128
+ split.piv = readBufferedUint32LE(decodeData, offset + 0x8);
129
+ const pointItems = readBufferedUint32LE(decodeData, offset + 0x4);
130
+ const cherryMax = readBufferedUint32LE(decodeData, offset + 0xc);
131
+ split.additional = { pointItems, cherryMax };
132
+ split.graze = readBufferedUint32LE(decodeData, offset + 0x14);
133
+ split.power = String(decodeData[offset + 0x22] ?? 0);
134
+ split.lives = resourceCount(decodeData[offset + 0x23] ?? 0);
135
+ split.bombs = resourceCount(decodeData[offset + 0x24] ?? 0);
136
+ return split;
137
+ }
138
+ //# sourceMappingURL=th07.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th07.js","sourceRoot":"","sources":["../../src/games/th07.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAA4C,MAAM,aAAa,CAAC;AACjH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AACpF,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAEhF,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;;;;;;;;;;;;GAcG;AACH,MAAM,4BAA4B,GAAG,EAAE,CAAC;AACxC,0CAA0C;AAC1C,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,QAAoB;IAC5C,IAAI,QAAQ,CAAC,MAAM,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,kBAAkB,CAAC,gCAAgC,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;IAChC,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAE,CAAC;IAEpC,iBAAiB,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAE,EAAE,CAAC,CAAC,CAAC;IAEhD,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEjD,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,MAAM,KAAK,CAAC;YAAE,QAAQ,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAChC,MAAM,IAAI,kBAAkB,CAAC,oCAAoC,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IACxD,IAAI,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAC3B,MAAM,IAAI,kBAAkB,CAAC,kCAAkC,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,IAAI,IAAI,CAAC;IACrD,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,IAAI,IAAI,CAAC;IACxD,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;IAExD,0EAA0E;IAC1E,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,oEAAoE;IACpE,wEAAwE;IACxE,2EAA2E;IAC3E,sEAAsE;IACtE,0EAA0E;IAC1E,uEAAuE;IACvE,6BAA6B;IAC7B,MAAM,iBAAiB,GAAa,EAAE,CAAC;IACvC,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAE,GAAG,WAAW,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;YAC7B,IAAI,GAAG,KAAK,CAAC;gBAAE,SAAS;YACxB,iBAAiB,CAAC,IAAI,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IACjF,iBAAiB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACtC,MAAM,KAAK,GAAG,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9E,KAAK,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC,CAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,kBAAkB,CAAC,IAAI;QAClC,aAAa;QACb,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC;QAC3B,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC;QACzB,SAAS;QACT,UAAU;QACV,KAAK,EAAE,IAAI;QACX,KAAK;QACL,OAAO,EAAE,QAAQ,KAAK,CAAC;QACvB,MAAM;QACN,UAAU,EAAE,gBAAgB,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;KAC/F,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,wBAAwB,CAAC,UAAsB,EAAE,iBAA2B;IACnF,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACzC,MAAM,KAAK,GAAG,MAAM,GAAG,4BAA4B,CAAC;QACpD,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QAC7F,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,UAAsB,EAAE,MAAc,EAAE,KAAa;IAC5E,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,KAAK,CAAC,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACvD,KAAK,CAAC,GAAG,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;IACjE,KAAK,CAAC,UAAU,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAC7C,KAAK,CAAC,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * T8RP (東方永夜抄, IN) decoder. Ported from Read_T8RP in threplay.
4
+ */
5
+ export declare function parseTh08(original: Uint8Array): ParsedReplay;
6
+ //# sourceMappingURL=th08.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th08.d.ts","sourceRoot":"","sources":["../../src/games/th08.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAMjH;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAkE5D"}
@@ -0,0 +1,118 @@
1
+ import { ByteReader } from "../byte-reader.js";
2
+ import { ReplayCorruptError } from "../errors.js";
3
+ import { additiveKeyDecode, decompress, readBufferedUint32LE } from "../lzss.js";
4
+ import { jumpToUser, parseIntStrict } from "../userdata.js";
5
+ import { emptySplit, normalizeText, resourceCount } from "../types.js";
6
+ import { REPLAY_GAME_TITLES } from "../game-ids.js";
7
+ const HEADER_SIZE = 0x68;
8
+ const SCORE_OFFSET_COUNT = 9;
9
+ /**
10
+ * T8RP (東方永夜抄, IN) decoder. Ported from Read_T8RP in threplay.
11
+ */
12
+ export function parseTh08(original) {
13
+ const reader = new ByteReader(original);
14
+ jumpToUser(reader, 12);
15
+ reader.readUint32LE();
16
+ reader.skip(17);
17
+ const name = reader.readAnsiString();
18
+ reader.skip(11);
19
+ const date = reader.readAnsiString();
20
+ reader.skip(9);
21
+ const character = reader.readAnsiString();
22
+ reader.skip(8);
23
+ const score = parseIntStrict(reader.readAnsiString());
24
+ reader.skip(8);
25
+ const difficulty = reader.readAnsiString();
26
+ const stage = reader.readAnsiString();
27
+ if (original.length < HEADER_SIZE + 0x20 + SCORE_OFFSET_COUNT * 4) {
28
+ throw new ReplayCorruptError("file too short for T8RP header");
29
+ }
30
+ const buffer = original.slice();
31
+ const length = readBufferedUint32LE(buffer, 0x0c);
32
+ additiveKeyDecode(buffer, 24, buffer[0x15], 7);
33
+ const dlength = readBufferedUint32LE(buffer, 0x1c);
34
+ const scoreOffsets = [];
35
+ let maxStage = 0;
36
+ for (let i = 0; i < SCORE_OFFSET_COUNT; i++) {
37
+ const offset = readBufferedUint32LE(buffer, 0x20 + 4 * i);
38
+ scoreOffsets.push(offset);
39
+ if (offset !== 0)
40
+ maxStage = i;
41
+ }
42
+ const shifted = buffer.slice(HEADER_SIZE);
43
+ const decodeData = decompress(shifted, length - HEADER_SIZE, dlength);
44
+ const splits = [];
45
+ // The USER section's stage field explicitly contains "Clear" — this is more
46
+ // reliable than the final score_offsets slot (which indicates reaching the
47
+ // true final battle) because it covers all routes and endings.
48
+ const cleared = stage.includes("Clear");
49
+ if (maxStage === SCORE_OFFSET_COUNT - 1) {
50
+ const offset = scoreOffsets[SCORE_OFFSET_COUNT - 1] - HEADER_SIZE;
51
+ splits.push(readSplit(decodeData, offset, 7, null));
52
+ }
53
+ else {
54
+ for (let i = 0; i <= maxStage; i++) {
55
+ if (scoreOffsets[i] === 0)
56
+ continue;
57
+ const offset = scoreOffsets[i] - HEADER_SIZE;
58
+ splits.push(readSplit(decodeData, offset, stageNumberFor(i), stageLabelFor(i)));
59
+ }
60
+ }
61
+ return {
62
+ game: "th08",
63
+ gameTitle: REPLAY_GAME_TITLES.th08,
64
+ formatVersion: null,
65
+ player: normalizeText(name),
66
+ date: normalizeText(date),
67
+ character: normalizeText(character),
68
+ difficulty: normalizeText(difficulty),
69
+ stage: normalizeText(stage),
70
+ score,
71
+ cleared,
72
+ splits,
73
+ frameCount: null,
74
+ };
75
+ }
76
+ function stageNumberFor(index) {
77
+ switch (index) {
78
+ case 3:
79
+ case 4:
80
+ return 4;
81
+ case 5:
82
+ return 5;
83
+ case 6:
84
+ case 7:
85
+ return 6;
86
+ default:
87
+ return index + 1;
88
+ }
89
+ }
90
+ function stageLabelFor(index) {
91
+ switch (index) {
92
+ case 3:
93
+ return "4A";
94
+ case 4:
95
+ return "4B";
96
+ case 6:
97
+ return "6A";
98
+ case 7:
99
+ return "6B";
100
+ default:
101
+ return null;
102
+ }
103
+ }
104
+ function readSplit(decodeData, offset, stage, route) {
105
+ const split = emptySplit();
106
+ split.stage = stage;
107
+ split.score = readBufferedUint32LE(decodeData, offset) * 10;
108
+ const pointItems = readBufferedUint32LE(decodeData, offset + 0x4);
109
+ const time = readBufferedUint32LE(decodeData, offset + 0xc);
110
+ split.additional = route ? { pointItems, time, route } : { pointItems, time };
111
+ split.graze = readBufferedUint32LE(decodeData, offset + 0x8);
112
+ split.piv = readBufferedUint32LE(decodeData, offset + 0x14);
113
+ split.power = String(decodeData[offset + 0x1c] ?? 0);
114
+ split.lives = resourceCount(decodeData[offset + 0x1d] ?? 0);
115
+ split.bombs = resourceCount(decodeData[offset + 0x1e] ?? 0);
116
+ return split;
117
+ }
118
+ //# sourceMappingURL=th08.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th08.js","sourceRoot":"","sources":["../../src/games/th08.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * T9RP (東方花映塚, PoFV) decoder. Ported from Read_T9RP in threplay.
4
+ * Since this title is VS-battle only, the player's character does not appear
5
+ * as the top-level `character` but as "PlayerChar vs OpponentChar" in each
6
+ * split's `additional` (in story mode, the leading character from the splits
7
+ * is also duplicated into the top-level `character`).
8
+ */
9
+ export declare function parseTh09(original: Uint8Array): ParsedReplay;
10
+ //# sourceMappingURL=th09.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th09.d.ts","sourceRoot":"","sources":["../../src/games/th09.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAwBjH;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CA+E5D"}
@@ -0,0 +1,110 @@
1
+ import { ByteReader } from "../byte-reader.js";
2
+ import { ReplayCorruptError } from "../errors.js";
3
+ import { additiveKeyDecode, decompress, readBufferedUint32LE } from "../lzss.js";
4
+ import { jumpToUser } from "../userdata.js";
5
+ import { emptySplit, normalizeText, resourceCount } from "../types.js";
6
+ import { REPLAY_GAME_TITLES } from "../game-ids.js";
7
+ const CHARACTERS = [
8
+ "Reimu",
9
+ "Marisa",
10
+ "Sakuya",
11
+ "Youmu",
12
+ "Reisen",
13
+ "Cirno",
14
+ "Lyrica",
15
+ "Mystia",
16
+ "Tewi",
17
+ "Yuuka",
18
+ "Aya",
19
+ "Medicine",
20
+ "Komachi",
21
+ "Eiki",
22
+ "Merlin",
23
+ "Lunasa",
24
+ ];
25
+ const HEADER_SIZE = 0xc0;
26
+ /**
27
+ * T9RP (東方花映塚, PoFV) decoder. Ported from Read_T9RP in threplay.
28
+ * Since this title is VS-battle only, the player's character does not appear
29
+ * as the top-level `character` but as "PlayerChar vs OpponentChar" in each
30
+ * split's `additional` (in story mode, the leading character from the splits
31
+ * is also duplicated into the top-level `character`).
32
+ */
33
+ export function parseTh09(original) {
34
+ const reader = new ByteReader(original);
35
+ jumpToUser(reader, 12);
36
+ reader.readUint32LE();
37
+ reader.skip(17);
38
+ const name = reader.readAnsiString();
39
+ reader.skip(11);
40
+ const date = reader.readAnsiString();
41
+ reader.skip(8);
42
+ const difficulty = reader.readAnsiString();
43
+ reader.skip(8);
44
+ const stage = reader.readAnsiString();
45
+ if (original.length < HEADER_SIZE + 0x20 + 40 * 4) {
46
+ throw new ReplayCorruptError("file too short for T9RP header");
47
+ }
48
+ const buffer = original.slice();
49
+ const length = readBufferedUint32LE(buffer, 0x0c);
50
+ additiveKeyDecode(buffer, 24, buffer[0x15], 7);
51
+ const dlength = readBufferedUint32LE(buffer, 0x1c);
52
+ const scoreOffsets = [];
53
+ let maxStage = 0;
54
+ for (let i = 0; i < 40; i++) {
55
+ const offset = readBufferedUint32LE(buffer, 0x20 + 4 * i);
56
+ scoreOffsets.push(offset);
57
+ if (i < 10 && offset !== 0)
58
+ maxStage = i;
59
+ }
60
+ const shifted = buffer.slice(HEADER_SIZE);
61
+ const decodeData = decompress(shifted, length - HEADER_SIZE, dlength);
62
+ const splits = [];
63
+ let character = null;
64
+ if (scoreOffsets[9] === 0) {
65
+ // Story mode: self and opponent characters are recorded per stage.
66
+ for (let i = 0; i <= maxStage; i++) {
67
+ const raw = scoreOffsets[i];
68
+ if (!raw)
69
+ continue;
70
+ const offset = raw - HEADER_SIZE;
71
+ const offsetP2 = scoreOffsets[10 + i] - HEADER_SIZE;
72
+ const split = emptySplit();
73
+ split.stage = i + 1;
74
+ split.score = readBufferedUint32LE(decodeData, offset) * 10;
75
+ split.lives = resourceCount(decodeData[offset + 0x8] ?? 0);
76
+ const selfChar = CHARACTERS[decodeData[offset + 0x6]] ?? "?";
77
+ const opponentChar = CHARACTERS[decodeData[offsetP2 + 0x6]] ?? "?";
78
+ split.additional = { self: selfChar, opponent: opponentChar };
79
+ if (i === 0)
80
+ character = selfChar;
81
+ splits.push(split);
82
+ }
83
+ }
84
+ else {
85
+ // VS mode: only one entry.
86
+ const offset1 = scoreOffsets[9] - HEADER_SIZE;
87
+ const offset2 = scoreOffsets[19] - HEADER_SIZE;
88
+ const selfChar = CHARACTERS[decodeData[offset1 + 0x6]] ?? "?";
89
+ const opponentChar = CHARACTERS[decodeData[offset2 + 0x6]] ?? "?";
90
+ const split = emptySplit();
91
+ split.additional = { self: selfChar, opponent: opponentChar };
92
+ character = selfChar;
93
+ splits.push(split);
94
+ }
95
+ return {
96
+ game: "th09",
97
+ gameTitle: REPLAY_GAME_TITLES.th09,
98
+ formatVersion: null,
99
+ player: normalizeText(name),
100
+ date: normalizeText(date),
101
+ character,
102
+ difficulty: normalizeText(difficulty),
103
+ stage: normalizeText(stage),
104
+ score: null,
105
+ cleared: null,
106
+ splits,
107
+ frameCount: null,
108
+ };
109
+ }
110
+ //# sourceMappingURL=th09.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th09.js","sourceRoot":"","sources":["../../src/games/th09.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAA4C,MAAM,aAAa,CAAC;AACjH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,UAAU,GAAG;IACjB,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,OAAO;IACP,KAAK;IACL,UAAU;IACV,SAAS;IACT,MAAM;IACN,QAAQ;IACR,QAAQ;CACT,CAAC;AAEF,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,QAAoB;IAC5C,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,EAAE,CAAC,CAAC;IAChB,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChB,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC3C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAEtC,IAAI,QAAQ,CAAC,MAAM,GAAG,WAAW,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,kBAAkB,CAAC,gCAAgC,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAClD,iBAAiB,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAE,EAAE,CAAC,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAEnD,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,CAAC,GAAG,EAAE,IAAI,MAAM,KAAK,CAAC;YAAE,QAAQ,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,OAAO,CAAC,CAAC;IAEtE,MAAM,MAAM,GAAuB,EAAE,CAAC;IACtC,IAAI,SAAS,GAAkB,IAAI,CAAC;IAEpC,IAAI,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,mEAAmE;QACnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,MAAM,MAAM,GAAG,GAAG,GAAG,WAAW,CAAC;YACjC,MAAM,QAAQ,GAAG,YAAY,CAAC,EAAE,GAAG,CAAC,CAAE,GAAG,WAAW,CAAC;YACrD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;YAC3B,KAAK,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;YACpB,KAAK,CAAC,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;YAC5D,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3D,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAE,CAAC,IAAI,GAAG,CAAC;YAC9D,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAE,CAAC,IAAI,GAAG,CAAC;YACpE,KAAK,CAAC,UAAU,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;YAC9D,IAAI,CAAC,KAAK,CAAC;gBAAE,SAAS,GAAG,QAAQ,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,2BAA2B;QAC3B,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAE,GAAG,WAAW,CAAC;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,CAAE,GAAG,WAAW,CAAC;QAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAE,CAAC,IAAI,GAAG,CAAC;QAC/D,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAE,CAAC,IAAI,GAAG,CAAC;QACnE,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,KAAK,CAAC,UAAU,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;QAC9D,SAAS,GAAG,QAAQ,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,SAAS,EAAE,kBAAkB,CAAC,IAAI;QAClC,aAAa,EAAE,IAAI;QACnB,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC;QAC3B,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC;QACzB,SAAS;QACT,UAAU,EAAE,aAAa,CAAC,UAAU,CAAC;QACrC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC;QAC3B,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,IAAI;QACb,MAAM;QACN,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /**
3
+ * t95r (東方文花帖, StB) decoder. Ported from Read_t95r in threplay.
4
+ * Has no LZSS body compression; everything is contained in the USER section.
5
+ */
6
+ export declare function parseTh095(original: Uint8Array): ParsedReplay;
7
+ //# sourceMappingURL=th095.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th095.d.ts","sourceRoot":"","sources":["../../src/games/th095.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG/D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CA+B7D"}
@@ -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
+ * t95r (東方文花帖, StB) decoder. Ported from Read_t95r in threplay.
7
+ * Has no LZSS body compression; everything is contained in the USER section.
8
+ */
9
+ export function parseTh095(original) {
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
+ const stagePart1 = reader.readAnsiString();
19
+ const stagePart2 = reader.readAnsiString();
20
+ reader.skip(5);
21
+ const date = reader.readAnsiString();
22
+ reader.skip(6);
23
+ const score = parseIntStrict(reader.readAnsiString());
24
+ return {
25
+ game: "th095",
26
+ gameTitle: REPLAY_GAME_TITLES.th095,
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=th095.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th095.js","sourceRoot":"","sources":["../../src/games/th095.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;;;GAGG;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,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,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IACrC,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,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
+ /** t10r (東方風神録, MoF) decoder. Ported from Read_t10r in threplay. */
3
+ export declare function parseTh10(original: Uint8Array): ParsedReplay;
4
+ //# sourceMappingURL=th10.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th10.d.ts","sourceRoot":"","sources":["../../src/games/th10.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAIjH,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAgD5D"}
@@ -0,0 +1,50 @@
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
+ /** t10r (東方風神録, MoF) decoder. Ported from Read_t10r in threplay. */
8
+ export function parseTh10(original) {
9
+ const decodedata = decodeModernBody(original, { blockSize: 0x400, base: 0xaa, add: 0xe1 }, { blockSize: 0x80, base: 0x3d, add: 0x7a });
10
+ const splits = [];
11
+ let stageOffset = 0x64;
12
+ let frameCount = 0;
13
+ const stageCount = Math.min(decodedata[0x4c] ?? 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 + 0xc) * 10;
18
+ split.power = (0.05 * readBufferedUint32LE(decodedata, stageOffset + 0x10)).toFixed(2);
19
+ split.piv = readBufferedUint32LE(decodedata, stageOffset + 0x14);
20
+ split.lives = resourceCount(decodedata[stageOffset + 0x1c] ?? 0);
21
+ split.graze = 0;
22
+ // threplay's Read_t10r did not extract the bomb count and always returned "0".
23
+ // Since that is not real data, this package returns null to indicate it cannot be obtained.
24
+ split.bombs = null;
25
+ // stageOffset+0x4 is the per-stage frame count (unused by threplay, but
26
+ // read by threp's own th10.cpp when it walks the same stage blocks).
27
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
28
+ split.frameCount = stageFrameCount;
29
+ splits.push(split);
30
+ frameCount += stageFrameCount;
31
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0x1c4;
32
+ }
33
+ const reader = new ByteReader(original);
34
+ const userdata = readModernUserdata(reader);
35
+ return {
36
+ game: "th10",
37
+ gameTitle: REPLAY_GAME_TITLES.th10,
38
+ formatVersion: null,
39
+ player: normalizeText(userdata.name),
40
+ date: normalizeText(userdata.date),
41
+ character: normalizeText(userdata.character),
42
+ difficulty: normalizeText(userdata.difficulty),
43
+ stage: normalizeText(userdata.stage),
44
+ score: userdata.score,
45
+ cleared: userdata.stage.includes("Clear"),
46
+ splits,
47
+ frameCount: stageCount > 0 ? frameCount : null,
48
+ };
49
+ }
50
+ //# sourceMappingURL=th10.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th10.js","sourceRoot":"","sources":["../../src/games/th10.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /** t11r (東方地霊殿, SA) decoder. Ported from Read_t11r in threplay. */
3
+ export declare function parseTh11(original: Uint8Array): ParsedReplay;
4
+ //# sourceMappingURL=th11.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th11.d.ts","sourceRoot":"","sources":["../../src/games/th11.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAIjH,mEAAmE;AACnE,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
+ /** t11r (東方地霊殿, SA) decoder. Ported from Read_t11r in threplay. */
8
+ export function parseTh11(original) {
9
+ const decodedata = decodeModernBody(original, { blockSize: 0x800, base: 0xaa, add: 0xe1 }, { blockSize: 0x40, base: 0x3d, add: 0x7a });
10
+ const splits = [];
11
+ let stageOffset = 0x70;
12
+ let frameCount = 0;
13
+ const stageCount = Math.min(decodedata[0x58] ?? 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 + 0xc) * 10;
18
+ split.power = (0.05 * readBufferedUint32LE(decodedata, stageOffset + 0x10)).toFixed(2);
19
+ split.piv = readBufferedUint32LE(decodedata, stageOffset + 0x14);
20
+ const lives = decodedata[stageOffset + 0x18] ?? 0;
21
+ const pieces = decodedata[stageOffset + 0x1a] ?? 0;
22
+ split.lives = resourceCount(lives, pieces, 5);
23
+ split.graze = readBufferedUint32LE(decodedata, stageOffset + 0x34);
24
+ // threplay's Read_t11r did not extract the bomb count and always returned "0".
25
+ // Since that is not real data, this package returns null to indicate it cannot be obtained.
26
+ split.bombs = null;
27
+ const stageFrameCount = readBufferedUint32LE(decodedata, stageOffset + 0x4);
28
+ split.frameCount = stageFrameCount;
29
+ splits.push(split);
30
+ frameCount += stageFrameCount;
31
+ stageOffset += readBufferedUint32LE(decodedata, stageOffset + 0x8) + 0x90;
32
+ }
33
+ const userdata = readModernUserdata(new ByteReader(original));
34
+ return {
35
+ game: "th11",
36
+ gameTitle: REPLAY_GAME_TITLES.th11,
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=th11.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th11.js","sourceRoot":"","sources":["../../src/games/th11.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type ParsedReplay } from "../types.js";
2
+ /** t12r (東方星蓮船, UFO) decoder. Ported from Read_t12r in threplay. */
3
+ export declare function parseTh12(original: Uint8Array): ParsedReplay;
4
+ //# sourceMappingURL=th12.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"th12.d.ts","sourceRoot":"","sources":["../../src/games/th12.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4C,KAAK,YAAY,EAAyB,MAAM,aAAa,CAAC;AAMjH,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,YAAY,CAwD5D"}