forked from Hay1tsme/artemis
let black do it's magic
This commit is contained in:
188
core/allnet.py
188
core/allnet.py
@ -16,8 +16,9 @@ from core.data import Data
|
||||
from core.utils import Utils
|
||||
from core.const import *
|
||||
|
||||
|
||||
class AllnetServlet:
|
||||
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
||||
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
||||
super().__init__()
|
||||
self.config = core_cfg
|
||||
self.config_folder = cfg_folder
|
||||
@ -27,35 +28,45 @@ class AllnetServlet:
|
||||
self.logger = logging.getLogger("allnet")
|
||||
if not hasattr(self.logger, "initialized"):
|
||||
log_fmt_str = "[%(asctime)s] Allnet | %(levelname)s | %(message)s"
|
||||
log_fmt = logging.Formatter(log_fmt_str)
|
||||
log_fmt = logging.Formatter(log_fmt_str)
|
||||
|
||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.config.server.log_dir, "allnet"), when="d", backupCount=10)
|
||||
fileHandler = TimedRotatingFileHandler(
|
||||
"{0}/{1}.log".format(self.config.server.log_dir, "allnet"),
|
||||
when="d",
|
||||
backupCount=10,
|
||||
)
|
||||
fileHandler.setFormatter(log_fmt)
|
||||
|
||||
|
||||
consoleHandler = logging.StreamHandler()
|
||||
consoleHandler.setFormatter(log_fmt)
|
||||
|
||||
self.logger.addHandler(fileHandler)
|
||||
self.logger.addHandler(consoleHandler)
|
||||
|
||||
|
||||
self.logger.setLevel(core_cfg.allnet.loglevel)
|
||||
coloredlogs.install(level=core_cfg.allnet.loglevel, logger=self.logger, fmt=log_fmt_str)
|
||||
coloredlogs.install(
|
||||
level=core_cfg.allnet.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||
)
|
||||
self.logger.initialized = True
|
||||
|
||||
plugins = Utils.get_all_titles()
|
||||
|
||||
if len(plugins) == 0:
|
||||
self.logger.error("No games detected!")
|
||||
|
||||
|
||||
for _, mod in plugins.items():
|
||||
if hasattr(mod.index, "get_allnet_info"):
|
||||
for code in mod.game_codes:
|
||||
enabled, uri, host = mod.index.get_allnet_info(code, self.config, self.config_folder)
|
||||
|
||||
enabled, uri, host = mod.index.get_allnet_info(
|
||||
code, self.config, self.config_folder
|
||||
)
|
||||
|
||||
if enabled:
|
||||
self.uri_registry[code] = (uri, host)
|
||||
|
||||
self.logger.info(f"Allnet serving {len(self.uri_registry)} games on port {core_cfg.allnet.port}")
|
||||
self.logger.info(
|
||||
f"Allnet serving {len(self.uri_registry)} games on port {core_cfg.allnet.port}"
|
||||
)
|
||||
|
||||
def handle_poweron(self, request: Request, _: Dict):
|
||||
request_ip = request.getClientAddress().host
|
||||
@ -67,14 +78,22 @@ class AllnetServlet:
|
||||
req = AllnetPowerOnRequest(req_dict[0])
|
||||
# Validate the request. Currently we only validate the fields we plan on using
|
||||
|
||||
if not req.game_id or not req.ver or not req.token or not req.serial or not req.ip:
|
||||
raise AllnetRequestException(f"Bad auth request params from {request_ip} - {vars(req)}")
|
||||
|
||||
if (
|
||||
not req.game_id
|
||||
or not req.ver
|
||||
or not req.token
|
||||
or not req.serial
|
||||
or not req.ip
|
||||
):
|
||||
raise AllnetRequestException(
|
||||
f"Bad auth request params from {request_ip} - {vars(req)}"
|
||||
)
|
||||
|
||||
except AllnetRequestException as e:
|
||||
if e.message != "":
|
||||
self.logger.error(e)
|
||||
return b""
|
||||
|
||||
|
||||
if req.format_ver == 3:
|
||||
resp = AllnetPowerOnResponse3(req.token)
|
||||
else:
|
||||
@ -83,26 +102,32 @@ class AllnetServlet:
|
||||
self.logger.debug(f"Allnet request: {vars(req)}")
|
||||
if req.game_id not in self.uri_registry:
|
||||
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
||||
self.data.base.log_event("allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg)
|
||||
self.data.base.log_event(
|
||||
"allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg
|
||||
)
|
||||
self.logger.warn(msg)
|
||||
|
||||
resp.stat = 0
|
||||
return self.dict_to_http_form_string([vars(resp)])
|
||||
|
||||
|
||||
resp.uri, resp.host = self.uri_registry[req.game_id]
|
||||
|
||||
machine = self.data.arcade.get_machine(req.serial)
|
||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
|
||||
self.data.base.log_event("allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg)
|
||||
self.data.base.log_event(
|
||||
"allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg
|
||||
)
|
||||
self.logger.warn(msg)
|
||||
|
||||
resp.stat = 0
|
||||
return self.dict_to_http_form_string([vars(resp)])
|
||||
|
||||
|
||||
if machine is not None:
|
||||
arcade = self.data.arcade.get_arcade(machine["arcade"])
|
||||
country = arcade["country"] if machine["country"] is None else machine["country"]
|
||||
country = (
|
||||
arcade["country"] if machine["country"] is None else machine["country"]
|
||||
)
|
||||
if country is None:
|
||||
country = AllnetCountryCode.JAPAN.value
|
||||
|
||||
@ -111,16 +136,30 @@ class AllnetServlet:
|
||||
resp.allnet_id = machine["id"]
|
||||
resp.name = arcade["name"] if arcade["name"] is not None else ""
|
||||
resp.nickname = arcade["nickname"] if arcade["nickname"] is not None else ""
|
||||
resp.region0 = arcade["region_id"] if arcade["region_id"] is not None else AllnetJapanRegionId.AICHI.value
|
||||
resp.region_name0 = arcade["country"] if arcade["country"] is not None else AllnetCountryCode.JAPAN.value
|
||||
resp.region_name1 = arcade["state"] if arcade["state"] is not None else AllnetJapanRegionId.AICHI.name
|
||||
resp.region0 = (
|
||||
arcade["region_id"]
|
||||
if arcade["region_id"] is not None
|
||||
else AllnetJapanRegionId.AICHI.value
|
||||
)
|
||||
resp.region_name0 = (
|
||||
arcade["country"]
|
||||
if arcade["country"] is not None
|
||||
else AllnetCountryCode.JAPAN.value
|
||||
)
|
||||
resp.region_name1 = (
|
||||
arcade["state"]
|
||||
if arcade["state"] is not None
|
||||
else AllnetJapanRegionId.AICHI.name
|
||||
)
|
||||
resp.region_name2 = arcade["city"] if arcade["city"] is not None else ""
|
||||
resp.client_timezone = arcade["timezone"] if arcade["timezone"] is not None else "+0900"
|
||||
|
||||
resp.client_timezone = (
|
||||
arcade["timezone"] if arcade["timezone"] is not None else "+0900"
|
||||
)
|
||||
|
||||
int_ver = req.ver.replace(".", "")
|
||||
resp.uri = resp.uri.replace("$v", int_ver)
|
||||
resp.host = resp.host.replace("$v", int_ver)
|
||||
|
||||
|
||||
msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}"
|
||||
self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg)
|
||||
self.logger.info(msg)
|
||||
@ -139,8 +178,10 @@ class AllnetServlet:
|
||||
# Validate the request. Currently we only validate the fields we plan on using
|
||||
|
||||
if not req.game_id or not req.ver or not req.serial:
|
||||
raise AllnetRequestException(f"Bad download request params from {request_ip} - {vars(req)}")
|
||||
|
||||
raise AllnetRequestException(
|
||||
f"Bad download request params from {request_ip} - {vars(req)}"
|
||||
)
|
||||
|
||||
except AllnetRequestException as e:
|
||||
if e.message != "":
|
||||
self.logger.error(e)
|
||||
@ -149,8 +190,8 @@ class AllnetServlet:
|
||||
resp = AllnetDownloadOrderResponse()
|
||||
if not self.config.allnet.allow_online_updates:
|
||||
return self.dict_to_http_form_string([vars(resp)])
|
||||
|
||||
else: # TODO: Actual dlorder response
|
||||
|
||||
else: # TODO: Actual dlorder response
|
||||
return self.dict_to_http_form_string([vars(resp)])
|
||||
|
||||
def handle_billing_request(self, request: Request, _: Dict):
|
||||
@ -159,10 +200,10 @@ class AllnetServlet:
|
||||
if req_dict is None:
|
||||
self.logger.error(f"Failed to parse request {request.content.getvalue()}")
|
||||
return b""
|
||||
|
||||
|
||||
self.logger.debug(f"request {req_dict}")
|
||||
|
||||
rsa = RSA.import_key(open(self.config.billing.signing_key, 'rb').read())
|
||||
rsa = RSA.import_key(open(self.config.billing.signing_key, "rb").read())
|
||||
signer = PKCS1_v1_5.new(rsa)
|
||||
digest = SHA.new()
|
||||
|
||||
@ -178,30 +219,34 @@ class AllnetServlet:
|
||||
machine = self.data.arcade.get_machine(kc_serial)
|
||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||
msg = f"Unrecognised serial {kc_serial} attempted billing checkin from {request_ip} for game {kc_game}."
|
||||
self.data.base.log_event("allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg)
|
||||
self.data.base.log_event(
|
||||
"allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg
|
||||
)
|
||||
self.logger.warn(msg)
|
||||
|
||||
resp = BillingResponse("", "", "", "")
|
||||
resp.result = "1"
|
||||
return self.dict_to_http_form_string([vars(resp)])
|
||||
|
||||
msg = f"Billing checkin from {request.getClientIP()}: game {kc_game} keychip {kc_serial} playcount " \
|
||||
msg = (
|
||||
f"Billing checkin from {request.getClientIP()}: game {kc_game} keychip {kc_serial} playcount "
|
||||
f"{kc_playcount} billing_type {kc_billigtype} nearfull {kc_nearfull} playlimit {kc_playlimit}"
|
||||
)
|
||||
self.logger.info(msg)
|
||||
self.data.base.log_event('billing', 'BILLING_CHECKIN_OK', logging.INFO, msg)
|
||||
self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg)
|
||||
|
||||
while kc_playcount > kc_playlimit:
|
||||
kc_playlimit += 1024
|
||||
kc_nearfull += 1024
|
||||
|
||||
|
||||
playlimit = kc_playlimit
|
||||
nearfull = kc_nearfull + (kc_billigtype * 0x00010000)
|
||||
|
||||
digest.update(playlimit.to_bytes(4, 'little') + kc_serial_bytes)
|
||||
digest.update(playlimit.to_bytes(4, "little") + kc_serial_bytes)
|
||||
playlimit_sig = signer.sign(digest).hex()
|
||||
|
||||
digest = SHA.new()
|
||||
digest.update(nearfull.to_bytes(4, 'little') + kc_serial_bytes)
|
||||
digest.update(nearfull.to_bytes(4, "little") + kc_serial_bytes)
|
||||
nearfull_sig = signer.sign(digest).hex()
|
||||
|
||||
# TODO: playhistory
|
||||
@ -222,16 +267,16 @@ class AllnetServlet:
|
||||
def kvp_to_dict(self, kvp: List[str]) -> List[Dict[str, Any]]:
|
||||
ret: List[Dict[str, Any]] = []
|
||||
for x in kvp:
|
||||
items = x.split('&')
|
||||
items = x.split("&")
|
||||
tmp = {}
|
||||
|
||||
for item in items:
|
||||
kvp = item.split('=')
|
||||
kvp = item.split("=")
|
||||
if len(kvp) == 2:
|
||||
tmp[kvp[0]] = kvp[1]
|
||||
|
||||
ret.append(tmp)
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
def billing_req_to_dict(self, data: bytes):
|
||||
@ -241,8 +286,8 @@ class AllnetServlet:
|
||||
try:
|
||||
decomp = zlib.decompressobj(-zlib.MAX_WBITS)
|
||||
unzipped = decomp.decompress(data)
|
||||
sections = unzipped.decode('ascii').split('\r\n')
|
||||
|
||||
sections = unzipped.decode("ascii").split("\r\n")
|
||||
|
||||
return self.kvp_to_dict(sections)
|
||||
|
||||
except Exception as e:
|
||||
@ -252,33 +297,38 @@ class AllnetServlet:
|
||||
def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]:
|
||||
"""
|
||||
Parses an allnet request string into a python dictionary
|
||||
"""
|
||||
"""
|
||||
try:
|
||||
zipped = base64.b64decode(data)
|
||||
unzipped = zlib.decompress(zipped)
|
||||
sections = unzipped.decode('utf-8').split('\r\n')
|
||||
|
||||
sections = unzipped.decode("utf-8").split("\r\n")
|
||||
|
||||
return self.kvp_to_dict(sections)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
|
||||
return None
|
||||
|
||||
def dict_to_http_form_string(self, data:List[Dict[str, Any]], crlf: bool = False, trailing_newline: bool = True) -> Optional[str]:
|
||||
def dict_to_http_form_string(
|
||||
self,
|
||||
data: List[Dict[str, Any]],
|
||||
crlf: bool = False,
|
||||
trailing_newline: bool = True,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Takes a python dictionary and parses it into an allnet response string
|
||||
"""
|
||||
try:
|
||||
urlencode = ""
|
||||
for item in data:
|
||||
for k,v in item.items():
|
||||
for k, v in item.items():
|
||||
urlencode += f"{k}={v}&"
|
||||
|
||||
if crlf:
|
||||
urlencode = urlencode[:-1] + "\r\n"
|
||||
else:
|
||||
urlencode = urlencode[:-1] + "\n"
|
||||
|
||||
|
||||
if not trailing_newline:
|
||||
if crlf:
|
||||
urlencode = urlencode[:-2]
|
||||
@ -286,23 +336,24 @@ class AllnetServlet:
|
||||
urlencode = urlencode[:-1]
|
||||
|
||||
return urlencode
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"dict_to_http_form_string: {e} while parsing {data}")
|
||||
return None
|
||||
|
||||
class AllnetPowerOnRequest():
|
||||
|
||||
class AllnetPowerOnRequest:
|
||||
def __init__(self, req: Dict) -> None:
|
||||
if req is None:
|
||||
raise AllnetRequestException("Request processing failed")
|
||||
self.game_id: str = req["game_id"] if "game_id" in req else ""
|
||||
self.ver: str = req["ver"] if "ver" in req else ""
|
||||
self.serial: str = req["serial"] if "serial" in req else ""
|
||||
self.ip: str = req["ip"] if "ip" in req else ""
|
||||
self.serial: str = req["serial"] if "serial" in req else ""
|
||||
self.ip: str = req["ip"] if "ip" in req else ""
|
||||
self.firm_ver: str = req["firm_ver"] if "firm_ver" in req else ""
|
||||
self.boot_ver: str = req["boot_ver"] if "boot_ver" in req else ""
|
||||
self.encode: str = req["encode"] if "encode" in req else ""
|
||||
|
||||
|
||||
try:
|
||||
self.hops = int(req["hops"]) if "hops" in req else 0
|
||||
self.format_ver = int(req["format_ver"]) if "format_ver" in req else 2
|
||||
@ -310,7 +361,8 @@ class AllnetPowerOnRequest():
|
||||
except ValueError as e:
|
||||
raise AllnetRequestException(f"Failed to parse int: {e}")
|
||||
|
||||
class AllnetPowerOnResponse3():
|
||||
|
||||
class AllnetPowerOnResponse3:
|
||||
def __init__(self, token) -> None:
|
||||
self.stat = 1
|
||||
self.uri = ""
|
||||
@ -326,12 +378,15 @@ class AllnetPowerOnResponse3():
|
||||
self.country = "JPN"
|
||||
self.allnet_id = "123"
|
||||
self.client_timezone = "+0900"
|
||||
self.utc_time = datetime.now(tz=pytz.timezone('UTC')).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
self.utc_time = datetime.now(tz=pytz.timezone("UTC")).strftime(
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
self.setting = ""
|
||||
self.res_ver = "3"
|
||||
self.token = str(token)
|
||||
|
||||
class AllnetPowerOnResponse2():
|
||||
|
||||
class AllnetPowerOnResponse2:
|
||||
def __init__(self) -> None:
|
||||
self.stat = 1
|
||||
self.uri = ""
|
||||
@ -355,23 +410,31 @@ class AllnetPowerOnResponse2():
|
||||
self.timezone = "+0900"
|
||||
self.res_class = "PowerOnResponseV2"
|
||||
|
||||
class AllnetDownloadOrderRequest():
|
||||
|
||||
class AllnetDownloadOrderRequest:
|
||||
def __init__(self, req: Dict) -> None:
|
||||
self.game_id = req["game_id"] if "game_id" in req else ""
|
||||
self.ver = req["ver"] if "ver" in req else ""
|
||||
self.serial = req["serial"] if "serial" in req else ""
|
||||
self.encode = req["encode"] if "encode" in req else ""
|
||||
|
||||
class AllnetDownloadOrderResponse():
|
||||
|
||||
class AllnetDownloadOrderResponse:
|
||||
def __init__(self, stat: int = 1, serial: str = "", uri: str = "null") -> None:
|
||||
self.stat = stat
|
||||
self.serial = serial
|
||||
self.uri = uri
|
||||
|
||||
class BillingResponse():
|
||||
def __init__(self, playlimit: str = "", playlimit_sig: str = "", nearfull: str = "", nearfull_sig: str = "",
|
||||
playhistory: str = "000000/0:000000/0:000000/0") -> None:
|
||||
|
||||
class BillingResponse:
|
||||
def __init__(
|
||||
self,
|
||||
playlimit: str = "",
|
||||
playlimit_sig: str = "",
|
||||
nearfull: str = "",
|
||||
nearfull_sig: str = "",
|
||||
playhistory: str = "000000/0:000000/0:000000/0",
|
||||
) -> None:
|
||||
self.result = "0"
|
||||
self.waitime = "100"
|
||||
self.linelimit = "1"
|
||||
@ -383,10 +446,11 @@ class BillingResponse():
|
||||
self.nearfullsig = nearfull_sig
|
||||
self.fixlogincnt = "0"
|
||||
self.fixinterval = "5"
|
||||
self.playhistory = playhistory
|
||||
self.playhistory = playhistory
|
||||
# playhistory -> YYYYMM/C:...
|
||||
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
|
||||
|
||||
|
||||
class AllnetRequestException(Exception):
|
||||
def __init__(self, message="") -> None:
|
||||
self.message = message
|
||||
|
Reference in New Issue
Block a user