Ejemplo n.º 1
0
    def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node:
        game = Node.void('game')

        # Generic profile stuff
        game.add_child(Node.string('name', profile.get_str('name')))
        game.add_child(
            Node.string('code', ID.format_extid(profile.get_int('extid'))))
        game.add_child(Node.u32('gamecoin_packet', profile.get_int('packet')))
        game.add_child(Node.u32('gamecoin_block', profile.get_int('block')))
        game.add_child(Node.u32('exp_point', profile.get_int('exp')))
        game.add_child(Node.u32('m_user_cnt', profile.get_int('m_user_cnt')))

        game_config = self.get_game_config()
        if game_config.get_bool('force_unlock_cards'):
            game.add_child(Node.bool_array('have_item', [True] * 512))
        else:
            game.add_child(
                Node.bool_array(
                    'have_item',
                    [x > 0 for x in profile.get_int_array('have_item', 512)]))
        if game_config.get_bool('force_unlock_songs'):
            game.add_child(Node.bool_array('have_note', [True] * 512))
        else:
            game.add_child(
                Node.bool_array(
                    'have_note',
                    [x > 0 for x in profile.get_int_array('have_note', 512)]))

        # Last played stuff
        lastdict = profile.get_dict('last')
        last = Node.void('last')
        game.add_child(last)
        last.set_attribute('music_id', str(lastdict.get_int('music_id')))
        last.set_attribute('music_type', str(lastdict.get_int('music_type')))
        last.set_attribute('sort_type', str(lastdict.get_int('sort_type')))
        last.set_attribute('headphone', str(lastdict.get_int('headphone')))
        last.set_attribute('hispeed', str(lastdict.get_int('hispeed')))
        last.set_attribute('appeal_id', str(lastdict.get_int('appeal_id')))
        last.set_attribute('frame0', str(lastdict.get_int('frame0')))
        last.set_attribute('frame1', str(lastdict.get_int('frame1')))
        last.set_attribute('frame2', str(lastdict.get_int('frame2')))
        last.set_attribute('frame3', str(lastdict.get_int('frame3')))
        last.set_attribute('frame4', str(lastdict.get_int('frame4')))

        return game
Ejemplo n.º 2
0
    def verify_game_save(self, location: str, refid: str, packet: int,
                         block: int, exp: int) -> None:
        call = self.call_node()

        game = Node.void('game')
        call.add_child(game)
        game.set_attribute('method', 'save')
        game.set_attribute('refid', refid)
        game.set_attribute('locid', location)
        game.set_attribute('ver', '0')
        game.add_child(Node.u8('headphone', 0))
        game.add_child(Node.u8('hispeed', 16))
        game.add_child(Node.u16('appeal_id', 19))
        game.add_child(Node.u16('frame0', 0))
        game.add_child(Node.u16('frame1', 0))
        game.add_child(Node.u16('frame2', 0))
        game.add_child(Node.u16('frame3', 0))
        game.add_child(Node.u16('frame4', 0))
        last = Node.void('last')
        game.add_child(last)
        last.set_attribute('music_type', '1')
        last.set_attribute('music_id', '29')
        last.set_attribute('sort_type', '4')
        game.add_child(Node.u32('earned_gamecoin_packet', packet))
        game.add_child(Node.u32('earned_gamecoin_block', block))
        game.add_child(Node.u32('gain_exp', exp))
        game.add_child(Node.u32('m_user_cnt', 0))
        game.add_child(Node.bool_array('have_item', [False] * 512))
        game.add_child(Node.bool_array('have_note', [False] * 512))
        tracking = Node.void('tracking')
        game.add_child(tracking)
        m0 = Node.void('m0')
        tracking.add_child(m0)
        m0.add_child(Node.u8('type', 2))
        m0.add_child(Node.u32('id', 5))
        m0.add_child(Node.u32('score', 774566))
        tracking.add_child(Node.time('p_start', Time.now() - 300))
        tracking.add_child(Node.time('p_end', Time.now()))

        # Swap with server
        resp = self.exchange('', call)

        # Verify that response is correct
        self.assert_path(resp, "response/game")
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
    def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node:
        statistics = self.get_play_statistics(userid)
        game_config = self.get_game_config()
        achievements = self.data.local.user.get_achievements(
            self.game, self.version, userid)
        scores = self.data.remote.music.get_scores(self.game, self.version,
                                                   userid)
        links = self.data.local.user.get_links(self.game, self.version, userid)
        root = Node.void('player')
        pdata = Node.void('pdata')
        root.add_child(pdata)

        base = Node.void('base')
        pdata.add_child(base)
        base.add_child(Node.s32('uid', profile.get_int('extid')))
        base.add_child(Node.string('name', profile.get_str('name')))
        base.add_child(Node.s16('icon_id', profile.get_int('icon')))
        base.add_child(Node.s16('lv', profile.get_int('lvl')))
        base.add_child(Node.s32('exp', profile.get_int('exp')))
        base.add_child(Node.s16('mg', profile.get_int('mg')))
        base.add_child(Node.s16('ap', profile.get_int('ap')))
        base.add_child(Node.s32('pc', profile.get_int('pc')))
        base.add_child(Node.s32('uattr', profile.get_int('uattr')))

        last_play_date = statistics.get_int_array('last_play_date', 3)
        today_play_date = Time.todays_date()
        if (last_play_date[0] == today_play_date[0]
                and last_play_date[1] == today_play_date[1]
                and last_play_date[2] == today_play_date[2]):
            today_count = statistics.get_int('today_plays', 0)
        else:
            today_count = 0

        con = Node.void('con')
        pdata.add_child(con)
        con.add_child(Node.s32('day', today_count))
        con.add_child(Node.s32('cnt', statistics.get_int('total_plays')))
        con.add_child(Node.s32('total_cnt', statistics.get_int('total_plays')))
        con.add_child(
            Node.s32('last', statistics.get_int('last_play_timestamp')))
        con.add_child(Node.s32('now', Time.now()))

        team = Node.void('team')
        pdata.add_child(team)
        team.add_child(Node.s32('id', profile.get_int('team_id', -1)))
        team.add_child(Node.string('name', profile.get_str('team_name', '')))

        custom = Node.void('custom')
        customdict = profile.get_dict('custom')
        pdata.add_child(custom)
        custom.add_child(Node.u8('s_gls', customdict.get_int('s_gls')))
        custom.add_child(Node.u8('bgm_m', customdict.get_int('bgm_m')))
        custom.add_child(Node.u8('st_f', customdict.get_int('st_f')))
        custom.add_child(Node.u8('st_bg', customdict.get_int('st_bg')))
        custom.add_child(Node.u8('st_bg_b', customdict.get_int('st_bg_b')))
        custom.add_child(Node.u8('eff_e', customdict.get_int('eff_e')))
        custom.add_child(Node.u8('se_s', customdict.get_int('se_s')))
        custom.add_child(Node.u8('se_s_v', customdict.get_int('se_s_v')))
        custom.add_child(
            Node.s16('last_music_id', customdict.get_int('last_music_id')))
        custom.add_child(
            Node.u8('last_note_grade', customdict.get_int('last_note_grade')))
        custom.add_child(Node.u8('sort_type', customdict.get_int('sort_type')))
        custom.add_child(
            Node.u8('narrowdown_type', customdict.get_int('narrowdown_type')))
        custom.add_child(
            Node.bool('is_begginer', customdict.get_bool(
                'is_begginer')))  # Yes, this is spelled right
        custom.add_child(Node.bool('is_tut', customdict.get_bool('is_tut')))
        custom.add_child(
            Node.s16_array('symbol_chat_0',
                           customdict.get_int_array('symbol_chat_0', 6)))
        custom.add_child(
            Node.s16_array('symbol_chat_1',
                           customdict.get_int_array('symbol_chat_1', 6)))
        custom.add_child(
            Node.u8('gauge_style', customdict.get_int('gauge_style')))
        custom.add_child(Node.u8('obj_shade', customdict.get_int('obj_shade')))
        custom.add_child(Node.u8('obj_size', customdict.get_int('obj_size')))
        custom.add_child(
            Node.s16_array('byword', customdict.get_int_array('byword', 2)))
        custom.add_child(
            Node.bool_array('is_auto_byword',
                            customdict.get_bool_array('is_auto_byword', 2)))
        custom.add_child(Node.bool('is_tweet',
                                   customdict.get_bool('is_tweet')))
        custom.add_child(
            Node.bool('is_link_twitter',
                      customdict.get_bool('is_link_twitter')))
        custom.add_child(Node.s16('mrec_type',
                                  customdict.get_int('mrec_type')))
        custom.add_child(
            Node.s16('card_disp_type', customdict.get_int('card_disp_type')))
        custom.add_child(Node.s16('tab_sel', customdict.get_int('tab_sel')))
        custom.add_child(
            Node.s32_array('hidden_param',
                           customdict.get_int_array('hidden_param', 20)))

        released = Node.void('released')
        pdata.add_child(released)

        for item in achievements:
            if item.type[:5] != 'item_':
                continue
            itemtype = int(item.type[5:])
            if game_config.get_bool('force_unlock_songs') and itemtype == 0:
                # Don't echo unlocks when we're force unlocking, we'll do it later
                continue

            info = Node.void('info')
            released.add_child(info)
            info.add_child(Node.u8('type', itemtype))
            info.add_child(Node.u16('id', item.id))
            info.add_child(Node.u16('param', item.data.get_int('param')))

        if game_config.get_bool('force_unlock_songs'):
            ids: Dict[int, int] = {}
            songs = self.data.local.music.get_all_songs(
                self.game, self.version)
            for song in songs:
                if song.id not in ids:
                    ids[song.id] = 0

                if song.data.get_int('difficulty') > 0:
                    ids[song.id] = ids[song.id] | (1 << song.chart)

            for songid in ids:
                if ids[songid] == 0:
                    continue

                info = Node.void('info')
                released.add_child(info)
                info.add_child(Node.u8('type', 0))
                info.add_child(Node.u16('id', songid))
                info.add_child(Node.u16('param', ids[songid]))

        # Scores
        record = Node.void('record')
        pdata.add_child(record)

        for score in scores:
            rec = Node.void('rec')
            record.add_child(rec)
            rec.add_child(Node.u16('mid', score.id))
            rec.add_child(Node.u8('ng', score.chart))
            rec.add_child(
                Node.s32(
                    'point',
                    score.data.get_dict('stats').get_int('earned_points')))
            rec.add_child(Node.s32('played_time', score.timestamp))

            mrec_0 = Node.void('mrec_0')
            rec.add_child(mrec_0)
            mrec_0.add_child(
                Node.s32('win',
                         score.data.get_dict('stats').get_int('win')))
            mrec_0.add_child(
                Node.s32('lose',
                         score.data.get_dict('stats').get_int('lose')))
            mrec_0.add_child(
                Node.s32('draw',
                         score.data.get_dict('stats').get_int('draw')))
            mrec_0.add_child(
                Node.u8(
                    'ct',
                    self.__db_to_game_clear_type(
                        score.data.get_int('clear_type'),
                        score.data.get_int('combo_type'))))
            mrec_0.add_child(
                Node.s16('ar',
                         int(score.data.get_int('achievement_rate') / 10)))
            mrec_0.add_child(Node.s32('bs', score.points))
            mrec_0.add_child(Node.s16('mc', score.data.get_int('combo')))
            mrec_0.add_child(Node.s16('bmc', score.data.get_int('miss_count')))

            mrec_1 = Node.void('mrec_1')
            rec.add_child(mrec_1)
            mrec_1.add_child(Node.s32('win', 0))
            mrec_1.add_child(Node.s32('lose', 0))
            mrec_1.add_child(Node.s32('draw', 0))
            mrec_1.add_child(Node.u8('ct', 0))
            mrec_1.add_child(Node.s16('ar', 0))
            mrec_1.add_child(Node.s32('bs', 0))
            mrec_1.add_child(Node.s16('mc', 0))
            mrec_1.add_child(Node.s16('bmc', -1))

        # Comment (seems unused?)
        pdata.add_child(Node.string('cmnt', ''))

        # Rivals
        rival = Node.void('rival')
        pdata.add_child(rival)

        slotid = 0
        for link in links:
            if link.type != 'rival':
                continue

            rprofile = self.get_profile(link.other_userid)
            if rprofile is None:
                continue

            r = Node.void('r')
            rival.add_child(r)
            r.add_child(Node.u8('slot_id', slotid))
            r.add_child(Node.string('name', rprofile.get_str('name')))
            r.add_child(Node.s32('id', rprofile.get_int('extid')))
            r.add_child(Node.bool('friend', True))
            r.add_child(Node.bool('locked', False))
            r.add_child(Node.s32('rc', 0))
            slotid = slotid + 1

        # Glass points
        glass = Node.void('glass')
        pdata.add_child(glass)

        for item in achievements:
            if item.type != 'glass':
                continue

            g = Node.void('g')
            glass.add_child(g)
            g.add_child(Node.s32('id', item.id))
            g.add_child(Node.s32('exp', item.data.get_int('exp')))

        # Favorite music
        fav_music_slot = Node.void('fav_music_slot')
        pdata.add_child(fav_music_slot)

        for item in achievements:
            if item.type != 'music':
                continue

            slot = Node.void('slot')
            fav_music_slot.add_child(slot)
            slot.add_child(Node.u8('slot_id', item.id))
            slot.add_child(Node.s16('music_id', item.data.get_int('music_id')))

        narrow_down = Node.void('narrow_down')
        pdata.add_child(narrow_down)
        narrow_down.add_child(
            Node.s32_array('adv_param', [
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                -1, -1
            ]))

        return root
Ejemplo n.º 6
0
    def verify_player_write(self, refid: str, extid: int, loc: str,
                            records: List[Dict[str, int]],
                            scores: List[Dict[str, int]]) -> int:
        call = self.call_node()

        player = Node.void('player')
        call.add_child(player)
        player.set_attribute('method', 'write')
        player.add_child(Node.string('rid', refid))
        player.add_child(Node.string('lid', loc))
        player.add_child(Node.u64('begin_time', Time.now() * 1000))
        player.add_child(Node.u64('end_time', Time.now() * 1000))
        pdata = Node.void('pdata')
        player.add_child(pdata)
        base = Node.void('base')
        pdata.add_child(base)
        base.add_child(Node.s32('uid', extid))
        base.add_child(Node.string('name', self.NAME))
        base.add_child(Node.s16('icon_id', 0))
        base.add_child(Node.s16('lv', 1))
        base.add_child(Node.s32('exp', 0))
        base.add_child(Node.s16('mg', 0))
        base.add_child(Node.s16('ap', 0))
        base.add_child(Node.s32('pc', 0))
        base.add_child(Node.s32('uattr', 0))
        con = Node.void('con')
        pdata.add_child(con)
        con.add_child(Node.s32('day', 0))
        con.add_child(Node.s32('cnt', 0))
        con.add_child(Node.s32('total_cnt', 0))
        con.add_child(Node.s32('last', 0))
        con.add_child(Node.s32('now', 0))
        custom = Node.void('custom')
        pdata.add_child(custom)
        custom.add_child(Node.u8('s_gls', 0))
        custom.add_child(Node.u8('bgm_m', 0))
        custom.add_child(Node.u8('st_f', 0))
        custom.add_child(Node.u8('st_bg', 0))
        custom.add_child(Node.u8('st_bg_b', 100))
        custom.add_child(Node.u8('eff_e', 0))
        custom.add_child(Node.u8('se_s', 0))
        custom.add_child(Node.u8('se_s_v', 100))
        custom.add_child(Node.s16('last_music_id', 85))
        custom.add_child(Node.u8('last_note_grade', 0))
        custom.add_child(Node.u8('sort_type', 0))
        custom.add_child(Node.u8('narrowdown_type', 0))
        custom.add_child(Node.bool('is_begginer', False))
        custom.add_child(Node.bool('is_tut', False))
        custom.add_child(Node.s16_array('symbol_chat_0', [0, 1, 2, 3, 4, 5]))
        custom.add_child(Node.s16_array('symbol_chat_1', [0, 1, 2, 3, 4, 5]))
        custom.add_child(Node.u8('gauge_style', 0))
        custom.add_child(Node.u8('obj_shade', 0))
        custom.add_child(Node.u8('obj_size', 0))
        custom.add_child(Node.s16_array('byword', [0, 0]))
        custom.add_child(Node.bool_array('is_auto_byword', [True, True]))
        custom.add_child(Node.bool('is_tweet', False))
        custom.add_child(Node.bool('is_link_twitter', False))
        custom.add_child(Node.s16('mrec_type', 0))
        custom.add_child(Node.s16('card_disp_type', 0))
        custom.add_child(Node.s16('tab_sel', 0))
        custom.add_child(
            Node.s32_array(
                'hidden_param',
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0]))
        pdata.add_child(Node.void('released'))
        pdata.add_child(Node.void('rival'))
        pdata.add_child(Node.void('glass'))
        pdata.add_child(Node.void('fav_music_slot'))
        lincle_link_4 = Node.void('lincle_link_4')
        pdata.add_child(lincle_link_4)
        lincle_link_4.add_child(Node.u32('qpro_add', 0))
        lincle_link_4.add_child(Node.u32('glass_add', 0))
        lincle_link_4.add_child(Node.bool('for_iidx_0_0', False))
        lincle_link_4.add_child(Node.bool('for_iidx_0_1', False))
        lincle_link_4.add_child(Node.bool('for_iidx_0_2', False))
        lincle_link_4.add_child(Node.bool('for_iidx_0_3', False))
        lincle_link_4.add_child(Node.bool('for_iidx_0_4', False))
        lincle_link_4.add_child(Node.bool('for_iidx_0_5', False))
        lincle_link_4.add_child(Node.bool('for_iidx_0_6', False))
        lincle_link_4.add_child(Node.bool('for_iidx_0', False))
        lincle_link_4.add_child(Node.bool('for_iidx_1', False))
        lincle_link_4.add_child(Node.bool('for_iidx_2', False))
        lincle_link_4.add_child(Node.bool('for_iidx_3', False))
        lincle_link_4.add_child(Node.bool('for_iidx_4', False))
        lincle_link_4.add_child(Node.bool('for_rb_0_0', False))
        lincle_link_4.add_child(Node.bool('for_rb_0_1', False))
        lincle_link_4.add_child(Node.bool('for_rb_0_2', False))
        lincle_link_4.add_child(Node.bool('for_rb_0_3', False))
        lincle_link_4.add_child(Node.bool('for_rb_0_4', False))
        lincle_link_4.add_child(Node.bool('for_rb_0_5', False))
        lincle_link_4.add_child(Node.bool('for_rb_0_6', False))
        lincle_link_4.add_child(Node.bool('for_rb_0', False))
        lincle_link_4.add_child(Node.bool('for_rb_1', False))
        lincle_link_4.add_child(Node.bool('for_rb_2', False))
        lincle_link_4.add_child(Node.bool('for_rb_3', False))
        lincle_link_4.add_child(Node.bool('for_rb_4', False))

        # First, filter down to only records that are also in the battle log
        def key(thing: Dict[str, int]) -> str:
            return f'{thing["id"]}-{thing["chart"]}'

        updates = [key(score) for score in scores]
        sortedrecords = {
            key(record): record
            for record in records if key(record) in updates
        }

        # Now, see what records need updating and update them
        for score in scores:
            if key(score) in sortedrecords:
                # Had a record, need to merge
                record = sortedrecords[key(score)]
            else:
                # First time playing
                record = {
                    'clear_type': 0,
                    'achievement_rate': 0,
                    'score': 0,
                    'combo': 0,
                    'miss_count': 999999999,
                }

            sortedrecords[key(score)] = {
                'id':
                score['id'],
                'chart':
                score['chart'],
                'clear_type':
                max(record['clear_type'], score['clear_type']),
                'achievement_rate':
                max(record['achievement_rate'], score['achievement_rate']),
                'score':
                max(record['score'], score['score']),
                'combo':
                max(record['combo'], score['combo']),
                'miss_count':
                min(record['miss_count'], score['miss_count']),
            }

        # Finally, send the records and battle logs
        recordnode = Node.void('record')
        pdata.add_child(recordnode)
        blog = Node.void('blog')
        pdata.add_child(blog)

        for (_, record) in sortedrecords.items():
            rec = Node.void('rec')
            recordnode.add_child(rec)
            rec.add_child(Node.u16('mid', record['id']))
            rec.add_child(Node.u8('ng', record['chart']))
            rec.add_child(Node.s32('point', 2))
            rec.add_child(Node.s32('played_time', Time.now()))
            mrec_0 = Node.void('mrec_0')
            rec.add_child(mrec_0)
            mrec_0.add_child(Node.s32('win', 1))
            mrec_0.add_child(Node.s32('lose', 0))
            mrec_0.add_child(Node.s32('draw', 0))
            mrec_0.add_child(Node.u8('ct', record['clear_type']))
            mrec_0.add_child(Node.s16('ar', record['achievement_rate']))
            mrec_0.add_child(Node.s32('bs', record['score']))
            mrec_0.add_child(Node.s16('mc', record['combo']))
            mrec_0.add_child(Node.s16('bmc', record['miss_count']))
            mrec_1 = Node.void('mrec_1')
            rec.add_child(mrec_1)
            mrec_1.add_child(Node.s32('win', 0))
            mrec_1.add_child(Node.s32('lose', 0))
            mrec_1.add_child(Node.s32('draw', 0))
            mrec_1.add_child(Node.u8('ct', 0))
            mrec_1.add_child(Node.s16('ar', 0))
            mrec_1.add_child(Node.s32('bs', 0))
            mrec_1.add_child(Node.s16('mc', 0))
            mrec_1.add_child(Node.s16('bmc', -1))

        scoreid = 0
        for score in scores:
            log = Node.void('log')
            blog.add_child(log)
            log.add_child(Node.u8('id', scoreid))
            log.add_child(Node.u16('mid', score['id']))
            log.add_child(Node.u8('ng', score['chart']))
            log.add_child(Node.u8('mt', 0))
            log.add_child(Node.u8('rt', 0))
            log.add_child(Node.s32('ruid', 0))
            myself = Node.void('myself')
            log.add_child(myself)
            myself.add_child(Node.s16('mg', 0))
            myself.add_child(Node.s16('ap', 0))
            myself.add_child(Node.u8('ct', score['clear_type']))
            myself.add_child(Node.s32('s', score['score']))
            myself.add_child(Node.s16('ar', score['achievement_rate']))
            rival = Node.void('rival')
            log.add_child(rival)
            rival.add_child(Node.s16('mg', 0))
            rival.add_child(Node.s16('ap', 0))
            rival.add_child(Node.u8('ct', 2))
            rival.add_child(Node.s32('s', 177))
            rival.add_child(Node.s16('ar', 500))
            log.add_child(Node.s32('time', Time.now()))
            scoreid = scoreid + 1

        # Swap with server
        resp = self.exchange('', call)

        # Verify that response is correct
        self.assert_path(resp, "response/player/uid")
        self.assert_path(resp, "response/player/time")
        return resp.child_value('player/uid')