Implement DRS

This commit is contained in:
drmext 2023-05-07 06:43:54 +00:00
parent 9126ffa211
commit 032cf5cd40
No known key found for this signature in database
GPG Key ID: F1ED48FFE79A6961
7 changed files with 521 additions and 6 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
*.pyc
mdb*.xml
*.xml
/db*.json
/*.db
.venv/

View File

@ -8,9 +8,9 @@ for experimental local testing and playing
## Instructions
1. Install [python](https://www.python.org/ftp/python/3.11.2/python-3.11.2-amd64.exe) with "Add python.exe to PATH" checked
1. Install [python](https://www.python.org/ftp/python/3.11.3/python-3.11.3-amd64.exe) with "Add python.exe to PATH" checked
1. Run `start.bat`
1. Run [start.bat (Windows)](start.bat) or [start.sh (Linux)](start.sh)
1. Edit prop/ea3-config.xml services *url* and url_slash *1*
@ -19,6 +19,8 @@ for experimental local testing and playing
- DDR A20 PLUS
- DDR A3
- DRS
- GITADORA 5 Matixx
- GITADORA 6 EXCHAIN
- GITADORA 7 NEX+AGE
@ -39,10 +41,12 @@ for experimental local testing and playing
- **URL Slash 1 (On)** must be enabled in tools or ea3-config
- GITADORA requires `mdb_*.xml` copied or symlinked to the server folder
- GITADORA requires `mdb_*.xml` copied to the server folder
- NOSTALGIA requires `music_list.xml` copied or symlinked to the server folder
- NOSTALGIA requires `music_list.xml` copied to the server folder
- DRS requires `music-info-base.xml` copied to the server folder
## Web Interface
- Extract [BounceTrippy](https://github.com/drmext/BounceTrippy/releases) webui to the server folder
- Extract [BounceTrippy](https://github.com/drmext/BounceTrippy/releases) webui to the server folder (DDR only)

View File

@ -106,6 +106,9 @@ async def core_get_game_version_from_software_version(software_version):
if ext >= 2020090402: # ???
return 6
elif model == "REC":
return 1
else:
return 0

View File

@ -14,6 +14,7 @@ def get_target_table(game_id):
"KFC": "sdvx_profile",
"M32": "gitadora_profile",
"PAN": "nostalgia_profile",
"REC": "dancerush_profile",
"JDZ": "iidx_profile",
"KDZ": "iidx_profile",
}

0
modules/drs/__init__.py Normal file
View File

25
modules/drs/eventlog.py Normal file
View File

@ -0,0 +1,25 @@
import config
from fastapi import APIRouter, Request, Response
from core_common import core_process_request, core_prepare_response, E
router = APIRouter(prefix="/local", tags=["local"])
router.model_whitelist = ["REC"]
@router.post("/{gameinfo}/eventlog/write")
async def drs_eventlog_write(request: Request):
request_info = await core_process_request(request)
response = E.response(
E.eventlog(
E.gamesession(9999999, __type="s64"),
E.logsendflg(0, __type="s32"),
E.logerrlevel(0, __type="s32"),
E.evtidnosendflg(0, __type="s32"),
)
)
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)

482
modules/drs/game.py Normal file
View File

@ -0,0 +1,482 @@
import xml.etree.ElementTree as ET
from os import path
from tinydb import Query, where
import config
import random
import time
from fastapi import APIRouter, Request, Response
from core_common import core_process_request, core_prepare_response, E
from core_database import get_db
router = APIRouter(prefix="/local", tags=["local"])
router.model_whitelist = ["REC"]
def get_profile(cid):
return get_db().table("dancerush_profile").get(where("card") == cid)
def get_game_profile(cid, game_version):
profile = get_profile(cid)
return profile["version"].get(str(game_version), None)
def get_id_from_profile(cid):
profile = get_db().table("dancerush_profile").get(where("card") == cid)
djid = "%08d" % profile["drs_id"]
djid_split = "-".join([djid[:4], djid[4:]])
return profile["drs_id"], djid_split
@router.post("/{gameinfo}/game/get_common")
async def drs_game_get_common(request: Request):
request_info = await core_process_request(request)
songs = {}
# TODO: server side song unlock is incomplete, use hex edits for now
for f in (
path.join("modules", "drs", "music-info-base.xml"),
path.join("music-info-base.xml"),
):
if path.exists(f):
with open(f, "r", encoding="utf-8") as fp:
tree = ET.parse(fp, ET.XMLParser())
root = tree.getroot()
for entry in root:
mid = entry.get("id")
songs[mid] = {}
for atr in (
"title_name",
"title_yomigana",
"artist_name",
"artist_yomigana",
"bpm_max",
"bpm_min",
# "distribution_date",
"volume",
"bg_no",
"region",
# "limitation_type",
# "price",
"genre",
"play_video_flags",
"is_fixed",
"version",
"demo_pri",
"license",
"color1",
"color2",
"color3",
):
songs[mid][atr] = entry.find(f"info/{atr}").text
if songs[mid][atr] == None:
songs[mid][atr] = ""
for atr in (
"1b",
"1a",
"2b",
"2a",
):
songs[mid][f"{atr}_difnum"] = entry.find(
f"difficulty/fumen_{atr}/difnum"
).text
# songs[mid][f"{atr}_playable"] = entry.find(f"difficulty/fumen_{atr}/playable").text
break
response = E.response(
E.game(
E.mdb(
*[
E.music(
E.info(
E.title_name(songs[s]["title_name"], __type="str"),
E.title_yomigana(songs[s]["title_yomigana"], __type="str"),
E.artist_name(songs[s]["artist_name"], __type="str"),
E.artist_yomigana(
songs[s]["artist_yomigana"], __type="str"
),
E.bpm_max(songs[s]["bpm_max"], __type="u32"),
E.bpm_min(songs[s]["bpm_min"], __type="u32"),
E.distribution_date(20180427, __type="u32"),
E.volume(songs[s]["volume"], __type="u16"),
E.bg_no(songs[s]["bg_no"], __type="u16"),
E.region("JUAKYC", __type="str"),
E.limitation_type(3, __type="u8"),
E.price(0, __type="s32"),
E.genre(songs[s]["genre"], __type="u32"),
E.play_video_flags(
songs[s]["play_video_flags"], __type="u32"
),
E.is_fixed(songs[s]["is_fixed"], __type="u8"),
E.version(songs[s]["version"], __type="u8"),
E.demo_pri(songs[s]["demo_pri"], __type="u8"),
E.license(songs[s]["license"], __type="str"),
E.color1(int(songs[s]["color1"], 16), __type="u32"),
E.color2(int(songs[s]["color2"], 16), __type="u32"),
E.color3(int(songs[s]["color3"], 16), __type="u32"),
),
E.difficulty(
E.fumen_1b(
E.difnum(songs[s]["1b_difnum"], __type="u8"),
E.playable(1, __type="u8"),
),
E.fumen_1a(
E.difnum(songs[s]["1a_difnum"], __type="u8"),
E.playable(1, __type="u8"),
),
E.fumen_2b(
E.difnum(songs[s]["2b_difnum"], __type="u8"),
E.playable(1, __type="u8"),
),
E.fumen_2a(
E.difnum(songs[s]["2a_difnum"], __type="u8"),
E.playable(1, __type="u8"),
),
),
id=s,
)
for s in songs
],
),
E.extra(*[E.info(E.music_id(i, __type="s32")) for i in songs]),
E.contest(
*[
E.info(
E.contest_id(i, __type="s32"),
E.start_date(1683422123358, __type="u64"),
E.end_date(1693422123358, __type="u64"),
E.title("", __type="str"),
E.regulation(i, __type="s32"),
E.target_music(
E.music(
E.music_id(1, __type="s32"),
E.music_type("1b", __type="str"),
)
),
)
for i in range(1, 3)
]
),
E.event(
*[
E.info(
E.event_id(e, __type="s32"),
E.start_date(1683422123358, __type="u64"),
E.end_date(1693422123358, __type="u64"),
E.param("", __type="str"),
)
for e in range(1, 14)
]
),
# E.kac2020(
# E.reward(
# E.data(
# E.music_id(1, __type="s32"),
# E.is_available(1, __type="bool"),
# )
# )
# ),
# E.silhouette(E.info(E.silhouette_id(i, __type="s32"))),
# E.music_condition(*[E.music(E.conditions(), id=s) for s in songs]),
)
)
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)
@router.post("/{gameinfo}/game/get_playdata_{player}")
async def drs_game_get_playdata(player: str, request: Request):
request_info = await core_process_request(request)
game_version = request_info["game_version"]
dataid = request_info["root"][0].find("userid/refid").text
profile = get_game_profile(dataid, game_version)
if profile:
djid, djid_split = get_id_from_profile(dataid)
response = E.response(
E.game(
E.result(0, __type="s32"),
E.userid(E.code(djid, __type="s32")),
E.profile(E.name(profile["name"], __type="str")),
E.playinfo(
E.softcode("", __type="str"),
E.start_date(1683422123358, __type="u64"),
E.end_date(1683422123358, __type="u64"),
E.mode_id(profile["mode_id"], __type="s32"),
E.music_id(profile["music_id"], __type="s32"),
E.music_type(profile["music_type"], __type="str"),
E.pcbid("0", __type="str"),
E.locid("EA000001", __type="str"),
),
E.paramdata(
*[
E.data(
E.data_type(p[0], __type="s32"),
E.data_id(p[1], __type="s32"),
E.param_list(p[2], __type="s32"),
)
for p in profile["params"]
]
),
E.dance_dance_rush(E.data()),
E.summer_dance_damp(E.data()),
E.kac2020(),
E.hidden_param(0, __type="s32"),
E.play_count(1001, __type="u32"),
E.daily_count(301, __type="u32"),
E.play_chain(31, __type="u32"),
)
)
else:
response = E.response(
E.game(
E.result(1, __type="s32"),
)
)
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)
@router.post("/{gameinfo}/game/lock_multi_login_{player}")
async def drs_game_lock_multi_login(player: str, request: Request):
request_info = await core_process_request(request)
response = E.response(E.game())
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)
@router.post("/{gameinfo}/game/sign_up_{player}")
async def drs_game_sign_up(player: str, request: Request):
request_info = await core_process_request(request)
game_version = request_info["game_version"]
root = request_info["root"][0]
dataid = root.find("userid/dataid").text
cardno = root.find("userid/cardno").text
name = root.find("profile/name").text
db = get_db().table("dancerush_profile")
all_profiles_for_card = db.get(Query().card == dataid)
if all_profiles_for_card is None:
all_profiles_for_card = {"card": dataid, "version": {}}
if "drs_id" not in all_profiles_for_card:
drs_id = random.randint(10000000, 99999999)
all_profiles_for_card["drs_id"] = drs_id
all_profiles_for_card["version"][str(game_version)] = {
"game_version": game_version,
"name": name,
"mode_id": 0,
"music_id": 1,
"music_type": "1a",
"params": [],
}
db.upsert(all_profiles_for_card, where("card") == dataid)
response = E.response(E.game())
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)
@router.post("/{gameinfo}/game/get_musicscore_{player}")
async def drs_get_musicscore(player: str, request: Request):
request_info = await core_process_request(request)
game_version = request_info["game_version"]
scores = []
db = get_db()
for record in db.table("drs_scores_best").search(
(where("game_version") == game_version)
):
scores.append(
[
record["music_id"],
record["music_type"],
record["score"],
record["rank"],
record["combo"],
record["param"],
]
)
response = E.response(
E.game(
E.scoredata(
*[
E.music(
E.music_id(s[0], __type="s32"),
E.music_type(s[1], __type="str"),
E.play_cnt(1, __type="s32"),
E.score(s[2], __type="s32"),
E.rank(s[3], __type="s32"),
E.combo(s[4], __type="s32"),
E.param(s[5], __type="s32"),
E.bestscore_date(1683422123358, __type="u64"),
E.lastplay_date(1683422123358, __type="u64"),
)
for s in scores
],
),
),
)
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)
@router.post("/{gameinfo}/game/save_musicscore")
async def drs_save_musicscore(request: Request):
request_info = await core_process_request(request)
game_version = request_info["game_version"]
timestamp = time.time()
root = request_info["root"][0][0]
dataid = root.find("userid/refid").text
profile = get_game_profile(dataid, game_version)
djid, djid_split = get_id_from_profile(dataid)
music_id = int(root.find("music_id").text)
music_type = root.find("music_type").text
mode = int(root.find("mode").text)
score = int(root.find("score").text)
rank = int(root.find("rank").text)
combo = int(root.find("combo").text)
param = int(root.find("param").text)
perfect = int(root.find("member/perfect").text)
great = int(root.find("member/great").text)
good = int(root.find("member/good").text)
bad = int(root.find("member/bad").text)
db = get_db()
db.table("drs_scores").insert(
{
"timestamp": timestamp,
"game_version": game_version,
"drs_id": djid,
"music_id": music_id,
"music_type": music_type,
"mode": mode,
"score": score,
"rank": rank,
"combo": combo,
"param": param,
"perfect": perfect,
"great": great,
"good": good,
"bad": bad,
},
)
best = db.table("drs_scores_best").get(
(where("drs_id") == djid)
& (where("game_version") == game_version)
& (where("music_id") == music_id)
& (where("music_type") == music_type)
)
best = {} if best is None else best
best_score_data = {
"game_version": game_version,
"drs_id": djid,
"name": profile["name"],
"music_id": music_id,
"music_type": music_type,
"score": max(score, best.get("score", score)),
"rank": max(rank, best.get("rank", rank)),
"combo": max(combo, best.get("combo", combo)),
"param": param,
}
db.table("drs_scores_best").upsert(
best_score_data,
(where("drs_id") == djid)
& (where("game_version") == game_version)
& (where("music_id") == music_id)
& (where("music_type") == music_type),
)
response = E.response(E.game())
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)
@router.post("/{gameinfo}/game/save_playdata")
async def drs_save_musicscore(request: Request):
request_info = await core_process_request(request)
game_version = request_info["game_version"]
root = request_info["root"][0][0]
dataid = root.find("userid/refid").text
profile = get_profile(dataid)
game_profile = profile["version"].get(str(game_version), {})
game_profile["mode_id"] = int(root.find("playinfo/mode_id").text)
game_profile["music_id"] = int(root.find("playinfo/music_id").text)
game_profile["music_type"] = root.find("playinfo/music_type").text
old_params = game_profile["params"]
params = {}
for old in old_params:
t = str(old[0])
i = str(old[1])
p = old[2]
if t not in params:
params[t] = {}
if i not in params[t]:
params[t][i] = {}
params[t][i] = p
for info in root.find("paramdata"):
t = info.find("data_type").text
i = info.find("data_id").text
p = info.find("param_list")
if t not in params:
params[t] = {}
if i not in params[t]:
params[t][i] = {}
params[t][i] = [int(x) for x in p.text.split(" ")]
params_list = []
for t in params:
for i in params[t]:
params_list.append([int(t), int(i), params[t][i]])
game_profile["params"] = params_list
profile["version"][str(game_version)] = game_profile
get_db().table("dancerush_profile").upsert(profile, where("card") == dataid)
response = E.response(E.game())
response_body, response_headers = await core_prepare_response(request, response)
return Response(content=response_body, headers=response_headers)