def handle_player23_read_score_request(self, request: Node) -> Node: refid = request.child_value('ref_id') root = Node.void('player23') userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: scores = self.data.remote.music.get_scores(self.game, self.version, userid) else: scores = [] 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.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.s16('cnt', score.plays)) return root
def handle_playerdata_usergamedata_recv_request(self, request: Node) -> Node: playerdata = Node.void('playerdata') player = Node.void('player') playerdata.add_child(player) refid = request.child_value('data/refid') userid = self.data.remote.user.from_refid(self.game, self.version, refid) if userid is not None: profile = self.get_profile(userid) links = self.data.local.user.get_links(self.game, self.version, userid) records = 0 record = Node.void('record') player.add_child(record) def acehex(val: int) -> str: return hex(val)[2:] if profile is None: # Just return a default empty node record.add_child(Node.string('d', '<NODATA>')) records = 1 else: # Figure out what profiles are being requested profiletypes = request.child_value('data/recv_csv').split(',')[::2] usergamedata = profile.get_dict('usergamedata') for ptype in profiletypes: if ptype in usergamedata: records = records + 1 if ptype == "COMMON": # Return basic profile options common = usergamedata[ptype]['strdata'].split(b',') common[self.GAME_COMMON_NAME_OFFSET] = profile.get_str('name').encode('ascii') common[self.GAME_COMMON_AREA_OFFSET] = acehex(profile.get_int('area')).encode('ascii') common[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET] = b'1' if profile.get_bool('workout_mode') else b'0' common[self.GAME_COMMON_WEIGHT_OFFSET] = str(float(profile.get_int('weight')) / 10.0).encode('ascii') common[self.GAME_COMMON_CHARACTER_OFFSET] = acehex(profile.get_int('character')).encode('ascii') usergamedata[ptype]['strdata'] = b','.join(common) if ptype == "OPTION": # Return user settings for frontend option = usergamedata[ptype]['strdata'].split(b',') option[self.GAME_OPTION_FAST_SLOW_OFFSET] = acehex(profile.get_int('early_late')).encode('ascii') option[self.GAME_OPTION_COMBO_POSITION_OFFSET] = acehex(profile.get_int('combo')).encode('ascii') option[self.GAME_OPTION_ARROW_SKIN_OFFSET] = acehex(profile.get_int('arrowskin')).encode('ascii') option[self.GAME_OPTION_GUIDELINE_OFFSET] = acehex(profile.get_int('guidelines')).encode('ascii') option[self.GAME_OPTION_FILTER_OFFSET] = acehex(profile.get_int('filter')).encode('ascii') usergamedata[ptype]['strdata'] = b','.join(option) if ptype == "LAST": # Return the number of calories expended in the last day 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]) last = usergamedata[ptype]['strdata'].split(b',') last[self.GAME_LAST_CALORIES_OFFSET] = acehex(total).encode('ascii') usergamedata[ptype]['strdata'] = b','.join(last) if ptype == "RIVAL": # Fill in the DDR code and active status of the three active # rivals. rival = usergamedata[ptype]['strdata'].split(b',') lastdict = profile.get_dict('last') friends: Dict[int, Optional[ValidatedDict]] = {} for link in links: if link.type[:7] != 'friend_': continue pos = int(link.type[7:]) friends[pos] = self.get_profile(link.other_userid) for rivalno in [1, 2, 3]: activeslot = { 1: self.GAME_RIVAL_SLOT_1_ACTIVE_OFFSET, 2: self.GAME_RIVAL_SLOT_2_ACTIVE_OFFSET, 3: self.GAME_RIVAL_SLOT_3_ACTIVE_OFFSET, }[rivalno] whichfriend = lastdict.get_int('rival{}'.format(rivalno)) - 1 if whichfriend < 0: # This rival isn't active rival[activeslot] = b'0' continue friendprofile = friends.get(whichfriend) if friendprofile is None: # This rival doesn't exist rival[activeslot] = b'0' continue ddrcodeslot = { 1: self.GAME_RIVAL_SLOT_1_DDRCODE_OFFSET, 2: self.GAME_RIVAL_SLOT_2_DDRCODE_OFFSET, 3: self.GAME_RIVAL_SLOT_3_DDRCODE_OFFSET, }[rivalno] rival[activeslot] = acehex(rivalno).encode('ascii') rival[ddrcodeslot] = acehex(friendprofile.get_int('extid')).encode('ascii') usergamedata[ptype]['strdata'] = b','.join(rival) dnode = Node.string('d', base64.b64encode(usergamedata[ptype]['strdata']).decode('ascii')) dnode.add_child(Node.string('bin1', base64.b64encode(usergamedata[ptype]['bindata']).decode('ascii'))) record.add_child(dnode) player.add_child(Node.u32('record_num', records)) playerdata.add_child(Node.s32('result', 0)) return playerdata
def handle_player_rb5_player_read_rank_5_request(self, request: Node) -> Node: # This gives us a 6-integer array mapping to user scores for the following: # [total score, basic chart score, medium chart score, hard chart score, # special chart score]. It also returns the previous rank, but this is # not used in-game as far as I can tell. current_scores = request.child_value('sc') current_minigame_score = request.child_value('mg_sc') # First, grab all scores on the network for this version. all_scores = self.data.remote.music.get_all_scores( self.game, self.version) # Now grab all participating users that had scores all_users = {userid for (userid, score) in all_scores} # Now, group the scores by user, so we can add up the totals, only including # scores where the user at least cleared the song. scores_by_user = { userid: [ score for (uid, score) in all_scores if uid == userid and score.data.get_int('clear_type') >= self.CLEAR_TYPE_CLEARED ] for userid in all_users } # Now grab all user profiles for this game all_profiles = { profile[0]: profile[1] for profile in self.data.remote.user.get_all_profiles( self.game, self.version) } # Now, sum up the scores into the five categories that the game expects. total_scores = sorted( [ sum([score.points for score in scores]) for userid, scores in scores_by_user.items() ], reverse=True, ) basic_scores = sorted( [ sum([ score.points for score in scores if score.chart == self.CHART_TYPE_BASIC ]) for userid, scores in scores_by_user.items() ], reverse=True, ) medium_scores = sorted( [ sum([ score.points for score in scores if score.chart == self.CHART_TYPE_MEDIUM ]) for userid, scores in scores_by_user.items() ], reverse=True, ) hard_scores = sorted([ sum([ score.points for score in scores if score.chart == self.CHART_TYPE_HARD ]) for userid, scores in scores_by_user.items() ], ) special_scores = sorted( [ sum([ score.points for score in scores if score.chart == self.CHART_TYPE_SPECIAL ]) for userid, scores in scores_by_user.items() ], reverse=True, ) minigame_scores = sorted( [ all_profiles.get(userid, ValidatedDict()).get_int('mgsc') for userid in all_users ], reverse=True, ) # Guarantee that a zero score is at the end of every list, so that it makes # the algorithm for figuring out place have no edge case. total_scores.append(0) basic_scores.append(0) medium_scores.append(0) hard_scores.append(0) special_scores.append(0) minigame_scores.append(0) # Now, figure out where we fit based on the scores sent from the game. user_place = [1, 1, 1, 1, 1, 1] which_score = [ total_scores, basic_scores, medium_scores, hard_scores, special_scores, minigame_scores, ] earned_scores = current_scores + [current_minigame_score] for i in range(len(user_place)): earned_score = earned_scores[i] scores = which_score[i] for score in scores: if earned_score >= score: break user_place[i] = user_place[i] + 1 # Separate out minigame rank from scores minigame_rank = user_place[-1] user_place = user_place[:-1] root = Node.void('player') # Populate current ranking. tbs = Node.void('tbs') root.add_child(tbs) tbs.add_child(Node.s32_array('new_rank', user_place)) tbs.add_child(Node.s32_array('old_rank', [-1, -1, -1, -1, -1])) # Populate current minigame ranking (LOL). mng = Node.void('mng') root.add_child(mng) mng.add_child(Node.s32('new_rank', minigame_rank)) mng.add_child(Node.s32('old_rank', -1)) return root
def handle_game_3_buy_request(self, request: Node) -> Node: refid = request.child_value('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: profile = self.get_profile(userid) else: profile = None if userid is not None and profile is not None: # Look up packets and blocks packet = profile.get_int('packet') block = profile.get_int('block') # Add on any additional we earned this round packet = packet + (request.child_value('earned_gamecoin_packet') or 0) block = block + (request.child_value('earned_gamecoin_block') or 0) currency_type = request.child_value('currency_type') price = request.child_value('item/price') if isinstance(price, list): # Sometimes we end up buying more than one item at once price = sum(price) if currency_type == self.GAME_CURRENCY_PACKETS: # This is a valid purchase newpacket = packet - price if newpacket < 0: result = 1 else: packet = newpacket result = 0 elif currency_type == self.GAME_CURRENCY_BLOCKS: # This is a valid purchase newblock = block - price if newblock < 0: result = 1 else: block = newblock result = 0 else: # Bad currency type result = 1 if result == 0: # Transaction is valid, update the profile with new packets and blocks profile.replace_int('packet', packet) profile.replace_int('block', block) self.put_profile(userid, profile) # If this was a song unlock, we should mark it as unlocked item_type = request.child_value('item/item_type') item_id = request.child_value('item/item_id') param = request.child_value('item/param') if not isinstance(item_type, list): # Sometimes we buy multiple things at once. Make it easier by always assuming this. item_type = [item_type] item_id = [item_id] param = [param] for i in range(len(item_type)): self.data.local.user.put_achievement( self.game, self.version, userid, item_id[i], f'item_{item_type[i]}', { 'param': param[i], }, ) else: # Unclear what to do here, return a bad response packet = 0 block = 0 result = 1 game = Node.void('game_3') game.add_child(Node.u32('gamecoin_packet', packet)) game.add_child(Node.u32('gamecoin_block', block)) game.add_child(Node.s8('result', result)) return game
def handle_player_read_request(self, request: Node) -> Node: refid = request.child_value('rid') profile = self.get_profile_by_refid(refid) if profile: return profile return Node.void('player')
def __verify_profile(self, resp: Node) -> int: for item in [ 'jubility', 'jubility_yday', 'tune_cnt', 'save_cnt', 'saved_cnt', 'fc_cnt', 'ex_cnt', 'pf_cnt', 'clear_cnt', 'match_cnt', 'beat_cnt', 'mynews_cnt', 'extra_point', 'is_extra_played', 'inherit', 'mtg_entry_cnt', 'mtg_hold_cnt', 'mtg_result', ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") for item in [ 'secret_list', 'title_list', 'theme_list', 'marker_list', 'parts_list', 'new/secret_list', 'new/title_list', 'new/theme_list', 'new/marker_list', ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ 'music_id', 'marker', 'title', 'theme', 'sort', 'rank_sort', 'combo_disp', 'seq_id', 'parts', 'category', 'play_time', 'expert_option', 'matching', 'hazard', 'hard', 'shopname', 'areaname', ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") # Misc stuff self.assert_path(resp, "response/gametop/data/player/session_id") self.assert_path(resp, "response/gametop/data/player/event_flag") self.assert_path(resp, "response/gametop/data/player/only_now_music") self.assert_path(resp, "response/gametop/data/player/lab_edit_seq") self.assert_path(resp, "response/gametop/data/player/kac_music") self.assert_path(resp, "response/gametop/data/player/rivallist") self.assert_path(resp, "response/gametop/data/player/share_music") self.assert_path(resp, "response/gametop/data/player/bonus_music") self.assert_path(resp, "response/gametop/data/player/history") self.assert_path(resp, "response/gametop/data/player/news/checked") self.assert_path(resp, "response/gametop/data/player/group/group_id") self.assert_path(resp, "response/gametop/data/player/bingo/reward/total") self.assert_path(resp, "response/gametop/data/player/bingo/reward/point") self.assert_path( resp, "response/gametop/data/player/challenge/today/music_id") self.assert_path(resp, "response/gametop/data/player/challenge/today/state") self.assert_path( resp, "response/gametop/data/player/challenge/whim/music_id") self.assert_path(resp, "response/gametop/data/player/challenge/whim/state") # Profile settings self.assert_path(resp, "response/gametop/data/player/name") self.assert_path(resp, "response/gametop/data/player/jid") self.assert_path(resp, "response/gametop/data/player/refid") # Non-player stuff self.assert_path(resp, "response/gametop/data/termver") self.assert_path(resp, "response/gametop/data/season_etime") self.assert_path(resp, "response/gametop/data/white_music_list") self.assert_path(resp, "response/gametop/data/open_music_list") # Return the jid return resp.child_value('gametop/data/player/jid')
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.music_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 handle_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) 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'), 'tid': request.child_value('e/tid'), 'tn': request.child_value('e/tn'), 'topt': request.child_value('e/topt'), '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'), }) 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.string('pn', profile.get_str('name'))) e.add_child(Node.s32('uattr', profile.get_int('uattr'))) e.add_child(Node.s32('mopt', lobby.get_int('mopt'))) e.add_child(Node.s16('mg', profile.get_int('mg'))) e.add_child(Node.s32('tid', lobby.get_int('tid'))) e.add_child(Node.string('tn', lobby.get_str('tn'))) e.add_child(Node.s32('topt', lobby.get_int('topt'))) 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))) return root
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) # Update experience and in-game currencies earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') if earned_gamecoin_packet is not None: newprofile.replace_int('packet', newprofile.get_int('packet') + earned_gamecoin_packet) earned_gamecoin_block = request.child_value('earned_gamecoin_block') if earned_gamecoin_block is not None: newprofile.replace_int('block', newprofile.get_int('block') + earned_gamecoin_block) gain_exp = request.child_value('gain_exp') if gain_exp is not None: newprofile.replace_int('exp', newprofile.get_int('exp') + gain_exp) # Miscelaneous stuff newprofile.replace_int('m_user_cnt', request.child_value('m_user_cnt')) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() if not game_config.get_bool('force_unlock_cards'): have_item = request.child_value('have_item') if have_item is not None: newprofile.replace_int_array('have_item', 512, [1 if x else 0 for x in have_item]) if not game_config.get_bool('force_unlock_songs'): have_note = request.child_value('have_note') if have_note is not None: newprofile.replace_int_array('have_note', 512, [1 if x else 0 for x in have_note]) # Grab last information. lastdict = newprofile.get_dict('last') lastdict.replace_int('headphone', request.child_value('headphone')) lastdict.replace_int('hispeed', request.child_value('hispeed')) lastdict.replace_int('appeal_id', request.child_value('appeal_id')) lastdict.replace_int('frame0', request.child_value('frame0')) lastdict.replace_int('frame1', request.child_value('frame1')) lastdict.replace_int('frame2', request.child_value('frame2')) lastdict.replace_int('frame3', request.child_value('frame3')) lastdict.replace_int('frame4', request.child_value('frame4')) last = request.child('last') if last is not None: lastdict.replace_int('music_id', intish(last.attribute('music_id'))) lastdict.replace_int('music_type', intish(last.attribute('music_type'))) lastdict.replace_int('sort_type', intish(last.attribute('sort_type'))) # Save back last information gleaned from results newprofile.replace_dict('last', lastdict) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: # For some reason, Pop'n 20 sends us two profile saves, one with 'not done yet' # so we only want to process the done yet node. The 'not gameover' save has # jubeat collabo stuff set in it, but we don't use that so it doesn't matter. if request.child_value('is_not_gameover') == 1: return oldprofile newprofile = copy.deepcopy(oldprofile) newprofile.replace_int('option', request.child_value('option')) newprofile.replace_int('chara', request.child_value('chara')) newprofile.replace_int('mode', request.child_value('mode')) newprofile.replace_int('button', request.child_value('button')) newprofile.replace_int('music', request.child_value('music')) newprofile.replace_int('sheet', request.child_value('sheet')) newprofile.replace_int('last_play_flag', request.child_value('last_play_flag')) newprofile.replace_int('category', request.child_value('category')) newprofile.replace_int('sub_category', request.child_value('sub_category')) newprofile.replace_int('chara_category', request.child_value('chara_category')) newprofile.replace_int('medal_and_friend', request.child_value('medal_and_friend')) newprofile.replace_int('ep', request.child_value('ep')) newprofile.replace_int_array('sp_color_flg', 2, request.child_value('sp_color_flg')) newprofile.replace_int('read_news', request.child_value('read_news')) newprofile.replace_int('consecutive_days_coupon', request.child_value('consecutive_days_coupon')) newprofile.replace_int('tutorial', request.child_value('tutorial')) newprofile.replace_int('music_open_pt', request.child_value('music_open_pt')) newprofile.replace_int('collabo', request.child_value('collabo')) sp_node = request.child('sp_data') if sp_node is not None: newprofile.replace_int('sp', sp_node.child_value('sp')) reflec_node = request.child('reflec_data') if reflec_node is not None: newprofile.replace_int_array('reflec', 2, reflec_node.child_value('reflec')) # Keep track of play statistics self.update_play_statistics(userid) # Extract player card stuff player_card_dict = newprofile.get_dict('player_card') player_card_dict.replace_int_array('title', 2, request.child_value('title')) player_card_dict.replace_int('frame', request.child_value('frame')) player_card_dict.replace_int('base', request.child_value('base')) player_card_dict.replace_int_array('seal', 2, request.child_value('seal')) player_card_dict.replace_int_array('get_title', 4, request.child_value('get_title')) player_card_dict.replace_int('get_frame', request.child_value('get_frame')) player_card_dict.replace_int('get_base', request.child_value('get_base')) player_card_dict.replace_int_array('get_seal', 2, request.child_value('get_seal')) player_card_ex = request.child('player_card_ex') if player_card_ex is not None: player_card_dict.replace_int('get_title_ex', player_card_ex.child_value('get_title_ex')) player_card_dict.replace_int('get_frame_ex', player_card_ex.child_value('get_frame_ex')) player_card_dict.replace_int('get_base_ex', player_card_ex.child_value('get_base_ex')) player_card_dict.replace_int('get_seal_ex', player_card_ex.child_value('get_seal_ex')) newprofile.replace_dict('player_card', player_card_dict) # Extract navigate stuff navigate_dict = newprofile.get_dict('navigate') navigate = request.child('navigate') if navigate is not None: navigate_dict.replace_int('genre', navigate.child_value('genre')) navigate_dict.replace_int('image', navigate.child_value('image')) navigate_dict.replace_int('level', navigate.child_value('level')) navigate_dict.replace_int('ojama', navigate.child_value('ojama')) navigate_dict.replace_int('limit_num', navigate.child_value('limit_num')) navigate_dict.replace_int('button', navigate.child_value('button')) navigate_dict.replace_int('life', navigate.child_value('life')) navigate_dict.replace_int('progress', navigate.child_value('progress')) newprofile.replace_dict('navigate', navigate_dict) # Extract scores for node in request.children: if node.name == 'stage': songid = node.child_value('no') 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, }[node.child_value('sheet')] medal = (node.child_value('n_data') >> (chart * 4)) & 0x000F 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_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] points = node.child_value('score') self.update_score(userid, songid, chart, points, medal) return newprofile
def handle_game_buy_request(self, request: Node) -> Node: refid = request.attribute('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None if userid is not None: profile = self.get_profile(userid) else: profile = None if userid is not None and profile is not None: # Look up packets and blocks packet = profile.get_int('packet') block = profile.get_int('block') # Add on any additional we earned this round packet = packet + (request.child_value('earned_gamecoin_packet') or 0) block = block + (request.child_value('earned_gamecoin_block') or 0) # Look up the item to get the actual price and currency used item = self.data.local.game.get_item(self.game, self.version, request.child_value('catalog_id'), 'song_unlock') if item is not None: currency_type = request.child_value('currency_type') if currency_type == self.GAME_CURRENCY_PACKETS: if 'packets' in item: # This is a valid purchase newpacket = packet - item.get_int('packets') if newpacket < 0: result = 1 else: packet = newpacket result = 0 else: # Bad transaction result = 1 elif currency_type == self.GAME_CURRENCY_BLOCKS: if 'blocks' in item: # This is a valid purchase newblock = block - item.get_int('blocks') if newblock < 0: result = 1 else: block = newblock result = 0 else: # Bad transaction result = 1 else: # Bad currency type result = 1 if result == 0: # Transaction is valid, update the profile with new packets and blocks profile.replace_int('packet', packet) profile.replace_int('block', block) self.put_profile(userid, profile) else: # Bad catalog ID result = 1 else: # Unclear what to do here, return a bad response packet = 0 block = 0 result = 1 game = Node.void('game') game.add_child(Node.u32('gamecoin_packet', packet)) game.add_child(Node.u32('gamecoin_block', block)) game.add_child(Node.s8('result', result)) return game
def handle_pcb23_write_request(self, request: Node) -> Node: # Update the name of this cab for admin purposes self.update_machine_name(request.child_value('pcb_setting/name')) return Node.void('pcb23')
def __verify_profile(self, resp: Node) -> int: self.assert_path(resp, "response/gametop/data/info/event_info") self.assert_path(resp, "response/gametop/data/info/share_music") self.assert_path(resp, "response/gametop/data/info/bonus_music") self.assert_path(resp, "response/gametop/data/info/only_now_music") self.assert_path(resp, "response/gametop/data/info/fc_challenge/today/music_id") self.assert_path(resp, "response/gametop/data/info/white_music_list") self.assert_path(resp, "response/gametop/data/info/open_music_list") self.assert_path(resp, "response/gametop/data/info/cabinet_survey/id") self.assert_path(resp, "response/gametop/data/info/cabinet_survey/status") self.assert_path(resp, "response/gametop/data/info/kaitou_bisco/remaining_days") self.assert_path(resp, "response/gametop/data/info/league/status") self.assert_path(resp, "response/gametop/data/info/bistro/bistro_id") self.assert_path(resp, "response/gametop/data/info/jbox/point") self.assert_path(resp, "response/gametop/data/info/jbox/emblem/normal/index") self.assert_path(resp, "response/gametop/data/info/jbox/emblem/premium/index") for item in [ 'jubility', 'jubility_yday', 'tune_cnt', 'save_cnt', 'saved_cnt', 'fc_cnt', 'ex_cnt', 'clear_cnt', 'pf_cnt', 'match_cnt', 'beat_cnt', 'mynews_cnt', 'bonus_tune_points', 'is_bonus_tune_played', 'inherit', 'mtg_entry_cnt', 'mtg_hold_cnt', 'mtg_result', ]: self.assert_path(resp, f"response/gametop/data/player/info/{item}") for item in [ 'music_list', 'secret_list', 'theme_list', 'marker_list', 'title_list', 'parts_list', 'emblem_list', 'new/secret_list', 'new/theme_list', 'new/marker_list', ]: self.assert_path(resp, f"response/gametop/data/player/item/{item}") for item in [ 'play_time', 'shopname', 'areaname', 'expert_option', 'category', 'sort', 'music_id', 'seq_id', ]: self.assert_path(resp, f"response/gametop/data/player/last/{item}") for item in [ 'marker', 'theme', 'title', 'parts', 'rank_sort', 'combo_disp', 'emblem', 'matching', 'hazard', 'hard', ]: self.assert_path(resp, f"response/gametop/data/player/last/settings/{item}") # Misc stuff self.assert_path(resp, "response/gametop/data/player/session_id") self.assert_path(resp, "response/gametop/data/player/event_flag") # Profile settings self.assert_path(resp, "response/gametop/data/player/name") self.assert_path(resp, "response/gametop/data/player/jid") # Required nodes for events and stuff self.assert_path(resp, "response/gametop/data/player/history") self.assert_path(resp, "response/gametop/data/player/lab_edit_seq") self.assert_path(resp, "response/gametop/data/player/event_info") self.assert_path(resp, "response/gametop/data/player/cabinet_survey/read_flag") self.assert_path(resp, "response/gametop/data/player/kaitou_bisco/read_flag") self.assert_path(resp, "response/gametop/data/player/navi/flag") self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/music_id") self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/state") self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/music_id") self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/state") self.assert_path(resp, "response/gametop/data/player/news/checked") self.assert_path(resp, "response/gametop/data/player/news/checked_flag") self.assert_path(resp, "response/gametop/data/player/rivallist") self.assert_path(resp, "response/gametop/data/player/free_first_play/is_available") self.assert_path(resp, "response/gametop/data/player/free_first_play/point") self.assert_path(resp, "response/gametop/data/player/free_first_play/point_used") self.assert_path(resp, "response/gametop/data/player/free_first_play/come_come_jbox/is_valid") self.assert_path(resp, "response/gametop/data/player/free_first_play/come_come_jbox/end_time_if_paired") self.assert_path(resp, "response/gametop/data/player/jbox/point") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/normal/index") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/premium/index") self.assert_path(resp, "response/gametop/data/player/career/level") self.assert_path(resp, "response/gametop/data/player/career/point") self.assert_path(resp, "response/gametop/data/player/career/param") self.assert_path(resp, "response/gametop/data/player/career/is_unlocked") self.assert_path(resp, "response/gametop/data/player/league/is_first_play") self.assert_path(resp, "response/gametop/data/player/league/class") self.assert_path(resp, "response/gametop/data/player/league/subclass") self.assert_path(resp, "response/gametop/data/player/new_music") self.assert_path(resp, "response/gametop/data/player/eapass_privilege/emblem_list") self.assert_path(resp, "response/gametop/data/player/bonus_music/music") self.assert_path(resp, "response/gametop/data/player/bonus_music/event_id") self.assert_path(resp, "response/gametop/data/player/bonus_music/till_time") self.assert_path(resp, "response/gametop/data/player/bistro/chef/id") self.assert_path(resp, "response/gametop/data/player/bistro/carry_over") self.assert_path(resp, "response/gametop/data/player/bistro/route_list/route_count") self.assert_path(resp, "response/gametop/data/player/bistro/extension") self.assert_path(resp, "response/gametop/data/player/gift_list") # Return the jid return resp.child_value('gametop/data/player/jid')
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, -1, -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)) vocaloid_event2 = Node.void('vocaloid_event2') data.add_child(vocaloid_event2) vocaloid_event2.add_child(Node.u8('state', 0)) vocaloid_event2.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)) tenka = Node.void('tenka') data.add_child(tenka) tenka.add_child(Node.bool('is_participant', False)) return shopinfo
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) play_stats = self.get_play_statistics(userid) # Grab last node and accessories so we can make decisions based on type last = request.child('last') lastdict = newprofile.get_dict('last') mode = int(last.attribute('mode')) style = int(last.attribute('style')) is_dp = style == self.GAME_STYLE_DOUBLE # Drill rankings title = request.child('title') title_gr = request.child('title_gr') titledict = newprofile.get_dict('title') title_grdict = newprofile.get_dict('title_gr') # Groove radar level ups gr = request.child('gr') # Set the correct values depending on if we're single or double play if is_dp: play_stats.increment_int('double_plays') if gr is not None: newprofile.replace_int_array( 'gr_d', 5, [ intish(gr.attribute('gr1')), intish(gr.attribute('gr2')), intish(gr.attribute('gr3')), intish(gr.attribute('gr4')), intish(gr.attribute('gr5')), ], ) if title is not None: titledict.replace_int('d', title.value) newprofile.replace_dict('title', titledict) if title_gr is not None: title_grdict.replace_int('d', title.value) newprofile.replace_dict('title_gr', title_grdict) else: play_stats.increment_int('single_plays') if gr is not None: newprofile.replace_int_array( 'gr_s', 5, [ intish(gr.attribute('gr1')), intish(gr.attribute('gr2')), intish(gr.attribute('gr3')), intish(gr.attribute('gr4')), intish(gr.attribute('gr5')), ], ) if title is not None: titledict.replace_int('s', title.value) newprofile.replace_dict('title', titledict) if title_gr is not None: title_grdict.replace_int('s', title.value) newprofile.replace_dict('title_gr', title_grdict) play_stats.increment_int('cnt_m{}'.format(mode)) # Update last attributes lastdict.replace_int('fri', intish(last.attribute('fri'))) lastdict.replace_int('style', intish(last.attribute('style'))) lastdict.replace_int('mode', intish(last.attribute('mode'))) lastdict.replace_int('cate', intish(last.attribute('cate'))) lastdict.replace_int('sort', intish(last.attribute('sort'))) lastdict.replace_int('mid', intish(last.attribute('mid'))) lastdict.replace_int('mtype', intish(last.attribute('mtype'))) lastdict.replace_int('cid', intish(last.attribute('cid'))) lastdict.replace_int('ctype', intish(last.attribute('ctype'))) lastdict.replace_int('sid', intish(last.attribute('sid'))) newprofile.replace_dict('last', lastdict) # Grab character options chara = request.child('chara') if chara is not None: newprofile.replace_int('chara', intish(chara.attribute('my'))) newprofile.replace_int_array('chara_opt', 96, request.child_value('chara_opt')) # Track event progress event = request.child('event') if event is not None: event_dict = newprofile.get_dict('event') event_dict.replace_int('diff_sum', intish(event.attribute('diff_sum'))) event_dict.replace_int('e_flags', intish(event.attribute('e_flags'))) event_dict.replace_int('welcome', intish(event.attribute('welcome'))) newprofile.replace_dict('event', event_dict) e_panel = request.child('e_panel') if e_panel is not None: e_panel_dict = newprofile.get_dict('e_panel') e_panel_dict.replace_int('play_id', intish(e_panel.attribute('play_id'))) e_panel_dict.replace_int_array('cell', 24, e_panel.child_value('cell')) e_panel_dict.replace_int_array('panel_state', 6, e_panel.child_value('panel_state')) newprofile.replace_dict('e_panel', e_panel_dict) e_pix = request.child('e_pix') if e_pix is not None: e_pix_dict = newprofile.get_dict('e_pix') max_distance = e_pix_dict.get_int('max_distance') max_planet = e_pix_dict.get_int('max_planet') total_distance = e_pix_dict.get_int('total_distance') total_planet = e_pix_dict.get_int('total_planet') cur_distance = intish(e_pix.attribute('this_distance')) cur_planet = intish(e_pix.attribute('this_planet')) if cur_distance is not None: max_distance = max(max_distance, cur_distance) total_distance += cur_distance if cur_planet is not None: max_planet = max(max_planet, cur_planet) total_planet += cur_planet e_pix_dict.replace_int('max_distance', max_distance) e_pix_dict.replace_int('max_planet', max_planet) e_pix_dict.replace_int('total_distance', total_distance) e_pix_dict.replace_int('total_planet', total_planet) e_pix_dict.replace_int('flags', intish(e_pix.attribute('flags'))) newprofile.replace_dict('e_pix', e_pix_dict) # Options opt = request.child('opt') if opt is not None: # A bug in old versions of AVS returns the wrong number for set newprofile.replace_int_array('opt', 16, opt.value[:16]) # Experience and stars exp = request.child_value('exp') if exp is not None: play_stats.replace_int('exp', play_stats.get_int('exp') + exp) star = request.child_value('star') if star is not None: newprofile.replace_int('star', newprofile.get_int('star') + star) star_c = request.child_value('star_c') if star_c is not None: newprofile.replace_int('star_c', newprofile.get_int('star_c') + exp) # Update game flags for child in request.children: if child.name != 'flag': continue try: value = int(child.attribute('data')) offset = int(child.attribute('no')) except ValueError: continue flags = newprofile.get_int_array('flag', 256, [1] * 256) if offset < 0 or offset >= len(flags): continue flags[offset] = value newprofile.replace_int_array('flag', 256, flags) # Workout mode support newweight = -1 oldweight = newprofile.get_int('weight') for child in request.children: if child.name != 'weight': continue newweight = child.value if newweight < 0: newweight = oldweight # Either update or unset the weight depending on the game if newweight == 0: # Weight is unset or we declined to use this feature, remove from profile if 'weight' in newprofile: del newprofile['weight'] else: # Weight has been set or previously retrieved, we should save calories newprofile.replace_int('weight', newweight) total = 0 for child in request.children: if child.name != 'calory': continue total += child.value self.data.local.user.put_time_based_achievement( self.game, self.version, userid, 0, 'workout', { 'calories': total, 'weight': newweight, }, ) # Look up old friends oldfriends: List[Optional[UserID]] = [None] * 10 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:]) oldfriends[pos] = link.other_userid # Save any rivals that were added/removed/changed newfriends = oldfriends[:] for child in request.children: if child.name != 'friend': continue code = int(child.attribute('code')) pos = int(child.attribute('pos')) if pos >= 0 and pos < 10: if code == 0: # We cleared this friend newfriends[pos] = None else: # Try looking up the userid newfriends[pos] = self.data.remote.user.from_extid(self.game, self.version, code) # Diff the set of links to determine updates for i in range(10): if newfriends[i] == oldfriends[i]: continue if newfriends[i] is None: # Kill the rival in this location self.data.local.user.destroy_link( self.game, self.version, userid, 'friend_{}'.format(i), oldfriends[i], ) elif oldfriends[i] is None: # Add rival in this location self.data.local.user.put_link( self.game, self.version, userid, 'friend_{}'.format(i), newfriends[i], {}, ) else: # Changed the rival here, kill the old one, add the new one self.data.local.user.destroy_link( self.game, self.version, userid, 'friend_{}'.format(i), oldfriends[i], ) self.data.local.user.put_link( self.game, self.version, userid, 'friend_{}'.format(i), newfriends[i], {}, ) # Keep track of play statistics self.update_play_statistics(userid, play_stats) return newprofile
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 handle_sysinfo_fan_request(self, request: Node) -> Node: sysinfo = Node.void('sysinfo') sysinfo.add_child(Node.u8('pref', 51)) sysinfo.add_child(Node.string('lid', request.child_value('lid'))) return sysinfo
def handle_lobby_delete_request(self, request: Node) -> Node: eid = request.child_value('eid') self.data.local.lobby.destroy_lobby(eid) return Node.void('lobby')
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, f'item_{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 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, 'item_{}'.format(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 __verify_profile(self, resp: Node) -> int: self.assert_path(resp, "response/gametop/data/info/event_info") self.assert_path(resp, "response/gametop/data/info/share_music") self.assert_path(resp, "response/gametop/data/info/bonus_music") self.assert_path(resp, "response/gametop/data/info/white_music_list") self.assert_path(resp, "response/gametop/data/info/white_marker_list") self.assert_path(resp, "response/gametop/data/info/white_theme_list") self.assert_path(resp, "response/gametop/data/info/open_music_list") self.assert_path(resp, "response/gametop/data/info/shareable_music_list") self.assert_path(resp, "response/gametop/data/info/jbox/point") self.assert_path(resp, "response/gametop/data/info/jbox/emblem/normal/index") self.assert_path(resp, "response/gametop/data/info/jbox/emblem/premium/index") self.assert_path(resp, "response/gametop/data/info/born/status") self.assert_path(resp, "response/gametop/data/info/born/year") self.assert_path(resp, "response/gametop/data/info/digdig/stage_list") self.assert_path(resp, "response/gametop/data/info/collection/rating_s") self.assert_path(resp, "response/gametop/data/info/generic_dig/map_list") for item in [ 'jubility', 'jubility_yday', 'tune_cnt', 'save_cnt', 'saved_cnt', 'fc_cnt', 'ex_cnt', 'clear_cnt', 'match_cnt', 'beat_cnt', 'mynews_cnt', 'bonus_tune_points', 'is_bonus_tune_played', 'inherit', 'mtg_entry_cnt', 'mtg_hold_cnt', 'mtg_result', ]: self.assert_path(resp, "response/gametop/data/player/info/{}".format(item)) for item in [ 'music_list', 'secret_list', 'theme_list', 'marker_list', 'title_list', 'parts_list', 'emblem_list', 'new/secret_list', 'new/theme_list', 'new/marker_list', ]: self.assert_path(resp, "response/gametop/data/player/item/{}".format(item)) for item in [ 'play_time', 'shopname', 'areaname', 'expert_option', 'category', 'sort', 'music_id', 'seq_id', ]: self.assert_path(resp, "response/gametop/data/player/last/{}".format(item)) for item in [ 'marker', 'theme', 'title', 'parts', 'rank_sort', 'combo_disp', 'emblem', 'matching', 'hazard', 'hard', ]: self.assert_path(resp, "response/gametop/data/player/last/settings/{}".format(item)) # Misc stuff self.assert_path(resp, "response/gametop/data/player/session_id") self.assert_path(resp, "response/gametop/data/player/event_flag") # Profile settings self.assert_path(resp, "response/gametop/data/player/name") self.assert_path(resp, "response/gametop/data/player/jid") # Required nodes for events and stuff self.assert_path(resp, "response/gametop/data/player/history") self.assert_path(resp, "response/gametop/data/player/lab_edit_seq") self.assert_path(resp, "response/gametop/data/player/event_info") self.assert_path(resp, "response/gametop/data/player/navi/flag") self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/music_id") self.assert_path(resp, "response/gametop/data/player/fc_challenge/today/state") self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/music_id") self.assert_path(resp, "response/gametop/data/player/fc_challenge/whim/state") self.assert_path(resp, "response/gametop/data/player/news/checked") self.assert_path(resp, "response/gametop/data/player/news/checked_flag") self.assert_path(resp, "response/gametop/data/player/rivallist") self.assert_path(resp, "response/gametop/data/player/free_first_play/is_available") self.assert_path(resp, "response/gametop/data/player/jbox/point") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/normal/index") self.assert_path(resp, "response/gametop/data/player/jbox/emblem/premium/index") self.assert_path(resp, "response/gametop/data/player/new_music") self.assert_path(resp, "response/gametop/data/player/gift_list") self.assert_path(resp, "response/gametop/data/player/born/status") self.assert_path(resp, "response/gametop/data/player/born/year") self.assert_path(resp, "response/gametop/data/player/generic_dig/map_list") self.assert_path(resp, "response/gametop/data/player/unlock/main/stage_list") self.assert_path(resp, "response/gametop/data/player/digdig/flag") self.assert_path(resp, "response/gametop/data/player/digdig/main/stage/point") self.assert_path(resp, "response/gametop/data/player/digdig/main/stage/param") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/ratio") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/used_point") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/point") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/excavated_point") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/state") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/item/kind") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/item/value") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/till_time") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/kind") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/value") self.assert_path(resp, "response/gametop/data/player/digdig/eternal/cube/norma/param") # Return the jid return resp.child_value('gametop/data/player/jid')
def unformat_profile(self, userid: UserID, request: Node, oldprofile: ValidatedDict) -> ValidatedDict: newprofile = copy.deepcopy(oldprofile) # Update blaster energy and in-game currencies earned_gamecoin_packet = request.child_value('earned_gamecoin_packet') if earned_gamecoin_packet is not None: newprofile.replace_int( 'packet', newprofile.get_int('packet') + earned_gamecoin_packet) earned_gamecoin_block = request.child_value('earned_gamecoin_block') if earned_gamecoin_block is not None: newprofile.replace_int( 'block', newprofile.get_int('block') + earned_gamecoin_block) earned_blaster_energy = request.child_value('earned_blaster_energy') if earned_blaster_energy is not None: newprofile.replace_int( 'blaster_energy', newprofile.get_int('blaster_energy') + earned_blaster_energy) # Miscelaneous stuff newprofile.replace_int('blaster_count', request.child_value('blaster_count')) newprofile.replace_int('skill_name_id', request.child_value('skill_name_id')) newprofile.replace_int_array('hidden_param', 20, request.child_value('hidden_param')) # Update user's unlock status if we aren't force unlocked game_config = self.get_game_config() if request.child('item') is not None: for child in request.child('item').children: if child.name != 'info': continue item_id = child.child_value('id') item_type = child.child_value('type') param = child.child_value('param') diff_param = child.child_value('diff_param') if game_config.get_bool( 'force_unlock_songs' ) and item_type == self.GAME_CATALOG_TYPE_SONG: # Don't save back songs, because they were force unlocked continue if diff_param is not None: paramvals = { 'diff_param': diff_param, 'param': param, } else: paramvals = { 'param': param, } self.data.local.user.put_achievement( self.game, self.version, userid, item_id, f'item_{item_type}', paramvals, ) # Grab last information. lastdict = newprofile.get_dict('last') lastdict.replace_int('headphone', request.child_value('headphone')) lastdict.replace_int('appeal_id', request.child_value('appeal_id')) lastdict.replace_int('comment_id', request.child_value('comment_id')) lastdict.replace_int('music_id', request.child_value('music_id')) lastdict.replace_int('music_type', request.child_value('music_type')) lastdict.replace_int('sort_type', request.child_value('sort_type')) lastdict.replace_int('narrow_down', request.child_value('narrow_down')) lastdict.replace_int('gauge_option', request.child_value('gauge_option')) # Save back last information gleaned from results newprofile.replace_dict('last', lastdict) # Keep track of play statistics self.update_play_statistics(userid) return newprofile
def __handle_usersave(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: if userid is None: # the game sends us empty user ID strings when a guest is playing. # Return early so it doesn't wait a minute and a half to show the # results screen. return if requestdata.child_value('isgameover'): style = int(requestdata.child_value('playstyle')) is_dp = style == self.GAME_STYLE_DOUBLE # We don't save anything for gameover requests, since we # already saved scores on individual ones. So, just use this # as a spot to bump play counts and such play_stats = self.get_play_statistics(userid) if is_dp: play_stats.increment_int('double_plays') else: play_stats.increment_int('single_plays') self.update_play_statistics(userid, play_stats) # Now is a good time to check if we have workout mode enabled, # and if so, store the calories earned for this set. profile = self.get_profile(userid) enabled = profile.get_bool('workout_mode') weight = profile.get_int('weight') if enabled and weight > 0: # We enabled weight display, find the calories and save them total = 0 for child in requestdata.children: if child.name != 'note': continue total = total + (child.child_value('calorie') or 0) self.data.local.user.put_time_based_achievement( self.game, self.version, userid, 0, 'workout', { 'calories': total, 'weight': weight, }, ) # Find any event updates for child in requestdata.children: if child.name != 'event': continue # Skip empty events or events we don't support eventid = child.child_value('eventid') eventtype = child.child_value('eventtype') if eventid == 0 or eventtype == 0: continue # Save data to replay to the client later completed = child.child_value('comptime') != 0 progress = child.child_value('savedata') self.data.local.user.put_achievement( self.game, self.version, userid, eventid, str(eventtype), { 'completed': completed, 'progress': progress, }, ) return # Find the highest stagenum played score = None stagenum = 0 for child in requestdata.children: if child.name != 'note': continue if child.child_value('stagenum') > stagenum: score = child stagenum = child.child_value('stagenum') if score is None: raise Exception('Couldn\'t find newest score to save!') songid = score.child_value('mcode') chart = self.game_to_db_chart(score.child_value('notetype')) rank = self.game_to_db_rank(score.child_value('rank')) halo = self.game_to_db_halo(score.child_value('clearkind')) points = score.child_value('score') combo = score.child_value('maxcombo') ghost = score.child_value('ghost') self.update_score( userid, songid, chart, points, rank, halo, combo, ghost=ghost, )
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 __handle_rivalload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: data = Node.void('data') response.add_child(data) data.add_child(Node.s32('recordtype', requestdata.child_value('loadflag'))) thismachine = self.data.local.machine.get_machine(self.config['machine']['pcbid']) machines_by_id: Dict[int, Optional[Machine]] = {thismachine.id: thismachine} loadkind = requestdata.child_value('loadflag') profiles_by_userid: Dict[UserID, ValidatedDict] = {} def get_machine(lid: int) -> Optional[Machine]: if lid not in machines_by_id: pcbid = self.data.local.machine.from_machine_id(lid) if pcbid is None: machines_by_id[lid] = None return None machine = self.data.local.machine.get_machine(pcbid) if machine is None: machines_by_id[lid] = None return None machines_by_id[lid] = machine return machines_by_id[lid] if loadkind == self.GAME_RIVAL_TYPE_WORLD: # Just load all scores for this network scores = self.data.remote.music.get_all_records(self.game, self.music_version) elif loadkind == self.GAME_RIVAL_TYPE_AREA: if thismachine.arcade is not None: match_arcade = thismachine.arcade match_machine = None else: match_arcade = None match_machine = thismachine.id # Load up all scores by any user registered on a machine in the same arcade profiles = self.data.local.user.get_all_profiles(self.game, self.version) userids: List[UserID] = [] for userid, profiledata in profiles: profiles_by_userid[userid] = profiledata # If we have an arcade to match, see if this user's location matches the arcade. # If we don't, just match lid directly if match_arcade is not None: theirmachine = get_machine(profiledata.get_int('lid')) if theirmachine is not None and theirmachine.arcade == match_arcade: userids.append(userid) elif match_machine is not None: if profiledata.get_int('lid') == match_machine: userids.append(userid) # Load all scores for users in the area scores = self.data.local.music.get_all_records(self.game, self.music_version, userlist=userids) elif loadkind == self.GAME_RIVAL_TYPE_MACHINE: # Load up all scores and filter them by those earned at this location scores = self.data.local.music.get_all_records(self.game, self.music_version, locationlist=[thismachine.id]) elif loadkind in [ self.GAME_RIVAL_TYPE_RIVAL1, self.GAME_RIVAL_TYPE_RIVAL2, self.GAME_RIVAL_TYPE_RIVAL3, ]: # Load up this user's highscores, format the way the below code expects it extid = requestdata.child_value('ddrcode') otherid = self.data.remote.user.from_extid(self.game, self.version, extid) userscores = self.data.remote.music.get_scores(self.game, self.music_version, otherid) scores = [(otherid, score) for score in userscores] else: # Nothing here scores = [] missing_users = [userid for (userid, _) in scores if userid not in profiles_by_userid] for (userid, profile) in self.get_any_profiles(missing_users): profiles_by_userid[userid] = profile for userid, score in scores: if profiles_by_userid.get(userid) is None: raise Exception('Logic error, couldn\'t find any profile for {}'.format(userid)) profiledata = profiles_by_userid[userid] record = Node.void('record') data.add_child(record) record.add_child(Node.u32('mcode', score.id)) record.add_child(Node.u8('notetype', self.db_to_game_chart(score.chart))) record.add_child(Node.u8('rank', self.db_to_game_rank(score.data.get_int('rank')))) record.add_child(Node.u8('clearkind', self.db_to_game_halo(score.data.get_int('halo')))) record.add_child(Node.u8('flagdata', 0)) record.add_child(Node.string('name', profiledata.get_str('name'))) record.add_child(Node.s32('area', profiledata.get_int('area', 58))) record.add_child(Node.s32('code', profiledata.get_int('extid'))) record.add_child(Node.s32('score', score.points)) record.add_child(Node.s32('ghostid', score.key))
def handle_game_3_save_m_request(self, request: Node) -> Node: refid = request.child_value('refid') if refid is not None: userid = self.data.remote.user.from_refid(self.game, self.version, refid) else: userid = None # Doesn't matter if userid is None here, that's an anonymous score musicid = request.child_value('music_id') chart = request.child_value('music_type') points = request.child_value('score') combo = request.child_value('max_chain') clear_type = self.__game_to_db_clear_type(request.child_value('clear_type')) grade = self.__game_to_db_grade(request.child_value('score_grade')) stats = { 'btn_rate': request.child_value('btn_rate'), 'long_rate': request.child_value('long_rate'), 'vol_rate': request.child_value('vol_rate'), 'critical': request.child_value('critical'), 'near': request.child_value('near'), 'error': request.child_value('error'), } # Save the score self.update_score( userid, musicid, chart, points, clear_type, grade, combo, stats, ) # Return a blank response return Node.void('game_3')
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 == '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