From 7e514734d27fe41b6509cfcddc562071e922e677 Mon Sep 17 00:00:00 2001 From: GEEKiDoS Date: Sat, 20 Apr 2024 16:32:27 +0800 Subject: [PATCH] sdvx basic data save working --- .gitignore | 2 + src/controllers/p2d/game.ts | 2 +- src/controllers/sdvx/ac-relay.ts | 31 ++++++ src/controllers/sdvx/music.ts | 90 +++++++++++++++++- src/controllers/sdvx/user.ts | 158 ++++++++++++++++++++----------- src/datas/sdvx.ts | 8 +- src/main.ts | 4 + src/services/sdvx/user.ts | 44 ++++++++- src/types/sdvx/index.ts | 64 ++++++++++++- src/types/sdvx/savedata.ts | 8 +- src/utils/kbinxml.ts | 2 + 11 files changed, 343 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index aa661c8..21d30e5 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ build/ input.xml output.json +dump.xml +pdata.json diff --git a/src/controllers/p2d/game.ts b/src/controllers/p2d/game.ts index 07c8864..8322a9b 100644 --- a/src/controllers/p2d/game.ts +++ b/src/controllers/p2d/game.ts @@ -15,7 +15,7 @@ export class Game { entry_begin_clock: v.u64(0), championship_begin_clock: v.u64(0), championship_end_clock: v.u64(0), - is_possible_play: v.bool(0), + is_possible_play: v.bool(false), repertoire_list: { repertoire_num: v.s32(0), } diff --git a/src/controllers/sdvx/ac-relay.ts b/src/controllers/sdvx/ac-relay.ts index f2b0ff8..3ca5c4b 100644 --- a/src/controllers/sdvx/ac-relay.ts +++ b/src/controllers/sdvx/ac-relay.ts @@ -4,6 +4,37 @@ import { Serializable, v } from "../../utils/kxml-value.js"; import * as data from '../../datas/sdvx.js'; export class AcRelay { + @sdvx() + async sv6_play_e(): Promise { + return { + status: v.s32(0), + error_code: v.s32(0), + xrpc_status_code: v.s32(0), + xrpc_fault_code: v.s32(0), + response: { + game: { + $status: 0, + }, + } + }; + } + + @sdvx() + async sv6_play_s(): Promise { + return { + status: v.s32(0), + error_code: v.s32(0), + xrpc_status_code: v.s32(0), + xrpc_fault_code: v.s32(0), + response: { + game: { + $status: 0, + play_id: v.u32(Math.floor(new Date().valueOf() / 6e4)), + }, + } + }; + } + @sdvx() async sv6_hiscore() { return { diff --git a/src/controllers/sdvx/music.ts b/src/controllers/sdvx/music.ts index fb35fc4..1e1ceec 100644 --- a/src/controllers/sdvx/music.ts +++ b/src/controllers/sdvx/music.ts @@ -1,10 +1,79 @@ import { sdvx } from "../../decorators/eacnet.js"; import { UserService } from "../../services/sdvx/user.js"; import { Serializable, v } from "../../utils/kxml-value.js"; +import { Context } from "../../types.js"; +import { PlayerMusicData, PlayerPlayLog } from "../../types/sdvx/index.js"; export class Music { userService: UserService; + @sdvx() + async sv6_save_m(ctx: Context): Promise { + const { track } = ctx.body as { track: PlayerPlayLog }; + + track.player = ctx.token; + track.play_time = new Date(); + + const mdata = (await this.userService.getPlayerMusicData(ctx.token, track.music_id, track.music_type)) ?? { + player: ctx.token, + music_id: track.music_id, + music_type: track.music_type, + score: 0, exscore: 0, clear_type: 0, score_grade: 0, + max_chain: 0, play_count: 0, + btn_rate: 0, long_rate: 0, vol_rate: 0, + best_play_time: track.play_time, + } as PlayerMusicData; + + if (track.score > mdata.score) { + mdata.score = track.score; + mdata.score_grade = track.score_grade; + + mdata.best_play_time = track.play_time; + mdata.btn_rate = track.btn_rate; + mdata.long_rate = track.long_rate; + mdata.vol_rate = track.vol_rate; + } + + if (track.exscore > mdata.score) { + mdata.exscore = track.exscore; + + // if score is same, use exscore to compare + if (track.score == mdata.score) { + mdata.best_play_time = track.play_time; + mdata.btn_rate = track.btn_rate; + mdata.long_rate = track.long_rate; + mdata.vol_rate = track.vol_rate; + } + } + + if (track.clear_type > mdata.clear_type) { + mdata.clear_type = track.clear_type; + } + + if (track.max_chain > mdata.max_chain) { + mdata.max_chain = track.max_chain; + } + + mdata.play_count++; + + await Promise.all([ + this.userService.insertPlayLog(track), + this.userService.upsertMusicData(mdata), + ]); + + return { + status: v.s32(0), + error_code: v.s32(0), + xrpc_status_code: v.s32(0), + xrpc_fault_code: v.s32(0), + response: { + game: { + $status: 0, + } + } + }; + } + @sdvx() async sv6_load_r(): Promise { return { @@ -20,8 +89,15 @@ export class Music { }; } + /* + * music_id diff + * 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() - async sv6_load_m(): Promise { + async sv6_load_m(ctx: Context): Promise { + const mdata = await this.userService.getPlayerMusicDatas(ctx.token); + return { status: v.s32(0), error_code: v.s32(0), @@ -31,7 +107,17 @@ export class Music { game: { $status: 0, music: { - info: [] + info: mdata.map(d => ({ + date: d.best_play_time, + param: v.u32([ + d.music_id, d.music_type, + d.score, d.exscore, d.clear_type, d.score_grade, d.max_chain, + d.play_count, d.btn_rate, d.long_rate, d.vol_rate, + 0, 0, 0, 0, 0, + Math.floor(d.best_play_time.valueOf() / 1000), + 0, 0, 0, 0, 0, 0, + ]) + })) }, } } diff --git a/src/controllers/sdvx/user.ts b/src/controllers/sdvx/user.ts index 51d71a1..6cf6efe 100644 --- a/src/controllers/sdvx/user.ts +++ b/src/controllers/sdvx/user.ts @@ -4,8 +4,10 @@ import { Context } from "../../types.js"; import { sdvx } 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'; +import { dateToString } from '../../utils/time.js'; -function trySave(kvalue: KValueG, value: T) { +function trySet(kvalue: KValueG, value: T) { if (value === undefined || value === null) return; @@ -22,11 +24,74 @@ function tryAdd(kvalue: KValueG, value: number) { kvalue.__value += value; } +export function tryMergeItem(params: Item[], newParams: object[]) { + if (!params) { + params = []; + } + + if (!newParams) { + return params; + } + + if (!(newParams instanceof Array)) { + newParams = [newParams]; + } + + for (const np of newParams) { + const target = params.find(p => p.id.__value === np['id'] && p.type.__value === np['type']); + + if (target) { + target.type = v.u8(np['type']); + target.param = v.u32(np['param']); + continue; + } + + params.push({ + type: v.u8(np['type']), + id: v.u32(np['id']), + param: v.u32(np['param']), + }) + } + + return params; +} + +export function tryMergeParam(params: Param[], newParams: object[]) { + if (!params) { + params = []; + } + + if (!newParams) { + return params; + } + + if (!(newParams instanceof Array)) { + newParams = [newParams]; + } + + for (const np of newParams) { + const target = params.find(p => p.id.__value === np['id'] && p.type.__value === np['type']); + + if (target) { + target.param = v.s32(np['param']); + continue; + } + + params.push({ + type: v.s32(np['type']), + id: v.s32(np['id']), + param: v.s32(np['param']), + }) + } + + return params; +} + export class User { userService: UserService; @sdvx() - async sv6_play_s(): Promise { + async sv6_save_c(): Promise { return { status: v.s32(0), error_code: v.s32(0), @@ -36,7 +101,7 @@ export class User { game: { $status: 0, play_id: v.u32(Math.floor(new Date().valueOf() / 6e4)), - }, + }, } }; } @@ -55,64 +120,43 @@ export class User { const newData = ctx.body; - trySave(playData.skill_level, newData.skill_level); - trySave(playData.skill_base_id, newData.skill_base_id); - trySave(playData.skill_name_id, newData.skill_name_id); + trySet(playData.appeal_id, newData.appeal_id); + + trySet(playData.skill_level, newData.skill_level); + trySet(playData.skill_base_id, newData.skill_base_id); + trySet(playData.skill_name_id, newData.skill_name_id); tryAdd(playData.blaster_energy, newData.earned_blaster_energy); tryAdd(playData.gamecoin_packet, newData.earned_gamecoin_packet); tryAdd(playData.gamecoin_block, newData.earned_gamecoin_block); - trySave(playData.hispeed, newData.hispeed); - trySave(playData.lanespeed, newData.lanespeed); - trySave(playData.gauge_option, newData.gauge_option); - trySave(playData.ars_option, newData.ars_option); - trySave(playData.notes_option, newData.notes_option); - trySave(playData.early_late_disp, newData.early_late_disp); - trySave(playData.draw_adjust, newData.draw_adjust); - trySave(playData.eff_c_left, newData.eff_c_left); - trySave(playData.eff_c_right, newData.eff_c_right); - trySave(playData.last_music_id, newData.music_id); - trySave(playData.last_music_type, newData.music_type); - trySave(playData.sort_type, newData.sort_type); - trySave(playData.narrow_down, newData.narrow_down); - trySave(playData.headphone, newData.headphone); + tryAdd(playData.play_count, 1); - if (newData.item?.info instanceof Array) { - playData.item.info = [ - ...(playData.item.info ?? []), - ...newData.item.info.map((info: { id: number; type: number; param: number; }) => ({ - id: v.u8(info.id), - type: v.u32(info.type), - param: v.u32(info.param), - })) - ] + trySet(playData.hispeed, newData.hispeed); + trySet(playData.lanespeed, newData.lanespeed); + trySet(playData.gauge_option, newData.gauge_option); + trySet(playData.ars_option, newData.ars_option); + trySet(playData.notes_option, newData.notes_option); + trySet(playData.early_late_disp, newData.early_late_disp); + trySet(playData.draw_adjust, newData.draw_adjust); + trySet(playData.eff_c_left, newData.eff_c_left); + trySet(playData.eff_c_right, newData.eff_c_right); + trySet(playData.last_music_id, newData.music_id); + trySet(playData.last_music_type, newData.music_type); + trySet(playData.sort_type, newData.sort_type); + trySet(playData.narrow_down, newData.narrow_down); + trySet(playData.headphone, newData.headphone); + + playData.item.info = tryMergeItem(playData.item.info, newData.item?.info); + playData.item_infinite.info = tryMergeItem(playData.item_infinite.info, newData.item_infinite?.info); + playData.param.info = tryMergeParam(playData.param.info, newData.param?.info); + + playData.last_date = v.str(dateToString(new Date())); + + if (!await this.userService.savePlayerData(ctx.token, playData)) { + ctx.logger.error('play data save failed'); } - if (newData.item_infinite?.info instanceof Array) { - playData.item_infinite.info = [ - ...(playData.item_infinite.info ?? []), - ...newData.item_infinite.info.map((info: { id: number; type: number; param: number; }) => ({ - id: v.u8(info.id), - type: v.u32(info.type), - param: v.u32(info.param), - })) - ] - } - - if (newData.param?.info instanceof Array) { - playData.param.info = [ - ...(playData.param.info ?? []), - ...newData.param.info.map((info: { id: number; type: number; param: number; }) => ({ - id: v.u8(info.id), - type: v.u32(info.type), - param: v.u32(info.param), - })) - ] - } - - await this.userService.savePlayerData(ctx.token, playData); - return { status: v.s32(0), error_code: v.s32(0), @@ -163,10 +207,10 @@ export class User { // blaster pass playData.ea_shop.blaster_pass_enable = v.bool(true); playData.ea_shop.blaster_pass_limit_date = v.u64(new Date().valueOf() + (30 * 60 * 60 * 1000)); - playData.item.info = [ - ...(playData.item.info ?? []), - ...data.charaItems, - ]; + + // unlock all navigaters + playData.item.info = tryMergeItem(playData.item.info, data.charaItems); + playData.start_date = v.str(dateToString(new Date())); return { status: v.s32(0), diff --git a/src/datas/sdvx.ts b/src/datas/sdvx.ts index a7e47e8..4070b7c 100644 --- a/src/datas/sdvx.ts +++ b/src/datas/sdvx.ts @@ -8643,10 +8643,10 @@ export const extendInfos = [ } ]; -export const charaItems = [...new Array(46).keys()].map((i) => ({ - type: v.u8(11), - id: v.u32(i), - param: v.u32(15), +export const charaItems = [...new Array(300).keys()].map((i) => ({ + type: 11, + id: i, + param: 15, })); export const musicInfos = []; diff --git a/src/main.ts b/src/main.ts index bd9cd68..48313bb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -114,6 +114,10 @@ async function main(): Promise { JSON.stringify(ctx.body, (key, value) => { if (key === 'pdata') { return ''; + } else if (key == 'item') { + return ''; + } else if (key == 'param') { + return ''; } return value; diff --git a/src/services/sdvx/user.ts b/src/services/sdvx/user.ts index 6db5007..5ffaea4 100644 --- a/src/services/sdvx/user.ts +++ b/src/services/sdvx/user.ts @@ -1,12 +1,13 @@ import { Binary, Db, FindOptions } from "mongodb"; import { inject, injectable } from "tsyringe"; -import { PlayerPlayData } from "../../types/sdvx/index.js"; +import { PlayerMusicData, PlayerPlayData, PlayerPlayLog } from "../../types/sdvx/index.js"; import { tokenToCode, tokenToSdvxId } from "../../utils/laochan-id.js"; import { v } from "../../utils/kxml-value.js"; import { dateToString } from "../../utils/time.js"; import { brotliCompress, brotliDecompress } from "zlib"; import { promisify } from "util"; import { SaveData } from "../../types/sdvx/savedata.js"; +import { writeFileSync } from "fs"; @injectable() export class UserService { @@ -19,11 +20,43 @@ export class UserService { return this.db.collection('sdvx_player_data'); } + get musicDataCol() { + return this.db.collection('sdvx_music_data'); + } + + get playLogCol() { + return this.db.collection('sdvx_play_log'); + } + + getPlayerMusicData(token: string, music_id: number, music_type: number) { + return this.musicDataCol.findOne({ player: token, music_id, music_type }); + } + + getPlayerMusicDatas(token: string) { + return this.musicDataCol.find({ player: token }).toArray(); + } + + async upsertMusicData(musicData: PlayerMusicData) { + return await this.musicDataCol.updateOne({ + player: musicData.player, + music_id: musicData.music_id, + music_type: musicData.music_type, + }, { + $set: musicData + }, { + upsert: true + }); + }; + + async insertPlayLog(playlog: PlayerPlayLog) { + return this.playLogCol.insertOne(playlog); + } + async hasPlayerData(token: string) { return !!await this.playDataCol.countDocuments({ _id: token }); } - async savePlayerData(token: string, data: SaveData, name?: string) { + async savePlayerData(token: string, data: SaveData, name?: string): Promise { const bin = await promisify(brotliCompress)(JSON.stringify(data)); const $set = { save_data: new Binary(bin), @@ -33,7 +66,10 @@ export class UserService { $set['name'] = name; } - return this.playDataCol.updateOne({ _id: token }, { $set }, { upsert: true }) + writeFileSync('pdata.json', await promisify(brotliDecompress)(bin)); + + const result = await this.playDataCol.updateOne({ _id: token }, { $set }, { upsert: true }) + return !!(result.modifiedCount ? result.modifiedCount : result.upsertedCount); } async getPlayerData(token: string, options?: FindOptions): Promise { @@ -95,7 +131,7 @@ export class UserService { param: {}, present: {}, cloud: { - relation: v.s8(0), + relation: v.s8(1), }, ea_shop: { shop_item: {}, diff --git a/src/types/sdvx/index.ts b/src/types/sdvx/index.ts index 1bfd086..f2d49bd 100644 --- a/src/types/sdvx/index.ts +++ b/src/types/sdvx/index.ts @@ -1,7 +1,69 @@ -import { Binary } from "mongodb"; +import { Binary, ObjectId } from "mongodb"; export interface PlayerPlayData { _id: string; name: string; save_data: Binary; } + +export interface PlayerMusicData { + _id: ObjectId; + player: string; + + music_id: number; + music_type: number; + + score: number; + exscore: number; + clear_type: number; + score_grade: number; + max_chain: number; + play_count: number; + btn_rate: number; + long_rate: number; + vol_rate: number; + + best_play_time: Date; +} + +export interface PlayerPlayLog { + _id: ObjectId; + player: string; + play_time: Date; + play_id: number; + track_no: number; + music_id: number; + music_type: number; + score: number; + exscore: number; + clear_type: number; + score_grade: number; + max_chain: number; + just: number; + critical: number; + near: number; + error: number; + effective_rate: number; + btn_rate: number; + long_rate: number; + vol_rate: number; + mode: number; + gauge_type: number; + notes_option: number; + online_num: number; + local_num: number; + challenge_type: number; + retry_cnt: number; + pad_type: number; + judge: number[]; + drop_frame: number; + drop_frame_max: number; + drop_count: number; + etc: string; + mix_id: number; + mix_like: boolean; + matching: { + score: number; + }[]; + is_blaster: boolean; +} diff --git a/src/types/sdvx/savedata.ts b/src/types/sdvx/savedata.ts index 2177b70..ec871cf 100644 --- a/src/types/sdvx/savedata.ts +++ b/src/types/sdvx/savedata.ts @@ -52,7 +52,7 @@ export interface SaveData { skill: { course?: Course[] }; - param: { info?: Item[] }; + param: { info?: Param[] }; present: { info?: Item[] }; cloud: { relation: KS8 }; @@ -84,3 +84,9 @@ export interface Item { id: KU32; param: KU32; } + +export interface Param { + type: KS32; + id: KS32; + param: KS32; +} diff --git a/src/utils/kbinxml.ts b/src/utils/kbinxml.ts index f3e1c55..7953cb0 100644 --- a/src/utils/kbinxml.ts +++ b/src/utils/kbinxml.ts @@ -2,6 +2,7 @@ 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, @@ -227,5 +228,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 = `` + serializeObject(obj, topName); + writeFileSync('dump.xml', xml); return to_bin(xml); }