mirror of
https://github.com/laochan-eacnet/backend.git
synced 2026-10-03 12:07:56 +03:00
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@laochan/ea3",
|
||||
"name": "@laochan/eacloud",
|
||||
"version": "0.0.0",
|
||||
"description": "Yet another e-amusement server emulator.",
|
||||
"description": "EACloud server emulator.",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">= 20.9 < 21"
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { inject, singleton } from 'tsyringe';
|
||||
import { Combine } from '../../utils/combine.js';
|
||||
import { generic } from '../../decorators/eacnet.js';
|
||||
import { Serializable, v } from '../../utils/kxml-value.js';
|
||||
import { EacGeneric } from '../eac-generic.js';
|
||||
import * as data from '../../datas/ddr.js';
|
||||
import { User } from './user.js';
|
||||
import { UserService } from '../../services/ddr/user.js';
|
||||
import { Context } from '../../types.js';
|
||||
|
||||
@singleton()
|
||||
export default class extends Combine(User, EacGeneric) {
|
||||
constructor(
|
||||
@inject(UserService) private readonly userService: UserService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.userService;
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getItemList(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
item_num: v.s32(1),
|
||||
item: {
|
||||
item_id: v.str('DDR_TICKET'),
|
||||
not_free_count: v.s32(114514),
|
||||
free_count: v.s32(0),
|
||||
buyable_point: v.s32(9999999),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async tabooword_2_check(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
tabooword_2: {
|
||||
$status: 0,
|
||||
is_taboo: v.bool(false),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async log_2_save(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
log_2: {
|
||||
$status: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getprev(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
fileinfo_2: {
|
||||
$status: 0,
|
||||
soundinfo: data.previewSoundInfos,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getmain(ctx: Context): Promise<Serializable> {
|
||||
const mcode = ctx.body.data.mcode as number;
|
||||
const result = data.mainSoundInfoMap.get(mcode);
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
fileinfo_2: {
|
||||
$status: 0,
|
||||
soundinfo: result,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getmusic(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
data_2: {
|
||||
$status: 0,
|
||||
mdb: {
|
||||
music: data.musics,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getGoodsList(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
goods_num: v.s32(3),
|
||||
goods: [{
|
||||
goods_id: v.str('G0000001'),
|
||||
item_id: v.str('DDR_TICKET'),
|
||||
price: v.s32(1),
|
||||
}, {
|
||||
goods_id: v.str('G0000002'),
|
||||
item_id: v.str('DDR_TICKET'),
|
||||
price: v.s32(1),
|
||||
}, {
|
||||
goods_id: v.str('G0000003'),
|
||||
item_id: v.str('DDR_TICKET'),
|
||||
price: v.s32(1),
|
||||
}],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
import { generic } from '../../decorators/eacnet.js';
|
||||
import { Context } from '../../types.js';
|
||||
import { Serializable, v } from '../../utils/kxml-value.js';
|
||||
import { mpacks } from '../../datas/ddr.js';
|
||||
import { PlayerRecordType, UserLoadReq, UserSaveReq } from '../../types/ddr/index.js';
|
||||
import { UserService } from '../../services/ddr/user.js';
|
||||
|
||||
export class User {
|
||||
userService: UserService;
|
||||
|
||||
async _usernew(token: string): Promise<Serializable> {
|
||||
await this.userService.upsertPlayData({
|
||||
_id: token,
|
||||
name: '',
|
||||
bestClearLevel: 0,
|
||||
singleGrade: 0,
|
||||
doubleGrade: 0,
|
||||
leagueClass: 0,
|
||||
records: {
|
||||
'COMMON': new Array(64).fill(''),
|
||||
'OPTION': new Array(64).fill(''),
|
||||
'LAST': new Array(64).fill(''),
|
||||
'RIVAL': new Array(64).fill(''),
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
$status: 0,
|
||||
|
||||
result: v.s32(0),
|
||||
seq: v.s32(0),
|
||||
code: v.s32(0),
|
||||
shoparea: v.str('JP-13'),
|
||||
};
|
||||
}
|
||||
|
||||
emptyData() {
|
||||
return {
|
||||
$status: 0,
|
||||
is_new: v.bool(true),
|
||||
is_refid_locked: v.bool(false),
|
||||
best_clear_level: v.s8(0),
|
||||
eventdata_count_all: v.s16(0),
|
||||
grade: {
|
||||
single_grade: v.u32(0),
|
||||
double_grade: v.u32(0),
|
||||
},
|
||||
golden_league: {
|
||||
league_class: v.s32(0),
|
||||
},
|
||||
gamesession: v.u64(Date.now()),
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _userload(token: string, data: UserLoadReq): Promise<Serializable> {
|
||||
if (data.refid.startsWith('X')) {
|
||||
return this.emptyData();
|
||||
}
|
||||
|
||||
const playData = await this.userService.findPlayData(token);
|
||||
|
||||
if (!playData) {
|
||||
return this.emptyData();
|
||||
}
|
||||
|
||||
const musicDatas = await this.userService.findMusicDatas(token);
|
||||
|
||||
return {
|
||||
$status: 0,
|
||||
is_new: v.bool(false),
|
||||
is_refid_locked: v.bool(false),
|
||||
best_clear_level: v.s8(playData.bestClearLevel),
|
||||
eventdata_count_all: v.s16(0),
|
||||
grade: {
|
||||
single_grade: v.u32(playData.singleGrade),
|
||||
double_grade: v.u32(playData.doubleGrade),
|
||||
},
|
||||
golden_league: {
|
||||
league_class: v.s32(playData.leagueClass),
|
||||
},
|
||||
gamesession: v.u64(Date.now()),
|
||||
music: musicDatas.map(m => ({
|
||||
mcode: v.u32(m.mcode),
|
||||
note_comp: m.notes.map(n => ({
|
||||
note_str: v.str(n.join(',')),
|
||||
})),
|
||||
})),
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _ghostload(): Promise<Serializable> {
|
||||
return {
|
||||
$status: 0,
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _usersave(token: string, data: UserSaveReq): Promise<Serializable> {
|
||||
const playData = await this.userService.findPlayData(token);
|
||||
|
||||
if (!playData) {
|
||||
return {
|
||||
$status: 404,
|
||||
result: v.s32(1),
|
||||
};
|
||||
}
|
||||
|
||||
playData.name = data.name;
|
||||
playData.records['COMMON'][24] = data.name;
|
||||
|
||||
const updateMusics = data.note.filter(n => n.mcode != 0)
|
||||
|
||||
await Promise.all([
|
||||
this.userService.upsertPlayData(playData),
|
||||
this.userService.insertPlayRecords(updateMusics.map(m => ({
|
||||
player: token,
|
||||
mcode: m.mcode,
|
||||
noteType: m.notetype,
|
||||
playtime: m.endtime,
|
||||
playStyle: m.playstyle,
|
||||
rank: m.rank,
|
||||
clearKind: m.clearkind,
|
||||
score: m.score,
|
||||
exScore: m.score,
|
||||
maxCombo: m.maxcombo,
|
||||
life: m.life,
|
||||
judge: {
|
||||
fast: m.fastcount,
|
||||
slow: m.slowcount,
|
||||
marvelous: m.judge_marvelous,
|
||||
great: m.judge_great,
|
||||
good: m.judge_good,
|
||||
boo: m.judge_boo,
|
||||
miss: m.judge_miss,
|
||||
ok: m.judge_ok,
|
||||
ng: m.judge_ng,
|
||||
},
|
||||
calorie: m.calorie,
|
||||
ghost: m.ghost,
|
||||
}))),
|
||||
...updateMusics.map(m => this.userService.updateMusicData(token, m))
|
||||
]);
|
||||
|
||||
return {
|
||||
$status: 0,
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _rivalload(): Promise<Serializable> {
|
||||
return {
|
||||
$status: 0,
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _inheritance(): Promise<Serializable> {
|
||||
return {
|
||||
$status: 0,
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _arcadeload(): Promise<Serializable> {
|
||||
return {
|
||||
$status: 0,
|
||||
is_acdata_exists: v.bool(false),
|
||||
eventload: {
|
||||
eventdata_count_all: v.s16(0),
|
||||
},
|
||||
acdata_gettime: v.u64(Date.now()),
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _linkage_pre_playable(): Promise<Serializable> {
|
||||
const now = new Date();
|
||||
|
||||
return {
|
||||
$status: 0,
|
||||
update_y: v.u16(now.getUTCFullYear()),
|
||||
update_m: v.u8(now.getUTCMonth()),
|
||||
next_y: v.u16(now.getUTCFullYear() + 1),
|
||||
next_m: v.u8(now.getUTCMonth()),
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
async _mpackload() {
|
||||
return {
|
||||
$status: 0,
|
||||
mpacklist: mpacks,
|
||||
result: v.s32(0),
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async usergamedata_advanced(ctx: Context): Promise<Serializable> {
|
||||
const data = ctx.body.data as {
|
||||
mode: string,
|
||||
};
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
playerdata_2: await {
|
||||
'usernew': this._usernew,
|
||||
'userload': this._userload,
|
||||
'ghostload': this._ghostload,
|
||||
'usersave': this._usersave,
|
||||
'rivalload': this._rivalload,
|
||||
'inheritance': this._inheritance,
|
||||
'arcadeload': this._arcadeload,
|
||||
'mpackload': this._mpackload,
|
||||
'linkage_pre_playable': this._linkage_pre_playable,
|
||||
}[data.mode].apply(this, [ctx.token, data]),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
convertCsvMask(maskString: string): number[] {
|
||||
const mask = BigInt('0x' + maskString);
|
||||
const fields = [];
|
||||
for (let i = 0; i < 64; i++) {
|
||||
if ((mask & (1n << BigInt(i))) == 0n)
|
||||
continue;
|
||||
|
||||
fields.push(i);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
@generic()
|
||||
async usergamedata_send(ctx: Context): Promise<Serializable> {
|
||||
const playerData = await this.userService.findPlayData(ctx.token);
|
||||
|
||||
const records = ((ctx.body.data.record.d instanceof Array ?
|
||||
ctx.body.data.record.d : [ctx.body.data.record.d]) as string[])
|
||||
.map(v => {
|
||||
const columns = atob(v).split(',')
|
||||
const mask = this.convertCsvMask(columns[0]);
|
||||
const type = columns[1] as PlayerRecordType;
|
||||
const fields = columns.slice(3);
|
||||
|
||||
return {
|
||||
mask, type, fields,
|
||||
}
|
||||
});
|
||||
|
||||
for (const record of records) {
|
||||
const row = playerData.records[record.type] ?? (new Array<string>(32).fill(''));
|
||||
record.mask.forEach((col, i) => {
|
||||
row[col] = record.fields[i];
|
||||
})
|
||||
}
|
||||
|
||||
await this.userService.upsertPlayData(playerData);
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
playerdata_2: {
|
||||
$status: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async usergamedata_recv(ctx: Context): Promise<Serializable> {
|
||||
const csv = ctx.body.data.recv_csv as string;
|
||||
const loadFields = csv.split(',');
|
||||
|
||||
const result: Serializable[] = [];
|
||||
const playData = await this.userService.findPlayData(ctx.token);
|
||||
|
||||
for (let i = 0; i < loadFields.length; i += 2) {
|
||||
const mask = this.convertCsvMask(loadFields[i + 1]);
|
||||
const type = loadFields[i] as PlayerRecordType;
|
||||
|
||||
if (!(playData && type in playData.records)) {
|
||||
result.push(v.str('<NODATA>'));
|
||||
continue;
|
||||
}
|
||||
|
||||
const row = ['1'];
|
||||
mask.forEach((col) => {
|
||||
row.push(playData.records[type][col]);
|
||||
});
|
||||
row.push('');
|
||||
|
||||
result.push(v.str(btoa(row.join(','))));
|
||||
}
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
playerdata_2: {
|
||||
$status: 0,
|
||||
player: {
|
||||
record_num: v.u32(loadFields.length),
|
||||
record: {
|
||||
d: result,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Context } from '../types.js';
|
||||
import { p2d, sdvx } from '../decorators/eacnet.js';
|
||||
import { generic, p2d } from '../decorators/eacnet.js';
|
||||
import { v } from '../utils/kxml-value.js';
|
||||
import { singleton } from 'tsyringe';
|
||||
|
||||
@@ -17,7 +17,7 @@ export class DefaultService {
|
||||
|
||||
p2d()(undefined, undefined, descriptor);
|
||||
return descriptor.value();
|
||||
} else if (ctx.service.name === 'sdvx') {
|
||||
} else if (['sdvx', 'ddr'].includes(ctx.service.name)) {
|
||||
const result = {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
@@ -39,7 +39,7 @@ export class DefaultService {
|
||||
value: () => result,
|
||||
};
|
||||
|
||||
sdvx()(undefined, undefined, descriptor);
|
||||
generic()(undefined, undefined, descriptor);
|
||||
return descriptor.value();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
import { generic } from '../decorators/eacnet.js';
|
||||
import { Context } from '../types.js';
|
||||
import config from '../utils/config.js';
|
||||
import { Serializable, v } from '../utils/kxml-value.js';
|
||||
import { tokenToCardNumber, tokenToHash, tokenToSnsId } from '../utils/laochan-id.js';
|
||||
|
||||
export class EacGeneric {
|
||||
@generic()
|
||||
async getGoodsList(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
goods_num: v.s32(0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getUserIDs(ctx: Context): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
card_num: v.str(tokenToCardNumber(ctx.token)),
|
||||
ref_id: v.str(tokenToHash(ctx.token)),
|
||||
data_id: v.str(tokenToHash(ctx.token)),
|
||||
sns_id: v.str(tokenToSnsId(ctx.token)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getSubscriptionStatus(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
subscription: {
|
||||
name: [
|
||||
v.str('eac_ddr_basic'),
|
||||
v.str('EAC_POPNLIVELY'),
|
||||
v.str('eac_sdvx_basic'),
|
||||
v.str('eacgitadora_basic'),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async heartbeat(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
state: v.s32(1),
|
||||
next_clock: v.u64(new Date().valueOf() + 36e5),
|
||||
server_state: v.s32(1),
|
||||
mainte_start_clock: v.u64(0),
|
||||
mainte_end_clock: v.u64(0),
|
||||
usta_boot_status: v.s32(1),
|
||||
usta_time_remain: v.s32(0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async checkVersion(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
permission: v.s32(1),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@generic()
|
||||
async getServerClock(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
server_clock: v.u64(new Date().valueOf()),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getServerState(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
server_state: v.s32(1),
|
||||
mainte_start_clock: v.u64(0),
|
||||
mainte_end_clock: v.u64(0),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_transactionIdMap: Map<string, string> = new Map<string, string>();
|
||||
|
||||
@generic()
|
||||
async reserveConsumeItem(ctx: Context): Promise<Serializable> {
|
||||
const goodsId = ctx.body.goodsId as string;
|
||||
const transactionId = (Math.random() * 10000000000).toString(16);
|
||||
|
||||
this._transactionIdMap.set(transactionId, goodsId);
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
transaction_id: v.str(transactionId),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async consumeItem(ctx: Context): Promise<Serializable> {
|
||||
const transactionId = ctx.body.transaction_id as string;
|
||||
const goodsId = this._transactionIdMap.get(transactionId);
|
||||
|
||||
this._transactionIdMap.delete(transactionId);
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
item_id: v.str(goodsId),
|
||||
free_count: v.s32(0),
|
||||
not_free_count: v.s32(1919810),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@generic()
|
||||
async getServices(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
service_num: v.s32(18),
|
||||
service: [
|
||||
{
|
||||
service_name: v.str('acRelay'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/AcRelay`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('cancelReserveConsumeItem'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/CancelReserveConsumeItem`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('checkGameStart'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/CheckGameStart`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('checkVersion'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/CheckVersion`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('consumeItem'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/ConsumeItem`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getGoodsList'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetGoodsList`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getHash'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetHash`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getItemList'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/GetItemList`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getLauncherData'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetLauncherData`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getResourceInfo'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetResourceInfo`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getServerClock'),
|
||||
url: v.str(`${config.selfUrl}/pre_process/GetServerClock`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getServerState'),
|
||||
url: v.str(`${config.selfUrl}/pre_process/GetServerStatus`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getServices'),
|
||||
url: v.str(`${config.selfUrl}/pre_process/GetServices`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getSubscriptionStatus'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/GetSubscriptionStatus`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getUserIDs'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/GetUserId`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('heartbeat'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/Heartbeat`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('reserveConsumeItem'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/ReserveConsumeItem`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('uploadFile'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/UploadFile`),
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -25,17 +25,3 @@ export const ITEM_LIST = data.items.map(id => ({
|
||||
free_count: v.s32(0),
|
||||
}));
|
||||
|
||||
export const MUSIC_LIST = [
|
||||
...data.musics.map(m => ({
|
||||
music_id: v.s32(m.id),
|
||||
kind: v.s32(1),
|
||||
note_bit: v.s32(m.noteBit),
|
||||
music_pack_item_id: v.str('')
|
||||
})),
|
||||
...data.omni_musics.map(m => ({
|
||||
music_id: v.s32(m.id),
|
||||
kind: v.s32(2),
|
||||
note_bit: v.s32(0),
|
||||
music_pack_item_id: v.str('')
|
||||
})),
|
||||
];
|
||||
|
||||
@@ -3,9 +3,7 @@ import { p2d } from "../../decorators/eacnet.js";
|
||||
import { UserService } from "../../services/p2d/user.js";
|
||||
import { Context } from "../../types.js";
|
||||
import { v } from "../../utils/kxml-value.js";
|
||||
import { MUSIC_LIST } from "./index.js";
|
||||
import { Binary } from "mongodb";
|
||||
import * as data from '../../datas/p2d.js'
|
||||
|
||||
// user only needs to unlock omni music, which is temporary
|
||||
const userUnlockMusic = new Map<string, number>();
|
||||
@@ -90,12 +88,10 @@ export class Music {
|
||||
const result = ctx.body as PlayerPlayLog;
|
||||
result.player = ctx.token;
|
||||
|
||||
let updateTask = Promise.resolve();
|
||||
|
||||
const play_style = Math.floor(result.note_id / 5);
|
||||
const diff = result.note_id % 5;
|
||||
|
||||
updateTask = (async () => {
|
||||
const updateTask = (async () => {
|
||||
const musicData = await this.userService.getMusicData(result.player, result.music_id, play_style);
|
||||
|
||||
musicData.play_num[diff]++;
|
||||
@@ -117,7 +113,7 @@ export class Music {
|
||||
musicData.clear_num[diff]++;
|
||||
}
|
||||
|
||||
this.userService.upsertMusicData(musicData);
|
||||
await this.userService.upsertMusicData(musicData);
|
||||
})();
|
||||
|
||||
// @ts-expect-error ghost is Buffer now, convert it to binary
|
||||
@@ -148,24 +144,7 @@ export class Music {
|
||||
}
|
||||
|
||||
@p2d()
|
||||
async getMusicList(ctx: Context) {
|
||||
let music = [...MUSIC_LIST];
|
||||
|
||||
if (userUnlockMusic.has(ctx.token)) {
|
||||
const targetMusic = userUnlockMusic.get(ctx.token);
|
||||
userUnlockMusic.delete(ctx.token);
|
||||
|
||||
const bitData = data.omni_musics.find(v => v.id === targetMusic);
|
||||
|
||||
music = music.filter(v => v.music_id['__value'] !== targetMusic);
|
||||
music.push({
|
||||
music_id: v.s32(bitData.id),
|
||||
kind: v.s32(1),
|
||||
note_bit: v.s32(bitData.noteBit),
|
||||
music_pack_item_id: v.str(''),
|
||||
});
|
||||
}
|
||||
|
||||
async getMusicList() {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error: v.s32(0),
|
||||
@@ -173,8 +152,7 @@ export class Music {
|
||||
status: v.s32(0),
|
||||
check_sum: v.str(''),
|
||||
music_list: {
|
||||
music_num: v.s32(music.length),
|
||||
music,
|
||||
music_num: v.s32(0),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { sdvx } from "../../decorators/eacnet.js";
|
||||
import { generic } from "../../decorators/eacnet.js";
|
||||
import { Context } from "../../types.js";
|
||||
import { Serializable, v } from "../../utils/kxml-value.js";
|
||||
import * as data from '../../datas/sdvx.js';
|
||||
|
||||
export class AcRelay {
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_play_e(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -19,7 +19,7 @@ export class AcRelay {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_play_s(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -35,7 +35,7 @@ export class AcRelay {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_hiscore() {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -51,7 +51,7 @@ export class AcRelay {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_log(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -66,7 +66,7 @@ export class AcRelay {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_common(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -98,7 +98,7 @@ export class AcRelay {
|
||||
}
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_music_url(ctx: Context): Promise<Serializable> {
|
||||
let { music_id } = ctx.body as { music_id: number | number[] };
|
||||
if (typeof music_id === 'number') {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { sdvx } from "../../decorators/eacnet.js";
|
||||
import { Combine } from "../../utils/combine.js";
|
||||
import config from "../../utils/config.js";
|
||||
import { Serializable, v } from "../../utils/kxml-value.js";
|
||||
import { AcRelay } from "./ac-relay.js";
|
||||
import { UserService } from "../../services/sdvx/user.js";
|
||||
import { inject, singleton } from "tsyringe";
|
||||
import { User } from "./user.js";
|
||||
import { Music } from "./music.js";
|
||||
import { EacGeneric } from "../eac-generic.js";
|
||||
|
||||
@singleton()
|
||||
export default class extends Combine(AcRelay, User, Music) {
|
||||
export default class extends Combine(AcRelay, User, Music, EacGeneric) {
|
||||
constructor(
|
||||
@inject(UserService) private readonly userService: UserService,
|
||||
) {
|
||||
@@ -17,142 +15,4 @@ export default class extends Combine(AcRelay, User, Music) {
|
||||
|
||||
this.userService;
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async heartbeat(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
state: v.s32(1),
|
||||
next_clock: v.u64(new Date().valueOf() + 36e5),
|
||||
server_state: v.s32(1),
|
||||
mainte_start_clock: v.u64(0),
|
||||
mainte_end_clock: v.u64(0),
|
||||
usta_boot_status: v.s32(1),
|
||||
usta_time_remain: v.s32(0),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async checkVersion(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
permission: v.s32(1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async getServerClock(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
server_clock: v.u64(new Date().valueOf()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async getServerState(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
server_state: v.s32(1),
|
||||
mainte_start_clock: v.u64(0),
|
||||
mainte_end_clock: v.u64(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@sdvx('SHIFT_JIS')
|
||||
async getServices(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
service_num: v.s32(18),
|
||||
service: [
|
||||
{
|
||||
service_name: v.str('acRelay'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/AcRelay`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('cancelReserveConsumeItem'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/CancelReserveConsumeItem`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('checkGameStart'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/CheckGameStart`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('checkVersion'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/CheckVersion`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('consumeItem'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/ConsumeItem`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getGoodsList'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetGoodsList`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getHash'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetHash`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getItemList'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/GetItemList`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getLauncherData'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetLauncherData`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getResourceInfo'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/GetResourceInfo`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getServerClock'),
|
||||
url: v.str(`${config.selfUrl}/pre_process/GetServerClock`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getServerState'),
|
||||
url: v.str(`${config.selfUrl}/pre_process/GetServerStatus`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getServices'),
|
||||
url: v.str(`${config.selfUrl}/pre_process/GetServices`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getSubscriptionStatus'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/GetSubscriptionStatus`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('getUserIDs'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/GetUserId`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('heartbeat'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/Heartbeat`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('reserveConsumeItem'),
|
||||
url: v.str(`${config.selfUrl}/need_auth/ReserveConsumeItem`),
|
||||
},
|
||||
{
|
||||
service_name: v.str('uploadFile'),
|
||||
url: v.str(`${config.selfUrl}/needless_auth/UploadFile`),
|
||||
},
|
||||
|
||||
],
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { sdvx } from "../../decorators/eacnet.js";
|
||||
import { generic } from "../../decorators/eacnet.js";
|
||||
import { UserService } from "../../services/sdvx/user.js";
|
||||
import { Serializable, v } from "../../utils/kxml-value.js";
|
||||
import { Context } from "../../types.js";
|
||||
@@ -7,7 +7,7 @@ import { PlayerMusicData, PlayerPlayLog } from "../../types/sdvx/index.js";
|
||||
export class Music {
|
||||
userService: UserService;
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_save_m(ctx: Context): Promise<Serializable> {
|
||||
const { track } = ctx.body as { track: PlayerPlayLog };
|
||||
|
||||
@@ -74,7 +74,7 @@ export class Music {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_load_r(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -94,7 +94,7 @@ export class Music {
|
||||
* score exscore clear_type score_grade max_chain play_count btn_rate long_rate vol_rate
|
||||
* 0 0 0 0 0 timestamp_s 0 0 0 0 0 0
|
||||
*/
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_load_m(ctx: Context): Promise<Serializable> {
|
||||
const mdata = await this.userService.getPlayerMusicDatas(ctx.token);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as data from '../../datas/sdvx.js';
|
||||
import { tokenToCardNumber, tokenToHash, tokenToSnsId } from "../../utils/laochan-id.js";
|
||||
import { Context } from "../../types.js";
|
||||
import { sdvx } from '../../decorators/eacnet.js';
|
||||
import { generic } from '../../decorators/eacnet.js';
|
||||
import { KValueG, Serializable, ValueTypes, v } from '../../utils/kxml-value.js';
|
||||
import { UserService } from '../../services/sdvx/user.js';
|
||||
import { Item, Param } from '../../types/sdvx/savedata.js';
|
||||
@@ -90,7 +89,7 @@ export function tryMergeParam(params: Param[], newParams: object[]) {
|
||||
export class User {
|
||||
userService: UserService;
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_save_c(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -106,7 +105,7 @@ export class User {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_save(ctx: Context): Promise<Serializable> {
|
||||
const playData = await this.userService.getPlayerData(ctx.token);
|
||||
if (!playData) {
|
||||
@@ -168,7 +167,7 @@ export class User {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_new(ctx: Context): Promise<Serializable> {
|
||||
await this.userService.createEmptyPlayerData(ctx.token, ctx.body.name);
|
||||
|
||||
@@ -185,7 +184,7 @@ export class User {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async sv6_load(ctx: Context): Promise<Serializable> {
|
||||
const playData = await this.userService.getPlayerData(ctx.token);
|
||||
|
||||
@@ -227,7 +226,7 @@ export class User {
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
@generic()
|
||||
async getItemList(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
@@ -242,43 +241,4 @@ export class User {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async getGoodsList(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
goods_num: v.s32(0),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async getUserIDs(ctx: Context): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
card_num: v.str(tokenToCardNumber(ctx.token)),
|
||||
ref_id: v.str(tokenToHash(ctx.token)),
|
||||
data_id: v.str(tokenToHash(ctx.token)),
|
||||
sns_id: v.str(tokenToSnsId(ctx.token)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async getSubscriptionStatus(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
response: {
|
||||
subscription: {
|
||||
name: v.str('eac_sdvx_basic'),
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21131
File diff suppressed because it is too large
Load Diff
+2
-7782
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ export function eacnet(protocol_name: string, topName: string, dataOffset: numbe
|
||||
const orig = descriptor.value as Function;
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
const obj = await orig.apply(this, args);
|
||||
|
||||
const kxml = toKBinXml(topName, obj, encoding);
|
||||
const compressed = LZ77.compress(kxml.data);
|
||||
const result = Buffer.allocUnsafe(compressed.length + dataOffset);
|
||||
@@ -21,4 +22,4 @@ export function eacnet(protocol_name: string, topName: string, dataOffset: numbe
|
||||
}
|
||||
|
||||
export const p2d = eacnet('P2D:2015091800', 'p2d', 0x2E);
|
||||
export const sdvx = eacnet('2020090800', 'eacnet', 0x2A);
|
||||
export const generic = eacnet('2020090800', 'eacnet', 0x2A);
|
||||
|
||||
+15
-4
@@ -62,8 +62,8 @@ async function main(): Promise<void> {
|
||||
.use(eacnet)
|
||||
.use(async (ctx, next) => {
|
||||
ctx.logger = ctx.service
|
||||
? register(`logger:${ctx.service.name}:${ctx.service.method}`, {
|
||||
useValue: new Logger(`${ctx.service.name}:${ctx.service.method}`),
|
||||
? register(`logger:${ctx.service.name}:${ctx.service.module ? ctx.service.module + ':' : '' }${ctx.service.method}`, {
|
||||
useValue: new Logger(`${ctx.service.name}:${ctx.service.module ? ctx.service.module + ':' : '' }${ctx.service.method}`),
|
||||
})
|
||||
: logger;
|
||||
|
||||
@@ -126,11 +126,22 @@ async function main(): Promise<void> {
|
||||
|
||||
let method = ctx.service.method;
|
||||
|
||||
if (!service || !(service[method])?.bind) {
|
||||
ctx.logger.warn(`unimplemented method.`);
|
||||
if (!service) {
|
||||
ctx.logger.warn(`unimplemented service ${ctx.service.name}.`);
|
||||
|
||||
service = container.resolve(DefaultService);
|
||||
method = 'default';
|
||||
} else {
|
||||
if (!(service[method])?.bind) {
|
||||
method = ctx.service.module + '_' + ctx.service.method;
|
||||
|
||||
if (!(service[method])?.bind) {
|
||||
ctx.logger.warn(`unimplemented method ${JSON.stringify(ctx.service)}.`);
|
||||
|
||||
service = container.resolve(DefaultService);
|
||||
method = 'default';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.body = await (service[method] as Function).bind(service)(ctx);
|
||||
|
||||
@@ -40,11 +40,12 @@ export async function eacnet(ctx: Context, next: Next): Promise<any> {
|
||||
|
||||
ctx.service = {
|
||||
name: info.game_id,
|
||||
module: request.module,
|
||||
method: request.method,
|
||||
};
|
||||
|
||||
ctx.body = request.data ?? {};
|
||||
ctx.eacnetRequest = request;
|
||||
ctx.eacnetRequest = request.service ? request : undefined;
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -59,6 +60,7 @@ export async function eacnet(ctx: Context, next: Next): Promise<any> {
|
||||
|
||||
ctx.service = {
|
||||
name: game,
|
||||
module: 'p2d',
|
||||
method: result[game].method,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { inject, injectable } from 'tsyringe';
|
||||
import { Db } from 'mongodb';
|
||||
import { NoteRecord, PlayerMusicData, PlayerPlayData, PlayerPlayRecord } from '../../types/ddr/index.js';
|
||||
|
||||
@injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
@inject(Db)
|
||||
private readonly db: Db,
|
||||
) {
|
||||
}
|
||||
|
||||
get playDataCol() {
|
||||
return this.db.collection<PlayerPlayData>('ddr_play_data');
|
||||
}
|
||||
|
||||
get playerMusicCol() {
|
||||
return this.db.collection<PlayerMusicData>('ddr_music_data');
|
||||
}
|
||||
|
||||
get playRecordCol() {
|
||||
return this.db.collection<PlayerPlayRecord>('ddr_play_record');
|
||||
}
|
||||
|
||||
findPlayData(token: string): Promise<PlayerPlayData> {
|
||||
return this.playDataCol.findOne({ _id: token });
|
||||
}
|
||||
|
||||
async upsertPlayData(data: PlayerPlayData): Promise<void> {
|
||||
await this.playDataCol.updateOne({ _id: data._id }, {
|
||||
$set: data,
|
||||
}, { upsert: true });
|
||||
}
|
||||
|
||||
async insertPlayRecords(records: PlayerPlayRecord[]): Promise<void> {
|
||||
if (!records.length) {
|
||||
return;
|
||||
}
|
||||
await this.playRecordCol.insertMany(records, {
|
||||
ordered: false,
|
||||
});
|
||||
}
|
||||
|
||||
async updateMusicData(player: string, record: NoteRecord): Promise<void> {
|
||||
const data = await this.playerMusicCol.findOne({ player, mcode: record.mcode }) ?? {
|
||||
player,
|
||||
mcode: record.mcode,
|
||||
notes: [],
|
||||
} as PlayerMusicData;
|
||||
|
||||
while (data.notes.length < record.notetype + 1) {
|
||||
data.notes.push([0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
let [playCount, rank, clearKind, score] = data.notes[record.notetype];
|
||||
|
||||
playCount++;
|
||||
rank = Math.max(record.rank, rank);
|
||||
clearKind = Math.max(record.clearkind, clearKind);
|
||||
score = Math.max(record.score, score);
|
||||
|
||||
data.notes[record.notetype] = [playCount, rank, clearKind, score, 0];
|
||||
|
||||
await this.playerMusicCol.updateOne({ player, mcode: record.mcode }, { $set: data }, { upsert: true });
|
||||
}
|
||||
|
||||
findMusicDatas(player: string): Promise<PlayerMusicData[]> {
|
||||
return this.playerMusicCol.find({ player }).toArray();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export interface ILaochanContext {
|
||||
|
||||
service: {
|
||||
name: string,
|
||||
module: string,
|
||||
method: string,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { PlayerRecordType } from "./index.js";
|
||||
|
||||
export interface PlayerPlayData {
|
||||
_id: string;
|
||||
name: string;
|
||||
bestClearLevel: number;
|
||||
singleGrade: number;
|
||||
doubleGrade: number;
|
||||
leagueClass: number;
|
||||
records: Record<PlayerRecordType, string[]>;
|
||||
}
|
||||
|
||||
export interface PlayerMusicData {
|
||||
player: string;
|
||||
mcode: number;
|
||||
notes: number[][];
|
||||
}
|
||||
|
||||
export interface PlayerPlayRecord {
|
||||
player: string;
|
||||
mcode: number;
|
||||
noteType: number;
|
||||
playtime: number;
|
||||
playStyle: number;
|
||||
rank: number;
|
||||
clearKind: number;
|
||||
score: number
|
||||
exScore: number;
|
||||
maxCombo: number;
|
||||
life: number;
|
||||
judge: {
|
||||
fast: number;
|
||||
slow: number;
|
||||
marvelous: number;
|
||||
great: number;
|
||||
good: number;
|
||||
boo: number;
|
||||
miss: number;
|
||||
ok: number;
|
||||
ng: number
|
||||
};
|
||||
calorie: number;
|
||||
ghost: string;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
export * from './db.js'
|
||||
|
||||
export type PlayerRecordType = 'COMMON' | 'OPTION' | 'LAST' | 'RIVAL';
|
||||
|
||||
export interface EventRecord {
|
||||
eventid: number;
|
||||
eventtype: number;
|
||||
eventno: number;
|
||||
comptime: number;
|
||||
savedata: number;
|
||||
}
|
||||
|
||||
export interface NoteRecord {
|
||||
stagenum: number
|
||||
mcode: number
|
||||
notetype: number
|
||||
rank: number
|
||||
clearkind: number
|
||||
score: number
|
||||
exscore: number
|
||||
maxcombo: number
|
||||
life: number
|
||||
fastcount: number
|
||||
slowcount: number
|
||||
judge_marvelous: number
|
||||
judge_perfect: number
|
||||
judge_great: number
|
||||
judge_good: number
|
||||
judge_boo: number
|
||||
judge_miss: number
|
||||
judge_ok: number
|
||||
judge_ng: number
|
||||
calorie: number
|
||||
ghostsize: number
|
||||
ghost: string
|
||||
opt_speed: number
|
||||
opt_boost: number
|
||||
opt_appearance: number
|
||||
opt_turn: number
|
||||
opt_dark: number
|
||||
opt_scroll: number
|
||||
opt_arrowcolor: number
|
||||
opt_cut: number
|
||||
opt_freeze: number
|
||||
opt_jump: number
|
||||
opt_arrowshape: number
|
||||
opt_filter: number
|
||||
opt_guideline: number
|
||||
opt_gauge: number
|
||||
opt_judgepriority: number
|
||||
opt_timing: number
|
||||
basename: string
|
||||
title_b64: string
|
||||
artist_b64: string
|
||||
bpmMax: number
|
||||
bpmMin: number
|
||||
level: number
|
||||
series: number
|
||||
bemaniFlag: number
|
||||
genreFlag: number
|
||||
limited: number
|
||||
region: number
|
||||
gr_voltage: number
|
||||
gr_stringeam: number
|
||||
gr_chaos: number
|
||||
gr_freeze: number
|
||||
gr_air: number
|
||||
share: boolean
|
||||
endtime: number
|
||||
folder: number
|
||||
playstyle: number
|
||||
measurement: {
|
||||
skipped_level: number
|
||||
measured_point: string
|
||||
measured_level: string
|
||||
measured_level_result: number
|
||||
recommended: {
|
||||
mcode: number
|
||||
notetype: number
|
||||
priority: number
|
||||
}[];
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserGameDataReq {
|
||||
mode: string;
|
||||
refid: string;
|
||||
pcbid: string;
|
||||
locid: string;
|
||||
shoparea: string;
|
||||
gamesession: string;
|
||||
}
|
||||
|
||||
export interface UserSaveReq extends UserGameDataReq {
|
||||
name: string;
|
||||
ddrcode: number;
|
||||
playide: number;
|
||||
playstyle: number;
|
||||
area: number;
|
||||
weight100: number;
|
||||
shopname: string;
|
||||
ispremium: boolean;
|
||||
iseapass: boolean;
|
||||
istakeover: boolean;
|
||||
isrepeater: boolean;
|
||||
isgameover: boolean;
|
||||
note: NoteRecord[];
|
||||
event: EventRecord[];
|
||||
}
|
||||
|
||||
export interface ArcadeLoadReq extends UserGameDataReq {
|
||||
ac_releasecode: string;
|
||||
}
|
||||
|
||||
export interface UserLoadReq extends UserGameDataReq {
|
||||
is_mat: boolean;
|
||||
}
|
||||
+16
-3
@@ -2,7 +2,6 @@ import { to_bin, to_xml } from "@kamyu/kbinxml";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
import _ from "lodash";
|
||||
import { Serializable } from "./kxml-value.js";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
export const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
@@ -135,10 +134,24 @@ function serializeValue(value: any, type: string): string {
|
||||
return value.toString('hex');
|
||||
}
|
||||
|
||||
if (['ip4', 'str', 'string'].includes(type)) {
|
||||
if (['ip4'].includes(type)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (['str', 'string'].includes(type)) {
|
||||
return value.replace(/[<>&'"]/g, (ch: string) => {
|
||||
switch (ch) {
|
||||
case '<': return '<';
|
||||
case '>': return '>';
|
||||
case '&': return '&';
|
||||
case '\'': return ''';
|
||||
case '"': return '"';
|
||||
}
|
||||
|
||||
return ch;
|
||||
});
|
||||
}
|
||||
|
||||
if (type == 'time') {
|
||||
if (value instanceof Date) {
|
||||
return Math.floor(value.valueOf() / 1000).toString();
|
||||
@@ -228,6 +241,6 @@ function serializeObject(obj: Serializable, name: string, linePrefix: string = '
|
||||
|
||||
export function toKBinXml(topName: string, obj: Serializable, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8') {
|
||||
const xml = `<?xml version="1.0" encoding="${encoding}"?>` + serializeObject(obj, topName);
|
||||
writeFileSync('dump.xml', xml);
|
||||
console.log(xml);
|
||||
return to_bin(xml);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user