@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,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":"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"}
|
|
@@ -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":"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,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAA4C,MAAM,aAAa,CAAC;AACjH,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,WAAW,GAAG,IAAI,CAAC;AACzB,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B;;GAEG;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,SAAS,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC1C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IACtD,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAEtC,IAAI,QAAQ,CAAC,MAAM,GAAG,WAAW,GAAG,IAAI,GAAG,kBAAkB,GAAG,CAAC,EAAE,CAAC;QAClE,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,kBAAkB,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,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,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,4EAA4E;IAC5E,2EAA2E;IAC3E,+DAA+D;IAC/D,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,QAAQ,KAAK,kBAAkB,GAAG,CAAC,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,YAAY,CAAC,kBAAkB,GAAG,CAAC,CAAE,GAAG,WAAW,CAAC;QACnE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,IAAI,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;gBAAE,SAAS;YACpC,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,CAAE,GAAG,WAAW,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClF,CAAC;IACH,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,EAAE,aAAa,CAAC,SAAS,CAAC;QACnC,UAAU,EAAE,aAAa,CAAC,UAAU,CAAC;QACrC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC;QAC3B,KAAK;QACL,OAAO;QACP,MAAM;QACN,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,CAAC,CAAC;QACP,KAAK,CAAC;YACJ,OAAO,CAAC,CAAC;QACX,KAAK,CAAC;YACJ,OAAO,CAAC,CAAC;QACX,KAAK,CAAC,CAAC;QACP,KAAK,CAAC;YACJ,OAAO,CAAC,CAAC;QACX;YACE,OAAO,KAAK,GAAG,CAAC,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,CAAC;YACJ,OAAO,IAAI,CAAC;QACd,KAAK,CAAC;YACJ,OAAO,IAAI,CAAC;QACd,KAAK,CAAC;YACJ,OAAO,IAAI,CAAC;QACd,KAAK,CAAC;YACJ,OAAO,IAAI,CAAC;QACd;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,UAAsB,EAAE,MAAc,EAAE,KAAa,EAAE,KAAoB;IAC5F,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,KAAK,CAAC,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;IAC5D,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;IAC5D,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC9E,KAAK,CAAC,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;IAC7D,KAAK,CAAC,GAAG,GAAG,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,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,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":"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"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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":"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,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAC3C,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,GAAG,CAAC,GAAG,EAAE,CAAC;QACvE,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACvF,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,MAAM,GAAG,UAAU,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9C,KAAK,CAAC,KAAK,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC,CAAC;QACnE,+EAA+E;QAC/E,4FAA4F;QAC5F,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,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,IAAI,CAAC;IAC5E,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 @@
|
|
|
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"}
|