def handle_game_load_c_request(self, request: Node) -> Node: extid = intish(request.attribute('code')) refid = request.attribute('refid') if extid is not None: # Rival score loading userid = self.data.remote.user.from_extid(self.game, self.version, extid) else: # Self score loading userid = self.data.remote.user.from_refid(self.game, self.version, refid) coursedata = [0] * 3200 if userid is not None: for course in self.data.local.user.get_achievements(self.game, self.version, userid): if course.type != 'course': continue # Grab course ID and chart (kinda pointless because we add it right back up # below, but it is more documented/readable this way. courseid = int(course.id / 4) coursechart = course.id % 4 # Populate course data index = ((courseid * 4) + coursechart) * 8 if index >= 0 and index <= (len(coursedata) - 8): coursedata[index + 0] = int(course.data.get_int('score') / 10000) coursedata[index + 1] = course.data.get_int('score') % 10000 coursedata[index + 2] = course.data.get_int('combo') coursedata[index + 3] = self.db_to_game_rank(course.data.get_int('rank')) coursedata[index + 5] = course.data.get_int('stage') coursedata[index + 6] = course.data.get_int('combo_type') game = Node.void('game') game.add_child(Node.u16_array('course', coursedata)) return game
def format_conversion(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('playerdata') root.add_child(Node.string('name', profile.get_str('name', 'なし'))) root.add_child(Node.s16('chara', profile.get_int('chara', -1))) root.add_child(Node.s32('option', profile.get_int('option', 0))) root.add_child(Node.u8('version', 0)) root.add_child(Node.u8('kind', 0)) root.add_child(Node.u8('season', 0)) clear_medal = [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 clear_medal[score.id] = clear_medal[ score.id] | self.__format_medal_for_score(score) root.add_child(Node.u16_array('clear_medal', clear_medal)) return root
def test_packet1(self) -> Node: root = Node.void('test') root.set_attribute('test', 'test string value') # Regular nodes root.add_child(Node.void('void_node')) root.add_child(Node.s8('s8_node', -1)) root.add_child(Node.u8('u8_node', 245)) root.add_child(Node.s16('s16_node', -8000)) root.add_child(Node.u16('u16_node', 65000)) root.add_child(Node.s32('s32_node', -2000000000)) root.add_child(Node.u32('u32_node', 4000000000)) root.add_child(Node.s64('s64_node', -1234567890000)) root.add_child(Node.u64('u64_node', 1234567890000)) root.add_child(Node.binary('bin_node', b'DEADBEEF')) root.add_child(Node.string('str_node', 'this is a string!')) root.add_child(Node.ipv4('ip4_node', '192.168.1.24')) root.add_child(Node.time('time_node', 1234567890)) root.add_child(Node.float('float_node', 2.5)) root.add_child(Node.fouru8('4u8_node', [0x20, 0x21, 0x22, 0x23])) root.add_child(Node.bool('bool_true_node', True)) root.add_child(Node.bool('bool_false_node', False)) # Array nodes root.add_child(Node.s8_array('s8_array_node', [-1, -2, 3, 4, -5])) root.add_child(Node.u8_array('u8_array_node', [245, 2, 0, 255, 1])) root.add_child(Node.s16_array('s16_array_node', [-8000, 8000])) root.add_child(Node.u16_array('u16_array_node', [65000, 1, 2, 65535])) root.add_child(Node.s32_array('s32_array_node', [-2000000000, -1])) root.add_child(Node.u32_array('u32_array_node', [4000000000, 0, 1, 2])) root.add_child( Node.s64_array('s64_array_node', [-1234567890000, -1, 1, 1337])) root.add_child( Node.u64_array('u64_array_node', [1234567890000, 123, 456, 7890])) root.add_child( Node.time_array('time_array_node', [1234567890, 98765432])) root.add_child( Node.float_array('float_array_node', [2.5, 0.0, 5.0, 20.5])) root.add_child( Node.bool_array('bool_array_node', [False, True, True, False])) # XML escaping escape = Node.string( 'escape_test', '\r\n<testing> & \'thing\' "thing" \r\nthing on new line\r\n ') escape.set_attribute('test', '<testing> & \'thing\' "thing" \r\n thing') root.add_child(escape) # Unicode unicode_node = Node.string('unicode', '今日は') unicode_node.set_attribute('unicode_attr', 'わたし') root.add_child(unicode_node) self.assertLoopback(root)
def parse_psmap(data: bytes, offset: str, rootname: str) -> Node: pe = pefile.PE(data=data, fast_load=True) root = Node.void(rootname) base = int(offset, 16) def virtual_to_physical(offset: int) -> int: for section in pe.sections: start = section.VirtualAddress + pe.OPTIONAL_HEADER.ImageBase end = start + section.SizeOfRawData if offset >= start and offset < end: return (offset - start) + section.PointerToRawData raise Exception( f'Couldn\'t find raw offset for virtual offset 0x{offset:08x}') if base >= pe.OPTIONAL_HEADER.ImageBase: # Assume this is virtual base = virtual_to_physical(base) def read_string(offset: int) -> str: # First, translate load offset in memory to disk offset offset = virtual_to_physical(offset) # Now, grab bytes until we're null-terminated bytestring = [] while data[offset] != 0: bytestring.append(data[offset]) offset = offset + 1 # Its shift-jis encoded, so decode it now return bytes(bytestring).decode('shift_jisx0213') # For recursing into nodes saved_root: List[Node] = [] saved_loc: List[int] = [] while True: chunk = data[base:(base + 16)] base = base + 16 (nodetype, mandatory, outoffset, width, nameptr, defaultptr) = struct.unpack('<BBHIII', chunk) if nodetype == 0xFF: # End of nodes, see if we should exit if len(saved_root) == 0: break else: root = saved_root.pop() oldbase = saved_loc.pop() if oldbase is not None: base = oldbase continue # Grab name, get rid of parse numbers name = read_string(nameptr) try: if name.index('#') >= 0: name = name[:name.index('#')] except ValueError: pass if nodetype == 0x00: raise Exception(f'Invalid node type 0x{nodetype:02x}') elif nodetype == 0x01: # This is a void node, so we should handle by recursing node = Node.void(name) root.add_child(node) # Recurse here saved_root.append(root) if defaultptr != 0: saved_loc.append(base) base = virtual_to_physical(defaultptr) else: saved_loc.append(None) root = node continue elif nodetype == 0x02 or nodetype == 0x43: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.s8_array(name, [-1] * elements) else: node = Node.s8(name, -1) elif nodetype == 0x03 or nodetype == 0x44: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.u8_array(name, [0] * elements) else: node = Node.u8(name, 0) elif nodetype == 0x04 or nodetype == 0x45: if nodetype < 0x40: elements = int(width / 2) else: elements = width if elements > 1: node = Node.s16_array(name, [-1] * elements) else: node = Node.s16(name, -1) elif nodetype == 0x05 or nodetype == 0x46: if nodetype < 0x40: elements = int(width / 2) else: elements = width if elements > 1: node = Node.u16_array(name, [0] * elements) else: node = Node.u16(name, 0) elif nodetype == 0x06 or nodetype == 0x47: if nodetype < 0x40: elements = int(width / 4) else: elements = width if elements > 1: node = Node.s32_array(name, [-1] * elements) else: node = Node.s32(name, -1) elif nodetype == 0x07 or nodetype == 0x48: if nodetype < 0x40: elements = int(width / 4) else: elements = width if elements > 1: node = Node.u32_array(name, [0] * elements) else: node = Node.u32(name, 0) elif nodetype == 0x08 or nodetype == 0x49: if nodetype < 0x40: elements = int(width / 8) else: elements = width if elements > 1: node = Node.s64_array(name, [-1] * elements) else: node = Node.s64(name, -1) elif nodetype == 0x09 or nodetype == 0x4A: if nodetype < 0x40: elements = int(width / 8) else: elements = width if elements > 1: node = Node.u64_array(name, [0] * elements) else: node = Node.u64(name, 0) elif nodetype == 0x0A: node = Node.string(name, '') elif nodetype == 0x0D: node = Node.float(name, 0.0) elif nodetype == 0x32 or nodetype == 0x6D: if nodetype < 0x40: elements = int(width / 1) else: elements = width if elements > 1: node = Node.bool_array(name, [False] * elements) else: node = Node.bool(name, False) elif nodetype == 0x2F: # Special case, this is an attribute if name[-1] != '@': raise Exception( f'Attribute name {name} expected to end with @') root.set_attribute(name[:-1], '') continue else: raise Exception(f'Unimplemented node type 0x{nodetype:02x}') # Append it root.add_child(node) return root
def 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 format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('game') # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Basic game settings root.add_child(Node.string('seq', '')) root.add_child(Node.u32('code', profile.get_int('extid'))) root.add_child(Node.string('name', profile.get_str('name'))) root.add_child(Node.u8('area', profile.get_int('area', 51))) root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays'))) root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays'))) root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0'))) root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1'))) root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2'))) root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3'))) root.add_child(Node.u32('cnt_m4', play_stats.get_int('cnt_m4'))) root.add_child(Node.u32('cnt_m5', play_stats.get_int('cnt_m5'))) root.add_child(Node.u32('exp', play_stats.get_int('exp'))) root.add_child(Node.u32('exp_o', profile.get_int('exp_o'))) root.add_child(Node.u32('star', profile.get_int('star'))) root.add_child(Node.u32('star_c', profile.get_int('star_c'))) root.add_child(Node.u8('combo', profile.get_int('combo', 0))) root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0))) # Character stuff chara = Node.void('chara') root.add_child(chara) chara.set_attribute('my', str(profile.get_int('chara', 30))) root.add_child(Node.u16_array('chara_opt', profile.get_int_array('chara_opt', 96, [208] * 96))) # Drill rankings if 'title_gr' in profile: title_gr = Node.void('title_gr') root.add_child(title_gr) title_grdict = profile.get_dict('title_gr') if 't' in title_grdict: title_gr.set_attribute('t', str(title_grdict.get_int('t'))) if 's' in title_grdict: title_gr.set_attribute('s', str(title_grdict.get_int('s'))) if 'd' in title_grdict: title_gr.set_attribute('d', str(title_grdict.get_int('d'))) # Calorie mode if 'weight' in profile: workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, achievementtype='workout', since=Time.now() - Time.SECONDS_IN_DAY, ) total = sum([w.data.get_int('calories') for w in workouts]) workout = Node.void('workout') root.add_child(workout) workout.set_attribute('weight', str(profile.get_int('weight'))) workout.set_attribute('day', str(total)) workout.set_attribute('disp', '1') # Daily play counts last_play_date = play_stats.get_int_array('last_play_date', 3) today_play_date = Time.todays_date() if ( last_play_date[0] == today_play_date[0] and last_play_date[1] == today_play_date[1] and last_play_date[2] == today_play_date[2] ): today_count = play_stats.get_int('today_plays', 0) else: today_count = 0 daycount = Node.void('daycount') root.add_child(daycount) daycount.set_attribute('playcount', str(today_count)) # Daily combo stuff, unknown how this works dailycombo = Node.void('dailycombo') root.add_child(dailycombo) dailycombo.set_attribute('daily_combo', str(0)) dailycombo.set_attribute('daily_combo_lv', str(0)) # Last cursor settings last = Node.void('last') root.add_child(last) lastdict = profile.get_dict('last') last.set_attribute('rival1', str(lastdict.get_int('rival1', -1))) last.set_attribute('rival2', str(lastdict.get_int('rival2', -1))) last.set_attribute('rival3', str(lastdict.get_int('rival3', -1))) last.set_attribute('fri', str(lastdict.get_int('rival1', -1))) # This literally goes to the same memory in X3 last.set_attribute('style', str(lastdict.get_int('style'))) last.set_attribute('mode', str(lastdict.get_int('mode'))) last.set_attribute('cate', str(lastdict.get_int('cate'))) last.set_attribute('sort', str(lastdict.get_int('sort'))) last.set_attribute('mid', str(lastdict.get_int('mid'))) last.set_attribute('mtype', str(lastdict.get_int('mtype'))) last.set_attribute('cid', str(lastdict.get_int('cid'))) last.set_attribute('ctype', str(lastdict.get_int('ctype'))) last.set_attribute('sid', str(lastdict.get_int('sid'))) # Result stars result_star = Node.void('result_star') root.add_child(result_star) result_stars = profile.get_int_array('result_stars', 9) for i in range(9): result_star.set_attribute(f'slot{i + 1}', str(result_stars[i])) # Target stuff target = Node.void('target') root.add_child(target) target.set_attribute('flag', str(profile.get_int('target_flag'))) target.set_attribute('setnum', str(profile.get_int('target_setnum'))) # Groove gauge level-ups gr_s = Node.void('gr_s') root.add_child(gr_s) index = 1 for entry in profile.get_int_array('gr_s', 5): gr_s.set_attribute(f'gr{index}', str(entry)) index = index + 1 gr_d = Node.void('gr_d') root.add_child(gr_d) index = 1 for entry in profile.get_int_array('gr_d', 5): gr_d.set_attribute(f'gr{index}', str(entry)) index = index + 1 # Options in menus root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16))) root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16))) # Unlock flags root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256))) # Ranking display? root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100))) # Rivals links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: if link.type[:7] != 'friend_': continue pos = int(link.type[7:]) friend = self.get_profile(link.other_userid) play_stats = self.get_play_statistics(link.other_userid) if friend is not None: friendnode = Node.void('friend') root.add_child(friendnode) friendnode.set_attribute('pos', str(pos)) friendnode.set_attribute('vs', '0') friendnode.set_attribute('up', '0') friendnode.add_child(Node.u32('code', friend.get_int('extid'))) friendnode.add_child(Node.string('name', friend.get_str('name'))) friendnode.add_child(Node.u8('area', friend.get_int('area', 51))) friendnode.add_child(Node.u32('exp', play_stats.get_int('exp'))) friendnode.add_child(Node.u32('star', friend.get_int('star'))) # Drill rankings if 'title' in friend: title = Node.void('title') friendnode.add_child(title) titledict = friend.get_dict('title') if 't' in titledict: title.set_attribute('t', str(titledict.get_int('t'))) if 's' in titledict: title.set_attribute('s', str(titledict.get_int('s'))) if 'd' in titledict: title.set_attribute('d', str(titledict.get_int('d'))) if 'title_gr' in friend: title_gr = Node.void('title_gr') friendnode.add_child(title_gr) title_grdict = friend.get_dict('title_gr') if 't' in title_grdict: title_gr.set_attribute('t', str(title_grdict.get_int('t'))) if 's' in title_grdict: title_gr.set_attribute('s', str(title_grdict.get_int('s'))) if 'd' in title_grdict: title_gr.set_attribute('d', str(title_grdict.get_int('d'))) # Groove gauge level-ups gr_s = Node.void('gr_s') friendnode.add_child(gr_s) index = 1 for entry in friend.get_int_array('gr_s', 5): gr_s.set_attribute(f'gr{index}', str(entry)) index = index + 1 gr_d = Node.void('gr_d') friendnode.add_child(gr_d) index = 1 for entry in friend.get_int_array('gr_d', 5): gr_d.set_attribute(f'gr{index}', str(entry)) index = index + 1 # Play area areas = profile.get_int_array('play_area', 55) play_area = Node.void('play_area') root.add_child(play_area) for i in range(len(areas)): play_area.set_attribute(f'play_cnt{i}', str(areas[i])) return root
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('playerdata') # Set up the base profile base = Node.void('base') root.add_child(base) base.add_child(Node.string('name', profile.get_str('name', 'なし'))) base.add_child( Node.string('g_pm_id', ID.format_extid(profile.get_int('extid')))) base.add_child(Node.u8('mode', profile.get_int('mode', 0))) base.add_child(Node.s8('button', profile.get_int('button', 0))) base.add_child( Node.s8('last_play_flag', profile.get_int('last_play_flag', -1))) base.add_child( Node.u8('medal_and_friend', profile.get_int('medal_and_friend', 0))) base.add_child(Node.s8('category', profile.get_int('category', -1))) base.add_child( Node.s8('sub_category', profile.get_int('sub_category', -1))) base.add_child(Node.s16('chara', profile.get_int('chara', -1))) base.add_child( Node.s8('chara_category', profile.get_int('chara_category', -1))) base.add_child(Node.u8('collabo', 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 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 format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('game') # Look up play stats we bridge to every mix play_stats = self.get_play_statistics(userid) # Basic game settings root.add_child(Node.string('seq', '')) root.add_child(Node.u32('code', profile.get_int('extid'))) root.add_child(Node.string('name', profile.get_str('name'))) root.add_child(Node.u8('area', profile.get_int('area', 51))) root.add_child(Node.u32('cnt_s', play_stats.get_int('single_plays'))) root.add_child(Node.u32('cnt_d', play_stats.get_int('double_plays'))) root.add_child(Node.u32('cnt_b', play_stats.get_int('battle_plays'))) # This could be wrong, its a guess root.add_child(Node.u32('cnt_m0', play_stats.get_int('cnt_m0'))) root.add_child(Node.u32('cnt_m1', play_stats.get_int('cnt_m1'))) root.add_child(Node.u32('cnt_m2', play_stats.get_int('cnt_m2'))) root.add_child(Node.u32('cnt_m3', play_stats.get_int('cnt_m3'))) root.add_child(Node.u32('exp', play_stats.get_int('exp'))) root.add_child(Node.u32('exp_o', profile.get_int('exp_o'))) root.add_child(Node.u32('star', profile.get_int('star'))) root.add_child(Node.u32('star_c', profile.get_int('star_c'))) root.add_child(Node.u8('combo', profile.get_int('combo', 0))) root.add_child(Node.u8('timing_diff', profile.get_int('early_late', 0))) # Character stuff chara = Node.void('chara') root.add_child(chara) if 'chara' in profile: chara.set_attribute('my', str(profile.get_int('chara'))) root.add_child(Node.u8_array('chara_opt', profile.get_int_array('chara_opt', 96))) # Drill rankings if 'title' in profile: title = Node.void('title') root.add_child(title) titledict = profile.get_dict('title') if 't' in titledict: title.set_attribute('t', str(titledict.get_int('t'))) if 's' in titledict: title.set_attribute('s', str(titledict.get_int('s'))) if 'd' in titledict: title.set_attribute('d', str(titledict.get_int('d'))) if 'title_gr' in profile: title_gr = Node.void('title_gr') root.add_child(title_gr) title_grdict = profile.get_dict('title_gr') if 't' in title_grdict: title_gr.set_attribute('t', str(title_grdict.get_int('t'))) if 's' in title_grdict: title_gr.set_attribute('s', str(title_grdict.get_int('s'))) if 'd' in title_grdict: title_gr.set_attribute('d', str(title_grdict.get_int('d'))) # Event progrses if 'event' in profile: event = Node.void('event') root.add_child(event) event_dict = profile.get_dict('event') if 'diff_sum' in event_dict: event.set_attribute('diff_sum', str(event_dict.get_int('diff_sum'))) if 'welcome' in event_dict: event.set_attribute('welcome', str(event_dict.get_int('welcome'))) if 'e_flags' in event_dict: event.set_attribute('e_flags', str(event_dict.get_int('e_flags'))) if 'e_panel' in profile: e_panel = Node.void('e_panel') root.add_child(e_panel) e_panel_dict = profile.get_dict('e_panel') if 'play_id' in e_panel_dict: e_panel.set_attribute('play_id', str(e_panel_dict.get_int('play_id'))) e_panel.add_child(Node.u8_array('cell', e_panel_dict.get_int_array('cell', 24))) e_panel.add_child(Node.u8_array('panel_state', e_panel_dict.get_int_array('panel_state', 6))) if 'e_pix' in profile: e_pix = Node.void('e_pix') root.add_child(e_pix) e_pix_dict = profile.get_dict('e_pix') if 'max_distance' in e_pix_dict: e_pix.set_attribute('max_distance', str(e_pix_dict.get_int('max_distance'))) if 'max_planet' in e_pix_dict: e_pix.set_attribute('max_planet', str(e_pix_dict.get_int('max_planet'))) if 'total_distance' in e_pix_dict: e_pix.set_attribute('total_distance', str(e_pix_dict.get_int('total_distance'))) if 'total_planet' in e_pix_dict: e_pix.set_attribute('total_planet', str(e_pix_dict.get_int('total_planet'))) if 'border_character' in e_pix_dict: e_pix.set_attribute('border_character', str(e_pix_dict.get_int('border_character'))) if 'border_balloon' in e_pix_dict: e_pix.set_attribute('border_balloon', str(e_pix_dict.get_int('border_balloon'))) if 'border_music_aftr' in e_pix_dict: e_pix.set_attribute('border_music_aftr', str(e_pix_dict.get_int('border_music_aftr'))) if 'border_music_meii' in e_pix_dict: e_pix.set_attribute('border_music_meii', str(e_pix_dict.get_int('border_music_meii'))) if 'border_music_dirt' in e_pix_dict: e_pix.set_attribute('border_music_dirt', str(e_pix_dict.get_int('border_music_dirt'))) if 'flags' in e_pix_dict: e_pix.set_attribute('flags', str(e_pix_dict.get_int('flags'))) # Calorie mode if 'weight' in profile: workouts = self.data.local.user.get_time_based_achievements( self.game, self.version, userid, achievementtype='workout', since=Time.now() - Time.SECONDS_IN_DAY, ) total = sum([w.data.get_int('calories') for w in workouts]) workout = Node.void('workout') root.add_child(workout) workout.set_attribute('weight', str(profile.get_int('weight'))) workout.set_attribute('day', str(total)) workout.set_attribute('disp', '1') # Last cursor settings last = Node.void('last') root.add_child(last) lastdict = profile.get_dict('last') last.set_attribute('fri', str(lastdict.get_int('fri'))) last.set_attribute('style', str(lastdict.get_int('style'))) last.set_attribute('mode', str(lastdict.get_int('mode'))) last.set_attribute('cate', str(lastdict.get_int('cate'))) last.set_attribute('sort', str(lastdict.get_int('sort'))) last.set_attribute('mid', str(lastdict.get_int('mid'))) last.set_attribute('mtype', str(lastdict.get_int('mtype'))) last.set_attribute('cid', str(lastdict.get_int('cid'))) last.set_attribute('ctype', str(lastdict.get_int('ctype'))) last.set_attribute('sid', str(lastdict.get_int('sid'))) # Groove gauge level-ups gr_s = Node.void('gr_s') root.add_child(gr_s) index = 1 for entry in profile.get_int_array('gr_s', 5): gr_s.set_attribute(f'gr{index}', str(entry)) index = index + 1 gr_d = Node.void('gr_d') root.add_child(gr_d) index = 1 for entry in profile.get_int_array('gr_d', 5): gr_d.set_attribute(f'gr{index}', str(entry)) index = index + 1 # Options in menus root.add_child(Node.s16_array('opt', profile.get_int_array('opt', 16))) root.add_child(Node.s16_array('opt_ex', profile.get_int_array('opt_ex', 16))) # Unlock flags root.add_child(Node.u8_array('flag', profile.get_int_array('flag', 256, [1] * 256))) # Ranking display? root.add_child(Node.u16_array('rank', profile.get_int_array('rank', 100))) # Rivals links = self.data.local.user.get_links(self.game, self.version, userid) for link in links: if link.type[:7] != 'friend_': continue pos = int(link.type[7:]) friend = self.get_profile(link.other_userid) play_stats = self.get_play_statistics(link.other_userid) if friend is not None: friendnode = Node.void('friend') root.add_child(friendnode) friendnode.set_attribute('pos', str(pos)) friendnode.set_attribute('vs', '0') friendnode.set_attribute('up', '0') friendnode.add_child(Node.u32('code', friend.get_int('extid'))) friendnode.add_child(Node.string('name', friend.get_str('name'))) friendnode.add_child(Node.u8('area', friend.get_int('area', 51))) friendnode.add_child(Node.u32('exp', play_stats.get_int('exp'))) friendnode.add_child(Node.u32('star', friend.get_int('star'))) # Drill rankings if 'title' in friend: title = Node.void('title') friendnode.add_child(title) titledict = friend.get_dict('title') if 't' in titledict: title.set_attribute('t', str(titledict.get_int('t'))) if 's' in titledict: title.set_attribute('s', str(titledict.get_int('s'))) if 'd' in titledict: title.set_attribute('d', str(titledict.get_int('d'))) if 'title_gr' in friend: title_gr = Node.void('title_gr') friendnode.add_child(title_gr) title_grdict = friend.get_dict('title_gr') if 't' in title_grdict: title_gr.set_attribute('t', str(title_grdict.get_int('t'))) if 's' in title_grdict: title_gr.set_attribute('s', str(title_grdict.get_int('s'))) if 'd' in title_grdict: title_gr.set_attribute('d', str(title_grdict.get_int('d'))) # Groove gauge level-ups gr_s = Node.void('gr_s') friendnode.add_child(gr_s) index = 1 for entry in friend.get_int_array('gr_s', 5): gr_s.set_attribute(f'gr{index}', str(entry)) index = index + 1 gr_d = Node.void('gr_d') friendnode.add_child(gr_d) index = 1 for entry in friend.get_int_array('gr_d', 5): gr_d.set_attribute(f'gr{index}', str(entry)) index = index + 1 return root