def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: name = 'なし' # Nothing shop = '未設定' # Not set shop_area = '未設定' # Not set for i in range(len(profile['strdatas'])): strdata = profile['strdatas'][i] # Figure out the profile type csvs = strdata.split(b',') if len(csvs) < 2: # Not long enough to care about continue datatype = csvs[1].decode('ascii') if datatype != 'IBBDAT00': # Not the right profile type requested continue name = self.__update_value(name, csvs[27]) shop = self.__update_value(shop, csvs[30]) shop_area = self.__update_value(shop_area, csvs[31]) return { 'name': name, 'extid': ID.format_extid(profile.get_int('extid')), 'shop': shop, 'shop_area': shop_area, 'first_play_time': playstats.get_int('first_play_timestamp'), 'last_play_time': playstats.get_int('last_play_timestamp'), 'plays': playstats.get_int('total_plays'), }
def format_profile(self, profile: ValidatedDict, playstats: ValidatedDict) -> Dict[str, Any]: return { 'name': profile.get_str('name'), 'extid': ID.format_extid(profile.get_int('extid')), 'first_play_time': playstats.get_int('first_play_timestamp'), 'last_play_time': playstats.get_int('last_play_timestamp'), }
def handle_pcbinfo_get_request(self, request: Node) -> Node: shop_id = ID.parse_machine_id(request.child_value('lid')) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name close = machine.data.get_bool('close') hour = machine.data.get_int('hour') minute = machine.data.get_int('minute') pref = machine.data.get_int('pref', 51) else: machine_name = '' close = False hour = 0 minute = 0 pref = 51 root = Node.void('pcbinfo') info = Node.void('info') root.add_child(info) info.add_child(Node.string('name', machine_name)) info.add_child(Node.s16('pref', pref)) info.add_child(Node.bool('close', close)) info.add_child(Node.u8('hour', hour)) info.add_child(Node.u8('min', minute)) return root
def handle_game_3_new_request(self, request: Node) -> Node: refid = request.child_value('refid') name = request.child_value('name') loc = ID.parse_machine_id(request.child_value('locid')) self.new_profile_by_refid(refid, name, loc) root = Node.void('game_3') return root
def handle_facility_get_request(self, request: Node) -> Node: """ Handle a facility request. The only method of note is the 'get' request, which expects to return a bunch of information about the arcade this cabinet is in, as well as some settings for URLs and the name of the cab. """ machine = self.data.local.machine.get_machine(self.config['machine']['pcbid']) root = Node.void('facility') root.set_attribute('expire', '600') location = Node.void('location') location.add_child(Node.string('id', ID.format_machine_id(machine.id))) location.add_child(Node.string('country', 'US')) location.add_child(Node.string('region', '.')) location.add_child(Node.string('name', machine.name)) location.add_child(Node.u8('type', 0)) line = Node.void('line') line.add_child(Node.string('id', '.')) line.add_child(Node.u8('class', 0)) portfw = Node.void('portfw') portfw.add_child(Node.ipv4('globalip', self.config['client']['address'])) portfw.add_child(Node.u16('globalport', machine.port)) portfw.add_child(Node.u16('privateport', machine.port)) public = Node.void('public') public.add_child(Node.u8('flag', 1)) public.add_child(Node.string('name', '.')) public.add_child(Node.string('latitude', '0')) public.add_child(Node.string('longitude', '0')) share = Node.void('share') eacoin = Node.void('eacoin') eacoin.add_child(Node.s32('notchamount', 3000)) eacoin.add_child(Node.s32('notchcount', 3)) eacoin.add_child(Node.s32('supplylimit', 10000)) eapass = Node.void('eapass') eapass.add_child(Node.u16('valid', 365)) url = Node.void('url') url.add_child(Node.string('eapass', self.config['server']['uri'] or 'www.ea-pass.konami.net')) url.add_child(Node.string('arcadefan', self.config['server']['uri'] or 'www.konami.jp/am')) url.add_child(Node.string('konaminetdx', self.config['server']['uri'] or 'http://am.573.jp')) url.add_child(Node.string('konamiid', self.config['server']['uri'] or 'https://id.konami.net')) url.add_child(Node.string('eagate', self.config['server']['uri'] or 'http://eagate.573.jp')) share.add_child(eacoin) share.add_child(url) share.add_child(eapass) root.add_child(location) root.add_child(line) root.add_child(portfw) root.add_child(public) root.add_child(share) return root
def __handle_usernew(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: if userid is None: raise Exception('Expecting valid UserID to create new profile!') machine = self.data.local.machine.get_machine(self.config['machine']['pcbid']) self.put_profile(userid, ValidatedDict({ 'lid': machine.id, })) profile = self.get_profile(userid) response.add_child(Node.string('seq', ID.format_extid(profile.get_int('extid')))) response.add_child(Node.s32('code', profile.get_int('extid'))) response.add_child(Node.string('shoparea', ''))
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: game = Node.void('game') # 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.u32('exp_point', profile.get_int('exp'))) game.add_child(Node.u32('m_user_cnt', profile.get_int('m_user_cnt'))) game_config = self.get_game_config() if game_config.get_bool('force_unlock_cards'): game.add_child(Node.bool_array('have_item', [True] * 512)) else: game.add_child( Node.bool_array( 'have_item', [x > 0 for x in profile.get_int_array('have_item', 512)])) if game_config.get_bool('force_unlock_songs'): game.add_child(Node.bool_array('have_note', [True] * 512)) else: game.add_child( Node.bool_array( 'have_note', [x > 0 for x in profile.get_int_array('have_note', 512)])) # Last played stuff lastdict = profile.get_dict('last') last = Node.void('last') game.add_child(last) last.set_attribute('music_id', str(lastdict.get_int('music_id'))) last.set_attribute('music_type', str(lastdict.get_int('music_type'))) last.set_attribute('sort_type', str(lastdict.get_int('sort_type'))) last.set_attribute('headphone', str(lastdict.get_int('headphone'))) last.set_attribute('hispeed', str(lastdict.get_int('hispeed'))) last.set_attribute('appeal_id', str(lastdict.get_int('appeal_id'))) last.set_attribute('frame0', str(lastdict.get_int('frame0'))) last.set_attribute('frame1', str(lastdict.get_int('frame1'))) last.set_attribute('frame2', str(lastdict.get_int('frame2'))) last.set_attribute('frame3', str(lastdict.get_int('frame3'))) last.set_attribute('frame4', str(lastdict.get_int('frame4'))) return game
def searchrivals() -> Dict[str, Any]: frontend = SoundVoltexFrontend(g.data, g.config, g.cache) version = int(request.get_json()['version']) name = request.get_json()['term'] # Try to treat the term as an extid extid = ID.parse_extid(name) matches = set() profiles = g.data.remote.user.get_all_profiles(GameConstants.SDVX, version) for (userid, profile) in profiles: if profile.get_int('extid') == extid or profile.get_str('name').lower() == name.lower(): matches.add(userid) playerinfo = frontend.get_all_player_info(list(matches), allow_remote=True) return { 'results': playerinfo, }
def handle_pcb_rb5_pcb_boot_request(self, request: Node) -> Node: shop_id = ID.parse_machine_id(request.child_value('lid')) machine = self.get_machine_by_id(shop_id) if machine is not None: machine_name = machine.name close = machine.data.get_bool('close') hour = machine.data.get_int('hour') minute = machine.data.get_int('minute') else: machine_name = '' close = False hour = 0 minute = 0 root = Node.void('pcb') sinfo = Node.void('sinfo') root.add_child(sinfo) sinfo.add_child(Node.string('nm', machine_name)) sinfo.add_child(Node.bool('cl_enbl', close)) sinfo.add_child(Node.u8('cl_h', hour)) sinfo.add_child(Node.u8('cl_m', minute)) sinfo.add_child(Node.bool('shop_flag', True)) return root
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 elif method == 'friend': refid = request.attribute('ref_id') root = Node.void('playerdata') # 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.set_attribute('status', str(Status.NO_PROFILE)) 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) for rival in links[:2]: rivalid = rival.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) # 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('open', 1)) # Set up some sane defaults. friend.add_child( Node.string('name', rivalprofile.get_str('name', 'なし'))) friend.add_child( Node.string('g_pm_id', ID.format_extid( rivalprofile.get_int('extid')))) friend.add_child( Node.s16('chara', rivalprofile.get_int('chara', -1))) # Perform hiscore/medal conversion. hiscore_array = [0] * int( (((self.GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8) clear_medal = [0] * self.GAME_MAX_MUSIC_ID 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) friend.add_child(Node.u16_array('clear_medal', clear_medal)) friend.add_child(Node.binary('hiscore', hiscore)) return root # Invalid method return None
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: game_config = self.get_game_config() newprofile = copy.deepcopy(oldprofile) newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('lid'))) newprofile.replace_str('name', request.child_value('pdata/base/name')) newprofile.replace_int('lvl', request.child_value('pdata/base/lv')) newprofile.replace_int('exp', request.child_value('pdata/base/exp')) newprofile.replace_int('mg', request.child_value('pdata/base/mg')) newprofile.replace_int('ap', request.child_value('pdata/base/ap')) newprofile.replace_int('flag', request.child_value('pdata/base/flag')) customdict = newprofile.get_dict('custom') custom = request.child('pdata/custom') if custom: customdict.replace_int('bgm_m', custom.child_value('bgm_m')) customdict.replace_int('st_f', custom.child_value('st_f')) customdict.replace_int('st_bg', custom.child_value('st_bg')) customdict.replace_int('st_bg_b', custom.child_value('st_bg_b')) customdict.replace_int('eff_e', custom.child_value('eff_e')) customdict.replace_int('se_s', custom.child_value('se_s')) customdict.replace_int('se_s_v', custom.child_value('se_s_v')) newprofile.replace_dict('custom', customdict) # Music unlocks and other stuff released = request.child('pdata/released') if released: for child in released.children: if child.name != 'info': continue item_id = child.child_value('id') item_type = child.child_value('type') if game_config.get_bool( 'force_unlock_songs') and item_type == 0: # Don't save unlocks when we're force unlocking continue self.data.local.user.put_achievement( self.game, self.version, userid, item_id, 'item_{}'.format(item_type), {}, ) # Grab any new records set during this play session. Reflec Beat original only sends # the top record back for songs that were played at least once during the session. # Note that it sends the top record, so if you play the song twice, it will return # only one record. Also, if you get a lower score than a previous try, it will return # the previous try. So, we must also look at the battle log for the actual play scores, # and combine the data if we can. savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {} songplays = request.child('pdata/record') if songplays: for child in songplays.children: if child.name != 'rec': continue songid = child.child_value('mid') chart = child.child_value('ng') # These don't get sent with the battle logs, so we try to construct # the values here. if songid not in savedrecords: savedrecords[songid] = {} savedrecords[songid][chart] = { 'achievement_rate': child.child_value('ar') * 10, 'points': child.child_value('bs'), 'combo': child.child_value('mc'), 'miss_count': child.child_value('bmc'), 'win': child.child_value('win'), 'lose': child.child_value('lose'), 'draw': child.child_value('draw'), } # Now, see the actual battles that were played. If we can, unify the data with a record. # We only do that when the record achievement rate and score matches the battle achievement # rate and score, so we know for a fact that that record was generated by this battle. battlelogs = request.child('pdata/blog') if battlelogs: for child in battlelogs.children: if child.name != 'log': continue songid = child.child_value('mid') chart = child.child_value('ng') clear_type = child.child_value('myself/ct') achievement_rate = child.child_value('myself/ar') * 10 points = child.child_value('myself/s') clear_type, combo_type = self.__game_to_db_clear_type( clear_type, achievement_rate) combo = None miss_count = -1 stats = None if songid in savedrecords: if chart in savedrecords[songid]: data = savedrecords[songid][chart] if (data['achievement_rate'] == achievement_rate and data['points'] == points): # This is the same record! Use the stats from it to update our # internal representation. combo = data['combo'] miss_count = data['miss_count'] stats = { 'win': data['win'], 'lose': data['lose'], 'draw': data['draw'], } self.update_score( userid, songid, chart, points, achievement_rate, clear_type, combo_type, miss_count, combo=combo, stats=stats, ) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def handle_game_3_hiscore_request(self, request: Node) -> Node: # Grab location for local scores locid = ID.parse_machine_id(request.child_value('locid')) # Start the response packet game = Node.void('game_3') # First, grab hit chart playcounts = self.data.local.music.get_hit_chart( self.game, self.version, 1024) hitchart = Node.void('hitchart') game.add_child(hitchart) for (songid, count) in playcounts: info = Node.void('info') hitchart.add_child(info) info.add_child(Node.u32('id', songid)) info.add_child(Node.u32('cnt', count)) # Now, grab user records records = self.data.remote.music.get_all_records( self.game, self.version) users = { uid: prof for (uid, prof) in self.get_any_profiles([r[0] for r in records]) } hiscore_allover = Node.void('hiscore_allover') game.add_child(hiscore_allover) # Output records for (userid, score) in records: info = Node.void('info') if userid not in users: raise Exception( 'Logic error, could not find profile for user!') profile = users[userid] info.add_child(Node.u32('id', score.id)) info.add_child(Node.u32('type', score.chart)) info.add_child(Node.string('name', profile.get_str('name'))) info.add_child( Node.string('seq', ID.format_extid(profile.get_int('extid')))) info.add_child(Node.u32('score', score.points)) # Add to global scores hiscore_allover.add_child(info) # Now, grab local records area_users = [ uid for (uid, prof) in self.data.local.user.get_all_profiles( self.game, self.version) if prof.get_int('loc', -1) == locid ] records = self.data.local.music.get_all_records(self.game, self.version, userlist=area_users) missing_players = [uid for (uid, _) in records if uid not in users] for (uid, prof) in self.get_any_profiles(missing_players): users[uid] = prof hiscore_location = Node.void('hiscore_location') game.add_child(hiscore_location) # Output records for (userid, score) in records: info = Node.void('info') if userid not in users: raise Exception( 'Logic error, could not find profile for user!') profile = users[userid] info.add_child(Node.u32('id', score.id)) info.add_child(Node.u32('type', score.chart)) info.add_child(Node.string('name', profile.get_str('name'))) info.add_child( Node.string('seq', ID.format_extid(profile.get_int('extid')))) info.add_child(Node.u32('score', score.points)) # Add to global scores hiscore_location.add_child(info) # Now, grab clear rates clear_rate = Node.void('clear_rate') game.add_child(clear_rate) clears = self.get_clear_rates() for songid in clears: for chart in clears[songid]: if clears[songid][chart]['total'] > 0: rate = float(clears[songid][chart]['clears']) / float( clears[songid][chart]['total']) dnode = Node.void('d') clear_rate.add_child(dnode) dnode.add_child(Node.u32('id', songid)) dnode.add_child(Node.u32('type', chart)) dnode.add_child(Node.s16('cr', int(rate * 10000))) return game
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'))) # Enable Ryusei Festa ryusei_festa = Node.void('ryusei_festa') game.add_child(ryusei_festa) ryusei_festa.add_child(Node.bool('ryusei_festa_trigger', True)) # 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.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 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 unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: game_config = self.get_game_config() newprofile = copy.deepcopy(oldprofile) newprofile.replace_int('lid', ID.parse_machine_id(request.child_value('lid'))) newprofile.replace_str('name', request.child_value('pdata/base/name')) newprofile.replace_int('icon', request.child_value('pdata/base/icon_id')) newprofile.replace_int('lvl', request.child_value('pdata/base/lv')) newprofile.replace_int('exp', request.child_value('pdata/base/exp')) newprofile.replace_int('mg', request.child_value('pdata/base/mg')) newprofile.replace_int('ap', request.child_value('pdata/base/ap')) newprofile.replace_int('pc', request.child_value('pdata/base/pc')) newprofile.replace_int('uattr', request.child_value('pdata/base/uattr')) customdict = newprofile.get_dict('custom') custom = request.child('pdata/custom') if custom: customdict.replace_int('s_gls', custom.child_value('s_gls')) customdict.replace_int('bgm_m', custom.child_value('bgm_m')) customdict.replace_int('st_f', custom.child_value('st_f')) customdict.replace_int('st_bg', custom.child_value('st_bg')) customdict.replace_int('st_bg_b', custom.child_value('st_bg_b')) customdict.replace_int('eff_e', custom.child_value('eff_e')) customdict.replace_int('se_s', custom.child_value('se_s')) customdict.replace_int('se_s_v', custom.child_value('se_s_v')) customdict.replace_int('last_music_id', custom.child_value('last_music_id')) customdict.replace_int('last_note_grade', custom.child_value('last_note_grade')) customdict.replace_int('sort_type', custom.child_value('sort_type')) customdict.replace_int('narrowdown_type', custom.child_value('narrowdown_type')) customdict.replace_bool( 'is_begginer', custom.child_value( 'is_begginer')) # Yes, this is spelled right customdict.replace_bool('is_tut', custom.child_value('is_tut')) customdict.replace_int_array('symbol_chat_0', 6, custom.child_value('symbol_chat_0')) customdict.replace_int_array('symbol_chat_1', 6, custom.child_value('symbol_chat_1')) customdict.replace_int('gauge_style', custom.child_value('gauge_style')) customdict.replace_int('obj_shade', custom.child_value('obj_shade')) customdict.replace_int('obj_size', custom.child_value('obj_size')) customdict.replace_int_array('byword', 2, custom.child_value('byword')) customdict.replace_bool_array('is_auto_byword', 2, custom.child_value('is_auto_byword')) customdict.replace_bool('is_tweet', custom.child_value('is_tweet')) customdict.replace_bool('is_link_twitter', custom.child_value('is_link_twitter')) customdict.replace_int('mrec_type', custom.child_value('mrec_type')) customdict.replace_int('card_disp_type', custom.child_value('card_disp_type')) customdict.replace_int('tab_sel', custom.child_value('tab_sel')) customdict.replace_int_array('hidden_param', 20, custom.child_value('hidden_param')) newprofile.replace_dict('custom', customdict) # Music unlocks and other stuff released = request.child('pdata/released') if released: for child in released.children: if child.name != 'info': continue item_id = child.child_value('id') item_type = child.child_value('type') param = child.child_value('param') if game_config.get_bool( 'force_unlock_songs') and item_type == 0: # Don't save unlocks when we're force unlocking continue self.data.local.user.put_achievement( self.game, self.version, userid, item_id, f'item_{item_type}', { 'param': param, }, ) # Grab any new records set during this play session. Reflec Beat Limelight only sends # the top record back for songs that were played at least once during the session. # Note that it sends the top record, so if you play the song twice, it will return # only one record. Also, if you get a lower score than a previous try, it will return # the previous try. So, we must also look at the battle log for the actual play scores, # and combine the data if we can. savedrecords: Dict[int, Dict[int, Dict[str, int]]] = {} songplays = request.child('pdata/record') if songplays: for child in songplays.children: if child.name != 'rec': continue songid = child.child_value('mid') chart = child.child_value('ng') # These don't get sent with the battle logs, so we try to construct # the values here. if songid not in savedrecords: savedrecords[songid] = {} savedrecords[songid][chart] = { 'achievement_rate': child.child_value('mrec_0/ar') * 10, 'points': child.child_value('mrec_0/bs'), 'combo': child.child_value('mrec_0/mc'), 'miss_count': child.child_value('mrec_0/bmc'), 'win': child.child_value('mrec_0/win'), 'lose': child.child_value('mrec_0/lose'), 'draw': child.child_value('mrec_0/draw'), 'earned_points': child.child_value('point'), } # Now, see the actual battles that were played. If we can, unify the data with a record. # We only do that when the record achievement rate and score matches the battle achievement # rate and score, so we know for a fact that that record was generated by this battle. battlelogs = request.child('pdata/blog') if battlelogs: for child in battlelogs.children: if child.name != 'log': continue songid = child.child_value('mid') chart = child.child_value('ng') clear_type = child.child_value('myself/ct') achievement_rate = child.child_value('myself/ar') * 10 points = child.child_value('myself/s') clear_type, combo_type = self.__game_to_db_clear_type( clear_type) combo = None miss_count = -1 stats = None if songid in savedrecords: if chart in savedrecords[songid]: data = savedrecords[songid][chart] if (data['achievement_rate'] == achievement_rate and data['points'] == points): # This is the same record! Use the stats from it to update our # internal representation. combo = data['combo'] miss_count = data['miss_count'] stats = { 'win': data['win'], 'lose': data['lose'], 'draw': data['draw'], 'earned_points': data['earned_points'], } self.update_score( userid, songid, chart, points, achievement_rate, clear_type, combo_type, miss_count, combo=combo, stats=stats, ) # Keep track of glass points so unlocks work glass = request.child('pdata/glass') if glass: for child in glass.children: if child.name != 'g': continue gid = child.child_value('id') exp = child.child_value('exp') self.data.local.user.put_achievement( self.game, self.version, userid, gid, 'glass', { 'exp': exp, }, ) # Keep track of favorite music selections fav_music_slot = request.child('pdata/fav_music_slot') if fav_music_slot: for child in fav_music_slot.children: if child.name != 'slot': continue slot_id = child.child_value('slot_id') music_id = child.child_value('music_id') if music_id == -1: # Delete this favorite self.data.local.user.destroy_achievement( self.game, self.version, userid, slot_id, 'music', ) else: # Add/update this favorite self.data.local.user.put_achievement( self.game, self.version, userid, slot_id, 'music', { 'music_id': music_id, }, ) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
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', 'dialog#{}'.format(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 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 test_format_extid(self) -> None: self.assertEqual(ID.format_extid(12345678), '1234-5678') self.assertEqual(ID.parse_extid('1234-5678'), 12345678) self.assertEqual(ID.parse_extid('bla'), None) self.assertEqual(ID.parse_extid('blah-blah'), None)
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.u8('active_fr_num', 0)) # TODO: Hook up rivals code? 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)) # 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 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 unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: game_config = self.get_game_config() newprofile = copy.deepcopy(oldprofile) # Save base player profile info newprofile.replace_int( 'lid', ID.parse_machine_id(request.child_value('pdata/account/lid'))) newprofile.replace_str('name', request.child_value('pdata/base/name')) newprofile.replace_int('mg', request.child_value('pdata/base/mg')) newprofile.replace_int('ap', request.child_value('pdata/base/ap')) newprofile.replace_int('uattr', request.child_value('pdata/base/uattr')) newprofile.replace_int('money', request.child_value('pdata/base/money')) newprofile.replace_int('class', request.child_value('pdata/base/class')) newprofile.replace_int('class_ar', request.child_value('pdata/base/class_ar')) newprofile.replace_int('skill_point', request.child_value('pdata/base/skill_point')) newprofile.replace_int('mgid', request.child_value('pdata/minigame/mgid')) newprofile.replace_int('mgsc', request.child_value('pdata/minigame/sc')) newprofile.replace_int_array( 'favorites', 30, request.child_value('pdata/mylist/list/mlst')) # Save player config configdict = newprofile.get_dict('config') config = request.child('pdata/config') if config: configdict.replace_int('msel_bgm', config.child_value('msel_bgm')) configdict.replace_int('narrowdown_type', config.child_value('narrowdown_type')) configdict.replace_int('icon_id', config.child_value('icon_id')) configdict.replace_int('byword_0', config.child_value('byword_0')) configdict.replace_int('byword_1', config.child_value('byword_1')) configdict.replace_bool('is_auto_byword_0', config.child_value('is_auto_byword_0')) configdict.replace_bool('is_auto_byword_1', config.child_value('is_auto_byword_1')) configdict.replace_int('mrec_type', config.child_value('mrec_type')) configdict.replace_int('tab_sel', config.child_value('tab_sel')) configdict.replace_int('card_disp', config.child_value('card_disp')) configdict.replace_int('score_tab_disp', config.child_value('score_tab_disp')) configdict.replace_int('last_music_id', config.child_value('last_music_id')) configdict.replace_int('last_note_grade', config.child_value('last_note_grade')) configdict.replace_int('sort_type', config.child_value('sort_type')) configdict.replace_int('rival_panel_type', config.child_value('rival_panel_type')) configdict.replace_int('random_entry_work', config.child_value('random_entry_work')) configdict.replace_int('custom_folder_work', config.child_value('custom_folder_work')) configdict.replace_int('folder_type', config.child_value('folder_type')) configdict.replace_int('folder_lamp_type', config.child_value('folder_lamp_type')) configdict.replace_bool('is_tweet', config.child_value('is_tweet')) configdict.replace_bool('is_link_twitter', config.child_value('is_link_twitter')) newprofile.replace_dict('config', configdict) # Save player custom settings customdict = newprofile.get_dict('custom') custom = request.child('pdata/custom') if custom: customdict.replace_int('st_shot', custom.child_value('st_shot')) customdict.replace_int('st_frame', custom.child_value('st_frame')) customdict.replace_int('st_expl', custom.child_value('st_expl')) customdict.replace_int('st_bg', custom.child_value('st_bg')) customdict.replace_int('st_shot_vol', custom.child_value('st_shot_vol')) customdict.replace_int('st_bg_bri', custom.child_value('st_bg_bri')) customdict.replace_int('st_obj_size', custom.child_value('st_obj_size')) customdict.replace_int('st_jr_gauge', custom.child_value('st_jr_gauge')) customdict.replace_int('st_clr_gauge', custom.child_value('st_clr_gauge')) customdict.replace_int('st_gr_gauge_type', custom.child_value('st_gr_gauge_type')) customdict.replace_int('voice_message_set', custom.child_value('voice_message_set')) customdict.replace_int('same_time_note_disp', custom.child_value('same_time_note_disp')) customdict.replace_int('st_score_disp_type', custom.child_value('st_score_disp_type')) customdict.replace_int('st_bonus_type', custom.child_value('st_bonus_type')) customdict.replace_int('st_rivalnote_type', custom.child_value('st_rivalnote_type')) customdict.replace_int('st_topassist_type', custom.child_value('st_topassist_type')) customdict.replace_int('high_speed', custom.child_value('high_speed')) customdict.replace_int('st_hazard', custom.child_value('st_hazard')) customdict.replace_int('st_clr_cond', custom.child_value('st_clr_cond')) customdict.replace_int('voice_message_volume', custom.child_value('voice_message_volume')) newprofile.replace_dict('custom', customdict) # Save player parameter info params = request.child('pdata/player_param') if params: for child in params.children: if child.name != 'item': continue item_type = child.child_value('type') bank = child.child_value('bank') data = child.child_value('data') while len(data) < 256: data.append(0) self.data.local.user.put_achievement( self.game, self.version, userid, bank, f'player_param_{item_type}', { 'data': data, }, ) # Save player episode info episode = request.child('pdata/episode') if episode: for child in episode.children: if child.name != 'info': continue # I assume this is copypasta, but I want to be sure extid = child.child_value('user_id') if extid != newprofile.get_int('extid'): raise Exception( f'Unexpected user ID, got {extid} expecting {newprofile.get_int("extid")}' ) episode_type = child.child_value('type') episode_value0 = child.child_value('value0') episode_value1 = child.child_value('value1') episode_text = child.child_value('text') episode_time = child.child_value('time') self.data.local.user.put_achievement( self.game, self.version, userid, episode_type, 'episode', { 'value0': episode_value0, 'value1': episode_value1, 'text': episode_text, 'time': episode_time, }, ) # Save released info released = request.child('pdata/released') if released: for child in released.children: if child.name != 'info': continue item_id = child.child_value('id') item_type = child.child_value('type') param = child.child_value('param') time = child.child_value('insert_time') or Time.now() if game_config.get_bool( 'force_unlock_songs') and item_type == 0: # Don't save unlocks when we're force unlocking continue self.data.local.user.put_achievement( self.game, self.version, userid, item_id, f'item_{item_type}', { 'param': param, 'time': time, }, ) # Save announce info announce = request.child('pdata/announce') if announce: for child in announce.children: if child.name != 'info': continue announce_id = child.child_value('id') announce_type = child.child_value('type') param = child.child_value('param') need = child.child_value('bneedannounce') self.data.local.user.put_achievement( self.game, self.version, userid, announce_id, f'announcement_{announce_type}', { 'param': param, 'need': need, }, ) # Grab any new records set during this play session songplays = request.child('pdata/stglog') if songplays: for child in songplays.children: if child.name != 'log': continue songid = child.child_value('mid') chart = child.child_value('ng') clear_type = child.child_value('ct') if songid == 0 and chart == 0 and clear_type == -1: # Dummy song save during profile create continue points = child.child_value('sc') achievement_rate = child.child_value('ar') param = child.child_value('param') miss_count = child.child_value('jt_ms') k_flag = child.child_value('k_flag') # Param is some random bits along with the combo type combo_type = param & 0x3 param = param ^ combo_type clear_type = self._game_to_db_clear_type(clear_type) combo_type = self._game_to_db_combo_type( combo_type, miss_count) self.update_score( userid, songid, chart, points, achievement_rate, clear_type, combo_type, miss_count, param=param, kflag=k_flag, ) # Grab any new rivals added during this play session rivalnode = request.child('pdata/rival') if rivalnode: for child in rivalnode.children: if child.name != 'r': continue extid = child.child_value('id') other_userid = self.data.remote.user.from_extid( self.game, self.version, extid) if other_userid is None: continue self.data.local.user.put_link( self.game, self.version, userid, 'rival', other_userid, {}, ) # Save player dojo dojo = request.child('pdata/dojo') if dojo: dojoid = dojo.child_value('class') clear_type = dojo.child_value('clear_type') ar = dojo.child_value('t_ar') score = dojo.child_value('t_score') # Figure out timestamp stuff data = self.data.local.user.get_achievement( self.game, self.version, userid, dojoid, 'dojo', ) or ValidatedDict() if ar >= data.get_int('ar'): # We set a new achievement rate, keep the new values record_time = Time.now() else: # We didn't, keep the old values for achievement rate, but # override score and clear_type only if they were better. record_time = data.get_int('record_timestamp') ar = data.get_int('ar') score = max(score, data.get_int('score')) clear_type = max(clear_type, data.get_int('clear_type')) play_time = Time.now() plays = data.get_int('plays') + 1 self.data.local.user.put_achievement( self.game, self.version, userid, dojoid, 'dojo', { 'clear_type': clear_type, 'ar': ar, 'score': score, 'plays': plays, 'play_timestamp': play_time, 'record_timestamp': record_time, }, ) # Save yurukome stuff yurukome_list = request.child('pdata/yurukome_list') if yurukome_list: for child in yurukome_list.children: if child.name != 'yurukome': continue yurukome_id = child.child_value('yurukome_id') self.data.local.user.put_achievement( self.game, self.version, userid, yurukome_id, 'yurukome', {}, ) # Save mycourse stuff mycoursedict = newprofile.get_dict('mycourse') mycourse = request.child('pdata/mycourse') if mycourse: # Only replace course if it was a new record score-wise. score_1 = mycourse.child_value('score_1') score_2 = mycourse.child_value('score_2') score_3 = mycourse.child_value('score_3') score_4 = mycourse.child_value('score_4') total = 0 for score in [score_1, score_2, score_3, score_4]: if score is not None and score >= 0: total = total + score oldtotal = (mycoursedict.get_int('score_1', 0) + mycoursedict.get_int('score_2', 0) + mycoursedict.get_int('score_3', 0) + mycoursedict.get_int('score_4', 0)) if total >= oldtotal: mycoursedict.replace_int('music_id_1', mycourse.child_value('music_id_1')) mycoursedict.replace_int('note_grade_1', mycourse.child_value('note_grade_1')) mycoursedict.replace_int('score_1', score_1) mycoursedict.replace_int('music_id_2', mycourse.child_value('music_id_2')) mycoursedict.replace_int('note_grade_2', mycourse.child_value('note_grade_2')) mycoursedict.replace_int('score_2', score_2) mycoursedict.replace_int('music_id_3', mycourse.child_value('music_id_3')) mycoursedict.replace_int('note_grade_3', mycourse.child_value('note_grade_3')) mycoursedict.replace_int('score_3', score_3) mycoursedict.replace_int('music_id_4', mycourse.child_value('music_id_4')) mycoursedict.replace_int('note_grade_4', mycourse.child_value('note_grade_4')) mycoursedict.replace_int('score_4', score_4) mycoursedict.replace_int('insert_time', Time.now()) newprofile.replace_dict('mycourse', mycoursedict) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
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', 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.s8('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)) # These are probably from an old event, but if they aren't present and defaulted, # then different songs show up in the Zoo event. base.add_child( Node.u16_array( 'gitadora_point', profile.get_int_array('gitadora_point', 3, [2000, 2000, 2000]))) base.add_child( Node.u8('gitadora_select', profile.get_int('gitadora_select', 2))) # 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 medal = { 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_CIRCLE_CLEARED, # Map approximately 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, }[score.data.get_int('medal')] clear_medal[score.id] = clear_medal[score.id] | (medal << (score.chart * 4)) 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) 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)) # Avatar section avatar_dict = profile.get_dict('avatar') avatar = Node.void('avatar') root.add_child(avatar) avatar.add_child(Node.u8('hair', avatar_dict.get_int('hair', 0))) avatar.add_child(Node.u8('face', avatar_dict.get_int('face', 0))) avatar.add_child(Node.u8('body', avatar_dict.get_int('body', 0))) avatar.add_child(Node.u8('effect', avatar_dict.get_int('effect', 0))) avatar.add_child(Node.u8('object', avatar_dict.get_int('object', 0))) avatar.add_child( Node.u8_array('comment', avatar_dict.get_int_array('comment', 2))) avatar.add_child( Node.s32_array('get_hair', avatar_dict.get_int_array('get_hair', 2))) avatar.add_child( Node.s32_array('get_face', avatar_dict.get_int_array('get_face', 2))) avatar.add_child( Node.s32_array('get_body', avatar_dict.get_int_array('get_body', 2))) avatar.add_child( Node.s32_array('get_effect', avatar_dict.get_int_array('get_effect', 2))) avatar.add_child( Node.s32_array('get_object', avatar_dict.get_int_array('get_object', 2))) avatar.add_child( Node.s32_array('get_comment_over', avatar_dict.get_int_array('get_comment_over', 3))) avatar.add_child( Node.s32_array('get_comment_under', avatar_dict.get_int_array('get_comment_under', 3))) # Avatar add section avatar_add_dict = profile.get_dict('avatar_add') avatar_add = Node.void('avatar_add') root.add_child(avatar_add) avatar_add.add_child( Node.s32_array('get_hair', avatar_add_dict.get_int_array('get_hair', 2))) avatar_add.add_child( Node.s32_array('get_face', avatar_add_dict.get_int_array('get_face', 2))) avatar_add.add_child( Node.s32_array('get_body', avatar_add_dict.get_int_array('get_body', 2))) avatar_add.add_child( Node.s32_array('get_effect', avatar_add_dict.get_int_array('get_effect', 2))) avatar_add.add_child( Node.s32_array('get_object', avatar_add_dict.get_int_array('get_object', 2))) avatar_add.add_child( Node.s32_array( 'get_comment_over', avatar_add_dict.get_int_array('get_comment_over', 2))) avatar_add.add_child( Node.s32_array( 'get_comment_under', avatar_add_dict.get_int_array('get_comment_under', 2))) avatar_add.add_child( Node.s32_array('new_hair', avatar_add_dict.get_int_array('new_hair', 2))) avatar_add.add_child( Node.s32_array('new_face', avatar_add_dict.get_int_array('new_face', 2))) avatar_add.add_child( Node.s32_array('new_body', avatar_add_dict.get_int_array('new_body', 2))) avatar_add.add_child( Node.s32_array('new_effect', avatar_add_dict.get_int_array('new_effect', 2))) avatar_add.add_child( Node.s32_array('new_object', avatar_add_dict.get_int_array('new_object', 2))) avatar_add.add_child( Node.s32_array( 'new_comment_over', avatar_add_dict.get_int_array('new_comment_over', 2))) avatar_add.add_child( Node.s32_array( 'new_comment_under', avatar_add_dict.get_int_array('new_comment_under', 2))) # 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.u8('netvs_play_cnt', 0)) for dialog in [0, 1, 2, 3, 4, 5]: # TODO: Configure this, maybe? netvs.add_child(Node.string('dialog', 'dialog#{}'.format(dialog))) sp_data = Node.void('sp_data') root.add_child(sp_data) sp_data.add_child(Node.s32('sp', profile.get_int('sp', 0))) gakuen = Node.void('gakuen_data') root.add_child(gakuen) gakuen.add_child(Node.s32('music_list', -1)) saucer = Node.void('flying_saucer') root.add_child(saucer) saucer.add_child(Node.s32('music_list', -1)) saucer.add_child(Node.s32('tune_count', -1)) saucer.add_child(Node.u32('clear_norma', 0)) saucer.add_child(Node.u32('clear_norma_add', 0)) zoo_dict = profile.get_dict('zoo') zoo = Node.void('zoo') root.add_child(zoo) zoo.add_child( Node.u16_array('point', zoo_dict.get_int_array('point', 5))) zoo.add_child( Node.s32_array('music_list', zoo_dict.get_int_array('music_list', 2))) zoo.add_child( Node.s8_array('today_play_flag', zoo_dict.get_int_array('today_play_flag', 4))) triple = Node.void('triple_journey') root.add_child(triple) triple.add_child(Node.s32('music_list', -1)) triple.add_child( Node.s32_array('boss_damage', [65534, 65534, 65534, 65534])) triple.add_child(Node.s32_array('boss_stun', [0, 0, 0, 0])) triple.add_child(Node.s32('magic_gauge', 0)) triple.add_child(Node.s32('today_party', 0)) triple.add_child(Node.bool('union_magic', False)) triple.add_child(Node.float('base_attack_rate', 1.0)) triple.add_child(Node.s32('iidx_play_num', 0)) triple.add_child(Node.s32('reflec_play_num', 0)) triple.add_child(Node.s32('voltex_play_num', 0)) triple.add_child(Node.bool('iidx_play_flg', True)) triple.add_child(Node.bool('reflec_play_flg', True)) triple.add_child(Node.bool('voltex_play_flg', True)) ios = Node.void('ios') root.add_child(ios) ios.add_child(Node.s32('continueRightAnswer', 30)) ios.add_child(Node.s32('totalRightAnswer', 30)) kac2013 = Node.void('kac2013') root.add_child(kac2013) kac2013.add_child(Node.s8('music_num', 0)) kac2013.add_child(Node.s16('music', 0)) kac2013.add_child(Node.u8('sheet', 0)) baseball = Node.void('baseball_data') root.add_child(baseball) baseball.add_child(Node.s64('music_list', -1)) for id in [3, 5, 7]: node = Node.void('floor_infection') root.add_child(node) node.add_child(Node.s32('infection_id', id)) node.add_child(Node.s32('music_list', -1)) return root
def test_format_machine_id(self) -> None: self.assertEqual(ID.format_machine_id(123), 'US-123') self.assertEqual(ID.parse_machine_id('US-123'), 123) self.assertEqual(ID.parse_machine_id('bla'), None) self.assertEqual(ID.parse_machine_id('US-blah'), None)