Commit 653ec1d3 authored by nanahira's avatar nanahira

first

parent 7fcb61a9
Pipeline #4735 failed with stages
in 23 seconds
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
/build
/output
.git*
.dockerignore
Dockerfile
.gitlab-ci.yml
/config.yaml
.idea
/replay
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
/build
/dist
/output
/config.yaml
.idea
/replay
stages:
- build
- combine
- deploy
variables:
GIT_DEPTH: "1"
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_TEST_ARM_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
CONTAINER_TEST_X86_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-x86:
stage: build
tags:
- docker
script:
- docker build --pull -t $CONTAINER_TEST_X86_IMAGE .
- docker push $CONTAINER_TEST_X86_IMAGE
build-arm:
stage: build
tags:
- docker-arm
script:
- docker build --pull -t $CONTAINER_TEST_ARM_IMAGE .
- docker push $CONTAINER_TEST_ARM_IMAGE
combine:
stage: combine
tags:
- docker
script:
- docker pull $CONTAINER_TEST_X86_IMAGE
- docker pull $CONTAINER_TEST_ARM_IMAGE
- docker manifest create $CONTAINER_TEST_IMAGE --amend $CONTAINER_TEST_X86_IMAGE --amend $CONTAINER_TEST_ARM_IMAGE
- docker manifest push $CONTAINER_TEST_IMAGE
deploy_latest:
stage: deploy
tags:
- docker
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE
- docker push $CONTAINER_RELEASE_IMAGE
only:
- master
deploy_tag:
stage: deploy
tags:
- docker
variables:
CONTAINER_TAG_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
script:
- docker pull $CONTAINER_TEST_IMAGE
- docker tag $CONTAINER_TEST_IMAGE $CONTAINER_TAG_IMAGE
- docker push $CONTAINER_TAG_IMAGE
only:
- tags
/install-npm.sh
.git*
/output
/dest
/config.yaml
.idea
.dockerignore
Dockerfile
/src
.eslintrc.js
.prettierrc
webpack.config.js
tsconfig.json
/replay
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
FROM node:bullseye-slim
LABEL Author="Nanahira <nanahira@momobako.com>"
RUN apt update && apt -y install python3 build-essential && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /usr/src/app
COPY ./package*.json ./
RUN npm ci
COPY . ./
RUN npm run build
CMD ["npm", "run", "start"]
This diff is collapsed.
import lzma from 'lzma-native';
import _ from 'lodash';
import fs from 'fs';
export class replayHeader {
public static replayCompressedFlag = 0x1;
public static replayTagFlag = 0x2;
public static replayDecodedFlag = 0x4;
id = 0;
version = 0;
flag = 0;
seed = 0;
dataSizeRaw: number[] = [];
hash = 0;
props: number[] = [];
public getDataSize() {
return (
this.dataSizeRaw[0] +
this.dataSizeRaw[1] * 0x100 +
this.dataSizeRaw[2] * 0x10000 +
this.dataSizeRaw[3] * 0x1000000
);
}
public getIsTag() {
return (this.flag & replayHeader.replayTagFlag) > 0;
}
public getIsCompressed() {
return (this.flag & replayHeader.replayCompressedFlag) > 0;
}
public getLzmaHeader() {
const bytes = [].concat(
this.props.slice(0, 5),
this.dataSizeRaw,
[0, 0, 0, 0],
);
return Buffer.from(bytes);
}
public get dataSize() {
return this.getDataSize();
}
public get isTag() {
return this.getIsTag();
}
public get isCompressed() {
return this.getIsCompressed();
}
}
export class ReplayReader {
pointer = 0;
constructor(public buffer: Buffer) {}
public readByte() {
const answer = this.buffer.readUInt8(this.pointer);
this.pointer += 1;
return answer;
}
public readByteArray(length: number) {
const answer: number[] = [];
_.range(1, length + 1).forEach((i) => answer.push(this.readByte()));
return answer;
}
public readInt8() {
const answer = this.buffer.readInt8(this.pointer);
this.pointer += 1;
return answer;
}
public readUInt8() {
const answer = this.buffer.readUInt8(this.pointer);
this.pointer += 1;
return answer;
}
public readInt16() {
const answer = this.buffer.readInt16LE(this.pointer);
this.pointer += 2;
return answer;
}
public readInt32() {
const answer = this.buffer.readInt32LE(this.pointer);
this.pointer += 4;
return answer;
}
public readAll() {
const answer = this.buffer.slice(this.pointer);
// @pointer = 0
return answer;
}
public readString(length: number) {
if (this.pointer + length > this.buffer.length) {
return null;
}
const full = this.buffer
.slice(this.pointer, this.pointer + length)
.toString('utf16le');
const answer = full.split('\u0000')[0];
this.pointer += length;
return answer;
}
public readRaw(length: number) {
if (this.pointer + length > this.buffer.length) {
return null;
}
const answer = this.buffer.slice(this.pointer, this.pointer + length);
this.pointer += length;
return answer;
}
}
export class Deck {
main: number[];
ex: number[];
}
async function lzmaDecompress(buf: Buffer): Promise<Buffer> {
return new Promise((resolve) => {
lzma.decompress(buf, 5, resolve);
});
}
export class Replay {
header: replayHeader;
hostName = '';
clientName = '';
startLp = 0;
startHand = 0;
drawCount = 0;
opt = 0;
hostDeck: Deck;
clientDeck: Deck;
tagHostName = '';
tagClientName = '';
tagHostDeck: Deck;
tagClientDeck: Deck;
responses: Buffer[];
constructor() {
this.header = null;
this.hostName = '';
this.clientName = '';
this.startLp = 0;
this.startHand = 0;
this.drawCount = 0;
this.opt = 0;
this.hostDeck = null;
this.clientDeck = null;
this.tagHostName = null;
this.tagClientName = null;
this.tagHostDeck = null;
this.tagClientDeck = null;
this.responses = null;
}
public getDecks() {
if (this.isTag) {
return [
this.hostDeck,
this.clientDeck,
this.tagHostDeck,
this.tagClientDeck,
];
} else {
return [this.hostDeck, this.clientDeck];
}
}
public getIsTag() {
return this.header && this.header.isTag;
}
public static async fromFile(filePath: string) {
return Replay.fromBuffer(await fs.promises.readFile(filePath));
}
public static async fromBuffer(buffer: Buffer) {
let reader = new ReplayReader(buffer);
const header = Replay.readHeader(reader);
const lzmaBuffer = Buffer.concat([
header.getLzmaHeader(),
reader.readAll(),
]);
let decompressed: Buffer;
if (!header.isCompressed) {
// console.error(`no decompress`);
decompressed = lzmaBuffer;
} else {
// console.error(`decompressing`);
decompressed = Buffer.from(await lzmaDecompress(lzmaBuffer));
// console.error(`decompressed`);
}
reader = new ReplayReader(decompressed);
const replay = Replay.readReplay(header, reader);
return replay;
}
public static readHeader(reader: ReplayReader) {
const header = new replayHeader();
header.id = reader.readInt32();
header.version = reader.readInt32();
header.flag = reader.readInt32();
header.seed = reader.readInt32();
header.dataSizeRaw = reader.readByteArray(4);
header.hash = reader.readInt32();
header.props = reader.readByteArray(8);
return header;
}
public static readReplay(header: replayHeader, reader: ReplayReader) {
const replay = new Replay();
// console.error('reading info');
replay.header = header;
replay.hostName = reader.readString(40);
if (header.isTag) {
replay.tagHostName = reader.readString(40);
}
if (header.isTag) {
replay.tagClientName = reader.readString(40);
}
replay.clientName = reader.readString(40);
replay.startLp = reader.readInt32();
replay.startHand = reader.readInt32();
replay.drawCount = reader.readInt32();
replay.opt = reader.readInt32();
// console.error(JSON.stringify(replay, null, 2));
// console.error('reading deck');
replay.hostDeck = Replay.readDeck(reader);
if (header.isTag) {
replay.tagHostDeck = Replay.readDeck(reader);
}
if (header.isTag) {
replay.tagClientDeck = Replay.readDeck(reader);
}
replay.clientDeck = Replay.readDeck(reader);
// console.error('reading response');
replay.responses = Replay.readResponses(reader);
return replay;
}
public static readDeck(reader: ReplayReader) {
const deck = new Deck();
deck.main = Replay.readDeckPack(reader);
deck.ex = Replay.readDeckPack(reader);
return deck;
}
public static readDeckPack(reader: ReplayReader) {
const length = reader.readInt32();
if (length > 128) {
return [];
}
const answer: number[] = [];
_.range(1, length + 1).forEach(() => answer.push(reader.readInt32()));
return answer;
}
public static readResponses(reader: ReplayReader) {
let length: number;
let single: Buffer;
const answer: Buffer[] = [];
while (true) {
try {
length = reader.readUInt8();
if (length > 64) {
length = 64;
}
single = reader.readRaw(length);
if (!single) {
break;
}
answer.push(single);
} catch (_error) {
break;
}
}
return answer;
}
public get decks() {
return this.getDecks();
}
public get isTag() {
return this.getIsTag();
}
}
export * from './Replay';
#!/bin/bash
npm install --save \
lodash
npm install --save-dev \
@types/node \
@types/lodash \
typescript \
'@typescript-eslint/eslint-plugin@^4.28.2' \
'@typescript-eslint/parser@^4.28.2 '\
'eslint@^7.30.0' \
'eslint-config-prettier@^8.3.0' \
'eslint-plugin-prettier@^3.4.0' \
prettier
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "ygopro-replay-parser",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/parser.js"
},
"repository": {
"type": "git",
"url": "git@git.mycard.moe:nanahira/ygopro-replay-parser.git"
},
"author": "Nanahira",
"license": "AGPL-3.0",
"dependencies": {
"lodash": "^4.17.21",
"lzma-native": "^8.0.1",
"moment": "^2.29.1"
},
"devDependencies": {
"@types/lodash": "^4.14.172",
"@types/lzma-native": "^4.0.1",
"@types/node": "^16.6.1",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"prettier": "^2.3.2",
"typescript": "^4.3.5"
}
}
import { Replay } from './Replay';
import { promises as fs } from 'fs';
interface ReplayParseResult {
replay: Replay;
path: string;
}
async function runForSingleFile(path: string): Promise<ReplayParseResult> {
console.error(`Reading ${path}`);
return {
path,
replay: await Replay.fromFile(path),
};
}
async function runForDirectory(dir: string) {
const files = (await fs.readdir(dir)).filter((f) => f.endsWith('yrp'));
const res: ReplayParseResult[] = [];
for (const file of files) {
const path = `${dir}/${file}`;
res.push(await runForSingleFile(path));
}
return res;
}
async function main() {
const res = await runForDirectory(process.argv[2] || './replay');
console.log(JSON.stringify(res, null, 2));
}
main();
{
"compilerOptions": {
"outDir": "dist",
"module": "commonjs",
"target": "esnext",
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"declaration": true,
"sourceMap": true,
"noImplicitAny": true,
},
"compileOnSave": true,
"allowJs": true,
"include": [
"*.ts",
"src/**/*.ts",
"test/**/*.ts"
]
}
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