mirror of
https://github.com/drmext/MonkeyBusiness.git
synced 2026-03-21 18:04:48 -05:00
Fix
This commit is contained in:
parent
7e2ae7fbfb
commit
360a08a0ef
|
|
@ -26,4 +26,6 @@ Run [start.bat (Windows)](start.bat) or [start.sh (Linux, MacOS)](start.sh)
|
||||||
|
|
||||||
- **URL Slash 1 (On)** [may still be required in rare cases](modules/__init__.py#L46)
|
- **URL Slash 1 (On)** [may still be required in rare cases](modules/__init__.py#L46)
|
||||||
|
|
||||||
|
- **URL Slash 0 (Off)** may be required in other cases
|
||||||
|
|
||||||
- When initially creating a DDR profile, complete an entire credit without pfree hacks
|
- When initially creating a DDR profile, complete an entire credit without pfree hacks
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ def get_ip():
|
||||||
|
|
||||||
ip = get_ip()
|
ip = get_ip()
|
||||||
port = 8000
|
port = 8000
|
||||||
services_prefix = "/core"
|
response_compression = False
|
||||||
verbose_log = True
|
verbose_log = True
|
||||||
|
|
||||||
arcade = "M0NKYBUS1N3Z"
|
arcade = "M0NKYBUS1N3Z"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import config
|
import config
|
||||||
|
|
||||||
import random
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from lxml.builder import ElementMaker
|
from lxml.builder import ElementMaker
|
||||||
|
|
@ -8,7 +7,7 @@ from lxml.builder import ElementMaker
|
||||||
from kbinxml import KBinXML
|
from kbinxml import KBinXML
|
||||||
|
|
||||||
from utils.arc4 import EamuseARC4
|
from utils.arc4 import EamuseARC4
|
||||||
from utils.lz77 import EamuseLZ77
|
from utils.lz77 import lz77_decode, lz77_encode
|
||||||
|
|
||||||
|
|
||||||
def _add_val_as_str(elm, val):
|
def _add_val_as_str(elm, val):
|
||||||
|
|
@ -36,6 +35,17 @@ def _add_list_as_str(elm, vals):
|
||||||
return new_val
|
return new_val
|
||||||
|
|
||||||
|
|
||||||
|
def _prng():
|
||||||
|
state = 0x41C64E6D
|
||||||
|
while True:
|
||||||
|
x = (state * 0x838C9CDA) + 0x6072
|
||||||
|
# state = (state * 0x41C64E6D + 0x3039)
|
||||||
|
# state = (state * 0x41C64E6D + 0x3039)
|
||||||
|
state = (state * 0xC2A29A69 + 0xD3DC167E) & 0xFFFFFFFF
|
||||||
|
yield (x & 0x7FFF0000) | state >> 0xF & 0xFFFF
|
||||||
|
prng_init = _prng()
|
||||||
|
|
||||||
|
|
||||||
E = ElementMaker(
|
E = ElementMaker(
|
||||||
typemap={
|
typemap={
|
||||||
int: _add_val_as_str,
|
int: _add_val_as_str,
|
||||||
|
|
@ -134,22 +144,19 @@ async def core_process_request(request):
|
||||||
if not cl or not data:
|
if not cl or not data:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
if "X-Compress" in request.headers:
|
request.compress = request.headers.get("X-Compress", "none") # intentionally lowercase 'none' (NOT None)
|
||||||
request.compress = request.headers.get("X-Compress")
|
|
||||||
else:
|
|
||||||
request.compress = None
|
|
||||||
|
|
||||||
if "X-Eamuse-Info" in request.headers:
|
if "X-Eamuse-Info" in request.headers:
|
||||||
xeamuseinfo = request.headers.get("X-Eamuse-Info")
|
xeamuseinfo = request.headers.get("X-Eamuse-Info")
|
||||||
key = bytes.fromhex(xeamuseinfo[2:].replace("-", ""))
|
version, unix_time, prng = xeamuseinfo.split("-")
|
||||||
xml_dec = EamuseARC4(key).decrypt(data[: int(cl)])
|
xml_dec = EamuseARC4(bytes.fromhex(unix_time), bytes.fromhex(prng)).decrypt(data[: int(cl)])
|
||||||
request.is_encrypted = True
|
request.is_encrypted = True
|
||||||
else:
|
else:
|
||||||
xml_dec = data[: int(cl)]
|
xml_dec = data[: int(cl)]
|
||||||
request.is_encrypted = False
|
request.is_encrypted = False
|
||||||
|
|
||||||
if request.compress == "lz77":
|
if request.compress == "lz77":
|
||||||
xml_dec = EamuseLZ77.decode(xml_dec)
|
xml_dec = lz77_decode(xml_dec)
|
||||||
|
|
||||||
xml = KBinXML(xml_dec, convert_illegal_things=True)
|
xml = KBinXML(xml_dec, convert_illegal_things=True)
|
||||||
root = xml.xml_doc
|
root = xml.xml_doc
|
||||||
|
|
@ -196,17 +203,24 @@ async def core_prepare_response(request, xml):
|
||||||
|
|
||||||
response_headers = {"User-Agent": "EAMUSE.Httpac/1.0"}
|
response_headers = {"User-Agent": "EAMUSE.Httpac/1.0"}
|
||||||
|
|
||||||
if request.is_encrypted:
|
if config.response_compression:
|
||||||
xeamuseinfo = "1-%08x-%04x" % (int(time.time()), random.randint(0x0000, 0xFFFF))
|
response_headers["X-Compress"] = request.compress
|
||||||
response_headers["X-Eamuse-Info"] = xeamuseinfo
|
if request.compress == "lz77":
|
||||||
key = bytes.fromhex(xeamuseinfo[2:].replace("-", ""))
|
response = lz77_encode(xml_binary) # very slow
|
||||||
response = EamuseARC4(key).encrypt(xml_binary)
|
else:
|
||||||
|
response = xml_binary
|
||||||
else:
|
else:
|
||||||
response = bytes(xml_binary)
|
response_headers["X-Compress"] = "none" # intentionally lowercase 'none' (NOT None)
|
||||||
|
response = xml_binary
|
||||||
|
|
||||||
request.compress = None
|
|
||||||
# if request.compress == "lz77":
|
if request.is_encrypted:
|
||||||
# response_headers["X-Compress"] = request.compress
|
version = 1
|
||||||
# response = EamuseLZ77.encode(response)
|
unix_time = int(time.time())
|
||||||
|
prng = next(prng_init) & 0xFFFF
|
||||||
|
response_headers["X-Eamuse-Info"] = f"{version}-{unix_time:04x}-{prng:02x}"
|
||||||
|
response = EamuseARC4(unix_time.to_bytes(4), prng.to_bytes(2)).encrypt(response)
|
||||||
|
else:
|
||||||
|
response = bytes(response)
|
||||||
|
|
||||||
return response, response_headers
|
return response, response_headers
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ from pydantic import BaseModel
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import utils.card as conv
|
import utils.card as conv
|
||||||
from utils.lz77 import EamuseLZ77
|
from utils.lz77 import lz77_decode
|
||||||
|
|
||||||
import lxml.etree as ET
|
import lxml.etree as ET
|
||||||
import ujson as json
|
import json
|
||||||
import struct
|
import struct
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
from os import path
|
from os import path
|
||||||
|
|
@ -202,7 +202,7 @@ class ARC:
|
||||||
return self.__data[fileoffset : (fileoffset + compressedsize)]
|
return self.__data[fileoffset : (fileoffset + compressedsize)]
|
||||||
else:
|
else:
|
||||||
# Compressed
|
# Compressed
|
||||||
return EamuseLZ77.decode(
|
return lz77_decode(
|
||||||
self.__data[fileoffset : (fileoffset + compressedsize)]
|
self.__data[fileoffset : (fileoffset + compressedsize)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -259,6 +259,6 @@ async def ddr_receive_mdb(file: UploadFile = File(...)) -> bytes:
|
||||||
mdb[mcode] = mdb_old[mcode]
|
mdb[mcode] = mdb_old[mcode]
|
||||||
|
|
||||||
with open(ddr_metadata, "w", encoding="utf-8") as fp:
|
with open(ddr_metadata, "w", encoding="utf-8") as fp:
|
||||||
json.dump(mdb, fp, indent=4, ensure_ascii=False, escape_forward_slashes=False)
|
json.dump(mdb, fp, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
return Response(status_code=201)
|
return Response(status_code=201)
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,9 @@ from typing import Optional
|
||||||
import config
|
import config
|
||||||
import utils.card as conv
|
import utils.card as conv
|
||||||
import utils.musicdata_tool as mdt
|
import utils.musicdata_tool as mdt
|
||||||
from utils.lz77 import EamuseLZ77
|
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import ujson as json
|
import json
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -309,7 +308,6 @@ async def iidx_receive_mdb(file: UploadFile = File(...)) -> bytes:
|
||||||
open(iidx_metadata, "w", encoding="utf8"),
|
open(iidx_metadata, "w", encoding="utf8"),
|
||||||
indent=4,
|
indent=4,
|
||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
escape_forward_slashes=False,
|
|
||||||
)
|
)
|
||||||
return Response(status_code=201)
|
return Response(status_code=201)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
12
pyeamu.py
12
pyeamu.py
|
|
@ -2,7 +2,7 @@ from urllib.parse import urlparse, urlunparse, urlencode
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
import ujson as json
|
import json
|
||||||
from os import name, path
|
from os import name, path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
@ -33,14 +33,14 @@ for host in ("localhost", config.ip, socket.gethostname()):
|
||||||
server_services_urls = []
|
server_services_urls = []
|
||||||
for server_address in server_addresses:
|
for server_address in server_addresses:
|
||||||
server_services_urls.append(
|
server_services_urls.append(
|
||||||
urlunparse(("http", server_address, config.services_prefix, None, None, None))
|
urlunparse(("http", server_address, "/core", None, None, None))
|
||||||
)
|
)
|
||||||
|
|
||||||
settings = {}
|
settings = {}
|
||||||
for s in (
|
for s in (
|
||||||
"ip",
|
"ip",
|
||||||
"port",
|
"port",
|
||||||
"services_prefix",
|
"response_compression",
|
||||||
"verbose_log",
|
"verbose_log",
|
||||||
"arcade",
|
"arcade",
|
||||||
"paseli",
|
"paseli",
|
||||||
|
|
@ -64,7 +64,7 @@ app.add_middleware(
|
||||||
if path.exists("webui"):
|
if path.exists("webui"):
|
||||||
webui = True
|
webui = True
|
||||||
with open(path.join("webui", "monkey.json"), "w") as f:
|
with open(path.join("webui", "monkey.json"), "w") as f:
|
||||||
json.dump(settings, f, indent=2, escape_forward_slashes=False)
|
json.dump(settings, f, indent=2)
|
||||||
app.mount("/webui", StaticFiles(directory="webui", html=True), name="webui")
|
app.mount("/webui", StaticFiles(directory="webui", html=True), name="webui")
|
||||||
else:
|
else:
|
||||||
webui = False
|
webui = False
|
||||||
|
|
@ -112,8 +112,8 @@ if __name__ == "__main__":
|
||||||
uvicorn.run("pyeamu:app", host="0.0.0.0", port=config.port, reload=True)
|
uvicorn.run("pyeamu:app", host="0.0.0.0", port=config.port, reload=True)
|
||||||
|
|
||||||
|
|
||||||
@app.post(urlpathjoin([config.services_prefix]))
|
@app.post("/core")
|
||||||
@app.post(urlpathjoin([config.services_prefix, "/{gameinfo}/services/get"]))
|
@app.post("/core/{gameinfo}/services/get")
|
||||||
async def services_get(
|
async def services_get(
|
||||||
request: Request,
|
request: Request,
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,4 @@ kbinxml>=2.0
|
||||||
pycryptodomex
|
pycryptodomex
|
||||||
python-multipart
|
python-multipart
|
||||||
tinydb
|
tinydb
|
||||||
ujson
|
|
||||||
uvicorn[standard]
|
uvicorn[standard]
|
||||||
|
|
|
||||||
2
start.sh
2
start.sh
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
ver="3.12"
|
ver="3.14"
|
||||||
py="python$ver"
|
py="python$ver"
|
||||||
|
|
||||||
if ! command -v $py &> /dev/null
|
if ! command -v $py &> /dev/null
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ from Cryptodome.Hash import MD5
|
||||||
|
|
||||||
|
|
||||||
class EamuseARC4:
|
class EamuseARC4:
|
||||||
def __init__(self, eamuseKey):
|
def __init__(self, seconds, prng):
|
||||||
self.internal_key = bytearray.fromhex(
|
self.internal_key = b"\x69\xD7\x46\x27\xD9\x85\xEE\x21\x87\x16\x15\x70\xD0\x8D\x93\xB1\x24\x55\x03\x5B\x6D\xF0\xD8\x20\x5D\xF5"
|
||||||
"69D74627D985EE2187161570D08D93B12455035B6DF0D8205DF5"
|
self.key = MD5.new(seconds + prng + self.internal_key).digest()
|
||||||
)
|
|
||||||
self.key = MD5.new(eamuseKey + self.internal_key).digest()
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
def decrypt(self, data):
|
||||||
return ARC4.new(self.key).decrypt(bytes(data))
|
return ARC4.new(self.key).decrypt(bytes(data))
|
||||||
|
|
|
||||||
156
utils/lz77.py
156
utils/lz77.py
|
|
@ -1,33 +1,125 @@
|
||||||
class EamuseLZ77:
|
WINDOW_SIZE = 0x1000
|
||||||
@staticmethod
|
WINDOW_MASK = WINDOW_SIZE - 1
|
||||||
def decode(data):
|
THRESHOLD = 3
|
||||||
data_length = len(data)
|
INPLACE_THRESHOLD = 0xA
|
||||||
offset = 0
|
LOOK_RANGE = 0x200
|
||||||
output = []
|
MAX_LEN = 0xF + THRESHOLD
|
||||||
while offset < data_length:
|
MAX_BUFFER = 0x10 + 1
|
||||||
flag = data[offset]
|
|
||||||
offset += 1
|
|
||||||
for bit in range(8):
|
|
||||||
if flag & (1 << bit):
|
|
||||||
output.append(data[offset])
|
|
||||||
offset += 1
|
|
||||||
else:
|
|
||||||
if offset >= data_length:
|
|
||||||
break
|
|
||||||
lookback_flag = int.from_bytes(data[offset : offset + 2], "big")
|
|
||||||
lookback_length = (lookback_flag & 0x000F) + 3
|
|
||||||
lookback_offset = lookback_flag >> 4
|
|
||||||
offset += 2
|
|
||||||
if lookback_flag == 0:
|
|
||||||
break
|
|
||||||
for _ in range(lookback_length):
|
|
||||||
loffset = len(output) - lookback_offset
|
|
||||||
if loffset <= 0 or loffset >= len(output):
|
|
||||||
output.append(0)
|
|
||||||
else:
|
|
||||||
output.append(output[loffset])
|
|
||||||
return bytes(output)
|
|
||||||
|
|
||||||
# @staticmethod
|
|
||||||
# def encode(data):
|
def match_current(window: bytes, pos: int, max_len: int, data: bytes, dpos: int) -> int:
|
||||||
# return bytes(output)
|
length = 0
|
||||||
|
while (
|
||||||
|
dpos + length < len(data)
|
||||||
|
and length < max_len
|
||||||
|
and window[(pos + length) & WINDOW_MASK] == data[dpos + length]
|
||||||
|
and length < MAX_LEN
|
||||||
|
):
|
||||||
|
length += 1
|
||||||
|
return length
|
||||||
|
|
||||||
|
|
||||||
|
def match_window(window: bytes, pos: int, data: bytes, d_pos: int) -> None | tuple[int, int]:
|
||||||
|
max_pos = 0
|
||||||
|
max_len = 0
|
||||||
|
for i in range(THRESHOLD, LOOK_RANGE):
|
||||||
|
length = match_current(window, (pos - i) & WINDOW_MASK, i, data, d_pos)
|
||||||
|
if length >= INPLACE_THRESHOLD:
|
||||||
|
return (i, length)
|
||||||
|
if length >= THRESHOLD:
|
||||||
|
max_pos = i
|
||||||
|
max_len = length
|
||||||
|
if max_len >= THRESHOLD:
|
||||||
|
return (max_pos, max_len)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def lz77_encode(data: bytes) -> bytes:
|
||||||
|
output = bytearray()
|
||||||
|
window = bytearray(WINDOW_SIZE)
|
||||||
|
current_pos = 0
|
||||||
|
current_window = 0
|
||||||
|
current_buffer = 0
|
||||||
|
flag_byte = 0
|
||||||
|
bit = 0
|
||||||
|
buffer = [0] * MAX_BUFFER
|
||||||
|
pad = 3
|
||||||
|
while current_pos < len(data):
|
||||||
|
flag_byte = 0
|
||||||
|
current_buffer = 0
|
||||||
|
for bit_pos in range(8):
|
||||||
|
if current_pos >= len(data):
|
||||||
|
pad = 0
|
||||||
|
flag_byte = flag_byte >> (8 - bit_pos)
|
||||||
|
buffer[current_buffer] = 0
|
||||||
|
buffer[current_buffer + 1] = 0
|
||||||
|
current_buffer += 2
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
found = match_window(window, current_window, data, current_pos)
|
||||||
|
if found is not None and found[1] >= THRESHOLD:
|
||||||
|
pos, length = found
|
||||||
|
|
||||||
|
byte1 = pos >> 4
|
||||||
|
byte2 = (((pos & 0x0F) << 4) | ((length - THRESHOLD) & 0x0F))
|
||||||
|
buffer[current_buffer] = byte1
|
||||||
|
buffer[current_buffer + 1] = byte2
|
||||||
|
current_buffer += 2
|
||||||
|
bit = 0
|
||||||
|
for _ in range(length):
|
||||||
|
window[current_window & WINDOW_MASK] = data[current_pos]
|
||||||
|
current_pos += 1
|
||||||
|
current_window += 1
|
||||||
|
else:
|
||||||
|
buffer[current_buffer] = data[current_pos]
|
||||||
|
window[current_window] = data[current_pos]
|
||||||
|
current_pos += 1
|
||||||
|
current_window += 1
|
||||||
|
current_buffer += 1
|
||||||
|
bit = 1
|
||||||
|
|
||||||
|
flag_byte = (flag_byte >> 1) | ((bit & 1) << 7)
|
||||||
|
current_window = current_window & WINDOW_MASK
|
||||||
|
|
||||||
|
output.append(flag_byte)
|
||||||
|
for i in range(current_buffer):
|
||||||
|
output.append(buffer[i])
|
||||||
|
for _ in range(pad):
|
||||||
|
output.append(0)
|
||||||
|
|
||||||
|
return bytes(output)
|
||||||
|
|
||||||
|
|
||||||
|
def lz77_decode(data: bytes) -> bytes:
|
||||||
|
output = bytearray()
|
||||||
|
cur_byte = 0
|
||||||
|
window = bytearray(WINDOW_SIZE)
|
||||||
|
window_cursor = 0
|
||||||
|
|
||||||
|
while cur_byte < len(data):
|
||||||
|
flag = data[cur_byte]
|
||||||
|
cur_byte += 1
|
||||||
|
|
||||||
|
for i in range(8):
|
||||||
|
if (flag >> i) & 1 == 1:
|
||||||
|
output.append(data[cur_byte])
|
||||||
|
window[window_cursor] = data[cur_byte]
|
||||||
|
window_cursor = (window_cursor + 1) & WINDOW_MASK
|
||||||
|
cur_byte += 1
|
||||||
|
else:
|
||||||
|
w = ((data[cur_byte]) << 8) | (data[cur_byte + 1])
|
||||||
|
if w == 0:
|
||||||
|
return bytes(output)
|
||||||
|
|
||||||
|
cur_byte += 2
|
||||||
|
position = ((window_cursor - (w >> 4)) & WINDOW_MASK)
|
||||||
|
length = (w & 0x0F) + THRESHOLD
|
||||||
|
|
||||||
|
for _ in range(length):
|
||||||
|
b = window[position & WINDOW_MASK]
|
||||||
|
output.append(b)
|
||||||
|
window[window_cursor] = b
|
||||||
|
window_cursor = (window_cursor + 1) & WINDOW_MASK
|
||||||
|
position += 1
|
||||||
|
|
||||||
|
return bytes(output)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user