def verify_eventlog_write(self, location: str) -> None: call = self.call_node() # Construct node eventlog = Node.void('eventlog') call.add_child(eventlog) eventlog.set_attribute('method', 'write') eventlog.add_child(Node.u32('retrycnt', 0)) data = Node.void('data') eventlog.add_child(data) data.add_child(Node.string('eventid', 'S_PWRON')) data.add_child(Node.s32('eventorder', 0)) data.add_child(Node.u64('pcbtime', int(time.time() * 1000))) data.add_child(Node.s64('gamesession', -1)) data.add_child(Node.string('strdata1', '2.3.8')) data.add_child(Node.string('strdata2', '')) data.add_child(Node.s64('numdata1', 1)) data.add_child(Node.s64('numdata2', 0)) data.add_child(Node.string('locationid', location)) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/eventlog/gamesession") self.assert_path(resp, "response/eventlog/logsendflg") self.assert_path(resp, "response/eventlog/logerrlevel") self.assert_path(resp, "response/eventlog/evtidnosendflg")
def verify_playerdata_usergamedata_advanced_inheritance( self, refid: str, locid: str) -> None: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'inheritance')) data.add_child(Node.string('locid', locid)) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/InheritanceStatus") self.assert_path(resp, "response/playerdata/result")
def verify_playerdata_usergamedata_advanced_usernew(self, refid: str) -> int: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'usernew')) data.add_child(Node.string('shoparea', '.')) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/seq") self.assert_path(resp, "response/playerdata/code") self.assert_path(resp, "response/playerdata/shoparea") self.assert_path(resp, "response/playerdata/result") return resp.child_value('playerdata/code')
def verify_gameend_regist( self, ref_id: str, jid: int, scores: List[Dict[str, Any]], ) -> None: call = self.call_node() # Construct node gameend = Node.void('gameend') call.add_child(gameend) gameend.set_attribute('method', 'regist') gameend.add_child(Node.s32('retry', 0)) pcbinfo = Node.void('pcbinfo') gameend.add_child(pcbinfo) pcbinfo.set_attribute('client_data_version', '0') data = Node.void('data') gameend.add_child(data) player = Node.void('player') data.add_child(player) player.add_child(Node.string('refid', ref_id)) player.add_child(Node.s32('jid', jid)) player.add_child(Node.string('name', self.NAME)) result = Node.void('result') data.add_child(result) result.set_attribute('count', str(len(scores))) # Send scores scoreid = 0 for score in scores: # Always played bits = 0x1 if score['clear']: bits |= 0x2 if score['fc']: bits |= 0x4 if score['ex']: bits |= 0x8 # Intentionally starting at 1 because that's what the game does scoreid = scoreid + 1 tune = Node.void('tune') result.add_child(tune) tune.set_attribute('id', str(scoreid)) tune.add_child(Node.s32('music', score['id'])) tune.add_child(Node.s64('timestamp', Time.now() * 1000)) player_1 = Node.void('player') tune.add_child(player_1) player_1.set_attribute('rank', '1') scorenode = Node.s32('score', score['score']) player_1.add_child(scorenode) scorenode.set_attribute('seq', str(score['chart'])) scorenode.set_attribute('clear', str(bits)) scorenode.set_attribute('combo', '69') player_1.add_child(Node.u8_array('mbar', [239, 175, 170, 170, 190, 234, 187, 158, 153, 230, 170, 90, 102, 170, 85, 150, 150, 102, 85, 234, 171, 169, 157, 150, 170, 101, 230, 90, 214, 255])) # Swap with server resp = self.exchange('', call) self.assert_path(resp, "response/gameend/data/player/session_id")
def verify_playerdata_usergamedata_advanced_userload( self, refid: str) -> Tuple[bool, List[Dict[str, Any]]]: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'userload')) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/result") self.assert_path(resp, "response/playerdata/is_new") music = [] for child in resp.child('playerdata').children: if child.name != 'music': continue songid = child.child_value('mcode') chart = 0 for note in child.children: if note.name != 'note': continue if note.child_value('count') != 0: # Actual song music.append({ 'id': songid, 'chart': chart, 'rank': note.child_value('rank'), 'halo': note.child_value('clearkind'), 'score': note.child_value('score'), 'ghostid': note.child_value('ghostid'), }) chart = chart + 1 return ( resp.child_value('playerdata/is_new'), music, )
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 handle_eventlog_write_request(self, request: Node) -> Node: # Just turn off further logging gamesession = request.child_value('data/gamesession') if gamesession < 0: gamesession = random.randint(1, 1000000) root = Node.void('eventlog') root.add_child(Node.s64('gamesession', gamesession)) root.add_child(Node.s32('logsendflg', 0)) root.add_child(Node.s32('logerrlevel', 0)) root.add_child(Node.s32('evtidnosendflg', 0)) return root
def verify_playerdata_usergamedata_advanced_rivalload( self, refid: str, loadflag: int) -> None: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'rivalload')) data.add_child(Node.u64('targettime', Time.now() * 1000)) data.add_child(Node.string('shoparea', '.')) data.add_child(Node.bool('isdouble', False)) data.add_child(Node.s32('loadflag', loadflag)) data.add_child(Node.s32('ddrcode', 0)) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/data/recordtype") if loadflag != 2: # As implemented, its possible for a machine not in an arcade to have scores. # So, if the test PCBID we're using isn't in an arcade, we won't fetch scores # for area records (flag 2), so don't check for these in that case. self.assert_path(resp, "response/playerdata/data/record/mcode") self.assert_path(resp, "response/playerdata/data/record/notetype") self.assert_path(resp, "response/playerdata/data/record/rank") self.assert_path(resp, "response/playerdata/data/record/clearkind") self.assert_path(resp, "response/playerdata/data/record/flagdata") self.assert_path(resp, "response/playerdata/data/record/name") self.assert_path(resp, "response/playerdata/data/record/area") self.assert_path(resp, "response/playerdata/data/record/code") self.assert_path(resp, "response/playerdata/data/record/score") self.assert_path(resp, "response/playerdata/data/record/ghostid") if resp.child_value('playerdata/data/recordtype') != loadflag: raise Exception('Invalid record type returned!')
def handle_lobby_entry_request(self, request: Node) -> Node: root = Node.void('lobby') data = Node.void('data') root.add_child(data) roomid = Node.s64('roomid', -2) roomid.set_attribute('master', '1') data.add_child(roomid) refresh_intr = Node.s16('refresh_intr', 3) data.add_child(refresh_intr) # Grab music id from the request request_data = request.child('data') request_music = request_data.child('music') music_id = request_music.child('id') seq_id = request_music.child('seq') music = Node.void('music') music.add_child(music_id) music.add_child(seq_id) data.add_child(music) return root
def verify_playerdata_usergamedata_advanced_ghostload( self, refid: str, ghostid: int) -> Dict[str, Any]: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'ghostload')) data.add_child(Node.s32('ghostid', ghostid)) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/ghostdata/code") self.assert_path(resp, "response/playerdata/ghostdata/mcode") self.assert_path(resp, "response/playerdata/ghostdata/notetype") self.assert_path(resp, "response/playerdata/ghostdata/ghostsize") self.assert_path(resp, "response/playerdata/ghostdata/ghost") return { 'extid': resp.child_value('playerdata/ghostdata/code'), 'id': resp.child_value('playerdata/ghostdata/mcode'), 'chart': resp.child_value('playerdata/ghostdata/notetype'), 'ghost': resp.child_value('playerdata/ghostdata/ghost'), }
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('gametop') data = Node.void('data') root.add_child(data) player = Node.void('player') data.add_child(player) # Player info and statistics info = Node.void('info') player.add_child(info) info.add_child(Node.s16('jubility', profile.get_int('jubility'))) info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) if 'total_best_score' in profile: info.add_child(Node.s32('total_best_score', profile.get_int('total_best_score'))) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. info.add_child(Node.bool('inherit', profile.get_bool('has_old_version'))) # Not saved, but loaded info.add_child(Node.s32('mtg_entry_cnt', 123)) info.add_child(Node.s32('mtg_hold_cnt', 456)) info.add_child(Node.u8('mtg_result', 10)) # Secret unlocks item = Node.void('item') player.add_child(item) item.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list', 32, [-1] * 32, ), )) item.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list', 96, [-1] * 96, ), )) item.add_child(Node.s16('theme_list', profile.get_int('theme_list', -1))) item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 96, [-1] * 96))) new = Node.void('new') item.add_child(new) new.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list_new', 32, [-1] * 32, ), )) new.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list_new', 96, [-1] * 96, ), )) new.add_child(Node.s16('theme_list', profile.get_int('theme_list_new', -1))) new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) # Last played data, for showing cursor and such lastdict = profile.get_dict('last') last = Node.void('last') player.add_child(last) last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) last.add_child(Node.s8('marker', lastdict.get_int('marker'))) last.add_child(Node.s16('title', lastdict.get_int('title'))) last.add_child(Node.s8('theme', lastdict.get_int('theme'))) last.add_child(Node.s8('sort', lastdict.get_int('sort'))) last.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) last.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) last.add_child(Node.s16('parts', lastdict.get_int('parts'))) last.add_child(Node.s8('category', lastdict.get_int('category'))) last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) # Miscelaneous crap player.add_child(Node.s32('session_id', 1)) # Maybe hook this up? Unsure what it does, is it like IIDX dailies? today_music = Node.void('today_music') player.add_child(today_music) today_music.add_child(Node.s32('music_id', 0)) # No news, ever. news = Node.void('news') player.add_child(news) news.add_child(Node.s16('checked', 0)) # No rival support, yet. rivallist = Node.void('rivallist') player.add_child(rivallist) rivallist.set_attribute('count', '0') mylist = Node.void('mylist') player.add_child(mylist) mylist.set_attribute('count', '0') # No collaboration support yet. collabo = Node.void('collabo') player.add_child(collabo) collabo.add_child(Node.bool('success', False)) collabo.add_child(Node.bool('completed', False)) # Daily FC challenge. entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') if entry is None: entry = ValidatedDict() # Figure out if we've played these songs start_time, end_time = self.data.local.network.get_schedule_duration('daily') today_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('today', -1), timelimit=start_time) challenge = Node.void('challenge') player.add_child(challenge) today = Node.void('today') challenge.add_child(today) today.add_child(Node.s32('music_id', entry.get_int('today', -1))) today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) onlynow = Node.void('onlynow') challenge.add_child(onlynow) onlynow.add_child(Node.s32('magic_no', 0)) onlynow.add_child(Node.s16('cycle', 0)) # Bistro event bistro = Node.void('bistro') player.add_child(bistro) # Presumably these can affect the speed of the event info_1 = Node.void('info') bistro.add_child(info_1) info_1.add_child(Node.float('delicious_rate', 1.0)) info_1.add_child(Node.float('favorite_rate', 1.0)) bistro.add_child(Node.s32('carry_over', profile.get_int('bistro_carry_over'))) # Your chef dude, I guess? chefdict = profile.get_dict('chef') chef = Node.void('chef') bistro.add_child(chef) chef.add_child(Node.s32('id', chefdict.get_int('id', 1))) chef.add_child(Node.u8('ability', chefdict.get_int('ability', 2))) chef.add_child(Node.u8('remain', chefdict.get_int('remain', 30))) chef.add_child(Node.u8('rate', chefdict.get_int('rate', 1))) # Routes, similar to story mode in Pop'n I guess? routes = [ { 'id': 50000284, 'price': 20, 'satisfaction': 10, 'favorite': True, }, { 'id': 50000283, 'price': 20, 'satisfaction': 20, 'favorite': False, }, { 'id': 50000282, 'price': 30, 'satisfaction': 10, 'favorite': False, }, { 'id': 50000275, 'price': 10, 'satisfaction': 55, 'favorite': False, }, { 'id': 50000274, 'price': 40, 'satisfaction': 40, 'favorite': False, }, { 'id': 50000273, 'price': 80, 'satisfaction': 60, 'favorite': False, }, { 'id': 50000272, 'price': 70, 'satisfaction': 60, 'favorite': False, }, { 'id': 50000271, 'price': 90, 'satisfaction': 80, 'favorite': False, }, { 'id': 50000270, 'price': 90, 'satisfaction': 20, 'favorite': False, }, ] for route_no in range(len(routes)): routedata = routes[route_no] route = Node.void('route') bistro.add_child(route) route.set_attribute('no', str(route_no)) music = Node.void('music') route.add_child(music) music.add_child(Node.s32('id', routedata['id'])) music.add_child(Node.u16('price', routedata['price'])) music.add_child(Node.s32('price_s32', routedata['price'])) # Look up any updated satisfaction stored by the game routesaved = self.data.local.user.get_achievement(self.game, self.version, userid, route_no + 1, 'route') if routesaved is None: routesaved = ValidatedDict() satisfaction = routesaved.get_int('satisfaction', routedata['satisfaction']) gourmates = Node.void('gourmates') route.add_child(gourmates) gourmates.add_child(Node.s32('id', route_no + 1)) gourmates.add_child(Node.u8('favorite', 1 if routedata['favorite'] else 0)) gourmates.add_child(Node.u16('satisfaction', satisfaction)) gourmates.add_child(Node.s32('satisfaction_s32', satisfaction)) # Sane defaults for unknown nodes only_now_music = Node.void('only_now_music') player.add_child(only_now_music) only_now_music.set_attribute('count', '0') requested_music = Node.void('requested_music') player.add_child(requested_music) requested_music.set_attribute('count', '0') kac_music = Node.void('kac_music') player.add_child(kac_music) kac_music.set_attribute('count', '0') history = Node.void('history') player.add_child(history) history.set_attribute('count', '0') # Basic profile info player.add_child(Node.string('name', profile.get_str('name', 'なし'))) player.add_child(Node.s32('jid', profile.get_int('extid'))) player.add_child(Node.string('refid', profile.get_str('refid'))) # Miscelaneous history stuff data.add_child(Node.u8('termver', 16)) data.add_child(Node.u32('season_etime', 0)) data.add_child(Node.s32('bistro_last_music_id', 0)) data.add_child(Node.s32_array( 'white_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child(Node.s32_array( 'old_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child(Node.s32_array( 'open_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) # Unsupported collaboration events with other games collabo_info = Node.void('collabo_info') data.add_child(collabo_info) # Unsupported marathon stuff run_run_marathon = Node.void('run_run_marathon') collabo_info.add_child(run_run_marathon) run_run_marathon.set_attribute('type', '1') run_run_marathon.add_child(Node.u8('state', 1)) run_run_marathon.add_child(Node.bool('is_report_end', True)) # Unsupported policy break stuff policy_break = Node.void('policy_break') collabo_info.add_child(policy_break) policy_break.set_attribute('type', '1') policy_break.add_child(Node.u8('state', 1)) policy_break.add_child(Node.bool('is_report_end', False)) # Unsupported vocaloid stuff vocaloid_event = Node.void('vocaloid_event') collabo_info.add_child(vocaloid_event) vocaloid_event.set_attribute('type', '1') vocaloid_event.add_child(Node.u8('state', 0)) vocaloid_event.add_child(Node.s32('music_id', 0)) # No obnoxious 30 second wait to play. matching_off = Node.void('matching_off') data.add_child(matching_off) matching_off.add_child(Node.bool('is_open', True)) 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_userload(self, userid: Optional[UserID], requestdata: Node, response: Node) -> None: has_profile: bool = False achievements: List[Achievement] = [] scores: List[Score] = [] if userid is not None: has_profile = self.has_profile(userid) achievements = self.data.local.user.get_achievements( self.game, self.version, userid) scores = self.data.remote.music.get_scores(self.game, self.music_version, userid) # Place scores into an arrangement for easier distribution to Ace. scores_by_mcode: Dict[int, List[Optional[Score]]] = {} for score in scores: if score.id not in scores_by_mcode: scores_by_mcode[score.id] = [None] * 9 scores_by_mcode[score.id][self.db_to_game_chart( score.chart)] = score # First, set new flag response.add_child(Node.bool('is_new', not has_profile)) # Now, return the scores to Ace for mcode in scores_by_mcode: music = Node.void('music') response.add_child(music) music.add_child(Node.u32('mcode', mcode)) scores_that_matter = scores_by_mcode[mcode] while scores_that_matter[-1] is None: scores_that_matter = scores_that_matter[:-1] for score in scores_that_matter: note = Node.void('note') music.add_child(note) if score is None: note.add_child(Node.u16('count', 0)) note.add_child(Node.u8('rank', 0)) note.add_child(Node.u8('clearkind', 0)) note.add_child(Node.s32('score', 0)) note.add_child(Node.s32('ghostid', 0)) else: note.add_child(Node.u16('count', score.plays)) note.add_child( Node.u8( 'rank', self.db_to_game_rank(score.data.get_int('rank')))) note.add_child( Node.u8( 'clearkind', self.db_to_game_halo(score.data.get_int('halo')))) note.add_child(Node.s32('score', score.points)) note.add_child(Node.s32('ghostid', score.key)) # Active event settings activeevents = [ 1, 3, 5, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, ] # Event reward settings rewards = { '30': { 999: 5, } } # Now handle event progress and activation. events = { ach.id: ach.data for ach in achievements if ach.type == '9999' } progress = [ach for ach in achievements if ach.type != '9999'] # Make sure we always send a babylon's adventure save event or the game won't send progress babylon_included = False for evtprogress in progress: if evtprogress.id == 999 and evtprogress.type == '30': babylon_included = True break if not babylon_included: progress.append( Achievement( 999, '30', None, { 'completed': False, 'progress': 0, }, )) for event in activeevents: # Get completion data playerstats = events.get(event, ValidatedDict({'completed': False})) # Return the data eventdata = Node.void('eventdata') response.add_child(eventdata) eventdata.add_child(Node.u32('eventid', event)) eventdata.add_child(Node.s32('eventtype', 9999)) eventdata.add_child(Node.u32('eventno', 0)) eventdata.add_child(Node.s64('condition', 0)) eventdata.add_child(Node.u32('reward', 0)) eventdata.add_child( Node.s32('comptime', 1 if playerstats.get_bool('completed') else 0)) eventdata.add_child(Node.s64('savedata', 0)) for evtprogress in progress: # Babylon's adventure progres and anything else the game sends eventdata = Node.void('eventdata') response.add_child(eventdata) eventdata.add_child(Node.u32('eventid', evtprogress.id)) eventdata.add_child(Node.s32('eventtype', int(evtprogress.type))) eventdata.add_child(Node.u32('eventno', 0)) eventdata.add_child(Node.s64('condition', 0)) eventdata.add_child( Node.u32('reward', rewards.get(evtprogress.type, {}).get(evtprogress.id))) eventdata.add_child( Node.s32('comptime', 1 if evtprogress.data.get_bool('completed') else 0)) eventdata.add_child( Node.s64('savedata', evtprogress.data.get_int('progress')))
def verify_playerdata_usergamedata_advanced_usersave( self, refid: str, extid: int, locid: str, score: Dict[str, Any], scorepos: int = 0) -> None: call = self.call_node() # Construct node playerdata = Node.void('playerdata') call.add_child(playerdata) playerdata.set_attribute('method', 'usergamedata_advanced') playerdata.add_child(Node.u32('retrycnt', 0)) info = Node.void('info') playerdata.add_child(info) info.add_child(Node.s32('version', 1)) data = Node.void('data') playerdata.add_child(data) data.add_child(Node.string('mode', 'usersave')) data.add_child(Node.string('name', self.NAME)) data.add_child(Node.s32('ddrcode', extid)) data.add_child(Node.s32('playside', 1)) data.add_child(Node.s32('playstyle', 0)) data.add_child(Node.s32('area', 58)) data.add_child(Node.s32('weight100', 0)) data.add_child(Node.string('shopname', 'gmw=')) data.add_child(Node.bool('ispremium', False)) data.add_child(Node.bool('iseapass', True)) data.add_child(Node.bool('istakeover', False)) data.add_child(Node.bool('isrepeater', False)) data.add_child(Node.bool('isgameover', scorepos < 0)) data.add_child(Node.string('locid', locid)) data.add_child(Node.string('shoparea', '.')) data.add_child(Node.s64('gamesession', 123456)) data.add_child(Node.string('refid', refid)) data.add_child(Node.string('dataid', refid)) data.add_child(Node.string('gamekind', 'MDX')) data.add_child(Node.string('pcbid', self.pcbid)) data.add_child(Node.void('record')) for i in range(5): if i == scorepos: # Fill in score here note = Node.void('note') data.add_child(note) note.add_child(Node.u8('stagenum', i + 1)) note.add_child(Node.u32('mcode', score['id'])) note.add_child(Node.u8('notetype', score['chart'])) note.add_child(Node.u8('rank', score['rank'])) note.add_child(Node.u8('clearkind', score['halo'])) note.add_child(Node.s32('score', score['score'])) note.add_child(Node.s32('exscore', 0)) note.add_child(Node.s32('maxcombo', 0)) note.add_child(Node.s32('life', 0)) note.add_child(Node.s32('fastcount', 0)) note.add_child(Node.s32('slowcount', 0)) note.add_child(Node.s32('judge_marvelous', 0)) note.add_child(Node.s32('judge_perfect', 0)) note.add_child(Node.s32('judge_great', 0)) note.add_child(Node.s32('judge_good', 0)) note.add_child(Node.s32('judge_boo', 0)) note.add_child(Node.s32('judge_miss', 0)) note.add_child(Node.s32('judge_ok', 0)) note.add_child(Node.s32('judge_ng', 0)) note.add_child(Node.s32('calorie', 0)) note.add_child(Node.s32('ghostsize', len(score['ghost']))) note.add_child(Node.string('ghost', score['ghost'])) note.add_child(Node.u8('opt_speed', 0)) note.add_child(Node.u8('opt_boost', 0)) note.add_child(Node.u8('opt_appearance', 0)) note.add_child(Node.u8('opt_turn', 0)) note.add_child(Node.u8('opt_dark', 0)) note.add_child(Node.u8('opt_scroll', 0)) note.add_child(Node.u8('opt_arrowcolor', 0)) note.add_child(Node.u8('opt_cut', 0)) note.add_child(Node.u8('opt_freeze', 0)) note.add_child(Node.u8('opt_jump', 0)) note.add_child(Node.u8('opt_arrowshape', 0)) note.add_child(Node.u8('opt_filter', 0)) note.add_child(Node.u8('opt_guideline', 0)) note.add_child(Node.u8('opt_gauge', 0)) note.add_child(Node.u8('opt_judgepriority', 0)) note.add_child(Node.u8('opt_timing', 0)) note.add_child(Node.string('basename', '')) note.add_child(Node.string('title_b64', '')) note.add_child(Node.string('artist_b64', '')) note.add_child(Node.u16('bpmMax', 0)) note.add_child(Node.u16('bpmMin', 0)) note.add_child(Node.u8('level', 0)) note.add_child(Node.u8('series', 0)) note.add_child(Node.u32('bemaniFlag', 0)) note.add_child(Node.u32('genreFlag', 0)) note.add_child(Node.u8('limited', 0)) note.add_child(Node.u8('region', 0)) note.add_child(Node.s32('gr_voltage', 0)) note.add_child(Node.s32('gr_stream', 0)) note.add_child(Node.s32('gr_chaos', 0)) note.add_child(Node.s32('gr_freeze', 0)) note.add_child(Node.s32('gr_air', 0)) note.add_child(Node.bool('share', False)) note.add_child(Node.u64('endtime', 0)) note.add_child(Node.s32('folder', 0)) else: note = Node.void('note') data.add_child(note) note.add_child(Node.u8('stagenum', 0)) note.add_child(Node.u32('mcode', 0)) note.add_child(Node.u8('notetype', 0)) note.add_child(Node.u8('rank', 0)) note.add_child(Node.u8('clearkind', 0)) note.add_child(Node.s32('score', 0)) note.add_child(Node.s32('exscore', 0)) note.add_child(Node.s32('maxcombo', 0)) note.add_child(Node.s32('life', 0)) note.add_child(Node.s32('fastcount', 0)) note.add_child(Node.s32('slowcount', 0)) note.add_child(Node.s32('judge_marvelous', 0)) note.add_child(Node.s32('judge_perfect', 0)) note.add_child(Node.s32('judge_great', 0)) note.add_child(Node.s32('judge_good', 0)) note.add_child(Node.s32('judge_boo', 0)) note.add_child(Node.s32('judge_miss', 0)) note.add_child(Node.s32('judge_ok', 0)) note.add_child(Node.s32('judge_ng', 0)) note.add_child(Node.s32('calorie', 0)) note.add_child(Node.s32('ghostsize', 0)) note.add_child(Node.string('ghost', '')) note.add_child(Node.u8('opt_speed', 0)) note.add_child(Node.u8('opt_boost', 0)) note.add_child(Node.u8('opt_appearance', 0)) note.add_child(Node.u8('opt_turn', 0)) note.add_child(Node.u8('opt_dark', 0)) note.add_child(Node.u8('opt_scroll', 0)) note.add_child(Node.u8('opt_arrowcolor', 0)) note.add_child(Node.u8('opt_cut', 0)) note.add_child(Node.u8('opt_freeze', 0)) note.add_child(Node.u8('opt_jump', 0)) note.add_child(Node.u8('opt_arrowshape', 0)) note.add_child(Node.u8('opt_filter', 0)) note.add_child(Node.u8('opt_guideline', 0)) note.add_child(Node.u8('opt_gauge', 0)) note.add_child(Node.u8('opt_judgepriority', 0)) note.add_child(Node.u8('opt_timing', 0)) note.add_child(Node.string('basename', '')) note.add_child(Node.string('title_b64', '')) note.add_child(Node.string('artist_b64', '')) note.add_child(Node.u16('bpmMax', 0)) note.add_child(Node.u16('bpmMin', 0)) note.add_child(Node.u8('level', 0)) note.add_child(Node.u8('series', 0)) note.add_child(Node.u32('bemaniFlag', 0)) note.add_child(Node.u32('genreFlag', 0)) note.add_child(Node.u8('limited', 0)) note.add_child(Node.u8('region', 0)) note.add_child(Node.s32('gr_voltage', 0)) note.add_child(Node.s32('gr_stream', 0)) note.add_child(Node.s32('gr_chaos', 0)) note.add_child(Node.s32('gr_freeze', 0)) note.add_child(Node.s32('gr_air', 0)) note.add_child(Node.bool('share', False)) note.add_child(Node.u64('endtime', 0)) note.add_child(Node.s32('folder', 0)) # Swap with server resp = self.exchange('', call) # Verify that response is correct self.assert_path(resp, "response/playerdata/result")
def format_profile(self, userid: UserID, profile: ValidatedDict) -> Node: root = Node.void('gametop') data = Node.void('data') root.add_child(data) player = Node.void('player') data.add_child(player) # Player info and statistics info = Node.void('info') player.add_child(info) info.add_child(Node.s16('jubility', profile.get_int('jubility'))) info.add_child(Node.s16('jubility_yday', profile.get_int('jubility_yday'))) info.add_child(Node.s32('tune_cnt', profile.get_int('tune_cnt'))) info.add_child(Node.s32('save_cnt', profile.get_int('save_cnt'))) info.add_child(Node.s32('saved_cnt', profile.get_int('saved_cnt'))) info.add_child(Node.s32('fc_cnt', profile.get_int('fc_cnt'))) info.add_child(Node.s32('ex_cnt', profile.get_int('ex_cnt'))) info.add_child(Node.s32('pf_cnt', profile.get_int('pf_cnt'))) info.add_child(Node.s32('clear_cnt', profile.get_int('clear_cnt'))) info.add_child(Node.s32('match_cnt', profile.get_int('match_cnt'))) info.add_child(Node.s32('beat_cnt', profile.get_int('beat_cnt'))) info.add_child(Node.s32('mynews_cnt', profile.get_int('mynews_cnt'))) info.add_child(Node.s32('extra_point', profile.get_int('extra_point'))) info.add_child(Node.bool('is_extra_played', profile.get_bool('is_extra_played'))) if 'total_best_score' in profile: info.add_child(Node.s32('total_best_score', profile.get_int('total_best_score'))) # Looks to be set to true when there's an old profile, stops tutorial from # happening on first load. info.add_child(Node.bool('inherit', profile.get_bool('has_old_version'))) # Not saved, but loaded info.add_child(Node.s32('mtg_entry_cnt', 123)) info.add_child(Node.s32('mtg_hold_cnt', 456)) info.add_child(Node.u8('mtg_result', 10)) # First play stuff we don't support free_first_play = Node.void('free_first_play') player.add_child(free_first_play) free_first_play.add_child(Node.bool('is_available', False)) free_first_play.add_child(Node.s32('point', 0)) free_first_play.add_child(Node.s32('point_used', 0)) # Secret unlocks item = Node.void('item') player.add_child(item) item.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list', 32, [-1] * 32, ), )) item.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list', 96, [-1] * 96, ), )) item.add_child(Node.s16('theme_list', profile.get_int('theme_list', -1))) item.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list', 2, [-1] * 2))) item.add_child(Node.s32_array('parts_list', profile.get_int_array('parts_list', 96, [-1] * 96))) new = Node.void('new') item.add_child(new) new.add_child(Node.s32_array( 'secret_list', profile.get_int_array( 'secret_list_new', 32, [-1] * 32, ), )) new.add_child(Node.s32_array( 'title_list', profile.get_int_array( 'title_list_new', 96, [-1] * 96, ), )) new.add_child(Node.s16('theme_list', profile.get_int('theme_list_new', -1))) new.add_child(Node.s32_array('marker_list', profile.get_int_array('marker_list_new', 2, [-1] * 2))) # Last played data, for showing cursor and such lastdict = profile.get_dict('last') last = Node.void('last') player.add_child(last) last.add_child(Node.s32('music_id', lastdict.get_int('music_id'))) last.add_child(Node.s8('marker', lastdict.get_int('marker'))) last.add_child(Node.s16('title', lastdict.get_int('title'))) last.add_child(Node.s8('theme', lastdict.get_int('theme'))) last.add_child(Node.s8('sort', lastdict.get_int('sort'))) last.add_child(Node.s8('rank_sort', lastdict.get_int('rank_sort'))) last.add_child(Node.s8('combo_disp', lastdict.get_int('combo_disp'))) last.add_child(Node.s8('seq_id', lastdict.get_int('seq_id'))) last.add_child(Node.s16('parts', lastdict.get_int('parts'))) last.add_child(Node.s8('category', lastdict.get_int('category'))) last.add_child(Node.s64('play_time', lastdict.get_int('play_time'))) last.add_child(Node.string('shopname', lastdict.get_str('shopname'))) last.add_child(Node.string('areaname', lastdict.get_str('areaname'))) last.add_child(Node.s8('expert_option', lastdict.get_int('expert_option'))) last.add_child(Node.s8('matching', lastdict.get_int('matching'))) last.add_child(Node.s8('hazard', lastdict.get_int('hazard'))) last.add_child(Node.s8('hard', lastdict.get_int('hard'))) # Miscelaneous crap player.add_child(Node.s32('session_id', 1)) player.add_child(Node.u64('event_flag', 0)) # Macchiato event macchiatodict = profile.get_dict('macchiato') macchiato = Node.void('macchiato') player.add_child(macchiato) macchiato.add_child(Node.s32('pack_id', macchiatodict.get_int('pack_id'))) macchiato.add_child(Node.u16('bean_num', macchiatodict.get_int('bean_num'))) macchiato.add_child(Node.s32('daily_milk_num', macchiatodict.get_int('daily_milk_num'))) macchiato.add_child(Node.bool('is_received_daily_milk', macchiatodict.get_bool('is_received_daily_milk'))) macchiato.add_child(Node.s32('today_tune_cnt', macchiatodict.get_int('today_tune_cnt'))) macchiato.add_child(Node.s32_array( 'daily_milk_bonus', macchiatodict.get_int_array('daily_milk_bonus', 9, [-1, -1, -1, -1, -1, -1, -1, -1, -1]), )) macchiato.add_child(Node.s32('daily_play_burst', macchiatodict.get_int('daily_play_burst'))) macchiato.add_child(Node.bool('sub_menu_is_completed', macchiatodict.get_bool('sub_menu_is_completed'))) macchiato.add_child(Node.s32('compensation_milk', macchiatodict.get_int('compensation_milk'))) macchiato.add_child(Node.s32('match_cnt', macchiatodict.get_int('match_cnt'))) # Probably never will support this macchiato_music_list = Node.void('macchiato_music_list') macchiato.add_child(macchiato_music_list) macchiato_music_list.set_attribute('count', '0') # Same with this comment macchiato.add_child(Node.s32('sub_pack_id', 0)) sub_macchiato_music_list = Node.void('sub_macchiato_music_list') macchiato.add_child(sub_macchiato_music_list) sub_macchiato_music_list.set_attribute('count', '0') # And this season_music_list = Node.void('season_music_list') macchiato.add_child(season_music_list) season_music_list.set_attribute('count', '0') # Weird, this is sent as a void with a bunch of subnodes, but returned as an int array. achievement_list = Node.void('achievement_list') macchiato.add_child(achievement_list) achievement_list.set_attribute('count', '0') # Also probably never supporting this either cow_list = Node.void('cow_list') macchiato.add_child(cow_list) cow_list.set_attribute('count', '0') # No news, ever. news = Node.void('news') player.add_child(news) news.add_child(Node.s16('checked', 0)) # No rival support, yet. rivallist = Node.void('rivallist') player.add_child(rivallist) rivallist.set_attribute('count', '0') # Full combo daily challenge. entry = self.data.local.game.get_time_sensitive_settings(self.game, self.version, 'fc_challenge') if entry is None: entry = ValidatedDict() # Figure out if we've played these songs start_time, end_time = self.data.local.network.get_schedule_duration('daily') today_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('today', -1), timelimit=start_time) whim_attempts = self.data.local.music.get_all_attempts(self.game, self.version, userid, entry.get_int('whim', -1), timelimit=start_time) challenge = Node.void('challenge') player.add_child(challenge) today = Node.void('today') challenge.add_child(today) today.add_child(Node.s32('music_id', entry.get_int('today', -1))) today.add_child(Node.u8('state', 0x40 if len(today_attempts) > 0 else 0x0)) whim = Node.void('whim') challenge.add_child(whim) whim.add_child(Node.s32('music_id', entry.get_int('whim', -1))) whim.add_child(Node.u8('state', 0x40 if len(whim_attempts) > 0 else 0x0)) # Sane defaults for unknown nodes only_now_music = Node.void('only_now_music') player.add_child(only_now_music) only_now_music.set_attribute('count', '0') lab_edit_seq = Node.void('lab_edit_seq') player.add_child(lab_edit_seq) lab_edit_seq.set_attribute('count', '0') kac_music = Node.void('kac_music') player.add_child(kac_music) kac_music.set_attribute('count', '0') history = Node.void('history') player.add_child(history) history.set_attribute('count', '0') share_music = Node.void('share_music') player.add_child(share_music) share_music.set_attribute('count', '0') bonus_music = Node.void('bonus_music') player.add_child(bonus_music) bonus_music.set_attribute('count', '0') bingo = Node.void('bingo') player.add_child(bingo) reward = Node.void('reward') bingo.add_child(reward) reward.add_child(Node.s32('total', 0)) reward.add_child(Node.s32('point', 0)) group = Node.void('group') player.add_child(group) group.add_child(Node.s32('group_id', 0)) # Basic profile info player.add_child(Node.string('name', profile.get_str('name', 'なし'))) player.add_child(Node.s32('jid', profile.get_int('extid'))) player.add_child(Node.string('refid', profile.get_str('refid'))) # Miscelaneous history stuff data.add_child(Node.u8('termver', 16)) data.add_child(Node.u32('season_etime', 0)) data.add_child(Node.s32_array( 'white_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) data.add_child(Node.s32_array( 'open_music_list', [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, ], )) # Unsupported collaboration events with other games collabo_info = Node.void('collabo_info') data.add_child(collabo_info) # Unsupported policy break stuff policy_break = Node.void('policy_break') collabo_info.add_child(policy_break) policy_break.set_attribute('type', '1') policy_break.add_child(Node.u8('state', 1)) policy_break.add_child(Node.bool('is_report_end', False)) # Unsupported vocaloid stuff vocaloid_event = Node.void('vocaloid_event') collabo_info.add_child(vocaloid_event) vocaloid_event.set_attribute('type', '1') vocaloid_event.add_child(Node.u8('state', 0)) vocaloid_event.add_child(Node.s32('music_id', 0)) # Unsupported vocaloid stuff vocaloid_event2 = Node.void('vocaloid_event2') collabo_info.add_child(vocaloid_event2) vocaloid_event2.set_attribute('type', '1') vocaloid_event2.add_child(Node.u8('state', 0)) vocaloid_event2.add_child(Node.s32('music_id', 0)) # Maybe it is possible to turn off internet matching here? lab = Node.void('lab') data.add_child(lab) lab.add_child(Node.bool('is_open', False)) matching_off = Node.void('matching_off') data.add_child(matching_off) matching_off.add_child(Node.bool('is_open', True)) return root