Commit 3dd42f5b authored by nanahira's avatar nanahira

add fromUpdateDeckPayload

parent 2126ac5a
......@@ -23,6 +23,10 @@ const ygomobileDeckUrl = deck.toYGOMobileDeckURL(); // YGOMobile Deck URL
const yetAnotherDeck3 = YGOProDeck.fromYGOMobileDeckURL(ygomobileDeckUrl); // decode it back
const updateDeckPayload = deck.toUpdateDeckPayload(); // payload for MSG_UPDATE_DECK
// decode it back, but a callback is needed for check if it's extra deck
// use SQLite3 is recommended
// by default, it would assume everything is main deck
const yetAnotherDeck4 = YGOProDeck.fromUpdateDeckPayload(updateDeckPayload, (code, i, mainc) => i >= mainc - 15);
```
......
import { fromBase64Url, toBase64Url } from './src/base64';
import { BufferWriter, countItems } from './src/utils';
import { BufferReader, BufferWriter, countItems } from './src/utils';
import { fromYdkeURL, toYdkeURL } from './src/ydke';
import { fromYGOMobileDeckURL, toYGOMobileDeckURL } from './src/ygom';
......@@ -105,6 +105,32 @@ export default class YGOProDeck {
return new YGOProDeck().fromYdkString(str);
}
fromUpdateDeckPayload(buf: Uint8Array, isExtraDeckCard: (code: number, i: number, mainc: number) => boolean = () => false) {
const reader = new BufferReader(buf);
const mainc = reader.readUint32LE();
const sidec = reader.readUint32LE();
this.main = [];
this.extra = [];
this.side = [];
for(let i = 0; i < mainc; i++) {
const id = reader.readUint32LE();
if (isExtraDeckCard(id, i, mainc)) {
this.extra.push(id);
} else {
this.main.push(id);
}
}
for(let i = 0; i < sidec; i++) {
const id = reader.readUint32LE();
this.side.push(id);
}
return this;
}
static fromUpdateDeckPayload(buf: Uint8Array, isExtraDeckCard: (code: number) => boolean = () => false) {
return new YGOProDeck().fromUpdateDeckPayload(buf, isExtraDeckCard);
}
toUpdateDeckPayload() {
const cards = [...this.main, ...this.extra, ...this.side];
const writer = new BufferWriter(cards.length * 4 + 8)
......
......@@ -5,50 +5,146 @@ export function countItems<T>(arr: T[]) {
}
return map;
}
export class BufferWriter {
abstract class BufferCursor {
buffer: Uint8Array;
pointer = 0;
constructor(bufOrLength: Uint8Array | number) {
if (typeof bufOrLength === 'number') {
this.buffer = new Uint8Array(bufOrLength);
} else {
this.buffer = bufOrLength;
}
}
/**
* 安全地将 pointer 向前移动指定字节数。
* @param bytes 要移动的字节数
* @returns 原来 pointer 的值
* @throws RangeError 如果越界
*/
protected increasePointer(bytes: number): number {
const old = this.pointer;
const next = old + bytes;
if (next > this.buffer.length) {
throw new RangeError(
`Pointer overflow: tried to move to ${next}, but buffer length is ${this.buffer.length}`
);
}
this.pointer = next;
return old;
}
}
export class BufferWriter extends BufferCursor {
constructor(length: number) {
this.buffer = new Uint8Array(length);
super(length);
}
writeUint32LE(value: number) {
this.buffer[this.pointer++] = value & 0xff;
this.buffer[this.pointer++] = (value >>> 8) & 0xff;
this.buffer[this.pointer++] = (value >>> 16) & 0xff;
this.buffer[this.pointer++] = (value >>> 24) & 0xff;
const idx = this.increasePointer(4);
this.buffer[idx ] = value & 0xff;
this.buffer[idx + 1] = (value >>> 8) & 0xff;
this.buffer[idx + 2] = (value >>> 16) & 0xff;
this.buffer[idx + 3] = (value >>> 24) & 0xff;
return this;
}
writeUint32BE(value: number) {
this.buffer[this.pointer++] = (value >>> 24) & 0xff;
this.buffer[this.pointer++] = (value >>> 16) & 0xff;
this.buffer[this.pointer++] = (value >>> 8) & 0xff;
this.buffer[this.pointer++] = value & 0xff;
const idx = this.increasePointer(4);
this.buffer[idx ] = (value >>> 24) & 0xff;
this.buffer[idx + 1] = (value >>> 16) & 0xff;
this.buffer[idx + 2] = (value >>> 8) & 0xff;
this.buffer[idx + 3] = value & 0xff;
return this;
}
writeUint16LE(value: number) {
this.buffer[this.pointer++] = value & 0xff;
this.buffer[this.pointer++] = (value >>> 8) & 0xff;
const idx = this.increasePointer(2);
this.buffer[idx ] = value & 0xff;
this.buffer[idx + 1] = (value >>> 8) & 0xff;
return this;
}
writeUint16BE(value: number) {
this.buffer[this.pointer++] = (value >>> 8) & 0xff;
this.buffer[this.pointer++] = value & 0xff;
const idx = this.increasePointer(2);
this.buffer[idx ] = (value >>> 8) & 0xff;
this.buffer[idx + 1] = value & 0xff;
return this;
}
writeUint8(value: number) {
this.buffer[this.pointer++] = value & 0xff;
const idx = this.increasePointer(1);
this.buffer[idx] = value & 0xff;
return this;
}
writeString(str: string) {
for (let i = 0; i < str.length; i++) {
this.buffer[this.pointer++] = str.charCodeAt(i);
const idx = this.increasePointer(1);
this.buffer[idx] = str.charCodeAt(i);
}
return this;
}
}
export class BufferReader extends BufferCursor {
constructor(buf: Uint8Array) {
super(buf);
}
readUint32LE(): number {
const idx = this.increasePointer(4);
const b = this.buffer;
// >>>0 保证无符号输出
return (
(b[idx ] ) |
(b[idx + 1] << 8) |
(b[idx + 2] << 16) |
(b[idx + 3] << 24)
) >>> 0;
}
readUint32BE(): number {
const idx = this.increasePointer(4);
const b = this.buffer;
return (
(b[idx ] << 24) |
(b[idx + 1] << 16) |
(b[idx + 2] << 8) |
(b[idx + 3] )
) >>> 0;
}
readUint16LE(): number {
const idx = this.increasePointer(2);
const b = this.buffer;
return (b[idx] | (b[idx + 1] << 8));
}
readUint16BE(): number {
const idx = this.increasePointer(2);
const b = this.buffer;
return ((b[idx] << 8) | b[idx + 1]);
}
readUint8(): number {
const idx = this.increasePointer(1);
return this.buffer[idx];
}
readString(length: number): string {
let s = '';
for (let i = 0; i < length; i++) {
const idx = this.increasePointer(1);
s += String.fromCharCode(this.buffer[idx]);
}
return s;
}
readRemaining(): Uint8Array {
const remaining = this.buffer.length - this.pointer;
const idx = this.increasePointer(remaining);
return this.buffer.subarray(idx, this.buffer.length);
}
}
import YGOProDeck from '..';
import { BufferReader } from '../src/utils';
const deck = new YGOProDeck();
deck.main.push(123);
......@@ -86,4 +87,22 @@ describe('Sample test.', () => {
expect(decoded.extra).toStrictEqual(deck.extra);
expect(decoded.side).toStrictEqual(deck.side);
});
it('should encode and decode update deck payload', () => {
const uri = 'http://deck.ourygo.top?name=%E7%99%BD%E9%93%B6%E5%9F%8E%E7%A0%81&ygotype=deck&v=1&d=J-xK4Mka02AuEAMf2dV6mj7aRemuJNQJK8BwrcEYh0MqOJqBJSgYqUwxPEVhOG8R18WWVKmzkT-xEdwxbmGVkkOdrVIpufaYI3Hs8oOrcya8Bi40h9G79iFW80rq-o6P-AHsusPY5nmvHLol0DIqEykESVlf6VSbxVJp-j7XZtTE0XvmJW80rqH28R4rgyRovOusVJzbutenYFBA_cyK6d3UWcQkJQlLjaroWavH-INFA56k5DQNWOQ1gpvxrKVBLgEk1olpolKmSgriramLlgtBK1EQ6C6oi94ZyHe7N6T7mqE6peds7mahrORP6A';
const deck = new YGOProDeck().fromYGOMobileDeckURL(uri);
const updateDeckPayload = deck.toUpdateDeckPayload();
const bufferReader = new BufferReader(updateDeckPayload);
const mainc = bufferReader.readUint32LE();
const sidec = bufferReader.readUint32LE();
expect(mainc).toBe(58 + 15);
expect(sidec).toBe(15);
const deckAgain = new YGOProDeck().fromUpdateDeckPayload(updateDeckPayload, (id, i, mainc) => i >= 58);
expect(deckAgain.main).toHaveLength(58);
expect(deckAgain.extra).toHaveLength(15);
expect(deckAgain.side).toHaveLength(15);
expect(deckAgain.main).toStrictEqual(deck.main);
expect(deckAgain.extra).toStrictEqual(deck.extra);
expect(deckAgain.side).toStrictEqual(deck.side);
})
});
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