├── 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 |
--------------------------------------------------------------------------------