sao: backport changes from diana

This commit is contained in:
2024-06-25 14:02:53 -04:00
parent 6ae11f96a2
commit e91f84fecc
167 changed files with 100587 additions and 28291 deletions

View File

@ -1,17 +1,20 @@
from typing import Tuple, Dict, List
from starlette.requests import Request
from starlette.responses import Response
from starlette.responses import Response, PlainTextResponse, FileResponse
from starlette.routing import Route
import yaml
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
from os import path
from Crypto.Cipher import Blowfish
from hashlib import md5
from Crypto.Hash import MD5
import secrets
import traceback
import sys
from core import CoreConfig, Utils
from core import CoreConfig
from core.title import BaseServlet
from core.utils import Utils
from titles.sao.config import SaoConfig
from titles.sao.const import SaoConstants
from titles.sao.base import SaoBase
@ -55,13 +58,15 @@ class SaoServlet(BaseServlet):
self.static_hash = None
if self.game_cfg.hash.verify_hash:
self.static_hash = md5(self.game_cfg.hash.hash_base.encode()).digest() # Greate hashing guys, really validates the data
self.static_hash = MD5.new(self.game_cfg.hash.hash_base.encode()).digest() # Greate hashing guys, really validates the data
def get_routes(self) -> List[Route]:
return [
Route("/{datecode:int}/proto/if/{category:str}/{endpoint:str}", self.render_POST, methods=['POST'])
Route("/{datecode:int}/proto/if/{category:str}/{endpoint:str}", self.render_POST, methods=['POST']),
Route("/saoresource/{resource_type:str}/{resource_id:int}/{endpoint:str}", self.handle_resource),
Route("/system_status.php", self.render_system_status),
]
@classmethod
def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool:
game_cfg = SaoConfig()
@ -75,29 +80,25 @@ class SaoServlet(BaseServlet):
return False
return True
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
port_ssl = Utils.get_title_port_ssl(self.core_cfg)
port_normal = Utils.get_title_port(self.core_cfg)
proto = "http"
port = f":{port_normal}" if port_normal != 80 else ""
def get_allnet_info(self, game_id: str, int_ver: int, serial: str) -> Tuple[str, str]:
if self.core_cfg.server.is_using_proxy:
return (f"https://{self.core_cfg.server.hostname}/", "")
if self.game_cfg.server.use_https:
proto = "https"
port = f":{port_ssl}" if port_ssl != 443 else ""
return (f"http://{self.core_cfg.server.hostname}:{Utils.get_title_port(self.core_cfg)}/", "")
return (f"{proto}://{self.core_cfg.server.hostname}{port}/", "")
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]:
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, List[str], List[str]]:
if not self.game_cfg.server.enable:
return (False, [], [])
return (True, SaoConstants.GAME_CDS, SaoConstants.NETID_PREFIX)
async def render_system_status(self, request: Request) -> bytes:
return PlainTextResponse("open")
async def render_POST(self, request: Request) -> bytes:
endpoint = request.path_params.get('endpoint', '')
endpoint = request.path_params['endpoint']
ip = Utils.get_ip_addr(request)
iv = b""
req_raw = await request.body()
@ -122,10 +123,23 @@ class SaoServlet(BaseServlet):
else:
req_data = req_raw[40:]
handler = getattr(self.base, f"handle_{cmd_str}", self.base.handle_noop)
self.logger.info(f"{endpoint} - {cmd_str} request")
self.logger.debug(f"Request: {req_raw.hex()}")
resp = await handler(req_header, req_data)
self.logger.debug(f"{endpoint} ({cmd_str}) Request from {ip}: {req_raw.hex()}")
handler = getattr(self.base, f"handle_{cmd_str}", None)
if handler is None:
self.logger.info(f"Using Generic handler for {endpoint}")
handler = self.base.handle_noop
try:
resp = await handler(req_header, req_data, ip)
except Exception as e:
self.logger.error(f"Error handling {endpoint} - {e}")
tp, val, tb = sys.exc_info()
traceback.print_exception(tp, val, tb, limit=5)
with open("{0}/{1}.log".format(self.core_cfg.server.log_dir, "sao"), "a") as f:
traceback.print_exception(tp, val, tb, limit=5, file=f)
resp = SaoNoopResponse(req_header.cmd + 1).make()
if resp is None:
resp = SaoNoopResponse(req_header.cmd + 1).make()
@ -138,7 +152,7 @@ class SaoServlet(BaseServlet):
else:
self.logger.error(f"Unknown response type {type(resp)}")
return Response()
return SaoNoopResponse(req_header.cmd + 1).make()
self.logger.debug(f"Response: {resp.hex()}")
@ -154,6 +168,17 @@ class SaoServlet(BaseServlet):
tmp = struct.pack("!I", crypt_data_len) # does it want the length of the encrypted response??
resp = resp[:20] + tmp + iv + data_crypt
self.logger.debug(f"Encrypted Response: {resp.hex()}")
return Response(resp, media_type="text/html; charset=utf-8")
return Response(resp)
async def handle_resource(self, request: Request) -> bytes:
# TODO: better guard against path traversal attacks
resource_type = request.path_params['resource_type'].replace(".\\", "").replace("..\\", "")
resource_id = request.path_params['resource_id']
endpoint = request.path_params['endpoint'].replace(".\\", "").replace("..\\", "")
req_ip = Utils.get_ip_addr(request)
self.logger.debug(f"{req_ip} requested {resource_type} id {resource_id} {endpoint}")
if path.exists(f"./titles/sao/data/{resource_type}/{resource_id}/{endpoint}"):
return FileResponse(f"./titles/sao/data/{resource_type}/{resource_id}/{endpoint}")
return Response(status_code=404)