1
0
mirror of synced 2024-12-21 10:25:54 +01:00
bemaniutils/bemani/client/mga/mga.py

346 lines
13 KiB
Python
Raw Normal View History

import base64
import time
from typing import Optional
from bemani.client.base import BaseClient
from bemani.protocol import Node
class MetalGearArcadeClient(BaseClient):
NAME = ''
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', '1.9.1'))
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_getmaster(self) -> None:
call = self.call_node()
# Construct node
system = Node.void('system')
call.add_child(system)
system.set_attribute('method', 'getmaster')
data = Node.void('data')
system.add_child(data)
data.add_child(Node.string('gamekind', 'I36'))
data.add_child(Node.string('datatype', 'S_SRVMSG'))
data.add_child(Node.string('datakey', 'INFO'))
# Swap with server
resp = self.exchange('', call)
# Verify that response is correct
self.assert_path(resp, "response/system/result")
def verify_usergamedata_send(self, ref_id: str, msg_type: str) -> None:
call = self.call_node()
# Set up profile write
profiledata = [
b'ffffffff',
b'PLAYDATA',
b'8',
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'ffffffffffffa928',
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'PLAYER',
b'JP-13',
b'ea',
b'',
b'JP-13',
b'',
b'',
b''
]
outfitdata = [
b'ffffffff',
b'OUTFIT',
b'8',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'202000020400800',
b'1000100',
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'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAABwACxEWEUgRWBEuADkRZgBmAAAcAAsRFhFIEVgRLgA5EWYAZgAAHAALERYRSBFYES4AORFmAGYA',
b'AAAAAA==',
b'',
]
weapondata = [
b'ffffffff',
b'WEAPON',
b'8',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'0',
b'201000000003',
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'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
]
mainruledata = [
b'ffffffff',
b'MAINRULE',
b'8',
b'6',
b'800000',
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'1.000000',
b'0.000000',
b'10.000000',
b'4.000000',
b'0.000000',
b'0.000000',
b'0.000000',
b'0.000000',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAA=',
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[27] = b''
elif msg_type == 'existing':
# Exiting profile gets our hardcoded name saved.
profiledata[27] = self.NAME.encode('shift-jis')
# Construct node
playerdata = Node.void('playerdata')
call.add_child(playerdata)
playerdata.set_attribute('method', 'usergamedata_send')
playerdata.add_child(Node.u32('retrycnt', 0))
data = Node.void('data')
playerdata.add_child(data)
data.add_child(Node.string('eaid', ref_id))
data.add_child(Node.string('gamekind', 'I36'))
data.add_child(Node.u32('datanum', 4))
record = Node.void('record')
data.add_child(record)
d = Node.string('d', base64.b64encode(b','.join(profiledata)).decode('ascii'))
record.add_child(d)
d.add_child(Node.string('bin1', ''))
d = Node.string('d', base64.b64encode(b','.join(outfitdata)).decode('ascii'))
record.add_child(d)
d.add_child(Node.string('bin1', ''))
d = Node.string('d', base64.b64encode(b','.join(weapondata)).decode('ascii'))
record.add_child(d)
d.add_child(Node.string('bin1', ''))
d = Node.string('d', base64.b64encode(b','.join(mainruledata)).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')
data = Node.void('data')
playerdata.add_child(data)
data.add_child(Node.string('eaid', ref_id))
data.add_child(Node.string('gamekind', 'I36'))
data.add_child(Node.u32('recv_num', 4))
data.add_child(Node.string('recv_csv', 'PLAYDATA,3fffffffff,OUTFIT,3fffffffff,WEAPON,3fffffffff,MAINRULE,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")
# Grab binary data, parse out name
bindata = resp.child_value('playerdata/player/record/d')
profiledata = base64.b64decode(bindata).split(b',')
# We lob off the first two values in returning profile, so the name is offset by two
return profiledata[25].decode('shift-jis')
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)
self.verify_system_getmaster()
# 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)
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')
# MGA doesn't read a new profile, it just writes out CSV for a blank one
self.verify_usergamedata_send(ref_id, msg_type='new')
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:
# Verify profile saving
name = self.verify_usergamedata_recv(ref_id)
if name != '':
raise Exception('New profile has a name associated with it!')
self.verify_usergamedata_send(ref_id, msg_type='existing')
name = self.verify_usergamedata_recv(ref_id)
if name != self.NAME:
raise Exception('Existing profile has no name associated with it!')
else:
print("Skipping score checks for existing card")