mirror of
https://github.com/573dev/gfdm-server.git
synced 2026-03-21 17:54:19 -05:00
Have a bunch of stuff working. Next is gametop
This commit is contained in:
parent
27117aa5c4
commit
ca2f46dce6
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -107,3 +107,6 @@ ENV/
|
|||
/decompiled/
|
||||
/database/
|
||||
|
||||
|
||||
# Ignore this for now, not sure I want to just throw it up on github yet
|
||||
/v8_server/model/data/mdb.json
|
||||
|
|
|
|||
|
|
@ -63,7 +63,15 @@ class CardMng(object):
|
|||
|
||||
if user is None:
|
||||
# The user doesn't exist, force system to create a new account
|
||||
response = E.response(E.cardmng({"status": str(CardMng.NOT_REGISTERED)}))
|
||||
response = E.response(
|
||||
E.cardmng(
|
||||
{
|
||||
"newflag": "1",
|
||||
"binded": "0",
|
||||
"status": str(CardMng.NOT_REGISTERED),
|
||||
}
|
||||
)
|
||||
)
|
||||
else:
|
||||
refid = RefID.from_userid(user.userid)
|
||||
bound = Profile.from_userid(user.userid) is not None
|
||||
|
|
@ -76,12 +84,13 @@ class CardMng(object):
|
|||
{
|
||||
"refid": refid.refid,
|
||||
"dataid": refid.refid,
|
||||
"newflag": "1",
|
||||
"newflag": "0",
|
||||
"binded": "1" if bound else "0",
|
||||
"expired": "0",
|
||||
"exflag": "0",
|
||||
"useridflag": "1",
|
||||
"extidflag": "1",
|
||||
"status": str(CardMng.SUCCESS),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
|
|||
1
v8_server/eamuse/services/lobby.py
Normal file
1
v8_server/eamuse/services/lobby.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# >>libshare-pj.c: xrpc_module_add(aLobby, "lobby", aLobby, &off_100404C4);
|
||||
|
|
@ -1,8 +1,18 @@
|
|||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from lxml import etree
|
||||
from lxml.builder import E
|
||||
|
||||
from v8_server import db
|
||||
from v8_server.eamuse.services.services import ServiceRequest
|
||||
from v8_server.eamuse.utils.crc import calculate_crc8
|
||||
from v8_server.eamuse.utils.xml import XMLBinTypes as T, e_type
|
||||
from v8_server.model.song import HitChart
|
||||
from v8_server.model.user import User, UserAccount
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Local(object):
|
||||
|
|
@ -21,10 +31,30 @@ class Local(object):
|
|||
|
||||
CARDUTIL = "cardutil"
|
||||
CARDUTIL_CHECK = "check"
|
||||
CARDUTIL_REGIST = "regist"
|
||||
|
||||
GAMEINFO = "gameinfo"
|
||||
GAMEINFO_GET = "get"
|
||||
|
||||
GAMEEND = "gameend"
|
||||
GAMEEND_REGIST = "regist"
|
||||
|
||||
GAMEINFO = "gameinfo"
|
||||
GAMEINFO_GET = "get"
|
||||
|
||||
GAMETOP = "gametop"
|
||||
GAMETOP_GET = "get"
|
||||
GAMETOP_GET_RIVAL = "get_rival"
|
||||
|
||||
CUSTOMIZE = "customize"
|
||||
CUSTOMIZE_REGIST = "regist"
|
||||
|
||||
ASSERT_REPORT = "assert_report"
|
||||
ASSERT_REPORT_REGIST = "regist"
|
||||
|
||||
# Not sure about this one yet
|
||||
INCREMENT = "increment"
|
||||
|
||||
@classmethod
|
||||
def shopinfo(cls, req: ServiceRequest) -> etree:
|
||||
"""
|
||||
|
|
@ -110,6 +140,9 @@ class Local(object):
|
|||
"""
|
||||
Handle the demodata request.
|
||||
|
||||
Potentially this is just some initial demo data for initial boot/factory reset
|
||||
After this data, the game might keep track of all this stuff itself.
|
||||
|
||||
# Example Request:
|
||||
<call model="K32:J:B:A:2011033000" srcid="00010203040506070809">
|
||||
<demodata method="get">
|
||||
|
|
@ -125,27 +158,40 @@ class Local(object):
|
|||
<demodata expire="600"/>
|
||||
</response>
|
||||
"""
|
||||
# TODO: Figure out what this thing actually needs to send back
|
||||
dtfmt = "%Y-%m-%d %H:%M:%S%z"
|
||||
|
||||
if req.method == cls.DEMODATA_GET:
|
||||
# response = E.response(E.demodata({"expire": "600"}))
|
||||
hitchart_number = int(req.xml[0].find("hitchart_nr").text)
|
||||
rank_data = HitChart.get_ranking(hitchart_number)
|
||||
|
||||
# try some dummy response that might have some info in it
|
||||
response = E.response(
|
||||
E.demodata(
|
||||
E.hitchart({"nr": "3"}),
|
||||
E.data(
|
||||
E.musicid("133", e_type(T.s32)),
|
||||
E.last1("1", e_type(T.s32)),
|
||||
E.mode("0", e_type(T.u8)), # Unknown what mode we need
|
||||
E.hitchart(
|
||||
E.start(datetime.now().strftime(dtfmt)),
|
||||
E.end(datetime.now().strftime(dtfmt)),
|
||||
*[
|
||||
E.data(
|
||||
E.musicid(str(x), e_type(T.s32)),
|
||||
E.last1("0", e_type(T.s32)),
|
||||
)
|
||||
for x in rank_data
|
||||
],
|
||||
{"nr": str(hitchart_number)},
|
||||
),
|
||||
E.data(
|
||||
E.musicid("208", e_type(T.s32)),
|
||||
E.last1("2", e_type(T.s32)),
|
||||
),
|
||||
E.data(
|
||||
E.musicid("209", e_type(T.s32)),
|
||||
E.last1("3", e_type(T.s32)),
|
||||
E.bossdata( # No idea what this stuff means
|
||||
E.division("14", e_type(T.u8)), # Shows up as "Extra Lv X"
|
||||
E.border("0 0 0 0 0 0 0 0 0", e_type(T.u8, count=9)),
|
||||
E.extra_border("90", e_type(T.u8)),
|
||||
E.bsc_encore_border("92", e_type(T.u8)),
|
||||
E.adv_encore_border("93", e_type(T.u8)),
|
||||
E.ext_encore_border("94", e_type(T.u8)),
|
||||
E.bsc_premium_border("95", e_type(T.u8)),
|
||||
E.adv_premium_border("95", e_type(T.u8)),
|
||||
E.ext_premium_border("95", e_type(T.u8)),
|
||||
),
|
||||
E.info(E.message("SenPi's Kickass Machine")),
|
||||
E.assert_report_state("0", e_type(T.u8)),
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
|
@ -186,6 +232,30 @@ class Local(object):
|
|||
response = E.response(
|
||||
E.cardutil(E.card(E.kind("0", e_type(T.s8)), {"no": "1", "state": "0"}))
|
||||
)
|
||||
elif req.method == cls.CARDUTIL_REGIST:
|
||||
root = req.xml[0].find("data")
|
||||
refid = root.find("refid").text
|
||||
name = root.find("name").text
|
||||
chara = root.find("chara").text
|
||||
cardid = root.find("uid").text
|
||||
is_succession = root.find("is_succession").text
|
||||
|
||||
user = User.from_refid(refid)
|
||||
if user is None:
|
||||
raise Exception("This user should theoretically exist here")
|
||||
if user.card.cardid != cardid:
|
||||
raise Exception(f"Card ID is incorrect: {user.card.cardid} != {cardid}")
|
||||
|
||||
user_account = UserAccount(
|
||||
userid=user.userid,
|
||||
name=name,
|
||||
chara=int(chara),
|
||||
is_succession=True if is_succession == "1" else False,
|
||||
)
|
||||
db.session.add(user_account)
|
||||
db.session.commit()
|
||||
|
||||
response = E.response(E.cardutil())
|
||||
else:
|
||||
raise Exception(
|
||||
"Not sure how to handle this cardutil request. "
|
||||
|
|
@ -217,7 +287,48 @@ class Local(object):
|
|||
</response>
|
||||
"""
|
||||
if req.method == cls.GAMEINFO_GET:
|
||||
response = E.response(E.gameinfo())
|
||||
response = E.response(
|
||||
E.gameinfo(
|
||||
E.mode("0", e_type(T.u8)),
|
||||
E.free_music("262143", e_type(T.u32)),
|
||||
E.key(E.musicid("-1", e_type(T.s32))),
|
||||
E.limit_gdp("40000", e_type(T.u32)),
|
||||
E.free_chara("1824", e_type(T.u32)),
|
||||
E.tag(str(calculate_crc8(str(262143 + 1824))), e_type(T.u8)),
|
||||
E.bossdata(
|
||||
E.division("14", e_type(T.u8)), # Shows up as "Extra Lv X"
|
||||
E.border("0 0 0 0 0 0 0 0 0", e_type(T.u8, count=9)),
|
||||
E.extra_border("90", e_type(T.u8)),
|
||||
E.bsc_encore_border("92", e_type(T.u8)),
|
||||
E.adv_encore_border("93", e_type(T.u8)),
|
||||
E.ext_encore_border("94", e_type(T.u8)),
|
||||
E.bsc_premium_border("95", e_type(T.u8)),
|
||||
E.adv_premium_border("95", e_type(T.u8)),
|
||||
E.ext_premium_border("95", e_type(T.u8)),
|
||||
),
|
||||
E.battledata(
|
||||
E.battle_music_level(
|
||||
" ".join("0" * 13), e_type(T.u8, count=13)
|
||||
),
|
||||
E.standard_skill(" ".join("0" * 13), e_type(T.s32, count=13)),
|
||||
E.border_skill(" ".join("0" * 13), e_type(T.s32, count=13)),
|
||||
),
|
||||
E.quest(
|
||||
E.division("0", e_type(T.u8)),
|
||||
E.border("0", e_type(T.u8)),
|
||||
E.qdata(" ".join("0" * 26), e_type(T.u32, count=26)),
|
||||
*[
|
||||
E(f"play_{x}", " ".join("0" * 32), e_type(T.u32, count=32))
|
||||
for x in range(0, 13)
|
||||
],
|
||||
*[
|
||||
E(f"clear_{x}", " ".join("0" * 32), e_type(T.u32, count=32))
|
||||
for x in range(0, 13)
|
||||
],
|
||||
),
|
||||
E.campaign(E.campaign("0", e_type(T.u8))),
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise Exception(
|
||||
"Not sure how to handle this gameinfo request. "
|
||||
|
|
@ -225,3 +336,46 @@ class Local(object):
|
|||
)
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def gametop(cls, req: ServiceRequest) -> etree:
|
||||
|
||||
if req.method == cls.GAMETOP_GET:
|
||||
pass
|
||||
elif req.method == cls.GAMETOP_GET_RIVAL:
|
||||
pass
|
||||
else:
|
||||
raise Exception(
|
||||
"Not sure how to handle this gametop request. "
|
||||
f'method "{req.method}" is unknown for request: {req}'
|
||||
)
|
||||
|
||||
return request
|
||||
|
||||
@classmethod
|
||||
def gameend(cls, req: ServiceRequest) -> etree:
|
||||
"""
|
||||
Handle a GameEnd request.
|
||||
|
||||
For now just save the hitchart data
|
||||
"""
|
||||
if req.method == cls.GAMEEND_REGIST:
|
||||
# Grab the hitchart data
|
||||
hc_root = req.xml[0].find("hitchart")
|
||||
|
||||
for mid in hc_root.findall("musicid"):
|
||||
musicid = int(mid.text)
|
||||
hc = HitChart(musicid=musicid, playdate=datetime.now())
|
||||
logger.debug(f"Saving HitChart: {hc}")
|
||||
db.session.add(hc)
|
||||
db.session.commit()
|
||||
|
||||
# Just send back a dummy object for now
|
||||
response = E.response(E.gameend())
|
||||
else:
|
||||
raise Exception(
|
||||
"Not sure how to handle this gameend request. "
|
||||
f'method "{req.method}" is unknown for request: {req}'
|
||||
)
|
||||
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -38,20 +38,21 @@ class ServiceType(IntEnum):
|
|||
|
||||
# Extra for testing
|
||||
# BINARY = 8
|
||||
# DLSTATUS = 9
|
||||
# EACOIN = 10
|
||||
DLSTATUS = 9
|
||||
EACOIN = 10
|
||||
# EEMALL = 11
|
||||
# INFO = 12
|
||||
# LOBBY = 13
|
||||
# NETLOG = 14
|
||||
LOBBY = 13
|
||||
NETLOG = 14
|
||||
# NUMBERING = 15
|
||||
# PKGLIST = 16
|
||||
# POSEVENT = 17
|
||||
# REFERENCE = 18
|
||||
# SHOPINF = 19
|
||||
# SIDMGR = 20
|
||||
# USERDATA = 21
|
||||
# USERID = 22
|
||||
SIDMGR = 20
|
||||
USERDATA = 21
|
||||
USERID = 22
|
||||
TRACEROUTE = 23
|
||||
|
||||
|
||||
class Services(object):
|
||||
|
|
@ -66,7 +67,8 @@ class Services(object):
|
|||
|
||||
# Default service url that GFDM uses. You will need to set up your network so that
|
||||
# this URL points to this server.
|
||||
SERVICE_URL = "https://eamuse.konami.fun"
|
||||
# SERVICE_URL = "https://eamuse.konami.fun"
|
||||
SERVICE_URL = "https://e.k.f"
|
||||
|
||||
# The base route that GFDM uses to query the eAmuse server to get the list of
|
||||
# offered services
|
||||
|
|
|
|||
14
v8_server/eamuse/utils/crc.py
Normal file
14
v8_server/eamuse/utils/crc.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
def calculate_crc8(value: str) -> int:
|
||||
"""
|
||||
Calculate the CRC8 of a string representation of an int
|
||||
"""
|
||||
crc = 0
|
||||
|
||||
for c in bytearray(value.encode("ASCII")):
|
||||
for i in range(8, 0, -1):
|
||||
t = c ^ crc
|
||||
crc >>= 1
|
||||
if (t & 0x01) != 0:
|
||||
crc ^= 0x8C
|
||||
c >>= 1
|
||||
return crc
|
||||
103
v8_server/model/song.py
Normal file
103
v8_server/model/song.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
from flask_sqlalchemy.model import DefaultMeta
|
||||
from sqlalchemy import Column, ForeignKey, PrimaryKeyConstraint, event, func, text
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.types import DateTime, Integer, String
|
||||
|
||||
from v8_server import db
|
||||
|
||||
|
||||
BaseModel: DefaultMeta = db.Model
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Song(BaseModel):
|
||||
"""
|
||||
Table representing the song data
|
||||
"""
|
||||
|
||||
__tablename__ = "songs"
|
||||
musicid = Column(Integer, nullable=False, primary_key=True)
|
||||
bpm = Column(Integer, nullable=False)
|
||||
title_ascii = Column(String(16), nullable=False)
|
||||
hitcharts = relationship("HitChart", back_populates="song")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Song<musicid: {self.musicid}, bpm: {self.bpm}, "
|
||||
f'title_ascii: "{self.title_ascii}">'
|
||||
)
|
||||
|
||||
|
||||
class HitChart(BaseModel):
|
||||
"""
|
||||
Table representing the song hit chart
|
||||
"""
|
||||
|
||||
__tablename__ = "hitchart"
|
||||
__table_args__ = (PrimaryKeyConstraint("musicid", "playdate"),)
|
||||
musicid = Column(Integer, ForeignKey("songs.musicid"), nullable=False)
|
||||
playdate = Column(DateTime, nullable=False)
|
||||
song = relationship("Song", back_populates="hitcharts")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"HitChart<musicid: {self.musicid}, playdate: {self.playdate}>"
|
||||
|
||||
@classmethod
|
||||
def get_ranking(cls, count) -> List[int]:
|
||||
items = (
|
||||
db.session.query(
|
||||
HitChart.musicid, func.count(HitChart.musicid).label("count")
|
||||
)
|
||||
.group_by(HitChart.musicid)
|
||||
.order_by(text("count DESC"))
|
||||
.order_by(HitChart.musicid.desc())
|
||||
.limit(count)
|
||||
.all()
|
||||
)
|
||||
|
||||
results = []
|
||||
for item in items:
|
||||
results.append(item[0])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def insert_initial_song_data(target, connection, **kwargs) -> None:
|
||||
# Make sure both Song and HitChart have been created
|
||||
try:
|
||||
db.session.execute("SELECT * FROM songs")
|
||||
db.session.execute("SELECT * FROM hitchart")
|
||||
except Exception:
|
||||
return
|
||||
|
||||
# Load up the mdb.json file
|
||||
data_path = Path(__file__).parent / "data" / "mdb.json"
|
||||
with data_path.open() as f:
|
||||
json_data = json.loads(f.read())
|
||||
|
||||
for key, song in json_data["musicdb"]["songs"].items():
|
||||
song_obj = Song(musicid=key, bpm=song["bpm"], title_ascii=song["title_ascii"])
|
||||
db.session.add(song_obj)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Insert initial hitchart data, just add one entry for every song with the current
|
||||
# timestamp
|
||||
now = datetime.now()
|
||||
for key, song in json_data["musicdb"]["songs"].items():
|
||||
hc = HitChart(musicid=key, playdate=now)
|
||||
db.session.add(hc)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
event.listen(Song.__table__, "after_create", insert_initial_song_data)
|
||||
event.listen(HitChart.__table__, "after_create", insert_initial_song_data)
|
||||
|
|
@ -6,7 +6,7 @@ from typing import Optional
|
|||
from flask_sqlalchemy.model import DefaultMeta
|
||||
from sqlalchemy import JSON, Column, ForeignKey, UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.types import Integer, String
|
||||
from sqlalchemy.types import Boolean, Integer, String
|
||||
|
||||
from v8_server import db
|
||||
|
||||
|
|
@ -27,9 +27,10 @@ class User(BaseModel):
|
|||
|
||||
userid = Column(Integer, nullable=False, primary_key=True)
|
||||
pin = Column(String(4), nullable=False)
|
||||
cards = relationship("Card", back_populates="user")
|
||||
card = relationship("Card", uselist=False, back_populates="user")
|
||||
extids = relationship("ExtID", back_populates="user")
|
||||
refids = relationship("RefID", back_populates="user")
|
||||
user_account = relationship("UserAccount", uselist=False, back_populates="user")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'User<userid: {self.userid}, pin: "{self.pin}">'
|
||||
|
|
@ -39,6 +40,25 @@ class User(BaseModel):
|
|||
card = db.session.query(Card).filter(Card.cardid == cardid).one_or_none()
|
||||
return card.user if card is not None else None
|
||||
|
||||
@classmethod
|
||||
def from_refid(cls, refid: str) -> Optional[User]:
|
||||
ref = db.session.query(RefID).filter(RefID.refid == refid).one_or_none()
|
||||
return ref.user if ref is not None else None
|
||||
|
||||
|
||||
class UserAccount(BaseModel):
|
||||
"""
|
||||
Table representing a user account.
|
||||
"""
|
||||
|
||||
__tablename__ = "user_accounts"
|
||||
|
||||
userid = Column(Integer, ForeignKey("users.userid"), primary_key=True)
|
||||
name = Column(String(8), nullable=False)
|
||||
chara = Column(Integer, nullable=False)
|
||||
is_succession = Column(Boolean, nullable=False)
|
||||
user = relationship("User", back_populates="user_account")
|
||||
|
||||
|
||||
class Card(BaseModel):
|
||||
"""
|
||||
|
|
@ -51,7 +71,7 @@ class Card(BaseModel):
|
|||
|
||||
cardid = Column(String(16), nullable=False, primary_key=True)
|
||||
userid = Column(Integer, ForeignKey("users.userid"), nullable=False)
|
||||
user = relationship("User", back_populates="cards")
|
||||
user = relationship("User", back_populates="card")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Card<cardid: "{self.cardid}", userid: {self.userid}>'
|
||||
|
|
@ -64,7 +84,7 @@ class ExtID(BaseModel):
|
|||
"""
|
||||
|
||||
__tablename__ = "extids"
|
||||
__table_args_ = (UniqueConstraint("game", "userid", name="game_userid"),)
|
||||
__table_args__ = (UniqueConstraint("game", "userid", name="game_userid"),)
|
||||
extid = Column(Integer, nullable=False, primary_key=True)
|
||||
game = Column(String(32), nullable=False)
|
||||
userid = Column(Integer, ForeignKey("users.userid"), nullable=False)
|
||||
|
|
|
|||
|
|
@ -145,6 +145,8 @@ def local_service() -> FlaskResponse:
|
|||
response = Local.cardutil(req)
|
||||
elif req.module == Local.GAMEINFO:
|
||||
response = Local.gameinfo(req)
|
||||
elif req.module == Local.GAMEEND:
|
||||
response = Local.gameend(req)
|
||||
else:
|
||||
raise Exception(f"Not sure how to handle this Local Request: {req}")
|
||||
return req.response(response)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user