mirror of
https://github.com/573dev/gfdm-server.git
synced 2026-03-21 17:54:19 -05:00
Lots of hacking. Got passed the network boot
This commit is contained in:
parent
e95d5d2816
commit
7b23ef0e58
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -101,3 +101,6 @@ ENV/
|
|||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# request data
|
||||
requests/
|
||||
|
|
|
|||
10
setup.py
10
setup.py
|
|
@ -7,7 +7,15 @@ from setuptools import find_packages, setup
|
|||
TEST_DEPS = ["coverage[toml]", "pytest", "pytest-cov"]
|
||||
DOCS_DEPS = ["sphinx", "sphinx-rtd-theme", "sphinx-autoapi", "recommonmark"]
|
||||
CHECK_DEPS = ["isort", "flake8", "flake8-quotes", "pep8-naming", "mypy", "black"]
|
||||
REQUIREMENTS = ["flask", "watchdog", "pyopenssl"]
|
||||
REQUIREMENTS = [
|
||||
"flask",
|
||||
"watchdog",
|
||||
"pyopenssl",
|
||||
"lxml",
|
||||
"pycryptodome",
|
||||
"lzss",
|
||||
"kbinxml",
|
||||
]
|
||||
|
||||
EXTRAS = {
|
||||
"test": TEST_DEPS,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from typing import Optional, Union
|
|||
from flask import Flask
|
||||
|
||||
from v8_server.config import Development, Production
|
||||
from v8_server.utils import generate_secret_key
|
||||
from v8_server.utils.flask import generate_secret_key
|
||||
|
||||
from .version import __version__
|
||||
|
||||
|
|
|
|||
0
v8_server/utils/__init__.py
Normal file
0
v8_server/utils/__init__.py
Normal file
23
v8_server/utils/arc4.py
Normal file
23
v8_server/utils/arc4.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from Crypto.Cipher import ARC4
|
||||
from Crypto.Hash import MD5
|
||||
|
||||
|
||||
class EamuseARC4(object):
|
||||
def __init__(self, eamuse_key):
|
||||
# fmt: off
|
||||
self.internal_key = bytearray([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0xd7,
|
||||
0x46, 0x27, 0xd9, 0x85, 0xee, 0x21, 0x87, 0x16,
|
||||
0x15, 0x70, 0xd0, 0x8d, 0x93, 0xb1, 0x24, 0x55,
|
||||
0x03, 0x5b, 0x6d, 0xf0, 0xd8, 0x20, 0x5d, 0xf5,
|
||||
])
|
||||
# fmt: on
|
||||
|
||||
self.key = MD5.new(eamuse_key + self.internal_key[6:]).digest()
|
||||
self.arc = ARC4.new(self.key)
|
||||
|
||||
def decrypt(self, data):
|
||||
return self.arc.decrypt(bytes(data))
|
||||
|
||||
def encrypt(self, data):
|
||||
return self.arc.encrypt(bytes(data))
|
||||
5
v8_server/utils/eamuse.py
Normal file
5
v8_server/utils/eamuse.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from time import time
|
||||
|
||||
|
||||
def get_timestamp() -> str:
|
||||
return str(int(round(time() * 1000)))
|
||||
116
v8_server/utils/xml.py
Normal file
116
v8_server/utils/xml.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
from binascii import unhexlify
|
||||
from pathlib import Path
|
||||
from random import randint
|
||||
from time import time
|
||||
|
||||
import lxml
|
||||
import lzss
|
||||
from kbinxml import KBinXML
|
||||
from lxml import etree as ET
|
||||
|
||||
from v8_server.utils.arc4 import EamuseARC4
|
||||
from v8_server.utils.eamuse import get_timestamp
|
||||
|
||||
|
||||
EAMUSE_CONFIG = {"encrypted": False, "compressed": False}
|
||||
REQUESTS_PATH = Path("./requests")
|
||||
|
||||
|
||||
def eamuse_read_xml(request):
|
||||
# Get encrypted/compressed data from client
|
||||
headers = request.headers
|
||||
data = request.data
|
||||
|
||||
if "x-eamuse-info" in headers:
|
||||
# Decrypt/Compress data
|
||||
x_eamuse_info = headers["x-eamuse-info"]
|
||||
key = unhexlify(x_eamuse_info[2:].replace("-", ""))
|
||||
arc4 = EamuseARC4(key)
|
||||
xml_dec = arc4.decrypt(data)
|
||||
EAMUSE_CONFIG["encrypted"] = True
|
||||
else:
|
||||
xml_dec = data
|
||||
EAMUSE_CONFIG["encrypted"] = False
|
||||
|
||||
compress = headers["x-compress"] if "x-compress" in headers else None
|
||||
|
||||
if compress == "lz77":
|
||||
xml_dec = lzss.decompress(xml_dec)
|
||||
EAMUSE_CONFIG["compress"] = True
|
||||
else:
|
||||
EAMUSE_CONFIG["compress"] = False
|
||||
|
||||
xml_text = KBinXML(xml_dec).to_text().encode("UTF-8")
|
||||
|
||||
root = ET.fromstring(xml_text)
|
||||
|
||||
REQUESTS_PATH.mkdir(exist_ok=True)
|
||||
output_filename = REQUESTS_PATH / f"eamuse_{get_timestamp()}_req.xml"
|
||||
with output_filename.open("w") as f:
|
||||
f.write(xml_text.decode("UTF-8"))
|
||||
|
||||
model = root.attrib["model"]
|
||||
module = root[0].tag
|
||||
method = root[0].attrib["method"] if "method" in root[0].attrib else None
|
||||
command = root[0].attrib["command"] if "command" in root[0].attrib else None
|
||||
|
||||
print(
|
||||
"---- Read Request ----\n"
|
||||
f"Headers: [{dict(headers)}]\n"
|
||||
f" Model: [{model}]\n"
|
||||
f" Module: [{module}]\n"
|
||||
f" Method: [{method}]\n"
|
||||
f"Command: [{command}]\n"
|
||||
" Data:\n"
|
||||
f"{xml_text.decode('UTF-8')[:-1]}\n"
|
||||
)
|
||||
|
||||
# Return raw XML
|
||||
return xml_text, model, module, method, command
|
||||
|
||||
|
||||
def eamuse_prepare_xml(xml):
|
||||
x_eamuse_info = f"1-{int(time()):08x}-{randint(0x0000, 0xffff):04x}"
|
||||
key = unhexlify(x_eamuse_info[2:].replace("-", ""))
|
||||
|
||||
xml_root = xml
|
||||
if type(xml) == lxml.etree._Element:
|
||||
xml = ET.tostring(xml, encoding="UTF-8").decode("UTF-8")
|
||||
|
||||
REQUESTS_PATH.mkdir(exist_ok=True)
|
||||
timestamp = get_timestamp()
|
||||
output_filename = REQUESTS_PATH / f"eamuse_{timestamp}_resp.xml"
|
||||
with output_filename.open("wb") as f:
|
||||
f.write(xml.encode("UTF-8"))
|
||||
|
||||
headers = {
|
||||
"Content-Type": "applicaton/octet_stream",
|
||||
"Server": "Microsoft-HTTPAPI/2.0",
|
||||
}
|
||||
|
||||
# Convert XML to binary
|
||||
xml_bin = KBinXML(xml.encode("UTF-8")).to_binary()
|
||||
output_filename = REQUESTS_PATH / f"eamuse_{timestamp}_resp.bin"
|
||||
with output_filename.open("wb") as f:
|
||||
f.write(xml_bin)
|
||||
|
||||
if EAMUSE_CONFIG["compress"]:
|
||||
headers["X-Compress"] = "lz77"
|
||||
# Actually do some compression here if we have to
|
||||
|
||||
headers["X-Eamuse-Info"] = x_eamuse_info
|
||||
|
||||
if EAMUSE_CONFIG["encrypted"]:
|
||||
arc4 = EamuseARC4(key)
|
||||
data = arc4.encrypt(xml_bin)
|
||||
else:
|
||||
data = xml_bin
|
||||
|
||||
print(
|
||||
"---- Write Response ----\n"
|
||||
" Data: [\n"
|
||||
f"{ET.tostring(xml_root, pretty_print=True).decode('UTF-8')[:-1]}\n"
|
||||
f"Headers: [{headers}]\n"
|
||||
)
|
||||
|
||||
return data, headers
|
||||
|
|
@ -1,6 +1,204 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from flask import request
|
||||
from lxml import etree as ET
|
||||
from lxml.builder import E
|
||||
|
||||
from v8_server import app
|
||||
from v8_server.utils.xml import eamuse_prepare_xml, eamuse_read_xml
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def hello_world():
|
||||
return "Hello, World!"
|
||||
@app.route("/", defaults={"path": ""}, methods=["GET", "POST"])
|
||||
@app.route("/<path:path>", methods=["GET", "POST"])
|
||||
def catch_all(path):
|
||||
d = f"""
|
||||
{request.args}
|
||||
{request.form}
|
||||
{request.files}
|
||||
{request.values}
|
||||
{request.json}
|
||||
{request.data}
|
||||
{request.headers}
|
||||
"""
|
||||
print(d)
|
||||
return "You want path: %s" % path
|
||||
|
||||
|
||||
@app.route("/pcbtracker/service", methods=["POST"])
|
||||
def pcbtracker():
|
||||
"""
|
||||
Handle a PCBTracker.alive request. The only method of note is the "alive" method
|
||||
which returns whether PASELI should be active or not for this session.
|
||||
|
||||
For V8 it should not be active.
|
||||
"""
|
||||
request_xml, model, module, method, command = eamuse_read_xml(request)
|
||||
|
||||
if method == "alive":
|
||||
response = E.response(E.pcbtracker(OrderedDict(ecenable="0", expire="600")))
|
||||
response_body, headers = eamuse_prepare_xml(response)
|
||||
else:
|
||||
# There shoulnd't really even be any other methods
|
||||
raise Exception("Not sure how to handle this PCBTracker Request")
|
||||
|
||||
return response_body, headers
|
||||
|
||||
|
||||
@app.route("/message/service", methods=["POST"])
|
||||
def message():
|
||||
"""
|
||||
Unknown what this does. Possibly for operator messages?
|
||||
"""
|
||||
request_xml, model, module, method, command = eamuse_read_xml(request)
|
||||
|
||||
response = E.response(E.message(OrderedDict(expire="600")))
|
||||
response_body, headers = eamuse_prepare_xml(response)
|
||||
|
||||
return response_body, headers
|
||||
|
||||
|
||||
@app.route("/pcbevent/service", methods=["POST"])
|
||||
def pcbevent():
|
||||
"""
|
||||
Handle a PCBEvent request. We do nothing for this aside from logging the event.
|
||||
"""
|
||||
request_xml, model, module, method, command = eamuse_read_xml(request)
|
||||
|
||||
response = E.response(E.pcbevent(OrderedDict(expire="600")))
|
||||
response_body, headers = eamuse_prepare_xml(response)
|
||||
|
||||
return response_body, headers
|
||||
|
||||
|
||||
@app.route("/facility/service", methods=["POST"])
|
||||
def facility():
|
||||
"""
|
||||
Handle a facility request. The only method of note is the "get" request,
|
||||
which expects to return a bunch of information about the arcade this cabinet is in,
|
||||
as well as some settings for URLs and the name of the cab.
|
||||
"""
|
||||
request_xml, model, module, method, command = eamuse_read_xml(request)
|
||||
|
||||
response = E.response(
|
||||
E.facility(
|
||||
E.location(
|
||||
E.id("US-123"),
|
||||
E.country("US"),
|
||||
E.region("."),
|
||||
E.name("H"),
|
||||
E.type("0", {"__type": "u8"}),
|
||||
),
|
||||
E.line(E.id("."), E("class", "0", {"__type": "u8"}),),
|
||||
E.portfw(
|
||||
E.globalip("192.168.1.139", {"__type": "ip4", "__count": "1"}),
|
||||
E.globalport("80", {"__type": "u16"}),
|
||||
E.privateport("80", {"__type": "u16"}),
|
||||
),
|
||||
E.public(
|
||||
E.flag("1", {"__type": "u8"}),
|
||||
E.name("."),
|
||||
E.latitude("0"),
|
||||
E.longitude("0"),
|
||||
),
|
||||
E.share(
|
||||
E.eacoin(
|
||||
E.notchamount("3000", {"__type": "s32"}),
|
||||
E.notchcount("3", {"__type": "s32"}),
|
||||
E.supplylimit("10000", {"__type": "s32"}),
|
||||
),
|
||||
E.eapass(E.valid("365", {"__type": "u16"})),
|
||||
E.url(
|
||||
E.eapass("www.ea-pass.konami.net"),
|
||||
E.arcadefan("www.konami.jp/am"),
|
||||
E.konaminetdx("http://am.573.jp"),
|
||||
E.konamiid("http://id.konami.net"),
|
||||
E.eagate("http://eagate.573.jp"),
|
||||
),
|
||||
),
|
||||
{"expire": "600"},
|
||||
)
|
||||
)
|
||||
|
||||
response_body, headers = eamuse_prepare_xml(response)
|
||||
|
||||
return response_body, headers
|
||||
|
||||
|
||||
@app.route("/package/service", methods=["POST"])
|
||||
def package():
|
||||
"""
|
||||
This is for supporting downloading of updates. We don't support this.
|
||||
"""
|
||||
request_xml, model, module, method, command = eamuse_read_xml(request)
|
||||
|
||||
response = E.response(E.package())
|
||||
response_body, headers = eamuse_prepare_xml(response)
|
||||
|
||||
return response_body, headers
|
||||
|
||||
|
||||
@app.route("/service/services/services/", methods=["POST"])
|
||||
def services():
|
||||
# We don't need to actually read the data here, but let's do it anyway as it saves a
|
||||
# copy
|
||||
_ = eamuse_read_xml(request)
|
||||
|
||||
service_names = [
|
||||
"pcbtracker",
|
||||
"pcbevent",
|
||||
"facility",
|
||||
"message",
|
||||
"package",
|
||||
"userdata",
|
||||
"userid",
|
||||
"dlstatus",
|
||||
"traceroute",
|
||||
"eacoin",
|
||||
"netlog",
|
||||
"sidmgr",
|
||||
]
|
||||
|
||||
"""
|
||||
"binary",
|
||||
"cardmng",
|
||||
"dlstatus",
|
||||
"eacoin",
|
||||
"eemall",
|
||||
"facility",
|
||||
"info",
|
||||
"lobby",
|
||||
"local",
|
||||
"netlog",
|
||||
"numbering",
|
||||
"pcbevent",
|
||||
"pkglist",
|
||||
"posevent",
|
||||
"reference",
|
||||
"shopinf",
|
||||
"sidmgr",
|
||||
"userdata",
|
||||
"userid",
|
||||
"""
|
||||
|
||||
services = {
|
||||
"ntp": "ntp://pool.ntp.org",
|
||||
"keepalive": (
|
||||
"http://eamuse.konami.fun/"
|
||||
"keepalive?pa=127.0.0.1&ia=127.0.0.1&ga=127.0.0.1&ma=127.0.0.1&t1=2&t2=10"
|
||||
),
|
||||
**{k: f"http://eamuse.konami.fun/{k}/service" for k in service_names},
|
||||
}
|
||||
|
||||
response = E.response(
|
||||
E.services(
|
||||
expire="600",
|
||||
method="get",
|
||||
mode="operation",
|
||||
status="0",
|
||||
*[E.item(OrderedDict(name=k, url=services[k])) for k in services],
|
||||
)
|
||||
)
|
||||
|
||||
response_body, headers = eamuse_prepare_xml(response)
|
||||
|
||||
return response_body, 200, headers
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user