sdvx basic data save working

This commit is contained in:
GEEKiDoS
2024-04-20 16:32:27 +08:00
parent c788854684
commit 7e514734d2
11 changed files with 343 additions and 70 deletions
+2
View File
@@ -32,3 +32,5 @@ build/
input.xml
output.json
dump.xml
pdata.json
+1 -1
View File
@@ -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),
}
+31
View File
@@ -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<Serializable> {
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<Serializable> {
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 {
+88 -2
View File
@@ -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<Serializable> {
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<Serializable> {
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<Serializable> {
async sv6_load_m(ctx: Context): Promise<Serializable> {
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,
])
}))
},
}
}
+101 -57
View File
@@ -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<T>(kvalue: KValueG<ValueTypes, T>, value: T) {
function trySet<T>(kvalue: KValueG<ValueTypes, T>, value: T) {
if (value === undefined || value === null)
return;
@@ -22,11 +24,74 @@ function tryAdd(kvalue: KValueG<ValueTypes, number>, 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<Serializable> {
async sv6_save_c(): Promise<Serializable> {
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),
+4 -4
View File
@@ -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 = [];
+4
View File
@@ -114,6 +114,10 @@ async function main(): Promise<void> {
JSON.stringify(ctx.body, (key, value) => {
if (key === 'pdata') {
return '<PDATA>';
} else if (key == 'item') {
return '<ITEM>';
} else if (key == 'param') {
return '<PARAM>';
}
return value;
+40 -4
View File
@@ -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<PlayerPlayData>('sdvx_player_data');
}
get musicDataCol() {
return this.db.collection<PlayerMusicData>('sdvx_music_data');
}
get playLogCol() {
return this.db.collection<PlayerPlayLog>('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<boolean> {
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<SaveData | undefined> {
@@ -95,7 +131,7 @@ export class UserService {
param: {},
present: {},
cloud: {
relation: v.s8(0),
relation: v.s8(1),
},
ea_shop: {
shop_item: {},
+63 -1
View File
@@ -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;
}
+7 -1
View File
@@ -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;
}
+2
View File
@@ -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 = `<?xml version="1.0" encoding="${encoding}"?>` + serializeObject(obj, topName);
writeFileSync('dump.xml', xml);
return to_bin(xml);
}