Commit 59dcd5c1 authored by nanahira's avatar nanahira

support new replay recover

parent 6c459522
...@@ -33,9 +33,12 @@ var __importStar = (this && this.__importStar) || (function () { ...@@ -33,9 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
}; };
})(); })();
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.Replay = exports.ReplayHeader = void 0; exports.Replay = exports.ReplayHeader = exports.REPLAY_ID_YRP2 = exports.REPLAY_ID_YRP1 = exports.SEED_COUNT = void 0;
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const lzma = __importStar(require("lzma")); const lzma = __importStar(require("lzma"));
exports.SEED_COUNT = 8;
exports.REPLAY_ID_YRP1 = 0x31707279;
exports.REPLAY_ID_YRP2 = 0x32707279;
/** /**
* Metadata stored at the beginning of every replay file. * Metadata stored at the beginning of every replay file.
*/ */
...@@ -52,12 +55,14 @@ class ReplayHeader { ...@@ -52,12 +55,14 @@ class ReplayHeader {
dataSizeRaw = []; dataSizeRaw = [];
hash = 0; hash = 0;
props = []; props = [];
seedSequence = [];
headerVersion = 0;
value1 = 0;
value2 = 0;
value3 = 0;
/** Decompressed size as little‑endian 32‑bit */ /** Decompressed size as little‑endian 32‑bit */
get dataSize() { get dataSize() {
return (this.dataSizeRaw[0] + return Buffer.from(this.dataSizeRaw).readUInt32LE(0);
this.dataSizeRaw[1] * 0x100 +
this.dataSizeRaw[2] * 0x10000 +
this.dataSizeRaw[3] * 0x1000000);
} }
get isTag() { get isTag() {
return (this.flag & ReplayHeader.REPLAY_TAG_FLAG) !== 0; return (this.flag & ReplayHeader.REPLAY_TAG_FLAG) !== 0;
...@@ -112,6 +117,12 @@ class ReplayReader { ...@@ -112,6 +117,12 @@ class ReplayReader {
readInt32() { readInt32() {
return this.advance(4, () => this.buffer.readInt32LE(this.pointer)); return this.advance(4, () => this.buffer.readInt32LE(this.pointer));
} }
readUInt16() {
return this.advance(2, () => this.buffer.readUInt16LE(this.pointer));
}
readUInt32() {
return this.advance(4, () => this.buffer.readUInt32LE(this.pointer));
}
readAll() { readAll() {
return this.buffer.slice(this.pointer); return this.buffer.slice(this.pointer);
} }
...@@ -162,6 +173,12 @@ class ReplayWriter { ...@@ -162,6 +173,12 @@ class ReplayWriter {
writeInt32(val) { writeInt32(val) {
this.advance(() => this.buffer.writeInt32LE(val, this.pointer), 4); this.advance(() => this.buffer.writeInt32LE(val, this.pointer), 4);
} }
writeUInt16(val) {
this.advance(() => this.buffer.writeUInt16LE(val, this.pointer), 2);
}
writeUInt32(val) {
this.advance(() => this.buffer.writeUInt32LE(val, this.pointer), 4);
}
writeAll(buf) { writeAll(buf) {
this.buffer = Buffer.concat([this.buffer, buf]); this.buffer = Buffer.concat([this.buffer, buf]);
} }
...@@ -207,8 +224,8 @@ class Replay { ...@@ -207,8 +224,8 @@ class Replay {
return this.header?.isTag ?? false; return this.header?.isTag ?? false;
} }
/* ------------------ Static helpers ------------------ */ /* ------------------ Static helpers ------------------ */
static fromFile(path) { static async fromFile(path) {
return Replay.fromBuffer(fs.readFileSync(path)); return Replay.fromBuffer(await fs.promises.readFile(path));
} }
static fromBuffer(buffer) { static fromBuffer(buffer) {
const headerReader = new ReplayReader(buffer); const headerReader = new ReplayReader(buffer);
...@@ -227,13 +244,22 @@ class Replay { ...@@ -227,13 +244,22 @@ class Replay {
} }
static readHeader(reader) { static readHeader(reader) {
const h = new ReplayHeader(); const h = new ReplayHeader();
h.id = reader.readInt32(); h.id = reader.readUInt32();
h.version = reader.readInt32(); h.version = reader.readUInt32();
h.flag = reader.readInt32(); h.flag = reader.readUInt32();
h.seed = reader.readInt32(); h.seed = reader.readUInt32();
h.dataSizeRaw = reader.readByteArray(4); h.dataSizeRaw = reader.readByteArray(4);
h.hash = reader.readInt32(); h.hash = reader.readUInt32();
h.props = reader.readByteArray(8); h.props = reader.readByteArray(8);
if (h.id === exports.REPLAY_ID_YRP2) {
for (let i = 0; i < exports.SEED_COUNT; i++) {
h.seedSequence.push(reader.readUInt32());
}
h.headerVersion = reader.readUInt32();
h.value1 = reader.readUInt32();
h.value2 = reader.readUInt32();
h.value3 = reader.readUInt32();
}
return h; return h;
} }
static readReplay(header, reader) { static readReplay(header, reader) {
...@@ -313,17 +339,26 @@ class Replay { ...@@ -313,17 +339,26 @@ class Replay {
} }
return Buffer.concat([headerWriter.buffer, body]); return Buffer.concat([headerWriter.buffer, body]);
} }
writeToFile(path) { async writeToFile(path) {
fs.writeFileSync(path, this.toBuffer()); await fs.promises.writeFile(path, this.toBuffer());
} }
writeHeader(w) { writeHeader(w) {
w.writeInt32(this.header.id); w.writeUInt32(this.header.id);
w.writeInt32(this.header.version); w.writeUInt32(this.header.version);
w.writeInt32(this.header.flag); w.writeUInt32(this.header.flag);
w.writeInt32(this.header.seed); w.writeUInt32(this.header.seed);
w.writeByteArray(this.header.dataSizeRaw); w.writeByteArray(this.header.dataSizeRaw);
w.writeInt32(this.header.hash); w.writeUInt32(this.header.hash);
w.writeByteArray(this.header.props); w.writeByteArray(this.header.props);
if (this.header.id === exports.REPLAY_ID_YRP2) {
for (let i = 0; i < exports.SEED_COUNT; i++) {
w.writeUInt32(this.header.seedSequence[i]);
}
w.writeUInt32(this.header.headerVersion);
w.writeUInt32(this.header.value1);
w.writeUInt32(this.header.value2);
w.writeUInt32(this.header.value3);
}
} }
writeContent(w) { writeContent(w) {
w.writeString(this.hostName, 40); w.writeString(this.hostName, 40);
......
...@@ -7,6 +7,10 @@ export interface DeckObject { ...@@ -7,6 +7,10 @@ export interface DeckObject {
ex: number[]; ex: number[];
} }
export const SEED_COUNT = 8;
export const REPLAY_ID_YRP1 = 0x31707279;
export const REPLAY_ID_YRP2 = 0x32707279;
/** /**
* Metadata stored at the beginning of every replay file. * Metadata stored at the beginning of every replay file.
*/ */
...@@ -24,15 +28,16 @@ export class ReplayHeader { ...@@ -24,15 +28,16 @@ export class ReplayHeader {
dataSizeRaw: number[] = []; dataSizeRaw: number[] = [];
hash = 0; hash = 0;
props: number[] = []; props: number[] = [];
seedSequence: number[] = [];
headerVersion = 0;
value1 = 0;
value2 = 0;
value3 = 0;
/** Decompressed size as little‑endian 32‑bit */ /** Decompressed size as little‑endian 32‑bit */
get dataSize(): number { get dataSize(): number {
return ( return Buffer.from(this.dataSizeRaw).readUInt32LE(0);
this.dataSizeRaw[0] +
this.dataSizeRaw[1] * 0x100 +
this.dataSizeRaw[2] * 0x10000 +
this.dataSizeRaw[3] * 0x1000000
);
} }
get isTag(): boolean { get isTag(): boolean {
...@@ -94,6 +99,14 @@ class ReplayReader { ...@@ -94,6 +99,14 @@ class ReplayReader {
return this.advance(4, () => this.buffer.readInt32LE(this.pointer)); return this.advance(4, () => this.buffer.readInt32LE(this.pointer));
} }
readUInt16(): number {
return this.advance(2, () => this.buffer.readUInt16LE(this.pointer));
}
readUInt32(): number {
return this.advance(4, () => this.buffer.readUInt32LE(this.pointer));
}
readAll(): Buffer { readAll(): Buffer {
return this.buffer.slice(this.pointer); return this.buffer.slice(this.pointer);
} }
...@@ -149,6 +162,14 @@ class ReplayWriter { ...@@ -149,6 +162,14 @@ class ReplayWriter {
this.advance(() => this.buffer.writeInt32LE(val, this.pointer), 4); this.advance(() => this.buffer.writeInt32LE(val, this.pointer), 4);
} }
writeUInt16(val: number): void {
this.advance(() => this.buffer.writeUInt16LE(val, this.pointer), 2);
}
writeUInt32(val: number): void {
this.advance(() => this.buffer.writeUInt32LE(val, this.pointer), 4);
}
writeAll(buf: Buffer): void { writeAll(buf: Buffer): void {
this.buffer = Buffer.concat([this.buffer, buf]); this.buffer = Buffer.concat([this.buffer, buf]);
} }
...@@ -202,8 +223,8 @@ export class Replay { ...@@ -202,8 +223,8 @@ export class Replay {
/* ------------------ Static helpers ------------------ */ /* ------------------ Static helpers ------------------ */
static fromFile(path: string): Replay { static async fromFile(path: string): Promise<Replay> {
return Replay.fromBuffer(fs.readFileSync(path)); return Replay.fromBuffer(await fs.promises.readFile(path));
} }
static fromBuffer(buffer: Buffer): Replay { static fromBuffer(buffer: Buffer): Replay {
...@@ -227,13 +248,22 @@ export class Replay { ...@@ -227,13 +248,22 @@ export class Replay {
private static readHeader(reader: ReplayReader): ReplayHeader { private static readHeader(reader: ReplayReader): ReplayHeader {
const h = new ReplayHeader(); const h = new ReplayHeader();
h.id = reader.readInt32(); h.id = reader.readUInt32();
h.version = reader.readInt32(); h.version = reader.readUInt32();
h.flag = reader.readInt32(); h.flag = reader.readUInt32();
h.seed = reader.readInt32(); h.seed = reader.readUInt32();
h.dataSizeRaw = reader.readByteArray(4); h.dataSizeRaw = reader.readByteArray(4);
h.hash = reader.readInt32(); h.hash = reader.readUInt32();
h.props = reader.readByteArray(8); h.props = reader.readByteArray(8);
if (h.id === REPLAY_ID_YRP2) {
for(let i = 0; i < SEED_COUNT; i++) {
h.seedSequence.push(reader.readUInt32());
}
h.headerVersion = reader.readUInt32();
h.value1 = reader.readUInt32();
h.value2 = reader.readUInt32();
h.value3 = reader.readUInt32();
}
return h; return h;
} }
...@@ -331,18 +361,27 @@ export class Replay { ...@@ -331,18 +361,27 @@ export class Replay {
return Buffer.concat([headerWriter.buffer, body]); return Buffer.concat([headerWriter.buffer, body]);
} }
writeToFile(path: string): void { async writeToFile(path: string): Promise<void> {
fs.writeFileSync(path, this.toBuffer()); await fs.promises.writeFile(path, this.toBuffer());
} }
private writeHeader(w: ReplayWriter): void { private writeHeader(w: ReplayWriter): void {
w.writeInt32(this.header!.id); w.writeUInt32(this.header!.id);
w.writeInt32(this.header!.version); w.writeUInt32(this.header!.version);
w.writeInt32(this.header!.flag); w.writeUInt32(this.header!.flag);
w.writeInt32(this.header!.seed); w.writeUInt32(this.header!.seed);
w.writeByteArray(this.header!.dataSizeRaw); w.writeByteArray(this.header!.dataSizeRaw);
w.writeInt32(this.header!.hash); w.writeUInt32(this.header!.hash);
w.writeByteArray(this.header!.props); w.writeByteArray(this.header!.props);
if (this.header!.id === REPLAY_ID_YRP2) {
for (let i = 0; i < SEED_COUNT; i++) {
w.writeUInt32(this.header!.seedSequence[i]);
}
w.writeUInt32(this.header!.headerVersion);
w.writeUInt32(this.header!.value1);
w.writeUInt32(this.header!.value2);
w.writeUInt32(this.header!.value3);
}
} }
private writeContent(w: ReplayWriter): void { private writeContent(w: ReplayWriter): void {
......
...@@ -1362,11 +1362,15 @@ class Room ...@@ -1362,11 +1362,15 @@ class Room
@hostinfo.start_lp, @hostinfo.start_hand, @hostinfo.draw_count, @hostinfo.time_limit, @hostinfo.replay_mode] @hostinfo.start_lp, @hostinfo.start_hand, @hostinfo.draw_count, @hostinfo.time_limit, @hostinfo.replay_mode]
if firstSeed if firstSeed
# first seed is number[8], so we have to make it base64 if Array.isArray(firstSeed)
firstSeedBuf = Buffer.allocUnsafe(firstSeed.length * 4) # new replay with extended header and long seed
for i in [0...firstSeed.length] firstSeedBuf = Buffer.allocUnsafe(firstSeed.length * 4)
firstSeedBuf.writeUInt32LE(firstSeed[i], i * 4) for i in [0...firstSeed.length]
param.push(firstSeedBuf.toString('base64')) firstSeedBuf.writeUInt32LE(firstSeed[i], i * 4)
param.push(firstSeedBuf.toString('base64'))
else
# old replay with short seed
param.push(firstSeed.toString())
try try
@process = spawn './ygopro', param, {cwd: 'ygopro'} @process = spawn './ygopro', param, {cwd: 'ygopro'}
...@@ -1511,7 +1515,7 @@ class Room ...@@ -1511,7 +1515,7 @@ class Room
return false return false
try try
@recover_replay = await ReplayParser.fromFile(settings.modules.tournament_mode.replay_path + @recover_duel_log.replayFileName) @recover_replay = await ReplayParser.fromFile(settings.modules.tournament_mode.replay_path + @recover_duel_log.replayFileName)
@spawn(@recover_replay.header.seed) # TODO: refa header.seed @spawn(if @recover_replay.header.seed_sequence.length then @recover_replay.header.seed_sequence else @recover_replay.header.seed)
return true return true
catch e catch e
log.warn("LOAD RECOVER REPLAY FAIL", e.toString()) log.warn("LOAD RECOVER REPLAY FAIL", e.toString())
......
...@@ -564,7 +564,7 @@ ...@@ -564,7 +564,7 @@
long_resolve_cards = global.long_resolve_cards = (await loadJSONAsync('./data/long_resolve_cards.json')); long_resolve_cards = global.long_resolve_cards = (await loadJSONAsync('./data/long_resolve_cards.json'));
} }
if (settings.modules.tournament_mode.enable_recover) { if (settings.modules.tournament_mode.enable_recover) {
ReplayParser = global.ReplayParser = require("./Replay.js"); ReplayParser = global.ReplayParser = (require("./Replay.js")).Replay;
} }
if (settings.modules.athletic_check.enabled) { if (settings.modules.athletic_check.enabled) {
AthleticChecker = require("./athletic-check.js").AthleticChecker; AthleticChecker = require("./athletic-check.js").AthleticChecker;
...@@ -1771,12 +1771,17 @@ ...@@ -1771,12 +1771,17 @@
var e, firstSeedBuf, i, j, param, ref; var e, firstSeedBuf, i, j, param, ref;
param = [0, this.hostinfo.lflist, this.hostinfo.rule, this.hostinfo.mode, this.hostinfo.duel_rule, (this.hostinfo.no_check_deck ? 'T' : 'F'), (this.hostinfo.no_shuffle_deck ? 'T' : 'F'), this.hostinfo.start_lp, this.hostinfo.start_hand, this.hostinfo.draw_count, this.hostinfo.time_limit, this.hostinfo.replay_mode]; param = [0, this.hostinfo.lflist, this.hostinfo.rule, this.hostinfo.mode, this.hostinfo.duel_rule, (this.hostinfo.no_check_deck ? 'T' : 'F'), (this.hostinfo.no_shuffle_deck ? 'T' : 'F'), this.hostinfo.start_lp, this.hostinfo.start_hand, this.hostinfo.draw_count, this.hostinfo.time_limit, this.hostinfo.replay_mode];
if (firstSeed) { if (firstSeed) {
// first seed is number[8], so we have to make it base64 if (Array.isArray(firstSeed)) {
firstSeedBuf = Buffer.allocUnsafe(firstSeed.length * 4); // new replay with extended header and long seed
for (i = j = 0, ref = firstSeed.length; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) { firstSeedBuf = Buffer.allocUnsafe(firstSeed.length * 4);
firstSeedBuf.writeUInt32LE(firstSeed[i], i * 4); for (i = j = 0, ref = firstSeed.length; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {
firstSeedBuf.writeUInt32LE(firstSeed[i], i * 4);
}
param.push(firstSeedBuf.toString('base64'));
} else {
// old replay with short seed
param.push(firstSeed.toString());
} }
param.push(firstSeedBuf.toString('base64'));
} }
try { try {
this.process = spawn('./ygopro', param, { this.process = spawn('./ygopro', param, {
...@@ -1992,7 +1997,7 @@ ...@@ -1992,7 +1997,7 @@
} }
try { try {
this.recover_replay = (await ReplayParser.fromFile(settings.modules.tournament_mode.replay_path + this.recover_duel_log.replayFileName)); this.recover_replay = (await ReplayParser.fromFile(settings.modules.tournament_mode.replay_path + this.recover_duel_log.replayFileName));
this.spawn(this.recover_replay.header.seed); // TODO: refa header.seed this.spawn(this.recover_replay.header.seed_sequence.length ? this.recover_replay.header.seed_sequence : this.recover_replay.header.seed);
return true; return true;
} catch (error1) { } catch (error1) {
e = error1; e = error1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment