262 lines
11 KiB
Python
262 lines
11 KiB
Python
|
import yaml
|
||
|
import jinja2
|
||
|
from typing import List
|
||
|
from starlette.requests import Request
|
||
|
from starlette.responses import Response, RedirectResponse, JSONResponse, PlainTextResponse
|
||
|
from starlette.routing import Route
|
||
|
from os import path
|
||
|
import random
|
||
|
from Crypto.Protocol.KDF import PBKDF2
|
||
|
from Crypto.Hash import SHA1
|
||
|
from Crypto.Cipher import AES, _mode_cbc
|
||
|
|
||
|
from core.frontend import FE_Base, UserSession
|
||
|
from core.config import CoreConfig
|
||
|
from .database import SaoData
|
||
|
from .config import SaoConfig
|
||
|
from .const import SaoConstants
|
||
|
|
||
|
|
||
|
class SaoFrontend(FE_Base):
|
||
|
SN_PREFIX = SaoConstants.SERIAL_IDENT
|
||
|
NETID_PREFIX = SaoConstants.NETID_PREFIX
|
||
|
ALL_HEROS = []
|
||
|
def __init__(
|
||
|
self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str
|
||
|
) -> None:
|
||
|
super().__init__(cfg, environment)
|
||
|
self.data = SaoData(cfg)
|
||
|
self.game_cfg = SaoConfig()
|
||
|
if path.exists(f"{cfg_dir}/{SaoConstants.CONFIG_NAME}"):
|
||
|
self.game_cfg.update(
|
||
|
yaml.safe_load(open(f"{cfg_dir}/{SaoConstants.CONFIG_NAME}"))
|
||
|
)
|
||
|
self.nav_name = "SAO"
|
||
|
self.card_key= None
|
||
|
self.card_iv = None
|
||
|
|
||
|
if self.game_cfg.card.enable and self.game_cfg.card.crypt_password and self.game_cfg.card.crypt_salt:
|
||
|
hash = PBKDF2(
|
||
|
self.game_cfg.card.crypt_password,
|
||
|
bytes.fromhex(self.game_cfg.card.crypt_salt),
|
||
|
48,
|
||
|
count=1000,
|
||
|
hmac_hash_module=SHA1,
|
||
|
)
|
||
|
|
||
|
self.card_key = hash[:32]
|
||
|
self.card_iv = hash[32:48]
|
||
|
|
||
|
def get_routes(self) -> List[Route]:
|
||
|
return [
|
||
|
Route("/", self.render_GET, methods=['GET']),
|
||
|
Route("/update.name", self.change_name, methods=['POST']),
|
||
|
Route("/matching.auth", self.matching_auth, methods=['POST']),
|
||
|
Route("/matching.auth/", self.matching_auth, methods=['POST']),
|
||
|
Route("/qr.read", self.read_qr, methods=['POST']),
|
||
|
Route("/qr.register", self.reg_qr, methods=['POST']),
|
||
|
Route("/profile.register", self.reg_profile, methods=['POST'])
|
||
|
]
|
||
|
|
||
|
async def render_GET(self, request: Request) -> Response:
|
||
|
template = self.environment.get_template(
|
||
|
"titles/sao/templates/sao_index.jinja"
|
||
|
)
|
||
|
pf = None
|
||
|
|
||
|
usr_sesh = self.validate_session(request)
|
||
|
if not usr_sesh:
|
||
|
usr_sesh = UserSession()
|
||
|
|
||
|
else:
|
||
|
profile = await self.data.profile.get_profile(usr_sesh.user_id)
|
||
|
if profile is not None:
|
||
|
pf = profile._asdict()
|
||
|
if not self.ALL_HEROS:
|
||
|
self.ALL_HEROS = await self.data.static.get_heros()
|
||
|
|
||
|
err = 0
|
||
|
suc = 0
|
||
|
if "e" in request.query_params:
|
||
|
err = request.query_params.get("e", 0)
|
||
|
if "s" in request.query_params:
|
||
|
suc = request.query_params.get("s", 0)
|
||
|
|
||
|
return Response(template.render(
|
||
|
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||
|
game_list=self.environment.globals["game_list"],
|
||
|
sesh=vars(usr_sesh),
|
||
|
profile=pf,
|
||
|
success=int(suc),
|
||
|
error=int(err),
|
||
|
all_heros=self.ALL_HEROS if self.ALL_HEROS else []
|
||
|
), media_type="text/html; charset=utf-8")
|
||
|
|
||
|
async def change_name(self, request: Request) -> RedirectResponse:
|
||
|
usr_sesh = self.validate_session(request)
|
||
|
if not usr_sesh:
|
||
|
return RedirectResponse("/game/sao/", 303)
|
||
|
|
||
|
frm = await request.form()
|
||
|
new_name = frm.get("new_name")
|
||
|
|
||
|
if len(new_name) > 16:
|
||
|
return RedirectResponse("/game/sao/?e=8", 303)
|
||
|
|
||
|
await self.data.profile.set_profile_name(usr_sesh.user_id, new_name)
|
||
|
self.logger.info(f"User {usr_sesh.user_id} changed name to {new_name}")
|
||
|
|
||
|
return RedirectResponse("/game/sao/?s=1", 303)
|
||
|
|
||
|
async def matching_auth(self, request: Request) -> JSONResponse:
|
||
|
self.logger.debug(f"Mathing auth params: {request.query_params}")
|
||
|
self.logger.debug(f"Mathing auth headers: {request.headers}")
|
||
|
uid = request.query_params.get('userId', '')
|
||
|
|
||
|
if not uid:
|
||
|
uid = f'Guest{str(random.randint(1,9999)).zfill(4)}'
|
||
|
self.logger.info(f"Matching auth request with no userId, using {uid}")
|
||
|
|
||
|
else:
|
||
|
self.logger.info(f"Matching auth request for userId {uid}")
|
||
|
|
||
|
return JSONResponse({ "ResultCode": 1, "UserId": uid }) # Just auth everything for now
|
||
|
|
||
|
async def read_qr(self, request: Request) -> PlainTextResponse:
|
||
|
if not self.card_key or not self.card_iv:
|
||
|
return PlainTextResponse("e13-1", 400)
|
||
|
|
||
|
usr_sesh = self.validate_session(request)
|
||
|
if not usr_sesh:
|
||
|
return PlainTextResponse("e9", 403)
|
||
|
|
||
|
frm = await request.form()
|
||
|
qr_data = frm.get("qr_data", "")
|
||
|
if not qr_data.isalnum() or not len(qr_data) == 0x40:
|
||
|
return PlainTextResponse("e14-1", 400)
|
||
|
|
||
|
try:
|
||
|
cipher: _mode_cbc.CbcMode = AES.new(self.card_key, AES.MODE_CBC, iv=self.card_iv)
|
||
|
except Exception as e:
|
||
|
self.logger.error(f"Error creating card cipher - {e}")
|
||
|
return PlainTextResponse("e13-2", 500)
|
||
|
sn = b""
|
||
|
try:
|
||
|
sn = cipher.decrypt(bytes.fromhex(qr_data))[:19]
|
||
|
sn = sn.decode()
|
||
|
except Exception as e:
|
||
|
self.logger.error(f"Error decrypting card data {qr_data} ({sn}) - {e}")
|
||
|
return PlainTextResponse("e14-2", 400)
|
||
|
|
||
|
if not sn.isnumeric():
|
||
|
self.logger.error(f"Card serial {sn} decrypted incorrectly")
|
||
|
return PlainTextResponse("e13-3", 400)
|
||
|
|
||
|
return PlainTextResponse(sn)
|
||
|
|
||
|
async def reg_qr(self, request: Request) -> RedirectResponse:
|
||
|
if not self.card_key or not self.card_iv:
|
||
|
return RedirectResponse("/game/sao/?e=14", 303)
|
||
|
|
||
|
usr_sesh = self.validate_session(request)
|
||
|
if not usr_sesh:
|
||
|
return RedirectResponse("/game/sao/?e=9", 303)
|
||
|
|
||
|
frm = await request.form()
|
||
|
serial = frm.get("qr_register_serial")
|
||
|
hero = frm.get("qr_register_hero")
|
||
|
is_holo = bool(frm.get("qr_register_holo", False))
|
||
|
|
||
|
user_hero = await self.data.item.get_hero_log(usr_sesh.user_id, hero)
|
||
|
if not user_hero:
|
||
|
hero_statc = await self.data.static.get_hero_by_id(hero)
|
||
|
if not hero_statc:
|
||
|
self.logger.error(f"Failed to find hero log {hero}! Please run the reader")
|
||
|
return RedirectResponse(" /game/sao/?e=13", 303)
|
||
|
|
||
|
skills = await self.data.static.get_skill_table_by_subid(hero_statc['SkillTableSubId'])
|
||
|
if not skills:
|
||
|
self.logger.error(f"Failed to find skill table {hero_statc['SkillTableSubId']}! Please run the reader")
|
||
|
return RedirectResponse("/game/sao/?e=13", 303)
|
||
|
|
||
|
default_skills = []
|
||
|
now_have_skills = [None, None, None, None, None]
|
||
|
x = 0
|
||
|
for skill in skills:
|
||
|
if skill['LevelObtained'] == 1 and skill['AwakeningId'] == 0:
|
||
|
default_skills[x] = skill['SkillId']
|
||
|
x += 1
|
||
|
if x >= 5:
|
||
|
break
|
||
|
|
||
|
for skill in default_skills:
|
||
|
skill_info = await self.data.static.get_skill_by_id(skill)
|
||
|
skill_slot = skill_info['Level'] - 1
|
||
|
if now_have_skills[skill_slot] is not None:
|
||
|
now_have_skills[skill]
|
||
|
|
||
|
|
||
|
user_hero_id = await self.data.item.put_hero_log(
|
||
|
usr_sesh.user_id,
|
||
|
hero,
|
||
|
1,
|
||
|
0,
|
||
|
hero_statc['DefaultEquipmentId1'],
|
||
|
hero_statc['DefaultEquipmentId2'],
|
||
|
now_have_skills[0],
|
||
|
now_have_skills[1],
|
||
|
now_have_skills[2],
|
||
|
now_have_skills[3],
|
||
|
now_have_skills[4]
|
||
|
)
|
||
|
if not user_hero_id:
|
||
|
self.logger.error(f"Failed to give user {usr_sesh.user_id} hero {hero}!")
|
||
|
return RedirectResponse("/game/sao/?e=99", 303)
|
||
|
else:
|
||
|
user_hero_id = user_hero['id']
|
||
|
|
||
|
card_id = await self.data.profile.put_hero_card(usr_sesh.user_id, serial, user_hero_id, is_holo)
|
||
|
if not card_id:
|
||
|
self.logger.error(f"Failed to give user {usr_sesh.user_id} hero card {hero}!")
|
||
|
return RedirectResponse("/game/sao/?e=99", 303)
|
||
|
|
||
|
self.logger.info(f"User {usr_sesh.user_id} added hero {hero} as card with id {card_id}")
|
||
|
|
||
|
return RedirectResponse("/game/sao/?s=1", 303)
|
||
|
|
||
|
async def reg_profile(self, request: Request) -> RedirectResponse:
|
||
|
usr_sesh = self.validate_session(request)
|
||
|
if not usr_sesh:
|
||
|
return RedirectResponse("/game/sao/?e=9", 303)
|
||
|
|
||
|
frm = await request.form()
|
||
|
name = frm.get("sao_register_username")
|
||
|
|
||
|
if len(name) > 16:
|
||
|
return RedirectResponse("/game/sao/?e=8", 303)
|
||
|
|
||
|
profile_id = await self.data.profile.create_profile(usr_sesh.user_id)
|
||
|
if not profile_id:
|
||
|
self.logger.error(f"Failed to web register User {usr_sesh.user_id} with name {name}")
|
||
|
return RedirectResponse("/game/sao/?e=99", 303)
|
||
|
|
||
|
await self.data.profile.set_profile_name(usr_sesh.user_id, name)
|
||
|
|
||
|
equip1 = await self.data.item.put_equipment(usr_sesh.user_id, 101000000)
|
||
|
equip2 = await self.data.item.put_equipment(usr_sesh.user_id, 102000000)
|
||
|
equip3 = await self.data.item.put_equipment(usr_sesh.user_id, 109000000)
|
||
|
if not equip1 or not equip2 or not equip3:
|
||
|
self.logger.error(f"Failed to create profile for user {usr_sesh.user_id} from (could not add equipment)")
|
||
|
return RedirectResponse("/game/sao/?e=98", 303)
|
||
|
|
||
|
hero1 = await self.data.item.put_hero_log(usr_sesh.user_id, 101000010, 1, 0, equip1, None, 1002, 1003, 1014, None, None)
|
||
|
hero2 = await self.data.item.put_hero_log(usr_sesh.user_id, 102000010, 1, 0, equip2, None, 3001, 3002, 3004, None, None)
|
||
|
hero3 = await self.data.item.put_hero_log(usr_sesh.user_id, 105000010, 1, 0, equip3, None, 10005, 10002, 10004, None, None)
|
||
|
if not hero1 or not hero2 or not hero3:
|
||
|
self.logger.error(f"Failed to create profile for user {usr_sesh.user_id} (could not add heros)")
|
||
|
return RedirectResponse("/game/sao/?e=97", 303)
|
||
|
|
||
|
await self.data.item.put_hero_party(usr_sesh.user_id, 0, hero1, hero2, hero3)
|
||
|
self.logger.info(f"Web registered User {usr_sesh.user_id} profile {profile_id} with name {name}")
|
||
|
|
||
|
return RedirectResponse("/game/sao/?s=1", 303)
|