import { AchievementsTuneStreet } from "../models/achievements"; import { Params, Profile } from "../models/common"; import * as utils from "./utils"; /** * Handler for getting the current state of the game (phase, good prices, etc...) */ export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const result = K.ATTR({ game_phase: "2", ir_phase: "14", // None at 0/7/14 event_phase: "15", // Town Mode (max value 17 / Town Max = 15) netvs_phase: "18", // Max 18 card_phase: "3", gfdm_phase: "2", jubeat_phase: "2", local_matching_enable: "0", matching_sec: "120", boss_diff: "100,100,100,100,100,100,100,100,100,100", boss_battle_point: "1", }); return send.object(result); }; /** * Handler for new profile */ export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; if (!refid) return send.deny(); const name = $(data).attr()['name']; send.object(await getProfile(refid, name)); }; /** * Handler for existing profile */ export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; if (!refid) return send.deny(); send.object(await getProfile(refid)); }; /** * Get/create the profile and scores based on refid * @param refid the profile refid * @param name if defined, create/update the profile with the given name */ export const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); //const rivals = await utils.readRivals(refid); if (name && name.length > 0) { profile.name = name; await utils.writeProfile(refid, profile); } const params = await utils.readParams(refid, version); let binary_profile = Array(2892).fill(0); let name_binary = U.EncodeString(profile.name, 'shift_jis'); for (let i = 0; i < name_binary.length || i < 12; i++) { binary_profile[i] = name_binary[i]; } binary_profile[13] = { 0: 0, 1: 0, 2: 1, 3: 1, 4: 5, 5: 2, 6: 4, 7: 4, 8: 4, 9: 4, 10: 1, 11: 5, 12: 4, 13: 6, 14: 6, 15: 6, }[_.get(params, `params.play_mode`, 0)] & 0xFF; // mode_num binary_profile[14] = { 0: 1, 1: 0, 2: 1, 3: 0, 4: -1, 5: -1, 6: -1, 7: -1, 8: -1, 9: -1, 10: 0, 11: -1, 12: -1, 13: -1, 14: -1, 15: -1, }[_.get(params, `params.play_mode`, 0)] & 0xFF; //botton_num binary_profile[15] = _.get(params, `params.last_play_flag`, 0) & 0xFF; binary_profile[16] = _.get(params, `params.medal_and_friend`, 0) & 0xFF; let friendIdBinary = U.EncodeString(profile.friendId, 'shift_jis'); for (let i = 0; i < friendIdBinary.length || i < 12; i++) { binary_profile[17 + i] = friendIdBinary[i]; } // binary_profile[30] = customize_available // binary_profile[31] = customize_level_min // binary_profile[32] = customize_level_max // binary_profile[33] = customize_medal_min // binary_profile[34] = customize_medal_max // binary_profile[35] = customize_friend_no // binary_profile[36] = customize_friend_winlose // binary_profile[37] = read_news_no_max binary_profile[38] = _.get(params, `params.skin_tex_note`, 0) & 0xFF; binary_profile[39] = _.get(params, `params.skin_tex_cmn`, 0) & 0xFF; binary_profile[40] = _.get(params, `params.skin_sd_bgm`, 0) & 0xFF; binary_profile[41] = _.get(params, `params.skin_sd_se`, 0) & 0xFF; binary_profile[44] = _.get(params, `params.option`, 0) & 0xFF; binary_profile[45] = (_.get(params, `params.option`, 0) >> 8) & 0xFF; binary_profile[46] = (_.get(params, `params.option`, 0) >> 16) & 0xFF; binary_profile[47] = (_.get(params, `params.option`, 0) >> 24) & 0xFF; // binary_profile[48] = jubeatcollabo // binary_profile[52] = color_3p_flag binary_profile[60] = _.get(params, `params.chara`, 0) & 0xFF; binary_profile[61] = (_.get(params, `params.chara`, 0) >> 8) & 0xFF; binary_profile[62] = _.get(params, `params.music`, 0) & 0xFF; binary_profile[63] = (_.get(params, `params.music`, 0) >> 8) & 0xFF; binary_profile[64] = _.get(params, `params.sheet`, 0) & 0xFF; binary_profile[65] = _.get(params, `params.category`, 0) & 0xFF; // binary_profile[66] = norma_point // binary_profile[67] = rivals.rivals.length; // TODO: implements rivals // binary_profile[2208 -> 2351] = ir // binary_profile[2352 -> 2892] = netvs // Get Score let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 7) * 17) + 7) / 8)).fill(0); const scoresData = await utils.readScores(refid, version); const playCount = new Map(); for (const key in scoresData.scores) { const keyData = key.split(':'); const score = scoresData.scores[key]; const music = parseInt(keyData[0], 10); const sheet = parseInt(keyData[1], 10); if (music > GAME_MAX_MUSIC_ID) { continue; } if (sheet == 0) { continue; } //flag const flags = __format_flags_for_score(sheet, score.clear_type); const flags_index = music * 2; binary_profile[108 + flags_index] = binary_profile[108 + flags_index] | (flags & 0xFF); binary_profile[109 + flags_index] = binary_profile[109 + flags_index] | ((flags >> 8) & 0xFF); if (sheet == 7 || sheet == 8) { continue; } const hiscore_index = (music * 7) + { 9: 0, 4: 1, 5: 2, 6: 3, 1: 4, 2: 5, 3: 6, }[sheet]; const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); const hiscore_bit_pos = ((hiscore_index * 17) % 8); const hiscore_value = score.score << hiscore_bit_pos; hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); playCount.set(music, (playCount.get(music) || 0) + score.cnt); } let myBest = Array(20).fill(-1); const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); let i = 0; for (const value of sortedPlayCount.keys()) { if (i >= 20) { break; } myBest[i] = value; i++; } let profile_pos = 68 for (const musicid of myBest) { binary_profile[profile_pos] = musicid & 0xFF binary_profile[profile_pos + 1] = (musicid >> 8) & 0xFF profile_pos = profile_pos + 2 } const achievements = await utils.readAchievements(refid, version, defaultAchievements); // Town mode let town = Array(272).fill(0); const tp = _.get(params, `params.tp`, 100); town[0] = tp & 0xFF town[1] = (tp >> 8) & 0xFF town[2] = (tp >> 16) & 0xFF town[3] = (tp >> 24) & 0xFF for (let i = 0; i < 3; i++) { const value = achievements.bought_flg[i] || 0; town[(i * 4) + 4] = value & 0xFF town[(i * 4) + 4 + 1] = (value >> 8) & 0xFF town[(i * 4) + 4 + 2] = (value >> 16) & 0xFF town[(i * 4) + 4 + 3] = (value >> 24) & 0xFF } for (let i = 0; i < 8; i++) { const value = achievements.build_flg[i] || 0; town[(i * 4) + 16] = value & 0xFF town[(i * 4) + 16 + 1] = (value >> 8) & 0xFF town[(i * 4) + 16 + 2] = (value >> 16) & 0xFF town[(i * 4) + 16 + 3] = (value >> 24) & 0xFF } for (let i = 0; i < 19; i++) { const value = achievements.chara_flg[i] || 0; town[(i * 4) + 48] = value & 0xFF town[(i * 4) + 48 + 1] = (value >> 8) & 0xFF town[(i * 4) + 48 + 2] = (value >> 16) & 0xFF town[(i * 4) + 48 + 3] = (value >> 24) & 0xFF } for (let i = 0; i < 4; i++) { const value = achievements.event_flg[i] || 0; town[(i * 4) + 124] = value & 0xFF town[(i * 4) + 124 + 1] = (value >> 8) & 0xFF town[(i * 4) + 124 + 2] = (value >> 16) & 0xFF town[(i * 4) + 124 + 3] = (value >> 24) & 0xFF } town[140] = achievements.play_type & 0xFF for (let applyIdx = 0; applyIdx < achievements.apply.length; applyIdx++) { let apply_name = U.EncodeString(achievements.apply[applyIdx], 'shift_jis'); for (let i = 0; i < apply_name.length || i < 12; i++) { const index = applyIdx * 13 + i + 141; town[index] = apply_name[i]; } } const player = { b: K.ITEM('bin', Buffer.from(binary_profile)), hiscore: K.ITEM('bin', Buffer.from(hiscore_array)), town: K.ITEM('bin', Buffer.from(town)), } return player; } /** * Handler for getting town mode maps data */ export const map = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { let refid = $(data).attr()['ref_id']; if (!refid) return send.deny(); const friendId = $(data).attr()['gpm_id']; let isRandom = parseInt($(data).attr()['is_random'], 10); let friends = await DB.Find(null, { collection: 'achievements', version: 'v19' }); if (friendId != undefined && friendId != null) { // Check if friend exists let friend = await DB.FindOne(null, { collection: 'profile', friendId }); if(friend != undefined && friend != null) { // Check if friend has tunestreet town mode data const achievements = await DB.Find(null, { collection: 'achievements', version: 'v19' }); if(achievements != undefined && achievements != null) { refid = friend.__refid; isRandom == 0; } } } if (isRandom == 1) { // Pick a random refid from players having tunestreet data refid = friends[Math.floor(Math.random() * friends.length)].__refid } const player = { residence: K.ATTR({ id: "0" }), map: [] } // player map player.map.push(K.ITEM("bin", Buffer.from(await formatMap(refid)), { residence: "0" })); // Friends map (max 9) const usedFriends = [refid]; let i = 1; while(i < 10 && i < friends.length) { const friendRefid = friends[Math.floor(Math.random() * friends.length)].__refid; if(usedFriends.indexOf(friendRefid) == -1) { usedFriends.push(friendRefid); player.map.push(K.ITEM("bin", Buffer.from(await formatMap(friendRefid)), { residence: `${i}` })); i++; } } send.object(player); } const formatMap = async (refid: string) => { const profile = await utils.readProfile(refid); const params = await utils.readParams(refid, version); const achievements = await utils.readAchievements(refid, version, defaultAchievements); let map = Array(180); map[0] = _.get(params, `params.chara`, 0) & 0xFF map[1] = (_.get(params, `params.chara`, 0) >> 8) & 0xFF // Building data for (let i = 0; i <= 7; i++) { if (achievements.building[i]) { let idx = 0; for (let j = 0; j <= 7; j++) { map[(8 * i) + 42 + idx] = achievements.building[i][j] || 0; idx++; } } } // Friend ID for (let i = 0; i < profile.friendId.length || i < 12; i++) { map[106 + i] = profile.friendId[i]; } // Player Name let name_binary = U.EncodeString(profile.name, 'shift_jis'); for (let i = 0; i < name_binary.length || i < 12; i++) { map[i + 119] = name_binary[i]; } // map[132] = message // Base state for (let i = 0; i < 4; i++) { map[i + 173] = achievements.base[i]; } // Player most played songs const scoresData = await utils.readScores(refid, version); const playCount = new Map(); for (const key in scoresData.scores) { const keyData = key.split(':'); const score = scoresData.scores[key]; const music = parseInt(keyData[0], 10); const sheet = parseInt(keyData[1], 10); if (music > GAME_MAX_MUSIC_ID || sheet == 0 || sheet == 7 || sheet == 8) { continue; } playCount.set(music, (playCount.get(music) || 0) + score.cnt); } let myBest = Array(20).fill(-1); const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); let i = 0; for (const value of sortedPlayCount.keys()) { if (i >= 20) { break; } myBest[i] = value; i++; } let mybest_pos = 2 for (let i = 0; i < myBest.length; i++) { map[mybest_pos] = myBest[i] & 0xFF map[mybest_pos + 1] = (myBest[i] >> 8) & 0xFF mybest_pos = mybest_pos + 2 } return map; } const __format_flags_for_score = (sheet: number, clear_type: number) => { const playedflag = { 9: 0x2000, 4: 0x0800, 5: 0x1000, 6: 0x4000, 1: 0x0800, 2: 0x1000, 3: 0x4000, 7: 0, 8: 0, }[sheet]; const shift = { 9: 4, 4: 0, 5: 2, 6: 6, 1: 0, 2: 2, 3: 6, 7: 9, 8: 8, }[sheet]; const flags = { 100: 0, 200: 0, 300: 0, 400: 1, 500: 1, 600: 1, 700: 1, 800: 2, 900: 2, 1000: 2, 1100: 3, }[clear_type] || 0; return (flags << shift) | playedflag } /** * Handler for saving profile and scores */ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; if (!refid) return send.deny(); const params = await utils.readParams(refid, version); params.params['tp'] = parseInt($(data).attr()['tp']); params.params['play_mode'] = parseInt($(data).attr()['play_mode']); params.params['chara'] = parseInt($(data).attr()['chara_num']); params.params['option'] = parseInt($(data).attr()['option']); params.params['last_play_flag'] = parseInt($(data).attr()['last_play_flag']); params.params['medal_and_friend'] = parseInt($(data).attr()['medal_and_friend']); params.params['music'] = parseInt($(data).attr()['music_num']); params.params['sheet'] = parseInt($(data).attr()['sheet_num']); params.params['category'] = parseInt($(data).attr()['category_num']); params.params['skin_sd_bgm'] = parseInt($(data).attr()['skin_sd_bgm']); params.params['skin_sd_se'] = parseInt($(data).attr()['skin_sd_se']); params.params['skin_tex_cmn'] = parseInt($(data).attr()['skin_tex_cmn']); params.params['skin_tex_note'] = parseInt($(data).attr()['skin_tex_note']); await utils.writeParams(refid, version, params); const scoresData = await utils.readScores(refid, version, true); for (const scoreData of $(data).elements('music')) { const music = parseInt(scoreData.attr()['music_num']); let sheet = parseInt(scoreData.attr()['sheet_num']); const score = parseInt(scoreData.attr()['score']); const data = parseInt(scoreData.attr()['data']); if (sheet == 4 || sheet == 5) { continue; } if (params.params['play_mode'] == 4) { if ([2, 6, 7].indexOf(sheet) != -1) { continue; } sheet = { 0: 1, 1: 2, 3: 3, }[sheet] } else { sheet = { 0: 4, 1: 5, 2: 9, 3: 6, 6: 7, 7: 8, }[sheet] } const shift = { 9: 4, 4: 0, 5: 2, 6: 6, 1: 0, 2: 2, 3: 6, 7: 9, 8: 8, }[sheet] let mask = 0x3; if (sheet == 7 || sheet == 8) { mask = 0x1; } const flags = (data >> shift) & mask; const clear_type = { 0: 100, 1: 500, 2: 800, 3: 1100, }[flags] const key = `${music}:${sheet}`; if (!scoresData.scores[key]) { scoresData.scores[key] = { score, cnt: 1, clear_type }; } else { scoresData.scores[key] = { score: Math.max(score, scoresData.scores[key].score), cnt: scoresData.scores[key].cnt + 1, clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0) }; } } utils.writeScores(refid, version, scoresData); // Town mode save const town = $(data).element('town'); if (town != undefined && town != null) { const achievements = await utils.readAchievements(refid, version, defaultAchievements); for (let i = 0; i <= 5; i++) { achievements.apply[i] = town.attr()[`apply_gpmid_${i}`]; } for (let i = 0; i <= 7; i++) { achievements.building[i] = town.attr()[`building_${i}`].split(',').map(Number); } achievements.bought_flg = town.attr()[`bought_flg`].split(',').map(Number); achievements.build_flg = town.attr()[`build_flg`].split(',').map(Number); achievements.chara_flg = town.attr()[`chara_flg`].split(',').map(Number); achievements.event_flg = town.attr()[`event_flg`].split(',').map(Number); achievements.base = town.attr()[`base`].split(',').map(Number); achievements.play_type = parseInt(town.attr()['play_type'], 10); await utils.writeAchievements(refid, version, achievements); } const profile = await utils.readProfile(refid); const result = { pref: K.ITEM('s8', -1), name: K.ITEM('str', profile.name) } send.object(result); }; export const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { // TODO: rival support (see e-amuemu C# code) send.deny(); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const version: string = 'v19'; const GAME_MAX_MUSIC_ID = 1047; const defaultAchievements: AchievementsTuneStreet = { collection: 'achievements', version: 'v19', apply: Array(6), bought_flg: Array(3), build_flg: Array(8), chara_flg: Array(19), event_flg: Array(4), base: Array(4), building: { 0: Array(8), 1: Array(8), 2: Array(8), 3: Array(8), 4: Array(8), 5: Array(8), 6: Array(8), 7: Array(8), }, play_type: 0 }