Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
    def handle_game_trace_request(self, request: Node) -> Node:
        # This is almost identical to 2013 and below, except it will never
        # even try to request course traces, so we fork from common functionality.
        extid = int(request.attribute('code'))
        chart = int(request.attribute('type'))
        mid = intish(request.attribute('mid'))

        # Base packet is just game, if we find something we add to it
        game = Node.void('game')

        # Rival trace loading
        userid = self.data.remote.user.from_extid(self.game, self.version,
                                                  extid)
        if userid is None:
            # Nothing to load
            return game

        # Load trace from song score
        songscore = self.data.remote.music.get_score(
            self.game,
            self.music_version,
            userid,
            mid,
            self.game_to_db_chart(chart),
        )
        if songscore is not None and 'trace' in songscore.data:
            game.add_child(Node.u32('size', len(songscore.data['trace'])))
            game.add_child(Node.u8_array('trace', songscore.data['trace']))

        return game
Beispiel #4
0
    def handle_game_trace_request(self, request: Node) -> Node:
        extid = int(request.attribute('code'))
        chart = int(request.attribute('type'))
        cid = intish(request.attribute('cid'))
        mid = intish(request.attribute('mid'))

        # Base packet is just game, if we find something we add to it
        game = Node.void('game')

        # Rival trace loading
        userid = self.data.remote.user.from_extid(self.game, self.version, extid)
        if userid is None:
            # Nothing to load
            return game

        if mid is not None:
            # Load trace from song score
            songscore = self.data.remote.music.get_score(
                self.game,
                self.music_version,
                userid,
                mid,
                self.game_to_db_chart(chart),
            )
            if songscore is not None and 'trace' in songscore.data:
                game.add_child(Node.u32('size', len(songscore.data['trace'])))
                game.add_child(Node.u8_array('trace', songscore.data['trace']))

        elif cid is not None:
            # Load trace from achievement
            coursescore = self.data.local.user.get_achievement(
                self.game,
                self.version,
                userid,
                (cid * 4) + chart,
                'course',
            )
            if coursescore is not None and 'trace' in coursescore:
                game.add_child(Node.u32('size', len(coursescore['trace'])))
                game.add_child(Node.u8_array('trace', coursescore['trace']))

        # Nothing found, return empty
        return game
Beispiel #5
0
    def handle_game_load_m_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)

        if userid is not None:
            scores = self.data.remote.music.get_scores(self.game,
                                                       self.music_version,
                                                       userid)
        else:
            scores = []

        sortedscores: Dict[int, Dict[int, Score]] = {}
        for score in scores:
            if score.id not in sortedscores:
                sortedscores[score.id] = {}
            sortedscores[score.id][score.chart] = score

        game = Node.void('game')
        for song in sortedscores:
            music = Node.void('music')
            game.add_child(music)
            music.set_attribute('reclink', str(song))

            for chart in sortedscores[song]:
                score = sortedscores[song][chart]
                try:
                    gamechart = self.db_to_game_chart(chart)
                except KeyError:
                    # Don't support this chart in this game
                    continue
                gamerank = self.db_to_game_rank(score.data.get_int('rank'))
                combo_type = self.db_to_game_halo(score.data.get_int('halo'))

                typenode = Node.void('type')
                music.add_child(typenode)
                typenode.set_attribute('diff', str(gamechart))

                typenode.add_child(Node.u32('score', score.points))
                typenode.add_child(Node.u16('count', score.plays))
                typenode.add_child(Node.u8('rank', gamerank))
                typenode.add_child(Node.u8('combo_type', combo_type))
                # The game optionally receives hard, life8, life4, risky, assist_clear, normal_clear
                # u8 values too, and saves music scores with these set, but the UI doesn't appear to
                # do anything with them, so we don't care.

        return game
Beispiel #6
0
    def handle_playerdata_usergamedata_send_request(self, request: Node) -> Node:
        playerdata = Node.void('playerdata')
        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) or ValidatedDict()
            usergamedata = profile.get_dict('usergamedata')

            for record in request.child('data/record').children:
                if record.name != 'd':
                    continue

                strdata = base64.b64decode(record.value)
                bindata = base64.b64decode(record.child_value('bin1'))

                # Grab and format the profile objects
                strdatalist = strdata.split(b',')
                profiletype = strdatalist[1].decode('utf-8')
                strdatalist = strdatalist[2:]

                # Extract relevant bits for frontend/API
                if profiletype == 'COMMON':
                    profile.replace_str('name', strdatalist[self.GAME_COMMON_NAME_OFFSET].decode('ascii'))
                    profile.replace_int('area', intish(strdatalist[self.GAME_COMMON_AREA_OFFSET].decode('ascii'), 16))
                    profile.replace_bool('workout_mode', int(strdatalist[self.GAME_COMMON_WEIGHT_DISPLAY_OFFSET].decode('ascii'), 16) != 0)
                    profile.replace_int('weight', int(float(strdatalist[self.GAME_COMMON_WEIGHT_OFFSET].decode('ascii')) * 10))
                    profile.replace_int('character', int(strdatalist[self.GAME_COMMON_CHARACTER_OFFSET].decode('ascii'), 16))
                if profiletype == 'OPTION':
                    profile.replace_int('combo', int(strdatalist[self.GAME_OPTION_COMBO_POSITION_OFFSET].decode('ascii'), 16))
                    profile.replace_int('early_late', int(strdatalist[self.GAME_OPTION_FAST_SLOW_OFFSET].decode('ascii'), 16))
                    profile.replace_int('arrowskin', int(strdatalist[self.GAME_OPTION_ARROW_SKIN_OFFSET].decode('ascii'), 16))
                    profile.replace_int('guidelines', int(strdatalist[self.GAME_OPTION_GUIDELINE_OFFSET].decode('ascii'), 16))
                    profile.replace_int('filter', int(strdatalist[self.GAME_OPTION_FILTER_OFFSET].decode('ascii'), 16))

                usergamedata[profiletype] = {
                    'strdata': b','.join(strdatalist),
                    'bindata': bindata,
                }

            profile.replace_dict('usergamedata', usergamedata)
            self.put_profile(userid, profile)

        playerdata.add_child(Node.s32('result', 0))
        return playerdata
Beispiel #7
0
    def handle_game_load_daily_request(self, request: Node) -> Node:
        extid = intish(request.attribute('code'))
        refid = request.attribute('refid')
        game = Node.void('game')
        profiledict = None

        if extid is not None:
            # Rival daily loading
            userid = self.data.remote.user.from_extid(self.game, self.version, extid)
        else:
            # Self daily loading
            userid = self.data.remote.user.from_refid(self.game, self.version, refid)
        if userid is not None:
            profiledict = self.get_profile(userid)

        if profiledict is not None:
            play_stats = self.get_play_statistics(userid)

            # Day 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')
            game.add_child(daycount)
            daycount.set_attribute('playcount', str(today_count))

            # Daily combo stuff, unclear how this works
            dailycombo = Node.void('dailycombo')
            game.add_child(dailycombo)
            dailycombo.set_attribute('daily_combo', str(0))
            dailycombo.set_attribute('daily_combo_lv', str(0))

        return game
Beispiel #8
0
    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(f'cnt_m{mode}')

        # Result stars
        result_star = request.child('result_star')
        if result_star is not None:
            newprofile.replace_int_array(
                'result_stars',
                9,
                [
                    intish(result_star.attribute('slot1')),
                    intish(result_star.attribute('slot2')),
                    intish(result_star.attribute('slot3')),
                    intish(result_star.attribute('slot4')),
                    intish(result_star.attribute('slot5')),
                    intish(result_star.attribute('slot6')),
                    intish(result_star.attribute('slot7')),
                    intish(result_star.attribute('slot8')),
                    intish(result_star.attribute('slot9')),
                ],
            )

        # Target stuff
        target = request.child('target')
        if target is not None:
            newprofile.replace_int('target_flag', intish(target.attribute('flag')))
            newprofile.replace_int('target_setnum', intish(target.attribute('setnum')))

        # Update last attributes
        lastdict.replace_int('rival1', intish(last.attribute('rival1')))
        lastdict.replace_int('rival2', intish(last.attribute('rival2')))
        lastdict.replace_int('rival3', intish(last.attribute('rival3')))
        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')))
        chara_opt = request.child('chara_opt')
        if chara_opt is not None:
            # A bug in old versions of AVS returns the wrong number for set
            newprofile.replace_int_array('chara_opt', 96, chara_opt.value[:96])

        # 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,
                    f'friend_{i}',
                    oldfriends[i],
                )
            elif oldfriends[i] is None:
                # Add rival in this location
                self.data.local.user.put_link(
                    self.game,
                    self.version,
                    userid,
                    f'friend_{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,
                    f'friend_{i}',
                    oldfriends[i],
                )
                self.data.local.user.put_link(
                    self.game,
                    self.version,
                    userid,
                    f'friend_{i}',
                    newfriends[i],
                    {},
                )

        # Play area counter
        shop_area = int(request.attribute('shop_area'))
        if shop_area >= 0 and shop_area < 55:
            areas = newprofile.get_int_array('play_area', 55)
            areas[shop_area] = areas[shop_area] + 1
            newprofile.replace_int_array('play_area', 55, areas)

        # Keep track of play statistics
        self.update_play_statistics(userid, play_stats)

        return newprofile
Beispiel #9
0
    def handle_game_load_m_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)

        if userid is not None:
            scores = self.data.remote.music.get_scores(self.game, self.music_version, userid)
            old_scores = [
                score for score in self.data.local.user.get_achievements(self.game, self.music_version, userid)
                if score.type == '2ndmix'
            ]
        else:
            scores = []
            old_scores = []

        sortedscores: Dict[int, Dict[int, Dict[str, Union[Score, Achievement]]]] = {}

        for score in scores:
            if score.id not in sortedscores:
                sortedscores[score.id] = {}
            if score.chart not in sortedscores[score.id]:
                sortedscores[score.id][score.chart] = {}
            sortedscores[score.id][score.chart]['score'] = score

        for oldscore in old_scores:
            songid = int(oldscore.id / 100)
            chart = int(oldscore.id % 100)
            if songid not in sortedscores:
                sortedscores[songid] = {}
            if chart not in sortedscores[songid]:
                sortedscores[songid][chart] = {}
            sortedscores[songid][chart]['oldscore'] = oldscore

        game = Node.void('game')
        for song in sortedscores:
            music = Node.void('music')
            game.add_child(music)
            music.set_attribute('reclink', str(song))

            for chart in sortedscores[song]:
                try:
                    gamechart = self.db_to_game_chart(chart)
                except KeyError:
                    # Don't support this chart in this game
                    continue
                scoredict = sortedscores[song][chart]

                if 'score' in scoredict:
                    # We played the normal version of this song
                    gamerank = self.db_to_game_rank(scoredict['score'].data.get_int('rank'))
                    combo_type = self.db_to_game_halo(scoredict['score'].data.get_int('halo'))
                    points = scoredict['score'].points  # type: ignore
                    plays = scoredict['score'].plays  # type: ignore
                else:
                    # We only played 2nd mix version of this song
                    gamerank = 0
                    combo_type = self.GAME_HALO_NONE
                    points = 0
                    plays = 0

                if 'oldscore' in scoredict:
                    # We played the 2nd mix version of this song
                    oldpoints = scoredict['oldscore'].data.get_int('points')
                    oldrank = scoredict['oldscore'].data.get_int('rank')
                    oldplays = scoredict['oldscore'].data.get_int('plays')
                else:
                    oldpoints = 0
                    oldrank = 0
                    oldplays = 0

                typenode = Node.void('type')
                music.add_child(typenode)
                typenode.set_attribute('diff', str(gamechart))

                typenode.add_child(Node.u32('score', points))
                typenode.add_child(Node.u16('count', plays))
                typenode.add_child(Node.u8('rank', gamerank))
                typenode.add_child(Node.u8('combo_type', combo_type))
                typenode.add_child(Node.u32('score_2nd', oldpoints))
                typenode.add_child(Node.u8('rank_2nd', oldrank))
                typenode.add_child(Node.u16('cnt_2nd', oldplays))

        return game
Beispiel #10
0
    def handle_game_friend_request(self, request: Node) -> Node:
        extid = intish(request.attribute('code'))
        userid = None
        friend = None

        if extid is not None:
            # Rival score loading
            userid = self.data.remote.user.from_extid(self.game, self.version, extid)
        if userid is not None:
            friend = self.get_profile(userid)
            play_stats = self.get_play_statistics(userid)

        if friend is None:
            # Return an empty node to tell the game we don't have a player here
            game = Node.void('game')
            return game

        game = Node.void('game')
        game.set_attribute('data', '1')
        game.add_child(Node.u32('code', friend.get_int('extid')))
        game.add_child(Node.string('name', friend.get_str('name')))
        game.add_child(Node.u8('area', friend.get_int('area', 51)))
        game.add_child(Node.u32('exp', play_stats.get_int('exp')))
        game.add_child(Node.u32('star', friend.get_int('star')))

        # Drill rankings
        if 'title' in friend:
            title = Node.void('title')
            game.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')
            game.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')
        game.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')
        game.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 game