def handle_lobby_rb5_lobby_entry_request(self, request: Node) -> Node: root = Node.void('lobby') root.add_child(Node.s32('interval', 120)) root.add_child(Node.s32('interval_p', 120)) # Create a lobby entry for this user extid = request.child_value('e/uid') userid = self.data.remote.user.from_extid(self.game, self.version, extid) if userid is not None: profile = self.get_profile(userid) info = self.data.local.lobby.get_play_session_info( self.game, self.version, userid) if profile is None or info is None: return root self.data.local.lobby.put_lobby( self.game, self.version, userid, { 'mid': request.child_value('e/mid'), 'ng': request.child_value('e/ng'), 'mopt': request.child_value('e/mopt'), 'lid': request.child_value('e/lid'), 'sn': request.child_value('e/sn'), 'pref': request.child_value('e/pref'), 'stg': request.child_value('e/stg'), 'pside': request.child_value('e/pside'), 'eatime': request.child_value('e/eatime'), 'ga': request.child_value('e/ga'), 'gp': request.child_value('e/gp'), 'la': request.child_value('e/la'), 'ver': request.child_value('e/ver'), }) lobby = self.data.local.lobby.get_lobby( self.game, self.version, userid, ) root.add_child(Node.s32('eid', lobby.get_int('id'))) e = Node.void('e') root.add_child(e) e.add_child(Node.s32('eid', lobby.get_int('id'))) e.add_child(Node.u16('mid', lobby.get_int('mid'))) e.add_child(Node.u8('ng', lobby.get_int('ng'))) e.add_child(Node.s32('uid', profile.get_int('extid'))) e.add_child(Node.s32('uattr', profile.get_int('uattr'))) e.add_child(Node.string('pn', profile.get_str('name'))) e.add_child(Node.s32('plyid', info.get_int('id'))) e.add_child(Node.s16('mg', profile.get_int('mg'))) e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) e.add_child(Node.string('lid', lobby.get_str('lid'))) e.add_child(Node.string('sn', lobby.get_str('sn'))) e.add_child(Node.u8('pref', lobby.get_int('pref'))) e.add_child(Node.s8('stg', lobby.get_int('stg'))) e.add_child(Node.s8('pside', lobby.get_int('pside'))) e.add_child(Node.s16('eatime', lobby.get_int('eatime'))) e.add_child(Node.u8_array('ga', lobby.get_int_array('ga', 4))) e.add_child(Node.u16('gp', lobby.get_int('gp'))) e.add_child(Node.u8_array('la', lobby.get_int_array('la', 4))) e.add_child(Node.u8('ver', lobby.get_int('ver'))) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: game = Node.void('game_3') # Generic profile stuff game.add_child(Node.string('name', profile.get_str('name'))) game.add_child( Node.string('code', ID.format_extid(profile.get_int('extid')))) game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet'))) game.add_child(Node.u32('gamecoin_block', profile.get_int('block'))) game.add_child( Node.s16('skill_name_id', profile.get_int('skill_name_id', -1))) game.add_child( Node.s32_array('hidden_param', profile.get_int_array('hidden_param', 20))) game.add_child( Node.u32('blaster_energy', profile.get_int('blaster_energy'))) game.add_child( Node.u32('blaster_count', profile.get_int('blaster_count'))) # Play statistics statistics = self.get_play_statistics(userid) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 game.add_child( Node.u32('play_count', statistics.get_int('total_plays', 0))) game.add_child(Node.u32('daily_count', today_count)) game.add_child( Node.u32('play_chain', statistics.get_int('consecutive_days', 0))) # Last played stuff if 'last' in profile: lastdict = profile.get_dict('last') last = Node.void('last') game.add_child(last) last.add_child( Node.s32('music_id', lastdict.get_int('music_id', -1))) last.add_child( Node.u8('music_type', lastdict.get_int('music_type'))) last.add_child(Node.u8('sort_type', lastdict.get_int('sort_type'))) last.add_child( Node.u8('narrow_down', lastdict.get_int('narrow_down'))) last.add_child(Node.u8('headphone', lastdict.get_int('headphone'))) last.add_child( Node.u16('appeal_id', lastdict.get_int('appeal_id', 1001))) last.add_child( Node.u16('comment_id', lastdict.get_int('comment_id'))) last.add_child( Node.u8('gauge_option', lastdict.get_int('gauge_option'))) # Item unlocks itemnode = Node.void('item') game.add_child(itemnode) game_config = self.get_game_config() achievements = self.data.local.user.get_achievements( self.game, self.version, userid) for item in achievements: if item.type[:5] != 'item_': continue itemtype = int(item.type[5:]) if game_config.get_bool( 'force_unlock_songs' ) and itemtype == self.GAME_CATALOG_TYPE_SONG: # Don't echo unlocked songs, we will add all of them later continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', itemtype)) info.add_child(Node.u32('id', item.id)) info.add_child(Node.u32('param', item.data.get_int('param'))) if 'diff_param' in item.data: info.add_child( Node.s32('diff_param', item.data.get_int('diff_param'))) if game_config.get_bool('force_unlock_songs'): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs( self.game, self.music_version) for song in songs: if song.id not in ids: ids[song.id] = 0 if song.data.get_int('difficulty') > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for itemid in ids: if ids[itemid] == 0: continue info = Node.void('info') itemnode.add_child(info) info.add_child(Node.u8('type', self.GAME_CATALOG_TYPE_SONG)) info.add_child(Node.u32('id', itemid)) info.add_child(Node.u32('param', ids[itemid])) return game
def handle_player22_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'read': refid = request.child_value('ref_id') # Pop'n Music 22 doesn't send a modelstring to load old profiles, # it just expects us to know. So always look for old profiles in # Pop'n 22 land. root = self.get_profile_by_refid(refid, self.OLD_PROFILE_FALLTHROUGH) if root is None: root = Node.void('player22') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'new': refid = request.child_value('ref_id') name = request.child_value('name') root = self.new_profile_by_refid(refid, name) if root is None: root = Node.void('player22') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'start': return Node.void('player22') elif method == 'logout': return Node.void('player22') elif method == 'write': refid = request.child_value('ref_id') root = Node.void('player22') if refid is None: return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: return root oldprofile = self.get_profile(userid) or ValidatedDict() newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: self.put_profile(userid, newprofile) return root elif method == 'friend': refid = request.attribute('ref_id') no = int(request.attribute('no', '-1')) root = Node.void('player22') if no < 0: root.add_child(Node.s8('result', 2)) return root # Look up our own user ID based on the RefID provided. userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: root.add_child(Node.s8('result', 2)) return root # Grab the links that we care about. links = self.data.local.user.get_links(self.game, self.version, userid) profiles: Dict[UserID, ValidatedDict] = {} rivals: List[Link] = [] for link in links: if link.type != 'rival': continue other_profile = self.get_profile(link.other_userid) if other_profile is None: continue profiles[link.other_userid] = other_profile rivals.append(link) # Somehow requested an invalid profile. if no >= len(rivals): root.add_child(Node.s8('result', 2)) return root rivalid = links[no].other_userid rivalprofile = profiles[rivalid] scores = self.data.remote.music.get_scores(self.game, self.version, rivalid) # First, output general profile info. friend = Node.void('friend') root.add_child(friend) friend.add_child(Node.s16('no', no)) friend.add_child( Node.string('g_pm_id', ID.format_extid(rivalprofile.get_int('extid')))) friend.add_child( Node.string('name', rivalprofile.get_str('name', 'なし'))) friend.add_child( Node.s16('chara', rivalprofile.get_int('chara', -1))) # This might be for having non-active or non-confirmed friends, but setting to 0 makes the # ranking numbers disappear and the player icon show a questionmark. friend.add_child(Node.s8('is_open', 1)) for score in scores: # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points medal = score.data.get_int('medal') music = Node.void('music') friend.add_child(music) music.set_attribute('music_num', str(score.id)) music.set_attribute( 'sheet_num', str({ self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, }[score.chart])) music.set_attribute('score', str(points)) music.set_attribute( 'clearmedal', str({ self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, }[medal])) return root elif method == 'conversion': refid = request.child_value('ref_id') name = request.child_value('name') chara = request.child_value('chara') root = self.new_profile_by_refid(refid, name, chara) if root is None: root = Node.void('playerdata') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'write_music': refid = request.child_value('ref_id') root = Node.void('player22') if refid is None: return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: return root songid = request.child_value('music_num') chart = { self.GAME_CHART_TYPE_EASY: self.CHART_TYPE_EASY, self.GAME_CHART_TYPE_NORMAL: self.CHART_TYPE_NORMAL, self.GAME_CHART_TYPE_HYPER: self.CHART_TYPE_HYPER, self.GAME_CHART_TYPE_EX: self.CHART_TYPE_EX, }[request.child_value('sheet_num')] medal = request.child_value('clearmedal') points = request.child_value('score') combo = request.child_value('combo') stats = { 'cool': request.child_value('cool'), 'great': request.child_value('great'), 'good': request.child_value('good'), 'bad': request.child_value('bad') } medal = { self.GAME_PLAY_MEDAL_CIRCLE_FAILED: self.PLAY_MEDAL_CIRCLE_FAILED, self.GAME_PLAY_MEDAL_DIAMOND_FAILED: self.PLAY_MEDAL_DIAMOND_FAILED, self.GAME_PLAY_MEDAL_STAR_FAILED: self.PLAY_MEDAL_STAR_FAILED, self.GAME_PLAY_MEDAL_EASY_CLEAR: self.PLAY_MEDAL_EASY_CLEAR, self.GAME_PLAY_MEDAL_CIRCLE_CLEARED: self.PLAY_MEDAL_CIRCLE_CLEARED, self.GAME_PLAY_MEDAL_DIAMOND_CLEARED: self.PLAY_MEDAL_DIAMOND_CLEARED, self.GAME_PLAY_MEDAL_STAR_CLEARED: self.PLAY_MEDAL_STAR_CLEARED, self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO: self.PLAY_MEDAL_CIRCLE_FULL_COMBO, self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO: self.PLAY_MEDAL_DIAMOND_FULL_COMBO, self.GAME_PLAY_MEDAL_STAR_FULL_COMBO: self.PLAY_MEDAL_STAR_FULL_COMBO, self.GAME_PLAY_MEDAL_PERFECT: self.PLAY_MEDAL_PERFECT, }[medal] self.update_score(userid, songid, chart, points, medal, combo=combo, stats=stats) return root # Invalid method return None
def verify_usergamedata_send(self, ref_id: str, ext_id: int, msg_type: str, send_only_common: bool = False) -> None: call = self.call_node() # Set up profile write profiledata = { 'COMMON': [ b'1', b'0', # shoparea spot, filled in below b'3c880f8', b'1', b'0', b'0', b'0', b'0', b'0', b'ffffffffffffffff', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'', # Name spot, filled in below ID.format_extid(ext_id).encode('ascii'), b'', b'', b'', b'', b'', b'', ], 'OPTION': [ b'0', b'3', b'0', b'0', b'0', b'0', b'0', b'3', b'0', b'0', b'0', b'0', b'1', b'2', b'0', b'0', b'0', b'10.000000', b'10.000000', b'10.000000', b'10.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'', b'', b'', b'', b'', b'', b'', b'', ], 'LAST': [ b'1', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'', b'', b'', b'', b'', b'', b'', b'', ], 'RIVAL': [ b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'0.000000', b'', b'', b'', b'', b'', b'', b'', b'', ] } if msg_type == 'new': # New profile gets blank name, because we save over it at the end of the round. profiledata['COMMON'][1] = b'0' profiledata['COMMON'][25] = b'' elif msg_type == 'existing': # Exiting profile gets our hardcoded name saved. profiledata['COMMON'][1] = b'3a' profiledata['COMMON'][25] = self.NAME.encode('shift-jis') else: raise Exception(f'Unknown message type {msg_type}!') if send_only_common: profiledata = {'COMMON': profiledata['COMMON']} # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_send') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('refid', ref_id)) data.add_child(Node.string('dataid', ref_id)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.u32('datanum', len(profiledata.keys()))) record = Node.void('record') data.add_child(record) for ptype in profiledata: profile = [b'ffffffff', ptype.encode('ascii')] + profiledata[ptype] d = Node.string( 'd', base64.b64encode(b','.join(profile)).decode('ascii')) record.add_child(d) d.add_child(Node.string('bin1', '')) # Swap with server resp = self.exchange('', call) self.assert_path(resp, "response/playerdata/result")
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('game') # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Basic game settings root.add_child(Node.string('seq', '')) root.add_child(Node.u32('code', profile.get_int('extid'))) root.add_child(Node.string('name', profile.get_str('name'))) root.add_child(Node.u8('area', profile.get_int('area', 51))) root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays'))) root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays'))) root.add_child(Node.u32('cnt_b', play_stats.get_int( 'battle_plays'))) # This could be wrong, its a guess root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0'))) root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1'))) root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2'))) root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3'))) root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4'))) root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5'))) root.add_child(Node.u32('exp', play_stats.get_int('exp'))) root.add_child(Node.u32('exp_o', profile.get_int('exp_o'))) root.add_child(Node.u32('star', profile.get_int('star'))) root.add_child(Node.u32('star_c', profile.get_int('star_c'))) root.add_child(Node.u8('combo', profile.get_int('combo', 0))) root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0))) # Character stuff chara = Node.void('chara') root.add_child(chara) chara.set_attribute('my', str(profile.get_int('chara', 30))) root.add_child( Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96))) # Drill rankings if 'title_gr' in profile: title_gr = Node.void('title_gr') root.add_child(title_gr) title_grdict = profile.get_dict('title_gr') if 't' in title_grdict: title_gr.set_attribute('t', str(title_grdict.get_int('t'))) if 's' in title_grdict: title_gr.set_attribute('s', str(title_grdict.get_int('s'))) if 'd' in title_grdict: title_gr.set_attribute('d', str(title_grdict.get_int('d'))) # Calorie mode if 'weight' in profile: workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, achievementtype='workout', since=Time.now() - Time.SECONDS_IN_DAY, ) total = sum([w.data.get_int('calories') for w in workouts]) workout = Node.void('workout') root.add_child(workout) workout.set_attribute('weight', str(profile.get_int('weight'))) workout.set_attribute('day', str(total)) workout.set_attribute('disp', '1') # Unsure if this should be last day, or total calories ever totalcalorie = Node.void('totalcalorie') root.add_child(totalcalorie) totalcalorie.set_attribute('total', str(total)) # Daily play counts last_play_date = play_stats.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = play_stats.get_int('today_plays', 0) else: today_count = 0 daycount = Node.void('daycount') root.add_child(daycount) daycount.set_attribute('playcount', str(today_count)) # Daily combo stuff, unknown how this works dailycombo = Node.void('dailycombo') root.add_child(dailycombo) dailycombo.set_attribute('daily_combo', str(0)) dailycombo.set_attribute('daily_combo_lv', str(0)) # Last cursor settings last = Node.void('last') root.add_child(last) lastdict = profile.get_dict('last') last.set_attribute('rival1', str(lastdict.get_int('rival1', -1))) last.set_attribute('rival2', str(lastdict.get_int('rival2', -1))) last.set_attribute('rival3', str(lastdict.get_int('rival3', -1))) last.set_attribute('fri', str(lastdict.get_int( 'rival1', -1))) # This literally goes to the same memory in 2014 last.set_attribute('style', str(lastdict.get_int('style'))) last.set_attribute('mode', str(lastdict.get_int('mode'))) last.set_attribute('cate', str(lastdict.get_int('cate'))) last.set_attribute('sort', str(lastdict.get_int('sort'))) last.set_attribute('mid', str(lastdict.get_int('mid'))) last.set_attribute('mtype', str(lastdict.get_int('mtype'))) last.set_attribute('cid', str(lastdict.get_int('cid'))) last.set_attribute('ctype', str(lastdict.get_int('ctype'))) last.set_attribute('sid', str(lastdict.get_int('sid'))) # Result stars result_star = Node.void('result_star') root.add_child(result_star) result_stars = profile.get_int_array('result_stars', 9) for i in range(9): result_star.set_attribute('slot{}'.format(i + 1), str(result_stars[i])) # Groove gauge level-ups gr_s = Node.void('gr_s') root.add_child(gr_s) index = 1 for entry in profile.get_int_array('gr_s', 5): gr_s.set_attribute('gr{}'.format(index), str(entry)) index = index + 1 gr_d = Node.void('gr_d') root.add_child(gr_d) index = 1 for entry in profile.get_int_array('gr_d', 5): gr_d.set_attribute('gr{}'.format(index), str(entry)) index = index + 1 # Options in menus root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16))) root.add_child( Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16))) option_ver = Node.void('option_ver') root.add_child(option_ver) option_ver.set_attribute('ver', str(profile.get_int('option_ver', 2))) if 'option_02' in profile: root.add_child( Node.s16_array('option_02', profile.get_int_array('option_02', 24))) # Unlock flags root.add_child( Node.u8_array('flag', profile.get_int_array('flag', 512, [1] * 512)[:256])) root.add_child( Node.u8_array('flag_ex', profile.get_int_array('flag', 512, [1] * 512))) # Ranking display? root.add_child( Node.u16_array('rank', profile.get_int_array('rank', 100))) # Rivals links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: if link.type[:7] != 'friend_': continue pos = int(link.type[7:]) friend = self.get_profile(link.other_userid) play_stats = self.get_play_statistics(link.other_userid) if friend is not None: friendnode = Node.void('friend') root.add_child(friendnode) friendnode.set_attribute('pos', str(pos)) friendnode.set_attribute('vs', '0') friendnode.set_attribute('up', '0') friendnode.add_child(Node.u32('code', friend.get_int('extid'))) friendnode.add_child( Node.string('name', friend.get_str('name'))) friendnode.add_child( Node.u8('area', friend.get_int('area', 51))) friendnode.add_child(Node.u32('exp', play_stats.get_int('exp'))) friendnode.add_child(Node.u32('star', friend.get_int('star'))) # Drill rankings if 'title' in friend: title = Node.void('title') friendnode.add_child(title) titledict = friend.get_dict('title') if 't' in titledict: title.set_attribute('t', str(titledict.get_int('t'))) if 's' in titledict: title.set_attribute('s', str(titledict.get_int('s'))) if 'd' in titledict: title.set_attribute('d', str(titledict.get_int('d'))) if 'title_gr' in friend: title_gr = Node.void('title_gr') friendnode.add_child(title_gr) title_grdict = friend.get_dict('title_gr') if 't' in title_grdict: title_gr.set_attribute('t', str(title_grdict.get_int('t'))) if 's' in title_grdict: title_gr.set_attribute('s', str(title_grdict.get_int('s'))) if 'd' in title_grdict: title_gr.set_attribute('d', str(title_grdict.get_int('d'))) # Groove gauge level-ups gr_s = Node.void('gr_s') friendnode.add_child(gr_s) index = 1 for entry in friend.get_int_array('gr_s', 5): gr_s.set_attribute('gr{}'.format(index), str(entry)) index = index + 1 gr_d = Node.void('gr_d') friendnode.add_child(gr_d) index = 1 for entry in friend.get_int_array('gr_d', 5): gr_d.set_attribute('gr{}'.format(index), str(entry)) index = index + 1 # Target stuff target = Node.void('target') root.add_child(target) target.set_attribute('flag', str(profile.get_int('target_flag'))) target.set_attribute('setnum', str(profile.get_int('target_setnum'))) # Play area areas = profile.get_int_array('play_area', 55) play_area = Node.void('play_area') root.add_child(play_area) for i in range(len(areas)): play_area.set_attribute('play_cnt{}'.format(i), str(areas[i])) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('playerdata') # Set up the base profile base = Node.void('base') root.add_child(base) base.add_child(Node.string('name', profile.get_str('name', 'なし'))) base.add_child( Node.string('g_pm_id', ID.format_extid(profile.get_int('extid')))) base.add_child(Node.u8('mode', profile.get_int('mode', 0))) base.add_child(Node.s8('button', profile.get_int('button', 0))) base.add_child( Node.s8('last_play_flag', profile.get_int('last_play_flag', -1))) base.add_child( Node.u8('medal_and_friend', profile.get_int('medal_and_friend', 0))) base.add_child(Node.s8('category', profile.get_int('category', -1))) base.add_child( Node.s8('sub_category', profile.get_int('sub_category', -1))) base.add_child(Node.s16('chara', profile.get_int('chara', -1))) base.add_child( Node.s8('chara_category', profile.get_int('chara_category', -1))) base.add_child(Node.u8('collabo', profile.get_int('collabo', 255))) base.add_child(Node.u8('sheet', profile.get_int('sheet', 0))) base.add_child(Node.s8('tutorial', profile.get_int('tutorial', 0))) base.add_child( Node.s32('music_open_pt', profile.get_int('music_open_pt', 0))) base.add_child(Node.s8('is_conv', -1)) base.add_child(Node.s32('option', profile.get_int('option', 0))) base.add_child(Node.s16('music', profile.get_int('music', -1))) base.add_child(Node.u16('ep', profile.get_int('ep', 0))) base.add_child( Node.s32_array('sp_color_flg', profile.get_int_array('sp_color_flg', 2))) base.add_child(Node.s32('read_news', profile.get_int('read_news', 0))) base.add_child( Node.s16('consecutive_days_coupon', profile.get_int('consecutive_days_coupon', 0))) base.add_child(Node.s8('staff', 0)) # Player card section player_card_dict = profile.get_dict('player_card') player_card = Node.void('player_card') root.add_child(player_card) player_card.add_child( Node.u8_array('title', player_card_dict.get_int_array('title', 2, [0, 1]))) player_card.add_child( Node.u8('frame', player_card_dict.get_int('frame'))) player_card.add_child(Node.u8('base', player_card_dict.get_int('base'))) player_card.add_child( Node.u8_array('seal', player_card_dict.get_int_array('seal', 2))) player_card.add_child( Node.s32_array('get_title', player_card_dict.get_int_array('get_title', 4))) player_card.add_child( Node.s32('get_frame', player_card_dict.get_int('get_frame'))) player_card.add_child( Node.s32('get_base', player_card_dict.get_int('get_base'))) player_card.add_child( Node.s32_array('get_seal', player_card_dict.get_int_array('get_seal', 2))) # Player card EX section player_card_ex = Node.void('player_card_ex') root.add_child(player_card_ex) player_card_ex.add_child( Node.s32('get_title_ex', player_card_dict.get_int('get_title_ex'))) player_card_ex.add_child( Node.s32('get_frame_ex', player_card_dict.get_int('get_frame_ex'))) player_card_ex.add_child( Node.s32('get_base_ex', player_card_dict.get_int('get_base_ex'))) player_card_ex.add_child( Node.s32('get_seal_ex', player_card_dict.get_int('get_seal_ex'))) # Statistics section and scores section statistics = self.get_play_statistics(userid) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 base.add_child(Node.u8('active_fr_num', 0)) # TODO: Hook up rivals code? base.add_child( Node.s32('total_play_cnt', statistics.get_int('total_plays', 0))) base.add_child(Node.s16('today_play_cnt', today_count)) base.add_child( Node.s16('consecutive_days', statistics.get_int('consecutive_days', 0))) last_played = [ x[0] for x in self.data.local.music.get_last_played( self.game, self.version, userid, 3) ] most_played = [ x[0] for x in self.data.local.music.get_most_played( self.game, self.version, userid, 20) ] while len(last_played) < 3: last_played.append(-1) while len(most_played) < 20: most_played.append(-1) hiscore_array = [0] * int( (((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8) clear_medal = [0] * self.GAME_MAX_MUSIC_ID clear_medal_sub = [0] * self.GAME_MAX_MUSIC_ID scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: if score.id > self.GAME_MAX_MUSIC_ID: continue # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points clear_medal[score.id] = clear_medal[ score.id] | self.__format_medal_for_score(score) hiscore_index = (score.id * 4) + { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY_POSITION, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL_POSITION, self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER_POSITION, self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX_POSITION, }[score.chart] hiscore_byte_pos = int((hiscore_index * 17) / 8) hiscore_bit_pos = int((hiscore_index * 17) % 8) hiscore_value = points << 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) hiscore = bytes(hiscore_array) player_card.add_child(Node.s16_array('best_music', most_played[0:3])) base.add_child(Node.s16_array('my_best', most_played)) base.add_child(Node.s16_array('latest_music', last_played)) base.add_child(Node.u16_array('clear_medal', clear_medal)) base.add_child(Node.u8_array('clear_medal_sub', clear_medal_sub)) # Goes outside of base for some reason root.add_child(Node.binary('hiscore', hiscore)) # Net VS section netvs = Node.void('netvs') root.add_child(netvs) netvs.add_child(Node.s32_array('get_ojama', [0, 0])) netvs.add_child(Node.s32('rank_point', 0)) netvs.add_child(Node.s32('play_point', 0)) netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0])) netvs.add_child(Node.u8('rank', 0)) netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0])) netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) netvs.add_child(Node.s8_array('jewelry', [0] * 15)) for dialog in [0, 1, 2, 3, 4, 5]: # TODO: Configure this, maybe? netvs.add_child(Node.string('dialog', f'dialog#{dialog}')) sp_data = Node.void('sp_data') root.add_child(sp_data) sp_data.add_child(Node.s32('sp', profile.get_int('sp', 0))) reflec_data = Node.void('reflec_data') root.add_child(reflec_data) reflec_data.add_child( Node.s8_array('reflec', profile.get_int_array('reflec', 2))) # Navigate section navigate_dict = profile.get_dict('navigate') navigate = Node.void('navigate') root.add_child(navigate) navigate.add_child(Node.s8('genre', navigate_dict.get_int('genre'))) navigate.add_child(Node.s8('image', navigate_dict.get_int('image'))) navigate.add_child(Node.s8('level', navigate_dict.get_int('level'))) navigate.add_child(Node.s8('ojama', navigate_dict.get_int('ojama'))) navigate.add_child( Node.s16('limit_num', navigate_dict.get_int('limit_num'))) navigate.add_child(Node.s8('button', navigate_dict.get_int('button'))) navigate.add_child(Node.s8('life', navigate_dict.get_int('life'))) navigate.add_child( Node.s16('progress', navigate_dict.get_int('progress'))) return root
def parse_psmap(data: bytes, offset: str, rootname: str) -> Node: pe = PEFile(data=data) root = Node.void(rootname) base = int(offset, 16) if pe.is_virtual(base): # Assume this is virtual base = pe.virtual_to_physical(base) def read_string(offset: int) -> str: # First, translate load offset in memory to disk offset offset = pe.virtual_to_physical(offset) # Now, grab bytes until we're null-terminated bytestring = [] while data[offset] != 0: bytestring.append(data[offset]) offset = offset + 1 # Its shift-jis encoded, so decode it now return bytes(bytestring).decode('shift_jisx0213') # For recursing into nodes saved_root: List[Node] = [] saved_loc: List[int] = [] while True: if pe.is_64bit(): # 64 bit chunk = data[base:(base + 24)] base = base + 24 (nodetype, mandatory, outoffset, width, nameptr, defaultptr) = struct.unpack('<BBHIQQ', chunk) else: # 32 bit chunk = data[base:(base + 16)] base = base + 16 (nodetype, mandatory, outoffset, width, nameptr, defaultptr) = struct.unpack('<BBHIII', chunk) if nodetype == 0xFF or nodetype == 0x00: # if nodetype is 0 then we probably read garbage # End of nodes, see if we should exit if len(saved_root) == 0: break else: root = saved_root.pop() oldbase = saved_loc.pop() if oldbase is not None: base = oldbase continue # Grab name, get rid of parse numbers name = read_string(nameptr) try: if name.index('#') >= 0: name = name[:name.index('#')] except ValueError: pass if nodetype == 0x01: # This is a void node, so we should handle by recursing node = Node.void(name) root.add_child(node) # Recurse here saved_root.append(root) if defaultptr != 0: saved_loc.append(base) base = pe.virtual_to_physical(defaultptr) else: saved_loc.append(None) root = node continue elif nodetype == 0x02 or nodetype == 0x43: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.s8_array(name, [-1] * elements) else: node = Node.s8(name, -1) elif nodetype == 0x03 or nodetype == 0x44: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.u8_array(name, [0] * elements) else: node = Node.u8(name, 0) elif nodetype == 0x04 or nodetype == 0x45: if nodetype < 0x40: elements = int(width / 2) else: elements = width if elements > 1: node = Node.s16_array(name, [-1] * elements) else: node = Node.s16(name, -1) elif nodetype == 0x05 or nodetype == 0x46: if nodetype < 0x40: elements = int(width / 2) else: elements = width if elements > 1: node = Node.u16_array(name, [0] * elements) else: node = Node.u16(name, 0) elif nodetype == 0x06 or nodetype == 0x47: if nodetype < 0x40: elements = int(width / 4) else: elements = width if elements > 1: node = Node.s32_array(name, [-1] * elements) else: node = Node.s32(name, -1) elif nodetype == 0x07 or nodetype == 0x48: if nodetype < 0x40: elements = int(width / 4) else: elements = width if elements > 1: node = Node.u32_array(name, [0] * elements) else: node = Node.u32(name, 0) elif nodetype == 0x08 or nodetype == 0x49: if nodetype < 0x40: elements = int(width / 8) else: elements = width if elements > 1: node = Node.s64_array(name, [-1] * elements) else: node = Node.s64(name, -1) elif nodetype == 0x09 or nodetype == 0x4A: if nodetype < 0x40: elements = int(width / 8) else: elements = width if elements > 1: node = Node.u64_array(name, [0] * elements) else: node = Node.u64(name, 0) elif nodetype == 0x0A: node = Node.string(name, '') elif nodetype == 0x0D: node = Node.float(name, 0.0) elif nodetype == 0x32 or nodetype == 0x6D: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.bool_array(name, [False] * elements) else: node = Node.bool(name, False) elif nodetype == 0x2F: # Special case, this is an attribute if name[-1] != '@': raise Exception( f'Attribute name {name} expected to end with @') root.set_attribute(name[:-1], '') continue else: raise Exception(f'Unimplemented node type 0x{nodetype:02x}') # Append it root.add_child(node) return root
def handle_game_3_common_request(self, request: Node) -> Node: game = Node.void('game_3') limited = Node.void('music_limited') game.add_child(limited) # Song unlock config game_config = self.get_game_config() if game_config.get_bool('force_unlock_songs'): ids = set() songs = self.data.local.music.get_all_songs( self.game, self.music_version) for song in songs: if song.data.get_int('limited') in ( self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): ids.add((song.id, song.chart)) for (songid, chart) in ids: info = Node.void('info') limited.add_child(info) info.add_child(Node.s32('music_id', songid)) info.add_child(Node.u8('music_type', chart)) info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) # Event config event = Node.void('event') game.add_child(event) def enable_event(eid: int) -> None: evt = Node.void('info') event.add_child(evt) evt.add_child(Node.u32('event_id', eid)) if not game_config.get_bool('disable_matching'): enable_event(143) # Matching enabled # These events are meant specifically for Museca Plus museca_plus_events = [ 140, # Agetta Moratta (vmlink_phase 3 in musicdb) 211, # News 1 ] event_ids = [ 1, # Extended pedal options 56, # Generator grafica icon <print 1 in musicdb> 83, # Paseli Light Start 86, # Generator grafica icon <print 2 in musicdb> 98, # Caption 2 notice (grs_grafica_caption_2.png) 105, # Makes the "Number of Layers" option visible in game settings 130, # Curator Rank 141, # Coconatsu & Mukipara grafica effects 145, # MUKIPARA UNLOCKS 146, # MUKIPARA UNLOCKS 147, # MUKIPARA UNLOCKS 148, # MUKIPARA UNLOCKS 149, # MUKIPARA UNLOCKS 195, # Fictional Curator (foot pedal options) ] for evtid in event_ids: enable_event(evtid) if self.omnimix: for evtid in museca_plus_events: enable_event(evtid) # Makes special missions available on grafica that have them. extend = Node.void('extend') game.add_child(extend) info = Node.void('info') extend.add_child(info) info.add_child(Node.u32('extend_id', 12)) info.add_child(Node.u32('extend_type', 9)) info.add_child(Node.s32('param_num_1', 2)) info.add_child(Node.s32('param_num_2', 50)) info.add_child(Node.s32('param_num_3', 59)) info.add_child(Node.s32('param_num_4', 64)) info.add_child(Node.s32('param_num_5', 86)) info.add_child(Node.string('param_str_1', 'available_ex: 1')) info.add_child(Node.string('param_str_2', 'available_ex: 1')) info.add_child(Node.string('param_str_3', 'available_ex: 1')) info.add_child(Node.string('param_str_4', 'available_ex: 1')) info.add_child(Node.string('param_str_5', 'available_ex: 1')) return game
def handle_game_3_common_request(self, request: Node) -> Node: game = Node.void('game_3') limited = Node.void('music_limited') game.add_child(limited) # Song unlock config game_config = self.get_game_config() if game_config.get_bool('force_unlock_songs'): ids = set() songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.data.get_int('limited') in (self.GAME_LIMITED_LOCKED, self.GAME_LIMITED_UNLOCKABLE): ids.add((song.id, song.chart)) for (songid, chart) in ids: info = Node.void('info') limited.add_child(info) info.add_child(Node.s32('music_id', songid)) info.add_child(Node.u8('music_type', chart)) info.add_child(Node.u8('limited', self.GAME_LIMITED_UNLOCKED)) # Event config event = Node.void('event') game.add_child(event) def enable_event(eid: int) -> None: evt = Node.void('info') event.add_child(evt) evt.add_child(Node.u32('event_id', eid)) if not game_config.get_bool('disable_matching'): enable_event(1) # Matching enabled enable_event(2) # Floor Infection enable_event(3) # Policy Break enable_event(60) # BEMANI Summer Diary for eventid in self._get_extra_events(): enable_event(eventid) # Skill Analyzer config skill_course = Node.void('skill_course') game.add_child(skill_course) seasons = self._get_skill_analyzer_seasons() skillnames = self.__get_skill_analyzer_skill_levels() courses = self._get_skill_analyzer_courses() max_level: Dict[int, int] = {} for course in courses: max_level[course['level']] = max(course['season_id'], max_level.get(course['level'], -1)) for course in courses: info = Node.void('info') skill_course.add_child(info) info.add_child(Node.s16('course_id', course.get('id', course['level']))) info.add_child(Node.s16('level', course['level'])) info.add_child(Node.s32('season_id', course['season_id'])) info.add_child(Node.string('season_name', seasons[course['season_id']])) info.add_child(Node.bool('season_new_flg', max_level[course['level']] == course['season_id'])) info.add_child(Node.string('course_name', course.get('skill_name', skillnames.get(course['level'], '')))) info.add_child(Node.s16('course_type', 0)) info.add_child(Node.s16('skill_name_id', course.get('skill_name_id', course['level']))) info.add_child(Node.bool('matching_assist', course['level'] >= 0 and course['level'] <= 6)) info.add_child(Node.s16('gauge_type', self.GAME_GAUGE_TYPE_SKILL)) info.add_child(Node.s16('paseli_type', 0)) for trackno, trackdata in enumerate(course['tracks']): track = Node.void('track') info.add_child(track) track.add_child(Node.s16('track_no', trackno)) track.add_child(Node.s32('music_id', trackdata['id'])) track.add_child(Node.s8('music_type', trackdata['type'])) return game
def verify_game_shop(self, location: str) -> None: call = self.call_node() game = Node.void('game_3') call.add_child(game) game.set_attribute('method', 'shop') game.set_attribute('ver', '0') game.add_child(Node.string('locid', location)) game.add_child(Node.string('regcode', '.')) game.add_child(Node.string('locname', '')) game.add_child(Node.u8('loctype', 0)) game.add_child(Node.string('cstcode', '')) game.add_child(Node.string('cpycode', '')) game.add_child(Node.s32('latde', 0)) game.add_child(Node.s32('londe', 0)) game.add_child(Node.u8('accu', 0)) game.add_child(Node.string('linid', '.')) game.add_child(Node.u8('linclass', 0)) game.add_child(Node.ipv4('ipaddr', '0.0.0.0')) game.add_child(Node.string('hadid', '00010203040506070809')) game.add_child(Node.string('licid', '00010203040506070809')) game.add_child(Node.string('actid', self.pcbid)) game.add_child(Node.s8('appstate', 0)) game.add_child(Node.s8('c_need', 1)) game.add_child(Node.s8('c_credit', 2)) game.add_child(Node.s8('s_credit', 2)) game.add_child(Node.bool('free_p', True)) game.add_child(Node.bool('close', False)) game.add_child(Node.s32('close_t', 1380)) game.add_child(Node.u32('playc', 0)) game.add_child(Node.u32('playn', 0)) game.add_child(Node.u32('playe', 0)) game.add_child(Node.u32('test_m', 0)) game.add_child(Node.u32('service', 0)) game.add_child(Node.bool('paseli', True)) game.add_child(Node.u32('update', 0)) game.add_child(Node.string('shopname', '')) game.add_child(Node.bool('newpc', False)) game.add_child(Node.s32('s_paseli', 206)) game.add_child(Node.s32('monitor', 1)) game.add_child(Node.string('romnumber', 'KFC-JA-M01')) game.add_child( Node.string('etc', 'TaxMode:1,BasicRate:100/1,FirstFree:0')) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/game_3/nxt_time")
def verify_gameend_regist(self, ref_id: str, jid: int, mode: int, scores: List[Dict[str, Any]], course: Dict[str, Any]) -> None: call = self.call_node() # Construct node gameend = Node.void('gameend') call.add_child(gameend) gameend.set_attribute('method', 'regist') gameend.add_child(Node.s32('retry', 0)) data = Node.void('data') gameend.add_child(data) player = Node.void('player') data.add_child(player) player.add_child(Node.s8('mode', mode)) player.add_child(Node.string('refid', ref_id)) player.add_child(Node.s32('jid', jid)) player.add_child(Node.string('name', self.NAME)) result = Node.void('result') data.add_child(result) result.set_attribute('count', str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 if score['clear']: bits |= 0x2 if score['fc']: bits |= 0x4 if score['ex']: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 tune = Node.void('tune') result.add_child(tune) tune.set_attribute('id', str(scoreid)) tune.set_attribute('count', '0') tune.add_child(Node.s32('music', score['id'])) player_1 = Node.void('player') tune.add_child(player_1) player_1.set_attribute('rank', '1') scorenode = Node.s32('score', score['score']) player_1.add_child(scorenode) scorenode.set_attribute('seq', str(score['chart'])) scorenode.set_attribute('clear', str(bits)) scorenode.set_attribute('combo', '69') player_1.add_child( Node.u8_array('mbar', [ 239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255 ])) if len(course) > 0: coursenode = Node.void('course') data.add_child(coursenode) coursenode.add_child(Node.s32('course_id', course['course_id'])) coursenode.add_child(Node.u8('rating', course['rating'])) index = 0 for coursescore in course['scores']: music = Node.void('music') coursenode.add_child(music) music.set_attribute('index', str(index)) music.add_child(Node.s32('score', coursescore)) index = index + 1 # Swap with server resp = self.exchange('', call) self.assert_path(resp, "response/gameend/data/player/session_id")
def verify_game_load(self, cardid: str, refid: str, msg_type: str) -> Dict[str, Any]: call = self.call_node() game = Node.void('game_3') call.add_child(game) game.set_attribute('method', 'load') game.set_attribute('ver', '0') game.add_child(Node.string('dataid', refid)) game.add_child(Node.string('cardid', cardid)) game.add_child(Node.string('refid', refid)) # Swap with server resp = self.exchange('', call) # Verify that response is correct if msg_type == "new": self.assert_path(resp, "response/game_3/result") if resp.child_value('game_3/result') != 1: raise Exception("Invalid result for new profile!") return None if msg_type == "existing": self.assert_path(resp, "response/game_3/name") self.assert_path(resp, "response/game_3/code") self.assert_path(resp, "response/game_3/gamecoin_packet") self.assert_path(resp, "response/game_3/gamecoin_block") self.assert_path(resp, "response/game_3/skill_name_id") self.assert_path(resp, "response/game_3/hidden_param") self.assert_path(resp, "response/game_3/blaster_energy") self.assert_path(resp, "response/game_3/blaster_count") self.assert_path(resp, "response/game_3/play_count") self.assert_path(resp, "response/game_3/daily_count") self.assert_path(resp, "response/game_3/play_chain") self.assert_path(resp, "response/game_3/item") self.assert_path(resp, "response/game_3/skill/course_all") self.assert_path(resp, "response/game_3/story") items: Dict[int, Dict[int, int]] = {} for child in resp.child('game_3/item').children: if child.name != 'info': continue itype = child.child_value('type') iid = child.child_value('id') param = child.child_value('param') if itype not in items: items[itype] = {} items[itype][iid] = param courses: Dict[int, Dict[int, Dict[str, int]]] = {} for child in resp.child('game_3/skill/course_all').children: if child.name != 'd': continue crsid = child.child_value('crsid') season = child.child_value('ssnid') achievement_rate = child.child_value('ar') clear_type = child.child_value('ct') if season not in courses: courses[season] = {} courses[season][crsid] = { 'achievement_rate': achievement_rate, 'clear_type': clear_type, } return { 'name': resp.child_value('game_3/name'), 'packet': resp.child_value('game_3/gamecoin_packet'), 'block': resp.child_value('game_3/gamecoin_block'), 'blaster_energy': resp.child_value('game_3/blaster_energy'), 'items': items, 'courses': courses, } else: raise Exception("Invalid game load type {}".format(msg_type))
def parse_psmap(data: bytes, offset: str, rootname: str) -> Node: pe = pefile.PE(data=data, fast_load=True) root = Node.void(rootname) base = int(offset, 16) def virtual_to_physical(offset: int) -> int: for section in pe.sections: start = section.VirtualAddress + pe.OPTIONAL_HEADER.ImageBase end = start + section.SizeOfRawData if offset >= start and offset < end: return (offset - start) + section.PointerToRawData raise Exception( 'Couldn\'t find raw offset for virtual offset 0x{:08x}'.format( offset)) if base >= pe.OPTIONAL_HEADER.ImageBase: # Assume this is virtual base = virtual_to_physical(base) def read_string(offset: int) -> str: # First, translate load offset in memory to disk offset offset = virtual_to_physical(offset) # Now, grab bytes until we're null-terminated bytestring = [] while data[offset] != 0: bytestring.append(data[offset]) offset = offset + 1 # Its shift-jis encoded, so decode it now return bytes(bytestring).decode('shift_jisx0213') # For recursing into nodes saved_root: List[Node] = [] saved_loc: List[int] = [] while True: chunk = data[base:(base + 16)] base = base + 16 (nodetype, mandatory, outoffset, width, nameptr, defaultptr) = struct.unpack('<BBHIII', chunk) if nodetype == 0xFF: # End of nodes, see if we should exit if len(saved_root) == 0: break else: root = saved_root.pop() oldbase = saved_loc.pop() if oldbase is not None: base = oldbase continue # Grab name, get rid of parse numbers name = read_string(nameptr) try: if name.index('#') >= 0: name = name[:name.index('#')] except ValueError: pass if nodetype == 0x00: raise Exception('Invalid node type 0x{:02x}'.format(nodetype)) elif nodetype == 0x01: # This is a void node, so we should handle by recursing node = Node.void(name) root.add_child(node) # Recurse here saved_root.append(root) if defaultptr != 0: saved_loc.append(base) base = virtual_to_physical(defaultptr) else: saved_loc.append(None) root = node continue elif nodetype == 0x02 or nodetype == 0x43: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.s8_array(name, [-1] * elements) else: node = Node.s8(name, -1) elif nodetype == 0x03 or nodetype == 0x44: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.u8_array(name, [0] * elements) else: node = Node.u8(name, 0) elif nodetype == 0x04 or nodetype == 0x45: if nodetype < 0x40: elements = int(width / 2) else: elements = width if elements > 1: node = Node.s16_array(name, [-1] * elements) else: node = Node.s16(name, -1) elif nodetype == 0x05 or nodetype == 0x46: if nodetype < 0x40: elements = int(width / 2) else: elements = width if elements > 1: node = Node.u16_array(name, [0] * elements) else: node = Node.u16(name, 0) elif nodetype == 0x06 or nodetype == 0x47: if nodetype < 0x40: elements = int(width / 4) else: elements = width if elements > 1: node = Node.s32_array(name, [-1] * elements) else: node = Node.s32(name, -1) elif nodetype == 0x07 or nodetype == 0x48: if nodetype < 0x40: elements = int(width / 4) else: elements = width if elements > 1: node = Node.u32_array(name, [0] * elements) else: node = Node.u32(name, 0) elif nodetype == 0x08 or nodetype == 0x49: if nodetype < 0x40: elements = int(width / 8) else: elements = width if elements > 1: node = Node.s64_array(name, [-1] * elements) else: node = Node.s64(name, -1) elif nodetype == 0x09 or nodetype == 0x4A: if nodetype < 0x40: elements = int(width / 8) else: elements = width if elements > 1: node = Node.u64_array(name, [0] * elements) else: node = Node.u64(name, 0) elif nodetype == 0x0A: node = Node.string(name, '') elif nodetype == 0x0D: node = Node.float(name, 0.0) elif nodetype == 0x32 or nodetype == 0x6D: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.bool_array(name, [False] * elements) else: node = Node.bool(name, False) else: raise Exception( 'Unimplemented node type 0x{:02x}'.format(nodetype)) # Append it root.add_child(node) return root
def _add_shop_score(self, root: Node) -> None: shop_score = Node.void('shop_score') root.add_child(shop_score) today = Node.void('today') shop_score.add_child(today) yesterday = Node.void('yesterday') shop_score.add_child(yesterday) all_profiles = self.data.local.user.get_all_profiles( self.game, self.version) all_attempts = self.data.local.music.get_all_attempts( self.game, self.music_version, timelimit=(Time.beginning_of_today() - Time.SECONDS_IN_DAY)) machine = self.data.local.machine.get_machine( self.config['machine']['pcbid']) if machine.arcade is not None: lids = [ machine.id for machine in self.data.local.machine.get_all_machines(machine.arcade) ] else: lids = [machine.id] relevant_profiles = [ profile for profile in all_profiles if profile[1].get_int('lid', -1) in lids ] for (rootnode, timeoffset) in [ (today, 0), (yesterday, Time.SECONDS_IN_DAY), ]: # Grab all attempts made in the relevant day relevant_attempts = [ attempt for attempt in all_attempts if (attempt[1].timestamp >= (Time.beginning_of_today() - timeoffset) and attempt[1].timestamp <= (Time.end_of_today() - timeoffset)) ] # Calculate scores based on attempt scores_by_user: Dict[UserID, Dict[int, Dict[int, Attempt]]] = {} for (userid, attempt) in relevant_attempts: if userid not in scores_by_user: scores_by_user[userid] = {} if attempt.id not in scores_by_user[userid]: scores_by_user[userid][attempt.id] = {} if attempt.chart not in scores_by_user[userid][attempt.id]: # No high score for this yet, just use this attempt scores_by_user[userid][attempt.id][attempt.chart] = attempt else: # If this attempt is better than the stored one, replace it if scores_by_user[userid][attempt.id][ attempt.chart].points < attempt.points: scores_by_user[userid][attempt.id][ attempt.chart] = attempt # Calculate points earned by user in the day points_by_user: Dict[UserID, int] = {} for userid in scores_by_user: points_by_user[userid] = 0 for mid in scores_by_user[userid]: for chart in scores_by_user[userid][mid]: points_by_user[userid] = points_by_user[ userid] + scores_by_user[userid][mid][chart].points # Output that day's earned points for (userid, profile) in relevant_profiles: data = Node.void('data') rootnode.add_child(data) data.add_child( Node.s16( 'day_id', int((Time.now() - timeoffset) / Time.SECONDS_IN_DAY))) data.add_child(Node.s32('user_id', profile.get_int('extid'))) data.add_child( Node.s16('icon_id', profile.get_dict('config').get_int('icon_id'))) data.add_child( Node.s16('point', min(points_by_user.get(userid, 0), 32767))) data.add_child(Node.s32('update_time', Time.now())) data.add_child(Node.string('name', profile.get_str('name'))) rootnode.add_child( Node.s32('time', Time.beginning_of_today() - timeoffset))
def verify_player_write(self, refid: str, extid: int, loc: str, records: List[Dict[str, int]], scores: List[Dict[str, int]]) -> int: call = self.call_node() player = Node.void('player') call.add_child(player) player.set_attribute('method', 'write') player.add_child(Node.string('rid', refid)) player.add_child(Node.string('lid', loc)) pdata = Node.void('pdata') player.add_child(pdata) base = Node.void('base') pdata.add_child(base) base.add_child(Node.s32('uid', extid)) base.add_child(Node.string('name', self.NAME)) base.add_child(Node.s16('lv', 1)) base.add_child(Node.s32('exp', 0)) base.add_child(Node.s16('mg', 0)) base.add_child(Node.s16('ap', 0)) base.add_child(Node.s32('flag', 0)) con = Node.void('con') pdata.add_child(con) con.add_child(Node.s32('day', 0)) con.add_child(Node.s32('cnt', 0)) con.add_child(Node.s32('last', 0)) con.add_child(Node.s32('now', 0)) custom = Node.void('custom') pdata.add_child(custom) custom.add_child(Node.u8('bgm_m', 0)) custom.add_child(Node.u8('st_f', 0)) custom.add_child(Node.u8('st_bg', 0)) custom.add_child(Node.u8('st_bg_b', 100)) custom.add_child(Node.u8('eff_e', 0)) custom.add_child(Node.u8('se_s', 0)) custom.add_child(Node.u8('se_s_v', 100)) pdata.add_child(Node.void('released')) # First, filter down to only records that are also in the battle log def key(thing: Dict[str, int]) -> str: return '{}-{}'.format(thing['id'], thing['chart']) updates = [key(score) for score in scores] sortedrecords = {key(record): record for record in records if key(record) in updates} # Now, see what records need updating and update them for score in scores: if key(score) in sortedrecords: # Had a record, need to merge record = sortedrecords[key(score)] else: # First time playing record = { 'clear_type': 0, 'achievement_rate': 0, 'score': 0, 'combo': 0, 'miss_count': 999999999, } sortedrecords[key(score)] = { 'id': score['id'], 'chart': score['chart'], 'clear_type': max(record['clear_type'], score['clear_type']), 'achievement_rate': max(record['achievement_rate'], score['achievement_rate']), 'score': max(record['score'], score['score']), 'combo': max(record['combo'], score['combo']), 'miss_count': min(record['miss_count'], score['miss_count']), } # Finally, send the records and battle logs recordnode = Node.void('record') pdata.add_child(recordnode) blog = Node.void('blog') pdata.add_child(blog) for (_, record) in sortedrecords.items(): rec = Node.void('rec') recordnode.add_child(rec) rec.add_child(Node.u16('mid', record['id'])) rec.add_child(Node.u8('ng', record['chart'])) rec.add_child(Node.s32('win', 1)) rec.add_child(Node.s32('lose', 0)) rec.add_child(Node.s32('draw', 0)) rec.add_child(Node.u8('ct', record['clear_type'])) rec.add_child(Node.s16('ar', record['achievement_rate'])) rec.add_child(Node.s16('bs', record['score'])) rec.add_child(Node.s16('mc', record['combo'])) rec.add_child(Node.s16('bmc', record['miss_count'])) scoreid = 0 for score in scores: log = Node.void('log') blog.add_child(log) log.add_child(Node.u8('id', scoreid)) log.add_child(Node.u16('mid', score['id'])) log.add_child(Node.u8('ng', score['chart'])) log.add_child(Node.u8('mt', 0)) log.add_child(Node.u8('rt', 0)) log.add_child(Node.s32('ruid', 0)) myself = Node.void('myself') log.add_child(myself) myself.add_child(Node.s16('mg', 0)) myself.add_child(Node.s16('ap', 0)) myself.add_child(Node.u8('ct', score['clear_type'])) myself.add_child(Node.s16('s', score['score'])) myself.add_child(Node.s16('ar', score['achievement_rate'])) rival = Node.void('rival') log.add_child(rival) rival.add_child(Node.s16('mg', 0)) rival.add_child(Node.s16('ap', 0)) rival.add_child(Node.u8('ct', 2)) rival.add_child(Node.s16('s', 177)) rival.add_child(Node.s16('ar', 500)) log.add_child(Node.s32('time', Time.now())) scoreid = scoreid + 1 # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/player/uid") self.assert_path(resp, "response/player/time") return resp.child_value('player/uid')
def verify_player23_read( self, ref_id: str, msg_type: str) -> Dict[str, Dict[int, Dict[str, int]]]: call = self.call_node() # Construct node player23 = Node.void('player23') call.add_child(player23) player23.set_attribute('method', 'read') player23.add_child(Node.string('ref_id', ref_id)) player23.add_child(Node.s8('pref', 51)) # Swap with server resp = self.exchange('', call) if msg_type == 'new': # Verify that response is correct self.assert_path(resp, "response/player23/result") status = resp.child_value('player23/result') if status != 2: raise Exception( 'Reference ID \'{}\' returned invalid status \'{}\''. format(ref_id, status)) return { 'medals': {}, 'items': {}, 'characters': {}, 'lumina': {}, } elif msg_type == 'query': # Verify that the response is correct self.__verify_profile(resp) self.assert_path(resp, "response/player23/result") status = resp.child_value('player23/result') if status != 0: raise Exception( 'Reference ID \'{}\' returned invalid status \'{}\''. format(ref_id, status)) name = resp.child_value('player23/account/name') if name != self.NAME: raise Exception( 'Invalid name \'{}\' returned for Ref ID \'{}\''.format( name, ref_id)) # Medals and items items: Dict[int, Dict[str, int]] = {} medals: Dict[int, Dict[str, int]] = {} charas: Dict[int, Dict[str, int]] = {} for obj in resp.child('player23').children: if obj.name == 'medal': medals[obj.child_value('medal_id')] = { 'level': obj.child_value('level'), 'exp': obj.child_value('exp'), } elif obj.name == 'item': items[obj.child_value('id')] = { 'type': obj.child_value('type'), 'param': obj.child_value('param'), } elif obj.name == 'chara_param': charas[obj.child_value('chara_id')] = { 'friendship': obj.child_value('friendship'), } return { 'medals': medals, 'items': items, 'characters': charas, 'lumina': { 0: { 'lumina': resp.child_value('player23/account/lumina') } }, } else: raise Exception( 'Unrecognized message type \'{}\''.format(msg_type))
def handle_playerdata_request(self, request: Node) -> Optional[Node]: method = request.attribute('method') if method == 'expire': return Node.void('playerdata') elif method == 'logout': return Node.void('playerdata') elif method == 'get': modelstring = request.attribute('model') refid = request.child_value('ref_id') root = self.get_profile_by_refid( refid, self.NEW_PROFILE_ONLY if modelstring is None else self.OLD_PROFILE_ONLY, ) if root is None: root = Node.void('playerdata') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'conversion': refid = request.child_value('ref_id') name = request.child_value('name') chara = request.child_value('chara') root = self.new_profile_by_refid(refid, name, chara) if root is None: root = Node.void('playerdata') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'new': refid = request.child_value('ref_id') name = request.child_value('name') root = self.new_profile_by_refid(refid, name) if root is None: root = Node.void('playerdata') root.set_attribute('status', str(Status.NO_PROFILE)) return root elif method == 'set': refid = request.attribute('ref_id') root = Node.void('playerdata') root.add_child(Node.s8('pref', -1)) if refid is None: return root userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is None: return root oldprofile = self.get_profile(userid) or ValidatedDict() newprofile = self.unformat_profile(userid, request, oldprofile) if newprofile is not None: self.put_profile(userid, newprofile) root.add_child(Node.string('name', newprofile['name'])) return root # Invalid method return None
def handle_shopinfo_regist_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes self.update_machine_name(request.child_value('shop/name')) shopinfo = Node.void('shopinfo') data = Node.void('data') shopinfo.add_child(data) data.add_child(Node.u32('cabid', 1)) data.add_child(Node.string('locationid', 'nowhere')) data.add_child(Node.u8('is_send', 1)) data.add_child( Node.s32_array( 'white_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -16385, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child(Node.u8('tax_phase', 1)) lab = Node.void('lab') data.add_child(lab) lab.add_child(Node.bool('is_open', False)) vocaloid_event = Node.void('vocaloid_event') data.add_child(vocaloid_event) vocaloid_event.add_child(Node.u8('state', 0)) vocaloid_event.add_child(Node.s32('music_id', 0)) matching_off = Node.void('matching_off') data.add_child(matching_off) matching_off.add_child(Node.bool('is_open', True)) return shopinfo
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() achievements = self.data.local.user.get_achievements(self.game, self.version, userid) links = self.data.local.user.get_links(self.game, self.version, userid) root = Node.void('player') pdata = Node.void('pdata') root.add_child(pdata) # Account time info last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if ( last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2] ): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 # Previous account info previous_version = self.previous_version() if previous_version: succeeded = previous_version.has_profile(userid) else: succeeded = False # Account info account = Node.void('account') pdata.add_child(account) account.add_child(Node.s32('usrid', profile.get_int('extid'))) account.add_child(Node.s32('tpc', statistics.get_int('total_plays', 0))) account.add_child(Node.s32('dpc', today_count)) account.add_child(Node.s32('crd', 1)) account.add_child(Node.s32('brd', 1)) account.add_child(Node.s32('tdc', statistics.get_int('total_days', 0))) account.add_child(Node.s32('intrvld', 0)) account.add_child(Node.s16('ver', 0)) account.add_child(Node.u64('pst', 0)) account.add_child(Node.u64('st', Time.now() * 1000)) account.add_child(Node.bool('succeed', succeeded)) account.add_child(Node.s32('opc', 0)) account.add_child(Node.s32('lpc', 0)) account.add_child(Node.s32('cpc', 0)) # Base profile info base = Node.void('base') pdata.add_child(base) base.add_child(Node.string('name', profile.get_str('name'))) base.add_child(Node.s32('mg', profile.get_int('mg'))) base.add_child(Node.s32('ap', profile.get_int('ap'))) base.add_child(Node.string('cmnt', '')) base.add_child(Node.s32('uattr', profile.get_int('uattr'))) base.add_child(Node.s32('money', profile.get_int('money'))) base.add_child(Node.s32('tbs', -1)) base.add_child(Node.s32_array('tbgs', [-1, -1, -1, -1])) base.add_child(Node.s16_array('mlog', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) base.add_child(Node.s32('class', profile.get_int('class'))) base.add_child(Node.s32('class_ar', profile.get_int('class_ar'))) # Rivals rival = Node.void('rival') pdata.add_child(rival) slotid = 0 for link in links: if link.type != 'rival': continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue lobbyinfo = self.data.local.lobby.get_play_session_info(self.game, self.version, link.other_userid) if lobbyinfo is None: lobbyinfo = ValidatedDict() r = Node.void('r') rival.add_child(r) r.add_child(Node.s32('slot_id', slotid)) r.add_child(Node.s32('id', rprofile.get_int('extid'))) r.add_child(Node.string('name', rprofile.get_str('name'))) r.add_child(Node.s32('icon', rprofile.get_dict('config').get_int('icon_id'))) r.add_child(Node.s32('class', rprofile.get_int('class'))) r.add_child(Node.s32('class_ar', rprofile.get_int('class_ar'))) r.add_child(Node.bool('friend', True)) r.add_child(Node.bool('target', False)) r.add_child(Node.u32('time', lobbyinfo.get_int('time'))) r.add_child(Node.u8_array('ga', lobbyinfo.get_int_array('ga', 4))) r.add_child(Node.u16('gp', lobbyinfo.get_int('gp'))) r.add_child(Node.u8_array('ipn', lobbyinfo.get_int_array('la', 4))) r.add_child(Node.u8_array('pnid', lobbyinfo.get_int_array('pnid', 16))) slotid = slotid + 1 # Configuration configdict = profile.get_dict('config') config = Node.void('config') pdata.add_child(config) config.add_child(Node.u8('msel_bgm', configdict.get_int('msel_bgm'))) config.add_child(Node.u8('narrowdown_type', configdict.get_int('narrowdown_type'))) config.add_child(Node.s16('icon_id', configdict.get_int('icon_id'))) config.add_child(Node.s16('byword_0', configdict.get_int('byword_0'))) config.add_child(Node.s16('byword_1', configdict.get_int('byword_1'))) config.add_child(Node.bool('is_auto_byword_0', configdict.get_bool('is_auto_byword_0'))) config.add_child(Node.bool('is_auto_byword_1', configdict.get_bool('is_auto_byword_1'))) config.add_child(Node.u8('mrec_type', configdict.get_int('mrec_type'))) config.add_child(Node.u8('tab_sel', configdict.get_int('tab_sel'))) config.add_child(Node.u8('card_disp', configdict.get_int('card_disp'))) config.add_child(Node.u8('score_tab_disp', configdict.get_int('score_tab_disp'))) config.add_child(Node.s16('last_music_id', configdict.get_int('last_music_id', -1))) config.add_child(Node.u8('last_note_grade', configdict.get_int('last_note_grade'))) config.add_child(Node.u8('sort_type', configdict.get_int('sort_type'))) config.add_child(Node.u8('rival_panel_type', configdict.get_int('rival_panel_type'))) config.add_child(Node.u64('random_entry_work', configdict.get_int('random_entry_work'))) config.add_child(Node.u64('custom_folder_work', configdict.get_int('custom_folder_work'))) config.add_child(Node.u8('folder_type', configdict.get_int('folder_type'))) config.add_child(Node.u8('folder_lamp_type', configdict.get_int('folder_lamp_type'))) config.add_child(Node.bool('is_tweet', configdict.get_bool('is_tweet'))) config.add_child(Node.bool('is_link_twitter', configdict.get_bool('is_link_twitter'))) # Customizations customdict = profile.get_dict('custom') custom = Node.void('custom') pdata.add_child(custom) custom.add_child(Node.u8('st_shot', customdict.get_int('st_shot'))) custom.add_child(Node.u8('st_frame', customdict.get_int('st_frame'))) custom.add_child(Node.u8('st_expl', customdict.get_int('st_expl'))) custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) custom.add_child(Node.u8('st_shot_vol', customdict.get_int('st_shot_vol'))) custom.add_child(Node.u8('st_bg_bri', customdict.get_int('st_bg_bri'))) custom.add_child(Node.u8('st_obj_size', customdict.get_int('st_obj_size'))) custom.add_child(Node.u8('st_jr_gauge', customdict.get_int('st_jr_gauge'))) custom.add_child(Node.u8('st_clr_gauge', customdict.get_int('st_clr_gauge'))) custom.add_child(Node.u8('st_jdg_disp', customdict.get_int('st_jdg_disp'))) custom.add_child(Node.u8('st_rnd', customdict.get_int('st_rnd'))) custom.add_child(Node.u8('st_hazard', customdict.get_int('st_hazard'))) custom.add_child(Node.u8('st_clr_cond', customdict.get_int('st_clr_cond'))) custom.add_child(Node.u8('same_time_note_disp', customdict.get_int('same_time_note_disp'))) custom.add_child(Node.u8('st_gr_gauge_type', customdict.get_int('st_gr_gauge_type'))) custom.add_child(Node.s16('voice_message_set', customdict.get_int('voice_message_set', -1))) custom.add_child(Node.u8('voice_message_volume', customdict.get_int('voice_message_volume'))) # Unlocks released = Node.void('released') pdata.add_child(released) for item in achievements: if item.type[:5] != 'item_': continue itemtype = int(item.type[5:]) if game_config.get_bool('force_unlock_songs') and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue info = Node.void('info') released.add_child(info) info.add_child(Node.u8('type', itemtype)) info.add_child(Node.u16('id', item.id)) info.add_child(Node.u16('param', item.data.get_int('param'))) if game_config.get_bool('force_unlock_songs'): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs(self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 if song.data.get_int('difficulty') > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for songid in ids: if ids[songid] == 0: continue info = Node.void('info') released.add_child(info) info.add_child(Node.u8('type', 0)) info.add_child(Node.u16('id', songid)) info.add_child(Node.u16('param', ids[songid])) # Announcements announce = Node.void('announce') pdata.add_child(announce) for announcement in achievements: if announcement.type[:13] != 'announcement_': continue announcementtype = int(announcement.type[13:]) info = Node.void('info') announce.add_child(info) info.add_child(Node.u8('type', announcementtype)) info.add_child(Node.u16('id', announcement.id)) info.add_child(Node.u16('param', announcement.data.get_int('param'))) info.add_child(Node.bool('bneedannounce', announcement.data.get_bool('need'))) # Dojo ranking return dojo = Node.void('dojo') pdata.add_child(dojo) for entry in achievements: if entry.type != 'dojo': continue rec = Node.void('rec') dojo.add_child(rec) rec.add_child(Node.s32('class', entry.id)) rec.add_child(Node.s32('clear_type', entry.data.get_int('clear_type'))) rec.add_child(Node.s32('total_ar', entry.data.get_int('ar'))) rec.add_child(Node.s32('total_score', entry.data.get_int('score'))) rec.add_child(Node.s32('play_count', entry.data.get_int('plays'))) rec.add_child(Node.s32('last_play_time', entry.data.get_int('play_timestamp'))) rec.add_child(Node.s32('record_update_time', entry.data.get_int('record_timestamp'))) rec.add_child(Node.s32('rank', 0)) # Player Parameters player_param = Node.void('player_param') pdata.add_child(player_param) for param in achievements: if param.type[:13] != 'player_param_': continue itemtype = int(param.type[13:]) itemnode = Node.void('item') player_param.add_child(itemnode) itemnode.add_child(Node.s32('type', itemtype)) itemnode.add_child(Node.s32('bank', param.id)) itemnode.add_child(Node.s32_array('data', param.data.get_int_array('data', 256))) # Shop score for players self._add_shop_score(pdata) # My List data mylist = Node.void('mylist') pdata.add_child(mylist) listdata = Node.void('list') mylist.add_child(listdata) listdata.add_child(Node.s16('idx', 0)) listdata.add_child(Node.s16_array('mlst', profile.get_int_array('favorites', 30, [-1] * 30))) # Minigame settings minigame = Node.void('minigame') pdata.add_child(minigame) minigame.add_child(Node.s8('mgid', profile.get_int('mgid'))) minigame.add_child(Node.s32('sc', profile.get_int('mgsc'))) # Derby settings derby = Node.void('derby') pdata.add_child(derby) derby.add_child(Node.bool('is_open', False)) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('gametop') data = Node.void('data') root.add_child(data) player = Node.void('player') data.add_child(player) # Player info and statistics info = Node.void('info') player.add_child(info) info.add_child(Node.s16('jubility', profile.get_int('jubility'))) info.add_child( Node.s16('jubility_yday', profile.get_int('jubility_yday'))) info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) if 'total_best_score' in profile: info.add_child( Node.s32('total_best_score', profile.get_int('total_best_score'))) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. info.add_child( Node.bool('inherit', profile.get_bool('has_old_version'))) # Not saved, but loaded info.add_child(Node.s32('mtg_entry_cnt', 123)) info.add_child(Node.s32('mtg_hold_cnt', 456)) info.add_child(Node.u8('mtg_result', 10)) # Secret unlocks item = Node.void('item') player.add_child(item) item.add_child( Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list', 32, [-1] * 32, ), )) item.add_child( Node.s32_array( 'title_list', profile.get_int_array( 'title_list', 96, [-1] * 96, ), )) item.add_child( Node.s16('theme_list', profile.get_int('theme_list', -1))) item.add_child( Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) item.add_child( Node.s32_array('parts_list', profile.get_int_array('parts_list', 96, [-1] * 96))) new = Node.void('new') item.add_child(new) new.add_child( Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list_new', 32, [-1] * 32, ), )) new.add_child( Node.s32_array( 'title_list', profile.get_int_array( 'title_list_new', 96, [-1] * 96, ), )) new.add_child( Node.s16('theme_list', profile.get_int('theme_list_new', -1))) new.add_child( Node.s32_array( 'marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) # Last played data, for showing cursor and such lastdict = profile.get_dict('last') last = Node.void('last') player.add_child(last) last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) last.add_child(Node.s8('marker', lastdict.get_int('marker'))) last.add_child(Node.s16('title', lastdict.get_int('title'))) last.add_child(Node.s8('theme', lastdict.get_int('theme'))) last.add_child(Node.s8('sort', lastdict.get_int('sort'))) last.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) last.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) last.add_child(Node.s16('parts', lastdict.get_int('parts'))) last.add_child(Node.s8('category', lastdict.get_int('category'))) last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) # Miscelaneous crap player.add_child(Node.s32('session_id', 1)) # Maybe hook this up? Unsure what it does, is it like IIDX dailies? today_music = Node.void('today_music') player.add_child(today_music) today_music.add_child(Node.s32('music_id', 0)) # No news, ever. news = Node.void('news') player.add_child(news) news.add_child(Node.s16('checked', 0)) # Add rivals to profile. rivallist = Node.void('rivallist') player.add_child(rivallist) links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: if link.type != 'rival': continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue rival = Node.void('rival') rivallist.add_child(rival) rival.add_child(Node.s32('jid', rprofile.get_int('extid'))) rival.add_child(Node.string('name', rprofile.get_str('name'))) # Lazy way of keeping track of rivals, since we can only have 4 # or the game with throw up. At least, I think Fulfill can have # 4 instead of the 3 found in newer versions, given the size of # the array that it loads the values in. However, to keep things # simple, I only supported three here. rivalcount += 1 if rivalcount >= 3: break rivallist.set_attribute('count', str(rivalcount)) # Unclear what this is. Looks related to Jubeat lab. mylist = Node.void('mylist') player.add_child(mylist) mylist.set_attribute('count', '0') # No collaboration support yet. collabo = Node.void('collabo') player.add_child(collabo) collabo.add_child(Node.bool('success', False)) collabo.add_child(Node.bool('completed', False)) # Daily FC challenge. entry = self.data.local.game.get_time_sensitive_settings( self.game, self.version, 'fc_challenge') if entry is None: entry = ValidatedDict() # Figure out if we've played these songs start_time, end_time = self.data.local.network.get_schedule_duration( 'daily') today_attempts = self.data.local.music.get_all_attempts( self.game, self.version, userid, entry.get_int('today', -1), timelimit=start_time) challenge = Node.void('challenge') player.add_child(challenge) today = Node.void('today') challenge.add_child(today) today.add_child(Node.s32('music_id', entry.get_int('today', -1))) today.add_child( Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) onlynow = Node.void('onlynow') challenge.add_child(onlynow) onlynow.add_child(Node.s32('magic_no', 0)) onlynow.add_child(Node.s16('cycle', 0)) # Bistro event bistro = Node.void('bistro') player.add_child(bistro) # Presumably these can affect the speed of the event info_1 = Node.void('info') bistro.add_child(info_1) info_1.add_child(Node.float('delicious_rate', 1.0)) info_1.add_child(Node.float('favorite_rate', 1.0)) bistro.add_child( Node.s32('carry_over', profile.get_int('bistro_carry_over'))) # Your chef dude, I guess? chefdict = profile.get_dict('chef') chef = Node.void('chef') bistro.add_child(chef) chef.add_child(Node.s32('id', chefdict.get_int('id', 1))) chef.add_child(Node.u8('ability', chefdict.get_int('ability', 2))) chef.add_child(Node.u8('remain', chefdict.get_int('remain', 30))) chef.add_child(Node.u8('rate', chefdict.get_int('rate', 1))) # Routes, similar to story mode in Pop'n I guess? routes = [ { 'id': 50000284, 'price': 20, 'satisfaction': 10, 'favorite': True, }, { 'id': 50000283, 'price': 20, 'satisfaction': 20, 'favorite': False, }, { 'id': 50000282, 'price': 30, 'satisfaction': 10, 'favorite': False, }, { 'id': 50000275, 'price': 10, 'satisfaction': 55, 'favorite': False, }, { 'id': 50000274, 'price': 40, 'satisfaction': 40, 'favorite': False, }, { 'id': 50000273, 'price': 80, 'satisfaction': 60, 'favorite': False, }, { 'id': 50000272, 'price': 70, 'satisfaction': 60, 'favorite': False, }, { 'id': 50000271, 'price': 90, 'satisfaction': 80, 'favorite': False, }, { 'id': 50000270, 'price': 90, 'satisfaction': 20, 'favorite': False, }, ] for route_no in range(len(routes)): routedata = routes[route_no] route = Node.void('route') bistro.add_child(route) route.set_attribute('no', str(route_no)) music = Node.void('music') route.add_child(music) music.add_child(Node.s32('id', routedata['id'])) music.add_child(Node.u16('price', routedata['price'])) music.add_child(Node.s32('price_s32', routedata['price'])) # Look up any updated satisfaction stored by the game routesaved = self.data.local.user.get_achievement( self.game, self.version, userid, route_no + 1, 'route') if routesaved is None: routesaved = ValidatedDict() satisfaction = routesaved.get_int('satisfaction', routedata['satisfaction']) gourmates = Node.void('gourmates') route.add_child(gourmates) gourmates.add_child(Node.s32('id', route_no + 1)) gourmates.add_child( Node.u8('favorite', 1 if routedata['favorite'] else 0)) gourmates.add_child(Node.u16('satisfaction', satisfaction)) gourmates.add_child(Node.s32('satisfaction_s32', satisfaction)) # Sane defaults for unknown nodes only_now_music = Node.void('only_now_music') player.add_child(only_now_music) only_now_music.set_attribute('count', '0') requested_music = Node.void('requested_music') player.add_child(requested_music) requested_music.set_attribute('count', '0') kac_music = Node.void('kac_music') player.add_child(kac_music) kac_music.set_attribute('count', '0') history = Node.void('history') player.add_child(history) history.set_attribute('count', '0') # Basic profile info player.add_child(Node.string('name', profile.get_str('name', 'なし'))) player.add_child(Node.s32('jid', profile.get_int('extid'))) player.add_child(Node.string('refid', profile.get_str('refid'))) # Miscelaneous history stuff data.add_child(Node.u8('termver', 16)) data.add_child(Node.u32('season_etime', 0)) data.add_child(Node.s32('bistro_last_music_id', 0)) data.add_child( Node.s32_array( 'white_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child( Node.s32_array( 'old_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child( Node.s32_array( 'open_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) # Unsupported collaboration events with other games collabo_info = Node.void('collabo_info') data.add_child(collabo_info) # Unsupported marathon stuff run_run_marathon = Node.void('run_run_marathon') collabo_info.add_child(run_run_marathon) run_run_marathon.set_attribute('type', '1') run_run_marathon.add_child(Node.u8('state', 1)) run_run_marathon.add_child(Node.bool('is_report_end', True)) # Unsupported policy break stuff policy_break = Node.void('policy_break') collabo_info.add_child(policy_break) policy_break.set_attribute('type', '1') policy_break.add_child(Node.u8('state', 1)) policy_break.add_child(Node.bool('is_report_end', False)) # Unsupported vocaloid stuff vocaloid_event = Node.void('vocaloid_event') collabo_info.add_child(vocaloid_event) vocaloid_event.set_attribute('type', '1') vocaloid_event.add_child(Node.u8('state', 0)) vocaloid_event.add_child(Node.s32('music_id', 0)) # No obnoxious 30 second wait to play. matching_off = Node.void('matching_off') data.add_child(matching_off) matching_off.add_child(Node.bool('is_open', True)) return root
def verify_playerdata_usergamedata_advanced_usersave( self, refid: str, extid: int, locid: str, score: Dict[str, Any], scorepos: int = 0) -> None: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'usersave')) data.add_child(Node.string('name', self.NAME)) data.add_child(Node.s32('ddrcode', extid)) data.add_child(Node.s32('playside', 1)) data.add_child(Node.s32('playstyle', 0)) data.add_child(Node.s32('area', 58)) data.add_child(Node.s32('weight100', 0)) data.add_child(Node.string('shopname', 'gmw=')) data.add_child(Node.bool('ispremium', False)) data.add_child(Node.bool('iseapass', True)) data.add_child(Node.bool('istakeover', False)) data.add_child(Node.bool('isrepeater', False)) data.add_child(Node.bool('isgameover', scorepos < 0)) data.add_child(Node.string('locid', locid)) data.add_child(Node.string('shoparea', '.')) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) for i in range(5): if i == scorepos: # Fill in score here note = Node.void('note') data.add_child(note) note.add_child(Node.u8('stagenum', i + 1)) note.add_child(Node.u32('mcode', score['id'])) note.add_child(Node.u8('notetype', score['chart'])) note.add_child(Node.u8('rank', score['rank'])) note.add_child(Node.u8('clearkind', score['halo'])) note.add_child(Node.s32('score', score['score'])) note.add_child(Node.s32('exscore', 0)) note.add_child(Node.s32('maxcombo', 0)) note.add_child(Node.s32('life', 0)) note.add_child(Node.s32('fastcount', 0)) note.add_child(Node.s32('slowcount', 0)) note.add_child(Node.s32('judge_marvelous', 0)) note.add_child(Node.s32('judge_perfect', 0)) note.add_child(Node.s32('judge_great', 0)) note.add_child(Node.s32('judge_good', 0)) note.add_child(Node.s32('judge_boo', 0)) note.add_child(Node.s32('judge_miss', 0)) note.add_child(Node.s32('judge_ok', 0)) note.add_child(Node.s32('judge_ng', 0)) note.add_child(Node.s32('calorie', 0)) note.add_child(Node.s32('ghostsize', len(score['ghost']))) note.add_child(Node.string('ghost', score['ghost'])) note.add_child(Node.u8('opt_speed', 0)) note.add_child(Node.u8('opt_boost', 0)) note.add_child(Node.u8('opt_appearance', 0)) note.add_child(Node.u8('opt_turn', 0)) note.add_child(Node.u8('opt_dark', 0)) note.add_child(Node.u8('opt_scroll', 0)) note.add_child(Node.u8('opt_arrowcolor', 0)) note.add_child(Node.u8('opt_cut', 0)) note.add_child(Node.u8('opt_freeze', 0)) note.add_child(Node.u8('opt_jump', 0)) note.add_child(Node.u8('opt_arrowshape', 0)) note.add_child(Node.u8('opt_filter', 0)) note.add_child(Node.u8('opt_guideline', 0)) note.add_child(Node.u8('opt_gauge', 0)) note.add_child(Node.u8('opt_judgepriority', 0)) note.add_child(Node.u8('opt_timing', 0)) note.add_child(Node.string('basename', '')) note.add_child(Node.string('title_b64', '')) note.add_child(Node.string('artist_b64', '')) note.add_child(Node.u16('bpmMax', 0)) note.add_child(Node.u16('bpmMin', 0)) note.add_child(Node.u8('level', 0)) note.add_child(Node.u8('series', 0)) note.add_child(Node.u32('bemaniFlag', 0)) note.add_child(Node.u32('genreFlag', 0)) note.add_child(Node.u8('limited', 0)) note.add_child(Node.u8('region', 0)) note.add_child(Node.s32('gr_voltage', 0)) note.add_child(Node.s32('gr_stream', 0)) note.add_child(Node.s32('gr_chaos', 0)) note.add_child(Node.s32('gr_freeze', 0)) note.add_child(Node.s32('gr_air', 0)) note.add_child(Node.bool('share', False)) note.add_child(Node.u64('endtime', 0)) note.add_child(Node.s32('folder', 0)) else: note = Node.void('note') data.add_child(note) note.add_child(Node.u8('stagenum', 0)) note.add_child(Node.u32('mcode', 0)) note.add_child(Node.u8('notetype', 0)) note.add_child(Node.u8('rank', 0)) note.add_child(Node.u8('clearkind', 0)) note.add_child(Node.s32('score', 0)) note.add_child(Node.s32('exscore', 0)) note.add_child(Node.s32('maxcombo', 0)) note.add_child(Node.s32('life', 0)) note.add_child(Node.s32('fastcount', 0)) note.add_child(Node.s32('slowcount', 0)) note.add_child(Node.s32('judge_marvelous', 0)) note.add_child(Node.s32('judge_perfect', 0)) note.add_child(Node.s32('judge_great', 0)) note.add_child(Node.s32('judge_good', 0)) note.add_child(Node.s32('judge_boo', 0)) note.add_child(Node.s32('judge_miss', 0)) note.add_child(Node.s32('judge_ok', 0)) note.add_child(Node.s32('judge_ng', 0)) note.add_child(Node.s32('calorie', 0)) note.add_child(Node.s32('ghostsize', 0)) note.add_child(Node.string('ghost', '')) note.add_child(Node.u8('opt_speed', 0)) note.add_child(Node.u8('opt_boost', 0)) note.add_child(Node.u8('opt_appearance', 0)) note.add_child(Node.u8('opt_turn', 0)) note.add_child(Node.u8('opt_dark', 0)) note.add_child(Node.u8('opt_scroll', 0)) note.add_child(Node.u8('opt_arrowcolor', 0)) note.add_child(Node.u8('opt_cut', 0)) note.add_child(Node.u8('opt_freeze', 0)) note.add_child(Node.u8('opt_jump', 0)) note.add_child(Node.u8('opt_arrowshape', 0)) note.add_child(Node.u8('opt_filter', 0)) note.add_child(Node.u8('opt_guideline', 0)) note.add_child(Node.u8('opt_gauge', 0)) note.add_child(Node.u8('opt_judgepriority', 0)) note.add_child(Node.u8('opt_timing', 0)) note.add_child(Node.string('basename', '')) note.add_child(Node.string('title_b64', '')) note.add_child(Node.string('artist_b64', '')) note.add_child(Node.u16('bpmMax', 0)) note.add_child(Node.u16('bpmMin', 0)) note.add_child(Node.u8('level', 0)) note.add_child(Node.u8('series', 0)) note.add_child(Node.u32('bemaniFlag', 0)) note.add_child(Node.u32('genreFlag', 0)) note.add_child(Node.u8('limited', 0)) note.add_child(Node.u8('region', 0)) note.add_child(Node.s32('gr_voltage', 0)) note.add_child(Node.s32('gr_stream', 0)) note.add_child(Node.s32('gr_chaos', 0)) note.add_child(Node.s32('gr_freeze', 0)) note.add_child(Node.s32('gr_air', 0)) note.add_child(Node.bool('share', False)) note.add_child(Node.u64('endtime', 0)) note.add_child(Node.s32('folder', 0)) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/result")
def handle_event_r_get_all_request(self, request: Node) -> Node: limit = request.child_value('limit') comments = [ achievement for achievement in self.data.local.user. get_all_time_based_achievements(self.game, self.version) if achievement[1].type == 'puzzle_comment' ] comments.sort(key=lambda x: x[1].timestamp, reverse=True) statuses = self.data.local.lobby.get_all_play_session_infos( self.game, self.version) statuses.sort(key=lambda x: x[1]['time'], reverse=True) # Cap all comment blocks to the limit if limit >= 0: comments = comments[:limit] statuses = statuses[:limit] # Mapping of profiles to userIDs uid_mapping = { uid: prof for (uid, prof) in self.get_any_profiles([c[0] for c in comments] + [s[0] for s in statuses]) } # Mapping of location ID to machine name lid_mapping: Dict[int, str] = {} root = Node.void('event_r') root.add_child(Node.s32('time', Time.now())) statusnode = Node.void('status') root.add_child(statusnode) commentnode = Node.void('comment') root.add_child(commentnode) for (uid, comment) in comments: lid = ID.parse_machine_id(comment.data.get_str('lid')) # Look up external data for the request if lid not in lid_mapping: machine = self.get_machine_by_id(lid) if machine is not None: lid_mapping[lid] = machine.name else: lid_mapping[lid] = '' c = Node.void('c') commentnode.add_child(c) c.add_child(Node.s32('uid', uid_mapping[uid].get_int('extid'))) c.add_child(Node.string('p_name', uid_mapping[uid].get_str('name'))) c.add_child(Node.s32('exp', uid_mapping[uid].get_int('exp'))) c.add_child( Node.s32('customize', comment.data.get_int('customize'))) c.add_child(Node.s32('tid', comment.data.get_int('teamid'))) c.add_child(Node.string('t_name', comment.data.get_str('teamname'))) c.add_child(Node.string('lid', comment.data.get_str('lid'))) c.add_child(Node.string('s_name', lid_mapping[lid])) c.add_child(Node.s8('pref', comment.data.get_int('prefecture'))) c.add_child(Node.s32('time', comment.timestamp)) c.add_child(Node.string('comment', comment.data.get_str('comment'))) c.add_child(Node.bool('is_tweet', comment.data.get_bool('tweet'))) for (uid, status) in statuses: lid = ID.parse_machine_id(status.get_str('lid')) # Look up external data for the request if lid not in lid_mapping: machine = self.get_machine_by_id(lid) if machine is not None: lid_mapping[lid] = machine.name else: lid_mapping[lid] = '' s = Node.void('s') statusnode.add_child(s) s.add_child(Node.s32('uid', uid_mapping[uid].get_int('extid'))) s.add_child(Node.string('p_name', uid_mapping[uid].get_str('name'))) s.add_child(Node.s32('exp', uid_mapping[uid].get_int('exp'))) s.add_child(Node.s32('customize', status.get_int('customize'))) s.add_child( Node.s32('tid', uid_mapping[uid].get_int('team_id', -1))) s.add_child( Node.string('t_name', uid_mapping[uid].get_str('team_name', ''))) s.add_child(Node.string('lid', status.get_str('lid'))) s.add_child(Node.string('s_name', lid_mapping[lid])) s.add_child(Node.s8('pref', status.get_int('prefecture'))) s.add_child(Node.s32('time', status.get_int('time'))) s.add_child(Node.s8('status', status.get_int('status'))) s.add_child(Node.s8('stage', status.get_int('stage'))) s.add_child(Node.s32('mid', status.get_int('mid'))) s.add_child(Node.s8('ng', status.get_int('ng'))) return root
def verify_player22_read(self, ref_id: str, msg_type: str) -> Optional[Dict[str, Any]]: call = self.call_node() # Construct node player22 = Node.void('player22') call.add_child(player22) player22.set_attribute('method', 'read') player22.add_child(Node.string('ref_id', value=ref_id)) player22.add_child(Node.string('shop_name', '')) player22.add_child(Node.s8('pref', 51)) # Swap with server resp = self.exchange('', call) if msg_type == 'new': # Verify that response is correct self.assert_path(resp, "response/player22/@status") status = int(resp.child('player22').attribute('status')) if status != 109: raise Exception( 'Reference ID \'{}\' returned invalid status \'{}\''. format(ref_id, status)) # No score data return None elif msg_type == 'query': # Verify that the response is correct self.assert_path(resp, "response/player22/account/name") self.assert_path(resp, "response/player22/account/g_pm_id") self.assert_path(resp, "response/player22/account/my_best") self.assert_path(resp, "response/player22/account/latest_music") self.assert_path(resp, "response/player22/netvs") self.assert_path(resp, "response/player22/config") self.assert_path(resp, "response/player22/option") self.assert_path(resp, "response/player22/info") self.assert_path(resp, "response/player22/custom_cate") self.assert_path(resp, "response/player22/customize") name = resp.child('player22').child('account').child('name').value if name != self.NAME: raise Exception( 'Invalid name \'{}\' returned for Ref ID \'{}\''.format( name, ref_id)) # Extract and return score data medals: Dict[int, List[int]] = {} scores: Dict[int, List[int]] = {} for child in resp.child('player22').children: if child.name == 'music': songid = child.child_value('music_num') chart = child.child_value('sheet_num') medal = child.child_value('clear_type') points = child.child_value('score') if songid not in medals: medals[songid] = [0, 0, 0, 0] medals[songid][chart] = medal if songid not in scores: scores[songid] = [0, 0, 0, 0] scores[songid][chart] = points return {'medals': medals, 'scores': scores} else: raise Exception( 'Unrecognized message type \'{}\''.format(msg_type))
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: statistics = self.get_play_statistics(userid) game_config = self.get_game_config() achievements = self.data.local.user.get_achievements( self.game, self.version, userid) scores = self.data.remote.music.get_scores(self.game, self.version, userid) links = self.data.local.user.get_links(self.game, self.version, userid) root = Node.void('player') pdata = Node.void('pdata') root.add_child(pdata) base = Node.void('base') pdata.add_child(base) base.add_child(Node.s32('uid', profile.get_int('extid'))) base.add_child(Node.string('name', profile.get_str('name'))) base.add_child(Node.s16('icon_id', profile.get_int('icon'))) base.add_child(Node.s16('lv', profile.get_int('lvl'))) base.add_child(Node.s32('exp', profile.get_int('exp'))) base.add_child(Node.s16('mg', profile.get_int('mg'))) base.add_child(Node.s16('ap', profile.get_int('ap'))) base.add_child(Node.s32('pc', profile.get_int('pc'))) base.add_child(Node.s32('uattr', profile.get_int('uattr'))) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 con = Node.void('con') pdata.add_child(con) con.add_child(Node.s32('day', today_count)) con.add_child(Node.s32('cnt', statistics.get_int('total_plays'))) con.add_child(Node.s32('total_cnt', statistics.get_int('total_plays'))) con.add_child( Node.s32('last', statistics.get_int('last_play_timestamp'))) con.add_child(Node.s32('now', Time.now())) team = Node.void('team') pdata.add_child(team) team.add_child(Node.s32('id', profile.get_int('team_id', -1))) team.add_child(Node.string('name', profile.get_str('team_name', ''))) custom = Node.void('custom') customdict = profile.get_dict('custom') pdata.add_child(custom) custom.add_child(Node.u8('s_gls', customdict.get_int('s_gls'))) custom.add_child(Node.u8('bgm_m', customdict.get_int('bgm_m'))) custom.add_child(Node.u8('st_f', customdict.get_int('st_f'))) custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg'))) custom.add_child(Node.u8('st_bg_b', customdict.get_int('st_bg_b'))) custom.add_child(Node.u8('eff_e', customdict.get_int('eff_e'))) custom.add_child(Node.u8('se_s', customdict.get_int('se_s'))) custom.add_child(Node.u8('se_s_v', customdict.get_int('se_s_v'))) custom.add_child( Node.s16('last_music_id', customdict.get_int('last_music_id'))) custom.add_child( Node.u8('last_note_grade', customdict.get_int('last_note_grade'))) custom.add_child(Node.u8('sort_type', customdict.get_int('sort_type'))) custom.add_child( Node.u8('narrowdown_type', customdict.get_int('narrowdown_type'))) custom.add_child( Node.bool('is_begginer', customdict.get_bool( 'is_begginer'))) # Yes, this is spelled right custom.add_child(Node.bool('is_tut', customdict.get_bool('is_tut'))) custom.add_child( Node.s16_array('symbol_chat_0', customdict.get_int_array('symbol_chat_0', 6))) custom.add_child( Node.s16_array('symbol_chat_1', customdict.get_int_array('symbol_chat_1', 6))) custom.add_child( Node.u8('gauge_style', customdict.get_int('gauge_style'))) custom.add_child(Node.u8('obj_shade', customdict.get_int('obj_shade'))) custom.add_child(Node.u8('obj_size', customdict.get_int('obj_size'))) custom.add_child( Node.s16_array('byword', customdict.get_int_array('byword', 2))) custom.add_child( Node.bool_array('is_auto_byword', customdict.get_bool_array('is_auto_byword', 2))) custom.add_child(Node.bool('is_tweet', customdict.get_bool('is_tweet'))) custom.add_child( Node.bool('is_link_twitter', customdict.get_bool('is_link_twitter'))) custom.add_child(Node.s16('mrec_type', customdict.get_int('mrec_type'))) custom.add_child( Node.s16('card_disp_type', customdict.get_int('card_disp_type'))) custom.add_child(Node.s16('tab_sel', customdict.get_int('tab_sel'))) custom.add_child( Node.s32_array('hidden_param', customdict.get_int_array('hidden_param', 20))) released = Node.void('released') pdata.add_child(released) for item in achievements: if item.type[:5] != 'item_': continue itemtype = int(item.type[5:]) if game_config.get_bool('force_unlock_songs') and itemtype == 0: # Don't echo unlocks when we're force unlocking, we'll do it later continue info = Node.void('info') released.add_child(info) info.add_child(Node.u8('type', itemtype)) info.add_child(Node.u16('id', item.id)) info.add_child(Node.u16('param', item.data.get_int('param'))) if game_config.get_bool('force_unlock_songs'): ids: Dict[int, int] = {} songs = self.data.local.music.get_all_songs( self.game, self.version) for song in songs: if song.id not in ids: ids[song.id] = 0 if song.data.get_int('difficulty') > 0: ids[song.id] = ids[song.id] | (1 << song.chart) for songid in ids: if ids[songid] == 0: continue info = Node.void('info') released.add_child(info) info.add_child(Node.u8('type', 0)) info.add_child(Node.u16('id', songid)) info.add_child(Node.u16('param', ids[songid])) # Scores record = Node.void('record') pdata.add_child(record) for score in scores: rec = Node.void('rec') record.add_child(rec) rec.add_child(Node.u16('mid', score.id)) rec.add_child(Node.u8('ng', score.chart)) rec.add_child( Node.s32( 'point', score.data.get_dict('stats').get_int('earned_points'))) rec.add_child(Node.s32('played_time', score.timestamp)) mrec_0 = Node.void('mrec_0') rec.add_child(mrec_0) mrec_0.add_child( Node.s32('win', score.data.get_dict('stats').get_int('win'))) mrec_0.add_child( Node.s32('lose', score.data.get_dict('stats').get_int('lose'))) mrec_0.add_child( Node.s32('draw', score.data.get_dict('stats').get_int('draw'))) mrec_0.add_child( Node.u8( 'ct', self.__db_to_game_clear_type( score.data.get_int('clear_type'), score.data.get_int('combo_type')))) mrec_0.add_child( Node.s16('ar', int(score.data.get_int('achievement_rate') / 10))) mrec_0.add_child(Node.s32('bs', score.points)) mrec_0.add_child(Node.s16('mc', score.data.get_int('combo'))) mrec_0.add_child(Node.s16('bmc', score.data.get_int('miss_count'))) mrec_1 = Node.void('mrec_1') rec.add_child(mrec_1) mrec_1.add_child(Node.s32('win', 0)) mrec_1.add_child(Node.s32('lose', 0)) mrec_1.add_child(Node.s32('draw', 0)) mrec_1.add_child(Node.u8('ct', 0)) mrec_1.add_child(Node.s16('ar', 0)) mrec_1.add_child(Node.s32('bs', 0)) mrec_1.add_child(Node.s16('mc', 0)) mrec_1.add_child(Node.s16('bmc', -1)) # Comment (seems unused?) pdata.add_child(Node.string('cmnt', '')) # Rivals rival = Node.void('rival') pdata.add_child(rival) slotid = 0 for link in links: if link.type != 'rival': continue rprofile = self.get_profile(link.other_userid) if rprofile is None: continue r = Node.void('r') rival.add_child(r) r.add_child(Node.u8('slot_id', slotid)) r.add_child(Node.string('name', rprofile.get_str('name'))) r.add_child(Node.s32('id', rprofile.get_int('extid'))) r.add_child(Node.bool('friend', True)) r.add_child(Node.bool('locked', False)) r.add_child(Node.s32('rc', 0)) slotid = slotid + 1 # Glass points glass = Node.void('glass') pdata.add_child(glass) for item in achievements: if item.type != 'glass': continue g = Node.void('g') glass.add_child(g) g.add_child(Node.s32('id', item.id)) g.add_child(Node.s32('exp', item.data.get_int('exp'))) # Favorite music fav_music_slot = Node.void('fav_music_slot') pdata.add_child(fav_music_slot) for item in achievements: if item.type != 'music': continue slot = Node.void('slot') fav_music_slot.add_child(slot) slot.add_child(Node.u8('slot_id', item.id)) slot.add_child(Node.s16('music_id', item.data.get_int('music_id'))) narrow_down = Node.void('narrow_down') pdata.add_child(narrow_down) narrow_down.add_child( Node.s32_array('adv_param', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ])) return root
def verify_playerdata_get(self, ref_id: str, msg_type: str) -> Optional[Dict[str, Any]]: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'get') if msg_type == 'new': playerdata.set_attribute( 'model', self.config['old_profile_model'].split(':')[0]) playerdata.add_child(Node.string('ref_id', ref_id)) playerdata.add_child(Node.string('shop_name', '')) playerdata.add_child(Node.s8('pref', 50)) playerdata.add_child(Node.s32('navigate', 1)) # Swap with server resp = self.exchange('pnm20/playerdata', call) if msg_type == 'new': # Verify that response is correct self.assert_path(resp, "response/playerdata/@status") status = int(resp.child('playerdata').attribute('status')) if status != 109: raise Exception( f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'' ) # No score data return None elif msg_type == 'query': # Verify that the response is correct self.assert_path(resp, "response/playerdata/base/g_pm_id") self.assert_path(resp, "response/playerdata/base/name") self.assert_path(resp, "response/playerdata/base/my_best") self.assert_path(resp, "response/playerdata/base/latest_music") self.assert_path(resp, "response/playerdata/base/clear_medal") self.assert_path(resp, "response/playerdata/base/clear_medal_sub") self.assert_path(resp, "response/playerdata/player_card") self.assert_path(resp, "response/playerdata/player_card_ex") self.assert_path(resp, "response/playerdata/hiscore") self.assert_path(resp, "response/playerdata/netvs") self.assert_path(resp, "response/playerdata/sp_data") self.assert_path(resp, "response/playerdata/reflec_data") self.assert_path(resp, "response/playerdata/navigate") name = resp.child('playerdata').child('base').child('name').value if name != self.NAME: raise Exception( f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'' ) # Extract and return score data self.assert_path(resp, "response/playerdata/base/clear_medal") def transform_medals(medal: int) -> Tuple[int, int, int, int]: return ( (medal >> 0) & 0xF, (medal >> 4) & 0xF, (medal >> 8) & 0xF, (medal >> 12) & 0xF, ) medals = [ transform_medals(medal) for medal in resp.child( 'playerdata').child('base').child('clear_medal').value ] hiscore = resp.child('playerdata').child('hiscore').value hiscores = [] for i in range(0, len(hiscore) * 8, 17): byte_offset = int(i / 8) bit_offset = int(i % 8) value = hiscore[byte_offset] value = value + (hiscore[byte_offset + 1] << 8) value = value + (hiscore[byte_offset + 2] << 16) value = value >> bit_offset hiscores.append(value & 0x1FFFF) scores = [(hiscores[x], hiscores[x + 1], hiscores[x + 2], hiscores[x + 3]) for x in range(0, len(hiscores), 4)] return {'medals': medals, 'scores': scores} else: raise Exception(f'Unrecognized message type \'{msg_type}\'')
def handle_gametop_get_course_request(self, request: Node) -> Node: data = request.child('data') player = data.child('player') extid = player.child_value('jid') gametop = Node.void('gametop') data = Node.void('data') gametop.add_child(data) # Course list available course_list = Node.void('course_list') data.add_child(course_list) validcourses: List[int] = [] for course in self.get_all_courses(): coursenode = Node.void('course') course_list.add_child(coursenode) # Basic course info if course['id'] in validcourses: raise Exception('Cannot have same course ID specified twice!') validcourses.append(course['id']) coursenode.add_child(Node.s32('id', course['id'])) coursenode.add_child(Node.string('name', course['name'])) coursenode.add_child(Node.u8('level', course['level'])) # Translate internal to game def translate_req(internal_req: int) -> int: return { self.COURSE_REQUIREMENT_SCORE: self.GAME_COURSE_REQUIREMENT_SCORE, self.COURSE_REQUIREMENT_FULL_COMBO: self.GAME_COURSE_REQUIREMENT_FULL_COMBO, self.COURSE_REQUIREMENT_PERFECT_PERCENT: self.GAME_COURSE_REQUIREMENT_PERFECT_PERCENT, }.get(internal_req, 0) # Course bronze/silver/gold rules ids = [0] * 3 bronze_values = [0] * 3 silver_values = [0] * 3 gold_values = [0] * 3 slot = 0 for req in course['requirements']: req_values = course['requirements'][req] ids[slot] = translate_req(req) bronze_values[slot] = req_values[0] silver_values[slot] = req_values[1] gold_values[slot] = req_values[2] slot = slot + 1 norma = Node.void('norma') coursenode.add_child(norma) norma.add_child(Node.s32_array('norma_id', ids)) norma.add_child(Node.s32_array('bronze_value', bronze_values)) norma.add_child(Node.s32_array('silver_value', silver_values)) norma.add_child(Node.s32_array('gold_value', gold_values)) # Music list for course music_index = 0 music_list = Node.void('music_list') coursenode.add_child(music_list) for entry in course['music']: music = Node.void('music') music.set_attribute('index', str(music_index)) music_list.add_child(music) music.add_child(Node.s32('music_id', entry[0])) music.add_child(Node.u8('seq', entry[1])) music_index = music_index + 1 # Look up profile so we can load the last course played userid = self.data.remote.user.from_extid(self.game, self.version, extid) profile = self.get_profile(userid) if profile is None: profile = ValidatedDict() # Player scores for courses player_list = Node.void('player_list') data.add_child(player_list) player = Node.void('player') player_list.add_child(player) player.add_child(Node.s32('jid', extid)) result_list = Node.void('result_list') player.add_child(result_list) playercourses = self.get_courses(userid) for courseid in playercourses: if courseid not in validcourses: continue rating = { self.COURSE_RATING_FAILED: self.GAME_COURSE_RATING_FAILED, self.COURSE_RATING_BRONZE: self.GAME_COURSE_RATING_BRONZE, self.COURSE_RATING_SILVER: self.GAME_COURSE_RATING_SILVER, self.COURSE_RATING_GOLD: self.GAME_COURSE_RATING_GOLD, }[playercourses[courseid]['rating']] scores = playercourses[courseid]['scores'] result = Node.void('result') result_list.add_child(result) result.add_child(Node.s32('id', courseid)) result.add_child(Node.u8('rating', rating)) result.add_child(Node.s32_array('score', scores)) # Last course ID last_course_id = Node.s32('last_course_id', profile.get_dict('last').get_int('last_course_id', -1)) data.add_child(last_course_id) return gametop
def format_conversion(self, userid: UserID, profile: ValidatedDict) -> Node: # Circular import, ugh from bemani.backend.popn.eclale import PopnMusicEclale root = Node.void('player23') root.add_child(Node.string('name', profile.get_str('name', 'なし'))) root.add_child(Node.s16('chara', profile.get_int('chara', -1))) root.add_child(Node.s8('result', 1)) scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) for score in scores: if score.id > self.GAME_MAX_MUSIC_ID: continue # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points medal = score.data.get_int('medal') music = Node.void('music') root.add_child(music) music.add_child(Node.s16('music_num', score.id)) music.add_child( Node.u8( 'sheet_num', { self.CHART_TYPE_EASY: PopnMusicEclale.GAME_CHART_TYPE_EASY, self.CHART_TYPE_NORMAL: PopnMusicEclale.GAME_CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER: PopnMusicEclale.GAME_CHART_TYPE_HYPER, self.CHART_TYPE_EX: PopnMusicEclale.GAME_CHART_TYPE_EX, }[score.chart])) music.add_child(Node.s32('score', points)) music.add_child( Node.u8( 'clear_type', { self.PLAY_MEDAL_CIRCLE_FAILED: PopnMusicEclale.GAME_PLAY_MEDAL_CIRCLE_FAILED, self.PLAY_MEDAL_DIAMOND_FAILED: PopnMusicEclale.GAME_PLAY_MEDAL_DIAMOND_FAILED, self.PLAY_MEDAL_STAR_FAILED: PopnMusicEclale.GAME_PLAY_MEDAL_STAR_FAILED, self.PLAY_MEDAL_EASY_CLEAR: PopnMusicEclale.GAME_PLAY_MEDAL_EASY_CLEAR, self.PLAY_MEDAL_CIRCLE_CLEARED: PopnMusicEclale.GAME_PLAY_MEDAL_CIRCLE_CLEARED, self.PLAY_MEDAL_DIAMOND_CLEARED: PopnMusicEclale.GAME_PLAY_MEDAL_DIAMOND_CLEARED, self.PLAY_MEDAL_STAR_CLEARED: PopnMusicEclale.GAME_PLAY_MEDAL_STAR_CLEARED, self.PLAY_MEDAL_CIRCLE_FULL_COMBO: PopnMusicEclale.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, self.PLAY_MEDAL_DIAMOND_FULL_COMBO: PopnMusicEclale.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: PopnMusicEclale.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: PopnMusicEclale.GAME_PLAY_MEDAL_PERFECT, }[medal])) music.add_child(Node.s16('cnt', score.plays)) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('gametop') data = Node.void('data') root.add_child(data) player = Node.void('player') data.add_child(player) # Player info and statistics info = Node.void('info') player.add_child(info) info.add_child(Node.s16('jubility', profile.get_int('jubility'))) info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) info.add_child(Node.s32('extra_point', profile.get_int('extra_point'))) info.add_child(Node.bool('is_extra_played', profile.get_bool('is_extra_played'))) if 'total_best_score' in profile: info.add_child(Node.s32('total_best_score', profile.get_int('total_best_score'))) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. info.add_child(Node.bool('inherit', profile.get_bool('has_old_version'))) # Not saved, but loaded info.add_child(Node.s32('mtg_entry_cnt', 123)) info.add_child(Node.s32('mtg_hold_cnt', 456)) info.add_child(Node.u8('mtg_result', 10)) # First play stuff we don't support free_first_play = Node.void('free_first_play') player.add_child(free_first_play) free_first_play.add_child(Node.bool('is_available', False)) free_first_play.add_child(Node.s32('point', 0)) free_first_play.add_child(Node.s32('point_used', 0)) # Secret unlocks item = Node.void('item') player.add_child(item) item.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list', 32, [-1] * 32, ), )) item.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list', 96, [-1] * 96, ), )) item.add_child(Node.s16('theme_list', profile.get_int('theme_list', -1))) item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 96, [-1] * 96))) new = Node.void('new') item.add_child(new) new.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list_new', 32, [-1] * 32, ), )) new.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list_new', 96, [-1] * 96, ), )) new.add_child(Node.s16('theme_list', profile.get_int('theme_list_new', -1))) new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) # Last played data, for showing cursor and such lastdict = profile.get_dict('last') last = Node.void('last') player.add_child(last) last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) last.add_child(Node.s8('marker', lastdict.get_int('marker'))) last.add_child(Node.s16('title', lastdict.get_int('title'))) last.add_child(Node.s8('theme', lastdict.get_int('theme'))) last.add_child(Node.s8('sort', lastdict.get_int('sort'))) last.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) last.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) last.add_child(Node.s16('parts', lastdict.get_int('parts'))) last.add_child(Node.s8('category', lastdict.get_int('category'))) last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option'))) last.add_child(Node.s8('matching', lastdict.get_int('matching'))) last.add_child(Node.s8('hazard', lastdict.get_int('hazard'))) last.add_child(Node.s8('hard', lastdict.get_int('hard'))) # Miscelaneous crap player.add_child(Node.s32('session_id', 1)) player.add_child(Node.u64('event_flag', 0)) # Macchiato event macchiatodict = profile.get_dict('macchiato') macchiato = Node.void('macchiato') player.add_child(macchiato) macchiato.add_child(Node.s32('pack_id', macchiatodict.get_int('pack_id'))) macchiato.add_child(Node.u16('bean_num', macchiatodict.get_int('bean_num'))) macchiato.add_child(Node.s32('daily_milk_num', macchiatodict.get_int('daily_milk_num'))) macchiato.add_child(Node.bool('is_received_daily_milk', macchiatodict.get_bool('is_received_daily_milk'))) macchiato.add_child(Node.s32('today_tune_cnt', macchiatodict.get_int('today_tune_cnt'))) macchiato.add_child(Node.s32_array( 'daily_milk_bonus', macchiatodict.get_int_array('daily_milk_bonus', 9, [-1, -1, -1, -1, -1, -1, -1, -1, -1]), )) macchiato.add_child(Node.s32('daily_play_burst', macchiatodict.get_int('daily_play_burst'))) macchiato.add_child(Node.bool('sub_menu_is_completed', macchiatodict.get_bool('sub_menu_is_completed'))) macchiato.add_child(Node.s32('compensation_milk', macchiatodict.get_int('compensation_milk'))) macchiato.add_child(Node.s32('match_cnt', macchiatodict.get_int('match_cnt'))) # Probably never will support this macchiato_music_list = Node.void('macchiato_music_list') macchiato.add_child(macchiato_music_list) macchiato_music_list.set_attribute('count', '0') # Same with this comment macchiato.add_child(Node.s32('sub_pack_id', 0)) sub_macchiato_music_list = Node.void('sub_macchiato_music_list') macchiato.add_child(sub_macchiato_music_list) sub_macchiato_music_list.set_attribute('count', '0') # And this season_music_list = Node.void('season_music_list') macchiato.add_child(season_music_list) season_music_list.set_attribute('count', '0') # Weird, this is sent as a void with a bunch of subnodes, but returned as an int array. achievement_list = Node.void('achievement_list') macchiato.add_child(achievement_list) achievement_list.set_attribute('count', '0') # Also probably never supporting this either cow_list = Node.void('cow_list') macchiato.add_child(cow_list) cow_list.set_attribute('count', '0') # No news, ever. news = Node.void('news') player.add_child(news) news.add_child(Node.s16('checked', 0)) # No rival support, yet. rivallist = Node.void('rivallist') player.add_child(rivallist) rivallist.set_attribute('count', '0') # Full combo daily challenge. entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') if entry is None: entry = ValidatedDict() # Figure out if we've played these songs start_time, end_time = self.data.local.network.get_schedule_duration('daily') today_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('today', -1), timelimit=start_time) whim_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('whim', -1), timelimit=start_time) challenge = Node.void('challenge') player.add_child(challenge) today = Node.void('today') challenge.add_child(today) today.add_child(Node.s32('music_id', entry.get_int('today', -1))) today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) whim = Node.void('whim') challenge.add_child(whim) whim.add_child(Node.s32('music_id', entry.get_int('whim', -1))) whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0)) # Sane defaults for unknown nodes only_now_music = Node.void('only_now_music') player.add_child(only_now_music) only_now_music.set_attribute('count', '0') lab_edit_seq = Node.void('lab_edit_seq') player.add_child(lab_edit_seq) lab_edit_seq.set_attribute('count', '0') kac_music = Node.void('kac_music') player.add_child(kac_music) kac_music.set_attribute('count', '0') history = Node.void('history') player.add_child(history) history.set_attribute('count', '0') share_music = Node.void('share_music') player.add_child(share_music) share_music.set_attribute('count', '0') bonus_music = Node.void('bonus_music') player.add_child(bonus_music) bonus_music.set_attribute('count', '0') bingo = Node.void('bingo') player.add_child(bingo) reward = Node.void('reward') bingo.add_child(reward) reward.add_child(Node.s32('total', 0)) reward.add_child(Node.s32('point', 0)) group = Node.void('group') player.add_child(group) group.add_child(Node.s32('group_id', 0)) # Basic profile info player.add_child(Node.string('name', profile.get_str('name', 'なし'))) player.add_child(Node.s32('jid', profile.get_int('extid'))) player.add_child(Node.string('refid', profile.get_str('refid'))) # Miscelaneous history stuff data.add_child(Node.u8('termver', 16)) data.add_child(Node.u32('season_etime', 0)) data.add_child(Node.s32_array( 'white_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child(Node.s32_array( 'open_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) # Unsupported collaboration events with other games collabo_info = Node.void('collabo_info') data.add_child(collabo_info) # Unsupported policy break stuff policy_break = Node.void('policy_break') collabo_info.add_child(policy_break) policy_break.set_attribute('type', '1') policy_break.add_child(Node.u8('state', 1)) policy_break.add_child(Node.bool('is_report_end', False)) # Unsupported vocaloid stuff vocaloid_event = Node.void('vocaloid_event') collabo_info.add_child(vocaloid_event) vocaloid_event.set_attribute('type', '1') vocaloid_event.add_child(Node.u8('state', 0)) vocaloid_event.add_child(Node.s32('music_id', 0)) # Unsupported vocaloid stuff vocaloid_event2 = Node.void('vocaloid_event2') collabo_info.add_child(vocaloid_event2) vocaloid_event2.set_attribute('type', '1') vocaloid_event2.add_child(Node.u8('state', 0)) vocaloid_event2.add_child(Node.s32('music_id', 0)) # Maybe it is possible to turn off internet matching here? lab = Node.void('lab') data.add_child(lab) lab.add_child(Node.bool('is_open', False)) matching_off = Node.void('matching_off') data.add_child(matching_off) matching_off.add_child(Node.bool('is_open', True)) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('player22') # Result root.add_child(Node.s8('result', 0)) # Set up account account = Node.void('account') root.add_child(account) account.add_child(Node.string('name', profile.get_str('name', 'なし'))) account.add_child( Node.string('g_pm_id', ID.format_extid(profile.get_int('extid')))) account.add_child(Node.s8('tutorial', profile.get_int('tutorial', -1))) account.add_child( Node.s16('read_news', profile.get_int('read_news', 0))) account.add_child(Node.s8('staff', 0)) account.add_child(Node.s8('is_conv', 0)) account.add_child(Node.s16('item_type', 0)) account.add_child(Node.s16('item_id', 0)) account.add_child( Node.s16_array('license_data', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1])) # Statistics section and scores section statistics = self.get_play_statistics(userid) last_play_date = statistics.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if (last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2]): today_count = statistics.get_int('today_plays', 0) else: today_count = 0 account.add_child( Node.s16('total_play_cnt', statistics.get_int('total_plays', 0))) account.add_child(Node.s16('today_play_cnt', today_count)) account.add_child( Node.s16('consecutive_days', statistics.get_int('consecutive_days', 0))) account.add_child( Node.s16('total_days', statistics.get_int('total_days', 0))) account.add_child(Node.s16('interval_day', 0)) # Number of rivals that are active for this version. links = self.data.local.user.get_links(self.game, self.version, userid) rivalcount = 0 for link in links: if link.type != 'rival': continue if not self.has_profile(link.other_userid): continue # This profile is valid. rivalcount += 1 account.add_child(Node.u8('active_fr_num', rivalcount)) # Add scores section last_played = [ x[0] for x in self.data.local.music.get_last_played( self.game, self.version, userid, 5) ] most_played = [ x[0] for x in self.data.local.music.get_most_played( self.game, self.version, userid, 10) ] while len(last_played) < 5: last_played.append(-1) while len(most_played) < 10: most_played.append(-1) account.add_child(Node.s16_array('my_best', most_played)) account.add_child(Node.s16_array('latest_music', last_played)) scores = self.data.remote.music.get_scores(self.game, self.version, userid) for score in scores: # Skip any scores for chart types we don't support if score.chart not in [ self.CHART_TYPE_EASY, self.CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER, self.CHART_TYPE_EX, ]: continue points = score.points medal = score.data.get_int('medal') music = Node.void('music') root.add_child(music) music.add_child(Node.s16('music_num', score.id)) music.add_child( Node.u8( 'sheet_num', { self.CHART_TYPE_EASY: self.GAME_CHART_TYPE_EASY, self.CHART_TYPE_NORMAL: self.GAME_CHART_TYPE_NORMAL, self.CHART_TYPE_HYPER: self.GAME_CHART_TYPE_HYPER, self.CHART_TYPE_EX: self.GAME_CHART_TYPE_EX, }[score.chart])) music.add_child(Node.s16('cnt', score.plays)) music.add_child(Node.s32('score', points)) music.add_child( Node.u8( 'clear_type', { self.PLAY_MEDAL_CIRCLE_FAILED: self.GAME_PLAY_MEDAL_CIRCLE_FAILED, self.PLAY_MEDAL_DIAMOND_FAILED: self.GAME_PLAY_MEDAL_DIAMOND_FAILED, self.PLAY_MEDAL_STAR_FAILED: self.GAME_PLAY_MEDAL_STAR_FAILED, self.PLAY_MEDAL_EASY_CLEAR: self.GAME_PLAY_MEDAL_EASY_CLEAR, self.PLAY_MEDAL_CIRCLE_CLEARED: self.GAME_PLAY_MEDAL_CIRCLE_CLEARED, self.PLAY_MEDAL_DIAMOND_CLEARED: self.GAME_PLAY_MEDAL_DIAMOND_CLEARED, self.PLAY_MEDAL_STAR_CLEARED: self.GAME_PLAY_MEDAL_STAR_CLEARED, self.PLAY_MEDAL_CIRCLE_FULL_COMBO: self.GAME_PLAY_MEDAL_CIRCLE_FULL_COMBO, self.PLAY_MEDAL_DIAMOND_FULL_COMBO: self.GAME_PLAY_MEDAL_DIAMOND_FULL_COMBO, self.PLAY_MEDAL_STAR_FULL_COMBO: self.GAME_PLAY_MEDAL_STAR_FULL_COMBO, self.PLAY_MEDAL_PERFECT: self.GAME_PLAY_MEDAL_PERFECT, }[medal])) music.add_child(Node.s32('old_score', 0)) music.add_child(Node.u8('old_clear_type', 0)) # Net VS section netvs = Node.void('netvs') root.add_child(netvs) netvs.add_child(Node.s32('rank_point', 0)) netvs.add_child(Node.s16_array('record', [0, 0, 0, 0, 0, 0])) netvs.add_child(Node.u8('rank', 0)) netvs.add_child(Node.s8('vs_rank_old', 0)) netvs.add_child(Node.s8_array('ojama_condition', [0] * 74)) netvs.add_child(Node.s8_array('set_ojama', [0, 0, 0])) netvs.add_child(Node.s8_array('set_recommend', [0, 0, 0])) netvs.add_child(Node.u32('netvs_play_cnt', 0)) for dialog in [0, 1, 2, 3, 4, 5]: # TODO: Configure this, maybe? netvs.add_child(Node.string('dialog', f'dialog#{dialog}')) # Set up config config = Node.void('config') root.add_child(config) config.add_child(Node.u8('mode', profile.get_int('mode', 0))) config.add_child(Node.s16('chara', profile.get_int('chara', -1))) config.add_child(Node.s16('music', profile.get_int('music', -1))) config.add_child(Node.u8('sheet', profile.get_int('sheet', 0))) config.add_child(Node.s8('category', profile.get_int('category', 1))) config.add_child( Node.s8('sub_category', profile.get_int('sub_category', -1))) config.add_child( Node.s8('chara_category', profile.get_int('chara_category', -1))) config.add_child(Node.s16('story_id', profile.get_int('story_id', -1))) config.add_child( Node.s16('course_id', profile.get_int('course_id', -1))) config.add_child( Node.s8('course_folder', profile.get_int('course_folder', -1))) config.add_child( Node.s8('story_folder', profile.get_int('story_folder', -1))) config.add_child( Node.s8('ms_banner_disp', profile.get_int('ms_banner_disp'))) config.add_child( Node.s8('ms_down_info', profile.get_int('ms_down_info'))) config.add_child( Node.s8('ms_side_info', profile.get_int('ms_side_info'))) config.add_child( Node.s8('ms_raise_type', profile.get_int('ms_raise_type'))) config.add_child(Node.s8('ms_rnd_type', profile.get_int('ms_rnd_type'))) # Set up option option_dict = profile.get_dict('option') option = Node.void('option') root.add_child(option) option.add_child( Node.s16('hispeed', option_dict.get_int('hispeed', 10))) option.add_child(Node.u8('popkun', option_dict.get_int('popkun', 0))) option.add_child( Node.bool('hidden', option_dict.get_bool('hidden', False))) option.add_child( Node.s16('hidden_rate', option_dict.get_int('hidden_rate', -1))) option.add_child( Node.bool('sudden', option_dict.get_bool('sudden', False))) option.add_child( Node.s16('sudden_rate', option_dict.get_int('sudden_rate', -1))) option.add_child(Node.s8('randmir', option_dict.get_int('randmir', 0))) option.add_child( Node.s8('gauge_type', option_dict.get_int('gauge_type', 0))) option.add_child(Node.u8('ojama_0', option_dict.get_int('ojama_0', 0))) option.add_child(Node.u8('ojama_1', option_dict.get_int('ojama_1', 0))) option.add_child( Node.bool('forever_0', option_dict.get_bool('forever_0', False))) option.add_child( Node.bool('forever_1', option_dict.get_bool('forever_1', False))) option.add_child( Node.bool('full_setting', option_dict.get_bool('full_setting', False))) # Set up info info = Node.void('info') root.add_child(info) info.add_child(Node.u16('ep', profile.get_int('ep', 0))) info.add_child(Node.u16('ap', profile.get_int('ap', 0))) # Set up custom_cate custom_cate = Node.void('custom_cate') root.add_child(custom_cate) custom_cate.add_child(Node.s8('valid', 0)) custom_cate.add_child(Node.s8('lv_min', -1)) custom_cate.add_child(Node.s8('lv_max', -1)) custom_cate.add_child(Node.s8('medal_min', -1)) custom_cate.add_child(Node.s8('medal_max', -1)) custom_cate.add_child(Node.s8('friend_no', -1)) custom_cate.add_child(Node.s8('score_flg', -1)) # Set up customize customize_dict = profile.get_dict('customize') customize = Node.void('customize') root.add_child(customize) customize.add_child( Node.u16('effect', customize_dict.get_int('effect'))) customize.add_child( Node.u16('hukidashi', customize_dict.get_int('hukidashi'))) customize.add_child(Node.u16('font', customize_dict.get_int('font'))) customize.add_child( Node.u16('comment_1', customize_dict.get_int('comment_1'))) customize.add_child( Node.u16('comment_2', customize_dict.get_int('comment_2'))) # Set up achievements achievements = self.data.local.user.get_achievements( self.game, self.version, userid) for achievement in achievements: if achievement.type == 'item': itemtype = achievement.data.get_int('type') param = achievement.data.get_int('param') item = Node.void('item') root.add_child(item) item.add_child(Node.u8('type', itemtype)) item.add_child(Node.u16('id', achievement.id)) item.add_child(Node.u16('param', param)) item.add_child(Node.bool('is_new', False)) elif achievement.type == 'achievement': count = achievement.data.get_int('count') ach_node = Node.void('achievement') root.add_child(ach_node) ach_node.add_child(Node.u8('type', achievement.id)) ach_node.add_child(Node.u32('count', count)) elif achievement.type == 'chara': friendship = achievement.data.get_int('friendship') chara = Node.void('chara_param') root.add_child(chara) chara.add_child(Node.u16('chara_id', achievement.id)) chara.add_child(Node.u16('friendship', friendship)) elif achievement.type == 'story': chapter = achievement.data.get_int('chapter') gauge = achievement.data.get_int('gauge') cleared = achievement.data.get_bool('cleared') clear_chapter = achievement.data.get_int('clear_chapter') story = Node.void('story') root.add_child(story) story.add_child(Node.u32('story_id', achievement.id)) story.add_child(Node.u32('chapter_id', chapter)) story.add_child(Node.u16('gauge_point', gauge)) story.add_child(Node.bool('is_cleared', cleared)) story.add_child(Node.u32('clear_chapter', clear_chapter)) return root
def verify_player24_read( self, ref_id: str, msg_type: str) -> Dict[str, Dict[int, Dict[str, int]]]: call = self.call_node() # Construct node player24 = Node.void('player24') call.add_child(player24) player24.set_attribute('method', 'read') player24.add_child(Node.string('ref_id', ref_id)) player24.add_child(Node.s8('pref', 51)) # Swap with server resp = self.exchange('', call) if msg_type == 'new': # Verify that response is correct self.assert_path(resp, "response/player24/result") status = resp.child_value('player24/result') if status != 2: raise Exception( f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'' ) return { 'items': {}, 'characters': {}, 'points': {}, } elif msg_type == 'query': # Verify that the response is correct self.__verify_profile(resp) self.assert_path(resp, "response/player24/result") status = resp.child_value('player24/result') if status != 0: raise Exception( f'Reference ID \'{ref_id}\' returned invalid status \'{status}\'' ) name = resp.child_value('player24/account/name') if name != self.NAME: raise Exception( f'Invalid name \'{name}\' returned for Ref ID \'{ref_id}\'' ) # Medals and items items: Dict[int, Dict[str, int]] = {} charas: Dict[int, Dict[str, int]] = {} courses: Dict[int, Dict[str, int]] = {} for obj in resp.child('player24').children: if obj.name == 'item': items[obj.child_value('id')] = { 'type': obj.child_value('type'), 'param': obj.child_value('param'), } elif obj.name == 'chara_param': charas[obj.child_value('chara_id')] = { 'friendship': obj.child_value('friendship'), } elif obj.name == 'course_data': courses[obj.child_value('course_id')] = { 'clear_type': obj.child_value('clear_type'), 'clear_rank': obj.child_value('clear_rank'), 'total_score': obj.child_value('total_score'), 'count': obj.child_value('update_count'), 'sheet_num': obj.child_value('sheet_num'), } return { 'items': items, 'characters': charas, 'courses': courses, 'points': { 0: { 'points': resp.child_value('player24/account/player_point') } }, } else: raise Exception(f'Unrecognized message type \'{msg_type}\'')