import base64 import time from typing import Optional from bemani.client.base import BaseClient from bemani.protocol import Node class MetalGearArcadeClient(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", "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")