├── modules ├── ddr │ ├── __init__.py │ ├── tax.py │ ├── eventlog.py │ ├── system.py │ ├── eventlog_2.py │ ├── system_2.py │ └── api.py ├── drs │ ├── __init__.py │ └── eventlog.py ├── core │ ├── __init__.py │ ├── pcbevent.py │ ├── package2.py │ ├── apsmanager.py │ ├── pcbtracker.py │ ├── ins.py │ ├── message.py │ ├── dlstatus.py │ ├── package.py │ ├── facility.py │ ├── cardmng.py │ └── eacoin.py ├── gitadora │ ├── __init__.py │ ├── shopinfo.py │ ├── lobby.py │ ├── playablemusic.py │ ├── api.py │ ├── cardutil.py │ └── gameinfo.py ├── iidx │ ├── __init__.py │ ├── ranking.py │ ├── iidx29ranking.py │ ├── iidx30ranking.py │ ├── iidx31ranking.py │ ├── iidx32ranking.py │ ├── iidx33ranking.py │ ├── iidx32streaming.py │ ├── iidx33streaming.py │ ├── iidx31streaming.py │ ├── shop.py │ ├── iidx29shop.py │ ├── iidx30shop.py │ ├── iidx31shop.py │ ├── iidx32shop.py │ ├── iidx29lobby.py │ ├── iidx30gamesystem.py │ ├── iidx29gamesystem.py │ ├── iidx29grade.py │ ├── iidx32grade.py │ ├── iidx33grade.py │ ├── iidx30grade.py │ ├── iidx31grade.py │ ├── iidx33shop.py │ ├── iidx30lobby.py │ ├── iidx31lobby.py │ ├── iidx32lobby.py │ ├── iidx33lobby.py │ ├── iidx32gamesystem.py │ ├── iidx33gamesystem.py │ └── iidx31gamesystem.py ├── sdvx │ ├── __init__.py │ └── eventlog.py ├── nostalgia │ ├── __init__.py │ └── op3_common.py └── __init__.py ├── .gitignore ├── requirements.txt ├── core_database.py ├── start.sh ├── utils ├── arc4.py ├── db │ ├── trim_monkey_db.py │ ├── README.md │ ├── delete_monkey_user.py │ ├── import_ddr_spice_automap.py │ └── import_iidx_spice_automap.py ├── lz77.py └── card.py ├── config.py ├── README.md ├── start.bat ├── core_common.py └── pyeamu.py /modules/ddr/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/drs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/gitadora/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/iidx/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/sdvx/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/nostalgia/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.xml 3 | /db*.json 4 | /*.db 5 | .venv/ 6 | webui/ 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | kbinxml>=2.0 3 | pycryptodomex 4 | python-multipart 5 | tinydb 6 | ujson 7 | uvicorn[standard] 8 | -------------------------------------------------------------------------------- /core_database.py: -------------------------------------------------------------------------------- 1 | from tinydb import TinyDB 2 | 3 | db = TinyDB("db.json", indent=2, encoding="utf-8", ensure_ascii=False) 4 | 5 | 6 | def get_db(): 7 | return db 8 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ver="3.12" 4 | py="python$ver" 5 | 6 | if ! command -v $py &> /dev/null 7 | then 8 | echo "$py not found" 9 | exit 10 | fi 11 | 12 | if [ ! -d .venv/ ] 13 | then 14 | $py -m venv .venv 15 | fi 16 | 17 | source .venv/bin/activate 18 | $py -m pip install -r requirements.txt 19 | $py pyeamu.py 20 | -------------------------------------------------------------------------------- /utils/arc4.py: -------------------------------------------------------------------------------- 1 | from Cryptodome.Cipher import ARC4 2 | from Cryptodome.Hash import MD5 3 | 4 | 5 | class EamuseARC4: 6 | def __init__(self, eamuseKey): 7 | self.internal_key = bytearray.fromhex( 8 | "69D74627D985EE2187161570D08D93B12455035B6DF0D8205DF5" 9 | ) 10 | self.key = MD5.new(eamuseKey + self.internal_key).digest() 11 | 12 | def decrypt(self, data): 13 | return ARC4.new(self.key).decrypt(bytes(data)) 14 | 15 | def encrypt(self, data): 16 | return ARC4.new(self.key).encrypt(bytes(data)) 17 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | # https://stackoverflow.com/a/28950776 4 | def get_ip(): 5 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 6 | s.settimeout(0) 7 | try: 8 | s.connect(("10.254.254.254", 1)) 9 | IP = s.getsockname()[0] 10 | except Exception: 11 | IP = "127.0.0.1" 12 | finally: 13 | s.close() 14 | return IP 15 | 16 | 17 | ip = get_ip() 18 | port = 8000 19 | services_prefix = "/core" 20 | verbose_log = True 21 | 22 | arcade = "M0NKYBUS1N3Z" 23 | paseli = 10000 24 | maintenance_mode = False 25 | -------------------------------------------------------------------------------- /modules/core/pcbevent.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/core", tags=["pcbevent"]) 6 | 7 | 8 | @router.post("/{gameinfo}/pcbevent/put") 9 | async def pcbevent_put(request: Request): 10 | request_info = await core_process_request(request) 11 | 12 | response = E.response(E.pcbevent(expire=600)) 13 | 14 | response_body, response_headers = await core_prepare_response(request, response) 15 | return Response(content=response_body, headers=response_headers) 16 | -------------------------------------------------------------------------------- /modules/core/package2.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/core", tags=["package2"]) 6 | 7 | 8 | @router.post("/{gameinfo}/package2/list") 9 | async def package2_list(request: Request): 10 | request_info = await core_process_request(request) 11 | 12 | response = E.response(E.package2(expire=1200, status=0)) 13 | 14 | response_body, response_headers = await core_prepare_response(request, response) 15 | return Response(content=response_body, headers=response_headers) 16 | -------------------------------------------------------------------------------- /modules/core/apsmanager.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/core", tags=["apsmanager"]) 6 | 7 | 8 | @router.post("/{gameinfo}/apsmanager/getstat") 9 | async def apsmanager_getstat(request: Request): 10 | request_info = await core_process_request(request) 11 | 12 | response = E.response(E.apsmanager(expire=600)) 13 | 14 | response_body, response_headers = await core_prepare_response(request, response) 15 | return Response(content=response_body, headers=response_headers) 16 | -------------------------------------------------------------------------------- /modules/iidx/ranking.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["LDJ", "KDZ", "JDZ"] 9 | 10 | 11 | @router.post("/{gameinfo}/ranking/getranker") 12 | async def ranking_getranker(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.ranking()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | -------------------------------------------------------------------------------- /modules/iidx/iidx29ranking.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX29ranking/getranker") 12 | async def iidx29ranking_getranker(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX29ranking()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | -------------------------------------------------------------------------------- /modules/iidx/iidx30ranking.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX30ranking/getranker") 12 | async def iidx30ranking_getranker(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX30ranking()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | -------------------------------------------------------------------------------- /modules/iidx/iidx31ranking.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX31ranking/getranker") 12 | async def iidx31ranking_getranker(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX31ranking()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | -------------------------------------------------------------------------------- /modules/iidx/iidx32ranking.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX32ranking/getranker") 12 | async def iidx32ranking_getranker(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX32ranking()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | -------------------------------------------------------------------------------- /modules/iidx/iidx33ranking.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX33ranking/getranker") 12 | async def iidx33ranking_getranker(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX33ranking()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | -------------------------------------------------------------------------------- /modules/ddr/tax.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/local", tags=["local"]) 6 | router.model_whitelist = ["MDX"] 7 | 8 | 9 | @router.post("/{gameinfo}/tax/get_phase") 10 | async def tax_get_phase(request: Request): 11 | request_info = await core_process_request(request) 12 | 13 | response = E.response( 14 | E.tax( 15 | E.phase(0, __type="s32"), 16 | ) 17 | ) 18 | 19 | response_body, response_headers = await core_prepare_response(request, response) 20 | return Response(content=response_body, headers=response_headers) 21 | -------------------------------------------------------------------------------- /modules/core/pcbtracker.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from time import time 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/core", tags=["pcbtracker"]) 10 | 11 | 12 | @router.post("/{gameinfo}/pcbtracker/alive") 13 | async def pcbtracker_alive(request: Request): 14 | request_info = await core_process_request(request) 15 | 16 | response = E.response( 17 | E.pcbtracker( 18 | status=0, 19 | expire=1200, 20 | ecenable=not config.maintenance_mode, 21 | eclimit=0, 22 | limit=0, 23 | time=int(time()), 24 | ) 25 | ) 26 | 27 | response_body, response_headers = await core_prepare_response(request, response) 28 | return Response(content=response_body, headers=response_headers) 29 | -------------------------------------------------------------------------------- /modules/drs/eventlog.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["REC"] 9 | 10 | 11 | @router.post("/{gameinfo}/eventlog/write") 12 | async def drs_eventlog_write(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.eventlog( 17 | E.gamesession(9999999, __type="s64"), 18 | E.logsendflg(0, __type="s32"), 19 | E.logerrlevel(0, __type="s32"), 20 | E.evtidnosendflg(0, __type="s32"), 21 | ) 22 | ) 23 | 24 | response_body, response_headers = await core_prepare_response(request, response) 25 | return Response(content=response_body, headers=response_headers) 26 | -------------------------------------------------------------------------------- /modules/ddr/eventlog.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["MDX"] 9 | 10 | 11 | @router.post("/{gameinfo}/eventlog/write") 12 | async def ddr_eventlog_write(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.eventlog( 17 | E.gamesession(9999999, __type="s64"), 18 | E.logsendflg(1, __type="s32"), 19 | E.logerrlevel(0, __type="s32"), 20 | E.evtidnosendflg(0, __type="s32"), 21 | ) 22 | ) 23 | 24 | response_body, response_headers = await core_prepare_response(request, response) 25 | return Response(content=response_body, headers=response_headers) 26 | -------------------------------------------------------------------------------- /modules/ddr/system.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | import utils.card as conv 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["MDX"] 9 | 10 | 11 | @router.post("/{gameinfo}/system/convcardnumber") 12 | async def system_convcardnumber(request: Request): 13 | request_info = await core_process_request(request) 14 | cid = request_info["root"][0].find("data/card_id").text 15 | 16 | response = E.response( 17 | E.system( 18 | E.data(E.card_number(conv.to_konami_id(cid), __type="str")), 19 | E.result(0, __type="s32"), 20 | ) 21 | ) 22 | 23 | response_body, response_headers = await core_prepare_response(request, response) 24 | return Response(content=response_body, headers=response_headers) 25 | -------------------------------------------------------------------------------- /modules/ddr/eventlog_2.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["MDX"] 9 | 10 | 11 | @router.post("/{gameinfo}/eventlog_2/write") 12 | async def ddr_eventlog_2_write(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.eventlog_2( 17 | E.gamesession(9999999, __type="s64"), 18 | E.logsendflg(1, __type="s32"), 19 | E.logerrlevel(0, __type="s32"), 20 | E.evtidnosendflg(0, __type="s32"), 21 | ) 22 | ) 23 | 24 | response_body, response_headers = await core_prepare_response(request, response) 25 | return Response(content=response_body, headers=response_headers) 26 | -------------------------------------------------------------------------------- /modules/ddr/system_2.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | import utils.card as conv 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["MDX"] 9 | 10 | 11 | @router.post("/{gameinfo}/system_2/convcardnumber") 12 | async def system_2_convcardnumber(request: Request): 13 | request_info = await core_process_request(request) 14 | cid = request_info["root"][0].find("data/card_id").text 15 | 16 | response = E.response( 17 | E.system_2( 18 | E.data(E.card_number(conv.to_konami_id(cid), __type="str")), 19 | E.result(0, __type="s32"), 20 | ) 21 | ) 22 | 23 | response_body, response_headers = await core_prepare_response(request, response) 24 | return Response(content=response_body, headers=response_headers) 25 | -------------------------------------------------------------------------------- /modules/sdvx/eventlog.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["KFC"] 9 | 10 | 11 | @router.post("/{gameinfo}/eventlog/write") 12 | async def sdvx_eventlog_write(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.eventlog( 17 | E.gamesession(9999999, __type="s64"), 18 | E.logsendflg(1 if config.maintenance_mode else 0, __type="s32"), 19 | E.logerrlevel(0, __type="s32"), 20 | E.evtidnosendflg(0, __type="s32"), 21 | ) 22 | ) 23 | 24 | response_body, response_headers = await core_prepare_response(request, response) 25 | return Response(content=response_body, headers=response_headers) 26 | -------------------------------------------------------------------------------- /modules/core/ins.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/core", tags=["ins"]) 6 | 7 | 8 | @router.post("/{gameinfo}/ins/netlog") 9 | async def ins_netlog(request: Request): 10 | request_info = await core_process_request(request) 11 | 12 | response = E.response(E.netlog(status=0)) 13 | 14 | response_body, response_headers = await core_prepare_response(request, response) 15 | return Response(content=response_body, headers=response_headers) 16 | 17 | 18 | @router.post("/{gameinfo}/ins/send") 19 | async def ins_send(request: Request): 20 | request_info = await core_process_request(request) 21 | 22 | response = E.response(E.netlog(status=0)) 23 | 24 | response_body, response_headers = await core_prepare_response(request, response) 25 | return Response(content=response_body, headers=response_headers) 26 | -------------------------------------------------------------------------------- /utils/db/trim_monkey_db.py: -------------------------------------------------------------------------------- 1 | import time 2 | from os import stat 3 | from shutil import copy 4 | 5 | from tinydb import TinyDB 6 | from tinydb.middlewares import CachingMiddleware 7 | from tinydb.storages import JSONStorage 8 | 9 | storage = CachingMiddleware(JSONStorage) 10 | storage.WRITE_CACHE_SIZE = 5000 11 | 12 | infile = "db.json" 13 | outfile = f"db_{round(time.time())}.json" 14 | 15 | copy(infile, outfile) 16 | 17 | db = TinyDB( 18 | infile, 19 | indent=2, 20 | encoding="utf-8", 21 | ensure_ascii=False, 22 | storage=storage, 23 | ) 24 | 25 | start_size = stat(infile).st_size 26 | 27 | # Non-best tables for GITADORA and IIDX are not used in game 28 | for table in ("guitarfreaks_scores", "drummania_scores", "iidx_scores"): 29 | db.drop_table(table) 30 | print("Dropped", table) 31 | 32 | db.close() 33 | 34 | end_size = stat(infile).st_size 35 | 36 | print(f"{infile} {round((start_size - end_size) / 1024 / 1024, 2)} MiB trimmed") 37 | -------------------------------------------------------------------------------- /modules/core/message.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/core", tags=["message"]) 8 | 9 | 10 | @router.post("/{gameinfo}/message/get") 11 | async def message_get(request: Request): 12 | request_info = await core_process_request(request) 13 | 14 | response = E.response( 15 | E.message( 16 | expire=300, 17 | *[ 18 | E.item( 19 | name=s, 20 | start=0, 21 | end=604800, 22 | ) 23 | for s in ("sys.mainte", "sys.eacoin.mainte") 24 | if config.maintenance_mode 25 | ] 26 | ) 27 | ) 28 | 29 | response_body, response_headers = await core_prepare_response(request, response) 30 | return Response(content=response_body, headers=response_headers) 31 | -------------------------------------------------------------------------------- /modules/core/dlstatus.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/core", tags=["dlstatus"]) 6 | 7 | 8 | @router.post("/{gameinfo}/dlstatus/done") 9 | async def dlstatus_done(request: Request): 10 | request_info = await core_process_request(request) 11 | 12 | response = E.response(E.dlstatus(status=0)) 13 | 14 | response_body, response_headers = await core_prepare_response(request, response) 15 | return Response(content=response_body, headers=response_headers) 16 | 17 | 18 | @router.post("/{gameinfo}/dlstatus/progress") 19 | async def dlstatus_progress(request: Request): 20 | request_info = await core_process_request(request) 21 | 22 | response = E.response(E.dlstatus(status=0)) 23 | 24 | response_body, response_headers = await core_prepare_response(request, response) 25 | return Response(content=response_body, headers=response_headers) 26 | -------------------------------------------------------------------------------- /modules/core/package.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/core", tags=["package"]) 6 | 7 | 8 | @router.post("/{gameinfo}/package/list") 9 | async def package_list(request: Request): 10 | request_info = await core_process_request(request) 11 | 12 | response = E.response(E.package(expire=1200, status=0)) 13 | 14 | response_body, response_headers = await core_prepare_response(request, response) 15 | return Response(content=response_body, headers=response_headers) 16 | 17 | 18 | @router.post("/{gameinfo}/package/intend") 19 | async def package_intend(request: Request): 20 | request_info = await core_process_request(request) 21 | 22 | response = E.response(E.package(status=0)) 23 | 24 | response_body, response_headers = await core_prepare_response(request, response) 25 | return Response(content=response_body, headers=response_headers) 26 | -------------------------------------------------------------------------------- /modules/iidx/iidx32streaming.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX32streaming/common") 12 | async def iidx32streaming_common(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX32streaming()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | 20 | 21 | @router.post("/{gameinfo}/IIDX32streaming/getcm") 22 | async def iidx32streaming_getcm(request: Request): 23 | request_info = await core_process_request(request) 24 | 25 | response = E.response(E.IIDX32streaming()) 26 | 27 | response_body, response_headers = await core_prepare_response(request, response) 28 | return Response(content=response_body, headers=response_headers) 29 | -------------------------------------------------------------------------------- /modules/iidx/iidx33streaming.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX33streaming/common") 12 | async def iidx33streaming_common(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX33streaming()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | 20 | 21 | @router.post("/{gameinfo}/IIDX33streaming/getcm") 22 | async def iidx33streaming_getcm(request: Request): 23 | request_info = await core_process_request(request) 24 | 25 | response = E.response(E.IIDX33streaming()) 26 | 27 | response_body, response_headers = await core_prepare_response(request, response) 28 | return Response(content=response_body, headers=response_headers) 29 | -------------------------------------------------------------------------------- /modules/iidx/iidx31streaming.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX31streaming/common") 12 | async def iidx31streaming_common(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX31streaming()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | 20 | 21 | @router.post("/{gameinfo}/IIDX31streaming/getcm") 22 | async def iidx31streaming_getcm(request: Request): 23 | request_info = await core_process_request(request) 24 | 25 | response = E.response(E.IIDX31streaming()) 26 | 27 | response_body, response_headers = await core_prepare_response(request, response) 28 | return Response(content=response_body, headers=response_headers) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MonkeyBusiness 2 | 3 | Experimental e-amuse server intended for testing hacks, also usable by players 4 | 5 | ## Usage 6 | 7 | Run [start.bat (Windows)](start.bat) or [start.sh (Linux, MacOS)](start.sh) 8 | 9 | [web interface](https://github.com/drmext/BounceTrippy/releases), [score import](utils/db) 10 | 11 | ## Playable Games 12 | - IIDX 18-20, 29-33 (Online Arena/BPL support) 13 | - DDR A20P, A3 (OmniMIX/GF, BPL, and [Fake PFREE](https://github.com/drmext/BemaniPatcher/blob/nopr/ddra3.html#L133) support) 14 | - GD 6-10 DELTA (Battle Mode support) 15 | - DRS 16 | - NOST 3 17 | - SDVX 6 18 | 19 | **Note**: Playable means settings/scores *should* save and load. Events are not implemented. 20 | 21 | ## Troubleshooting 22 | 23 | - Delete [or fix](start.bat#L9) `/.venv` if the server folder is moved or python is upgraded 24 | 25 | - DRS, GD, NOST, and SDVX require mdb xml files copied to the server folder 26 | 27 | - **URL Slash 1 (On)** [may still be required in rare cases](modules/__init__.py#L46) 28 | 29 | - When initially creating a DDR profile, complete an entire credit without pfree hacks 30 | -------------------------------------------------------------------------------- /modules/gitadora/shopinfo.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | router = APIRouter(prefix="/local", tags=["local"]) 6 | router.model_whitelist = ["M32"] 7 | 8 | 9 | @router.post("/{gameinfo}/{ver}_shopinfo/regist") 10 | async def gitadora_shopinfo_regist(ver: str, request: Request): 11 | request_info = await core_process_request(request) 12 | 13 | response = E.response( 14 | E( 15 | f"{ver}_shopinfo", 16 | E.data( 17 | E.cabid(1, __type="u32"), 18 | E.locationid("EA000001", __type="str"), 19 | E.is_send(0, __type="u8"), 20 | ), 21 | E.temperature( 22 | E.is_send(0, __type="bool"), 23 | ), 24 | E.tax( 25 | E.tax_phase(1, __type="s32"), 26 | ), 27 | ) 28 | ) 29 | 30 | response_body, response_headers = await core_prepare_response(request, response) 31 | return Response(content=response_body, headers=response_headers) 32 | -------------------------------------------------------------------------------- /utils/lz77.py: -------------------------------------------------------------------------------- 1 | class EamuseLZ77: 2 | @staticmethod 3 | def decode(data): 4 | data_length = len(data) 5 | offset = 0 6 | output = [] 7 | while offset < data_length: 8 | flag = data[offset] 9 | offset += 1 10 | for bit in range(8): 11 | if flag & (1 << bit): 12 | output.append(data[offset]) 13 | offset += 1 14 | else: 15 | if offset >= data_length: 16 | break 17 | lookback_flag = int.from_bytes(data[offset : offset + 2], "big") 18 | lookback_length = (lookback_flag & 0x000F) + 3 19 | lookback_offset = lookback_flag >> 4 20 | offset += 2 21 | if lookback_flag == 0: 22 | break 23 | for _ in range(lookback_length): 24 | loffset = len(output) - lookback_offset 25 | if loffset <= 0 or loffset >= len(output): 26 | output.append(0) 27 | else: 28 | output.append(output[loffset]) 29 | return bytes(output) 30 | 31 | # @staticmethod 32 | # def encode(data): 33 | # return bytes(output) 34 | -------------------------------------------------------------------------------- /utils/db/README.md: -------------------------------------------------------------------------------- 1 | # Database Utilities 2 | 3 | **Backup db.json before using these scripts** 4 | 5 | ## Shrink DB 6 | 7 | ### [trim_monkey_db.py](trim_monkey_db.py) 8 | 9 | This deletes unused Gitadora and IIDX non-best scores, which can drastically reduce the size of db.json in a multiuser environment 10 | 11 | Example: 12 | `python utils\db\trim_monkey_db.py` 13 | 14 | ## Score Import 15 | 16 | Instructions: 17 | 18 | 1. Enable `EA Automap` and `EA Netdump` in spicecfg 19 | 20 | 1. Boot the game on the source network to export 21 | 22 | 1. Card in on the source profile to export (all the way to music select menu) 23 | 24 | 1. Exit the game 25 | 26 | 1. Disable `EA Automap` and `EA Netdump` in spicecfg 27 | 28 | 1. Run the corresponding import script 29 | 30 | ### [import_ddr_spice_automap.py](import_ddr_spice_automap.py) 31 | 32 | Example: `python utils\db\import_ddr_spice_automap.py --automap_xml automap_0.xml --version 19 --monkey_db db.json --ddr_id 12345678` 33 | 34 | - `--version` 19 for A20P or 20 for A3 35 | 36 | - `--ddr_id` destination profile in db.json 37 | 38 | ### [import_iidx_spice_automap.py](import_iidx_spice_automap.py) 39 | 40 | Example: `python utils\db\import_iidx_spice_automap.py --automap_xml automap_0.xml --version 30 --monkey_db db.json --iidx_id 12345678` 41 | 42 | - `--version` must match the source export version (27+ supported) 43 | 44 | - `--iidx_id` destination profile in db.json -------------------------------------------------------------------------------- /modules/gitadora/lobby.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import E, core_prepare_response, core_process_request 4 | 5 | router = APIRouter(prefix="/lobby", tags=["lobby"]) 6 | router.model_whitelist = ["M32"] 7 | 8 | 9 | host = {} 10 | 11 | 12 | @router.post("/{gameinfo}/lobby/request") 13 | async def gitadora_lobby_request(request: Request): 14 | request_info = await core_process_request(request) 15 | 16 | root = request_info["root"][0][0] 17 | address_ip = root.find("address/ip").text 18 | check_attestid = root.find("check/attestid").text 19 | 20 | if host: 21 | if host["ip"] != address_ip: 22 | response = E.response( 23 | E.lobby( 24 | E.lobbydata( 25 | E.candidate( 26 | E.address( 27 | E.ip(host["ip"], __type="str"), 28 | ), 29 | E.check( 30 | E.attestid(host["attestid"], __type="str"), 31 | ), 32 | ), 33 | ), 34 | ) 35 | ) 36 | 37 | elif host["ip"] == address_ip: 38 | response = E.response(E.lobby()) 39 | 40 | del host["ip"] 41 | del host["attestid"] 42 | 43 | else: 44 | host["ip"] = address_ip 45 | host["attestid"] = check_attestid 46 | response = E.response(E.lobby()) 47 | 48 | response_body, response_headers = await core_prepare_response(request, response) 49 | return Response(content=response_body, headers=response_headers) 50 | -------------------------------------------------------------------------------- /utils/db/delete_monkey_user.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import time 3 | from os import stat 4 | from shutil import copy 5 | 6 | from tinydb import TinyDB, where 7 | from tinydb.middlewares import CachingMiddleware 8 | from tinydb.storages import JSONStorage 9 | 10 | 11 | def delete_scores(user_id, game): 12 | for scores in (f"{game}_scores_best", f"{game}_scores", f"{game}_profile"): 13 | if db.table(scores).search((where(f"{game}_id") == user_id)): 14 | db.table(scores).remove((where(f"{game}_id") == user_id)) 15 | 16 | 17 | # special case 18 | def delete_gitadora_scores(user_id): 19 | for scores in ( 20 | "guitarfreaks_scores_best", 21 | "drummania_scores_best", 22 | "guitarfreaks_scores", 23 | "drummania_scores", 24 | "gitadora_profile", 25 | ): 26 | if db.table(scores).search((where("gitadora_id") == user_id)): 27 | db.table(scores).remove((where("gitadora_id") == user_id)) 28 | 29 | 30 | if __name__ == "__main__": 31 | parser = argparse.ArgumentParser() 32 | parser.add_argument("-g", "--game", help="ex: ddr, gitadora, iidx", required=True) 33 | parser.add_argument("-i", "--user_id", help="ex: 12345678", required=True) 34 | args = parser.parse_args() 35 | 36 | storage = CachingMiddleware(JSONStorage) 37 | storage.WRITE_CACHE_SIZE = 5000 38 | 39 | infile = "db.json" 40 | outfile = f"db_{round(time.time())}.json" 41 | 42 | copy(infile, outfile) 43 | 44 | db = TinyDB( 45 | infile, 46 | indent=2, 47 | encoding="utf-8", 48 | ensure_ascii=False, 49 | storage=storage, 50 | ) 51 | 52 | start_size = stat(infile).st_size 53 | 54 | game = args.game.lower() 55 | user_id = int(args.user_id.replace("-", "")) 56 | if game == "gitadora": 57 | delete_gitadora_scores(user_id) 58 | else: 59 | delete_scores(user_id, game) 60 | 61 | db.close() 62 | 63 | end_size = stat(infile).st_size 64 | 65 | print(f"{infile} {round((start_size - end_size) / 1024 / 1024, 2)} MiB trimmed") 66 | -------------------------------------------------------------------------------- /modules/iidx/shop.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["LDJ", "KDZ", "JDZ"] 9 | 10 | 11 | @router.post("/{gameinfo}/shop/getname") 12 | async def shop_getname(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.shop( 17 | cls_opt=0, 18 | opname=config.arcade, 19 | pid=13, 20 | ) 21 | ) 22 | 23 | response_body, response_headers = await core_prepare_response(request, response) 24 | return Response(content=response_body, headers=response_headers) 25 | 26 | 27 | @router.post("/{gameinfo}/shop/getconvention") 28 | async def shop_getconvention(request: Request): 29 | request_info = await core_process_request(request) 30 | 31 | response = E.response( 32 | E.shop( 33 | E.valid(1, __type="bool"), 34 | music_0=-1, 35 | music_1=-1, 36 | music_2=-1, 37 | music_3=-1, 38 | start_time=0, 39 | end_time=0, 40 | ) 41 | ) 42 | 43 | response_body, response_headers = await core_prepare_response(request, response) 44 | return Response(content=response_body, headers=response_headers) 45 | 46 | 47 | @router.post("/{gameinfo}/shop/sentinfo") 48 | async def shop_sentinfo(request: Request): 49 | request_info = await core_process_request(request) 50 | 51 | response = E.response(E.shop()) 52 | 53 | response_body, response_headers = await core_prepare_response(request, response) 54 | return Response(content=response_body, headers=response_headers) 55 | 56 | 57 | @router.post("/{gameinfo}/shop/sendescapepackageinfo") 58 | async def shop_sendescapepackageinfo(request: Request): 59 | request_info = await core_process_request(request) 60 | 61 | response = E.response(E.shop(expire=1200)) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | TITLE MB 4 | 5 | cd /d %~dp0 6 | 7 | REM goto :make_venv_portable 8 | 9 | if not exist .venv\Scripts\activate.bat ( 10 | python -m venv .venv 11 | ) 12 | 13 | ( 14 | .venv\Scripts\activate.bat 15 | python -m pip install -r requirements.txt 16 | python pyeamu.py 17 | ) 18 | 19 | echo: 20 | echo Install python with "Add python.exe to PATH" checked 21 | echo https://www.python.org/downloads/ 22 | echo: 23 | echo Note: Choose the previous version if latest is 3.xx.0 24 | echo: 25 | 26 | pause 27 | 28 | goto :eof 29 | 30 | REM breaks when special characters are in path 31 | :make_venv_portable 32 | set pyvenv="%~dp0.venv\pyvenv.cfg" 33 | set pyvenvtemp="%~dp0.venv\pyvenv.tmp" 34 | for /f "tokens=*" %%i in ('python -V') do set pyver=%%i 35 | for /f %%i in ('where python') do set pypath=%%i 36 | set v=home version executable command 37 | for %%a in (%v%) do for /f "tokens=*" %%i in ('findstr /b %%a %pyvenv%') do set %%a=%%i 38 | if exist %pyvenvtemp% del %pyvenvtemp% 39 | setlocal enabledelayedexpansion 40 | ( 41 | for /f "tokens=1* delims=:" %%a in ('findstr /n "^" %pyvenv%') do ( 42 | set "line=%%b" 43 | if "!line!"=="%home%" (set line=home = %pypath:~0,-11%) 44 | if "!line!"=="%version%" (set line=version = %pyver:~7%) 45 | if "!line!"=="%executable%" (set line=executable = %pypath%) 46 | if "!line!"=="%command%" (set line=command = %pypath% -m venv %~dp0.venv) 47 | echo(!line! 48 | ) 49 | ) > %pyvenvtemp% 50 | endlocal 51 | del %pyvenv% 52 | rename %pyvenvtemp% pyvenv.cfg 53 | 54 | set activate="%~dp0.venv\Scripts\activate.bat" 55 | set activatetemp="%~dp0.venv\Scripts\activate.tmp" 56 | for /f "tokens=*" %%i in ('findstr "VIRTUAL_ENV=" %activate%') do set virtual=%%i 57 | if exist %activatetemp% del %activatetemp% 58 | setlocal enabledelayedexpansion 59 | ( 60 | for /f "tokens=1* delims=:" %%a in ('findstr /n "^" %activate%') do ( 61 | set "line=%%b" 62 | if "!line!"=="%virtual%" (set line=set VIRTUAL_ENV=%~dp0.venv) 63 | if "!line!"=="END" (set line=:END) 64 | echo(!line! 65 | ) 66 | ) > %activatetemp% 67 | endlocal 68 | del %activate% 69 | rename "%activatetemp%" activate.bat 70 | -------------------------------------------------------------------------------- /modules/iidx/iidx29shop.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX29shop/getname") 12 | async def iidx29shop_getname(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.IIDX29shop( 17 | cls_opt=0, 18 | opname=config.arcade, 19 | pid=13, 20 | ) 21 | ) 22 | 23 | response_body, response_headers = await core_prepare_response(request, response) 24 | return Response(content=response_body, headers=response_headers) 25 | 26 | 27 | @router.post("/{gameinfo}/IIDX29shop/getconvention") 28 | async def iidx29shop_getconvention(request: Request): 29 | request_info = await core_process_request(request) 30 | 31 | response = E.response( 32 | E.IIDX29shop( 33 | E.valid(1, __type="bool"), 34 | music_0=-1, 35 | music_1=-1, 36 | music_2=-1, 37 | music_3=-1, 38 | start_time=0, 39 | end_time=0, 40 | ) 41 | ) 42 | 43 | response_body, response_headers = await core_prepare_response(request, response) 44 | return Response(content=response_body, headers=response_headers) 45 | 46 | 47 | @router.post("/{gameinfo}/IIDX29shop/sentinfo") 48 | async def iidx29shop_sentinfo(request: Request): 49 | request_info = await core_process_request(request) 50 | 51 | response = E.response(E.IIDX29shop()) 52 | 53 | response_body, response_headers = await core_prepare_response(request, response) 54 | return Response(content=response_body, headers=response_headers) 55 | 56 | 57 | @router.post("/{gameinfo}/IIDX29shop/sendescapepackageinfo") 58 | async def iidx29shop_sendescapepackageinfo(request: Request): 59 | request_info = await core_process_request(request) 60 | 61 | response = E.response(E.IIDX29shop(expire=1200)) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | -------------------------------------------------------------------------------- /modules/iidx/iidx30shop.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX30shop/getname") 12 | async def iidx30shop_getname(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.IIDX30shop( 17 | cls_opt=0, 18 | opname=config.arcade, 19 | pid=13, 20 | ) 21 | ) 22 | 23 | response_body, response_headers = await core_prepare_response(request, response) 24 | return Response(content=response_body, headers=response_headers) 25 | 26 | 27 | @router.post("/{gameinfo}/IIDX30shop/getconvention") 28 | async def iidx30shop_getconvention(request: Request): 29 | request_info = await core_process_request(request) 30 | 31 | response = E.response( 32 | E.IIDX30shop( 33 | E.valid(1, __type="bool"), 34 | music_0=-1, 35 | music_1=-1, 36 | music_2=-1, 37 | music_3=-1, 38 | start_time=0, 39 | end_time=0, 40 | ) 41 | ) 42 | 43 | response_body, response_headers = await core_prepare_response(request, response) 44 | return Response(content=response_body, headers=response_headers) 45 | 46 | 47 | @router.post("/{gameinfo}/IIDX30shop/sentinfo") 48 | async def iidx30shop_sentinfo(request: Request): 49 | request_info = await core_process_request(request) 50 | 51 | response = E.response(E.IIDX30shop()) 52 | 53 | response_body, response_headers = await core_prepare_response(request, response) 54 | return Response(content=response_body, headers=response_headers) 55 | 56 | 57 | @router.post("/{gameinfo}/IIDX30shop/sendescapepackageinfo") 58 | async def iidx30shop_sendescapepackageinfo(request: Request): 59 | request_info = await core_process_request(request) 60 | 61 | response = E.response(E.IIDX30shop(expire=1200)) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | -------------------------------------------------------------------------------- /modules/iidx/iidx31shop.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX31shop/getname") 12 | async def iidx31shop_getname(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.IIDX31shop( 17 | cls_opt=0, 18 | opname=config.arcade, 19 | pid=13, 20 | ) 21 | ) 22 | 23 | response_body, response_headers = await core_prepare_response(request, response) 24 | return Response(content=response_body, headers=response_headers) 25 | 26 | 27 | @router.post("/{gameinfo}/IIDX31shop/getconvention") 28 | async def iidx31shop_getconvention(request: Request): 29 | request_info = await core_process_request(request) 30 | 31 | response = E.response( 32 | E.IIDX31shop( 33 | E.valid(1, __type="bool"), 34 | music_0=-1, 35 | music_1=-1, 36 | music_2=-1, 37 | music_3=-1, 38 | start_time=0, 39 | end_time=0, 40 | ) 41 | ) 42 | 43 | response_body, response_headers = await core_prepare_response(request, response) 44 | return Response(content=response_body, headers=response_headers) 45 | 46 | 47 | @router.post("/{gameinfo}/IIDX31shop/sentinfo") 48 | async def iidx31shop_sentinfo(request: Request): 49 | request_info = await core_process_request(request) 50 | 51 | response = E.response(E.IIDX31shop()) 52 | 53 | response_body, response_headers = await core_prepare_response(request, response) 54 | return Response(content=response_body, headers=response_headers) 55 | 56 | 57 | @router.post("/{gameinfo}/IIDX31shop/sendescapepackageinfo") 58 | async def iidx31shop_sendescapepackageinfo(request: Request): 59 | request_info = await core_process_request(request) 60 | 61 | response = E.response(E.IIDX31shop(expire=1200)) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | -------------------------------------------------------------------------------- /modules/iidx/iidx32shop.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX32shop/getname") 12 | async def iidx32shop_getname(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response( 16 | E.IIDX32shop( 17 | cls_opt=0, 18 | opname=config.arcade, 19 | pid=13, 20 | ) 21 | ) 22 | 23 | response_body, response_headers = await core_prepare_response(request, response) 24 | return Response(content=response_body, headers=response_headers) 25 | 26 | 27 | @router.post("/{gameinfo}/IIDX32shop/getconvention") 28 | async def iidx32shop_getconvention(request: Request): 29 | request_info = await core_process_request(request) 30 | 31 | response = E.response( 32 | E.IIDX32shop( 33 | E.valid(1, __type="bool"), 34 | music_0=-1, 35 | music_1=-1, 36 | music_2=-1, 37 | music_3=-1, 38 | start_time=0, 39 | end_time=0, 40 | ) 41 | ) 42 | 43 | response_body, response_headers = await core_prepare_response(request, response) 44 | return Response(content=response_body, headers=response_headers) 45 | 46 | 47 | @router.post("/{gameinfo}/IIDX32shop/sentinfo") 48 | async def iidx32shop_sentinfo(request: Request): 49 | request_info = await core_process_request(request) 50 | 51 | response = E.response(E.IIDX32shop()) 52 | 53 | response_body, response_headers = await core_prepare_response(request, response) 54 | return Response(content=response_body, headers=response_headers) 55 | 56 | 57 | @router.post("/{gameinfo}/IIDX32shop/sendescapepackageinfo") 58 | async def iidx32shop_sendescapepackageinfo(request: Request): 59 | request_info = await core_process_request(request) 60 | 61 | response = E.response(E.IIDX32shop(expire=1200)) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib import util 2 | from os import path 3 | from glob import glob 4 | 5 | from fastapi import APIRouter, Request 6 | from typing import Optional 7 | 8 | routers = [] 9 | for module_path in [ 10 | f 11 | for f in glob(path.join(path.dirname(__file__), "**/*.py"), recursive=True) 12 | if path.basename(f) != "__init__.py" 13 | ]: 14 | spec = util.spec_from_file_location("", module_path) 15 | module = util.module_from_spec(spec) 16 | spec.loader.exec_module(module) 17 | 18 | router = getattr(module, "router", None) 19 | if router is not None: 20 | routers.append(router) 21 | 22 | if path.basename(module_path) != "api.py": 23 | for obj in dir(module): 24 | globals()[obj] = module.__dict__[obj] 25 | 26 | router = APIRouter(tags=["slashless_forwarder"]) 27 | 28 | 29 | @router.post("/fwdr") 30 | async def forward_slashless( 31 | request: Request, 32 | model: Optional[str] = None, 33 | f: Optional[str] = None, 34 | module: Optional[str] = None, 35 | method: Optional[str] = None, 36 | ): 37 | if f != None: 38 | module, method = f.split(".") 39 | 40 | try: 41 | find_response = globals()[f"{module}_{method}".lower()] 42 | return await find_response(request) 43 | except KeyError: 44 | try: 45 | game_code = model.split(":")[0] 46 | # TODO: check for more edge cases 47 | if game_code == "MDX" and module == "eventlog" or module == "eventlog_2": 48 | find_response = globals()[f"ddr_{module}_{method}"] 49 | elif game_code == "REC": 50 | find_response = globals()[f"drs_{module}_{method}"] 51 | elif game_code == "KFC" and module == "eventlog": 52 | find_response = globals()[f"sdvx_{module}_{method}"] 53 | elif game_code == "M32": 54 | if module == "lobby": 55 | find_response = globals()[f"gitadora_{module}_{method}"] 56 | else: 57 | gd_module = module.split("_") 58 | find_response = globals()[f"gitadora_{gd_module[-1]}_{method}"] 59 | return await find_response(gd_module[0], request) 60 | return await find_response(request) 61 | except (KeyError, UnboundLocalError): 62 | print("Try URL Slash 1 (On) if this game is supported.") 63 | return Response(status_code=404) 64 | 65 | 66 | routers.append(router) 67 | -------------------------------------------------------------------------------- /modules/iidx/iidx29lobby.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX29lobby/entry") 12 | async def iidx29lobby_entry(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | response = E.response(E.IIDX29lobby()) 16 | 17 | response_body, response_headers = await core_prepare_response(request, response) 18 | return Response(content=response_body, headers=response_headers) 19 | 20 | 21 | @router.post("/{gameinfo}/IIDX29lobby/update") 22 | async def iidx29lobby_update(request: Request): 23 | request_info = await core_process_request(request) 24 | 25 | response = E.response(E.IIDX29lobby()) 26 | 27 | response_body, response_headers = await core_prepare_response(request, response) 28 | return Response(content=response_body, headers=response_headers) 29 | 30 | 31 | @router.post("/{gameinfo}/IIDX29lobby/delete") 32 | async def iidx29lobby_delete(request: Request): 33 | request_info = await core_process_request(request) 34 | 35 | response = E.response(E.IIDX29lobby()) 36 | 37 | response_body, response_headers = await core_prepare_response(request, response) 38 | return Response(content=response_body, headers=response_headers) 39 | 40 | 41 | @router.post("/{gameinfo}/IIDX29lobby/bplbattle_entry") 42 | async def iidx29lobby_bplbattle_entry(request: Request): 43 | request_info = await core_process_request(request) 44 | 45 | response = E.response(E.IIDX29lobby()) 46 | 47 | response_body, response_headers = await core_prepare_response(request, response) 48 | return Response(content=response_body, headers=response_headers) 49 | 50 | 51 | @router.post("/{gameinfo}/IIDX29lobby/bplbattle_update") 52 | async def iidx29lobby_bplbattle_update(request: Request): 53 | request_info = await core_process_request(request) 54 | 55 | response = E.response(E.IIDX29lobby()) 56 | 57 | response_body, response_headers = await core_prepare_response(request, response) 58 | return Response(content=response_body, headers=response_headers) 59 | 60 | 61 | @router.post("/{gameinfo}/IIDX29lobby/bplbattle_delete") 62 | async def iidx29lobby_bplbattle_delete(request: Request): 63 | request_info = await core_process_request(request) 64 | 65 | response = E.response(E.IIDX29lobby()) 66 | 67 | response_body, response_headers = await core_prepare_response(request, response) 68 | return Response(content=response_body, headers=response_headers) 69 | -------------------------------------------------------------------------------- /modules/core/facility.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | from tinydb import where 5 | 6 | from core_common import core_process_request, core_prepare_response, E 7 | from core_database import get_db 8 | 9 | router = APIRouter(prefix="/core", tags=["facility"]) 10 | 11 | 12 | @router.post("/{gameinfo}/facility/get") 13 | async def facility_get(request: Request): 14 | request_info = await core_process_request(request) 15 | pcbid = request_info["root"].attrib["srcid"] 16 | 17 | op = get_db().table("shop").get(where("pcbid") == pcbid) 18 | op = {} if op is None else op 19 | 20 | response = E.response( 21 | E.facility( 22 | E.location( 23 | E("id", "EA000001", __type="str"), 24 | E.country("JP", __type="str"), 25 | E.region("JP-13", __type="str"), 26 | E.customercode("X000000001", __type="str"), 27 | E.companycode("X000000001", __type="str"), 28 | E.latitude(0, __type="s32"), 29 | E.longitude(0, __type="s32"), 30 | E.accuracy(0, __type="u8"), 31 | E.countryname("Japan", __type="str"), 32 | E.regionname("Tokyo", __type="str"), 33 | E.countryjname("日本国", __type="str"), 34 | E.regionjname("東京都", __type="str"), 35 | E.name(op.get("opname", config.arcade), __type="str"), 36 | E("type", 255, __type="u8"), 37 | ), 38 | E.line( 39 | E("class", 8, __type="u8"), 40 | E.rtt(500, __type="u16"), 41 | E.upclass(8, __type="u8"), 42 | E("id", 3, __type="str"), 43 | ), 44 | E.portfw( 45 | E.globalip(request.client.host, __type="ip4"), 46 | E.globalport(5700, __type="u16"), 47 | E.privateport(5700, __type="u16"), 48 | ), 49 | E.public( 50 | E.flag(1, __type="u8"), 51 | E.name(op.get("opname", config.arcade), __type="str"), 52 | E.latitude(0, __type="str"), 53 | E.longitude(0, __type="str"), 54 | ), 55 | E.share( 56 | E.eacoin( 57 | E.notchamount(3000, __type="s32"), 58 | E.notchcount(3, __type="s32"), 59 | E.supplylimit(9999, __type="s32"), 60 | ), 61 | E.eapass( 62 | E.valid(365, __type="u16"), 63 | ), 64 | E.url( 65 | E.eapass("www.ea-pass.konami.net", __type="str"), 66 | E.arcadefan("www.konami.jp/am", __type="str"), 67 | E.konaminetdx("http://am.573.jp", __type="str"), 68 | E.konamiid("https://id.konami.net", __type="str"), 69 | E.eagate("http://eagate.573.jp", __type="str"), 70 | ), 71 | ), 72 | expire=10800, 73 | ) 74 | ) 75 | 76 | response_body, response_headers = await core_prepare_response(request, response) 77 | return Response(content=response_body, headers=response_headers) 78 | -------------------------------------------------------------------------------- /utils/card.py: -------------------------------------------------------------------------------- 1 | # https://bsnk.me/eamuse/ 2 | 3 | from Cryptodome.Cipher import DES3 4 | 5 | 6 | KEY = bytes(i * 2 for i in b"?I'llB2c.YouXXXeMeHaYpy!") 7 | IV = bytes(8) 8 | 9 | valid_characters = "0123456789ABCDEFGHJKLMNPRSTUWXYZ" 10 | 11 | 12 | def enc_des(uid): 13 | cipher = DES3.new(KEY, DES3.MODE_CBC, IV) 14 | return cipher.encrypt(uid) 15 | 16 | 17 | def dec_des(uid): 18 | cipher = DES3.new(KEY, DES3.MODE_CBC, IV) 19 | return cipher.decrypt(uid) 20 | 21 | 22 | def checksum(data): 23 | chk = sum(data[i] * (i % 3 + 1) for i in range(15)) 24 | while chk > 31: 25 | chk = (chk >> 5) + (chk & 31) 26 | return chk 27 | 28 | 29 | def pack_5(data): 30 | data = "".join(f"{i:05b}" for i in data) 31 | if len(data) % 8 != 0: 32 | data += "0" * (8 - (len(data) % 8)) 33 | return bytes(int(data[i : i + 8], 2) for i in range(0, len(data), 8)) 34 | 35 | 36 | def unpack_5(data): 37 | data = "".join(f"{i:08b}" for i in data) 38 | if len(data) % 5 != 0: 39 | data += "0" * (5 - (len(data) % 5)) 40 | return bytes(int(data[i : i + 5], 2) for i in range(0, len(data), 5)) 41 | 42 | 43 | def to_konami_id(uid): 44 | assert len(uid) == 16, "UID must be 16 bytes" 45 | 46 | if uid.upper().startswith("E004"): 47 | card_type = 1 48 | elif uid.upper().startswith("0"): 49 | card_type = 2 50 | else: 51 | raise ValueError("Invalid UID prefix") 52 | 53 | kid = bytes.fromhex(uid) 54 | assert len(kid) == 8, "ID must be 8 bytes" 55 | 56 | out = bytearray(unpack_5(enc_des(kid[::-1]))[:13]) + bytes(3) 57 | 58 | out[0] ^= card_type 59 | out[13] = 1 60 | for i in range(1, 14): 61 | out[i] ^= out[i - 1] 62 | out[14] = card_type 63 | out[15] = checksum(out) 64 | 65 | return "".join(valid_characters[i] for i in out) 66 | 67 | 68 | def to_uid(konami_id): 69 | if konami_id[14] == "1": 70 | card_type = 1 71 | elif konami_id[14] == "2": 72 | card_type = 2 73 | else: 74 | raise ValueError("Invalid ID") 75 | 76 | assert len(konami_id) == 16, f"ID must be 16 characters" 77 | assert all( 78 | i in valid_characters for i in konami_id 79 | ), "ID contains invalid characters" 80 | card = [valid_characters.index(i) for i in konami_id] 81 | assert card[11] % 2 == card[12] % 2, "Parity check failed" 82 | assert card[13] == card[12] ^ 1, "Card invalid" 83 | assert card[15] == checksum(card), "Checksum failed" 84 | 85 | for i in range(13, 0, -1): 86 | card[i] ^= card[i - 1] 87 | 88 | card[0] ^= card_type 89 | 90 | card_id = dec_des(pack_5(card[:13])[:8])[::-1] 91 | card_id = card_id.hex().upper() 92 | 93 | if card_type == 1: 94 | assert card_id[:4] == "E004", "Invalid card type" 95 | elif card_type == 2: 96 | assert card_id[0] == "0", "Invalid card type" 97 | return card_id 98 | 99 | 100 | if __name__ == "__main__": 101 | assert to_konami_id("0000000000000000") == "007TUT8XJNSSPN2P", "To KID failed" 102 | assert to_uid("007TUT8XJNSSPN2P") == "0000000000000000", "From KID failed" 103 | assert ( 104 | to_uid(to_konami_id("000000100200F000")) == "000000100200F000" 105 | ), "Roundtrip failed" 106 | -------------------------------------------------------------------------------- /modules/iidx/iidx30gamesystem.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/local2", tags=["local2"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | @router.post("/{gameinfo}/IIDX30gameSystem/systemInfo") 14 | async def iidx30gamesystem_systeminfo(request: Request): 15 | request_info = await core_process_request(request) 16 | 17 | unlock = () 18 | # force unlock LM exclusives to complete unlock all songs server side 19 | # this makes LM exclusive folder disappear, so just use hex edits 20 | # unlock = (28073, 28008, 29095, 29094, 29027, 30077, 30076, 30098, 30106, 30107, 30028, 30064, 30027) 21 | 22 | current_time = round(time()) 23 | 24 | response = E.response( 25 | E.IIDX30gameSystem( 26 | *[ 27 | E.music_open( 28 | E.music_id(mid, __type="s32"), 29 | E.kind(0, __type="s32"), 30 | ) 31 | for mid in unlock 32 | ], 33 | E.arena_schedule( 34 | E.phase(3, __type="u8"), 35 | E.start(current_time - 600, __type="u32"), 36 | E.end(current_time + 600, __type="u32"), 37 | ), 38 | *[ 39 | E.arena_reward( 40 | E.index(unlock.index(mid), __type="s32"), 41 | E.cube_num((unlock.index(mid) + 1) * 50, __type="s32"), 42 | E.kind(0, __type="s32"), 43 | E.value(mid, __type="str"), 44 | ) 45 | for mid in unlock 46 | ], 47 | *[ 48 | E.arena_music_difficult( 49 | E.play_style(sp_dp, __type="s32"), 50 | E.arena_class(arena_class, __type="s32"), 51 | E.low_difficult(8, __type="s32"), 52 | E.high_difficult(12, __type="s32"), 53 | E.is_leggendaria(1, __type="bool"), 54 | E.force_music_list_id(0, __type="s32"), 55 | ) 56 | for sp_dp in (0, 1) 57 | for arena_class in range(20) 58 | ], 59 | *[ 60 | E.arena_cpu_define( 61 | E.play_style(sp_dp, __type="s32"), 62 | E.arena_class(arena_class, __type="s32"), 63 | E.grade_id(18, __type="s32"), 64 | E.low_music_difficult(8, __type="s32"), 65 | E.high_music_difficult(12, __type="s32"), 66 | E.is_leggendaria(0, __type="bool"), 67 | ) 68 | for sp_dp in (0, 1) 69 | for arena_class in range(20) 70 | ], 71 | *[ 72 | E.maching_class_range( 73 | E.play_style(sp_dp, __type="s32"), 74 | E.matching_class(arena_class, __type="s32"), 75 | E.low_arena_class(arena_class, __type="s32"), 76 | E.high_arena_class(arena_class, __type="s32"), 77 | ) 78 | for sp_dp in (0, 1) 79 | for arena_class in range(20) 80 | ], 81 | E.CommonBossPhase(val=0), 82 | E.Event1InternalPhase(val=0), 83 | E.ExtraBossEventPhase(val=0), 84 | E.isNewSongAnother12OpenFlg(val=1), 85 | E.gradeOpenPhase(val=2), 86 | E.isEiseiOpenFlg(val=1), 87 | E.WorldTourismOpenList(val=-1), 88 | E.BPLBattleOpenPhase(val=3), 89 | ) 90 | ) 91 | 92 | response_body, response_headers = await core_prepare_response(request, response) 93 | return Response(content=response_body, headers=response_headers) 94 | -------------------------------------------------------------------------------- /modules/iidx/iidx29gamesystem.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local2", tags=["local2"]) 8 | router.model_whitelist = ["LDJ"] 9 | 10 | 11 | @router.post("/{gameinfo}/IIDX29gameSystem/systemInfo") 12 | async def iidx29gamesystem_systeminfo(request: Request): 13 | request_info = await core_process_request(request) 14 | 15 | unlock = () # (28008, 28065, 28073, 28088, 28089, 29027, 29094, 29095) 16 | sp_dp = (0, 1) 17 | 18 | response = E.response( 19 | E.IIDX29gameSystem( 20 | E.arena_schedule( 21 | E.phase(2, __type="u8"), 22 | E.start(1605784800, __type="u32"), 23 | E.end(1605871200, __type="u32"), 24 | ), 25 | E.CommonBossPhase(val=0), 26 | E.Event1InternalPhase(val=0), 27 | E.ExtraBossEventPhase(val=0), 28 | E.isNewSongAnother12OpenFlg(val=1), 29 | E.gradeOpenPhase(val=2), 30 | E.isEiseiOpenFlg(val=1), 31 | E.WorldTourismOpenList(val=1), 32 | E.BPLBattleOpenPhase(val=2), 33 | *[ 34 | E.music_open( 35 | E.music_id(s, __type="s32"), 36 | E.kind(0, __type="s32"), 37 | ) 38 | for s in unlock 39 | ], 40 | *[ 41 | E.arena_reward( 42 | E.index(unlock.index(s), __type="s32"), 43 | E.cube_num((unlock.index(s) + 1) * 50, __type="s32"), 44 | E.kind(0, __type="s32"), 45 | E.value(s, __type="str"), 46 | ) 47 | for s in unlock 48 | ], 49 | *[ 50 | E.arena_music_difficult( 51 | E.play_style(s, __type="s32"), 52 | E.arena_class(-1, __type="s32"), 53 | E.low_difficult(1, __type="s32"), 54 | E.high_difficult(12, __type="s32"), 55 | E.is_leggendaria(1, __type="bool"), 56 | E.force_music_list_id(0, __type="s32"), 57 | ) 58 | for s in sp_dp 59 | ], 60 | *[ 61 | E.arena_cpu_define( 62 | E.play_style(s, __type="s32"), 63 | E.arena_class(-1, __type="s32"), 64 | E.grade_id(18, __type="s32"), 65 | E.low_music_difficult(8, __type="s32"), 66 | E.high_music_difficult(12, __type="s32"), 67 | E.is_leggendaria(0, __type="bool"), 68 | ) 69 | for s in sp_dp 70 | ], 71 | *[ 72 | E.maching_class_range( 73 | E.play_style(s[0], __type="s32"), 74 | E.matching_class(s[1], __type="s32"), 75 | E.low_arena_class(0, __type="s32"), 76 | E.high_arena_class(19, __type="s32"), 77 | ) 78 | for s in ((0, 2), (0, 1), (1, 2), (1, 1)) 79 | ], 80 | *[ 81 | E.arena_force_music( 82 | E.play_style(s, __type="s32"), 83 | E.force_music_list_id(0, __type="s32"), 84 | E.index(0, __type="s32"), 85 | E.music_id(1000, __type="s32"), 86 | E.note_grade(0, __type="s32"), 87 | E.is_active(s, __type="bool"), 88 | ) 89 | for s in sp_dp 90 | ], 91 | ) 92 | ) 93 | 94 | response_body, response_headers = await core_prepare_response(request, response) 95 | return Response(content=response_body, headers=response_headers) 96 | -------------------------------------------------------------------------------- /modules/iidx/iidx29grade.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from tinydb import Query, where 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | from core_database import get_db 9 | 10 | router = APIRouter(prefix="/local2", tags=["local2"]) 11 | router.model_whitelist = ["LDJ"] 12 | 13 | 14 | def get_profile(iidx_id): 15 | return get_db().table("iidx_profile").get(where("iidx_id") == iidx_id) 16 | 17 | 18 | @router.post("/{gameinfo}/IIDX29grade/raised") 19 | async def iidx29grade_raised(request: Request): 20 | request_info = await core_process_request(request) 21 | game_version = request_info["game_version"] 22 | 23 | timestamp = time.time() 24 | 25 | iidx_id = int(request_info["root"][0].attrib["iidxid"]) 26 | achi = int(request_info["root"][0].attrib["achi"]) 27 | cstage = int(request_info["root"][0].attrib["cstage"]) 28 | gid = int(request_info["root"][0].attrib["gid"]) 29 | gtype = int(request_info["root"][0].attrib["gtype"]) 30 | is_ex = int(request_info["root"][0].attrib["is_ex"]) 31 | is_mirror = int(request_info["root"][0].attrib["is_mirror"]) 32 | 33 | db = get_db() 34 | db.table("iidx_class").insert( 35 | { 36 | "timestamp": timestamp, 37 | "game_version": game_version, 38 | "iidx_id": iidx_id, 39 | "achi": achi, 40 | "cstage": cstage, 41 | "gid": gid, 42 | "gtype": gtype, 43 | "is_ex": is_ex, 44 | "is_mirror": is_mirror, 45 | }, 46 | ) 47 | 48 | profile = get_profile(iidx_id) 49 | game_profile = profile["version"].get(str(game_version), {}) 50 | 51 | best_class = db.table("iidx_class_best").get( 52 | (where("iidx_id") == iidx_id) 53 | & (where("game_version") == game_version) 54 | & (where("gid") == gid) 55 | & (where("gtype") == gtype) 56 | ) 57 | 58 | best_class = {} if best_class is None else best_class 59 | 60 | best_class_data = { 61 | "game_version": game_version, 62 | "iidx_id": iidx_id, 63 | "achi": max(achi, best_class.get("achi", achi)), 64 | "cstage": max(cstage, best_class.get("cstage", cstage)), 65 | "gid": gid, 66 | "gtype": gtype, 67 | "is_ex": is_ex, 68 | "is_mirror": is_mirror, 69 | } 70 | 71 | db.table("iidx_class_best").upsert( 72 | best_class_data, 73 | (where("iidx_id") == iidx_id) 74 | & (where("game_version") == game_version) 75 | & (where("gid") == gid) 76 | & (where("gtype") == gtype), 77 | ) 78 | 79 | best_class_plays = db.table("iidx_class_best").search( 80 | (where("game_version") == game_version) & (where("iidx_id") == iidx_id) 81 | ) 82 | 83 | grades = [] 84 | for record in best_class_plays: 85 | grades.append( 86 | [record["gtype"], record["gid"], record["cstage"], record["achi"]] 87 | ) 88 | 89 | game_profile["grade_values"] = grades 90 | 91 | grade_sp = db.table("iidx_class_best").search( 92 | (where("iidx_id") == iidx_id) & (where("gtype") == 0) & (where("cstage") == 4) 93 | ) 94 | 95 | game_profile["grade_single"] = max([x["gid"] for x in grade_sp], default=-1) 96 | 97 | grade_dp = db.table("iidx_class_best").search( 98 | (where("iidx_id") == iidx_id) & (where("gtype") == 1) & (where("cstage") == 4) 99 | ) 100 | 101 | game_profile["grade_double"] = max([x["gid"] for x in grade_dp], default=-1) 102 | 103 | profile["version"][str(game_version)] = game_profile 104 | 105 | db.table("iidx_profile").upsert(profile, where("game_version") == game_version) 106 | 107 | response = E.response(E.IIDX29grade(pnum=1)) 108 | 109 | response_body, response_headers = await core_prepare_response(request, response) 110 | return Response(content=response_body, headers=response_headers) 111 | -------------------------------------------------------------------------------- /modules/iidx/iidx32grade.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from tinydb import Query, where 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | from core_database import get_db 9 | 10 | router = APIRouter(prefix="/local", tags=["local"]) 11 | router.model_whitelist = ["LDJ"] 12 | 13 | 14 | def get_profile(iidx_id): 15 | return get_db().table("iidx_profile").get(where("iidx_id") == iidx_id) 16 | 17 | 18 | @router.post("/{gameinfo}/IIDX32grade/raised") 19 | async def iidx32grade_raised(request: Request): 20 | request_info = await core_process_request(request) 21 | game_version = request_info["game_version"] 22 | 23 | timestamp = time.time() 24 | 25 | iidx_id = int(request_info["root"][0].attrib["iidxid"]) 26 | achi = int(request_info["root"][0].attrib["achi"]) 27 | cstage = int(request_info["root"][0].attrib["cstage"]) 28 | gid = int(request_info["root"][0].attrib["gid"]) 29 | gtype = int(request_info["root"][0].attrib["gtype"]) 30 | is_ex = int(request_info["root"][0].attrib["is_ex"]) 31 | is_mirror = int(request_info["root"][0].attrib["is_mirror"]) 32 | 33 | db = get_db() 34 | db.table("iidx_class").insert( 35 | { 36 | "timestamp": timestamp, 37 | "game_version": game_version, 38 | "iidx_id": iidx_id, 39 | "achi": achi, 40 | "cstage": cstage, 41 | "gid": gid, 42 | "gtype": gtype, 43 | "is_ex": is_ex, 44 | "is_mirror": is_mirror, 45 | }, 46 | ) 47 | 48 | profile = get_profile(iidx_id) 49 | game_profile = profile["version"].get(str(game_version), {}) 50 | 51 | best_class = db.table("iidx_class_best").get( 52 | (where("iidx_id") == iidx_id) 53 | & (where("game_version") == game_version) 54 | & (where("gid") == gid) 55 | & (where("gtype") == gtype) 56 | ) 57 | 58 | best_class = {} if best_class is None else best_class 59 | 60 | best_class_data = { 61 | "game_version": game_version, 62 | "iidx_id": iidx_id, 63 | "achi": max(achi, best_class.get("achi", achi)), 64 | "cstage": max(cstage, best_class.get("cstage", cstage)), 65 | "gid": gid, 66 | "gtype": gtype, 67 | "is_ex": is_ex, 68 | "is_mirror": is_mirror, 69 | } 70 | 71 | db.table("iidx_class_best").upsert( 72 | best_class_data, 73 | (where("iidx_id") == iidx_id) 74 | & (where("game_version") == game_version) 75 | & (where("gid") == gid) 76 | & (where("gtype") == gtype), 77 | ) 78 | 79 | best_class_plays = db.table("iidx_class_best").search( 80 | (where("game_version") == game_version) & (where("iidx_id") == iidx_id) 81 | ) 82 | 83 | grades = [] 84 | for record in best_class_plays: 85 | grades.append( 86 | [record["gtype"], record["gid"], record["cstage"], record["achi"]] 87 | ) 88 | 89 | game_profile["grade_values"] = grades 90 | 91 | grade_sp = db.table("iidx_class_best").search( 92 | (where("game_version") == game_version) 93 | & (where("iidx_id") == iidx_id) 94 | & (where("gtype") == 0) 95 | & (where("cstage") == 4) 96 | ) 97 | 98 | game_profile["grade_single"] = max([x["gid"] for x in grade_sp], default=-1) 99 | 100 | grade_dp = db.table("iidx_class_best").search( 101 | (where("game_version") == game_version) 102 | & (where("iidx_id") == iidx_id) 103 | & (where("gtype") == 1) 104 | & (where("cstage") == 4) 105 | ) 106 | 107 | game_profile["grade_double"] = max([x["gid"] for x in grade_dp], default=-1) 108 | 109 | profile["version"][str(game_version)] = game_profile 110 | 111 | db.table("iidx_profile").upsert(profile, where("game_version") == game_version) 112 | 113 | response = E.response(E.IIDX32grade(pnum=1)) 114 | 115 | response_body, response_headers = await core_prepare_response(request, response) 116 | return Response(content=response_body, headers=response_headers) 117 | -------------------------------------------------------------------------------- /modules/iidx/iidx33grade.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from tinydb import Query, where 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | from core_database import get_db 9 | 10 | router = APIRouter(prefix="/local", tags=["local"]) 11 | router.model_whitelist = ["LDJ"] 12 | 13 | 14 | def get_profile(iidx_id): 15 | return get_db().table("iidx_profile").get(where("iidx_id") == iidx_id) 16 | 17 | 18 | @router.post("/{gameinfo}/IIDX33grade/raised") 19 | async def iidx33grade_raised(request: Request): 20 | request_info = await core_process_request(request) 21 | game_version = request_info["game_version"] 22 | 23 | timestamp = time.time() 24 | 25 | iidx_id = int(request_info["root"][0].attrib["iidxid"]) 26 | achi = int(request_info["root"][0].attrib["achi"]) 27 | cstage = int(request_info["root"][0].attrib["cstage"]) 28 | gid = int(request_info["root"][0].attrib["gid"]) 29 | gtype = int(request_info["root"][0].attrib["gtype"]) 30 | is_ex = int(request_info["root"][0].attrib["is_ex"]) 31 | is_mirror = int(request_info["root"][0].attrib["is_mirror"]) 32 | 33 | db = get_db() 34 | db.table("iidx_class").insert( 35 | { 36 | "timestamp": timestamp, 37 | "game_version": game_version, 38 | "iidx_id": iidx_id, 39 | "achi": achi, 40 | "cstage": cstage, 41 | "gid": gid, 42 | "gtype": gtype, 43 | "is_ex": is_ex, 44 | "is_mirror": is_mirror, 45 | }, 46 | ) 47 | 48 | profile = get_profile(iidx_id) 49 | game_profile = profile["version"].get(str(game_version), {}) 50 | 51 | best_class = db.table("iidx_class_best").get( 52 | (where("iidx_id") == iidx_id) 53 | & (where("game_version") == game_version) 54 | & (where("gid") == gid) 55 | & (where("gtype") == gtype) 56 | ) 57 | 58 | best_class = {} if best_class is None else best_class 59 | 60 | best_class_data = { 61 | "game_version": game_version, 62 | "iidx_id": iidx_id, 63 | "achi": max(achi, best_class.get("achi", achi)), 64 | "cstage": max(cstage, best_class.get("cstage", cstage)), 65 | "gid": gid, 66 | "gtype": gtype, 67 | "is_ex": is_ex, 68 | "is_mirror": is_mirror, 69 | } 70 | 71 | db.table("iidx_class_best").upsert( 72 | best_class_data, 73 | (where("iidx_id") == iidx_id) 74 | & (where("game_version") == game_version) 75 | & (where("gid") == gid) 76 | & (where("gtype") == gtype), 77 | ) 78 | 79 | best_class_plays = db.table("iidx_class_best").search( 80 | (where("game_version") == game_version) & (where("iidx_id") == iidx_id) 81 | ) 82 | 83 | grades = [] 84 | for record in best_class_plays: 85 | grades.append( 86 | [record["gtype"], record["gid"], record["cstage"], record["achi"]] 87 | ) 88 | 89 | game_profile["grade_values"] = grades 90 | 91 | grade_sp = db.table("iidx_class_best").search( 92 | (where("game_version") == game_version) 93 | & (where("iidx_id") == iidx_id) 94 | & (where("gtype") == 0) 95 | & (where("cstage") == 4) 96 | ) 97 | 98 | game_profile["grade_single"] = max([x["gid"] for x in grade_sp], default=-1) 99 | 100 | grade_dp = db.table("iidx_class_best").search( 101 | (where("game_version") == game_version) 102 | & (where("iidx_id") == iidx_id) 103 | & (where("gtype") == 1) 104 | & (where("cstage") == 4) 105 | ) 106 | 107 | game_profile["grade_double"] = max([x["gid"] for x in grade_dp], default=-1) 108 | 109 | profile["version"][str(game_version)] = game_profile 110 | 111 | db.table("iidx_profile").upsert(profile, where("game_version") == game_version) 112 | 113 | response = E.response(E.IIDX33grade(pnum=1)) 114 | 115 | response_body, response_headers = await core_prepare_response(request, response) 116 | return Response(content=response_body, headers=response_headers) 117 | -------------------------------------------------------------------------------- /modules/iidx/iidx30grade.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from tinydb import Query, where 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | from core_database import get_db 9 | 10 | router = APIRouter(prefix="/local2", tags=["local2"]) 11 | router.model_whitelist = ["LDJ"] 12 | 13 | 14 | def get_profile(iidx_id): 15 | return get_db().table("iidx_profile").get(where("iidx_id") == iidx_id) 16 | 17 | 18 | @router.post("/{gameinfo}/IIDX30grade/raised") 19 | async def iidx30grade_raised(request: Request): 20 | request_info = await core_process_request(request) 21 | game_version = request_info["game_version"] 22 | 23 | timestamp = time.time() 24 | 25 | iidx_id = int(request_info["root"][0].attrib["iidxid"]) 26 | achi = int(request_info["root"][0].attrib["achi"]) 27 | cstage = int(request_info["root"][0].attrib["cstage"]) 28 | gid = int(request_info["root"][0].attrib["gid"]) 29 | gtype = int(request_info["root"][0].attrib["gtype"]) 30 | is_ex = int(request_info["root"][0].attrib["is_ex"]) 31 | is_mirror = int(request_info["root"][0].attrib["is_mirror"]) 32 | 33 | db = get_db() 34 | db.table("iidx_class").insert( 35 | { 36 | "timestamp": timestamp, 37 | "game_version": game_version, 38 | "iidx_id": iidx_id, 39 | "achi": achi, 40 | "cstage": cstage, 41 | "gid": gid, 42 | "gtype": gtype, 43 | "is_ex": is_ex, 44 | "is_mirror": is_mirror, 45 | }, 46 | ) 47 | 48 | profile = get_profile(iidx_id) 49 | game_profile = profile["version"].get(str(game_version), {}) 50 | 51 | best_class = db.table("iidx_class_best").get( 52 | (where("iidx_id") == iidx_id) 53 | & (where("game_version") == game_version) 54 | & (where("gid") == gid) 55 | & (where("gtype") == gtype) 56 | ) 57 | 58 | best_class = {} if best_class is None else best_class 59 | 60 | best_class_data = { 61 | "game_version": game_version, 62 | "iidx_id": iidx_id, 63 | "achi": max(achi, best_class.get("achi", achi)), 64 | "cstage": max(cstage, best_class.get("cstage", cstage)), 65 | "gid": gid, 66 | "gtype": gtype, 67 | "is_ex": is_ex, 68 | "is_mirror": is_mirror, 69 | } 70 | 71 | db.table("iidx_class_best").upsert( 72 | best_class_data, 73 | (where("iidx_id") == iidx_id) 74 | & (where("game_version") == game_version) 75 | & (where("gid") == gid) 76 | & (where("gtype") == gtype), 77 | ) 78 | 79 | best_class_plays = db.table("iidx_class_best").search( 80 | (where("game_version") == game_version) & (where("iidx_id") == iidx_id) 81 | ) 82 | 83 | grades = [] 84 | for record in best_class_plays: 85 | grades.append( 86 | [record["gtype"], record["gid"], record["cstage"], record["achi"]] 87 | ) 88 | 89 | game_profile["grade_values"] = grades 90 | 91 | grade_sp = db.table("iidx_class_best").search( 92 | (where("game_version") == game_version) 93 | & (where("iidx_id") == iidx_id) 94 | & (where("gtype") == 0) 95 | & (where("cstage") == 4) 96 | ) 97 | 98 | game_profile["grade_single"] = max([x["gid"] for x in grade_sp], default=-1) 99 | 100 | grade_dp = db.table("iidx_class_best").search( 101 | (where("game_version") == game_version) 102 | & (where("iidx_id") == iidx_id) 103 | & (where("gtype") == 1) 104 | & (where("cstage") == 4) 105 | ) 106 | 107 | game_profile["grade_double"] = max([x["gid"] for x in grade_dp], default=-1) 108 | 109 | profile["version"][str(game_version)] = game_profile 110 | 111 | db.table("iidx_profile").upsert(profile, where("game_version") == game_version) 112 | 113 | response = E.response(E.IIDX30grade(pnum=1)) 114 | 115 | response_body, response_headers = await core_prepare_response(request, response) 116 | return Response(content=response_body, headers=response_headers) 117 | -------------------------------------------------------------------------------- /modules/iidx/iidx31grade.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from tinydb import Query, where 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | from core_database import get_db 9 | 10 | router = APIRouter(prefix="/local2", tags=["local2"]) 11 | router.model_whitelist = ["LDJ"] 12 | 13 | 14 | def get_profile(iidx_id): 15 | return get_db().table("iidx_profile").get(where("iidx_id") == iidx_id) 16 | 17 | 18 | @router.post("/{gameinfo}/IIDX31grade/raised") 19 | async def iidx31grade_raised(request: Request): 20 | request_info = await core_process_request(request) 21 | game_version = request_info["game_version"] 22 | 23 | timestamp = time.time() 24 | 25 | iidx_id = int(request_info["root"][0].attrib["iidxid"]) 26 | achi = int(request_info["root"][0].attrib["achi"]) 27 | cstage = int(request_info["root"][0].attrib["cstage"]) 28 | gid = int(request_info["root"][0].attrib["gid"]) 29 | gtype = int(request_info["root"][0].attrib["gtype"]) 30 | is_ex = int(request_info["root"][0].attrib["is_ex"]) 31 | is_mirror = int(request_info["root"][0].attrib["is_mirror"]) 32 | 33 | db = get_db() 34 | db.table("iidx_class").insert( 35 | { 36 | "timestamp": timestamp, 37 | "game_version": game_version, 38 | "iidx_id": iidx_id, 39 | "achi": achi, 40 | "cstage": cstage, 41 | "gid": gid, 42 | "gtype": gtype, 43 | "is_ex": is_ex, 44 | "is_mirror": is_mirror, 45 | }, 46 | ) 47 | 48 | profile = get_profile(iidx_id) 49 | game_profile = profile["version"].get(str(game_version), {}) 50 | 51 | best_class = db.table("iidx_class_best").get( 52 | (where("iidx_id") == iidx_id) 53 | & (where("game_version") == game_version) 54 | & (where("gid") == gid) 55 | & (where("gtype") == gtype) 56 | ) 57 | 58 | best_class = {} if best_class is None else best_class 59 | 60 | best_class_data = { 61 | "game_version": game_version, 62 | "iidx_id": iidx_id, 63 | "achi": max(achi, best_class.get("achi", achi)), 64 | "cstage": max(cstage, best_class.get("cstage", cstage)), 65 | "gid": gid, 66 | "gtype": gtype, 67 | "is_ex": is_ex, 68 | "is_mirror": is_mirror, 69 | } 70 | 71 | db.table("iidx_class_best").upsert( 72 | best_class_data, 73 | (where("iidx_id") == iidx_id) 74 | & (where("game_version") == game_version) 75 | & (where("gid") == gid) 76 | & (where("gtype") == gtype), 77 | ) 78 | 79 | best_class_plays = db.table("iidx_class_best").search( 80 | (where("game_version") == game_version) & (where("iidx_id") == iidx_id) 81 | ) 82 | 83 | grades = [] 84 | for record in best_class_plays: 85 | grades.append( 86 | [record["gtype"], record["gid"], record["cstage"], record["achi"]] 87 | ) 88 | 89 | game_profile["grade_values"] = grades 90 | 91 | grade_sp = db.table("iidx_class_best").search( 92 | (where("game_version") == game_version) 93 | & (where("iidx_id") == iidx_id) 94 | & (where("gtype") == 0) 95 | & (where("cstage") == 4) 96 | ) 97 | 98 | game_profile["grade_single"] = max([x["gid"] for x in grade_sp], default=-1) 99 | 100 | grade_dp = db.table("iidx_class_best").search( 101 | (where("game_version") == game_version) 102 | & (where("iidx_id") == iidx_id) 103 | & (where("gtype") == 1) 104 | & (where("cstage") == 4) 105 | ) 106 | 107 | game_profile["grade_double"] = max([x["gid"] for x in grade_dp], default=-1) 108 | 109 | profile["version"][str(game_version)] = game_profile 110 | 111 | db.table("iidx_profile").upsert(profile, where("game_version") == game_version) 112 | 113 | response = E.response(E.IIDX31grade(pnum=1)) 114 | 115 | response_body, response_headers = await core_prepare_response(request, response) 116 | return Response(content=response_body, headers=response_headers) 117 | -------------------------------------------------------------------------------- /modules/iidx/iidx33shop.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | from tinydb import Query, where 5 | 6 | from core_common import core_process_request, core_prepare_response, E 7 | from core_database import get_db 8 | 9 | router = APIRouter(prefix="/local", tags=["local"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | @router.post("/{gameinfo}/IIDX33shop/getname") 14 | async def iidx33shop_getname(request: Request): 15 | request_info = await core_process_request(request) 16 | pcbid = request_info["root"].attrib["srcid"] 17 | 18 | op = get_db().table("shop").get(where("pcbid") == pcbid) 19 | op = {} if op is None else op 20 | 21 | response = E.response( 22 | E.IIDX33shop( 23 | cls_opt=0, 24 | opname=op.get("opname", config.arcade), 25 | pid=13, 26 | ) 27 | ) 28 | 29 | response_body, response_headers = await core_prepare_response(request, response) 30 | return Response(content=response_body, headers=response_headers) 31 | 32 | 33 | @router.post("/{gameinfo}/IIDX33shop/savename") 34 | async def iidx33shop_savename(request: Request): 35 | request_info = await core_process_request(request) 36 | pcbid = request_info["root"].attrib["srcid"] 37 | opname = request_info["root"][0].attrib["opname"] 38 | 39 | shop_info = { 40 | "pcbid": pcbid, 41 | "opname": opname, 42 | } 43 | 44 | get_db().table("shop").upsert(shop_info, where("pcbid") == pcbid) 45 | 46 | response = E.response(E.IIDX33shop()) 47 | 48 | response_body, response_headers = await core_prepare_response(request, response) 49 | return Response(content=response_body, headers=response_headers) 50 | 51 | 52 | @router.post("/{gameinfo}/IIDX33shop/getconvention") 53 | async def iidx33shop_getconvention(request: Request): 54 | request_info = await core_process_request(request) 55 | 56 | response = E.response( 57 | E.IIDX33shop( 58 | E.valid(1, __type="bool"), 59 | music_0=-1, 60 | music_1=-1, 61 | music_2=-1, 62 | music_3=-1, 63 | start_time=0, 64 | end_time=0, 65 | ) 66 | ) 67 | 68 | response_body, response_headers = await core_prepare_response(request, response) 69 | return Response(content=response_body, headers=response_headers) 70 | 71 | 72 | @router.post("/{gameinfo}/IIDX33shop/sentinfo") 73 | async def iidx33shop_sentinfo(request: Request): 74 | request_info = await core_process_request(request) 75 | 76 | response = E.response(E.IIDX33shop()) 77 | 78 | response_body, response_headers = await core_prepare_response(request, response) 79 | return Response(content=response_body, headers=response_headers) 80 | 81 | 82 | @router.post("/{gameinfo}/IIDX33shop/sendescapepackageinfo") 83 | async def iidx33shop_sendescapepackageinfo(request: Request): 84 | request_info = await core_process_request(request) 85 | 86 | response = E.response(E.IIDX33shop(expire=1200)) 87 | 88 | response_body, response_headers = await core_prepare_response(request, response) 89 | return Response(content=response_body, headers=response_headers) 90 | 91 | @router.post("/{gameinfo}/IIDX33shop/getclosingtime") 92 | async def iidx33shop_getclosingtime(request: Request): 93 | request_info = await core_process_request(request) 94 | 95 | response = E.response( 96 | E.IIDX33shop( 97 | E.exist(1, __type="bool"), 98 | *[E.week(cls_opt=0, week=i) for i in range(7)] 99 | ) 100 | ) 101 | 102 | response_body, response_headers = await core_prepare_response(request, response) 103 | return Response(content=response_body, headers=response_headers) 104 | 105 | @router.post("/{gameinfo}/IIDX33shop/saveclosingtime") 106 | async def iidx33shop_saveclosingtime(request: Request): 107 | request_info = await core_process_request(request) 108 | 109 | response = E.response(E.IIDX33shop()) 110 | 111 | response_body, response_headers = await core_prepare_response(request, response) 112 | return Response(content=response_body, headers=response_headers) 113 | -------------------------------------------------------------------------------- /modules/core/cardmng.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | from tinydb import Query, where 3 | 4 | from core_common import core_process_request, core_prepare_response, E 5 | from core_database import get_db 6 | 7 | router = APIRouter(prefix="/core", tags=["cardmng"]) 8 | 9 | 10 | def get_target_table(game_id): 11 | target_table = { 12 | "LDJ": "iidx_profile", 13 | "MDX": "ddr_profile", 14 | "KFC": "sdvx_profile", 15 | "M32": "gitadora_profile", 16 | "PAN": "nostalgia_profile", 17 | "REC": "dancerush_profile", 18 | "JDZ": "iidx_profile", 19 | "KDZ": "iidx_profile", 20 | } 21 | 22 | return target_table[game_id] 23 | 24 | 25 | def get_profile(game_id, cid): 26 | target_table = get_target_table(game_id) 27 | profile = get_db().table(target_table).get(where("card") == cid) 28 | 29 | if profile is None: 30 | profile = { 31 | "card": cid, 32 | "version": {}, 33 | } 34 | 35 | return profile 36 | 37 | 38 | def get_game_profile(game_id, game_version, cid): 39 | profile = get_profile(game_id, cid) 40 | 41 | if str(game_version) not in profile["version"]: 42 | profile["version"][str(game_version)] = {} 43 | 44 | return profile["version"][str(game_version)] 45 | 46 | 47 | def create_profile(game_id, game_version, cid, pin): 48 | target_table = get_target_table(game_id) 49 | profile = get_profile(game_id, cid) 50 | 51 | profile["pin"] = pin 52 | 53 | get_db().table(target_table).upsert(profile, where("card") == cid) 54 | 55 | 56 | @router.post("/{gameinfo}/cardmng/authpass") 57 | async def cardmng_authpass(request: Request): 58 | request_info = await core_process_request(request) 59 | 60 | cid = request_info["root"][0].attrib["refid"] 61 | passwd = request_info["root"][0].attrib["pass"] 62 | 63 | profile = get_profile(request_info["model"], cid) 64 | if profile is None or passwd != profile.get("pin", None): 65 | status = 116 66 | else: 67 | status = 0 68 | 69 | response = E.response(E.authpass(status=status)) 70 | 71 | response_body, response_headers = await core_prepare_response(request, response) 72 | return Response(content=response_body, headers=response_headers) 73 | 74 | 75 | @router.post("/{gameinfo}/cardmng/bindmodel") 76 | async def cardmng_bindmodel(request: Request): 77 | request_info = await core_process_request(request) 78 | 79 | response = E.response(E.bindmodel(dataid=1)) 80 | 81 | response_body, response_headers = await core_prepare_response(request, response) 82 | return Response(content=response_body, headers=response_headers) 83 | 84 | 85 | @router.post("/{gameinfo}/cardmng/getrefid") 86 | async def cardmng_getrefid(request: Request): 87 | request_info = await core_process_request(request) 88 | 89 | cid = request_info["root"][0].attrib["cardid"] 90 | passwd = request_info["root"][0].attrib["passwd"] 91 | 92 | create_profile(request_info["model"], request_info["game_version"], cid, passwd) 93 | 94 | response = E.response( 95 | E.getrefid( 96 | dataid=cid, 97 | refid=cid, 98 | ) 99 | ) 100 | 101 | response_body, response_headers = await core_prepare_response(request, response) 102 | return Response(content=response_body, headers=response_headers) 103 | 104 | 105 | @router.post("/{gameinfo}/cardmng/inquire") 106 | async def cardmng_inquire(request: Request): 107 | request_info = await core_process_request(request) 108 | 109 | cid = request_info["root"][0].attrib["cardid"] 110 | 111 | profile = get_game_profile(request_info["model"], request_info["game_version"], cid) 112 | if profile: 113 | binded = 1 114 | newflag = 0 115 | status = 0 116 | else: 117 | binded = 0 118 | newflag = 1 119 | status = 112 120 | 121 | response = E.response( 122 | E.inquire( 123 | dataid=cid, 124 | ecflag=1, 125 | expired=0, 126 | binded=binded, 127 | newflag=newflag, 128 | refid=cid, 129 | status=status, 130 | ) 131 | ) 132 | 133 | response_body, response_headers = await core_prepare_response(request, response) 134 | return Response(content=response_body, headers=response_headers) 135 | -------------------------------------------------------------------------------- /modules/core/eacoin.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | from fastapi import APIRouter, Request, Response 4 | from tinydb import where 5 | 6 | from core_common import core_process_request, core_prepare_response, E 7 | from core_database import get_db 8 | 9 | router = APIRouter(prefix="/core", tags=["eacoin"]) 10 | 11 | sessid = 0 12 | payments = {} 13 | 14 | @router.post("/{gameinfo}/eacoin/checkin") 15 | async def eacoin_checkin(request: Request): 16 | request_info = await core_process_request(request) 17 | pcbid = request_info["root"].attrib["srcid"] 18 | cardid = request_info["root"][0].find("cardid").text 19 | 20 | op = get_db().table("shop").get(where("pcbid") == pcbid) 21 | op = {} if op is None else op 22 | 23 | bal = get_db().table("paseli").get(where("cardid") == cardid) 24 | bal = {} if bal is None else bal 25 | 26 | global sessid 27 | sessid += 1 28 | payments[sessid] = cardid 29 | 30 | response = E.response( 31 | E.eacoin( 32 | E.sequence(1, __type="s16"), 33 | E.acstatus(1, __type="u8"), 34 | E.acid(1, __type="str"), 35 | E.acname(op.get("opname", config.arcade), __type="str"), 36 | E.balance(bal.get("balance", config.paseli), __type="s32"), 37 | E.sessid(sessid, __type="str"), 38 | E.inshopcharge(1, __type="u8"), 39 | ) 40 | ) 41 | 42 | response_body, response_headers = await core_prepare_response(request, response) 43 | return Response(content=response_body, headers=response_headers) 44 | 45 | 46 | @router.post("/{gameinfo}/eacoin/checkout") 47 | async def eacoin_checkout(request: Request): 48 | request_info = await core_process_request(request) 49 | 50 | response = E.response(E.eacoin()) 51 | 52 | response_body, response_headers = await core_prepare_response(request, response) 53 | return Response(content=response_body, headers=response_headers) 54 | 55 | 56 | @router.post("/{gameinfo}/eacoin/consume") 57 | async def eacoin_consume(request: Request): 58 | request_info = await core_process_request(request) 59 | sessid = int(request_info["root"][0].find("sessid").text) 60 | payment = int(request_info["root"][0].find("payment").text) 61 | 62 | cardid = payments.get(sessid, None) 63 | 64 | # fallback if server is restarted mid-round for IIDX movie or gacha purchases 65 | if cardid == None: 66 | response = E.response( 67 | E.eacoin( 68 | E.acstatus(0, __type="u8"), 69 | E.autocharge(0, __type="u8"), 70 | E.balance(config.paseli, __type="s32"), 71 | ) 72 | ) 73 | 74 | response_body, response_headers = await core_prepare_response(request, response) 75 | return Response(content=response_body, headers=response_headers) 76 | 77 | bal = get_db().table("paseli").get(where("cardid") == cardid) 78 | if bal == None: 79 | bal = { 80 | "cardid": cardid, 81 | "balance": config.paseli, 82 | "total_spent": 0, 83 | } 84 | 85 | new_balance = bal["balance"] - payment 86 | 87 | paseli_card = { 88 | "cardid": cardid, 89 | "balance": new_balance, 90 | "total_spent": bal["total_spent"] + payment, 91 | } 92 | 93 | response = E.response( 94 | E.eacoin( 95 | E.acstatus(0, __type="u8"), 96 | E.autocharge(0, __type="u8"), 97 | E.balance(new_balance, __type="s32"), 98 | ) 99 | ) 100 | 101 | if new_balance < 1000 or new_balance > config.paseli: 102 | paseli_card["balance"] = config.paseli 103 | 104 | get_db().table("paseli").upsert(paseli_card, where("cardid") == cardid) 105 | 106 | # del payments[sessid] 107 | 108 | response_body, response_headers = await core_prepare_response(request, response) 109 | return Response(content=response_body, headers=response_headers) 110 | 111 | 112 | @router.post("/{gameinfo}/eacoin/getbalance") 113 | async def eacoin_getbalance(request: Request): 114 | request_info = await core_process_request(request) 115 | 116 | response = E.response( 117 | E.eacoin( 118 | E.acstatus(0, __type="u8"), 119 | E.balance(config.paseli, __type="s32"), 120 | ) 121 | ) 122 | 123 | response_body, response_headers = await core_prepare_response(request, response) 124 | return Response(content=response_body, headers=response_headers) 125 | -------------------------------------------------------------------------------- /modules/gitadora/playablemusic.py: -------------------------------------------------------------------------------- 1 | import lxml.etree as ET 2 | from os import path 3 | 4 | from fastapi import APIRouter, Request, Response 5 | 6 | from core_common import core_process_request, core_prepare_response, E 7 | 8 | router = APIRouter(prefix="/local", tags=["local"]) 9 | router.model_whitelist = ["M32"] 10 | 11 | 12 | @router.post("/{gameinfo}/{ver}_playablemusic/get") 13 | async def gitadora_playablemusic_get(ver: str, request: Request): 14 | request_info = await core_process_request(request) 15 | spec = request_info["spec"] 16 | 17 | if spec in ("A", "B"): 18 | is_delta = False 19 | elif spec in ("C", "D"): 20 | is_delta = True 21 | 22 | # the game freezes if response has no songs 23 | # so make sure there is at least one 24 | # in case mdb isn't supplied 25 | songs = { 26 | 0: { 27 | "xg_diff_list": [ 28 | "0", 29 | "100", 30 | "295", 31 | "395", 32 | "0", 33 | "0", 34 | "0", 35 | "0", 36 | "0", 37 | "0", 38 | "0", 39 | "160", 40 | "490", 41 | "585", 42 | "0", 43 | ], 44 | "contain_stat": ["2", "2"], 45 | "data_ver": 115, 46 | } 47 | } 48 | 49 | if ver == "galaxywave": 50 | short_ver = "gw" 51 | elif ver == "fuzzup": 52 | short_ver = "fz" 53 | elif ver == "highvoltage": 54 | short_ver = "hv" 55 | elif ver == "nextage": 56 | short_ver = "nt" 57 | elif ver == "exchain": 58 | short_ver = "ex" 59 | elif ver == "matixx": 60 | short_ver = "mt" 61 | 62 | if not short_ver: 63 | short_ver = "MISSING_FALLBACK" 64 | 65 | if is_delta == True: 66 | paths = ( 67 | path.join("modules", "gitadora", f"mdb_{short_ver}_delta.xml"), 68 | path.join(f"mdb_{short_ver}_delta.xml"), 69 | ) 70 | else: 71 | paths = ( 72 | path.join("modules", "gitadora", f"mdb_{short_ver}.xml"), 73 | path.join(f"mdb_{short_ver}.xml"), 74 | ) 75 | 76 | for f in paths: 77 | if path.exists(f): 78 | with open(f, "r", encoding="utf-8") as fp: 79 | 80 | tree = ET.parse(fp, ET.XMLParser()) 81 | root = tree.getroot() 82 | 83 | for entry in root: 84 | if entry.tag == "mdb_data": 85 | lvl = entry.find("xg_diff_list").text.split(" ") 86 | if short_ver in ("fz", "hv", "nt", "ex"): 87 | d_ver = int(entry.find("data_ver").text) 88 | else: 89 | d_ver = 115 90 | 91 | mid = entry.find("music_id").text 92 | songs[mid] = {} 93 | songs[mid]["xg_diff_list"] = lvl[:5] + lvl[10:] + lvl[5:10] 94 | songs[mid]["contain_stat"] = entry.find( 95 | "contain_stat" 96 | ).text.split(" ") 97 | songs[mid]["data_ver"] = d_ver 98 | break 99 | 100 | response = E.response( 101 | E( 102 | f"{ver}_playablemusic", 103 | E.hot( 104 | E.major(-1, __type="s32"), 105 | E.minor(-1, __type="s32"), 106 | ), 107 | E.musicinfo( 108 | *[ 109 | E.music( 110 | E.id(s, __type="s32"), 111 | E.cont_gf( 112 | 1 if int(songs[s]["contain_stat"][0]) != 0 else 0, 113 | __type="bool", 114 | ), 115 | E.cont_dm( 116 | 1 if int(songs[s]["contain_stat"][1]) != 0 else 0, 117 | __type="bool", 118 | ), 119 | E.is_secret(0, __type="bool"), 120 | E.is_hot( 121 | 1 122 | if (int(songs[s]["contain_stat"][0]) & 1) 123 | or (int(songs[s]["contain_stat"][1]) & 1) 124 | else 0, 125 | __type="bool", 126 | ), 127 | E.data_ver(songs[s]["data_ver"], __type="s32"), 128 | E.seq_release_state(1, __type="s32"), 129 | E.diff(songs[s]["xg_diff_list"], __type="u16"), 130 | ) 131 | for s in songs 132 | ], 133 | nr=len(songs), 134 | ), 135 | ) 136 | ) 137 | 138 | response_body, response_headers = await core_prepare_response(request, response) 139 | return Response(content=response_body, headers=response_headers) 140 | -------------------------------------------------------------------------------- /utils/db/import_ddr_spice_automap.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import xml.etree.ElementTree as ET 3 | 4 | from tinydb import TinyDB, where 5 | from tinydb.middlewares import CachingMiddleware 6 | from tinydb.storages import JSONStorage 7 | 8 | 9 | def main(automap_xml, version, monkey_db, ddr_id): 10 | storage = CachingMiddleware(JSONStorage) 11 | storage.WRITE_CACHE_SIZE = 5000 12 | 13 | db = TinyDB( 14 | monkey_db, 15 | indent=2, 16 | encoding="utf-8", 17 | ensure_ascii=False, 18 | storage=storage, 19 | ) 20 | 21 | ddr_id = int(ddr_id.replace("-", "")) 22 | 23 | profile = db.table("ddr_profile").get(where("ddr_id") == ddr_id) 24 | if profile == None: 25 | raise SystemExit(f"ERROR: DDR profile {ddr_id} not in {monkey_db}") 26 | 27 | game_version = 19 28 | if profile["version"].get(str(game_version), None) == None: 29 | raise SystemExit( 30 | f"ERROR: DDR profile {ddr_id} version {game_version} not in {monkey_db}" 31 | ) 32 | 33 | scores = [] 34 | 35 | with open(automap_xml, "rb") as fp: 36 | automap_0 = fp.read().split(b"\n\n") 37 | 38 | if version == 19: 39 | playerdata = "playerdata" 40 | else: 41 | playerdata = "playerdata_2" 42 | 43 | scores_xml = False 44 | for xml in automap_0: 45 | tree = ET.ElementTree(ET.fromstring(xml.decode(encoding="shift-jis"))) 46 | root = tree.getroot() 47 | if scores_xml: 48 | for music in root.findall(f"{playerdata}/music"): 49 | mcode = int(music.find("mcode").text) 50 | for difficulty, chart in enumerate(music.findall("note")): 51 | if int(chart.find("count").text) > 0: 52 | rank = int(chart.find("rank").text) 53 | clearkind = int(chart.find("clearkind").text) 54 | score = int(chart.find("score").text) 55 | scores.append([mcode, difficulty, rank, clearkind, score]) 56 | break 57 | else: 58 | try: 59 | if root.find(f"{playerdata}/data/mode").text == "userload": 60 | if len(root.find(f"{playerdata}/data/refid").text) == 16: 61 | scores_xml = True 62 | except AttributeError: 63 | continue 64 | 65 | total_count = len(scores) 66 | 67 | if total_count == 0: 68 | raise SystemExit("ERROR: No scores to import") 69 | 70 | for s in scores: 71 | mcode = s[0] 72 | difficulty = s[1] 73 | rank = s[2] 74 | lamp = s[3] 75 | score = s[4] 76 | exscore = 0 77 | 78 | print( 79 | f"mcode: {mcode}, difficulty: {difficulty}, rank: {rank}, score: {score}, lamp: {lamp}" 80 | ) 81 | 82 | best = db.table("ddr_scores_best").get( 83 | (where("ddr_id") == ddr_id) 84 | & (where("game_version") == game_version) 85 | & (where("mcode") == mcode) 86 | & (where("difficulty") == difficulty) 87 | ) 88 | best = {} if best is None else best 89 | 90 | best_score_data = { 91 | "game_version": game_version, 92 | "ddr_id": ddr_id, 93 | "playstyle": 0 if difficulty < 5 else 1, 94 | "mcode": mcode, 95 | "difficulty": difficulty, 96 | "rank": min(rank, best.get("rank", rank)), 97 | "lamp": max(lamp, best.get("lamp", lamp)), 98 | "score": max(score, best.get("score", score)), 99 | "exscore": max(exscore, best.get("exscore", exscore)), 100 | } 101 | 102 | ghostid = db.table("ddr_scores").get( 103 | (where("ddr_id") == ddr_id) 104 | & (where("game_version") == game_version) 105 | & (where("mcode") == mcode) 106 | & (where("difficulty") == difficulty) 107 | & (where("exscore") == best.get("exscore", exscore)) 108 | ) 109 | if ghostid: 110 | best_score_data["ghostid"] = ghostid.doc_id 111 | else: 112 | best_score_data["ghostid"] = -1 113 | 114 | db.table("ddr_scores_best").upsert( 115 | best_score_data, 116 | (where("ddr_id") == ddr_id) 117 | & (where("game_version") == game_version) 118 | & (where("mcode") == mcode) 119 | & (where("difficulty") == difficulty), 120 | ) 121 | 122 | db.close() 123 | print() 124 | print(f"{total_count} scores imported to DDR profile {ddr_id} in {monkey_db}") 125 | 126 | 127 | if __name__ == "__main__": 128 | parser = argparse.ArgumentParser() 129 | parser.add_argument("--automap_xml", help="Input xml file", required=True) 130 | parser.add_argument( 131 | "--version", 132 | help="19 is A20P, 20 is A3", 133 | default=19, 134 | type=int, 135 | ) 136 | parser.add_argument("--monkey_db", help="Output json file", required=True) 137 | parser.add_argument("--ddr_id", help="12345678", required=True) 138 | args = parser.parse_args() 139 | 140 | main(args.automap_xml, args.version, args.monkey_db, args.ddr_id) 141 | -------------------------------------------------------------------------------- /modules/gitadora/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | from tinydb import Query, where 6 | from core_database import get_db 7 | from pydantic import BaseModel 8 | 9 | import config 10 | import utils.card as conv 11 | 12 | 13 | router = APIRouter(prefix="/gfdm", tags=["api_gfdm"]) 14 | 15 | 16 | class GFDM_Profile_Main_Items(BaseModel): 17 | card: str 18 | pin: str 19 | 20 | 21 | class GFDM_Profile_Version_Items(BaseModel): 22 | game_version: int 23 | name: str 24 | title: str 25 | rival_card_ids: list = [] 26 | 27 | 28 | @router.get("/profiles") 29 | async def gfdm_profiles(): 30 | return get_db().table("gitadora_profile").all() 31 | 32 | 33 | @router.get("/profiles/{gitadora_id}") 34 | async def gfdm_profile_id(gitadora_id: str): 35 | gitadora_id = int("".join([i for i in gitadora_id if i.isnumeric()])) 36 | return get_db().table("gitadora_profile").get(where("gitadora_id") == gitadora_id) 37 | 38 | 39 | @router.patch("/profiles/{gitadora_id}") 40 | async def gfdm_profile_id_patch(gitadora_id: str, item: GFDM_Profile_Main_Items): 41 | gitadora_id = int("".join([i for i in gitadora_id if i.isnumeric()])) 42 | profile = ( 43 | get_db().table("gitadora_profile").get(where("gitadora_id") == gitadora_id) 44 | ) 45 | 46 | profile["card"] = item.card 47 | profile["pin"] = item.pin 48 | 49 | get_db().table("gitadora_profile").upsert( 50 | profile, where("gitadora_id") == gitadora_id 51 | ) 52 | return Response(status_code=204) 53 | 54 | 55 | @router.patch("/profiles/{gitadora_id}/{version}") 56 | async def gfdm_profile_id_version_patch( 57 | gitadora_id: str, version: int, item: GFDM_Profile_Version_Items 58 | ): 59 | gitadora_id = int("".join([i for i in gitadora_id if i.isnumeric()])) 60 | profile = ( 61 | get_db().table("gitadora_profile").get(where("gitadora_id") == gitadora_id) 62 | ) 63 | game_profile = profile["version"].get(str(version), {}) 64 | 65 | game_profile["game_version"] = item.game_version 66 | game_profile["name"] = item.name 67 | game_profile["title"] = item.title 68 | game_profile["rival_card_ids"] = item.rival_card_ids 69 | 70 | profile["version"][str(version)] = game_profile 71 | get_db().table("gitadora_profile").upsert( 72 | profile, where("gitadora_id") == gitadora_id 73 | ) 74 | return Response(status_code=204) 75 | 76 | 77 | @router.get("/card/{card}") 78 | async def gfdm_card_to_profile(card: str): 79 | card = card.upper() 80 | lookalike = { 81 | "I": "1", 82 | "O": "0", 83 | "Q": "0", 84 | "V": "U", 85 | } 86 | for k, v in lookalike.items(): 87 | card = card.replace(k, v) 88 | if card.startswith("E004") or card.startswith("012E"): 89 | card = "".join([c for c in card if c in "0123456789ABCDEF"]) 90 | uid = card 91 | kid = conv.to_konami_id(card) 92 | else: 93 | card = "".join([c for c in card if c in conv.valid_characters]) 94 | uid = conv.to_uid(card) 95 | kid = card 96 | profile = get_db().table("gitadora_profile").get(where("card") == uid) 97 | return profile 98 | 99 | 100 | @router.get("/drummania/scores") 101 | async def dm_scores(): 102 | return get_db().table("drummania_scores").all() 103 | 104 | 105 | @router.get("/guitarfreaks/scores") 106 | async def gf_scores(): 107 | return get_db().table("guitarfreaks_scores").all() 108 | 109 | 110 | @router.get("/drummania/scores/{gitadora_id}") 111 | async def dm_scores_id(gitadora_id: str): 112 | gitadora_id = int("".join([i for i in gitadora_id if i.isnumeric()])) 113 | return ( 114 | get_db().table("drummania_scores").search((where("gitadora_id") == gitadora_id)) 115 | ) 116 | 117 | 118 | @router.get("/guitarfreaks/scores/{gitadora_id}") 119 | async def gf_scores_id(gitadora_id: str): 120 | gitadora_id = int("".join([i for i in gitadora_id if i.isnumeric()])) 121 | return ( 122 | get_db() 123 | .table("guitarfreaks_scores") 124 | .search((where("gitadora_id") == gitadora_id)) 125 | ) 126 | 127 | 128 | @router.get("/drummania/scores_best") 129 | async def dm_scores_best(): 130 | return get_db().table("drummania_scores_best").all() 131 | 132 | 133 | @router.get("/guitarfreaks/scores_best") 134 | async def gf_scores_best(): 135 | return get_db().table("guitarfreaks_scores_best").all() 136 | 137 | 138 | @router.get("/drummania/scores_best/{gitadora_id}") 139 | async def dm_scores_best_id(gitadora_id: str): 140 | gitadora_id = int("".join([i for i in gitadora_id if i.isnumeric()])) 141 | return ( 142 | get_db() 143 | .table("drummania_scores_best") 144 | .search((where("gitadora_id") == gitadora_id)) 145 | ) 146 | 147 | 148 | @router.get("/guitarfreaks/scores_best/{gitadora_id}") 149 | async def gf_scores_best_id(gitadora_id: str): 150 | gitadora_id = int("".join([i for i in gitadora_id if i.isnumeric()])) 151 | return ( 152 | get_db() 153 | .table("guitarfreaks_scores_best") 154 | .search((where("gitadora_id") == gitadora_id)) 155 | ) 156 | 157 | 158 | @router.get("/drummania/mcode/{mcode}/all") 159 | async def dm_scores_id(mcode: int): 160 | return get_db().table("drummania_scores").search((where("mcode") == mcode)) 161 | 162 | 163 | @router.get("/guitarfreaks/mcode/{mcode}/all") 164 | async def gf_scores_id(mcode: int): 165 | return get_db().table("guitarfreaks_scores").search((where("mcode") == mcode)) 166 | 167 | 168 | @router.get("/drummania/mcode/{mcode}/best") 169 | async def dm_scores_id_best(mcode: int): 170 | return get_db().table("drummania_scores_best").search((where("mcode") == mcode)) 171 | 172 | 173 | @router.get("/guitarfreaks/mcode/{mcode}/best") 174 | async def gf_scores_id_best(mcode: int): 175 | return get_db().table("guitarfreaks_scores_best").search((where("mcode") == mcode)) 176 | -------------------------------------------------------------------------------- /modules/iidx/iidx30lobby.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/lobby", tags=["lobby"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | arena_host = {} 14 | bpl_host = {} 15 | 16 | 17 | @router.post("/{gameinfo}/IIDX30lobby/entry") 18 | async def iidx30lobby_entry(request: Request): 19 | request_info = await core_process_request(request) 20 | 21 | root = request_info["root"][0] 22 | sp_dp = root.find("play_style").text 23 | arena_class = root.find("arena_class").text 24 | ga = root.find("address/ga").text.split() 25 | gp = root.find("address/gp").text 26 | la = root.find("address/la").text.split() 27 | 28 | if arena_host and time() < arena_host["time"]: 29 | # test menu reset 30 | if arena_host["ga"] == ga: 31 | is_arena_host = 1 32 | arena_host["time"] = time() + 30 33 | else: 34 | is_arena_host = 0 35 | response = E.response( 36 | E.IIDX30lobby( 37 | E.host(is_arena_host, __type="bool"), 38 | E.matching_class(arena_class, __type="s32"), 39 | E.address( 40 | E.ga(arena_host["ga"], __type="u8"), 41 | E.gp(arena_host["gp"], __type="u16"), 42 | E.la(arena_host["la"], __type="u8"), 43 | ), 44 | ) 45 | ) 46 | else: 47 | arena_host["ga"] = ga 48 | arena_host["gp"] = gp 49 | arena_host["la"] = la 50 | arena_host["time"] = time() + 30 51 | response = E.response( 52 | E.IIDX30lobby( 53 | E.host(1, __type="bool"), 54 | E.matching_class(arena_class, __type="s32"), 55 | E.address( 56 | E.ga(ga, __type="u8"), 57 | E.gp(gp, __type="u16"), 58 | E.la(la, __type="u8"), 59 | ), 60 | ) 61 | ) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | 66 | 67 | @router.post("/{gameinfo}/IIDX30lobby/update") 68 | async def iidx30lobby_update(request: Request): 69 | request_info = await core_process_request(request) 70 | 71 | response = E.response(E.IIDX30lobby()) 72 | 73 | response_body, response_headers = await core_prepare_response(request, response) 74 | return Response(content=response_body, headers=response_headers) 75 | 76 | 77 | @router.post("/{gameinfo}/IIDX30lobby/delete") 78 | async def iidx30lobby_delete(request: Request): 79 | request_info = await core_process_request(request) 80 | 81 | # normal reset 82 | del arena_host["ga"] 83 | del arena_host["gp"] 84 | del arena_host["la"] 85 | del arena_host["time"] 86 | response = E.response(E.IIDX30lobby()) 87 | 88 | response_body, response_headers = await core_prepare_response(request, response) 89 | return Response(content=response_body, headers=response_headers) 90 | 91 | 92 | @router.post("/{gameinfo}/IIDX30lobby/bplbattle_entry") 93 | async def iidx30lobby_bplbattle_entry(request: Request): 94 | request_info = await core_process_request(request) 95 | 96 | root = request_info["root"][0] 97 | sp_dp = root.find("play_style").text 98 | arena_class = root.find("arena_class").text 99 | password = root.find("passward").text # passward 100 | ga = root.find("address/ga").text.split() 101 | gp = root.find("address/gp").text 102 | la = root.find("address/la").text.split() 103 | 104 | if bpl_host and password in bpl_host and time() < bpl_host[password]["time"]: 105 | # test menu reset 106 | if bpl_host[password]["ga"] == ga: 107 | is_bpl_host = 1 108 | bpl_host[password]["time"] = time() + 30 109 | else: 110 | is_bpl_host = 0 111 | response = E.response( 112 | E.IIDX30lobby( 113 | E.host(is_bpl_host, __type="bool"), 114 | E.matching_class(arena_class, __type="s32"), 115 | E.address( 116 | E.ga(bpl_host[password]["ga"], __type="u8"), 117 | E.gp(bpl_host[password]["gp"], __type="u16"), 118 | E.la(bpl_host[password]["la"], __type="u8"), 119 | ), 120 | ) 121 | ) 122 | else: 123 | bpl_host[password] = {} 124 | bpl_host[password]["ga"] = ga 125 | bpl_host[password]["gp"] = gp 126 | bpl_host[password]["la"] = la 127 | bpl_host[password]["time"] = time() + 30 128 | response = E.response( 129 | E.IIDX30lobby( 130 | E.host(1, __type="bool"), 131 | E.matching_class(arena_class, __type="s32"), 132 | E.address( 133 | E.ga(ga, __type="u8"), 134 | E.gp(gp, __type="u16"), 135 | E.la(la, __type="u8"), 136 | ), 137 | ) 138 | ) 139 | 140 | response_body, response_headers = await core_prepare_response(request, response) 141 | return Response(content=response_body, headers=response_headers) 142 | 143 | 144 | @router.post("/{gameinfo}/IIDX30lobby/bplbattle_update") 145 | async def iidx30lobby_bplbattle_update(request: Request): 146 | request_info = await core_process_request(request) 147 | 148 | response = E.response(E.IIDX30lobby()) 149 | 150 | response_body, response_headers = await core_prepare_response(request, response) 151 | return Response(content=response_body, headers=response_headers) 152 | 153 | 154 | @router.post("/{gameinfo}/IIDX30lobby/bplbattle_delete") 155 | async def iidx30lobby_bplbattle_delete(request: Request): 156 | request_info = await core_process_request(request) 157 | 158 | root = request_info["root"][0] 159 | ga = root.find("address/ga").text.split() 160 | 161 | # normal reset 162 | for host in bpl_host: 163 | if bpl_host[host]["ga"] == ga: 164 | del bpl_host[host] 165 | break 166 | response = E.response(E.IIDX30lobby()) 167 | 168 | response_body, response_headers = await core_prepare_response(request, response) 169 | return Response(content=response_body, headers=response_headers) 170 | -------------------------------------------------------------------------------- /modules/iidx/iidx31lobby.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/lobby", tags=["lobby"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | arena_host = {} 14 | bpl_host = {} 15 | 16 | 17 | @router.post("/{gameinfo}/IIDX31lobby/entry") 18 | async def iidx31lobby_entry(request: Request): 19 | request_info = await core_process_request(request) 20 | 21 | root = request_info["root"][0] 22 | sp_dp = root.find("play_style").text 23 | arena_class = root.find("arena_class").text 24 | ga = root.find("address/ga").text.split() 25 | gp = root.find("address/gp").text 26 | la = root.find("address/la").text.split() 27 | 28 | if arena_host and time() < arena_host["time"]: 29 | # test menu reset 30 | if arena_host["ga"] == ga: 31 | is_arena_host = 1 32 | arena_host["time"] = time() + 30 33 | else: 34 | is_arena_host = 0 35 | response = E.response( 36 | E.IIDX31lobby( 37 | E.host(is_arena_host, __type="bool"), 38 | E.matching_class(arena_class, __type="s32"), 39 | E.address( 40 | E.ga(arena_host["ga"], __type="u8"), 41 | E.gp(arena_host["gp"], __type="u16"), 42 | E.la(arena_host["la"], __type="u8"), 43 | ), 44 | ) 45 | ) 46 | else: 47 | arena_host["ga"] = ga 48 | arena_host["gp"] = gp 49 | arena_host["la"] = la 50 | arena_host["time"] = time() + 30 51 | response = E.response( 52 | E.IIDX31lobby( 53 | E.host(1, __type="bool"), 54 | E.matching_class(arena_class, __type="s32"), 55 | E.address( 56 | E.ga(ga, __type="u8"), 57 | E.gp(gp, __type="u16"), 58 | E.la(la, __type="u8"), 59 | ), 60 | ) 61 | ) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | 66 | 67 | @router.post("/{gameinfo}/IIDX31lobby/update") 68 | async def iidx31lobby_update(request: Request): 69 | request_info = await core_process_request(request) 70 | 71 | response = E.response(E.IIDX31lobby()) 72 | 73 | response_body, response_headers = await core_prepare_response(request, response) 74 | return Response(content=response_body, headers=response_headers) 75 | 76 | 77 | @router.post("/{gameinfo}/IIDX31lobby/delete") 78 | async def iidx31lobby_delete(request: Request): 79 | request_info = await core_process_request(request) 80 | 81 | # normal reset 82 | del arena_host["ga"] 83 | del arena_host["gp"] 84 | del arena_host["la"] 85 | del arena_host["time"] 86 | response = E.response(E.IIDX31lobby()) 87 | 88 | response_body, response_headers = await core_prepare_response(request, response) 89 | return Response(content=response_body, headers=response_headers) 90 | 91 | 92 | @router.post("/{gameinfo}/IIDX31lobby/bplbattle_entry") 93 | async def iidx31lobby_bplbattle_entry(request: Request): 94 | request_info = await core_process_request(request) 95 | 96 | root = request_info["root"][0] 97 | sp_dp = root.find("play_style").text 98 | arena_class = root.find("arena_class").text 99 | password = root.find("passward").text # passward 100 | ga = root.find("address/ga").text.split() 101 | gp = root.find("address/gp").text 102 | la = root.find("address/la").text.split() 103 | 104 | if bpl_host and password in bpl_host and time() < bpl_host[password]["time"]: 105 | # test menu reset 106 | if bpl_host[password]["ga"] == ga: 107 | is_bpl_host = 1 108 | bpl_host[password]["time"] = time() + 30 109 | else: 110 | is_bpl_host = 0 111 | response = E.response( 112 | E.IIDX31lobby( 113 | E.host(is_bpl_host, __type="bool"), 114 | E.matching_class(arena_class, __type="s32"), 115 | E.address( 116 | E.ga(bpl_host[password]["ga"], __type="u8"), 117 | E.gp(bpl_host[password]["gp"], __type="u16"), 118 | E.la(bpl_host[password]["la"], __type="u8"), 119 | ), 120 | ) 121 | ) 122 | else: 123 | bpl_host[password] = {} 124 | bpl_host[password]["ga"] = ga 125 | bpl_host[password]["gp"] = gp 126 | bpl_host[password]["la"] = la 127 | bpl_host[password]["time"] = time() + 30 128 | response = E.response( 129 | E.IIDX31lobby( 130 | E.host(1, __type="bool"), 131 | E.matching_class(arena_class, __type="s32"), 132 | E.address( 133 | E.ga(ga, __type="u8"), 134 | E.gp(gp, __type="u16"), 135 | E.la(la, __type="u8"), 136 | ), 137 | ) 138 | ) 139 | 140 | response_body, response_headers = await core_prepare_response(request, response) 141 | return Response(content=response_body, headers=response_headers) 142 | 143 | 144 | @router.post("/{gameinfo}/IIDX31lobby/bplbattle_update") 145 | async def iidx31lobby_bplbattle_update(request: Request): 146 | request_info = await core_process_request(request) 147 | 148 | response = E.response(E.IIDX31lobby()) 149 | 150 | response_body, response_headers = await core_prepare_response(request, response) 151 | return Response(content=response_body, headers=response_headers) 152 | 153 | 154 | @router.post("/{gameinfo}/IIDX31lobby/bplbattle_delete") 155 | async def iidx31lobby_bplbattle_delete(request: Request): 156 | request_info = await core_process_request(request) 157 | 158 | root = request_info["root"][0] 159 | ga = root.find("address/ga").text.split() 160 | 161 | # normal reset 162 | for host in bpl_host: 163 | if bpl_host[host]["ga"] == ga: 164 | del bpl_host[host] 165 | break 166 | response = E.response(E.IIDX31lobby()) 167 | 168 | response_body, response_headers = await core_prepare_response(request, response) 169 | return Response(content=response_body, headers=response_headers) 170 | -------------------------------------------------------------------------------- /modules/iidx/iidx32lobby.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/lobby2", tags=["lobby2"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | arena_host = {} 14 | bpl_host = {} 15 | 16 | 17 | @router.post("/{gameinfo}/IIDX32lobby/entry") 18 | async def iidx32lobby_entry(request: Request): 19 | request_info = await core_process_request(request) 20 | 21 | root = request_info["root"][0] 22 | sp_dp = root.find("play_style").text 23 | arena_class = root.find("arena_class").text 24 | ga = root.find("address/ga").text.split() 25 | gp = root.find("address/gp").text 26 | la = root.find("address/la").text.split() 27 | 28 | if arena_host and time() < arena_host["time"]: 29 | # test menu reset 30 | if arena_host["ga"] == ga: 31 | is_arena_host = 1 32 | arena_host["time"] = time() + 30 33 | else: 34 | is_arena_host = 0 35 | response = E.response( 36 | E.IIDX32lobby( 37 | E.host(is_arena_host, __type="bool"), 38 | E.matching_class(arena_class, __type="s32"), 39 | E.address( 40 | E.ga(arena_host["ga"], __type="u8"), 41 | E.gp(arena_host["gp"], __type="u16"), 42 | E.la(arena_host["la"], __type="u8"), 43 | ), 44 | ) 45 | ) 46 | else: 47 | arena_host["ga"] = ga 48 | arena_host["gp"] = gp 49 | arena_host["la"] = la 50 | arena_host["time"] = time() + 30 51 | response = E.response( 52 | E.IIDX32lobby( 53 | E.host(1, __type="bool"), 54 | E.matching_class(arena_class, __type="s32"), 55 | E.address( 56 | E.ga(ga, __type="u8"), 57 | E.gp(gp, __type="u16"), 58 | E.la(la, __type="u8"), 59 | ), 60 | ) 61 | ) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | 66 | 67 | @router.post("/{gameinfo}/IIDX32lobby/update") 68 | async def iidx32lobby_update(request: Request): 69 | request_info = await core_process_request(request) 70 | 71 | response = E.response(E.IIDX32lobby()) 72 | 73 | response_body, response_headers = await core_prepare_response(request, response) 74 | return Response(content=response_body, headers=response_headers) 75 | 76 | 77 | @router.post("/{gameinfo}/IIDX32lobby/delete") 78 | async def iidx32lobby_delete(request: Request): 79 | request_info = await core_process_request(request) 80 | 81 | # normal reset 82 | del arena_host["ga"] 83 | del arena_host["gp"] 84 | del arena_host["la"] 85 | del arena_host["time"] 86 | response = E.response(E.IIDX32lobby()) 87 | 88 | response_body, response_headers = await core_prepare_response(request, response) 89 | return Response(content=response_body, headers=response_headers) 90 | 91 | 92 | @router.post("/{gameinfo}/IIDX32lobby/bplbattle_entry") 93 | async def iidx32lobby_bplbattle_entry(request: Request): 94 | request_info = await core_process_request(request) 95 | 96 | root = request_info["root"][0] 97 | sp_dp = root.find("play_style").text 98 | arena_class = root.find("arena_class").text 99 | password = root.find("passward").text # passward 100 | ga = root.find("address/ga").text.split() 101 | gp = root.find("address/gp").text 102 | la = root.find("address/la").text.split() 103 | 104 | if bpl_host and password in bpl_host and time() < bpl_host[password]["time"]: 105 | # test menu reset 106 | if bpl_host[password]["ga"] == ga: 107 | is_bpl_host = 1 108 | bpl_host[password]["time"] = time() + 30 109 | else: 110 | is_bpl_host = 0 111 | response = E.response( 112 | E.IIDX32lobby( 113 | E.host(is_bpl_host, __type="bool"), 114 | E.matching_class(arena_class, __type="s32"), 115 | E.address( 116 | E.ga(bpl_host[password]["ga"], __type="u8"), 117 | E.gp(bpl_host[password]["gp"], __type="u16"), 118 | E.la(bpl_host[password]["la"], __type="u8"), 119 | ), 120 | ) 121 | ) 122 | else: 123 | bpl_host[password] = {} 124 | bpl_host[password]["ga"] = ga 125 | bpl_host[password]["gp"] = gp 126 | bpl_host[password]["la"] = la 127 | bpl_host[password]["time"] = time() + 30 128 | response = E.response( 129 | E.IIDX32lobby( 130 | E.host(1, __type="bool"), 131 | E.matching_class(arena_class, __type="s32"), 132 | E.address( 133 | E.ga(ga, __type="u8"), 134 | E.gp(gp, __type="u16"), 135 | E.la(la, __type="u8"), 136 | ), 137 | ) 138 | ) 139 | 140 | response_body, response_headers = await core_prepare_response(request, response) 141 | return Response(content=response_body, headers=response_headers) 142 | 143 | 144 | @router.post("/{gameinfo}/IIDX32lobby/bplbattle_update") 145 | async def iidx32lobby_bplbattle_update(request: Request): 146 | request_info = await core_process_request(request) 147 | 148 | response = E.response(E.IIDX32lobby()) 149 | 150 | response_body, response_headers = await core_prepare_response(request, response) 151 | return Response(content=response_body, headers=response_headers) 152 | 153 | 154 | @router.post("/{gameinfo}/IIDX32lobby/bplbattle_delete") 155 | async def iidx32lobby_bplbattle_delete(request: Request): 156 | request_info = await core_process_request(request) 157 | 158 | root = request_info["root"][0] 159 | ga = root.find("address/ga").text.split() 160 | 161 | # normal reset 162 | for host in bpl_host: 163 | if bpl_host[host]["ga"] == ga: 164 | del bpl_host[host] 165 | break 166 | response = E.response(E.IIDX32lobby()) 167 | 168 | response_body, response_headers = await core_prepare_response(request, response) 169 | return Response(content=response_body, headers=response_headers) 170 | -------------------------------------------------------------------------------- /modules/iidx/iidx33lobby.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/lobby2", tags=["lobby2"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | arena_host = {} 14 | bpl_host = {} 15 | 16 | 17 | @router.post("/{gameinfo}/IIDX33lobby/entry") 18 | async def iidx33lobby_entry(request: Request): 19 | request_info = await core_process_request(request) 20 | 21 | root = request_info["root"][0] 22 | sp_dp = root.find("play_style").text 23 | arena_class = root.find("arena_class").text 24 | ga = root.find("address/ga").text.split() 25 | gp = root.find("address/gp").text 26 | la = root.find("address/la").text.split() 27 | 28 | if arena_host and time() < arena_host["time"]: 29 | # test menu reset 30 | if arena_host["ga"] == ga: 31 | is_arena_host = 1 32 | arena_host["time"] = time() + 30 33 | else: 34 | is_arena_host = 0 35 | response = E.response( 36 | E.IIDX33lobby( 37 | E.host(is_arena_host, __type="bool"), 38 | E.matching_class(arena_class, __type="s32"), 39 | E.address( 40 | E.ga(arena_host["ga"], __type="u8"), 41 | E.gp(arena_host["gp"], __type="u16"), 42 | E.la(arena_host["la"], __type="u8"), 43 | ), 44 | ) 45 | ) 46 | else: 47 | arena_host["ga"] = ga 48 | arena_host["gp"] = gp 49 | arena_host["la"] = la 50 | arena_host["time"] = time() + 30 51 | response = E.response( 52 | E.IIDX33lobby( 53 | E.host(1, __type="bool"), 54 | E.matching_class(arena_class, __type="s32"), 55 | E.address( 56 | E.ga(ga, __type="u8"), 57 | E.gp(gp, __type="u16"), 58 | E.la(la, __type="u8"), 59 | ), 60 | ) 61 | ) 62 | 63 | response_body, response_headers = await core_prepare_response(request, response) 64 | return Response(content=response_body, headers=response_headers) 65 | 66 | 67 | @router.post("/{gameinfo}/IIDX33lobby/update") 68 | async def iidx33lobby_update(request: Request): 69 | request_info = await core_process_request(request) 70 | 71 | response = E.response(E.IIDX33lobby()) 72 | 73 | response_body, response_headers = await core_prepare_response(request, response) 74 | return Response(content=response_body, headers=response_headers) 75 | 76 | 77 | @router.post("/{gameinfo}/IIDX33lobby/delete") 78 | async def iidx33lobby_delete(request: Request): 79 | request_info = await core_process_request(request) 80 | 81 | # normal reset 82 | del arena_host["ga"] 83 | del arena_host["gp"] 84 | del arena_host["la"] 85 | del arena_host["time"] 86 | response = E.response(E.IIDX33lobby()) 87 | 88 | response_body, response_headers = await core_prepare_response(request, response) 89 | return Response(content=response_body, headers=response_headers) 90 | 91 | 92 | @router.post("/{gameinfo}/IIDX33lobby/bplbattle_entry") 93 | async def iidx33lobby_bplbattle_entry(request: Request): 94 | request_info = await core_process_request(request) 95 | 96 | root = request_info["root"][0] 97 | sp_dp = root.find("play_style").text 98 | arena_class = root.find("arena_class").text 99 | password = root.find("passward").text # passward 100 | ga = root.find("address/ga").text.split() 101 | gp = root.find("address/gp").text 102 | la = root.find("address/la").text.split() 103 | 104 | if bpl_host and password in bpl_host and time() < bpl_host[password]["time"]: 105 | # test menu reset 106 | if bpl_host[password]["ga"] == ga: 107 | is_bpl_host = 1 108 | bpl_host[password]["time"] = time() + 30 109 | else: 110 | is_bpl_host = 0 111 | response = E.response( 112 | E.IIDX33lobby( 113 | E.host(is_bpl_host, __type="bool"), 114 | E.matching_class(arena_class, __type="s32"), 115 | E.address( 116 | E.ga(bpl_host[password]["ga"], __type="u8"), 117 | E.gp(bpl_host[password]["gp"], __type="u16"), 118 | E.la(bpl_host[password]["la"], __type="u8"), 119 | ), 120 | ) 121 | ) 122 | else: 123 | bpl_host[password] = {} 124 | bpl_host[password]["ga"] = ga 125 | bpl_host[password]["gp"] = gp 126 | bpl_host[password]["la"] = la 127 | bpl_host[password]["time"] = time() + 30 128 | response = E.response( 129 | E.IIDX33lobby( 130 | E.host(1, __type="bool"), 131 | E.matching_class(arena_class, __type="s32"), 132 | E.address( 133 | E.ga(ga, __type="u8"), 134 | E.gp(gp, __type="u16"), 135 | E.la(la, __type="u8"), 136 | ), 137 | ) 138 | ) 139 | 140 | response_body, response_headers = await core_prepare_response(request, response) 141 | return Response(content=response_body, headers=response_headers) 142 | 143 | 144 | @router.post("/{gameinfo}/IIDX33lobby/bplbattle_update") 145 | async def iidx33lobby_bplbattle_update(request: Request): 146 | request_info = await core_process_request(request) 147 | 148 | response = E.response(E.IIDX33lobby()) 149 | 150 | response_body, response_headers = await core_prepare_response(request, response) 151 | return Response(content=response_body, headers=response_headers) 152 | 153 | 154 | @router.post("/{gameinfo}/IIDX33lobby/bplbattle_delete") 155 | async def iidx33lobby_bplbattle_delete(request: Request): 156 | request_info = await core_process_request(request) 157 | 158 | root = request_info["root"][0] 159 | ga = root.find("address/ga").text.split() 160 | 161 | # normal reset 162 | for host in bpl_host: 163 | if bpl_host[host]["ga"] == ga: 164 | del bpl_host[host] 165 | break 166 | response = E.response(E.IIDX33lobby()) 167 | 168 | response_body, response_headers = await core_prepare_response(request, response) 169 | return Response(content=response_body, headers=response_headers) 170 | -------------------------------------------------------------------------------- /core_common.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | import random 4 | import time 5 | 6 | from lxml.builder import ElementMaker 7 | 8 | from kbinxml import KBinXML 9 | 10 | from utils.arc4 import EamuseARC4 11 | from utils.lz77 import EamuseLZ77 12 | 13 | 14 | def _add_val_as_str(elm, val): 15 | new_val = str(val) 16 | 17 | if elm is not None: 18 | elm.text = new_val 19 | 20 | else: 21 | return new_val 22 | 23 | 24 | def _add_bool_as_str(elm, val): 25 | return _add_val_as_str(elm, 1 if val else 0) 26 | 27 | 28 | def _add_list_as_str(elm, vals): 29 | new_val = " ".join([str(val) for val in vals]) 30 | 31 | if elm is not None: 32 | elm.text = new_val 33 | elm.attrib["__count"] = str(len(vals)) 34 | 35 | else: 36 | return new_val 37 | 38 | 39 | E = ElementMaker( 40 | typemap={ 41 | int: _add_val_as_str, 42 | bool: _add_bool_as_str, 43 | list: _add_list_as_str, 44 | float: _add_val_as_str, 45 | } 46 | ) 47 | 48 | 49 | async def core_get_game_version_from_software_version(software_version): 50 | _, model, dest, spec, rev, ext = software_version 51 | ext = int(ext) 52 | 53 | if model == "LDJ": 54 | if ext >= 2025091700: 55 | return 33 56 | elif ext >= 2024100900: 57 | return 32 58 | elif ext >= 2023101800: 59 | return 31 60 | elif ext >= 2022101700: 61 | return 30 62 | elif ext >= 2021101300: 63 | return 29 64 | # TODO: Consolidate IIDX modules to easily support versions 21-28 (probably never) 65 | elif ext >= 2020102800: 66 | return 28 67 | elif ext >= 2019101600: 68 | return 27 69 | elif ext >= 2018110700: 70 | return 26 71 | elif ext >= 2017122100: 72 | return 25 73 | elif ext >= 2016102400: 74 | return 24 75 | elif ext >= 2015111100: 76 | return 23 77 | elif ext >= 2014091700: 78 | return 22 79 | elif ext >= 2013100200: 80 | return 21 81 | elif ext >= 2012010100: 82 | return 20 83 | elif model == "KDZ": 84 | return 19 85 | elif model == "JDZ": 86 | return 18 87 | 88 | elif model == "M32": 89 | if ext >= 2024031300: 90 | return 10 91 | elif ext >= 2022121400: 92 | return 9 93 | elif ext >= 2021042100: 94 | return 8 95 | elif ext >= 2019100200: 96 | return 7 97 | elif ext >= 2018072700: 98 | return 6 99 | # TODO: Support versions 1-5 (never) 100 | elif ext >= 2017090600: 101 | return 5 102 | elif ext >= 2017011800: 103 | return 4 104 | elif ext >= 2015042100: 105 | return 3 106 | elif ext >= 2014021400: 107 | return 2 108 | elif ext >= 2013012400: 109 | return 1 110 | 111 | elif model == "MDX": 112 | if ext >= 2019022600: # ??? 113 | return 19 114 | 115 | elif model == "KFC": 116 | if ext >= 2020090402: # ??? 117 | return 6 118 | 119 | elif model == "REC": 120 | return 1 121 | 122 | # TODO: ??? 123 | # elif model == "PAN": 124 | # return 0 125 | 126 | else: 127 | return 0 128 | 129 | 130 | async def core_process_request(request): 131 | cl = request.headers.get("Content-Length") 132 | data = await request.body() 133 | 134 | if not cl or not data: 135 | return {} 136 | 137 | if "X-Compress" in request.headers: 138 | request.compress = request.headers.get("X-Compress") 139 | else: 140 | request.compress = None 141 | 142 | if "X-Eamuse-Info" in request.headers: 143 | xeamuseinfo = request.headers.get("X-Eamuse-Info") 144 | key = bytes.fromhex(xeamuseinfo[2:].replace("-", "")) 145 | xml_dec = EamuseARC4(key).decrypt(data[: int(cl)]) 146 | request.is_encrypted = True 147 | else: 148 | xml_dec = data[: int(cl)] 149 | request.is_encrypted = False 150 | 151 | if request.compress == "lz77": 152 | xml_dec = EamuseLZ77.decode(xml_dec) 153 | 154 | xml = KBinXML(xml_dec, convert_illegal_things=True) 155 | root = xml.xml_doc 156 | xml_text = xml.to_text() 157 | request.is_binxml = KBinXML.is_binary_xml(xml_dec) 158 | 159 | if config.verbose_log: 160 | print() 161 | print("\033[94mREQUEST\033[0m:") 162 | print(xml_text) 163 | 164 | model_parts = (root.attrib["model"], *root.attrib["model"].split(":")) 165 | module = root[0].tag 166 | method = root[0].attrib["method"] if "method" in root[0].attrib else None 167 | command = root[0].attrib["command"] if "command" in root[0].attrib else None 168 | game_version = await core_get_game_version_from_software_version(model_parts) 169 | 170 | return { 171 | "root": root, 172 | "text": xml_text, 173 | "module": module, 174 | "method": method, 175 | "command": command, 176 | "model": model_parts[1], 177 | "dest": model_parts[2], 178 | "spec": model_parts[3], 179 | "rev": model_parts[4], 180 | "ext": model_parts[5], 181 | "game_version": game_version, 182 | } 183 | 184 | 185 | async def core_prepare_response(request, xml): 186 | binxml = KBinXML(xml) 187 | 188 | if request.is_binxml: 189 | xml_binary = binxml.to_binary() 190 | else: 191 | xml_binary = binxml.to_text().encode("utf-8") # TODO: Proper encoding 192 | 193 | if config.verbose_log: 194 | print("\033[91mRESPONSE\033[0m:") 195 | print(binxml.to_text()) 196 | 197 | response_headers = {"User-Agent": "EAMUSE.Httpac/1.0"} 198 | 199 | if request.is_encrypted: 200 | xeamuseinfo = "1-%08x-%04x" % (int(time.time()), random.randint(0x0000, 0xFFFF)) 201 | response_headers["X-Eamuse-Info"] = xeamuseinfo 202 | key = bytes.fromhex(xeamuseinfo[2:].replace("-", "")) 203 | response = EamuseARC4(key).encrypt(xml_binary) 204 | else: 205 | response = bytes(xml_binary) 206 | 207 | request.compress = None 208 | # if request.compress == "lz77": 209 | # response_headers["X-Compress"] = request.compress 210 | # response = EamuseLZ77.encode(response) 211 | 212 | return response, response_headers 213 | -------------------------------------------------------------------------------- /pyeamu.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlparse, urlunparse, urlencode 2 | 3 | import uvicorn 4 | 5 | import ujson as json 6 | from os import name, path 7 | from typing import Optional 8 | 9 | from fastapi import FastAPI, Request, Response 10 | from fastapi.middleware.cors import CORSMiddleware 11 | from fastapi.staticfiles import StaticFiles 12 | from starlette.responses import RedirectResponse 13 | 14 | import config 15 | import modules 16 | import utils.card as conv 17 | 18 | from core_common import core_process_request, core_prepare_response, E 19 | 20 | import socket 21 | 22 | 23 | def urlpathjoin(parts, sep="/"): 24 | return sep + sep.join([x.lstrip(sep) for x in parts]) 25 | 26 | 27 | loopback = "127.0.0.1" 28 | 29 | server_addresses = [] 30 | for host in ("localhost", config.ip, socket.gethostname()): 31 | server_addresses.append(f"{host}:{config.port}") 32 | 33 | server_services_urls = [] 34 | for server_address in server_addresses: 35 | server_services_urls.append( 36 | urlunparse(("http", server_address, config.services_prefix, None, None, None)) 37 | ) 38 | 39 | settings = {} 40 | for s in ( 41 | "ip", 42 | "port", 43 | "services_prefix", 44 | "verbose_log", 45 | "arcade", 46 | "paseli", 47 | "maintenance_mode", 48 | ): 49 | settings[s] = getattr(config, s) 50 | 51 | app = FastAPI() 52 | for router in modules.routers: 53 | app.include_router(router) 54 | 55 | app.add_middleware( 56 | CORSMiddleware, 57 | allow_origins=["*"], 58 | allow_credentials=True, 59 | allow_methods=["*"], 60 | allow_headers=["*"], 61 | ) 62 | 63 | 64 | if path.exists("webui"): 65 | webui = True 66 | with open(path.join("webui", "monkey.json"), "w") as f: 67 | json.dump(settings, f, indent=2, escape_forward_slashes=False) 68 | app.mount("/webui", StaticFiles(directory="webui", html=True), name="webui") 69 | else: 70 | webui = False 71 | 72 | @app.get("/webui") 73 | async def redirect_to_config(): 74 | return RedirectResponse(url="/config") 75 | 76 | 77 | # Enable ANSI escape sequences 78 | if name == "nt": 79 | import ctypes 80 | 81 | kernel32 = ctypes.windll.kernel32 82 | kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) 83 | 84 | 85 | if __name__ == "__main__": 86 | print( 87 | """ 88 | █▄ ▄█ █▀█ █▄ █ █▄▀ ▀██ ▀▄▀ 89 | █ ▀ █ █▄█ █ ▀█ █ █ ▄▄█ █ 90 | 91 | ██▄ █ █ ▄▀▀ ▄█ █▄ █ ▀██ ▀█▀ 92 | █▄█ ▀▄█ ▄██ █ █ ▀█ ▄▄█ █▄▄ 93 | """ 94 | ) 95 | print() 96 | print("\033[1mGame Config\033[0m:") 97 | for server_services_url in server_services_urls: 98 | print(f"\033[92m{server_services_url}\033[0m") 99 | print("") 100 | print() 101 | print("\033[1mWeb Interface\033[0m:") 102 | if webui: 103 | for server_address in server_addresses: 104 | print(f"http://{server_address}/webui/") 105 | else: 106 | print("/webui missing") 107 | print("download it here: https://github.com/drmext/BounceTrippy/releases") 108 | print() 109 | print("\033[1mSource Repository\033[0m:") 110 | print("https://github.com/drmext/MonkeyBusiness") 111 | print() 112 | uvicorn.run("pyeamu:app", host="0.0.0.0", port=config.port, reload=True) 113 | 114 | 115 | @app.post(urlpathjoin([config.services_prefix])) 116 | @app.post(urlpathjoin([config.services_prefix, "/{gameinfo}/services/get"])) 117 | async def services_get( 118 | request: Request, 119 | model: Optional[str] = None, 120 | f: Optional[str] = None, 121 | module: Optional[str] = None, 122 | method: Optional[str] = None, 123 | ): 124 | request_info = await core_process_request(request) 125 | 126 | request_address = f"{urlparse(str(request.url)).netloc}:{config.port}" 127 | 128 | services = {} 129 | 130 | for service in modules.routers: 131 | model_blacklist = services.get("model_blacklist", []) 132 | model_whitelist = services.get("model_whitelist", []) 133 | 134 | if request_info["model"] in model_blacklist: 135 | continue 136 | 137 | if model_whitelist and request_info["model"] not in model_whitelist: 138 | continue 139 | 140 | if ( 141 | service.tags 142 | and service.tags[0].startswith("api_") 143 | or service.tags[0] == "slashless_forwarder" 144 | ): 145 | continue 146 | 147 | k = (service.tags[0] if service.tags else service.prefix).strip("/") 148 | if f == "services.get" or module == "services" and method == "get": 149 | # url_slash 0 150 | pre = "/fwdr" 151 | else: 152 | # url_slash 1 153 | pre = service.prefix 154 | if k not in services: 155 | services[k] = urlunparse(("http", request_address, pre, None, None, None)) 156 | 157 | keepalive_params = { 158 | "pa": loopback, 159 | "ia": loopback, 160 | "ga": loopback, 161 | "ma": loopback, 162 | "t1": 2, 163 | "t2": 10, 164 | } 165 | services["keepalive"] = urlunparse( 166 | ( 167 | "http", 168 | loopback, 169 | "/keepalive", 170 | None, 171 | urlencode(keepalive_params), 172 | None, 173 | ) 174 | ) 175 | services["ntp"] = urlunparse(("ntp", "pool.ntp.org", "/", None, None, None)) 176 | 177 | response = E.response( 178 | E.services( 179 | expire=10800, 180 | mode="operation", 181 | product_domain=1, 182 | *[E.item(name=k, url=services[k]) for k in services], 183 | ) 184 | ) 185 | 186 | response_body, response_headers = await core_prepare_response(request, response) 187 | return Response(content=response_body, headers=response_headers) 188 | 189 | 190 | @app.get("/") 191 | async def redirect_to_webui(): 192 | return RedirectResponse(url="/webui") 193 | 194 | 195 | @app.get("/config") 196 | async def get_config(): 197 | return settings 198 | 199 | 200 | @app.get("/conv/{card}") 201 | async def card_conv(card: str): 202 | card = card.upper() 203 | lookalike = { 204 | "I": "1", 205 | "O": "0", 206 | "Q": "0", 207 | "V": "U", 208 | } 209 | for k, v in lookalike.items(): 210 | card = card.replace(k, v) 211 | if card.startswith("E004") or card.startswith("012E"): 212 | card = "".join([c for c in card if c in "0123456789ABCDEF"]) 213 | uid = card 214 | kid = conv.to_konami_id(card) 215 | else: 216 | card = "".join([c for c in card if c in conv.valid_characters]) 217 | uid = conv.to_uid(card) 218 | kid = card 219 | return {"uid": uid, "konami_id": kid} 220 | -------------------------------------------------------------------------------- /modules/nostalgia/op3_common.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | from os import path 3 | 4 | from fastapi import APIRouter, Request, Response 5 | 6 | from core_common import core_process_request, core_prepare_response, E 7 | 8 | router = APIRouter(prefix="/local", tags=["local"]) 9 | router.model_whitelist = ["PAN"] 10 | 11 | 12 | @router.post("/{gameinfo}/op3_common/get_common_info") 13 | async def op3_common_get_common_info(request: Request): 14 | request_info = await core_process_request(request) 15 | 16 | response = E.response( 17 | E.get_common_info(E.olupdate(E.delete_flag(0, __type="bool"))) 18 | ) 19 | 20 | response_body, response_headers = await core_prepare_response(request, response) 21 | return Response(content=response_body, headers=response_headers) 22 | 23 | 24 | @router.post("/{gameinfo}/op3_common/get_music_info") 25 | async def op3_common_get_music_info(request: Request): 26 | request_info = await core_process_request(request) 27 | 28 | songs = {} 29 | 30 | revision = "21261" 31 | release_code = "2021090800" 32 | 33 | for f in ( 34 | path.join("modules", "nostalgia", "music_list.xml"), 35 | path.join("music_list.xml"), 36 | ): 37 | if path.exists(f): 38 | with open(f, "r", encoding="shift_jisx0213") as fp: 39 | 40 | tree = ET.parse(fp, ET.XMLParser()) 41 | root = tree.getroot() 42 | 43 | revision = root.get("revision") 44 | release_code = root.get("release_code") 45 | 46 | for entry in root: 47 | mid = entry.get("index") 48 | songs[mid] = {} 49 | for atr in ( 50 | "priority", 51 | "category_flag", 52 | "primary_category", 53 | "level_normal", 54 | "level_hard", 55 | "level_extreme", 56 | "level_real", 57 | "demo_popular", 58 | "demo_bemani", 59 | "destination_j", 60 | "destination_a", 61 | "destination_y", 62 | "destination_k", 63 | "offline", 64 | "unlock_type", 65 | "volume_bgm", 66 | "volume_key", 67 | "jk_jpn", 68 | "jk_asia", 69 | "jk_kor", 70 | "jk_idn", 71 | "real_unlock_type", 72 | "real_once_price", 73 | "real_forever_price", 74 | ): 75 | songs[mid][atr] = entry.find(atr).text 76 | break 77 | 78 | response = E.response( 79 | E.get_music_info( 80 | E.music_list( 81 | *[ 82 | E.music_spec( 83 | E.basename("", __type="str"), 84 | E.title("", __type="str"), 85 | E.title_kana("", __type="str"), 86 | E.artist("", __type="str"), 87 | E.artist_kana("", __type="str"), 88 | E.license("", __type="str"), 89 | E.license_site("", __type="str"), 90 | E.priority(songs[s]["priority"], __type="s8"), 91 | E.category_flag(songs[s]["category_flag"], __type="s32"), 92 | E.primary_category(songs[s]["primary_category"], __type="s8"), 93 | E.level_normal(songs[s]["level_normal"], __type="s8"), 94 | E.level_hard(songs[s]["level_hard"], __type="s8"), 95 | E.level_extreme(songs[s]["level_extreme"], __type="s8"), 96 | E.level_real(songs[s]["level_real"], __type="s8"), 97 | E.demo_popular(songs[s]["demo_popular"], __type="bool"), 98 | E.demo_bemani(songs[s]["demo_bemani"], __type="bool"), 99 | E.destination_j(songs[s]["destination_j"], __type="bool"), 100 | E.destination_a(songs[s]["destination_a"], __type="bool"), 101 | E.destination_y(songs[s]["destination_y"], __type="bool"), 102 | E.destination_k(songs[s]["destination_k"], __type="bool"), 103 | E.offline(songs[s]["offline"], __type="bool"), 104 | E.unlock_type(songs[s]["unlock_type"], __type="s8"), 105 | E.volume_bgm(songs[s]["volume_bgm"], __type="s8"), 106 | E.volume_key(songs[s]["volume_key"], __type="s8"), 107 | E.start_date("2017-03-01 10:00", __type="str"), 108 | E.end_date("9999-12-31 23:59", __type="str"), 109 | E.expiration_date("9999-12-31 23:59", __type="str"), 110 | E.description("", __type="str"), 111 | index=s, 112 | ) 113 | for s in songs 114 | ], 115 | revision=revision, 116 | release_code=release_code, 117 | ), 118 | E.overwrite_music_list( 119 | *[ 120 | E.music_spec( 121 | E.jk_jpn(songs[s]["jk_jpn"], __type="bool"), 122 | E.jk_asia(songs[s]["jk_asia"], __type="bool"), 123 | E.jk_kor(songs[s]["jk_kor"], __type="bool"), 124 | E.jk_idn(songs[s]["jk_idn"], __type="bool"), 125 | E.unlock_type(songs[s]["unlock_type"], __type="s8"), 126 | E.real_unlock_type(songs[s]["real_unlock_type"], __type="s8"), 127 | E.start_date("2017-03-01 10:00", __type="str"), 128 | E.end_date("9999-12-31 23:59", __type="str"), 129 | E.real_once_price(songs[s]["real_once_price"], __type="s32"), 130 | E.real_forever_price( 131 | songs[s]["real_forever_price"], __type="s32" 132 | ), 133 | E.real_start_date("2017-03-01 10:00", __type="str"), 134 | E.real_end_date("9999-12-31 23:59", __type="str"), 135 | index=s, 136 | ) 137 | for s in songs 138 | ], 139 | revision=revision, 140 | release_code=release_code, 141 | ), 142 | E.permitted_list( 143 | E.flag([-1] * 32, __type="s32", sheet_type="0"), 144 | E.flag([-1] * 32, __type="s32", sheet_type="1"), 145 | E.flag([-1] * 32, __type="s32", sheet_type="2"), 146 | E.flag([-1] * 32, __type="s32", sheet_type="3"), 147 | ), 148 | E.gamedata_flag_list(), 149 | E.trend_music_list(E.trend_music(music_index=1, rank=1)), 150 | ) 151 | ) 152 | 153 | response_body, response_headers = await core_prepare_response(request, response) 154 | return Response(content=response_body, headers=response_headers) 155 | -------------------------------------------------------------------------------- /modules/iidx/iidx32gamesystem.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/local", tags=["local"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | @router.post("/{gameinfo}/IIDX32gameSystem/systemInfo") 14 | async def iidx32gamesystem_systeminfo(request: Request): 15 | request_info = await core_process_request(request) 16 | 17 | unlock = () 18 | # force unlock LM exclusives to complete unlock all songs server side 19 | # this makes LM exclusive folder disappear, so just use hex edits 20 | # unlock = (30106, 31084, 30077, 31085, 30107, 30028, 30076, 31083, 30098) 21 | 22 | current_time = round(time()) 23 | 24 | response = E.response( 25 | E.IIDX32gameSystem( 26 | # E.option_2pp(), 27 | *[ 28 | E.music_open( 29 | E.music_id(mid, __type="s32"), 30 | E.kind(0, __type="s32"), 31 | ) 32 | for mid in unlock 33 | ], 34 | E.grade_course( 35 | E.play_style(0, __type="s32"), 36 | E.grade_id(15, __type="s32"), 37 | E.music_id_0(19022, __type="s32"), 38 | E.class_id_0(3, __type="s32"), 39 | E.music_id_1(23068, __type="s32"), 40 | E.class_id_1(3, __type="s32"), 41 | E.music_id_2(27013, __type="s32"), 42 | E.class_id_2(3, __type="s32"), 43 | E.music_id_3(29045, __type="s32"), 44 | E.class_id_3(3, __type="s32"), 45 | E.is_valid(1, __type="bool"), 46 | ), 47 | E.grade_course( 48 | E.play_style(0, __type="s32"), 49 | E.grade_id(16, __type="s32"), 50 | E.music_id_0(27034, __type="s32"), 51 | E.class_id_0(3, __type="s32"), 52 | E.music_id_1(24023, __type="s32"), 53 | E.class_id_1(3, __type="s32"), 54 | E.music_id_2(16009, __type="s32"), 55 | E.class_id_2(3, __type="s32"), 56 | E.music_id_3(25085, __type="s32"), 57 | E.class_id_3(3, __type="s32"), 58 | E.is_valid(1, __type="bool"), 59 | ), 60 | E.grade_course( 61 | E.play_style(0, __type="s32"), 62 | E.grade_id(17, __type="s32"), 63 | E.music_id_0(26087, __type="s32"), 64 | E.class_id_0(3, __type="s32"), 65 | E.music_id_1(19002, __type="s32"), 66 | E.class_id_1(3, __type="s32"), 67 | E.music_id_2(29050, __type="s32"), 68 | E.class_id_2(3, __type="s32"), 69 | E.music_id_3(30024, __type="s32"), 70 | E.class_id_3(3, __type="s32"), 71 | E.is_valid(1, __type="bool"), 72 | ), 73 | E.grade_course( 74 | E.play_style(0, __type="s32"), 75 | E.grade_id(18, __type="s32"), 76 | E.music_id_0(30052, __type="s32"), 77 | E.class_id_0(3, __type="s32"), 78 | E.music_id_1(18032, __type="s32"), 79 | E.class_id_1(3, __type="s32"), 80 | E.music_id_2(16020, __type="s32"), 81 | E.class_id_2(3, __type="s32"), 82 | E.music_id_3(12004, __type="s32"), 83 | E.class_id_3(3, __type="s32"), 84 | E.is_valid(1, __type="bool"), 85 | ), 86 | E.grade_course( 87 | E.play_style(1, __type="s32"), 88 | E.grade_id(15, __type="s32"), 89 | E.music_id_0(12002, __type="s32"), 90 | E.class_id_0(3, __type="s32"), 91 | E.music_id_1(31063, __type="s32"), 92 | E.class_id_1(3, __type="s32"), 93 | E.music_id_2(23046, __type="s32"), 94 | E.class_id_2(3, __type="s32"), 95 | E.music_id_3(30020, __type="s32"), 96 | E.class_id_3(3, __type="s32"), 97 | E.is_valid(1, __type="bool"), 98 | ), 99 | E.grade_course( 100 | E.play_style(1, __type="s32"), 101 | E.grade_id(16, __type="s32"), 102 | E.music_id_0(26106, __type="s32"), 103 | E.class_id_0(3, __type="s32"), 104 | E.music_id_1(14021, __type="s32"), 105 | E.class_id_1(3, __type="s32"), 106 | E.music_id_2(29052, __type="s32"), 107 | E.class_id_2(3, __type="s32"), 108 | E.music_id_3(23075, __type="s32"), 109 | E.class_id_3(3, __type="s32"), 110 | E.is_valid(1, __type="bool"), 111 | ), 112 | E.grade_course( 113 | E.play_style(1, __type="s32"), 114 | E.grade_id(17, __type="s32"), 115 | E.music_id_0(29042, __type="s32"), 116 | E.class_id_0(3, __type="s32"), 117 | E.music_id_1(26043, __type="s32"), 118 | E.class_id_1(3, __type="s32"), 119 | E.music_id_2(17017, __type="s32"), 120 | E.class_id_2(3, __type="s32"), 121 | E.music_id_3(28005, __type="s32"), 122 | E.class_id_3(3, __type="s32"), 123 | E.is_valid(1, __type="bool"), 124 | ), 125 | E.grade_course( 126 | E.play_style(1, __type="s32"), 127 | E.grade_id(18, __type="s32"), 128 | E.music_id_0(25007, __type="s32"), 129 | E.class_id_0(3, __type="s32"), 130 | E.music_id_1(29017, __type="s32"), 131 | E.class_id_1(3, __type="s32"), 132 | E.music_id_2(19002, __type="s32"), 133 | E.class_id_2(3, __type="s32"), 134 | E.music_id_3(9028, __type="s32"), 135 | E.class_id_3(3, __type="s32"), 136 | E.is_valid(1, __type="bool"), 137 | ), 138 | E.arena_schedule( 139 | E.season(1, __type="u8"), 140 | E.phase(4, __type="u8"), 141 | E.rule_type(0, __type="u8"), 142 | E.start(current_time - 600, __type="u32"), 143 | E.end(current_time + 600, __type="u32"), 144 | ), 145 | *[ 146 | E.arena_reward( 147 | E.index(unlock.index(mid), __type="s32"), 148 | E.cube_num((unlock.index(mid) + 1) * 50, __type="s32"), 149 | E.kind(0, __type="s32"), 150 | E.value(mid, __type="str"), 151 | ) 152 | for mid in unlock 153 | ], 154 | *[ 155 | E.arena_music_difficult( 156 | E.play_style(sp_dp, __type="s32"), 157 | E.arena_class(arena_class, __type="s32"), 158 | E.low_difficult(8, __type="s32"), 159 | E.high_difficult(12, __type="s32"), 160 | E.is_leggendaria(1, __type="bool"), 161 | E.force_music_list_id(0, __type="s32"), 162 | ) 163 | for sp_dp in (0, 1) 164 | for arena_class in range(20) 165 | ], 166 | *[ 167 | E.arena_cpu_define( 168 | E.play_style(sp_dp, __type="s32"), 169 | E.arena_class(arena_class, __type="s32"), 170 | E.grade_id(18, __type="s32"), 171 | E.low_music_difficult(8, __type="s32"), 172 | E.high_music_difficult(12, __type="s32"), 173 | E.is_leggendaria(0, __type="bool"), 174 | ) 175 | for sp_dp in (0, 1) 176 | for arena_class in range(20) 177 | ], 178 | *[ 179 | E.maching_class_range( 180 | E.play_style(sp_dp, __type="s32"), 181 | E.matching_class(arena_class, __type="s32"), 182 | E.low_arena_class(arena_class, __type="s32"), 183 | E.high_arena_class(arena_class, __type="s32"), 184 | ) 185 | for sp_dp in (0, 1) 186 | for arena_class in range(20) 187 | ], 188 | E.Event1Phase(val=0), 189 | E.isNewSongAnother12OpenFlg(val=1), 190 | E.isKiwamiOpenFlg(val=1), 191 | E.WorldTourismOpenList(val=-1), 192 | E.OldBPLBattleOpenPhase(val=3), 193 | E.BPLBattleOpenPhase(val=3), 194 | ) 195 | ) 196 | 197 | response_body, response_headers = await core_prepare_response(request, response) 198 | return Response(content=response_body, headers=response_headers) 199 | -------------------------------------------------------------------------------- /modules/iidx/iidx33gamesystem.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/local", tags=["local"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | @router.post("/{gameinfo}/IIDX33gameSystem/systemInfo") 14 | async def iidx33gamesystem_systeminfo(request: Request): 15 | request_info = await core_process_request(request) 16 | 17 | unlock = () 18 | # force unlock LM exclusives to complete unlock all songs server side 19 | # this makes LM exclusive folder disappear, so just use hex edits 20 | # unlock = (30106, 31084, 30077, 31085, 30107, 30028, 30076, 31083, 30098) 21 | 22 | current_time = round(time()) 23 | 24 | response = E.response( 25 | E.IIDX33gameSystem( 26 | # E.option_2pp(), 27 | *[ 28 | E.music_open( 29 | E.music_id(mid, __type="s32"), 30 | E.kind(0, __type="s32"), 31 | ) 32 | for mid in unlock 33 | ], 34 | E.grade_course( 35 | E.play_style(0, __type="s32"), 36 | E.grade_id(15, __type="s32"), 37 | E.music_id_0(19022, __type="s32"), 38 | E.class_id_0(3, __type="s32"), 39 | E.music_id_1(23068, __type="s32"), 40 | E.class_id_1(3, __type="s32"), 41 | E.music_id_2(27013, __type="s32"), 42 | E.class_id_2(3, __type="s32"), 43 | E.music_id_3(29045, __type="s32"), 44 | E.class_id_3(3, __type="s32"), 45 | E.is_valid(1, __type="bool"), 46 | ), 47 | E.grade_course( 48 | E.play_style(0, __type="s32"), 49 | E.grade_id(16, __type="s32"), 50 | E.music_id_0(27034, __type="s32"), 51 | E.class_id_0(3, __type="s32"), 52 | E.music_id_1(24023, __type="s32"), 53 | E.class_id_1(3, __type="s32"), 54 | E.music_id_2(16009, __type="s32"), 55 | E.class_id_2(3, __type="s32"), 56 | E.music_id_3(25085, __type="s32"), 57 | E.class_id_3(3, __type="s32"), 58 | E.is_valid(1, __type="bool"), 59 | ), 60 | E.grade_course( 61 | E.play_style(0, __type="s32"), 62 | E.grade_id(17, __type="s32"), 63 | E.music_id_0(26087, __type="s32"), 64 | E.class_id_0(3, __type="s32"), 65 | E.music_id_1(19002, __type="s32"), 66 | E.class_id_1(3, __type="s32"), 67 | E.music_id_2(29050, __type="s32"), 68 | E.class_id_2(3, __type="s32"), 69 | E.music_id_3(30024, __type="s32"), 70 | E.class_id_3(3, __type="s32"), 71 | E.is_valid(1, __type="bool"), 72 | ), 73 | E.grade_course( 74 | E.play_style(0, __type="s32"), 75 | E.grade_id(18, __type="s32"), 76 | E.music_id_0(30052, __type="s32"), 77 | E.class_id_0(3, __type="s32"), 78 | E.music_id_1(18032, __type="s32"), 79 | E.class_id_1(3, __type="s32"), 80 | E.music_id_2(16020, __type="s32"), 81 | E.class_id_2(3, __type="s32"), 82 | E.music_id_3(12004, __type="s32"), 83 | E.class_id_3(3, __type="s32"), 84 | E.is_valid(1, __type="bool"), 85 | ), 86 | E.grade_course( 87 | E.play_style(1, __type="s32"), 88 | E.grade_id(15, __type="s32"), 89 | E.music_id_0(12002, __type="s32"), 90 | E.class_id_0(3, __type="s32"), 91 | E.music_id_1(31063, __type="s32"), 92 | E.class_id_1(3, __type="s32"), 93 | E.music_id_2(23046, __type="s32"), 94 | E.class_id_2(3, __type="s32"), 95 | E.music_id_3(30020, __type="s32"), 96 | E.class_id_3(3, __type="s32"), 97 | E.is_valid(1, __type="bool"), 98 | ), 99 | E.grade_course( 100 | E.play_style(1, __type="s32"), 101 | E.grade_id(16, __type="s32"), 102 | E.music_id_0(26106, __type="s32"), 103 | E.class_id_0(3, __type="s32"), 104 | E.music_id_1(14021, __type="s32"), 105 | E.class_id_1(3, __type="s32"), 106 | E.music_id_2(29052, __type="s32"), 107 | E.class_id_2(3, __type="s32"), 108 | E.music_id_3(23075, __type="s32"), 109 | E.class_id_3(3, __type="s32"), 110 | E.is_valid(1, __type="bool"), 111 | ), 112 | E.grade_course( 113 | E.play_style(1, __type="s32"), 114 | E.grade_id(17, __type="s32"), 115 | E.music_id_0(29042, __type="s32"), 116 | E.class_id_0(3, __type="s32"), 117 | E.music_id_1(26043, __type="s32"), 118 | E.class_id_1(3, __type="s32"), 119 | E.music_id_2(17017, __type="s32"), 120 | E.class_id_2(3, __type="s32"), 121 | E.music_id_3(28005, __type="s32"), 122 | E.class_id_3(3, __type="s32"), 123 | E.is_valid(1, __type="bool"), 124 | ), 125 | E.grade_course( 126 | E.play_style(1, __type="s32"), 127 | E.grade_id(18, __type="s32"), 128 | E.music_id_0(25007, __type="s32"), 129 | E.class_id_0(3, __type="s32"), 130 | E.music_id_1(29017, __type="s32"), 131 | E.class_id_1(3, __type="s32"), 132 | E.music_id_2(19002, __type="s32"), 133 | E.class_id_2(3, __type="s32"), 134 | E.music_id_3(9028, __type="s32"), 135 | E.class_id_3(3, __type="s32"), 136 | E.is_valid(1, __type="bool"), 137 | ), 138 | E.arena_schedule( 139 | E.season(1, __type="u8"), 140 | E.phase(4, __type="u8"), 141 | E.rule_type(0, __type="u8"), 142 | E.start(current_time - 600, __type="u32"), 143 | E.end(current_time + 600, __type="u32"), 144 | ), 145 | *[ 146 | E.arena_reward( 147 | E.index(unlock.index(mid), __type="s32"), 148 | E.cube_num((unlock.index(mid) + 1) * 50, __type="s32"), 149 | E.kind(0, __type="s32"), 150 | E.value(mid, __type="str"), 151 | ) 152 | for mid in unlock 153 | ], 154 | *[ 155 | E.arena_music_difficult( 156 | E.play_style(sp_dp, __type="s32"), 157 | E.arena_class(arena_class, __type="s32"), 158 | E.low_difficult(8, __type="s32"), 159 | E.high_difficult(12, __type="s32"), 160 | E.is_leggendaria(1, __type="bool"), 161 | E.force_music_list_id(0, __type="s32"), 162 | ) 163 | for sp_dp in (0, 1) 164 | for arena_class in range(20) 165 | ], 166 | *[ 167 | E.arena_cpu_define( 168 | E.play_style(sp_dp, __type="s32"), 169 | E.arena_class(arena_class, __type="s32"), 170 | E.grade_id(18, __type="s32"), 171 | E.low_music_difficult(8, __type="s32"), 172 | E.high_music_difficult(12, __type="s32"), 173 | E.is_leggendaria(0, __type="bool"), 174 | ) 175 | for sp_dp in (0, 1) 176 | for arena_class in range(20) 177 | ], 178 | *[ 179 | E.maching_class_range( 180 | E.play_style(sp_dp, __type="s32"), 181 | E.matching_class(arena_class, __type="s32"), 182 | E.low_arena_class(arena_class, __type="s32"), 183 | E.high_arena_class(arena_class, __type="s32"), 184 | ) 185 | for sp_dp in (0, 1) 186 | for arena_class in range(20) 187 | ], 188 | E.Event1Phase(val=0), 189 | E.isNewSongAnother12OpenFlg(val=1), 190 | E.isKiwamiOpenFlg(val=1), 191 | E.WorldTourismOpenList(val=-1), 192 | E.OldBPLBattleOpenPhase(val=1), 193 | E.BPLBattleOpenPhase(val=3), 194 | E.beat(val=0), 195 | ) 196 | ) 197 | 198 | response_body, response_headers = await core_prepare_response(request, response) 199 | return Response(content=response_body, headers=response_headers) 200 | -------------------------------------------------------------------------------- /modules/iidx/iidx31gamesystem.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | import config 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | 9 | router = APIRouter(prefix="/local2", tags=["local2"]) 10 | router.model_whitelist = ["LDJ"] 11 | 12 | 13 | @router.post("/{gameinfo}/IIDX31gameSystem/systemInfo") 14 | async def iidx31gamesystem_systeminfo(request: Request): 15 | request_info = await core_process_request(request) 16 | 17 | unlock = () 18 | # force unlock LM exclusives to complete unlock all songs server side 19 | # this makes LM exclusive folder disappear, so just use hex edits 20 | # unlock = (30106, 31084, 30077, 31085, 30107, 30028, 30076, 31083, 30098) 21 | 22 | current_time = round(time()) 23 | 24 | response = E.response( 25 | E.IIDX31gameSystem( 26 | *[ 27 | E.music_open( 28 | E.music_id(mid, __type="s32"), 29 | E.kind(0, __type="s32"), 30 | ) 31 | for mid in unlock 32 | ], 33 | E.grade_course( 34 | E.play_style(0, __type="s32"), 35 | E.grade_id(15, __type="s32"), 36 | E.music_id_0(25090, __type="s32"), 37 | E.class_id_0(3, __type="s32"), 38 | E.music_id_1(23068, __type="s32"), 39 | E.class_id_1(3, __type="s32"), 40 | E.music_id_2(19004, __type="s32"), 41 | E.class_id_2(3, __type="s32"), 42 | E.music_id_3(29045, __type="s32"), 43 | E.class_id_3(3, __type="s32"), 44 | E.is_valid(1, __type="bool"), 45 | ), 46 | E.grade_course( 47 | E.play_style(0, __type="s32"), 48 | E.grade_id(16, __type="s32"), 49 | E.music_id_0(23005, __type="s32"), 50 | E.class_id_0(3, __type="s32"), 51 | E.music_id_1(27078, __type="s32"), 52 | E.class_id_1(3, __type="s32"), 53 | E.music_id_2(22065, __type="s32"), 54 | E.class_id_2(3, __type="s32"), 55 | E.music_id_3(27060, __type="s32"), 56 | E.class_id_3(3, __type="s32"), 57 | E.is_valid(1, __type="bool"), 58 | ), 59 | E.grade_course( 60 | E.play_style(0, __type="s32"), 61 | E.grade_id(17, __type="s32"), 62 | E.music_id_0(29007, __type="s32"), 63 | E.class_id_0(3, __type="s32"), 64 | E.music_id_1(26108, __type="s32"), 65 | E.class_id_1(3, __type="s32"), 66 | E.music_id_2(19002, __type="s32"), 67 | E.class_id_2(3, __type="s32"), 68 | E.music_id_3(18004, __type="s32"), 69 | E.class_id_3(3, __type="s32"), 70 | E.is_valid(1, __type="bool"), 71 | ), 72 | E.grade_course( 73 | E.play_style(0, __type="s32"), 74 | E.grade_id(18, __type="s32"), 75 | E.music_id_0(25007, __type="s32"), 76 | E.class_id_0(3, __type="s32"), 77 | E.music_id_1(18032, __type="s32"), 78 | E.class_id_1(3, __type="s32"), 79 | E.music_id_2(16020, __type="s32"), 80 | E.class_id_2(3, __type="s32"), 81 | E.music_id_3(12004, __type="s32"), 82 | E.class_id_3(3, __type="s32"), 83 | E.is_valid(1, __type="bool"), 84 | ), 85 | E.grade_course( 86 | E.play_style(1, __type="s32"), 87 | E.grade_id(15, __type="s32"), 88 | E.music_id_0(15032, __type="s32"), 89 | E.class_id_0(3, __type="s32"), 90 | E.music_id_1(29033, __type="s32"), 91 | E.class_id_1(3, __type="s32"), 92 | E.music_id_2(27092, __type="s32"), 93 | E.class_id_2(3, __type="s32"), 94 | E.music_id_3(30020, __type="s32"), 95 | E.class_id_3(3, __type="s32"), 96 | E.is_valid(1, __type="bool"), 97 | ), 98 | E.grade_course( 99 | E.play_style(1, __type="s32"), 100 | E.grade_id(16, __type="s32"), 101 | E.music_id_0(10028, __type="s32"), 102 | E.class_id_0(3, __type="s32"), 103 | E.music_id_1(26070, __type="s32"), 104 | E.class_id_1(3, __type="s32"), 105 | E.music_id_2(28091, __type="s32"), 106 | E.class_id_2(3, __type="s32"), 107 | E.music_id_3(23075, __type="s32"), 108 | E.class_id_3(3, __type="s32"), 109 | E.is_valid(1, __type="bool"), 110 | ), 111 | E.grade_course( 112 | E.play_style(1, __type="s32"), 113 | E.grade_id(17, __type="s32"), 114 | E.music_id_0(26012, __type="s32"), 115 | E.class_id_0(3, __type="s32"), 116 | E.music_id_1(28002, __type="s32"), 117 | E.class_id_1(3, __type="s32"), 118 | E.music_id_2(17017, __type="s32"), 119 | E.class_id_2(3, __type="s32"), 120 | E.music_id_3(28005, __type="s32"), 121 | E.class_id_3(3, __type="s32"), 122 | E.is_valid(1, __type="bool"), 123 | ), 124 | E.grade_course( 125 | E.play_style(1, __type="s32"), 126 | E.grade_id(18, __type="s32"), 127 | E.music_id_0(28008, __type="s32"), 128 | E.class_id_0(3, __type="s32"), 129 | E.music_id_1(15001, __type="s32"), 130 | E.class_id_1(3, __type="s32"), 131 | E.music_id_2(19002, __type="s32"), 132 | E.class_id_2(3, __type="s32"), 133 | E.music_id_3(9028, __type="s32"), 134 | E.class_id_3(3, __type="s32"), 135 | E.is_valid(1, __type="bool"), 136 | ), 137 | E.arena_schedule( 138 | E.phase(3, __type="u8"), 139 | E.rule_type(0, __type="u8"), 140 | E.start(current_time - 600, __type="u32"), 141 | E.end(current_time + 600, __type="u32"), 142 | ), 143 | *[ 144 | E.arena_reward( 145 | E.index(unlock.index(mid), __type="s32"), 146 | E.cube_num((unlock.index(mid) + 1) * 50, __type="s32"), 147 | E.kind(0, __type="s32"), 148 | E.value(mid, __type="str"), 149 | ) 150 | for mid in unlock 151 | ], 152 | *[ 153 | E.arena_music_difficult( 154 | E.play_style(sp_dp, __type="s32"), 155 | E.arena_class(arena_class, __type="s32"), 156 | E.low_difficult(8, __type="s32"), 157 | E.high_difficult(12, __type="s32"), 158 | E.is_leggendaria(1, __type="bool"), 159 | E.force_music_list_id(0, __type="s32"), 160 | ) 161 | for sp_dp in (0, 1) 162 | for arena_class in range(20) 163 | ], 164 | *[ 165 | E.arena_cpu_define( 166 | E.play_style(sp_dp, __type="s32"), 167 | E.arena_class(arena_class, __type="s32"), 168 | E.grade_id(18, __type="s32"), 169 | E.low_music_difficult(8, __type="s32"), 170 | E.high_music_difficult(12, __type="s32"), 171 | E.is_leggendaria(0, __type="bool"), 172 | ) 173 | for sp_dp in (0, 1) 174 | for arena_class in range(20) 175 | ], 176 | *[ 177 | E.maching_class_range( 178 | E.play_style(sp_dp, __type="s32"), 179 | E.matching_class(arena_class, __type="s32"), 180 | E.low_arena_class(arena_class, __type="s32"), 181 | E.high_arena_class(arena_class, __type="s32"), 182 | ) 183 | for sp_dp in (0, 1) 184 | for arena_class in range(20) 185 | ], 186 | E.CommonBossPhase(val=0), 187 | E.Event1Phase(val=0), 188 | E.Event1Value(val=0), 189 | E.Event2Phase(val=0), 190 | E.ExtraBossEventPhase(val=0), 191 | E.isNewSongAnother12OpenFlg(val=1), 192 | E.isKiwamiOpenFlg(val=1), 193 | E.WorldTourismOpenList(val=-1), 194 | E.OldBPLBattleOpenPhase(val=3), 195 | E.BPLBattleOpenPhase(val=3), 196 | E.UnlockLeggendaria(val=1), 197 | E.Event1AllPlayerTotalGetMetron(val=0), 198 | ) 199 | ) 200 | 201 | response_body, response_headers = await core_prepare_response(request, response) 202 | return Response(content=response_body, headers=response_headers) 203 | -------------------------------------------------------------------------------- /modules/ddr/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Request, Response, File, UploadFile 2 | 3 | from core_common import core_process_request, core_prepare_response, E 4 | 5 | from tinydb import Query, where 6 | from core_database import get_db 7 | from pydantic import BaseModel 8 | 9 | import config 10 | import utils.card as conv 11 | from utils.lz77 import EamuseLZ77 12 | 13 | import lxml.etree as ET 14 | import ujson as json 15 | import struct 16 | from typing import Dict, List, Tuple 17 | from os import path 18 | 19 | 20 | router = APIRouter(prefix="/ddr", tags=["api_ddr"]) 21 | 22 | 23 | class DDR_Profile_Main_Items(BaseModel): 24 | card: str 25 | pin: str 26 | 27 | 28 | class DDR_Profile_Version_Items(BaseModel): 29 | game_version: int 30 | calories_disp: bool 31 | character: str 32 | arrow_skin: str 33 | filter: str 34 | guideline: str 35 | priority: str 36 | timing_disp: bool 37 | common: str 38 | option: str 39 | last: str 40 | rival: str 41 | rival_1_ddr_id: int 42 | rival_2_ddr_id: int 43 | rival_3_ddr_id: int 44 | single_grade: int 45 | double_grade: int 46 | 47 | 48 | @router.get("/profiles") 49 | async def ddr_profiles(): 50 | return get_db().table("ddr_profile").all() 51 | 52 | 53 | @router.get("/profiles/{ddr_id}") 54 | async def ddr_profile_id(ddr_id: str): 55 | ddr_id = int("".join([i for i in ddr_id if i.isnumeric()])) 56 | return get_db().table("ddr_profile").get(where("ddr_id") == ddr_id) 57 | 58 | 59 | @router.patch("/profiles/{ddr_id}") 60 | async def ddr_profile_id_patch(ddr_id: str, item: DDR_Profile_Main_Items): 61 | ddr_id = int("".join([i for i in ddr_id if i.isnumeric()])) 62 | profile = get_db().table("ddr_profile").get(where("ddr_id") == ddr_id) 63 | 64 | profile["card"] = item.card 65 | profile["pin"] = item.pin 66 | 67 | get_db().table("ddr_profile").upsert(profile, where("ddr_id") == ddr_id) 68 | return Response(status_code=204) 69 | 70 | 71 | @router.patch("/profiles/{ddr_id}/{version}") 72 | async def ddr_profile_id_version_patch( 73 | ddr_id: str, version: int, item: DDR_Profile_Version_Items 74 | ): 75 | ddr_id = int("".join([i for i in ddr_id if i.isnumeric()])) 76 | profile = get_db().table("ddr_profile").get(where("ddr_id") == ddr_id) 77 | game_profile = profile["version"].get(str(version), {}) 78 | 79 | if version >= 19: 80 | game_profile["game_version"] = item.game_version 81 | game_profile["calories_disp"] = "On" if item.calories_disp else "Off" 82 | game_profile["character"] = item.character 83 | game_profile["arrow_skin"] = item.arrow_skin 84 | game_profile["filter"] = item.filter 85 | game_profile["guideline"] = item.guideline 86 | game_profile["priority"] = item.priority 87 | game_profile["timing_disp"] = "On" if item.timing_disp else "Off" 88 | game_profile["common"] = item.common 89 | game_profile["option"] = item.option 90 | game_profile["last"] = item.last 91 | game_profile["rival"] = item.rival 92 | game_profile["rival_1_ddr_id"] = item.rival_1_ddr_id 93 | game_profile["rival_2_ddr_id"] = item.rival_2_ddr_id 94 | game_profile["rival_3_ddr_id"] = item.rival_3_ddr_id 95 | 96 | profile["version"][str(version)] = game_profile 97 | get_db().table("ddr_profile").upsert(profile, where("ddr_id") == ddr_id) 98 | return Response(status_code=204) 99 | 100 | 101 | @router.get("/card/{card}") 102 | async def ddr_card_to_profile(card: str): 103 | card = card.upper() 104 | lookalike = { 105 | "I": "1", 106 | "O": "0", 107 | "Q": "0", 108 | "V": "U", 109 | } 110 | for k, v in lookalike.items(): 111 | card = card.replace(k, v) 112 | if card.startswith("E004") or card.startswith("012E"): 113 | card = "".join([c for c in card if c in "0123456789ABCDEF"]) 114 | uid = card 115 | kid = conv.to_konami_id(card) 116 | else: 117 | card = "".join([c for c in card if c in conv.valid_characters]) 118 | uid = conv.to_uid(card) 119 | kid = card 120 | profile = get_db().table("ddr_profile").get(where("card") == uid) 121 | return profile 122 | 123 | 124 | @router.get("/scores") 125 | async def ddr_scores(): 126 | return get_db().table("ddr_scores").all() 127 | 128 | 129 | @router.get("/scores/{ddr_id}") 130 | async def ddr_scores_id(ddr_id: str): 131 | ddr_id = int("".join([i for i in ddr_id if i.isnumeric()])) 132 | return get_db().table("ddr_scores").search((where("ddr_id") == ddr_id)) 133 | 134 | 135 | @router.get("/scores_best") 136 | async def ddr_scores_best(): 137 | return get_db().table("ddr_scores_best").all() 138 | 139 | 140 | @router.get("/scores_best/{ddr_id}") 141 | async def ddr_scores_best_id(ddr_id: str): 142 | ddr_id = int("".join([i for i in ddr_id if i.isnumeric()])) 143 | return get_db().table("ddr_scores_best").search((where("ddr_id") == ddr_id)) 144 | 145 | 146 | @router.get("/mcode/{mcode}/all") 147 | async def ddr_scores_id(mcode: int): 148 | return get_db().table("ddr_scores").search((where("mcode") == mcode)) 149 | 150 | 151 | @router.get("/mcode/{mcode}/best") 152 | async def ddr_scores_id_best(mcode: int): 153 | return get_db().table("ddr_scores_best").search((where("mcode") == mcode)) 154 | 155 | 156 | class ARC: 157 | # https://github.com/DragonMinded/bemaniutils/blob/trunk/bemani/format/arc.py 158 | """ 159 | Class representing an `.arc` file. These are found in DDR Ace, and possibly 160 | other games that use ESS. Given a serires of bytes, this will allow you to 161 | query included filenames as well as read the contents of any file inside the 162 | archive. 163 | """ 164 | 165 | def __init__(self, data: bytes) -> None: 166 | self.__files: Dict[str, Tuple[int, int, int]] = {} 167 | self.__data = data 168 | self.__parse_file(data) 169 | 170 | def __parse_file(self, data: bytes) -> None: 171 | # Check file header 172 | if data[0:4] != bytes([0x20, 0x11, 0x75, 0x19]): 173 | # raise Exception("Unknown file format!") 174 | return Response(status_code=406) 175 | 176 | # Grab header offsets 177 | (_, numfiles, _) = struct.unpack(" List[str]: 195 | return [f for f in self.__files] 196 | 197 | def read_file(self, filename: str) -> bytes: 198 | (fileoffset, uncompressedsize, compressedsize) = self.__files[filename] 199 | 200 | if compressedsize == uncompressedsize: 201 | # Just stored 202 | return self.__data[fileoffset : (fileoffset + compressedsize)] 203 | else: 204 | # Compressed 205 | return EamuseLZ77.decode( 206 | self.__data[fileoffset : (fileoffset + compressedsize)] 207 | ) 208 | 209 | 210 | @router.post("/parse_mdb/upload") 211 | async def ddr_receive_mdb(file: UploadFile = File(...)) -> bytes: 212 | data = await file.read() 213 | arc = ARC(data) 214 | try: 215 | mdb_new = ET.fromstring( 216 | arc.read_file("data/gamedata/musicdb.xml"), 217 | parser=ET.XMLParser(encoding="utf-8"), 218 | ) 219 | except KeyError: 220 | return Response(status_code=406) 221 | 222 | def get_attr(attrname): 223 | try: 224 | mdb[mcode][attrname] = attr.find(attrname).text.rstrip() 225 | except AttributeError: 226 | mdb[mcode][attrname] = "" 227 | 228 | mdb = {} 229 | for attr in mdb_new: 230 | mcode = attr.find("mcode").text 231 | mdb[mcode] = {} 232 | 233 | attributes = ( 234 | "basename", 235 | "title", 236 | "title_yomi", 237 | "artist", 238 | "bpmmin", 239 | "bpmmax", 240 | "series", 241 | "eventno", 242 | "bemaniflag", 243 | "bgstage", 244 | "movie", 245 | "genreflag", 246 | "voice", 247 | ) 248 | 249 | for a in attributes: 250 | get_attr(a) 251 | 252 | mdb[mcode]["diffLv"] = attr.find("diffLv").text.split(" ") 253 | 254 | ddr_metadata = path.join("webui", "ddr.json") 255 | if path.exists(ddr_metadata): 256 | with open(ddr_metadata, "r", encoding="utf-8") as fp: 257 | mdb_old = json.load(fp) 258 | for mcode in mdb_old.keys(): 259 | mdb[mcode] = mdb_old[mcode] 260 | 261 | with open(ddr_metadata, "w", encoding="utf-8") as fp: 262 | json.dump(mdb, fp, indent=4, ensure_ascii=False, escape_forward_slashes=False) 263 | 264 | return Response(status_code=201) 265 | -------------------------------------------------------------------------------- /modules/gitadora/cardutil.py: -------------------------------------------------------------------------------- 1 | from tinydb import Query, where 2 | 3 | import random 4 | 5 | from fastapi import APIRouter, Request, Response 6 | 7 | from core_common import core_process_request, core_prepare_response, E 8 | from core_database import get_db 9 | 10 | router = APIRouter(prefix="/local", tags=["local"]) 11 | router.model_whitelist = ["M32"] 12 | 13 | 14 | def get_profile(cid): 15 | return get_db().table("gitadora_profile").get(where("card") == cid) 16 | 17 | 18 | def get_game_profile(cid, game_version): 19 | profile = get_profile(cid) 20 | 21 | return profile["version"].get(str(game_version), None) 22 | 23 | 24 | @router.post("/{gameinfo}/{ver}_cardutil/check") 25 | async def gitadora_cardutil_check(ver: str, request: Request): 26 | request_info = await core_process_request(request) 27 | game_version = request_info["game_version"] 28 | 29 | data = request_info["root"][0].find("player") 30 | 31 | no = int(data.attrib["no"]) 32 | 33 | dataid = data.find("refid").text 34 | 35 | profile = get_game_profile(dataid, game_version) 36 | 37 | if profile is None: 38 | state = 0 39 | name = "" 40 | did = 0 41 | else: 42 | state = 2 43 | name = profile["name"] 44 | did = 1 45 | 46 | response = E.response( 47 | E( 48 | f"{ver}_cardutil", 49 | E.player( 50 | E.name(name, __type="str"), 51 | E.charaid(0, __type="s32"), 52 | E.did(did, __type="s32"), 53 | E.skilldata( 54 | E.skill(0, __type="s32"), 55 | E.all_skill(0, __type="s32"), 56 | E.old_skill(0, __type="s32"), 57 | E.old_all_skill(0, __type="s32"), 58 | ), 59 | no=1, 60 | state=state, 61 | ), 62 | ) 63 | ) 64 | 65 | response_body, response_headers = await core_prepare_response(request, response) 66 | return Response(content=response_body, headers=response_headers) 67 | 68 | 69 | @router.post("/{gameinfo}/{ver}_cardutil/regist") 70 | async def gitadora_cardutil_regist(ver: str, request: Request): 71 | request_info = await core_process_request(request) 72 | game_version = request_info["game_version"] 73 | spec = request_info["spec"] 74 | 75 | data = request_info["root"][0].find("player") 76 | 77 | no = int(data.attrib["no"]) 78 | 79 | dataid = data.find("refid").text 80 | 81 | db = get_db().table("gitadora_profile") 82 | all_profiles_for_card = db.get(Query().card == dataid) 83 | 84 | if "gitadora_id" not in all_profiles_for_card: 85 | gitadora_id = random.randint(10000000, 99999999) 86 | all_profiles_for_card["gitadora_id"] = gitadora_id 87 | 88 | all_profiles_for_card["version"][str(game_version)] = { 89 | "game_version": game_version, 90 | "name": "kors k", 91 | "title": "MONKEY BUSINESS", 92 | "charaid": 0, 93 | "stickers": {}, 94 | "rival_card_ids": [], 95 | } 96 | 97 | for game_type in ("drummania", "guitarfreaks"): 98 | all_profiles_for_card["version"][str(game_version)][game_type] = { 99 | "customdata_playstyle": [ 100 | 0, 101 | 1, 102 | 0, 103 | 0, 104 | 0, 105 | 0, 106 | 0, 107 | 0, 108 | 0, 109 | 0, 110 | 0, 111 | 0, 112 | 0, 113 | 0, 114 | 0, 115 | 0, 116 | 0, 117 | 0, 118 | 0, 119 | 0, 120 | 0, 121 | 0, 122 | 0, 123 | 0, 124 | 0, 125 | 0, 126 | 0, 127 | 0, 128 | 0, 129 | 0, 130 | 0, 131 | 0, 132 | 0, 133 | 0, 134 | 0, 135 | 0, 136 | 20, 137 | 0, 138 | 0, 139 | 0, 140 | 0, 141 | 0, 142 | 0, 143 | 0, 144 | 0, 145 | 0, 146 | 0, 147 | 0, 148 | 20, 149 | 0, 150 | ], 151 | "customdata_custom": [0] * 50, 152 | "playinfo_cabid": 1, 153 | "playinfo_play": 0, 154 | "playinfo_playtime": 0, 155 | "playinfo_playterm": 0, 156 | "playinfo_session_cnt": 0, 157 | "playinfo_saved_cnt": 0, 158 | "playinfo_matching_num": 0, 159 | "playinfo_extra_stage": 0, 160 | "playinfo_extra_play": 0, 161 | "playinfo_extra_clear": 0, 162 | "playinfo_encore_play": 0, 163 | "playinfo_encore_clear": 0, 164 | "playinfo_pencore_play": 0, 165 | "playinfo_pencore_clear": 0, 166 | "playinfo_max_clear_diff": 0, 167 | "playinfo_max_full_diff": 0, 168 | "playinfo_max_exce_diff": 0, 169 | "playinfo_clear_num": 0, 170 | "playinfo_full_num": 0, 171 | "playinfo_exce_num": 0, 172 | "playinfo_no_num": 0, 173 | "playinfo_e_num": 0, 174 | "playinfo_d_num": 0, 175 | "playinfo_c_num": 0, 176 | "playinfo_b_num": 0, 177 | "playinfo_a_num": 0, 178 | "playinfo_s_num": 0, 179 | "playinfo_ss_num": 0, 180 | "playinfo_last_category": 0, 181 | "playinfo_last_musicid": 0, 182 | "playinfo_last_seq": 0, 183 | "playinfo_disp_level": 0, 184 | "tutorial_progress": 0, 185 | "tutorial_disp_state": 0, 186 | "information": [0] * 50, 187 | "reward": [0] * 50, 188 | "skilldata_skill": 0, 189 | "skilldata_allskill": 0, 190 | "groove_extra_gauge": 0, 191 | "groove_encore_gauge": 0, 192 | "groove_encore_cnt": 0, 193 | "groove_encore_success": 0, 194 | "groove_unlock_point": 0, 195 | "record_max_skill": 0, 196 | "record_max_all_skill": 0, 197 | "record_max_clear_diff": 0, 198 | "record_max_full_diff": 0, 199 | "record_max_exce_diff": 0, 200 | "record_max_clear_music_num": 0, 201 | "record_max_full_music_num": 0, 202 | "record_max_exce_music_num": 0, 203 | "record_max_clear_seq_num": 0, 204 | "record_max_classic_all_skill": 0, 205 | "record_diff_100_nr": 0, 206 | "record_diff_150_nr": 0, 207 | "record_diff_200_nr": 0, 208 | "record_diff_250_nr": 0, 209 | "record_diff_300_nr": 0, 210 | "record_diff_350_nr": 0, 211 | "record_diff_400_nr": 0, 212 | "record_diff_450_nr": 0, 213 | "record_diff_500_nr": 0, 214 | "record_diff_550_nr": 0, 215 | "record_diff_600_nr": 0, 216 | "record_diff_650_nr": 0, 217 | "record_diff_700_nr": 0, 218 | "record_diff_750_nr": 0, 219 | "record_diff_800_nr": 0, 220 | "record_diff_850_nr": 0, 221 | "record_diff_900_nr": 0, 222 | "record_diff_950_nr": 0, 223 | "record_diff_100_clear": [0] * 7, 224 | "record_diff_150_clear": [0] * 7, 225 | "record_diff_200_clear": [0] * 7, 226 | "record_diff_250_clear": [0] * 7, 227 | "record_diff_300_clear": [0] * 7, 228 | "record_diff_350_clear": [0] * 7, 229 | "record_diff_400_clear": [0] * 7, 230 | "record_diff_450_clear": [0] * 7, 231 | "record_diff_500_clear": [0] * 7, 232 | "record_diff_550_clear": [0] * 7, 233 | "record_diff_600_clear": [0] * 7, 234 | "record_diff_650_clear": [0] * 7, 235 | "record_diff_700_clear": [0] * 7, 236 | "record_diff_750_clear": [0] * 7, 237 | "record_diff_800_clear": [0] * 7, 238 | "record_diff_850_clear": [0] * 7, 239 | "record_diff_900_clear": [0] * 7, 240 | "record_diff_950_clear": [0] * 7, 241 | "favorite_music_list_1": [-1] * 100, 242 | "favorite_music_list_2": [-1] * 100, 243 | "favorite_music_list_3": [-1] * 100, 244 | "recommend_musicid_list": [-1] * 5, 245 | "thanks_medal_medal": 0, 246 | "thanks_medal_granted_total_medal": 0, 247 | # "skindata_skin": [0] * 100, 248 | } 249 | 250 | db.upsert(all_profiles_for_card, where("card") == dataid) 251 | 252 | response = E.response( 253 | E( 254 | f"{ver}_cardutil", 255 | E.player( 256 | E.is_succession(0, __type="bool"), 257 | E.did(1, __type="s32"), 258 | no=no, 259 | ), 260 | ) 261 | ) 262 | 263 | response_body, response_headers = await core_prepare_response(request, response) 264 | return Response(content=response_body, headers=response_headers) 265 | -------------------------------------------------------------------------------- /modules/gitadora/gameinfo.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from fastapi import APIRouter, Request, Response 4 | 5 | from core_common import core_process_request, core_prepare_response, E 6 | 7 | router = APIRouter(prefix="/local", tags=["local"]) 8 | router.model_whitelist = ["M32"] 9 | 10 | 11 | @router.post("/{gameinfo}/{ver}_gameinfo/get") 12 | async def gitadora_gameinfo_get(ver: str, request: Request): 13 | request_info = await core_process_request(request) 14 | game_version = request_info["game_version"] 15 | 16 | response = E.response( 17 | E( 18 | f"{ver}_gameinfo", 19 | E.now_date(round(time.time()), __type="u64"), 20 | E.extra( 21 | E.extra_lv(0, __type="s32" if game_version >= 10 else "u8"), 22 | E.extramusic( 23 | E.music( 24 | E.musicid(0, __type="s32"), 25 | E.get_border(0, __type="u8"), 26 | ), 27 | ), 28 | ), 29 | E.general_term( 30 | *[ 31 | E.termdata( 32 | E.type(f"general_{s}", __type="str"), 33 | E.term(1, __type="s32" if game_version >= 10 else "u8"), 34 | E.state(0, __type="s32"), 35 | E.start_date_ms(0, __type="u64"), 36 | E.end_date_ms(0, __type="u64"), 37 | ) 38 | for s in [ 39 | "paseli_lottery_info_2020", 40 | "50th_konami_logo", 41 | "mix_up_2022_info", 42 | "guitar_controller_reward_2021", 43 | "bpl_season3_info_2023", 44 | "knst_musicpack_17", 45 | "knst_sp_music_202203", 46 | "kac_11th_A_info", 47 | "kac_11th_B_info", 48 | "paseli_festival_info_2023_11", 49 | "bpl_season3_info_sdvx", 50 | "otobear_birthday", 51 | "custom_skip_dm", 52 | "ticket_contents_ver", 53 | "ultimate_mobile_2019_info", 54 | "cardconnect_champ", 55 | "kac_9th_info", 56 | "floor_break_info", 57 | ] 58 | ], 59 | ), 60 | *[ 61 | E( 62 | x, 63 | E.term(1, __type="u8"), 64 | E.start_date_ms(0, __type="u64"), 65 | E.end_date_ms(0, __type="u64"), 66 | ) 67 | for x in [ 68 | "phrase_combo_challenge", 69 | "sdvx_stamprally3", 70 | "chronicle_1", 71 | "paseli_point_lottery", 72 | ] 73 | ], 74 | *[ 75 | E( 76 | f"phrase_combo_challenge_{x}", 77 | E.term(1, __type="u8"), 78 | E.start_date_ms(0, __type="u64"), 79 | E.end_date_ms(0, __type="u64"), 80 | ) 81 | for x in range(2, 21) 82 | ], 83 | E.long_otobear_fes_1( 84 | E.term(1, __type="u8"), 85 | E.start_date_ms(0, __type="u64"), 86 | E.end_date_ms(0, __type="u64"), 87 | E.bonus_musicid(), 88 | ), 89 | E.monstar_subjugation( 90 | E.bonus_musicid(0, __type="s32"), 91 | *[ 92 | E( 93 | f"monstar_subjugation_{x}", 94 | E.term(1, __type="u8"), 95 | E.start_date_ms(0, __type="u64"), 96 | E.end_date_ms(0, __type="u64"), 97 | ) 98 | for x in range(1, 5) 99 | ], 100 | ), 101 | E.bear_fes( 102 | *[ 103 | E( 104 | f"bear_fes_{x}", 105 | E.term(1, __type="u8"), 106 | E.start_date_ms(0, __type="u64"), 107 | E.end_date_ms(0, __type="u64"), 108 | ) 109 | for x in range(1, 5) 110 | ], 111 | ), 112 | *[ 113 | E( 114 | f"kouyou_challenge_{x}", 115 | E.term(0, __type="u8"), 116 | E.bonus_musicid(0, __type="s32"), 117 | ) 118 | for x in range(1, 4) 119 | ], 120 | *[ 121 | E( 122 | x, 123 | E.term(1, __type="u8"), 124 | E.start_date_ms(0, __type="u64"), 125 | E.end_date_ms(0, __type="u64"), 126 | E.box_term( 127 | E.state(0, __type="u8"), 128 | ), 129 | ) 130 | for x in ["thanksgiving", "lotterybox"] 131 | ], 132 | E.sticker_campaign( 133 | E.term(0, __type="u8"), 134 | E.sticker_list(), 135 | ), 136 | E.infect_music( 137 | E.term(1, __type="u8"), 138 | ), 139 | E.unlock_challenge( 140 | E.term(0, __type="s32" if game_version >= 10 else "u8"), 141 | ), 142 | E.battle( 143 | E.term(1, __type="s32" if game_version >= 10 else "u8"), 144 | ), 145 | E.battle_chara( 146 | E.term(1, __type="s32" if game_version >= 10 else "u8"), 147 | ), 148 | E.data_ver_limit( 149 | E.term(0, __type="s32" if game_version >= 9 else "u8"), 150 | ), 151 | E.ea_pass_propel( 152 | E.term(0, __type="s32" if game_version >= 10 else "u8"), 153 | ), 154 | E.monthly_skill( 155 | E.term(0, __type="u8"), 156 | E.target_music( 157 | E.music( 158 | E.musicid(0, __type="s32"), 159 | ), 160 | ), 161 | ), 162 | E.update_prog( 163 | E.term(0, __type="s32" if game_version >= 10 else "u8"), 164 | ), 165 | E.rockwave(E.event_list()), 166 | E.livehouse( 167 | E.event_list(), 168 | E.bonus( 169 | E.term(0, __type="u8"), 170 | E.stage_bonus(0, __type="s32"), 171 | E.charm_bonus(0, __type="s32"), 172 | E.start_date_ms(0, __type="u64"), 173 | E.end_date_ms(0, __type="u64"), 174 | ), 175 | ), 176 | E.general_term(), 177 | E.jubeat_omiyage_challenge(), 178 | E.kac2017(), 179 | E.nostalgia_concert(), 180 | E.trbitemdata(), 181 | E.ctrl_movie(), 182 | E.ng_jacket(), 183 | E.ng_recommend_music(), 184 | E.ranking( 185 | E.skill_0_999(), 186 | E.skill_1000_1499(), 187 | E.skill_1500_1999(), 188 | E.skill_2000_2499(), 189 | E.skill_2500_2999(), 190 | E.skill_3000_3499(), 191 | E.skill_3500_3999(), 192 | E.skill_4000_4499(), 193 | E.skill_4500_4999(), 194 | E.skill_5000_5499(), 195 | E.skill_5500_5999(), 196 | E.skill_6000_6499(), 197 | E.skill_6500_6999(), 198 | E.skill_7000_7499(), 199 | E.skill_7500_7999(), 200 | E.skill_8000_8499(), 201 | E.skill_8500_9999(), 202 | E.total(), 203 | E.original(), 204 | E.bemani(), 205 | E.famous(), 206 | E.anime(), 207 | E.band(), 208 | E.western(), 209 | ), 210 | E.processing_report_state(0, __type="u8"), 211 | E.assert_report_state(0, __type="u8"), 212 | E.recommendmusic( 213 | E.music( 214 | E.musicid(0, __type="s32"), 215 | ), 216 | nr=1, 217 | ), 218 | E.demomusic(nr=0), 219 | E.event_skill(), 220 | E.temperature( 221 | E.is_send(0, __type="bool"), 222 | ), 223 | E.bemani_summer_2018( 224 | E.is_open(0, __type="bool"), 225 | ), 226 | E.kac2018( 227 | E.event( 228 | E.term(0, __type="s32"), 229 | E.since(0, __type="u64"), 230 | E.till(0, __type="u64"), 231 | E.is_open(0, __type="bool"), 232 | E.target_music( 233 | E.music_id([0] * 6, __type="s32"), 234 | ), 235 | ), 236 | ), 237 | ) 238 | ) 239 | 240 | response_body, response_headers = await core_prepare_response(request, response) 241 | return Response(content=response_body, headers=response_headers) 242 | -------------------------------------------------------------------------------- /utils/db/import_iidx_spice_automap.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import xml.etree.ElementTree as ET 3 | from enum import IntEnum 4 | 5 | from tinydb import TinyDB, where 6 | from tinydb.middlewares import CachingMiddleware 7 | from tinydb.storages import JSONStorage 8 | 9 | 10 | def main(automap_xml, version, monkey_db, iidx_id): 11 | class ClearFlags(IntEnum): 12 | NO_PLAY = 0 13 | FAILED = 1 14 | ASSIST_CLEAR = 2 15 | EASY_CLEAR = 3 16 | CLEAR = 4 17 | HARD_CLEAR = 5 18 | EX_HARD_CLEAR = 6 19 | FULL_COMBO = 7 20 | 21 | storage = CachingMiddleware(JSONStorage) 22 | storage.WRITE_CACHE_SIZE = 5000 23 | 24 | db = TinyDB( 25 | monkey_db, 26 | indent=2, 27 | encoding="utf-8", 28 | ensure_ascii=False, 29 | storage=storage, 30 | ) 31 | 32 | iidx_id = int(iidx_id.replace("-", "")) 33 | 34 | profile = db.table("iidx_profile").get(where("iidx_id") == iidx_id) 35 | if profile == None: 36 | raise SystemExit(f"ERROR: IIDX profile {iidx_id} not in {monkey_db}") 37 | 38 | game_version = 30 39 | 40 | scores = [] 41 | 42 | with open(automap_xml, "rb") as fp: 43 | automap_0 = fp.read().split(b"\n\n") 44 | 45 | scores_xml = False 46 | for xml in automap_0: 47 | try: 48 | tree = ET.ElementTree(ET.fromstring(xml.decode(encoding="shift-jis"))) 49 | root = tree.getroot() 50 | except: 51 | continue 52 | if scores_xml: 53 | sp_dp = int(root.find(f"IIDX{version}music/style").get("type")) 54 | print(sp_dp) 55 | for m in root.findall(f"IIDX{version}music/m"): 56 | score = [int(x) for x in m.text.split()] 57 | if score[0] != -1: 58 | # skip rivals 59 | continue 60 | music_id = score[1] 61 | for difficulty in range(5): 62 | d = difficulty + 2 63 | if score[d] != -1: 64 | clear_flg = score[d] 65 | ex_score = score[d + 5] 66 | miss_count = score[d + 10] 67 | scores.append( 68 | [ 69 | sp_dp, 70 | music_id, 71 | difficulty, 72 | clear_flg, 73 | ex_score, 74 | miss_count, 75 | ] 76 | ) 77 | scores_xml = False 78 | else: 79 | try: 80 | if root.find(f"IIDX{version}music").get("method") == "getrank": 81 | scores_xml = True 82 | except AttributeError: 83 | continue 84 | 85 | total_count = len(scores) 86 | 87 | if total_count == 0: 88 | raise SystemExit("ERROR: No scores to import") 89 | 90 | for s in scores: 91 | play_style = s[0] 92 | music_id = s[1] 93 | difficulty = s[2] 94 | clear_flg = s[3] 95 | ex_score = s[4] 96 | miss_count = s[5] 97 | 98 | print( 99 | f"music_id: {music_id}, sp_dp: {play_style}, difficulty: {difficulty}, clear_flg: {clear_flg}, ex_score: {ex_score}, miss_count: {miss_count}" 100 | ) 101 | 102 | best_score = db.table("iidx_scores_best").get( 103 | (where("iidx_id") == iidx_id) 104 | & (where("play_style") == play_style) 105 | & (where("music_id") == music_id) 106 | & (where("chart_id") == difficulty) 107 | ) 108 | best_score = {} if best_score is None else best_score 109 | 110 | if clear_flg < ClearFlags.EASY_CLEAR: 111 | miss_count = -1 112 | best_miss_count = best_score.get("miss_count", miss_count) 113 | if best_miss_count == -1: 114 | miss_count = max(miss_count, best_miss_count) 115 | elif clear_flg > ClearFlags.ASSIST_CLEAR: 116 | miss_count = min(miss_count, best_miss_count) 117 | else: 118 | miss_count = best_miss_count 119 | best_ex_score = best_score.get("ex_score", ex_score) 120 | best_score_data = { 121 | "game_version": game_version, 122 | "iidx_id": iidx_id, 123 | "pid": 13, 124 | "play_style": play_style, 125 | "music_id": music_id, 126 | "chart_id": difficulty, 127 | "miss_count": miss_count, 128 | "ex_score": max(ex_score, best_ex_score), 129 | "ghost": best_score.get( 130 | "ghost", 131 | "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 132 | ), 133 | "ghost_gauge": best_score.get( 134 | "ghost_gauge", 135 | "4c0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 136 | ), 137 | "clear_flg": max(clear_flg, best_score.get("clear_flg", clear_flg)), 138 | "gauge_type": best_score.get("gauge_type", 4), 139 | } 140 | 141 | db.table("iidx_scores_best").upsert( 142 | best_score_data, 143 | (where("iidx_id") == iidx_id) 144 | & (where("play_style") == play_style) 145 | & (where("music_id") == music_id) 146 | & (where("chart_id") == difficulty), 147 | ) 148 | 149 | score_stats = db.table("iidx_score_stats").get( 150 | (where("music_id") == music_id) 151 | & (where("play_style") == play_style) 152 | & (where("chart_id") == difficulty) 153 | ) 154 | score_stats = {} if score_stats is None else score_stats 155 | 156 | score_stats["game_version"] = game_version 157 | score_stats["play_style"] = play_style 158 | score_stats["music_id"] = music_id 159 | score_stats["chart_id"] = difficulty 160 | score_stats["play_count"] = score_stats.get("play_count", 0) + 1 161 | score_stats["fc_count"] = score_stats.get("fc_count", 0) + ( 162 | 1 if clear_flg == ClearFlags.FULL_COMBO else 0 163 | ) 164 | score_stats["clear_count"] = score_stats.get("clear_count", 0) + ( 165 | 1 if clear_flg >= ClearFlags.EASY_CLEAR else 0 166 | ) 167 | score_stats["fc_rate"] = int( 168 | (score_stats["fc_count"] / score_stats["play_count"]) * 1000 169 | ) 170 | score_stats["clear_rate"] = int( 171 | (score_stats["clear_count"] / score_stats["play_count"]) * 1000 172 | ) 173 | 174 | db.table("iidx_score_stats").upsert( 175 | score_stats, 176 | (where("music_id") == music_id) 177 | & (where("play_style") == play_style) 178 | & (where("chart_id") == difficulty), 179 | ) 180 | 181 | db.close() 182 | print() 183 | print(f"{total_count} scores imported to IIDX profile {iidx_id} in {monkey_db}") 184 | 185 | 186 | if __name__ == "__main__": 187 | parser = argparse.ArgumentParser() 188 | parser.add_argument("--automap_xml", help="Input xml file", required=True) 189 | parser.add_argument( 190 | "--version", 191 | help="", 192 | default=30, 193 | type=int, 194 | ) 195 | parser.add_argument("--monkey_db", help="Output json file", required=True) 196 | parser.add_argument("--iidx_id", help="12345678", required=True) 197 | args = parser.parse_args() 198 | 199 | main(args.automap_xml, args.version, args.monkey_db, args.iidx_id) 200 | --------------------------------------------------------------------------------