forked from Hay1tsme/artemis
Compare commits
5 Commits
f999269e41
...
07ec4e5263
Author | SHA1 | Date | |
---|---|---|---|
|
07ec4e5263 | ||
3979a020a6 | |||
ca9ccbe8a3 | |||
bf5c959324 | |||
|
7aa3cf82f1 |
@ -10,13 +10,14 @@ class ADBFelicaLookupRequest(ADBBaseRequest):
|
||||
self.pmm = hex(pmm)[2:].upper()
|
||||
|
||||
class ADBFelicaLookupResponse(ADBBaseResponse):
|
||||
def __init__(self, access_code: str = None, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x03, length: int = 0x30, status: int = 1) -> None:
|
||||
def __init__(self, access_code: str = None, idx: int = 0, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x03, length: int = 0x30, status: int = 1) -> None:
|
||||
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||
self.access_code = access_code if access_code is not None else "00000000000000000000"
|
||||
self.idx = idx
|
||||
|
||||
@classmethod
|
||||
def from_req(cls, req: ADBHeader, access_code: str = None) -> "ADBFelicaLookupResponse":
|
||||
c = cls(access_code, req.game_id, req.store_id, req.keychip_id)
|
||||
def from_req(cls, req: ADBHeader, access_code: str = None, idx: int = 0) -> "ADBFelicaLookupResponse":
|
||||
c = cls(access_code, idx, req.game_id, req.store_id, req.keychip_id)
|
||||
c.head.protocol_ver = req.protocol_ver
|
||||
return c
|
||||
|
||||
@ -26,7 +27,7 @@ class ADBFelicaLookupResponse(ADBBaseResponse):
|
||||
"access_code" / Int8ub[10],
|
||||
Padding(2)
|
||||
).build(dict(
|
||||
felica_idx = 0,
|
||||
felica_idx = self.idx,
|
||||
access_code = bytes.fromhex(self.access_code)
|
||||
))
|
||||
|
||||
|
@ -194,6 +194,9 @@ class AimedbServlette():
|
||||
|
||||
if user_id and user_id > 0:
|
||||
await self.data.card.update_card_last_login(req.access_code)
|
||||
if req.access_code.startswith("010") or req.access_code.startswith("3"):
|
||||
await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number)
|
||||
self.logger.info(f"Attempt to set chip id to {req.serial_number} for access code {req.access_code}")
|
||||
return ret
|
||||
|
||||
async def handle_lookup_ex(self, data: bytes, resp_code: int) -> ADBBaseResponse:
|
||||
@ -229,15 +232,24 @@ class AimedbServlette():
|
||||
|
||||
async def handle_felica_lookup(self, data: bytes, resp_code: int) -> bytes:
|
||||
"""
|
||||
On official, I think a card has to be registered for this to actually work, but
|
||||
I'm making the executive decision to not implement that and just kick back our
|
||||
faux generated access code. The real felica IDm -> access code conversion is done
|
||||
on the ADB server, which we do not and will not ever have access to. Because we can
|
||||
assure that all IDms will be unique, this basic 0-padded hex -> int conversion will
|
||||
be fine.
|
||||
On official, the IDm is used as a key to look up the stored access code in a large
|
||||
database. We do not have access to that database so we have to make due with what we got.
|
||||
Interestingly, namco games are able to read S_PAD0 and send the server the correct access
|
||||
code, but aimedb doesn't. Until somebody either enters the correct code manually, or scans
|
||||
on a game that reads it correctly from the card, this will have to do. It's the same conversion
|
||||
used on the big boy networks.
|
||||
"""
|
||||
req = ADBFelicaLookupRequest(data)
|
||||
ac = self.data.card.to_access_code(req.idm)
|
||||
card = await self.data.card.get_card_by_idm(req.idm)
|
||||
if not card:
|
||||
ac = self.data.card.to_access_code(req.idm)
|
||||
test = await self.data.card.get_card_by_access_code(ac)
|
||||
if test:
|
||||
await self.data.card.set_idm_by_access_code(ac, req.idm)
|
||||
|
||||
else:
|
||||
ac = card['access_code']
|
||||
|
||||
self.logger.info(
|
||||
f"idm {req.idm} ipm {req.pmm} -> access_code {ac}"
|
||||
)
|
||||
@ -245,7 +257,8 @@ class AimedbServlette():
|
||||
|
||||
async def handle_felica_register(self, data: bytes, resp_code: int) -> bytes:
|
||||
"""
|
||||
I've never seen this used.
|
||||
Used to register felica moble access codes. Will never be used on our network
|
||||
because we don't implement felica_lookup properly.
|
||||
"""
|
||||
req = ADBFelicaLookupRequest(data)
|
||||
ac = self.data.card.to_access_code(req.idm)
|
||||
@ -279,8 +292,18 @@ class AimedbServlette():
|
||||
|
||||
async def handle_felica_lookup_ex(self, data: bytes, resp_code: int) -> bytes:
|
||||
req = ADBFelicaLookup2Request(data)
|
||||
access_code = self.data.card.to_access_code(req.idm)
|
||||
user_id = await self.data.card.get_user_id_from_card(access_code=access_code)
|
||||
user_id = None
|
||||
card = await self.data.card.get_card_by_idm(req.idm)
|
||||
if not card:
|
||||
access_code = self.data.card.to_access_code(req.idm)
|
||||
card = await self.data.card.get_card_by_access_code(access_code)
|
||||
if card:
|
||||
user_id = card['user']
|
||||
await self.data.card.set_idm_by_access_code(access_code, req.idm)
|
||||
|
||||
else:
|
||||
user_id = card['user']
|
||||
access_code = card['access_code']
|
||||
|
||||
if user_id is None:
|
||||
user_id = -1
|
||||
@ -290,6 +313,14 @@ class AimedbServlette():
|
||||
)
|
||||
|
||||
resp = ADBFelicaLookup2Response.from_req(req.head, user_id, access_code)
|
||||
|
||||
if user_id > 0:
|
||||
if card['is_banned'] and card['is_locked']:
|
||||
resp.head.status = ADBStatus.BAN_SYS_USER
|
||||
elif card['is_banned']:
|
||||
resp.head.status = ADBStatus.BAN_SYS
|
||||
elif card['is_locked']:
|
||||
resp.head.status = ADBStatus.LOCK_USER
|
||||
|
||||
if user_id and user_id > 0 and self.config.aimedb.id_secret:
|
||||
auth_key = create_sega_auth_key(user_id, req.head.game_id, req.head.store_id, req.head.keychip_id, self.config.aimedb.id_secret, self.config.aimedb.id_lifetime_seconds)
|
||||
@ -337,6 +368,16 @@ class AimedbServlette():
|
||||
self.logger.info(
|
||||
f"Registration blocked!: access code {req.access_code}"
|
||||
)
|
||||
|
||||
if user_id > 0:
|
||||
if req.access_code.startswith("010") or req.access_code.startswith("3"):
|
||||
await self.data.card.set_chip_id_by_access_code(req.access_code, req.serial_number)
|
||||
self.logger.info(f"Attempt to set chip id to {req.serial_number} for access code {req.access_code}")
|
||||
|
||||
elif req.access_code.startswith("0008"):
|
||||
idm = self.data.card.to_idm(req.access_code)
|
||||
await self.data.card.set_idm_by_access_code(req.access_code, idm)
|
||||
self.logger.info(f"Attempt to set IDm to {idm} for access code {req.access_code}")
|
||||
|
||||
resp = ADBLookupResponse.from_req(req.head, user_id)
|
||||
if resp.user_id <= 0:
|
||||
|
@ -0,0 +1,27 @@
|
||||
"""chuni_add_net_battle_uk
|
||||
|
||||
Revision ID: 1e150d16ab6b
|
||||
Revises: b23f985100ba
|
||||
Create Date: 2024-06-21 22:57:18.418488
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1e150d16ab6b'
|
||||
down_revision = 'b23f985100ba'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_unique_constraint(None, 'chuni_profile_net_battle', ['user'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_constraint(None, 'chuni_profile_net_battle', type_='unique')
|
||||
# ### end Alembic commands ###
|
@ -0,0 +1,50 @@
|
||||
"""card_add_idm_chip_id
|
||||
|
||||
Revision ID: 48f4acc43a7e
|
||||
Revises: 1e150d16ab6b
|
||||
Create Date: 2024-06-21 23:53:34.369134
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '48f4acc43a7e'
|
||||
down_revision = '1e150d16ab6b'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('aime_card', sa.Column('idm', sa.String(length=16), nullable=True))
|
||||
op.add_column('aime_card', sa.Column('chip_id', sa.BIGINT(), nullable=True))
|
||||
op.alter_column('aime_card', 'access_code',
|
||||
existing_type=mysql.VARCHAR(length=20),
|
||||
nullable=False)
|
||||
op.alter_column('aime_card', 'created_date',
|
||||
existing_type=mysql.TIMESTAMP(),
|
||||
server_default=sa.text('now()'),
|
||||
existing_nullable=True)
|
||||
op.create_unique_constraint(None, 'aime_card', ['chip_id'])
|
||||
op.create_unique_constraint(None, 'aime_card', ['idm'])
|
||||
op.create_unique_constraint(None, 'aime_card', ['access_code'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'aime_card', type_='unique')
|
||||
op.drop_constraint(None, 'aime_card', type_='unique')
|
||||
op.drop_constraint(None, 'aime_card', type_='unique')
|
||||
op.alter_column('aime_card', 'created_date',
|
||||
existing_type=mysql.TIMESTAMP(),
|
||||
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||
existing_nullable=True)
|
||||
op.alter_column('aime_card', 'access_code',
|
||||
existing_type=mysql.VARCHAR(length=20),
|
||||
nullable=True)
|
||||
op.drop_column('aime_card', 'chip_id')
|
||||
op.drop_column('aime_card', 'idm')
|
||||
# ### end Alembic commands ###
|
@ -1,6 +1,6 @@
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint
|
||||
from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP
|
||||
from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP, BIGINT
|
||||
from sqlalchemy.sql.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.engine import Row
|
||||
@ -11,12 +11,10 @@ aime_card = Table(
|
||||
"aime_card",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("access_code", String(20)),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("access_code", String(20), nullable=False, unique=True),
|
||||
Column("idm", String(16), unique=True),
|
||||
Column("chip_id", BIGINT, unique=True),
|
||||
Column("created_date", TIMESTAMP, server_default=func.now()),
|
||||
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
||||
Column("is_locked", Boolean, server_default="0"),
|
||||
@ -122,6 +120,26 @@ class CardData(BaseData):
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to update last login time for {access_code}")
|
||||
|
||||
async def get_card_by_idm(self, idm: str) -> Optional[Row]:
|
||||
result = await self.execute(aime_card.select(aime_card.c.idm == idm))
|
||||
if result:
|
||||
return result.fetchone()
|
||||
|
||||
async def get_card_by_chip_id(self, chip_id: int) -> Optional[Row]:
|
||||
result = await self.execute(aime_card.select(aime_card.c.chip_id == chip_id))
|
||||
if result:
|
||||
return result.fetchone()
|
||||
|
||||
async def set_chip_id_by_access_code(self, access_code: str, chip_id: int) -> Optional[Row]:
|
||||
result = await self.execute(aime_card.update(aime_card.c.access_code == access_code).values(chip_id=chip_id))
|
||||
if not result:
|
||||
self.logger.error(f"Failed to update chip ID to {chip_id} for {access_code}")
|
||||
|
||||
async def set_idm_by_access_code(self, access_code: str, idm: str) -> Optional[Row]:
|
||||
result = await self.execute(aime_card.update(aime_card.c.access_code == access_code).values(idm=idm))
|
||||
if not result:
|
||||
self.logger.error(f"Failed to update IDm to {idm} for {access_code}")
|
||||
|
||||
def to_access_code(self, luid: str) -> str:
|
||||
"""
|
||||
Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string
|
||||
@ -132,4 +150,4 @@ class CardData(BaseData):
|
||||
"""
|
||||
Given a 20 digit access code as a string, return the 16 hex character luid
|
||||
"""
|
||||
return f"{int(access_code):0{16}x}"
|
||||
return f"{int(access_code):0{16}X}"
|
||||
|
@ -48,9 +48,8 @@ class ChuniReader(BaseReader):
|
||||
for root, dirs, files in walk(f"{root_dir}loginBonusPreset"):
|
||||
for dir in dirs:
|
||||
if path.exists(f"{root}/{dir}/LoginBonusPreset.xml"):
|
||||
with open(f"{root}/{dir}/LoginBonusPreset.xml", "rb") as fp:
|
||||
bytedata = fp.read()
|
||||
strdata = bytedata.decode("UTF-8")
|
||||
with open(f"{root}/{dir}/LoginBonusPreset.xml", "r", encoding="utf-8") as fp:
|
||||
strdata = fp.read()
|
||||
|
||||
xml_root = ET.fromstring(strdata)
|
||||
for name in xml_root.findall("name"):
|
||||
@ -121,9 +120,8 @@ class ChuniReader(BaseReader):
|
||||
for root, dirs, files in walk(evt_dir):
|
||||
for dir in dirs:
|
||||
if path.exists(f"{root}/{dir}/Event.xml"):
|
||||
with open(f"{root}/{dir}/Event.xml", "rb") as fp:
|
||||
bytedata = fp.read()
|
||||
strdata = bytedata.decode("UTF-8")
|
||||
with open(f"{root}/{dir}/Event.xml", "r", encoding="utf-8") as fp:
|
||||
strdata = fp.read()
|
||||
|
||||
xml_root = ET.fromstring(strdata)
|
||||
for name in xml_root.findall("name"):
|
||||
@ -144,9 +142,8 @@ class ChuniReader(BaseReader):
|
||||
for root, dirs, files in walk(music_dir):
|
||||
for dir in dirs:
|
||||
if path.exists(f"{root}/{dir}/Music.xml"):
|
||||
with open(f"{root}/{dir}/Music.xml", "rb") as fp:
|
||||
bytedata = fp.read()
|
||||
strdata = bytedata.decode("UTF-8")
|
||||
with open(f"{root}/{dir}/Music.xml", "r", encoding='utf-8') as fp:
|
||||
strdata = fp.read()
|
||||
|
||||
xml_root = ET.fromstring(strdata)
|
||||
for name in xml_root.findall("name"):
|
||||
@ -210,9 +207,8 @@ class ChuniReader(BaseReader):
|
||||
for root, dirs, files in walk(charge_dir):
|
||||
for dir in dirs:
|
||||
if path.exists(f"{root}/{dir}/ChargeItem.xml"):
|
||||
with open(f"{root}/{dir}/ChargeItem.xml", "rb") as fp:
|
||||
bytedata = fp.read()
|
||||
strdata = bytedata.decode("UTF-8")
|
||||
with open(f"{root}/{dir}/ChargeItem.xml", "r", encoding='utf-8') as fp:
|
||||
strdata = fp.read()
|
||||
|
||||
xml_root = ET.fromstring(strdata)
|
||||
for name in xml_root.findall("name"):
|
||||
@ -240,9 +236,8 @@ class ChuniReader(BaseReader):
|
||||
for root, dirs, files in walk(avatar_dir):
|
||||
for dir in dirs:
|
||||
if path.exists(f"{root}/{dir}/AvatarAccessory.xml"):
|
||||
with open(f"{root}/{dir}/AvatarAccessory.xml", "rb") as fp:
|
||||
bytedata = fp.read()
|
||||
strdata = bytedata.decode("UTF-8")
|
||||
with open(f"{root}/{dir}/AvatarAccessory.xml", "r", encoding='utf-8') as fp:
|
||||
strdata = fp.read()
|
||||
|
||||
xml_root = ET.fromstring(strdata)
|
||||
for name in xml_root.findall("name"):
|
||||
|
@ -262,7 +262,7 @@ cmission_progress = Table(
|
||||
"chuni_item_cmission_progress",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("missionId", Integer, nullable=False),
|
||||
Column("order", Integer),
|
||||
Column("stage", Integer),
|
||||
|
@ -412,6 +412,18 @@ rating = Table(
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
net_battle = Table(
|
||||
"chuni_profile_net_battle",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, unique=True),
|
||||
Column("isRankUpChallengeFailed", Boolean),
|
||||
Column("highestBattleRankId", Integer),
|
||||
Column("battleIconId", Integer),
|
||||
Column("battleIconNum", Integer),
|
||||
Column("avatarEffectPoint", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
class ChuniProfileData(BaseData):
|
||||
async def update_name(self, user_id: int, new_name: str) -> bool:
|
||||
@ -779,4 +791,32 @@ class ChuniProfileData(BaseData):
|
||||
else:
|
||||
versions_raw = result.fetchall()
|
||||
versions = [row[0] for row in versions_raw]
|
||||
return sorted(versions, reverse=True)
|
||||
return sorted(versions, reverse=True)
|
||||
|
||||
async def put_net_battle(self, user_id: int, net_battle_data: Dict) -> Optional[int]:
|
||||
sql = insert(net_battle).values(
|
||||
user=user_id,
|
||||
isRankUpChallengeFailed=net_battle_data['isRankUpChallengeFailed'],
|
||||
highestBattleRankId=net_battle_data['highestBattleRankId'],
|
||||
battleIconId=net_battle_data['battleIconId'],
|
||||
battleIconNum=net_battle_data['battleIconNum'],
|
||||
avatarEffectPoint=net_battle_data['avatarEffectPoint'],
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
isRankUpChallengeFailed=net_battle_data['isRankUpChallengeFailed'],
|
||||
highestBattleRankId=net_battle_data['highestBattleRankId'],
|
||||
battleIconId=net_battle_data['battleIconId'],
|
||||
battleIconNum=net_battle_data['battleIconNum'],
|
||||
avatarEffectPoint=net_battle_data['avatarEffectPoint'],
|
||||
)
|
||||
|
||||
result = await self.execute(conflict)
|
||||
if result:
|
||||
return result.inserted_primary_key['id']
|
||||
self.logger.error(f"Failed to put net battle data for user {user_id}")
|
||||
|
||||
async def get_net_battle(self, user_id: int, net_battle_data: Dict) -> Optional[Row]:
|
||||
result = await self.execute(net_battle.select(net_battle.c.user == user_id))
|
||||
if result:
|
||||
return result.fetchone()
|
||||
|
@ -165,6 +165,8 @@ class PokkenBase:
|
||||
f"Register new card {access_code} (UserId {user_id}, CardId {card_id})"
|
||||
)
|
||||
|
||||
await self.data.card.set_chip_id_by_access_code(access_code, int(request.load_user.chip_id[:8], 16))
|
||||
|
||||
elif card is None:
|
||||
self.logger.info(f"Registration of card {access_code} blocked!")
|
||||
res.load_user.CopyFrom(load_usr)
|
||||
@ -173,6 +175,8 @@ class PokkenBase:
|
||||
else:
|
||||
user_id = card['user']
|
||||
card_id = card['id']
|
||||
if not card['chip_id']:
|
||||
await self.data.card.set_chip_id_by_access_code(access_code, int(request.load_user.chip_id[:8], 16))
|
||||
|
||||
"""
|
||||
TODO: Unlock all supports? Probably
|
||||
|
@ -83,6 +83,10 @@ class SaoBase:
|
||||
if not user_id:
|
||||
user_id = await self.data.user.create_user() #works
|
||||
card_id = await self.data.card.create_card(user_id, req.access_code)
|
||||
if req.access_code.startswith("5"):
|
||||
await self.data.card.set_idm_by_access_code(card_id, req.chip_id[:16])
|
||||
elif req.access_code.startswith("010") or req.access_code.startswith("3"):
|
||||
await self.data.card.set_chip_id_by_access_code(card_id, int(req.chip_id[:8], 16))
|
||||
|
||||
if card_id is None:
|
||||
user_id = -1
|
||||
|
Loading…
Reference in New Issue
Block a user