1
0
mirror of synced 2024-12-18 09:15:54 +01:00
bemaniutils/bemani/client/mga/mga.py
2024-01-02 02:46:24 +00:00

351 lines
13 KiB
Python

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")