diff --git a/src/controllers/gitadora/user.ts b/src/controllers/gitadora/user.ts index 6be6f62..f7b7cae 100644 --- a/src/controllers/gitadora/user.ts +++ b/src/controllers/gitadora/user.ts @@ -1,3 +1,4 @@ +import { generic } from "../../decorators/eacnet.js"; import { UserService } from "../../services/gitadora/user.js"; import { Context } from "../../types.js"; import { Player } from "../../types/gitadora/data.js"; @@ -7,7 +8,11 @@ import { tokenToHash } from "../../utils/laochan-id.js"; export class User { userService: UserService; - knst_cardutil_check(): Serializable { + @generic() + async knst_cardutil_check(ctx: Context): Promise { + const player = await this.userService.findPlayer(ctx.token); + const type = this.getGameType(ctx); + return { status: v.s32(0), error_code: v.s32(0), @@ -19,12 +24,43 @@ export class User { player: { $no: 1, $state: 2, + refid: v.str(tokenToHash(ctx.token)), + did: v.s32(1), + cardnumber: v.str(''), + usercode: v.str(tokenToHash(ctx.token)), + player_type: v.s8(0), + name: v.str(''), + title: v.str(''), + charaid: v.s32(0), + skilldata: { + skill: v.s32(player.gameSave[type].skill), + all_skill: v.s32(player.gameSave.gf.skill + player.gameSave.dm.skill), + old_skill: v.s32(-1), + old_all_skill: v.s32(-1), + }, } }, }, }; } + @generic() + knst_cardutil_regist(): Serializable { + return { + status: v.s32(0), + error_code: v.s32(0), + xrpc_status_code: v.s32(0), + xrpc_fault_code: v.s32(0), + response: { + knst_cardutil: { + $status: 0, + is_succession: v.bool(false), + did: v.s32(1), + }, + }, + }; + } + getGameType(ctx: Context): 'gf' | 'dm' { if (typeof ctx.body.gametype !== 'string') { throw new Error('invaild request'); @@ -39,6 +75,7 @@ export class User { throw new Error('unknown game type'); } + @generic() async knst_gametop_get(ctx: Context): Promise { const type = this.getGameType(ctx); const [data, musics] = await Promise.all([ @@ -58,22 +95,22 @@ export class User { $status: 0, player: { now_date: v.u64(Date.now()), - rq_key: v.str('dummy_rq_key'), + rq_key: v.str('dummy-req-key'), player_info: { refid: v.str(tokenToHash(ctx.token)), did: v.s32(1), cardnumber: v.str(''), - usercode: v.str(tokenToHash(ctx.token)), + usercode: v.str(tokenToHash(ctx.token).slice(0, 10)), player_type: v.s8(0), name: v.str(''), title: v.str(''), charaid: v.s32(0), }, playinfo: Object.fromEntries( - Object.entries(save).map(kv => [kv[0], v.s32(kv[1])]) + Object.entries(save.playInfo).map(kv => [kv[0], v.s32(kv[1])]) ), reward: { - status: v.s32(save.rewardStatus), + status: v.u32(save.rewardStatus), }, customdata: { playstyle: v.s32(save.customData.playStyle), @@ -95,7 +132,13 @@ export class User { total_nr: v.s32(1), } }, - secretmusic: {}, + secretmusic: { + music: data.secretMusics.map(m => ({ + musicid: v.s32(m), + seq: v.u16(255), + kind: v.s32(1), + })), + }, favoritemusic: { list_1: v.s32(save.favoriteMusic.list1), list_2: v.s32(save.favoriteMusic.list2), @@ -110,7 +153,7 @@ export class User { Object.entries(data.gameSave.gf.maxRecord).map(kv => [kv[0], v.s32(kv[1])]) ), diff_record: Object.fromEntries( - Object.entries(data.gameSave.gf.maxRecord).map(kv => [kv[0], v.s32(kv[1])]) + Object.entries(data.gameSave.gf.diffRecord).map(kv => [kv[0], v.s32(kv[1])]) ), }, dm: { @@ -118,7 +161,7 @@ export class User { Object.entries(data.gameSave.dm.maxRecord).map(kv => [kv[0], v.s32(kv[1])]) ), diff_record: Object.fromEntries( - Object.entries(data.gameSave.dm.maxRecord).map(kv => [kv[0], v.s32(kv[1])]) + Object.entries(data.gameSave.dm.diffRecord).map(kv => [kv[0], v.s32(kv[1])]) ), }, }, @@ -140,13 +183,13 @@ export class User { for (const seq in m.seqs) { const rec = m.seqs[seq]; - if (rec.score > bestScore) { - bestScore = rec.score; + if (rec.skill > bestScore) { + bestScore = rec.skill; bestSeq = +seq; } mdata[+seq] = rec.clear ? rec.perc : -1; - mdata[8 + seq] = rec.rank; + mdata[8 + (+seq)] = rec.rank; meter[+seq] = rec.meter; meterProg[+seq] = rec.meterProgress; } @@ -168,29 +211,38 @@ export class User { }; } + @generic() async knst_gameend_regist(ctx: Context): Promise { const type = this.getGameType(ctx); const player = ctx.body.player as Player; + const stages = ctx.body.player.stage ? ( + ctx.body.player.stage instanceof Array ? + ctx.body.player.stage : [ctx.body.player.stage] + ) : []; + await Promise.all([ - this.userService.updateMusicData(ctx.token, ctx.body.player.stage, type), + stages.map(m => this.userService.updateMusicData(ctx.token, m, type)), this.userService.upsertPlayer(ctx.token, { - [type]: { - playInfo: player.playinfo, - rewardStatus: player.reward.status, - customData: { - playStyle: player.customdata.playstyle, - custom: player.customdata.custom, + secretMusics: player.secretmusic.music.map(v => v.musicid), + gameSave: { + [type]: { + playInfo: player.playinfo, + rewardStatus: player.reward.status, + customData: { + playStyle: player.customdata.playstyle, + custom: player.customdata.custom, + }, + skill: player.skilldata.skill, + favoriteMusic: { + list1: player.favoritemusic.music_list_1, + list2: player.favoritemusic.music_list_2, + list3: player.favoritemusic.music_list_3, + }, + information: player.information.info, + maxRecord: player.record.max, + diffRecord: player.record.diff, }, - skill: player.skilldata.skill, - favoriteMusic: { - list1: player.favoritemusic.music_list_1, - list2: player.favoritemusic.music_list_2, - list3: player.favoritemusic.music_list_3, - }, - information: player.information.info, - maxRecord: player.record.max, - diffRecord: player.record.diff, }, }) ]); diff --git a/src/decorators/eacnet.ts b/src/decorators/eacnet.ts index 0a4dc87..d383f2e 100644 --- a/src/decorators/eacnet.ts +++ b/src/decorators/eacnet.ts @@ -1,5 +1,6 @@ import { LZ77 } from '../utils/lz77.js'; import { toKBinXml } from '../utils/kbinxml.js'; +import config from '../utils/config.js'; export function eacnet(protocol_name: string, topName: string, dataOffset: number) { return (encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8') => { @@ -8,7 +9,7 @@ export function eacnet(protocol_name: string, topName: string, dataOffset: numbe descriptor.value = async function (...args: any[]) { const obj = await orig.apply(this, args); - const kxml = toKBinXml(topName, obj, encoding); + const kxml = toKBinXml(topName, obj, encoding, config.isDev); const compressed = LZ77.compress(kxml.data); const result = Buffer.allocUnsafe(compressed.length + dataOffset); diff --git a/src/main.ts b/src/main.ts index 90ee6fc..7f91454 100644 --- a/src/main.ts +++ b/src/main.ts @@ -118,6 +118,8 @@ async function main(): Promise { return ''; } else if (key == 'param') { return ''; + } else if (typeof value === 'bigint') { + return value.toString(); } return value; diff --git a/src/services/gitadora/user.ts b/src/services/gitadora/user.ts index 65ab7f1..8a6c021 100644 --- a/src/services/gitadora/user.ts +++ b/src/services/gitadora/user.ts @@ -124,7 +124,8 @@ export class UserService { async findPlayer(token: string): Promise { const player = await this.playerDataCol.findOne({ _id: token }) ?? { _id: token, - gameSave: {} + gameSave: {}, + secretMusics: [], } as PlayerData; if (!player.gameSave.gf) { @@ -159,6 +160,7 @@ export class UserService { }, { useBigInt64: true }) ?? { player: token, musicId: stage.musicid, + seqs: {}, } as MusicData)(), this.playRecordCol(type).insertOne({ player: token, @@ -167,18 +169,6 @@ export class UserService { ]); if (stage.seq in data.seqs) { - data.seqs[stage.seq] = { - fullcombo: stage.fullcombo, - excellent: stage.excellent, - clear: stage.clear, - score: stage.score, - perc: stage.perc, - rank: stage.rank, - meter: stage.meter, - meterProgress: stage.meter_prog, - bestRecord: result.insertedId, - } - } else { const seqData = data.seqs[stage.seq]; seqData.fullcombo = seqData.fullcombo || stage.fullcombo; @@ -191,16 +181,28 @@ export class UserService { } seqData.rank = Math.max(seqData.rank, stage.rank); - seqData.score = Math.max(seqData.score, stage.score); + seqData.skill = Math.max(seqData.skill, stage.score); if (stage.meter_prog > seqData.meterProgress) { seqData.meterProgress = stage.meter_prog; seqData.meter = stage.meter; } + } else { + data.seqs[stage.seq] = { + fullcombo: stage.fullcombo, + excellent: stage.excellent, + clear: stage.clear, + skill: stage.skill, + perc: stage.perc, + rank: stage.rank, + meter: stage.meter, + meterProgress: stage.meter_prog, + bestRecord: result.insertedId, + } } await this.musicDataCol(type).updateOne({ player: token, musicId: stage.musicid }, { $set: data, - }, { useBigInt64: true }); + }, { useBigInt64: true, upsert: true }); } } diff --git a/src/types/gitadora/data.ts b/src/types/gitadora/data.ts index bdd0cff..b408acf 100644 --- a/src/types/gitadora/data.ts +++ b/src/types/gitadora/data.ts @@ -139,7 +139,10 @@ export interface Player { reward: Reward; skilldata: Skilldata; groove: Groove; - record: Rec; + record: { + max: Record; + diff: Record; + }; battledata: Battledata; sessiondata: Sessiondata; favoritemusic: Favoritemusic; @@ -253,63 +256,6 @@ export interface PhraseComboChallenge { point: number; } -export interface Rec { - max: Max; - diff: Diff; -} - -export interface Diff { - diff_100_nr: number; - diff_150_nr: number; - diff_200_nr: number; - diff_250_nr: number; - diff_300_nr: number; - diff_350_nr: number; - diff_400_nr: number; - diff_450_nr: number; - diff_500_nr: number; - diff_550_nr: number; - diff_600_nr: number; - diff_650_nr: number; - diff_700_nr: number; - diff_750_nr: number; - diff_800_nr: number; - diff_850_nr: number; - diff_900_nr: number; - diff_950_nr: number; - diff_100_clear: number[]; - diff_150_clear: number[]; - diff_200_clear: number[]; - diff_250_clear: number[]; - diff_300_clear: number[]; - diff_350_clear: number[]; - diff_400_clear: number[]; - diff_450_clear: number[]; - diff_500_clear: number[]; - diff_550_clear: number[]; - diff_600_clear: number[]; - diff_650_clear: number[]; - diff_700_clear: number[]; - diff_750_clear: number[]; - diff_800_clear: number[]; - diff_850_clear: number[]; - diff_900_clear: number[]; - diff_950_clear: number[]; -} - -export interface Max { - skill: number; - all_skill: number; - clear_diff: number; - full_diff: number; - exce_diff: number; - clear_music_num: number; - full_music_num: number; - exce_music_num: number; - clear_seq_num: number; - classic_all_skill: number; -} - export interface Reward { status: number[]; } diff --git a/src/types/gitadora/db.ts b/src/types/gitadora/db.ts index d15ae55..5f8a067 100644 --- a/src/types/gitadora/db.ts +++ b/src/types/gitadora/db.ts @@ -22,7 +22,10 @@ export interface GameSave { export interface PlayerData { _id: string; - gameSave: Record<'gf' | 'dm', GameSave>; + gameSave: { + [type: string]: GameSave + }; + secretMusics: number[]; } export interface MusicData { @@ -33,7 +36,7 @@ export interface MusicData { clear: boolean; fullcombo: boolean; excellent: boolean; - score: number; + skill: number; perc: number; rank: number; meter: bigint; diff --git a/src/utils/kbinxml.ts b/src/utils/kbinxml.ts index 6f1464a..8b3f39b 100644 --- a/src/utils/kbinxml.ts +++ b/src/utils/kbinxml.ts @@ -2,6 +2,7 @@ import { to_bin, to_xml } from "@geekidos/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, @@ -215,7 +216,7 @@ function serializeObject(obj: Serializable, name: string, linePrefix: string = ' } // the kbinxml library had issue when serializing non-ordered stuff - attrs.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); + // attrs.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); for (const attr of attrs) { if (attr[1] === undefined) @@ -224,9 +225,9 @@ function serializeObject(obj: Serializable, name: string, linePrefix: string = ' output += ` ${attr[0]}="${_.escape(attr[1])}"`; } - output += '>'; - if (value) { + output += '>'; + if (value[1] === undefined) { return ''; } @@ -243,11 +244,11 @@ function serializeObject(obj: Serializable, name: string, linePrefix: string = ' .join(' '); } else { const elements = entries - .filter(v => !v[0].startsWith('$')) - .sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); + .filter(v => !v[0].startsWith('$')); + // .sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); if (elements.length) { - output += '\n'; + output += '>\n'; for (const element of elements) { if (element[1] instanceof Array) { @@ -261,6 +262,8 @@ function serializeObject(obj: Serializable, name: string, linePrefix: string = ' } output += linePrefix; + } else { + return output + ' />\n'; } } @@ -268,11 +271,16 @@ function serializeObject(obj: Serializable, name: string, linePrefix: string = ' } export function toKBinXml(topName: string, obj: Serializable, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8', dumpXml: boolean = false) { - const xml = `` + serializeObject(obj, topName); + const xml = `\n` + serializeObject(obj, topName); if (dumpXml) { console.log(xml); } - return to_bin(xml); + const bin = to_bin(xml); + if (dumpXml) { + writeFileSync('dump.bin', Buffer.from(bin.data)); + } + + return bin; }