Commit ac5426de authored by nanahira's avatar nanahira

use buffer mutate for polyfill

parent c7fc8f65
...@@ -223,8 +223,8 @@ class YGOProMessagesHelper { ...@@ -223,8 +223,8 @@ class YGOProMessagesHelper {
break; break;
} }
let buffer = messageBuffer.slice(3, 2 + messageLength); let buffer = messageBuffer.slice(3, 2 + messageLength);
let bufferMutated = false;
//console.log(l, direction, proto, cancel); //console.log(l, direction, proto, cancel);
let shrinkCount = 0;
for (let priority = 0; priority < 4; ++priority) { for (let priority = 0; priority < 4; ++priority) {
if (cancel) { if (cancel) {
break; break;
...@@ -232,15 +232,20 @@ class YGOProMessagesHelper { ...@@ -232,15 +232,20 @@ class YGOProMessagesHelper {
const handlerCollection = this.handlers[direction][priority]; const handlerCollection = this.handlers[direction][priority];
if (proto && handlerCollection.has(bufferProto)) { if (proto && handlerCollection.has(bufferProto)) {
let struct = this.structs.get(this.proto_structs[direction][proto]); let struct = this.structs.get(this.proto_structs[direction][proto]);
let info = null; for (const handler of handlerCollection.get(bufferProto)) {
if (struct) { let info = null;
struct._setBuff(buffer); if (struct) {
info = underscore_1.default.clone(struct.fields); struct._setBuff(buffer);
} info = underscore_1.default.clone(struct.fields);
for (let handler of handlerCollection.get(bufferProto)) { }
cancel = await handler.handle(buffer, info, datas, params); cancel = await handler.handle(buffer, info, datas, params);
if (cancel) { if (cancel) {
if (typeof cancel === "string") { if (Buffer.isBuffer(cancel)) {
buffer = cancel;
bufferMutated = true;
cancel = false;
}
else if (typeof cancel === "string") {
if (cancel === '_cancel') { if (cancel === '_cancel') {
return { return {
datas: [], datas: [],
...@@ -248,8 +253,15 @@ class YGOProMessagesHelper { ...@@ -248,8 +253,15 @@ class YGOProMessagesHelper {
}; };
} }
else if (cancel.startsWith('_shrink_')) { else if (cancel.startsWith('_shrink_')) {
shrinkCount += parseInt(cancel.slice(8)); const targetShrinkCount = parseInt(cancel.slice(8));
cancel = false; if (targetShrinkCount > buffer.length) {
cancel = true;
}
else {
buffer = buffer.slice(0, buffer.length - targetShrinkCount);
bufferMutated = true;
cancel = false;
}
} }
} }
break; break;
...@@ -257,14 +269,15 @@ class YGOProMessagesHelper { ...@@ -257,14 +269,15 @@ class YGOProMessagesHelper {
} }
} }
} }
if (!cancel && shrinkCount <= messageLength) { if (!cancel) {
if (shrinkCount > 0) { if (bufferMutated) {
// rewrite first 2 bytes of length const newLength = buffer.length + 1;
const oldLength = messageBuffer.readUInt16LE(0);
const newLength = oldLength - shrinkCount;
messageBuffer.writeUInt16LE(newLength, 0); messageBuffer.writeUInt16LE(newLength, 0);
datas.push(Buffer.concat([messageBuffer.slice(0, 3), buffer]));
}
else {
datas.push(messageBuffer.slice(0, 2 + messageLength));
} }
datas.push(messageBuffer.slice(0, 2 + messageLength - shrinkCount));
} }
messageBuffer = messageBuffer.slice(2 + messageLength); messageBuffer = messageBuffer.slice(2 + messageLength);
messageLength = 0; messageLength = 0;
......
...@@ -8,13 +8,13 @@ import net from "net"; ...@@ -8,13 +8,13 @@ import net from "net";
class Handler { class Handler {
private handler: (buffer: Buffer, info: any, datas: Buffer[], params: any) => Promise<boolean | string>; private handler: (buffer: Buffer, info: any, datas: Buffer[], params: any) => Promise<boolean | string | Buffer>;
synchronous: boolean; synchronous: boolean;
constructor(handler: (buffer: Buffer, info: any, datas: Buffer[], params: any) => Promise<boolean | string>, synchronous: boolean) { constructor(handler: (buffer: Buffer, info: any, datas: Buffer[], params: any) => Promise<boolean | string | Buffer>, synchronous: boolean) {
this.handler = handler; this.handler = handler;
this.synchronous = synchronous || false; this.synchronous = synchronous || false;
} }
async handle(buffer: Buffer, info: any, datas: Buffer[], params: any): Promise<boolean | string> { async handle(buffer: Buffer, info: any, datas: Buffer[], params: any): Promise<boolean | string | Buffer> {
if (this.synchronous) { if (this.synchronous) {
return await this.handler(buffer, info, datas, params); return await this.handler(buffer, info, datas, params);
} else { } else {
...@@ -264,7 +264,7 @@ export class YGOProMessagesHelper { ...@@ -264,7 +264,7 @@ export class YGOProMessagesHelper {
} else { } else {
if (messageBuffer.length >= 2 + messageLength) { if (messageBuffer.length >= 2 + messageLength) {
const proto = this.constants[direction][bufferProto]; const proto = this.constants[direction][bufferProto];
let cancel: string | boolean = proto && protoFilter && !protoFilter.includes(proto); let cancel: string | boolean | Buffer = proto && protoFilter && !protoFilter.includes(proto);
if (cancel && preconnect) { if (cancel && preconnect) {
feedback = { feedback = {
type: "INVALID_PACKET", type: "INVALID_PACKET",
...@@ -273,8 +273,8 @@ export class YGOProMessagesHelper { ...@@ -273,8 +273,8 @@ export class YGOProMessagesHelper {
break; break;
} }
let buffer = messageBuffer.slice(3, 2 + messageLength); let buffer = messageBuffer.slice(3, 2 + messageLength);
let bufferMutated = false;
//console.log(l, direction, proto, cancel); //console.log(l, direction, proto, cancel);
let shrinkCount = 0;
for (let priority = 0; priority < 4; ++priority) { for (let priority = 0; priority < 4; ++priority) {
if (cancel) { if (cancel) {
break; break;
...@@ -282,23 +282,33 @@ export class YGOProMessagesHelper { ...@@ -282,23 +282,33 @@ export class YGOProMessagesHelper {
const handlerCollection: Map<number, Handler[]> = this.handlers[direction][priority]; const handlerCollection: Map<number, Handler[]> = this.handlers[direction][priority];
if (proto && handlerCollection.has(bufferProto)) { if (proto && handlerCollection.has(bufferProto)) {
let struct = this.structs.get(this.proto_structs[direction][proto]); let struct = this.structs.get(this.proto_structs[direction][proto]);
let info = null; for (const handler of handlerCollection.get(bufferProto)) {
if (struct) { let info = null;
struct._setBuff(buffer); if (struct) {
info = _.clone(struct.fields); struct._setBuff(buffer);
} info = _.clone(struct.fields);
for (let handler of handlerCollection.get(bufferProto)) { }
cancel = await handler.handle(buffer, info, datas, params); cancel = await handler.handle(buffer, info, datas, params);
if (cancel) { if (cancel) {
if (typeof cancel === "string") { if (Buffer.isBuffer(cancel)) {
buffer = cancel as any;
bufferMutated = true;
cancel = false;
} else if (typeof cancel === "string") {
if (cancel === '_cancel') { if (cancel === '_cancel') {
return { return {
datas: [], datas: [],
feedback feedback
} }
} else if (cancel.startsWith('_shrink_')) { } else if (cancel.startsWith('_shrink_')) {
shrinkCount += parseInt(cancel.slice(8)); const targetShrinkCount = parseInt(cancel.slice(8));
cancel = false; if (targetShrinkCount > buffer.length) {
cancel = true;
} else {
buffer = buffer.slice(0, buffer.length - targetShrinkCount);
bufferMutated = true;
cancel = false;
}
} }
} }
break; break;
...@@ -306,14 +316,14 @@ export class YGOProMessagesHelper { ...@@ -306,14 +316,14 @@ export class YGOProMessagesHelper {
} }
} }
} }
if (!cancel && shrinkCount <= messageLength) { if (!cancel) {
if (shrinkCount > 0) { if (bufferMutated) {
// rewrite first 2 bytes of length const newLength = buffer.length + 1;
const oldLength = messageBuffer.readUInt16LE(0);
const newLength = oldLength - shrinkCount;
messageBuffer.writeUInt16LE(newLength, 0); messageBuffer.writeUInt16LE(newLength, 0);
datas.push(Buffer.concat([messageBuffer.slice(0, 3), buffer]));
} else {
datas.push(messageBuffer.slice(0, 2 + messageLength));
} }
datas.push(messageBuffer.slice(0, 2 + messageLength - shrinkCount));
} }
messageBuffer = messageBuffer.slice(2 + messageLength); messageBuffer = messageBuffer.slice(2 + messageLength);
messageLength = 0; messageLength = 0;
......
...@@ -2,33 +2,34 @@ ...@@ -2,33 +2,34 @@
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.BasePolyfiller = void 0; exports.BasePolyfiller = void 0;
class BasePolyfiller { class BasePolyfiller {
constructor() { async polyfillGameMsg(msgTitle, buffer) {
this.shrinkCount = 0; return;
}
async polyfillResponse(msgTitle, buffer) {
return;
} }
async polyfillGameMsg(msgTitle, buffer) { }
async polyfillResponse(msgTitle, buffer) { }
splice(buf, offset, deleteCount = 1) { splice(buf, offset, deleteCount = 1) {
if (offset < 0 || offset >= buf.length) if (offset < 0 || offset >= buf.length)
return Buffer.alloc(0); return Buffer.alloc(0);
deleteCount = Math.min(deleteCount, buf.length - offset); deleteCount = Math.min(deleteCount, buf.length - offset);
const end = offset + deleteCount; const end = offset + deleteCount;
const deleted = Buffer.allocUnsafe(deleteCount); const newBuf = Buffer.concat([
buf.copy(deleted, 0, offset, end); buf.slice(0, offset),
const moveLength = buf.length - end; buf.slice(end)
if (moveLength > 0) { ]);
buf.copy(buf, offset, end, buf.length); return newBuf;
}
buf.fill(0, buf.length - deleteCount);
this.shrinkCount += deleteCount;
return deleted;
} }
insert(buf, offset, insertBuf) { insert(buf, offset, insertBuf) {
const availableSpace = buf.length - offset; if (offset < 0)
const insertLength = Math.min(insertBuf.length, availableSpace); offset = 0;
buf.copy(buf, offset + insertLength, offset, buf.length - insertLength); if (offset > buf.length)
insertBuf.copy(buf, offset, 0, insertLength); offset = buf.length;
this.shrinkCount -= insertLength; const newBuf = Buffer.concat([
return buf; buf.slice(0, offset),
insertBuf,
buf.slice(offset)
]);
return newBuf;
} }
} }
exports.BasePolyfiller = BasePolyfiller; exports.BasePolyfiller = BasePolyfiller;
export class BasePolyfiller { export class BasePolyfiller {
shrinkCount = 0;
async polyfillGameMsg(msgTitle: string, buffer: Buffer) {} async polyfillGameMsg(msgTitle: string, buffer: Buffer): Promise<Buffer | undefined> {
return;
}
async polyfillResponse(msgTitle: string, buffer: Buffer) {} async polyfillResponse(msgTitle: string, buffer: Buffer): Promise<Buffer | undefined> {
return;
}
splice(buf: Buffer, offset: number, deleteCount = 1): Buffer { splice(buf: Buffer, offset: number, deleteCount = 1): Buffer {
if (offset < 0 || offset >= buf.length) return Buffer.alloc(0); if (offset < 0 || offset >= buf.length) return Buffer.alloc(0);
...@@ -11,31 +14,24 @@ export class BasePolyfiller { ...@@ -11,31 +14,24 @@ export class BasePolyfiller {
deleteCount = Math.min(deleteCount, buf.length - offset); deleteCount = Math.min(deleteCount, buf.length - offset);
const end = offset + deleteCount; const end = offset + deleteCount;
const deleted = Buffer.allocUnsafe(deleteCount); const newBuf = Buffer.concat([
buf.copy(deleted, 0, offset, end); buf.slice(0, offset),
buf.slice(end)
]);
const moveLength = buf.length - end; return newBuf;
if (moveLength > 0) {
buf.copy(buf, offset, end, buf.length);
}
buf.fill(0, buf.length - deleteCount);
this.shrinkCount += deleteCount;
return deleted;
} }
insert(buf: Buffer, offset: number, insertBuf: Buffer) { insert(buf: Buffer, offset: number, insertBuf: Buffer): Buffer {
const availableSpace = buf.length - offset; if (offset < 0) offset = 0;
const insertLength = Math.min(insertBuf.length, availableSpace); if (offset > buf.length) offset = buf.length;
buf.copy(buf, offset + insertLength, offset, buf.length - insertLength); const newBuf = Buffer.concat([
buf.slice(0, offset),
insertBuf.copy(buf, offset, 0, insertLength); insertBuf,
buf.slice(offset)
this.shrinkCount -= insertLength; ]);
return buf; return newBuf;
} }
} }
...@@ -15,17 +15,15 @@ const getPolyfillers = (version) => { ...@@ -15,17 +15,15 @@ const getPolyfillers = (version) => {
}; };
async function polyfillGameMsg(version, msgTitle, buffer) { async function polyfillGameMsg(version, msgTitle, buffer) {
const polyfillers = getPolyfillers(version); const polyfillers = getPolyfillers(version);
let shrinkCount = 0; let mutated = false;
for (const polyfiller of polyfillers) { for (const polyfiller of polyfillers) {
await polyfiller.polyfillGameMsg(msgTitle, buffer); const newBuf = await polyfiller.polyfillGameMsg(msgTitle, buffer);
if (polyfiller.shrinkCount > 0) { if (newBuf) {
if (polyfiller.shrinkCount === 0x3f3f3f3f) { mutated = true;
return 0x3f3f3f3f; // special case for cancel message buffer = newBuf;
}
shrinkCount += polyfiller.shrinkCount;
} }
} }
return shrinkCount; return mutated ? buffer : undefined;
} }
async function polyfillResponse(version, msgTitle, buffer) { async function polyfillResponse(version, msgTitle, buffer) {
const polyfillers = getPolyfillers(version); const polyfillers = getPolyfillers(version);
......
...@@ -15,17 +15,15 @@ const getPolyfillers = (version: number) => { ...@@ -15,17 +15,15 @@ const getPolyfillers = (version: number) => {
export async function polyfillGameMsg(version: number, msgTitle: string, buffer: Buffer) { export async function polyfillGameMsg(version: number, msgTitle: string, buffer: Buffer) {
const polyfillers = getPolyfillers(version); const polyfillers = getPolyfillers(version);
let shrinkCount = 0; let mutated = false;
for (const polyfiller of polyfillers) { for (const polyfiller of polyfillers) {
await polyfiller.polyfillGameMsg(msgTitle, buffer); const newBuf = await polyfiller.polyfillGameMsg(msgTitle, buffer);
if (polyfiller.shrinkCount > 0) { if (newBuf) {
if (polyfiller.shrinkCount === 0x3f3f3f3f) { mutated = true;
return 0x3f3f3f3f; // special case for cancel message buffer = newBuf;
}
shrinkCount += polyfiller.shrinkCount;
} }
} }
return shrinkCount; return mutated ? buffer : undefined;
} }
export async function polyfillResponse(version: number, msgTitle: string, buffer: Buffer) { export async function polyfillResponse(version: number, msgTitle: string, buffer: Buffer) {
......
...@@ -17,7 +17,7 @@ class Polyfiller1361 extends base_polyfiller_1.BasePolyfiller { ...@@ -17,7 +17,7 @@ class Polyfiller1361 extends base_polyfiller_1.BasePolyfiller {
// buf[0]: MSG_CONFIRM_CARDS // buf[0]: MSG_CONFIRM_CARDS
// buf[1]: playerid // buf[1]: playerid
// buf[2]: ADDED skip_panel // buf[2]: ADDED skip_panel
this.splice(buffer, 2, 1); return this.splice(buffer, 2, 1);
} }
else if (msgTitle === 'SELECT_CHAIN') { else if (msgTitle === 'SELECT_CHAIN') {
// buf[0]: MSG_SELECT_CHAIN // buf[0]: MSG_SELECT_CHAIN
...@@ -46,10 +46,10 @@ class Polyfiller1361 extends base_polyfiller_1.BasePolyfiller { ...@@ -46,10 +46,10 @@ class Polyfiller1361 extends base_polyfiller_1.BasePolyfiller {
// 从后往前 splice 每个 item 的 forced 字段 // 从后往前 splice 每个 item 的 forced 字段
for (let i = size - 1; i >= 0; i--) { for (let i = size - 1; i >= 0; i--) {
const itemOffset = itemStartOffset + i * 14; const itemOffset = itemStartOffset + i * 14;
this.splice(buffer, itemOffset + 1, 1); // 删除每个 item 的 forced(第 1 字节) buffer = this.splice(buffer, itemOffset + 1, 1); // 删除每个 item 的 forced(第 1 字节)
} }
// 最后再插入旧版所需的 forced 标志 // 最后再插入旧版所需的 forced 标志
this.insert(buffer, 4, Buffer.from([anyForced ? 1 : 0])); return this.insert(buffer, 4, Buffer.from([anyForced ? 1 : 0]));
} }
else if (msgTitle === 'SELECT_SUM') { else if (msgTitle === 'SELECT_SUM') {
// buf[0]: MSG_SELECT_SUM // buf[0]: MSG_SELECT_SUM
......
...@@ -15,7 +15,7 @@ export class Polyfiller1361 extends BasePolyfiller { ...@@ -15,7 +15,7 @@ export class Polyfiller1361 extends BasePolyfiller {
// buf[0]: MSG_CONFIRM_CARDS // buf[0]: MSG_CONFIRM_CARDS
// buf[1]: playerid // buf[1]: playerid
// buf[2]: ADDED skip_panel // buf[2]: ADDED skip_panel
this.splice(buffer, 2, 1); return this.splice(buffer, 2, 1);
} else if (msgTitle === 'SELECT_CHAIN') { } else if (msgTitle === 'SELECT_CHAIN') {
// buf[0]: MSG_SELECT_CHAIN // buf[0]: MSG_SELECT_CHAIN
// buf[1]: playerid // buf[1]: playerid
...@@ -46,11 +46,11 @@ export class Polyfiller1361 extends BasePolyfiller { ...@@ -46,11 +46,11 @@ export class Polyfiller1361 extends BasePolyfiller {
// 从后往前 splice 每个 item 的 forced 字段 // 从后往前 splice 每个 item 的 forced 字段
for (let i = size - 1; i >= 0; i--) { for (let i = size - 1; i >= 0; i--) {
const itemOffset = itemStartOffset + i * 14; const itemOffset = itemStartOffset + i * 14;
this.splice(buffer, itemOffset + 1, 1); // 删除每个 item 的 forced(第 1 字节) buffer = this.splice(buffer, itemOffset + 1, 1); // 删除每个 item 的 forced(第 1 字节)
} }
// 最后再插入旧版所需的 forced 标志 // 最后再插入旧版所需的 forced 标志
this.insert(buffer, 4, Buffer.from([anyForced ? 1 : 0])); return this.insert(buffer, 4, Buffer.from([anyForced ? 1 : 0]));
} else if (msgTitle === 'SELECT_SUM') { } else if (msgTitle === 'SELECT_SUM') {
// buf[0]: MSG_SELECT_SUM // buf[0]: MSG_SELECT_SUM
// buf[1]: 0 => equal, 1 => greater // buf[1]: 0 => equal, 1 => greater
......
...@@ -2594,11 +2594,11 @@ ygopro.stoc_follow 'GAME_MSG', true, (buffer, info, client, server, datas)-> ...@@ -2594,11 +2594,11 @@ ygopro.stoc_follow 'GAME_MSG', true, (buffer, info, client, server, datas)->
return unless room and !client.reconnecting return unless room and !client.reconnecting
msg = buffer.readInt8(0) msg = buffer.readInt8(0)
msg_name = ygopro.constants.MSG[msg] msg_name = ygopro.constants.MSG[msg]
shrink_count = await msg_polyfill.polyfillGameMsg(client.actual_version, msg_name, buffer) new_buf = await msg_polyfill.polyfillGameMsg(client.actual_version, msg_name, buffer)
if shrink_count == 0x3f3f3f3f if new_buf
return true buffer = new_buf
record_last_game_msg = () -> record_last_game_msg = () ->
client.last_game_msg = Buffer.from(buffer.slice(0, buffer.length - shrink_count)) client.last_game_msg = new_buf or buffer
client.last_game_msg_title = msg_name client.last_game_msg_title = msg_name
#console.log client.pos, "MSG", msg_name #console.log client.pos, "MSG", msg_name
if msg_name == 'RETRY' and room.recovering if msg_name == 'RETRY' and room.recovering
...@@ -2873,8 +2873,8 @@ ygopro.stoc_follow 'GAME_MSG', true, (buffer, info, client, server, datas)-> ...@@ -2873,8 +2873,8 @@ ygopro.stoc_follow 'GAME_MSG', true, (buffer, info, client, server, datas)->
room.recover_buffers[client.pos].push(buffer) room.recover_buffers[client.pos].push(buffer)
return true return true
if shrink_count > 0 if new_buf
return "_shrink_#{shrink_count}" return buffer
else else
return false return false
......
...@@ -3405,19 +3405,19 @@ ...@@ -3405,19 +3405,19 @@
}; };
ygopro.stoc_follow('GAME_MSG', true, async function(buffer, info, client, server, datas) { ygopro.stoc_follow('GAME_MSG', true, async function(buffer, info, client, server, datas) {
var card, chain, check, count, cpos, deck_found, found, hint_type, i, id, j, l, len, len1, len2, len3, limbo_found, line, loc, m, max_loop, msg, msg_name, n, o, oppo_pos, phase, player, playertype, pos, ppos, reason, record_last_game_msg, ref, ref1, ref2, ref3, ref4, ref5, room, shrink_count, trigger_location, val, win_pos; var card, chain, check, count, cpos, deck_found, found, hint_type, i, id, j, l, len, len1, len2, len3, limbo_found, line, loc, m, max_loop, msg, msg_name, n, new_buf, o, oppo_pos, phase, player, playertype, pos, ppos, reason, record_last_game_msg, ref, ref1, ref2, ref3, ref4, ref5, room, trigger_location, val, win_pos;
room = ROOM_all[client.rid]; room = ROOM_all[client.rid];
if (!(room && !client.reconnecting)) { if (!(room && !client.reconnecting)) {
return; return;
} }
msg = buffer.readInt8(0); msg = buffer.readInt8(0);
msg_name = ygopro.constants.MSG[msg]; msg_name = ygopro.constants.MSG[msg];
shrink_count = (await msg_polyfill.polyfillGameMsg(client.actual_version, msg_name, buffer)); new_buf = (await msg_polyfill.polyfillGameMsg(client.actual_version, msg_name, buffer));
if (shrink_count === 0x3f3f3f3f) { if (new_buf) {
return true; buffer = new_buf;
} }
record_last_game_msg = function() { record_last_game_msg = function() {
client.last_game_msg = Buffer.from(buffer.slice(0, buffer.length - shrink_count)); client.last_game_msg = new_buf || buffer;
return client.last_game_msg_title = msg_name; return client.last_game_msg_title = msg_name;
}; };
//console.log client.pos, "MSG", msg_name //console.log client.pos, "MSG", msg_name
...@@ -3792,8 +3792,8 @@ ...@@ -3792,8 +3792,8 @@
} }
return true; return true;
} }
if (shrink_count > 0) { if (new_buf) {
return `_shrink_${shrink_count}`; return buffer;
} else { } else {
return false; return false;
} }
......
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