509cb4f0d9
Exact commands run were: python3 -m libcst.tool codemod convert_format_to_fstring.ConvertFormatStringCommand . --no-format python3 setup.py build_ext --inplace
909 lines
37 KiB
Python
909 lines
37 KiB
Python
import base64
|
|
import random
|
|
import time
|
|
from typing import Optional, Dict, List, Tuple, Any
|
|
|
|
from bemani.client.base import BaseClient
|
|
from bemani.common import ID, Time
|
|
from bemani.protocol import Node
|
|
|
|
|
|
def b64str(string: str) -> str:
|
|
return base64.b64encode(string.encode()).decode('ascii')
|
|
|
|
|
|
class DDRAceClient(BaseClient):
|
|
NAME = 'TEST'
|
|
|
|
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', b64str('2.4.0')))
|
|
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_system_convcardnumber(self, cardno: str) -> None:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
system = Node.void('system')
|
|
call.add_child(system)
|
|
system.set_attribute('method', 'convcardnumber')
|
|
info = Node.void('info')
|
|
system.add_child(info)
|
|
info.add_child(Node.s32('version', 1))
|
|
data = Node.void('data')
|
|
system.add_child(data)
|
|
data.add_child(Node.string('card_id', cardno))
|
|
data.add_child(Node.s32('card_type', 1))
|
|
|
|
# Swap with server
|
|
resp = self.exchange('', call)
|
|
|
|
# Verify that response is correct
|
|
self.assert_path(resp, "response/system/data/card_number")
|
|
self.assert_path(resp, "response/system/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_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 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 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 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_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 verify_usergamedata_send(self, ref_id: str, ext_id: int, msg_type: str, send_only_common: bool=False) -> None:
|
|
call = self.call_node()
|
|
|
|
# Set up profile write
|
|
profiledata = {
|
|
'COMMON': [
|
|
b'1',
|
|
b'0', # shoparea spot, filled in below
|
|
b'3c880f8',
|
|
b'1',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'ffffffffffffffff',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'', # Name spot, filled in below
|
|
ID.format_extid(ext_id).encode('ascii'),
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
],
|
|
'OPTION': [
|
|
b'0',
|
|
b'3',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'3',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'1',
|
|
b'2',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'10.000000',
|
|
b'10.000000',
|
|
b'10.000000',
|
|
b'10.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
],
|
|
'LAST': [
|
|
b'1',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
],
|
|
'RIVAL': [
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'0.000000',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
b'',
|
|
]
|
|
}
|
|
|
|
if msg_type == 'new':
|
|
# New profile gets blank name, because we save over it at the end of the round.
|
|
profiledata['COMMON'][1] = b'0'
|
|
profiledata['COMMON'][25] = b''
|
|
|
|
elif msg_type == 'existing':
|
|
# Exiting profile gets our hardcoded name saved.
|
|
profiledata['COMMON'][1] = b'3a'
|
|
profiledata['COMMON'][25] = self.NAME.encode('shift-jis')
|
|
|
|
else:
|
|
raise Exception(f'Unknown message type {msg_type}!')
|
|
|
|
if send_only_common:
|
|
profiledata = {'COMMON': profiledata['COMMON']}
|
|
|
|
# Construct node
|
|
playerdata = Node.void('playerdata')
|
|
call.add_child(playerdata)
|
|
playerdata.set_attribute('method', 'usergamedata_send')
|
|
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('refid', ref_id))
|
|
data.add_child(Node.string('dataid', ref_id))
|
|
data.add_child(Node.string('gamekind', 'MDX'))
|
|
data.add_child(Node.u32('datanum', len(profiledata.keys())))
|
|
record = Node.void('record')
|
|
data.add_child(record)
|
|
for ptype in profiledata:
|
|
profile = [b'ffffffff', ptype.encode('ascii')] + profiledata[ptype]
|
|
d = Node.string('d', base64.b64encode(b','.join(profile)).decode('ascii'))
|
|
record.add_child(d)
|
|
d.add_child(Node.string('bin1', ''))
|
|
|
|
# Swap with server
|
|
resp = self.exchange('', call)
|
|
self.assert_path(resp, "response/playerdata/result")
|
|
|
|
def verify_usergamedata_recv(self, ref_id: str) -> str:
|
|
call = self.call_node()
|
|
|
|
# Construct node
|
|
playerdata = Node.void('playerdata')
|
|
call.add_child(playerdata)
|
|
playerdata.set_attribute('method', 'usergamedata_recv')
|
|
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('refid', ref_id))
|
|
data.add_child(Node.string('dataid', ref_id))
|
|
data.add_child(Node.string('gamekind', 'MDX'))
|
|
data.add_child(Node.u32('recv_num', 4))
|
|
data.add_child(Node.string('recv_csv', 'COMMON,3fffffffff,OPTION,3fffffffff,LAST,3fffffffff,RIVAL,3fffffffff'))
|
|
|
|
# Swap with server
|
|
resp = self.exchange('', call)
|
|
self.assert_path(resp, "response/playerdata/result")
|
|
self.assert_path(resp, "response/playerdata/player/record/d/bin1")
|
|
self.assert_path(resp, "response/playerdata/player/record_num")
|
|
|
|
profiles = 0
|
|
name = ''
|
|
for child in resp.child('playerdata/player/record').children:
|
|
if child.name != 'd':
|
|
continue
|
|
|
|
if profiles == 0:
|
|
bindata = child.value
|
|
profiledata = base64.b64decode(bindata).split(b',')
|
|
name = profiledata[25].decode('ascii')
|
|
|
|
profiles = profiles + 1
|
|
|
|
if profiles != 4:
|
|
raise Exception('Didn\'t receive all four profiles in the right order!')
|
|
|
|
return name
|
|
|
|
def verify(self, cardid: Optional[str]) -> None:
|
|
# Verify boot sequence is okay
|
|
self.verify_services_get(
|
|
expected_services=[
|
|
'pcbtracker',
|
|
'pcbevent',
|
|
'local',
|
|
'message',
|
|
'facility',
|
|
'cardmng',
|
|
'package',
|
|
'posevent',
|
|
'pkglist',
|
|
'dlstatus',
|
|
'eacoin',
|
|
'lobby',
|
|
'ntp',
|
|
'keepalive'
|
|
]
|
|
)
|
|
paseli_enabled = self.verify_pcbtracker_alive()
|
|
self.verify_message_get()
|
|
self.verify_package_list()
|
|
location = self.verify_facility_get()
|
|
self.verify_pcbevent_put()
|
|
self.verify_eventlog_write(location)
|
|
|
|
# Verify the game-wide packets Ace insists on sending before profile load
|
|
is_new, music = self.verify_playerdata_usergamedata_advanced_userload('X0000000000000000000000000123456')
|
|
if not is_new:
|
|
raise Exception('Fake profiles should be new!')
|
|
if len(music) > 0:
|
|
raise Exception('Fake profiles should have no scores associated!')
|
|
|
|
# Verify card registration and profile lookup
|
|
if cardid is not None:
|
|
card = cardid
|
|
else:
|
|
card = self.random_card()
|
|
print(f"Generated random card ID {card} for use.")
|
|
|
|
if cardid is None:
|
|
self.verify_cardmng_inquire(card, msg_type='unregistered', paseli_enabled=paseli_enabled)
|
|
self.verify_system_convcardnumber(card)
|
|
ref_id = self.verify_cardmng_getrefid(card)
|
|
if len(ref_id) != 16:
|
|
raise Exception(f'Invalid refid \'{ref_id}\' returned when registering card')
|
|
if ref_id != self.verify_cardmng_inquire(card, msg_type='new', paseli_enabled=paseli_enabled):
|
|
raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card')
|
|
extid = self.verify_playerdata_usergamedata_advanced_usernew(ref_id)
|
|
self.verify_usergamedata_send(ref_id, extid, 'new')
|
|
self.verify_playerdata_usergamedata_advanced_inheritance(ref_id, location)
|
|
name = self.verify_usergamedata_recv(ref_id)
|
|
if name != '':
|
|
raise Exception('Name stored on profile we just created!')
|
|
self.verify_usergamedata_send(ref_id, extid, 'existing', send_only_common=True)
|
|
name = self.verify_usergamedata_recv(ref_id)
|
|
if name != self.NAME:
|
|
raise Exception('Name stored on profile is incorrect!')
|
|
else:
|
|
print("Skipping new card checks for existing card")
|
|
ref_id = self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled)
|
|
|
|
# Verify pin handling and return card handling
|
|
self.verify_cardmng_authpass(ref_id, correct=True)
|
|
self.verify_cardmng_authpass(ref_id, correct=False)
|
|
if ref_id != self.verify_cardmng_inquire(card, msg_type='query', paseli_enabled=paseli_enabled):
|
|
raise Exception(f'Invalid refid \'{ref_id}\' returned when querying card')
|
|
|
|
if cardid is None:
|
|
is_new, music = self.verify_playerdata_usergamedata_advanced_userload(ref_id)
|
|
if is_new:
|
|
raise Exception('Profile should not be new!')
|
|
if len(music) > 0:
|
|
raise Exception('Created profile should have no scores associated!')
|
|
|
|
# Verify score saving and updating
|
|
for phase in [1, 2]:
|
|
if phase == 1:
|
|
dummyscores = [
|
|
# An okay score on a chart
|
|
{
|
|
'id': 10,
|
|
'chart': 3,
|
|
'rank': 5,
|
|
'halo': 6,
|
|
'score': 765432,
|
|
'ghost': '765432',
|
|
},
|
|
# A good score on an easier chart of the same song
|
|
{
|
|
'id': 10,
|
|
'chart': 2,
|
|
'rank': 2,
|
|
'halo': 8,
|
|
'score': 876543,
|
|
'ghost': '876543',
|
|
},
|
|
# A bad score on a hard chart
|
|
{
|
|
'id': 479,
|
|
'chart': 2,
|
|
'rank': 11,
|
|
'halo': 6,
|
|
'score': 654321,
|
|
'ghost': '654321',
|
|
},
|
|
# A terrible score on an easy chart
|
|
{
|
|
'id': 479,
|
|
'chart': 1,
|
|
'rank': 15,
|
|
'halo': 6,
|
|
'score': 123456,
|
|
'ghost': '123456',
|
|
},
|
|
]
|
|
if phase == 2:
|
|
dummyscores = [
|
|
# A better score on the same chart
|
|
{
|
|
'id': 10,
|
|
'chart': 3,
|
|
'rank': 4,
|
|
'halo': 7,
|
|
'score': 888888,
|
|
'ghost': '888888',
|
|
},
|
|
# A worse score on another same chart
|
|
{
|
|
'id': 10,
|
|
'chart': 2,
|
|
'rank': 3,
|
|
'halo': 7,
|
|
'score': 654321,
|
|
'ghost': '654321',
|
|
'expected_score': 876543,
|
|
'expected_halo': 8,
|
|
'expected_rank': 2,
|
|
'expected_ghost': '876543',
|
|
},
|
|
]
|
|
|
|
pos = 0
|
|
for dummyscore in dummyscores:
|
|
self.verify_playerdata_usergamedata_advanced_usersave(
|
|
ref_id,
|
|
extid,
|
|
location,
|
|
dummyscore,
|
|
pos,
|
|
)
|
|
pos = pos + 1
|
|
|
|
is_new, scores = self.verify_playerdata_usergamedata_advanced_userload(ref_id)
|
|
if is_new:
|
|
raise Exception('Profile should not be new!')
|
|
if len(scores) == 0:
|
|
raise Exception('Expected some scores after saving!')
|
|
|
|
for expected in dummyscores:
|
|
actual = None
|
|
for received in scores:
|
|
if received['id'] == expected['id'] and received['chart'] == expected['chart']:
|
|
actual = received
|
|
break
|
|
|
|
if actual is None:
|
|
raise Exception(f"Didn't find song {expected['id']} chart {expected['chart']} in response!")
|
|
|
|
if 'expected_score' in expected:
|
|
expected_score = expected['expected_score']
|
|
else:
|
|
expected_score = expected['score']
|
|
if 'expected_rank' in expected:
|
|
expected_rank = expected['expected_rank']
|
|
else:
|
|
expected_rank = expected['rank']
|
|
if 'expected_halo' in expected:
|
|
expected_halo = expected['expected_halo']
|
|
else:
|
|
expected_halo = expected['halo']
|
|
|
|
if actual['score'] != expected_score:
|
|
raise Exception(f'Expected a score of \'{expected_score}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got score \'{actual["score"]}\'')
|
|
if actual['rank'] != expected_rank:
|
|
raise Exception(f'Expected a rank of \'{expected_rank}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got rank \'{actual["rank"]}\'')
|
|
if actual['halo'] != expected_halo:
|
|
raise Exception(f'Expected a halo of \'{expected_halo}\' for song \'{expected["id"]}\' chart \'{expected["chart"]}\' but got halo \'{actual["halo"]}\'')
|
|
|
|
# Now verify that the ghost for this score is what we saved
|
|
ghost = self.verify_playerdata_usergamedata_advanced_ghostload(ref_id, received['ghostid'])
|
|
if 'expected_ghost' in expected:
|
|
expected_ghost = expected['expected_ghost']
|
|
else:
|
|
expected_ghost = expected['ghost']
|
|
|
|
if ghost['id'] != received['id']:
|
|
raise Exception(f'Wrong song ID \'{ghost["id"]}\' returned for ghost, expected ID \'{received["id"]}\'')
|
|
if ghost['chart'] != received['chart']:
|
|
raise Exception(f'Wrong song chart \'{ghost["chart"]}\' returned for ghost, expected chart \'{received["chart"]}\'')
|
|
if ghost['ghost'] != expected_ghost:
|
|
raise Exception(f'Wrong ghost data \'{ghost["ghost"]}\' returned for ghost, expected \'{expected_ghost}\'')
|
|
if ghost['extid'] != extid:
|
|
raise Exception(f'Wrong extid \'{ghost["extid"]}\' returned for ghost, expected \'{extid}\'')
|
|
|
|
# Sleep so we don't end up putting in score history on the same second
|
|
time.sleep(1)
|
|
|
|
# Simulate game over conditions
|
|
self.verify_playerdata_usergamedata_advanced_usersave(
|
|
ref_id,
|
|
extid,
|
|
location,
|
|
{},
|
|
-1,
|
|
)
|
|
else:
|
|
print("Skipping score checks for existing card")
|
|
|
|
# Verify global scores now that we've inserted some
|
|
self.verify_playerdata_usergamedata_advanced_rivalload('X0000000000000000000000000123456', 1)
|
|
self.verify_playerdata_usergamedata_advanced_rivalload('X0000000000000000000000000123456', 2)
|
|
self.verify_playerdata_usergamedata_advanced_rivalload('X0000000000000000000000000123456', 4)
|
|
|
|
# Verify paseli handling
|
|
if paseli_enabled:
|
|
print("PASELI enabled for this PCBID, executing PASELI checks")
|
|
else:
|
|
print("PASELI disabled for this PCBID, skipping PASELI checks")
|
|
return
|
|
|
|
sessid, balance = self.verify_eacoin_checkin(card)
|
|
if balance == 0:
|
|
print("Skipping PASELI consume check because card has 0 balance")
|
|
else:
|
|
self.verify_eacoin_consume(sessid, balance, random.randint(0, balance))
|
|
self.verify_eacoin_checkout(sessid)
|