diff --git a/src/database/index.ts b/src/database/index.ts index 0f96427..12ea464 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -1,72 +1,53 @@ -import { Binary } from "mongodb"; - -export interface PlayerInfo { - _id: string; - rivals: string[]; - pointCount: number; - customize: { - 2: string; - 3: string; - 4: string; - 5: string; - 6: string; - 7: string; - 8: string; - 11: string; - 10: string; - }, - otherCustomize: { - 1: string; - 2: string; - 3: string; - 4: string; - 5: string; - } -} +import { Binary, ObjectId } from "mongodb"; export interface PlayerPlayData { _id: string; + // konami binary xml pdata: Binary; - checksum: string; + // sha256 to ^ + check_sum: string; } export interface PlayerMusicData { player: string; - musicId: string; - clearFlag: { - spb: number; - spn: number; - sph: number; - spa: number; - spl: number; - dpb: number; - dpn: number; - dph: number; - dpa: number; - dpl: number; - }; - score: { - spb: number; - spn: number; - sph: number; - spa: number; - spl: number; - dpb: number; - dpn: number; - dph: number; - dpa: number; - dpl: number; - }; - missCount: { - spb: number; - spn: number; - sph: number; - spa: number; - spl: number; - dpb: number; - dpn: number; - dph: number; - dpa: number; - dpl: number; - }; + music_id: number; + play_style: number; + + // 1 = B, 5 = L, etc + score: number[]; + clear_flag: number[]; + miss_count: number[]; + play_num: number[]; + clear_num: number[]; } + +export interface PlayerPlayLog { + _id: ObjectId, + player: string; + clock: number, + music_id: number, + note_id: number, + score: number, + pgreat_count: number, + great_count: number, + miss_count: number, + clear_flag: number, + stage: number, + groove_gauge: number, + mode_id: number, + mode_sub_id: number, + fail_detail: number, + ghost_check_sum: string, + update_my_best_score: boolean, + is_limit_score: boolean, + rival_infinitas_id: string, + is_compe: boolean, + compe_id: number, + compe_music_index: number + valid_best_option: boolean, + arrange_0: number, + arrange_1: number, + assist: number, + flip: number, + ghost: Buffer, +}; diff --git a/src/main.ts b/src/main.ts index c506320..2f2376d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -109,11 +109,12 @@ async function main(): Promise { } logger.info( - '%s.%s [%s]: request = %s', + '%s %s.%s [%s]: request = %o', + ctx.url, ctx.service.name, ctx.service.method, ctx.token, - JSON.stringify(ctx.body), + ctx.body, ); let method = ctx.service.method; diff --git a/src/middlewares/eacnet.ts b/src/middlewares/eacnet.ts index 3af7a6a..1912a03 100644 --- a/src/middlewares/eacnet.ts +++ b/src/middlewares/eacnet.ts @@ -6,10 +6,15 @@ import { XMLParser } from 'fast-xml-parser'; const parser = new XMLParser({ ignoreAttributes: false, - parseAttributeValue: true, + parseAttributeValue: false, attributeNamePrefix: '$', removeNSPrefix: true, - allowBooleanAttributes: true, + numberParseOptions: { + hex: false, + leadingZeros: false, + eNotation: false, + skipLike: /.*/, + }, }); function parseValue(node: { $__type: string; $__count?: unknown }): any { @@ -25,15 +30,21 @@ function parseValue(node: { $__type: string; $__count?: unknown }): any { 'u32', 's64', 'u64', - 'float', - 'double', ].includes(node.$__type) ) { if (isArray) { - return node['#text'].split(' ').map((v) => new Number(v)); + return node['#text'].split(' ').map((v) => parseInt(v)); } - return new Number(node['#text']); + return parseInt(node['#text']); + } + + if (['float', 'double'].includes(node.$__type)) { + if (isArray) { + return node['#text'].split(' ').map((v) => parseFloat(v)); + } + + return parseFloat(node['#text']); } if (['b', 'bool'].includes(node.$__type)) { @@ -106,7 +117,9 @@ export async function eacnet(ctx: Context, next: Next): Promise { 'base64' ); const decoded = LZ77.decompress(buffer); - const xmlResult = kxmlToObject(parser.parse(to_xml(decoded).data)); + const xml = to_xml(decoded).data; + const parsedXml = parser.parse(xml); + const xmlResult = kxmlToObject(parsedXml); const game = Object.keys(xmlResult)[0]; diff --git a/src/services/p2d/data.json b/src/services/p2d/data.json index 69f0c39..7868cb7 100644 --- a/src/services/p2d/data.json +++ b/src/services/p2d/data.json @@ -88,10 +88,8 @@ 3013, 3018, 4000, - 4001, 4003, 4004, - 4005, 4006, 4007, 4008, @@ -114,7 +112,6 @@ 5009, 5011, 5012, - 5014, 5015, 5016, 5017, @@ -164,7 +161,6 @@ 7026, 7032, 7036, - 7038, 7039, 8001, 8003, @@ -180,7 +176,6 @@ 8020, 8022, 8023, - 8024, 8025, 8026, 8028, @@ -214,14 +209,12 @@ 9030, 9033, 9037, - 9038, 9042, 9043, 9044, 9045, 9046, 9048, - 9051, 9052, 9054, 9057, @@ -265,7 +258,6 @@ 11009, 11010, 11011, - 11012, 11013, 11014, 11015, @@ -273,7 +265,6 @@ 11026, 11027, 11028, - 11032, 11035, 11036, 11037, @@ -285,7 +276,6 @@ 11052, 12000, 12001, - 12002, 12003, 12004, 12005, @@ -293,13 +283,11 @@ 12007, 12008, 12009, - 12010, 12011, 12012, 12013, 12014, 12015, - 12016, 12018, 12019, 12020, @@ -321,7 +309,6 @@ 12050, 12052, 12053, - 12054, 13000, 13001, 13002, @@ -330,7 +317,6 @@ 13006, 13008, 13009, - 13010, 13011, 13012, 13013, @@ -351,7 +337,6 @@ 13035, 13036, 13037, - 13038, 13039, 13040, 13041, @@ -368,12 +353,10 @@ 14000, 14001, 14003, - 14004, 14005, 14006, 14007, 14008, - 14009, 14011, 14012, 14014, @@ -384,7 +367,6 @@ 14019, 14020, 14021, - 14022, 14023, 14024, 14025, @@ -398,32 +380,23 @@ 14034, 14038, 14045, - 14046, 14047, 14048, 14049, 14050, 14051, 14052, - 14053, 14055, 14056, - 15000, 15001, 15002, 15003, - 15004, - 15005, 15006, - 15007, - 15008, 15009, 15010, 15011, 15012, 15013, - 15014, - 15015, 15016, 15017, 15018, @@ -431,10 +404,7 @@ 15020, 15021, 15022, - 15023, 15024, - 15025, - 15026, 15028, 15031, 15032, @@ -445,21 +415,16 @@ 15039, 15040, 15044, - 15045, 15047, 15048, 15049, 15050, 15051, 15052, - 15054, 15056, 15057, 15058, 15059, - 15061, - 16000, - 16001, 16003, 16004, 16005, @@ -467,24 +432,18 @@ 16008, 16009, 16010, - 16011, 16012, 16013, 16014, - 16015, 16016, 16017, - 16018, 16019, 16020, 16021, - 16022, 16023, 16024, - 16025, 16026, 16029, - 16031, 16032, 16035, 16036, @@ -492,12 +451,8 @@ 16039, 16040, 16041, - 16042, 16043, 16044, - 16045, - 16047, - 16050, 16051, 16052, 16053, @@ -529,7 +484,6 @@ 17025, 17026, 17027, - 17028, 17030, 17031, 17032, @@ -552,7 +506,6 @@ 17056, 17058, 17059, - 17060, 17062, 17064, 17065, @@ -566,7 +519,6 @@ 18008, 18009, 18010, - 18011, 18012, 18013, 18014, @@ -579,7 +531,6 @@ 18022, 18023, 18024, - 18025, 18026, 18027, 18028, @@ -671,7 +622,6 @@ 19060, 19061, 19062, - 19063, 19064, 19065, 19067, @@ -681,7 +631,6 @@ 19071, 19072, 19073, - 19074, 19075, 20000, 20001, @@ -702,12 +651,10 @@ 20016, 20017, 20018, - 20019, 20020, 20021, 20022, 20023, - 20024, 20025, 20027, 20028, @@ -721,7 +668,6 @@ 20036, 20037, 20038, - 20039, 20040, 20041, 20042, @@ -747,7 +693,6 @@ 20065, 20066, 20067, - 20068, 20069, 20070, 20071, @@ -772,7 +717,6 @@ 20097, 20098, 20099, - 20100, 20101, 20102, 21001, @@ -786,7 +730,6 @@ 21009, 21010, 21011, - 21012, 21014, 21015, 21016, @@ -801,7 +744,6 @@ 21026, 21027, 21028, - 21029, 21030, 21032, 21033, @@ -815,17 +757,13 @@ 21045, 21046, 21047, - 21048, 21049, - 21050, 21051, - 21052, 21053, 21054, 21056, 21057, 21058, - 21059, 21060, 21061, 21062, @@ -835,10 +773,8 @@ 21066, 21067, 21068, - 21069, 21070, 21071, - 21073, 21074, 21075, 21076, @@ -853,21 +789,17 @@ 21086, 21087, 21088, - 21089, 21090, 22001, 22002, 22003, 22004, 22005, - 22006, 22007, - 22008, 22009, 22010, 22011, 22012, - 22013, 22014, 22015, 22016, @@ -876,13 +808,10 @@ 22019, 22020, 22023, - 22024, 22025, 22026, - 22027, 22028, 22029, - 22031, 22032, 22033, 22034, @@ -928,7 +857,6 @@ 22082, 22087, 22088, - 22089, 22090, 22091, 22092, @@ -968,7 +896,6 @@ 23028, 23029, 23030, - 23031, 23032, 23034, 23035, @@ -989,7 +916,6 @@ 23051, 23052, 23053, - 23054, 23055, 23056, 23057, @@ -1003,7 +929,6 @@ 23067, 23068, 23069, - 23070, 23074, 23075, 23076, @@ -1020,7 +945,6 @@ 23092, 23093, 23094, - 23095, 24000, 24001, 24002, @@ -1032,7 +956,6 @@ 24008, 24009, 24010, - 24011, 24012, 24013, 24014, @@ -1061,7 +984,6 @@ 24038, 24039, 24040, - 24041, 24042, 24043, 24044, @@ -1069,7 +991,6 @@ 24046, 24047, 24048, - 24050, 24051, 24053, 24054, @@ -1364,7 +1285,6 @@ 27112, 27113, 27114, - 27116, 27117, 28002, 28005, @@ -1537,5 +1457,87 @@ 80049, 80050, 80051 + ], + "lmusics": [ + 4001, + 4005, + 5014, + 7038, + 8024, + 9038, + 9051, + 11012, + 11032, + 12002, + 12010, + 12016, + 12054, + 13010, + 13038, + 14004, + 14009, + 14022, + 14046, + 14053, + 15000, + 15004, + 15005, + 15007, + 15008, + 15014, + 15015, + 15023, + 15025, + 15026, + 15045, + 15054, + 15061, + 16000, + 16001, + 16011, + 16015, + 16018, + 16022, + 16025, + 16031, + 16042, + 16045, + 16047, + 16050, + 17028, + 17060, + 18011, + 18025, + 19063, + 19074, + 20019, + 20024, + 20039, + 20068, + 20100, + 21012, + 21029, + 21048, + 21050, + 21052, + 21059, + 21069, + 21073, + 21089, + 22006, + 22008, + 22013, + 22024, + 22027, + 22031, + 22089, + 23031, + 23054, + 23070, + 23095, + 24011, + 24041, + 24050, + 27116 ] } diff --git a/src/services/p2d/index.ts b/src/services/p2d/index.ts index b6aade7..e8cd5fc 100644 --- a/src/services/p2d/index.ts +++ b/src/services/p2d/index.ts @@ -16,7 +16,7 @@ function Combine(...modules: ({ new(): any })[]) { modules.forEach(v => { const descriptors = Object.getOwnPropertyDescriptors(v.prototype); - for(const name in descriptors) { + for (const name in descriptors) { if (name === 'constructor') continue; @@ -40,9 +40,17 @@ export default class extends Combine(Service, User, Game) { } export const ITEM_LIST = data.items; -export const MUSIC_LIST = data.musics.map(id => ({ - music_id: v.s32(id), - kind: v.s32(1), - note_bit: v.s32(0), - music_pack_item_id: v.str('') -})); +export const MUSIC_LIST = [ + ...data.musics.map(id => ({ + music_id: v.s32(id), + kind: v.s32(1), + note_bit: v.s32(0), + music_pack_item_id: v.str('') + })), + ...data.lmusics.map(id => ({ + music_id: v.s32(id), + kind: v.s32(2), + note_bit: v.s32(1), + music_pack_item_id: v.str('') + })), +]; diff --git a/src/services/p2d/user.ts b/src/services/p2d/user.ts index 3e58512..c47f034 100644 --- a/src/services/p2d/user.ts +++ b/src/services/p2d/user.ts @@ -3,7 +3,8 @@ import { eacnet } from "../../decorators/eacnet.js"; import { Context } from "../../types.js"; import { v } from "../../utils/kxml-value.js"; import { ITEM_LIST, MUSIC_LIST } from "./index.js"; -import { PlayerMusicData, PlayerPlayData } from "../../database/index.js"; +import { PlayerMusicData, PlayerPlayData, PlayerPlayLog } from "../../database/index.js"; +import { fromToken } from "../../utils/laochan-id.js"; export class User { db: Db; @@ -12,10 +13,14 @@ export class User { return this.db.collection('player_play_data'); } - get playMusicCol() { + get musicDataCol() { return this.db.collection('player_music_data'); } + get playLogCol() { + return this.db.collection('player_play_log'); + } + @eacnet('p2d') async getCustomizeSetting() { return { @@ -58,7 +63,11 @@ export class User { status: v.s32(0), error: v.s32(0), result: { - point_count: v.s32(114514), + point_count: v.s32(1), + point: [{ + point_id: v.str('P0100000'), + point_num: v.u32(114514), + }] } } } @@ -85,16 +94,79 @@ export class User { } } + @eacnet('p2d') + async reportMusicResult(ctx: Context) { + 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 musicData = (await this.musicDataCol.findOne({ + player: result.player, + music_id: result.music_id, + play_style: play_style + })) ?? { + player: result.player, + music_id: result.music_id, + play_style: play_style, + + score: [0, 0, 0, 0, 0], + clear_flag: [0, 0, 0, 0, 0], + miss_count: [0, 0, 0, 0, 0], + play_num: [0, 0, 0, 0, 0], + clear_num: [0, 0, 0, 0, 0], + }; + + if (result.clear_flag > musicData.clear_flag[diff]) + musicData.clear_flag[diff] = result.clear_flag; + + if (result.miss_count < musicData.miss_count[diff]) + musicData.miss_count[diff] = result.miss_count; + + if (result.score > musicData.score[diff]) + musicData.score[diff] = result.score; + + if (result.clear_flag >= 2) + musicData.clear_num[diff]++; + + musicData.play_num[diff]++; + + await this.musicDataCol.updateOne({ + player: result.player, + music_id: result.music_id, + play_style: play_style + }, { + $set: musicData + }, { + upsert: true + }); + })(); + + await Promise.all([ + this.playLogCol.insertOne(result), + updateTask, + ]) + + return { + status: v.s32(0), + error: v.s32(0), + }; + } + @eacnet('p2d') async savePlayData(ctx: Context) { const uid = ctx.token; - const { pdata, checksum } = ctx.body as { pdata: Buffer, checksum: string }; + const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string }; await this.playDataCol .updateOne({ _id: uid }, { $set: { pdata: new Binary(pdata), - checksum: checksum, + check_sum, } }); @@ -122,6 +194,9 @@ export class User { status: v.s32(0), error: v.s32(0), result: { + size: v.s32(result.pdata.length()), + rest_size: v.s32(0), + check_sum: v.str(result.check_sum), pdata: v.bin(result.pdata.buffer), } } @@ -130,12 +205,10 @@ export class User { @eacnet('p2d') async registPlayer(ctx: Context) { const uid = ctx.token; - const { pdata, checksum } = ctx.body as { pdata: Buffer, checksum: string }; + const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string }; await this.playDataCol - .insertOne({ _id: uid, pdata: new Binary(pdata), checksum }); - - // this.userDataSaveTemp.set(uid, { pdata, checksum }); + .insertOne({ _id: uid, pdata: new Binary(pdata), check_sum }); return { status: v.s32(0), @@ -143,6 +216,53 @@ export class User { } } + @eacnet('p2d') + async checkPlayData(ctx: Context) { + const { check_sum } = ctx.body as { check_sum: string }; + + const data = await this.playDataCol.findOne({ _id: ctx.token }, { + projection: { + check_sum: 1, + } + }); + + const id = fromToken(ctx.token); + + if (!data) { + return { + status: v.s32(0), + error: v.s32(0), + result: { + infinitas_id: v.str(id), + valid: v.bool(false), + is_exist: v.bool(false), + } + }; + } + + if (data.check_sum !== check_sum) { + return { + status: v.s32(0), + error: v.s32(0), + result: { + infinitas_id: v.str(id), + valid: v.bool(false), + is_exist: v.bool(true), + } + }; + } + + return { + status: v.s32(0), + error: v.s32(0), + result: { + infinitas_id: v.str(id), + valid: v.bool(true), + is_exist: v.bool(true), + } + }; + } + @eacnet('p2d') async getRivalInfo() { return { @@ -163,7 +283,7 @@ export class User { error: v.s32(0), result: { status: v.s32(0), - check_sum: v.str('f57959c468c9c59c47a4cead973f59fbec72eaa53a790c5b792222148e69c19d'), + check_sum: v.str('adb004981ab29df534ef1d8b5e6216b447042f798010c2c1f233eebc058e1b67'), music_list: { music_num: v.s32(MUSIC_LIST.length), music: MUSIC_LIST, @@ -185,38 +305,28 @@ export class User { async getMusicData(ctx: Context) { const { play_style } = ctx.body as { play_style: number }; + const musicDatas = await this.musicDataCol.find({ + player: ctx.token, + play_style, + }).toArray(); + return { status: v.s32(0), error: v.s32(0), play_style: v.s32(play_style), result: { - music: MUSIC_LIST.map(music => ({ - music_id: music.music_id, - score: v.s32([9999, 9999, 9999, 9999]), - clear_flag: v.s32([7, 7, 7, 7]), - miss_count: v.s32([0, 0, 0, 0]), - play_num: v.s32([1, 1, 1 ,1]), - clear_num: v.s32([1, 1, 1 ,1]), + music: musicDatas.map(data => ({ + music_id: v.s32(data.music_id), + score: v.s32(data.score), + clear_flag: v.s32(data.clear_flag), + miss_count: v.s32(data.miss_count), + play_num: v.s32(data.play_num), + clear_num: v.s32(data.clear_num), })) }, } } - @eacnet('p2d') - async checkPlayData(ctx: Context) { - const count = await this.playDataCol.countDocuments({ _id: ctx.token }); - - return { - status: v.s32(0), - error: v.s32(0), - result: { - infinitas_id: v.str(ctx.token), - valid: v.bool(count), - is_exist: v.bool(count), - } - }; - } - @eacnet('p2d') async getItemList() { return { diff --git a/src/utils/laochan-id.ts b/src/utils/laochan-id.ts new file mode 100644 index 0000000..9293736 --- /dev/null +++ b/src/utils/laochan-id.ts @@ -0,0 +1,9 @@ +import { createHash } from 'crypto'; + +export function fromToken(token: string) { + const key = createHash('md5') + .update(token, 'utf-8') + .digest('hex'); + + return ('LCHAN' + key).slice(0, 13); +}