Compare commits
51 Commits
cardmaker_
...
frontend_u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c51103aaf5 | ||
|
|
08927db100 | ||
|
|
238e39f415 | ||
|
|
5499d38bb4 | ||
|
|
3a6cfedcca | ||
|
|
7a6272dcc5 | ||
|
|
136e47d1e6 | ||
|
|
dac655b4ae | ||
|
|
37e2da2051 | ||
|
|
9d74d60c14 | ||
|
|
d4ea3bc12a | ||
|
|
d8b0e2ea2a | ||
|
|
984949d902 | ||
|
|
2e8d99e5fa | ||
|
|
fd6cadf2da | ||
|
|
71489c1272 | ||
|
|
3773c57de1 | ||
|
|
8ea82ffe1a | ||
|
|
904ea10920 | ||
|
|
8a8c0e023e | ||
|
|
cf7cc0997a | ||
|
|
92567504f4 | ||
|
|
fd50a7ee68 | ||
|
|
9e3a51a57a | ||
|
|
4744e8cf5f | ||
|
|
88a1462304 | ||
|
|
2e277e7791 | ||
|
|
757fdc5c57 | ||
|
|
23bcb5cc13 | ||
|
|
9f0c181593 | ||
|
|
5a4baba102 | ||
|
|
156b4e4ede | ||
|
|
6c89a97fe3 | ||
|
|
b943807904 | ||
|
|
f417be671b | ||
|
|
20335aaebe | ||
|
|
097181008b | ||
|
|
63d81a2704 | ||
|
|
718229b267 | ||
|
|
7c78975431 | ||
|
|
14a315a673 | ||
|
|
343fe4357c | ||
|
|
d0e43140ba | ||
|
|
859bf4bf5d | ||
|
|
c6e7100f51 | ||
|
|
b0ca37815b | ||
|
|
f39317301b | ||
|
|
389784ce82 | ||
|
|
2f13596885 | ||
|
|
85b73e634d | ||
|
|
09c4f8cda4 |
50
changelog.md
50
changelog.md
@@ -1,6 +1,56 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
|
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
|
||||||
|
|
||||||
|
## 20230716
|
||||||
|
### General
|
||||||
|
+ Docker files added (#19)
|
||||||
|
+ Added support for threading
|
||||||
|
+ This comes with the caviat that enabling it will not allow you to use Ctrl + C to stop the server.
|
||||||
|
|
||||||
|
### Webui
|
||||||
|
+ Small improvements
|
||||||
|
+ Add card display
|
||||||
|
|
||||||
|
### Allnet
|
||||||
|
+ Billing format validation
|
||||||
|
+ Fix naomitest.html endpoint
|
||||||
|
+ Add event logging for auths and billing
|
||||||
|
+ LoaderStateRecorder endpoint handler added
|
||||||
|
|
||||||
|
### Mucha
|
||||||
|
+ Fixed log level always being "Info"
|
||||||
|
+ Add stub handler for DownloadState
|
||||||
|
|
||||||
|
### Sword Art Online
|
||||||
|
+ Support added
|
||||||
|
|
||||||
|
### Crossbeats
|
||||||
|
+ Added threading to profile loading
|
||||||
|
+ This should cause a noticeable speed-up
|
||||||
|
|
||||||
|
### Card Maker
|
||||||
|
+ DX Passes fixed
|
||||||
|
+ Various improvements
|
||||||
|
|
||||||
|
### Diva
|
||||||
|
+ Added clear status calculation
|
||||||
|
+ Various minor fixes and improvements
|
||||||
|
|
||||||
|
### Maimai
|
||||||
|
+ Added support for memorial photo uploads
|
||||||
|
+ Added support for the following versions
|
||||||
|
+ Festival
|
||||||
|
+ FiNALE
|
||||||
|
+ Various bug fixes and improvements
|
||||||
|
|
||||||
|
### Wacca
|
||||||
|
+ Fixed an error that sometimes occoured when trying to unlock songs (#22)
|
||||||
|
|
||||||
|
### Pokken
|
||||||
|
+ Profile saving added (loading TBA)
|
||||||
|
+ Use external STUN server for matching by default
|
||||||
|
+ Matching still not working
|
||||||
|
|
||||||
## 2023042300
|
## 2023042300
|
||||||
### Wacca
|
### Wacca
|
||||||
+ Time free now works properly
|
+ Time free now works properly
|
||||||
|
|||||||
6
core/adb_handlers/__init__.py
Normal file
6
core/adb_handlers/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from .base import ADBBaseRequest, ADBBaseResponse, ADBHeader, ADBHeaderException, PortalRegStatus, LogStatus, ADBStatus
|
||||||
|
from .base import CompanyCodes, ReaderFwVer, CMD_CODE_GOODBYE, HEADER_SIZE
|
||||||
|
from .lookup import ADBLookupRequest, ADBLookupResponse, ADBLookupExResponse
|
||||||
|
from .campaign import ADBCampaignClearRequest, ADBCampaignClearResponse, ADBCampaignResponse, ADBOldCampaignRequest, ADBOldCampaignResponse
|
||||||
|
from .felica import ADBFelicaLookupRequest, ADBFelicaLookupResponse, ADBFelicaLookup2Request, ADBFelicaLookup2Response
|
||||||
|
from .log import ADBLogExRequest, ADBLogRequest, ADBStatusLogRequest, ADBLogExResponse
|
||||||
170
core/adb_handlers/base.py
Normal file
170
core/adb_handlers/base.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import struct
|
||||||
|
from construct import Struct, Int16ul, Int32ul, PaddedString
|
||||||
|
from enum import Enum
|
||||||
|
import re
|
||||||
|
from typing import Union, Final
|
||||||
|
|
||||||
|
class LogStatus(Enum):
|
||||||
|
NONE = 0
|
||||||
|
START = 1
|
||||||
|
CONTINUE = 2
|
||||||
|
END = 3
|
||||||
|
OTHER = 4
|
||||||
|
|
||||||
|
class PortalRegStatus(Enum):
|
||||||
|
NO_REG = 0
|
||||||
|
PORTAL = 1
|
||||||
|
SEGA_ID = 2
|
||||||
|
|
||||||
|
class ADBStatus(Enum):
|
||||||
|
UNKNOWN = 0
|
||||||
|
GOOD = 1
|
||||||
|
BAD_AMIE_ID = 2
|
||||||
|
ALREADY_REG = 3
|
||||||
|
BAN_SYS_USER = 4
|
||||||
|
BAN_SYS = 5
|
||||||
|
BAN_USER = 6
|
||||||
|
BAN_GEN = 7
|
||||||
|
LOCK_SYS_USER = 8
|
||||||
|
LOCK_SYS = 9
|
||||||
|
LOCK_USER = 10
|
||||||
|
|
||||||
|
class CompanyCodes(Enum):
|
||||||
|
NONE = 0
|
||||||
|
SEGA = 1
|
||||||
|
BAMCO = 2
|
||||||
|
KONAMI = 3
|
||||||
|
TAITO = 4
|
||||||
|
|
||||||
|
class ReaderFwVer(Enum): # Newer readers use a singly byte value
|
||||||
|
NONE = 0
|
||||||
|
TN32_10 = 1
|
||||||
|
TN32_12 = 2
|
||||||
|
OTHER = 9
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if self == self.TN32_10:
|
||||||
|
return "TN32MSEC003S F/W Ver1.0"
|
||||||
|
elif self == self.TN32_12:
|
||||||
|
return "TN32MSEC003S F/W Ver1.2"
|
||||||
|
elif self == self.NONE:
|
||||||
|
return "Not Specified"
|
||||||
|
elif self == self.OTHER:
|
||||||
|
return "Unknown/Other"
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Bad ReaderFwVer value {self.value}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_byte(self, byte: bytes) -> Union["ReaderFwVer", int]:
|
||||||
|
try:
|
||||||
|
i = int.from_bytes(byte, 'little')
|
||||||
|
try:
|
||||||
|
return ReaderFwVer(i)
|
||||||
|
except ValueError:
|
||||||
|
return i
|
||||||
|
except TypeError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
class ADBHeaderException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
HEADER_SIZE: Final[int] = 0x20
|
||||||
|
CMD_CODE_GOODBYE: Final[int] = 0x66
|
||||||
|
|
||||||
|
# everything is LE
|
||||||
|
class ADBHeader:
|
||||||
|
def __init__(self, magic: int, protocol_ver: int, cmd: int, length: int, status: int, game_id: Union[str, bytes], store_id: int, keychip_id: Union[str, bytes]) -> None:
|
||||||
|
self.magic = magic # u16
|
||||||
|
self.protocol_ver = protocol_ver # u16
|
||||||
|
self.cmd = cmd # u16
|
||||||
|
self.length = length # u16
|
||||||
|
try:
|
||||||
|
self.status = ADBStatus(status) # u16
|
||||||
|
except ValueError as e:
|
||||||
|
raise ADBHeaderException(f"Status is incorrect! {e}")
|
||||||
|
self.game_id = game_id # 4 char + \x00
|
||||||
|
self.store_id = store_id # u32
|
||||||
|
self.keychip_id = keychip_id# 11 char + \x00
|
||||||
|
|
||||||
|
if type(self.game_id) == bytes:
|
||||||
|
self.game_id = self.game_id.decode()
|
||||||
|
|
||||||
|
if type(self.keychip_id) == bytes:
|
||||||
|
self.keychip_id = self.keychip_id.decode()
|
||||||
|
|
||||||
|
self.game_id = self.game_id.replace("\0", "")
|
||||||
|
self.keychip_id = self.keychip_id.replace("\0", "")
|
||||||
|
if self.cmd != CMD_CODE_GOODBYE: # Games for some reason send no data with goodbye
|
||||||
|
self.validate()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: bytes) -> "ADBHeader":
|
||||||
|
magic, protocol_ver, cmd, length, status, game_id, store_id, keychip_id = struct.unpack_from("<5H6sI12s", data)
|
||||||
|
head = cls(magic, protocol_ver, cmd, length, status, game_id, store_id, keychip_id)
|
||||||
|
|
||||||
|
if head.length != len(data):
|
||||||
|
raise ADBHeaderException(f"Length is incorrect! Expect {head.length}, got {len(data)}")
|
||||||
|
|
||||||
|
return head
|
||||||
|
|
||||||
|
def validate(self) -> bool:
|
||||||
|
if self.magic != 0xa13e:
|
||||||
|
raise ADBHeaderException(f"Magic {self.magic} != 0xa13e")
|
||||||
|
|
||||||
|
if self.protocol_ver < 0x1000:
|
||||||
|
raise ADBHeaderException(f"Protocol version {hex(self.protocol_ver)} is invalid!")
|
||||||
|
|
||||||
|
if re.fullmatch(r"^S[0-9A-Z]{3}[P]?$", self.game_id) is None:
|
||||||
|
raise ADBHeaderException(f"Game ID {self.game_id} is invalid!")
|
||||||
|
|
||||||
|
if self.store_id == 0:
|
||||||
|
raise ADBHeaderException(f"Store ID cannot be 0!")
|
||||||
|
|
||||||
|
if re.fullmatch(r"^A[0-9]{2}[E|X][0-9]{2}[A-HJ-NP-Z][0-9]{4}$", self.keychip_id) is None:
|
||||||
|
raise ADBHeaderException(f"Keychip ID {self.keychip_id} is invalid!")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
resp_struct = Struct(
|
||||||
|
"magic" / Int16ul,
|
||||||
|
"unknown" / Int16ul,
|
||||||
|
"response_code" / Int16ul,
|
||||||
|
"length" / Int16ul,
|
||||||
|
"status" / Int16ul,
|
||||||
|
"game_id" / PaddedString(6, 'utf_8'),
|
||||||
|
"store_id" / Int32ul,
|
||||||
|
"keychip_id" / PaddedString(12, 'utf_8'),
|
||||||
|
)
|
||||||
|
|
||||||
|
return resp_struct.build(dict(
|
||||||
|
magic=self.magic,
|
||||||
|
unknown=self.protocol_ver,
|
||||||
|
response_code=self.cmd,
|
||||||
|
length=self.length,
|
||||||
|
status=self.status.value,
|
||||||
|
game_id = self.game_id,
|
||||||
|
store_id = self.store_id,
|
||||||
|
keychip_id = self.keychip_id,
|
||||||
|
))
|
||||||
|
|
||||||
|
class ADBBaseRequest:
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
self.head = ADBHeader.from_data(data)
|
||||||
|
|
||||||
|
class ADBBaseResponse:
|
||||||
|
def __init__(self, code: int = 0, length: int = 0x20, status: int = 1, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", protocol_ver: int = 0x3087) -> None:
|
||||||
|
self.head = ADBHeader(0xa13e, protocol_ver, code, length, status, game_id, store_id, keychip_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader, cmd: int, length: int = 0x20, status: int = 1) -> "ADBBaseResponse":
|
||||||
|
return cls(cmd, length, status, req.game_id, req.store_id, req.keychip_id, req.protocol_ver)
|
||||||
|
|
||||||
|
def append_padding(self, data: bytes):
|
||||||
|
"""Appends 0s to the end of the data until it's at the correct size"""
|
||||||
|
padding_size = self.head.length - len(data)
|
||||||
|
data += bytes(padding_size)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
return self.head.make()
|
||||||
132
core/adb_handlers/campaign.py
Normal file
132
core/adb_handlers/campaign.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
from construct import Struct, Int16ul, Padding, Bytes, Int32ul, Int32sl
|
||||||
|
|
||||||
|
from .base import *
|
||||||
|
|
||||||
|
class Campaign:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.id = 0
|
||||||
|
self.name = ""
|
||||||
|
self.announce_date = 0
|
||||||
|
self.start_date = 0
|
||||||
|
self.end_date = 0
|
||||||
|
self.distrib_start_date = 0
|
||||||
|
self.distrib_end_date = 0
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
name_padding = bytes(128 - len(self.name))
|
||||||
|
return Struct(
|
||||||
|
"id" / Int32ul,
|
||||||
|
"name" / Bytes(128),
|
||||||
|
"announce_date" / Int32ul,
|
||||||
|
"start_date" / Int32ul,
|
||||||
|
"end_date" / Int32ul,
|
||||||
|
"distrib_start_date" / Int32ul,
|
||||||
|
"distrib_end_date" / Int32ul,
|
||||||
|
Padding(8),
|
||||||
|
).build(dict(
|
||||||
|
id = self.id,
|
||||||
|
name = self.name.encode() + name_padding,
|
||||||
|
announce_date = self.announce_date,
|
||||||
|
start_date = self.start_date,
|
||||||
|
end_date = self.end_date,
|
||||||
|
distrib_start_date = self.distrib_start_date,
|
||||||
|
distrib_end_date = self.distrib_end_date,
|
||||||
|
))
|
||||||
|
|
||||||
|
class CampaignClear:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.id = 0
|
||||||
|
self.entry_flag = 0
|
||||||
|
self.clear_flag = 0
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
return Struct(
|
||||||
|
"id" / Int32ul,
|
||||||
|
"entry_flag" / Int32ul,
|
||||||
|
"clear_flag" / Int32ul,
|
||||||
|
Padding(4),
|
||||||
|
).build(dict(
|
||||||
|
id = self.id,
|
||||||
|
entry_flag = self.entry_flag,
|
||||||
|
clear_flag = self.clear_flag,
|
||||||
|
))
|
||||||
|
|
||||||
|
class ADBCampaignResponse(ADBBaseResponse):
|
||||||
|
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x0C, length: int = 0x200, status: int = 1) -> None:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||||
|
self.campaigns = [Campaign(), Campaign(), Campaign()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader) -> "ADBCampaignResponse":
|
||||||
|
c = cls(req.game_id, req.store_id, req.keychip_id)
|
||||||
|
c.head.protocol_ver = req.protocol_ver
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
body = b""
|
||||||
|
|
||||||
|
for c in self.campaigns:
|
||||||
|
body += c.make()
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(body)
|
||||||
|
return self.head.make() + body
|
||||||
|
|
||||||
|
class ADBOldCampaignRequest(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.campaign_id = struct.unpack_from("<I", data, 0x20)
|
||||||
|
|
||||||
|
class ADBOldCampaignResponse(ADBBaseResponse):
|
||||||
|
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x0C, length: int = 0x30, status: int = 1) -> None:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||||
|
self.info0 = 0
|
||||||
|
self.info1 = 0
|
||||||
|
self.info2 = 0
|
||||||
|
self.info3 = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader) -> "ADBCampaignResponse":
|
||||||
|
c = cls(req.game_id, req.store_id, req.keychip_id)
|
||||||
|
c.head.protocol_ver = req.protocol_ver
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
resp_struct = Struct(
|
||||||
|
"info0" / Int32sl,
|
||||||
|
"info1" / Int32sl,
|
||||||
|
"info2" / Int32sl,
|
||||||
|
"info3" / Int32sl,
|
||||||
|
).build(
|
||||||
|
info0 = self.info0,
|
||||||
|
info1 = self.info1,
|
||||||
|
info2 = self.info2,
|
||||||
|
info3 = self.info3,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(resp_struct)
|
||||||
|
return self.head.make() + resp_struct
|
||||||
|
|
||||||
|
class ADBCampaignClearRequest(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.aime_id = struct.unpack_from("<i", data, 0x20)
|
||||||
|
|
||||||
|
class ADBCampaignClearResponse(ADBBaseResponse):
|
||||||
|
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x0E, length: int = 0x50, status: int = 1) -> None:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||||
|
self.campaign_clear_status = [CampaignClear(), CampaignClear(), CampaignClear()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader) -> "ADBCampaignResponse":
|
||||||
|
c = cls(req.game_id, req.store_id, req.keychip_id)
|
||||||
|
c.head.protocol_ver = req.protocol_ver
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
body = b""
|
||||||
|
|
||||||
|
for c in self.campaign_clear_status:
|
||||||
|
body += c.make()
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(body)
|
||||||
|
return self.head.make() + body
|
||||||
84
core/adb_handlers/felica.py
Normal file
84
core/adb_handlers/felica.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from construct import Struct, Int32sl, Padding, Int8ub, Int16sl
|
||||||
|
from typing import Union
|
||||||
|
from .base import *
|
||||||
|
|
||||||
|
class ADBFelicaLookupRequest(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
idm, pmm = struct.unpack_from(">QQ", data, 0x20)
|
||||||
|
self.idm = hex(idm)[2:].upper()
|
||||||
|
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:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||||
|
self.access_code = access_code if access_code is not None else "00000000000000000000"
|
||||||
|
|
||||||
|
@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)
|
||||||
|
c.head.protocol_ver = req.protocol_ver
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
resp_struct = Struct(
|
||||||
|
"felica_idx" / Int32ul,
|
||||||
|
"access_code" / Int8ub[10],
|
||||||
|
Padding(2)
|
||||||
|
).build(dict(
|
||||||
|
felica_idx = 0,
|
||||||
|
access_code = bytes.fromhex(self.access_code)
|
||||||
|
))
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(resp_struct)
|
||||||
|
|
||||||
|
return self.head.make() + resp_struct
|
||||||
|
|
||||||
|
class ADBFelicaLookup2Request(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.random = struct.unpack_from("<16s", data, 0x20)[0]
|
||||||
|
idm, pmm = struct.unpack_from(">QQ", data, 0x30)
|
||||||
|
self.card_key_ver, self.write_ct, self.maca, company, fw_ver, self.dfc = struct.unpack_from("<16s16sQccH", data, 0x40)
|
||||||
|
self.idm = hex(idm)[2:].upper()
|
||||||
|
self.pmm = hex(pmm)[2:].upper()
|
||||||
|
self.company = CompanyCodes(int.from_bytes(company, 'little'))
|
||||||
|
self.fw_ver = ReaderFwVer.from_byte(fw_ver)
|
||||||
|
|
||||||
|
class ADBFelicaLookup2Response(ADBBaseResponse):
|
||||||
|
def __init__(self, user_id: Union[int, None] = None, access_code: Union[str, None] = None, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x12, length: int = 0x130, status: int = 1) -> None:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||||
|
self.user_id = user_id if user_id is not None else -1
|
||||||
|
self.access_code = access_code if access_code is not None else "00000000000000000000"
|
||||||
|
self.company = CompanyCodes.SEGA
|
||||||
|
self.portal_status = PortalRegStatus.NO_REG
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader, user_id: Union[int, None] = None, access_code: Union[str, None] = None) -> "ADBFelicaLookup2Response":
|
||||||
|
c = cls(user_id, access_code, req.game_id, req.store_id, req.keychip_id)
|
||||||
|
c.head.protocol_ver = req.protocol_ver
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
resp_struct = Struct(
|
||||||
|
"user_id" / Int32sl,
|
||||||
|
"relation1" / Int32sl,
|
||||||
|
"relation2" / Int32sl,
|
||||||
|
"access_code" / Int8ub[10],
|
||||||
|
"portal_status" / Int8ub,
|
||||||
|
"company_code" / Int8ub,
|
||||||
|
Padding(8),
|
||||||
|
"auth_key" / Int8ub[256],
|
||||||
|
).build(dict(
|
||||||
|
user_id = self.user_id,
|
||||||
|
relation1 = -1, # Unsupported
|
||||||
|
relation2 = -1, # Unsupported
|
||||||
|
access_code = bytes.fromhex(self.access_code),
|
||||||
|
portal_status = self.portal_status.value,
|
||||||
|
company_code = self.company.value,
|
||||||
|
auth_key = [0] * 256 # Unsupported
|
||||||
|
))
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(resp_struct)
|
||||||
|
|
||||||
|
return self.head.make() + resp_struct
|
||||||
56
core/adb_handlers/log.py
Normal file
56
core/adb_handlers/log.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from construct import Struct, Padding, Int8sl
|
||||||
|
from typing import Final, List
|
||||||
|
|
||||||
|
from .base import *
|
||||||
|
NUM_LOGS: Final[int] = 20
|
||||||
|
NUM_LEN_LOG_EX: Final[int] = 48
|
||||||
|
|
||||||
|
class AmLogEx:
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
self.aime_id, status, self.user_id, self.credit_ct, self.bet_ct, self.won_ct, self.local_time, \
|
||||||
|
self.tseq, self.place_id = struct.unpack("<IIQiii4xQiI", data)
|
||||||
|
self.status = LogStatus(status)
|
||||||
|
|
||||||
|
class ADBStatusLogRequest(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.aime_id, status = struct.unpack_from("<II", data, 0x20)
|
||||||
|
self.status = LogStatus(status)
|
||||||
|
|
||||||
|
class ADBLogRequest(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.aime_id, status, self.user_id, self.credit_ct, self.bet_ct, self.won_ct = struct.unpack_from("<IIQiii", data, 0x20)
|
||||||
|
self.status = LogStatus(status)
|
||||||
|
|
||||||
|
class ADBLogExRequest(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.logs: List[AmLogEx] = []
|
||||||
|
|
||||||
|
for x in range(NUM_LOGS):
|
||||||
|
self.logs.append(AmLogEx(data[0x20 + (NUM_LEN_LOG_EX * x): 0x50 + (NUM_LEN_LOG_EX * x)]))
|
||||||
|
|
||||||
|
self.num_logs = struct.unpack_from("<I", data, 0x03E0)[0]
|
||||||
|
|
||||||
|
class ADBLogExResponse(ADBBaseResponse):
|
||||||
|
def __init__(self, game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", protocol_ver: int = 12423, code: int = 20, length: int = 64, status: int = 1) -> None:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id, protocol_ver)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader) -> "ADBLogExResponse":
|
||||||
|
c = cls(req.game_id, req.store_id, req.keychip_id, req.protocol_ver)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self) -> bytes:
|
||||||
|
resp_struct = Struct(
|
||||||
|
"log_result" / Int8sl[NUM_LOGS],
|
||||||
|
Padding(12)
|
||||||
|
)
|
||||||
|
|
||||||
|
body = resp_struct.build(dict(
|
||||||
|
log_result = [1] * NUM_LOGS
|
||||||
|
))
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(body)
|
||||||
|
return self.head.make() + body
|
||||||
81
core/adb_handlers/lookup.py
Normal file
81
core/adb_handlers/lookup.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from construct import Struct, Int32sl, Padding, Int8sl
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from .base import *
|
||||||
|
|
||||||
|
class ADBLookupException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ADBLookupRequest(ADBBaseRequest):
|
||||||
|
def __init__(self, data: bytes) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
self.access_code = data[0x20:0x2A].hex()
|
||||||
|
company_code, fw_version, self.serial_number = struct.unpack_from("<bbI", data, 0x2A)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.company_code = CompanyCodes(company_code)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ADBLookupException(f"Invalid company code - {e}")
|
||||||
|
|
||||||
|
self.fw_version = ReaderFwVer.from_byte(fw_version)
|
||||||
|
|
||||||
|
class ADBLookupResponse(ADBBaseResponse):
|
||||||
|
def __init__(self, user_id: Union[int, None], game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888", code: int = 0x06, length: int = 0x30, status: int = 1) -> None:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||||
|
self.user_id = user_id if user_id is not None else -1
|
||||||
|
self.portal_reg = PortalRegStatus.NO_REG
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader, user_id: Union[int, None]) -> "ADBLookupResponse":
|
||||||
|
c = cls(user_id, req.game_id, req.store_id, req.keychip_id)
|
||||||
|
c.head.protocol_ver = req.protocol_ver
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self):
|
||||||
|
resp_struct = Struct(
|
||||||
|
"user_id" / Int32sl,
|
||||||
|
"portal_reg" / Int8sl,
|
||||||
|
Padding(11)
|
||||||
|
)
|
||||||
|
|
||||||
|
body = resp_struct.build(dict(
|
||||||
|
user_id = self.user_id,
|
||||||
|
portal_reg = self.portal_reg.value
|
||||||
|
))
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(body)
|
||||||
|
return self.head.make() + body
|
||||||
|
|
||||||
|
class ADBLookupExResponse(ADBBaseResponse):
|
||||||
|
def __init__(self, user_id: Union[int, None], game_id: str = "SXXX", store_id: int = 1, keychip_id: str = "A69E01A8888",
|
||||||
|
code: int = 0x10, length: int = 0x130, status: int = 1) -> None:
|
||||||
|
super().__init__(code, length, status, game_id, store_id, keychip_id)
|
||||||
|
self.user_id = user_id if user_id is not None else -1
|
||||||
|
self.portal_reg = PortalRegStatus.NO_REG
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_req(cls, req: ADBHeader, user_id: Union[int, None]) -> "ADBLookupExResponse":
|
||||||
|
c = cls(user_id, req.game_id, req.store_id, req.keychip_id)
|
||||||
|
c.head.protocol_ver = req.protocol_ver
|
||||||
|
return c
|
||||||
|
|
||||||
|
def make(self):
|
||||||
|
resp_struct = Struct(
|
||||||
|
"user_id" / Int32sl,
|
||||||
|
"portal_reg" / Int8sl,
|
||||||
|
Padding(3),
|
||||||
|
"auth_key" / Int8sl[256],
|
||||||
|
"relation1" / Int32sl,
|
||||||
|
"relation2" / Int32sl,
|
||||||
|
)
|
||||||
|
|
||||||
|
body = resp_struct.build(dict(
|
||||||
|
user_id = self.user_id,
|
||||||
|
portal_reg = self.portal_reg.value,
|
||||||
|
auth_key = [0] * 256,
|
||||||
|
relation1 = -1,
|
||||||
|
relation2 = -1
|
||||||
|
))
|
||||||
|
|
||||||
|
self.head.length = HEADER_SIZE + len(body)
|
||||||
|
return self.head.make() + body
|
||||||
352
core/aimedb.py
352
core/aimedb.py
@@ -2,27 +2,17 @@ from twisted.internet.protocol import Factory, Protocol
|
|||||||
import logging, coloredlogs
|
import logging, coloredlogs
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
import struct
|
import struct
|
||||||
from typing import Dict, Any
|
from typing import Dict, Tuple, Callable, Union
|
||||||
|
from typing_extensions import Final
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from core.data import Data
|
from core.data import Data
|
||||||
|
from .adb_handlers import *
|
||||||
|
|
||||||
|
|
||||||
class AimedbProtocol(Protocol):
|
class AimedbProtocol(Protocol):
|
||||||
AIMEDB_RESPONSE_CODES = {
|
request_list: Dict[int, Tuple[Callable[[bytes, int], Union[ADBBaseResponse, bytes]], int, str]] = {}
|
||||||
"felica_lookup": 0x03,
|
|
||||||
"lookup": 0x06,
|
|
||||||
"log": 0x0A,
|
|
||||||
"campaign": 0x0C,
|
|
||||||
"touch": 0x0E,
|
|
||||||
"lookup2": 0x10,
|
|
||||||
"felica_lookup2": 0x12,
|
|
||||||
"log2": 0x14,
|
|
||||||
"hello": 0x65,
|
|
||||||
}
|
|
||||||
|
|
||||||
request_list: Dict[int, Any] = {}
|
|
||||||
|
|
||||||
def __init__(self, core_cfg: CoreConfig) -> None:
|
def __init__(self, core_cfg: CoreConfig) -> None:
|
||||||
self.logger = logging.getLogger("aimedb")
|
self.logger = logging.getLogger("aimedb")
|
||||||
@@ -32,16 +22,27 @@ class AimedbProtocol(Protocol):
|
|||||||
self.logger.error("!!!KEY NOT SET!!!")
|
self.logger.error("!!!KEY NOT SET!!!")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
self.request_list[0x01] = self.handle_felica_lookup
|
self.register_handler(0x01, 0x03, self.handle_felica_lookup, 'felica_lookup')
|
||||||
self.request_list[0x04] = self.handle_lookup
|
self.register_handler(0x02, 0x03, self.handle_felica_register, 'felica_register')
|
||||||
self.request_list[0x05] = self.handle_register
|
|
||||||
self.request_list[0x09] = self.handle_log
|
self.register_handler(0x04, 0x06, self.handle_lookup, 'lookup')
|
||||||
self.request_list[0x0B] = self.handle_campaign
|
self.register_handler(0x05, 0x06, self.handle_register, 'register')
|
||||||
self.request_list[0x0D] = self.handle_touch
|
|
||||||
self.request_list[0x0F] = self.handle_lookup2
|
self.register_handler(0x07, 0x08, self.handle_status_log, 'status_log')
|
||||||
self.request_list[0x11] = self.handle_felica_lookup2
|
self.register_handler(0x09, 0x0A, self.handle_log, 'aime_log')
|
||||||
self.request_list[0x13] = self.handle_log2
|
|
||||||
self.request_list[0x64] = self.handle_hello
|
self.register_handler(0x0B, 0x0C, self.handle_campaign, 'campaign')
|
||||||
|
self.register_handler(0x0D, 0x0E, self.handle_campaign_clear, 'campaign_clear')
|
||||||
|
|
||||||
|
self.register_handler(0x0F, 0x10, self.handle_lookup_ex, 'lookup_ex')
|
||||||
|
self.register_handler(0x11, 0x12, self.handle_felica_lookup_ex, 'felica_lookup_ex')
|
||||||
|
|
||||||
|
self.register_handler(0x13, 0x14, self.handle_log_ex, 'aime_log_ex')
|
||||||
|
self.register_handler(0x64, 0x65, self.handle_hello, 'hello')
|
||||||
|
self.register_handler(0x66, 0, self.handle_goodbye, 'goodbye')
|
||||||
|
|
||||||
|
def register_handler(self, cmd: int, resp:int, handler: Callable[[bytes, int], Union[ADBBaseResponse, bytes]], name: str) -> None:
|
||||||
|
self.request_list[cmd] = (handler, resp, name)
|
||||||
|
|
||||||
def append_padding(self, data: bytes):
|
def append_padding(self, data: bytes):
|
||||||
"""Appends 0s to the end of the data until it's at the correct size"""
|
"""Appends 0s to the end of the data until it's at the correct size"""
|
||||||
@@ -63,202 +64,233 @@ class AimedbProtocol(Protocol):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
decrypted = cipher.decrypt(data)
|
decrypted = cipher.decrypt(data)
|
||||||
except:
|
|
||||||
self.logger.error(f"Failed to decrypt {data.hex()}")
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to decrypt {data.hex()} because {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}")
|
self.logger.debug(f"{self.transport.getPeer().host} wrote {decrypted.hex()}")
|
||||||
|
|
||||||
if not decrypted[1] == 0xA1 and not decrypted[0] == 0x3E:
|
|
||||||
self.logger.error(f"Bad magic")
|
|
||||||
return None
|
|
||||||
|
|
||||||
req_code = decrypted[4]
|
|
||||||
|
|
||||||
if req_code == 0x66:
|
|
||||||
self.logger.info(f"goodbye from {self.transport.getPeer().host}")
|
|
||||||
self.transport.loseConnection()
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = self.request_list[req_code](decrypted)
|
head = ADBHeader.from_data(decrypted)
|
||||||
encrypted = cipher.encrypt(resp)
|
|
||||||
self.logger.debug(f"Response {resp.hex()}")
|
except ADBHeaderException as e:
|
||||||
|
self.logger.error(f"Error parsing ADB header: {e}")
|
||||||
|
try:
|
||||||
|
encrypted = cipher.encrypt(ADBBaseResponse().make())
|
||||||
self.transport.write(encrypted)
|
self.transport.write(encrypted)
|
||||||
|
|
||||||
except KeyError:
|
except Exception as e:
|
||||||
self.logger.error(f"Unknown command code {hex(req_code)}")
|
self.logger.error(f"Failed to encrypt default response because {e}")
|
||||||
return None
|
|
||||||
|
|
||||||
except ValueError as e:
|
return
|
||||||
self.logger.error(f"Failed to encrypt {resp.hex()} because {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def handle_campaign(self, data: bytes) -> bytes:
|
if head.keychip_id == "ABCD1234567" or head.store_id == 0xfff0:
|
||||||
self.logger.info(f"campaign from {self.transport.getPeer().host}")
|
self.logger.warning(f"Request from uninitialized AMLib: {vars(head)}")
|
||||||
ret = struct.pack(
|
|
||||||
"<5H",
|
handler, resp_code, name = self.request_list.get(head.cmd, (self.handle_default, None, 'default'))
|
||||||
0xA13E,
|
|
||||||
0x3087,
|
if resp_code is None:
|
||||||
self.AIMEDB_RESPONSE_CODES["campaign"],
|
self.logger.warning(f"No handler for cmd {hex(head.cmd)}")
|
||||||
0x0200,
|
|
||||||
0x0001,
|
elif resp_code > 0:
|
||||||
|
self.logger.info(f"{name} from {head.keychip_id} ({head.game_id}) @ {self.transport.getPeer().host}")
|
||||||
|
|
||||||
|
resp = handler(decrypted, resp_code)
|
||||||
|
|
||||||
|
if type(resp) == ADBBaseResponse or issubclass(type(resp), ADBBaseResponse):
|
||||||
|
resp_bytes = resp.make()
|
||||||
|
if len(resp_bytes) != resp.head.length:
|
||||||
|
resp_bytes = self.append_padding(resp_bytes)
|
||||||
|
|
||||||
|
elif type(resp) == bytes:
|
||||||
|
resp_bytes = resp
|
||||||
|
|
||||||
|
elif resp is None: # Nothing to send, probably a goodbye
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise TypeError(f"Unsupported type returned by ADB handler for {name}: {type(resp)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
encrypted = cipher.encrypt(resp_bytes)
|
||||||
|
self.logger.debug(f"Response {resp_bytes.hex()}")
|
||||||
|
self.transport.write(encrypted)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to encrypt {resp_bytes.hex()} because {e}")
|
||||||
|
|
||||||
|
def handle_default(self, data: bytes, resp_code: int, length: int = 0x20) -> ADBBaseResponse:
|
||||||
|
req = ADBHeader.from_data(data)
|
||||||
|
return ADBBaseResponse(resp_code, length, 1, req.game_id, req.store_id, req.keychip_id, req.protocol_ver)
|
||||||
|
|
||||||
|
def handle_hello(self, data: bytes, resp_code: int) -> ADBBaseResponse:
|
||||||
|
return self.handle_default(data, resp_code)
|
||||||
|
|
||||||
|
def handle_campaign(self, data: bytes, resp_code: int) -> ADBBaseResponse:
|
||||||
|
h = ADBHeader.from_data(data)
|
||||||
|
if h.protocol_ver >= 0x3030:
|
||||||
|
req = h
|
||||||
|
resp = ADBCampaignResponse.from_req(req)
|
||||||
|
|
||||||
|
else:
|
||||||
|
req = ADBOldCampaignRequest(data)
|
||||||
|
|
||||||
|
self.logger.info(f"Legacy campaign request for campaign {req.campaign_id} (protocol version {hex(h.protocol_ver)})")
|
||||||
|
resp = ADBOldCampaignResponse.from_req(req.head)
|
||||||
|
|
||||||
|
# We don't currently support campaigns
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def handle_lookup(self, data: bytes, resp_code: int) -> ADBBaseResponse:
|
||||||
|
req = ADBLookupRequest(data)
|
||||||
|
user_id = self.data.card.get_user_id_from_card(req.access_code)
|
||||||
|
|
||||||
|
ret = ADBLookupResponse.from_req(req.head, user_id)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"access_code {req.access_code} -> user_id {ret.user_id}"
|
||||||
)
|
)
|
||||||
return self.append_padding(ret)
|
return ret
|
||||||
|
|
||||||
def handle_hello(self, data: bytes) -> bytes:
|
def handle_lookup_ex(self, data: bytes, resp_code: int) -> ADBBaseResponse:
|
||||||
self.logger.info(f"hello from {self.transport.getPeer().host}")
|
req = ADBLookupRequest(data)
|
||||||
ret = struct.pack(
|
user_id = self.data.card.get_user_id_from_card(req.access_code)
|
||||||
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["hello"], 0x0020, 0x0001
|
|
||||||
|
ret = ADBLookupExResponse.from_req(req.head, user_id)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"access_code {req.access_code} -> user_id {ret.user_id}"
|
||||||
)
|
)
|
||||||
return self.append_padding(ret)
|
return ret
|
||||||
|
|
||||||
def handle_lookup(self, data: bytes) -> bytes:
|
def handle_felica_lookup(self, data: bytes, resp_code: int) -> bytes:
|
||||||
luid = data[0x20:0x2A].hex()
|
"""
|
||||||
user_id = self.data.card.get_user_id_from_card(access_code=luid)
|
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.
|
||||||
|
"""
|
||||||
|
req = ADBFelicaLookupRequest(data)
|
||||||
|
ac = self.data.card.to_access_code(req.idm)
|
||||||
|
self.logger.info(
|
||||||
|
f"idm {req.idm} ipm {req.pmm} -> access_code {ac}"
|
||||||
|
)
|
||||||
|
return ADBFelicaLookupResponse.from_req(req.head, ac)
|
||||||
|
|
||||||
|
def handle_felica_register(self, data: bytes, resp_code: int) -> bytes:
|
||||||
|
"""
|
||||||
|
I've never seen this used.
|
||||||
|
"""
|
||||||
|
req = ADBFelicaLookupRequest(data)
|
||||||
|
ac = self.data.card.to_access_code(req.idm)
|
||||||
|
|
||||||
|
if self.config.server.allow_user_registration:
|
||||||
|
user_id = self.data.user.create_user()
|
||||||
|
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
|
self.logger.error("Failed to register user!")
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
|
else:
|
||||||
|
card_id = self.data.card.create_card(user_id, ac)
|
||||||
|
|
||||||
|
if card_id is None:
|
||||||
|
self.logger.error("Failed to register card!")
|
||||||
user_id = -1
|
user_id = -1
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"lookup from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}"
|
f"Register access code {ac} (IDm: {req.idm} PMm: {req.pmm}) -> user_id {user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
ret = struct.pack(
|
|
||||||
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["lookup"], 0x0130, 0x0001
|
|
||||||
)
|
|
||||||
ret += bytes(0x20 - len(ret))
|
|
||||||
|
|
||||||
if user_id is None:
|
|
||||||
ret += struct.pack("<iH", -1, 0)
|
|
||||||
else:
|
else:
|
||||||
ret += struct.pack("<l", user_id)
|
|
||||||
return self.append_padding(ret)
|
|
||||||
|
|
||||||
def handle_lookup2(self, data: bytes) -> bytes:
|
|
||||||
self.logger.info(f"lookup2")
|
|
||||||
|
|
||||||
ret = bytearray(self.handle_lookup(data))
|
|
||||||
ret[4] = self.AIMEDB_RESPONSE_CODES["lookup2"]
|
|
||||||
|
|
||||||
return bytes(ret)
|
|
||||||
|
|
||||||
def handle_felica_lookup(self, data: bytes) -> bytes:
|
|
||||||
idm = data[0x20:0x28].hex()
|
|
||||||
pmm = data[0x28:0x30].hex()
|
|
||||||
access_code = self.data.card.to_access_code(idm)
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"felica_lookup from {self.transport.getPeer().host}: idm {idm} pmm {pmm} -> access_code {access_code}"
|
f"Registration blocked!: access code {ac} (IDm: {req.idm} PMm: {req.pmm})"
|
||||||
)
|
)
|
||||||
|
|
||||||
ret = struct.pack(
|
return ADBFelicaLookupResponse.from_req(req.head, ac)
|
||||||
"<5H",
|
|
||||||
0xA13E,
|
|
||||||
0x3087,
|
|
||||||
self.AIMEDB_RESPONSE_CODES["felica_lookup"],
|
|
||||||
0x0030,
|
|
||||||
0x0001,
|
|
||||||
)
|
|
||||||
ret += bytes(26)
|
|
||||||
ret += bytes.fromhex(access_code)
|
|
||||||
|
|
||||||
return self.append_padding(ret)
|
def handle_felica_lookup_ex(self, data: bytes, resp_code: int) -> bytes:
|
||||||
|
req = ADBFelicaLookup2Request(data)
|
||||||
def handle_felica_lookup2(self, data: bytes) -> bytes:
|
access_code = self.data.card.to_access_code(req.idm)
|
||||||
idm = data[0x30:0x38].hex()
|
|
||||||
pmm = data[0x38:0x40].hex()
|
|
||||||
access_code = self.data.card.to_access_code(idm)
|
|
||||||
user_id = self.data.card.get_user_id_from_card(access_code=access_code)
|
user_id = self.data.card.get_user_id_from_card(access_code=access_code)
|
||||||
|
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
user_id = -1
|
user_id = -1
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"felica_lookup2 from {self.transport.getPeer().host}: idm {idm} ipm {pmm} -> access_code {access_code} user_id {user_id}"
|
f"idm {req.idm} ipm {req.pmm} -> access_code {access_code} user_id {user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
ret = struct.pack(
|
return ADBFelicaLookup2Response.from_req(req.head, user_id, access_code)
|
||||||
"<5H",
|
|
||||||
0xA13E,
|
|
||||||
0x3087,
|
|
||||||
self.AIMEDB_RESPONSE_CODES["felica_lookup2"],
|
|
||||||
0x0140,
|
|
||||||
0x0001,
|
|
||||||
)
|
|
||||||
ret += bytes(22)
|
|
||||||
ret += struct.pack("<lq", user_id, -1) # first -1 is ext_id, 3rd is access code
|
|
||||||
ret += bytes.fromhex(access_code)
|
|
||||||
ret += struct.pack("<l", 1)
|
|
||||||
|
|
||||||
return self.append_padding(ret)
|
def handle_campaign_clear(self, data: bytes, resp_code: int) -> ADBBaseResponse:
|
||||||
|
req = ADBCampaignClearRequest(data)
|
||||||
|
|
||||||
def handle_touch(self, data: bytes) -> bytes:
|
resp = ADBCampaignClearResponse.from_req(req.head)
|
||||||
self.logger.info(f"touch from {self.transport.getPeer().host}")
|
|
||||||
ret = struct.pack(
|
|
||||||
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["touch"], 0x0050, 0x0001
|
|
||||||
)
|
|
||||||
ret += bytes(5)
|
|
||||||
ret += struct.pack("<3H", 0x6F, 0, 1)
|
|
||||||
|
|
||||||
return self.append_padding(ret)
|
# We don't support campaign stuff
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def handle_register(self, data: bytes, resp_code: int) -> bytes:
|
||||||
|
req = ADBLookupRequest(data)
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
def handle_register(self, data: bytes) -> bytes:
|
|
||||||
luid = data[0x20:0x2A].hex()
|
|
||||||
if self.config.server.allow_user_registration:
|
if self.config.server.allow_user_registration:
|
||||||
user_id = self.data.user.create_user()
|
user_id = self.data.user.create_user()
|
||||||
|
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
user_id = -1
|
|
||||||
self.logger.error("Failed to register user!")
|
self.logger.error("Failed to register user!")
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
card_id = self.data.card.create_card(user_id, luid)
|
card_id = self.data.card.create_card(user_id, req.access_code)
|
||||||
|
|
||||||
if card_id is None:
|
if card_id is None:
|
||||||
user_id = -1
|
|
||||||
self.logger.error("Failed to register card!")
|
self.logger.error("Failed to register card!")
|
||||||
|
user_id = -1
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"register from {self.transport.getPeer().host}: luid {luid} -> user_id {user_id}"
|
f"Register access code {req.access_code} -> user_id {user_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"register from {self.transport.getPeer().host} blocked!: luid {luid}"
|
f"Registration blocked!: access code {req.access_code}"
|
||||||
)
|
)
|
||||||
user_id = -1
|
|
||||||
|
|
||||||
ret = struct.pack(
|
resp = ADBLookupResponse.from_req(req.head, user_id)
|
||||||
"<5H",
|
if resp.user_id <= 0:
|
||||||
0xA13E,
|
resp.head.status = ADBStatus.BAN_SYS # Closest we can get to a "You cannot register"
|
||||||
0x3087,
|
|
||||||
self.AIMEDB_RESPONSE_CODES["lookup"],
|
|
||||||
0x0030,
|
|
||||||
0x0001 if user_id > -1 else 0,
|
|
||||||
)
|
|
||||||
ret += bytes(0x20 - len(ret))
|
|
||||||
ret += struct.pack("<l", user_id)
|
|
||||||
|
|
||||||
return self.append_padding(ret)
|
return resp
|
||||||
|
|
||||||
def handle_log(self, data: bytes) -> bytes:
|
# TODO: Save these in some capacity, as deemed relevant
|
||||||
# TODO: Save aimedb logs
|
def handle_status_log(self, data: bytes, resp_code: int) -> bytes:
|
||||||
self.logger.info(f"log from {self.transport.getPeer().host}")
|
req = ADBStatusLogRequest(data)
|
||||||
ret = struct.pack(
|
self.logger.info(f"User {req.aime_id} logged {req.status.name} event")
|
||||||
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["log"], 0x0020, 0x0001
|
return ADBBaseResponse(resp_code, 0x20, 1, req.head.game_id, req.head.store_id, req.head.keychip_id, req.head.protocol_ver)
|
||||||
)
|
|
||||||
return self.append_padding(ret)
|
|
||||||
|
|
||||||
def handle_log2(self, data: bytes) -> bytes:
|
def handle_log(self, data: bytes, resp_code: int) -> bytes:
|
||||||
self.logger.info(f"log2 from {self.transport.getPeer().host}")
|
req = ADBLogRequest(data)
|
||||||
ret = struct.pack(
|
self.logger.info(f"User {req.aime_id} logged {req.status.name} event, credit_ct: {req.credit_ct} bet_ct: {req.bet_ct} won_ct: {req.won_ct}")
|
||||||
"<5H", 0xA13E, 0x3087, self.AIMEDB_RESPONSE_CODES["log2"], 0x0040, 0x0001
|
return ADBBaseResponse(resp_code, 0x20, 1, req.head.game_id, req.head.store_id, req.head.keychip_id, req.head.protocol_ver)
|
||||||
)
|
|
||||||
ret += bytes(22)
|
|
||||||
ret += struct.pack("H", 1)
|
|
||||||
|
|
||||||
return self.append_padding(ret)
|
def handle_log_ex(self, data: bytes, resp_code: int) -> bytes:
|
||||||
|
req = ADBLogExRequest(data)
|
||||||
|
strs = []
|
||||||
|
self.logger.info(f"Recieved {req.num_logs} or {len(req.logs)} logs")
|
||||||
|
|
||||||
|
for x in range(req.num_logs):
|
||||||
|
self.logger.debug(f"User {req.logs[x].aime_id} logged {req.logs[x].status.name} event, credit_ct: {req.logs[x].credit_ct} bet_ct: {req.logs[x].bet_ct} won_ct: {req.logs[x].won_ct}")
|
||||||
|
return ADBLogExResponse.from_req(req.head)
|
||||||
|
|
||||||
|
def handle_goodbye(self, data: bytes, resp_code: int) -> None:
|
||||||
|
self.logger.info(f"goodbye from {self.transport.getPeer().host}")
|
||||||
|
self.transport.loseConnection()
|
||||||
|
return
|
||||||
|
|
||||||
class AimedbFactory(Factory):
|
class AimedbFactory(Factory):
|
||||||
protocol = AimedbProtocol
|
protocol = AimedbProtocol
|
||||||
|
|||||||
488
core/allnet.py
488
core/allnet.py
@@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, List, Any, Optional, Tuple, Union
|
from typing import Dict, List, Any, Optional, Tuple, Union, Final
|
||||||
import logging, coloredlogs
|
import logging, coloredlogs
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
from twisted.web.http import Request
|
from twisted.web.http import Request
|
||||||
@@ -6,18 +6,32 @@ from datetime import datetime
|
|||||||
import pytz
|
import pytz
|
||||||
import base64
|
import base64
|
||||||
import zlib
|
import zlib
|
||||||
|
import json
|
||||||
|
from enum import Enum
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
from Crypto.Hash import SHA
|
from Crypto.Hash import SHA
|
||||||
from Crypto.Signature import PKCS1_v1_5
|
from Crypto.Signature import PKCS1_v1_5
|
||||||
from time import strptime
|
from time import strptime
|
||||||
from os import path
|
from os import path
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import math
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from core.utils import Utils
|
from core.utils import Utils
|
||||||
from core.data import Data
|
from core.data import Data
|
||||||
from core.const import *
|
from core.const import *
|
||||||
|
|
||||||
|
BILLING_DT_FORMAT: Final[str] = "%Y%m%d%H%M%S"
|
||||||
|
|
||||||
|
class DLIMG_TYPE(Enum):
|
||||||
|
app = 0
|
||||||
|
opt = 1
|
||||||
|
|
||||||
|
class ALLNET_STAT(Enum):
|
||||||
|
ok = 0
|
||||||
|
bad_game = -1
|
||||||
|
bad_machine = -2
|
||||||
|
bad_shop = -3
|
||||||
|
|
||||||
class AllnetServlet:
|
class AllnetServlet:
|
||||||
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
def __init__(self, core_cfg: CoreConfig, cfg_folder: str):
|
||||||
@@ -72,8 +86,16 @@ class AllnetServlet:
|
|||||||
|
|
||||||
def handle_poweron(self, request: Request, _: Dict):
|
def handle_poweron(self, request: Request, _: Dict):
|
||||||
request_ip = Utils.get_ip_addr(request)
|
request_ip = Utils.get_ip_addr(request)
|
||||||
|
pragma_header = request.getHeader('Pragma')
|
||||||
|
is_dfi = pragma_header is not None and pragma_header == "DFI"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req_dict = self.allnet_req_to_dict(request.content.getvalue())
|
if is_dfi:
|
||||||
|
req_urlencode = self.from_dfi(request.content.getvalue())
|
||||||
|
else:
|
||||||
|
req_urlencode = request.content.getvalue().decode()
|
||||||
|
|
||||||
|
req_dict = self.allnet_req_to_dict(req_urlencode)
|
||||||
if req_dict is None:
|
if req_dict is None:
|
||||||
raise AllnetRequestException()
|
raise AllnetRequestException()
|
||||||
|
|
||||||
@@ -98,32 +120,6 @@ class AllnetServlet:
|
|||||||
resp = AllnetPowerOnResponse()
|
resp = AllnetPowerOnResponse()
|
||||||
|
|
||||||
self.logger.debug(f"Allnet request: {vars(req)}")
|
self.logger.debug(f"Allnet request: {vars(req)}")
|
||||||
if req.game_id not in self.uri_registry:
|
|
||||||
if not self.config.server.is_develop:
|
|
||||||
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
|
||||||
self.data.base.log_event(
|
|
||||||
"allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg
|
|
||||||
)
|
|
||||||
self.logger.warn(msg)
|
|
||||||
|
|
||||||
resp.stat = -1
|
|
||||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
|
||||||
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.logger.info(
|
|
||||||
f"Allowed unknown game {req.game_id} v{req.ver} to authenticate from {request_ip} due to 'is_develop' being enabled. S/N: {req.serial}"
|
|
||||||
)
|
|
||||||
resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/{req.game_id}/{req.ver.replace('.', '')}/"
|
|
||||||
resp.host = f"{self.config.title.hostname}:{self.config.title.port}"
|
|
||||||
|
|
||||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
|
||||||
resp_str = urllib.parse.unquote(urllib.parse.urlencode(resp_dict))
|
|
||||||
|
|
||||||
self.logger.debug(f"Allnet response: {resp_str}")
|
|
||||||
return (resp_str + "\n").encode("utf-8")
|
|
||||||
|
|
||||||
resp.uri, resp.host = self.uri_registry[req.game_id]
|
|
||||||
|
|
||||||
machine = self.data.arcade.get_machine(req.serial)
|
machine = self.data.arcade.get_machine(req.serial)
|
||||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||||
@@ -131,14 +127,38 @@ class AllnetServlet:
|
|||||||
self.data.base.log_event(
|
self.data.base.log_event(
|
||||||
"allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg
|
"allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg
|
||||||
)
|
)
|
||||||
self.logger.warn(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
resp.stat = -2
|
resp.stat = ALLNET_STAT.bad_machine.value
|
||||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
||||||
|
|
||||||
if machine is not None:
|
if machine is not None:
|
||||||
arcade = self.data.arcade.get_arcade(machine["arcade"])
|
arcade = self.data.arcade.get_arcade(machine["arcade"])
|
||||||
|
if self.config.server.check_arcade_ip:
|
||||||
|
if arcade["ip"] and arcade["ip"] is not None and arcade["ip"] != req.ip:
|
||||||
|
msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip} (expected {arcade['ip']})."
|
||||||
|
self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_BAD_IP", logging.ERROR, msg
|
||||||
|
)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
|
||||||
|
resp.stat = ALLNET_STAT.bad_shop.value
|
||||||
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
|
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
||||||
|
|
||||||
|
elif not arcade["ip"] or arcade["ip"] is None and self.config.server.strict_ip_checking:
|
||||||
|
msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)."
|
||||||
|
self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg
|
||||||
|
)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
|
||||||
|
resp.stat = ALLNET_STAT.bad_shop.value
|
||||||
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
|
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
country = (
|
country = (
|
||||||
arcade["country"] if machine["country"] is None else machine["country"]
|
arcade["country"] if machine["country"] is None else machine["country"]
|
||||||
)
|
)
|
||||||
@@ -170,6 +190,33 @@ class AllnetServlet:
|
|||||||
arcade["timezone"] if arcade["timezone"] is not None else "+0900"
|
arcade["timezone"] if arcade["timezone"] is not None else "+0900"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if req.game_id not in self.uri_registry:
|
||||||
|
if not self.config.server.is_develop:
|
||||||
|
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
||||||
|
self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg
|
||||||
|
)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
|
||||||
|
resp.stat = ALLNET_STAT.bad_game.value
|
||||||
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
|
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info(
|
||||||
|
f"Allowed unknown game {req.game_id} v{req.ver} to authenticate from {request_ip} due to 'is_develop' being enabled. S/N: {req.serial}"
|
||||||
|
)
|
||||||
|
resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/{req.game_id}/{req.ver.replace('.', '')}/"
|
||||||
|
resp.host = f"{self.config.title.hostname}:{self.config.title.port}"
|
||||||
|
|
||||||
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
|
resp_str = urllib.parse.unquote(urllib.parse.urlencode(resp_dict))
|
||||||
|
|
||||||
|
self.logger.debug(f"Allnet response: {resp_str}")
|
||||||
|
return (resp_str + "\n").encode("utf-8")
|
||||||
|
|
||||||
|
resp.uri, resp.host = self.uri_registry[req.game_id]
|
||||||
|
|
||||||
int_ver = req.ver.replace(".", "")
|
int_ver = req.ver.replace(".", "")
|
||||||
resp.uri = resp.uri.replace("$v", int_ver)
|
resp.uri = resp.uri.replace("$v", int_ver)
|
||||||
resp.host = resp.host.replace("$v", int_ver)
|
resp.host = resp.host.replace("$v", int_ver)
|
||||||
@@ -183,12 +230,24 @@ class AllnetServlet:
|
|||||||
self.logger.debug(f"Allnet response: {resp_dict}")
|
self.logger.debug(f"Allnet response: {resp_dict}")
|
||||||
resp_str += "\n"
|
resp_str += "\n"
|
||||||
|
|
||||||
|
"""if is_dfi:
|
||||||
|
request.responseHeaders.addRawHeader('Pragma', 'DFI')
|
||||||
|
return self.to_dfi(resp_str)"""
|
||||||
|
|
||||||
return resp_str.encode("utf-8")
|
return resp_str.encode("utf-8")
|
||||||
|
|
||||||
def handle_dlorder(self, request: Request, _: Dict):
|
def handle_dlorder(self, request: Request, _: Dict):
|
||||||
request_ip = Utils.get_ip_addr(request)
|
request_ip = Utils.get_ip_addr(request)
|
||||||
|
pragma_header = request.getHeader('Pragma')
|
||||||
|
is_dfi = pragma_header is not None and pragma_header == "DFI"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req_dict = self.allnet_req_to_dict(request.content.getvalue())
|
if is_dfi:
|
||||||
|
req_urlencode = self.from_dfi(request.content.getvalue())
|
||||||
|
else:
|
||||||
|
req_urlencode = request.content.getvalue().decode()
|
||||||
|
|
||||||
|
req_dict = self.allnet_req_to_dict(req_urlencode)
|
||||||
if req_dict is None:
|
if req_dict is None:
|
||||||
raise AllnetRequestException()
|
raise AllnetRequestException()
|
||||||
|
|
||||||
@@ -230,7 +289,13 @@ class AllnetServlet:
|
|||||||
self.logger.debug(f"Sending download uri {resp.uri}")
|
self.logger.debug(f"Sending download uri {resp.uri}")
|
||||||
self.data.base.log_event("allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"{Utils.get_ip_addr(request)} requested DL Order for {req.serial} {req.game_id} v{req.ver}")
|
self.data.base.log_event("allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"{Utils.get_ip_addr(request)} requested DL Order for {req.serial} {req.game_id} v{req.ver}")
|
||||||
|
|
||||||
return urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n"
|
res_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n"
|
||||||
|
"""if is_dfi:
|
||||||
|
request.responseHeaders.addRawHeader('Pragma', 'DFI')
|
||||||
|
return self.to_dfi(res_str)"""
|
||||||
|
|
||||||
|
return res_str
|
||||||
|
|
||||||
|
|
||||||
def handle_dlorder_ini(self, request: Request, match: Dict) -> bytes:
|
def handle_dlorder_ini(self, request: Request, match: Dict) -> bytes:
|
||||||
if "file" not in match:
|
if "file" not in match:
|
||||||
@@ -241,6 +306,7 @@ class AllnetServlet:
|
|||||||
if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"):
|
if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"):
|
||||||
self.logger.info(f"Request for DL INI file {req_file} from {Utils.get_ip_addr(request)} successful")
|
self.logger.info(f"Request for DL INI file {req_file} from {Utils.get_ip_addr(request)} successful")
|
||||||
self.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}")
|
self.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}")
|
||||||
|
|
||||||
return open(
|
return open(
|
||||||
f"{self.config.allnet.update_cfg_folder}/{req_file}", "rb"
|
f"{self.config.allnet.update_cfg_folder}/{req_file}", "rb"
|
||||||
).read()
|
).read()
|
||||||
@@ -249,10 +315,31 @@ class AllnetServlet:
|
|||||||
return b""
|
return b""
|
||||||
|
|
||||||
def handle_dlorder_report(self, request: Request, match: Dict) -> bytes:
|
def handle_dlorder_report(self, request: Request, match: Dict) -> bytes:
|
||||||
self.logger.info(
|
req_raw = request.content.getvalue()
|
||||||
f"DLI Report from {Utils.get_ip_addr(request)}: {request.content.getvalue()}"
|
try:
|
||||||
)
|
req_dict: Dict = json.loads(req_raw)
|
||||||
return b""
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Failed to parse DL Report: {e}")
|
||||||
|
return "NG"
|
||||||
|
|
||||||
|
dl_data_type = DLIMG_TYPE.app
|
||||||
|
dl_data = req_dict.get("appimage", {})
|
||||||
|
|
||||||
|
if dl_data is None or not dl_data:
|
||||||
|
dl_data_type = DLIMG_TYPE.opt
|
||||||
|
dl_data = req_dict.get("optimage", {})
|
||||||
|
|
||||||
|
if dl_data is None or not dl_data:
|
||||||
|
self.logger.warning(f"Failed to parse DL Report: Invalid format - contains neither appimage nor optimage")
|
||||||
|
return "NG"
|
||||||
|
|
||||||
|
dl_report_data = DLReport(dl_data, dl_data_type)
|
||||||
|
|
||||||
|
if not dl_report_data.validate():
|
||||||
|
self.logger.warning(f"Failed to parse DL Report: Invalid format - {dl_report_data.err}")
|
||||||
|
return "NG"
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
|
||||||
def handle_loaderstaterecorder(self, request: Request, match: Dict) -> bytes:
|
def handle_loaderstaterecorder(self, request: Request, match: Dict) -> bytes:
|
||||||
req_data = request.content.getvalue()
|
req_data = request.content.getvalue()
|
||||||
@@ -276,8 +363,16 @@ class AllnetServlet:
|
|||||||
return "OK".encode()
|
return "OK".encode()
|
||||||
|
|
||||||
def handle_billing_request(self, request: Request, _: Dict):
|
def handle_billing_request(self, request: Request, _: Dict):
|
||||||
req_dict = self.billing_req_to_dict(request.content.getvalue())
|
req_raw = request.content.getvalue()
|
||||||
|
|
||||||
|
if request.getHeader('Content-Type') == "application/octet-stream":
|
||||||
|
req_unzip = zlib.decompressobj(-zlib.MAX_WBITS).decompress(req_raw)
|
||||||
|
else:
|
||||||
|
req_unzip = req_raw
|
||||||
|
|
||||||
|
req_dict = self.billing_req_to_dict(req_unzip)
|
||||||
request_ip = Utils.get_ip_addr(request)
|
request_ip = Utils.get_ip_addr(request)
|
||||||
|
|
||||||
if req_dict is None:
|
if req_dict is None:
|
||||||
self.logger.error(f"Failed to parse request {request.content.getvalue()}")
|
self.logger.error(f"Failed to parse request {request.content.getvalue()}")
|
||||||
return b""
|
return b""
|
||||||
@@ -287,45 +382,60 @@ class AllnetServlet:
|
|||||||
rsa = RSA.import_key(open(self.config.billing.signing_key, "rb").read())
|
rsa = RSA.import_key(open(self.config.billing.signing_key, "rb").read())
|
||||||
signer = PKCS1_v1_5.new(rsa)
|
signer = PKCS1_v1_5.new(rsa)
|
||||||
digest = SHA.new()
|
digest = SHA.new()
|
||||||
|
traces: List[TraceData] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
kc_playlimit = int(req_dict[0]["playlimit"])
|
for x in range(len(req_dict)):
|
||||||
kc_nearfull = int(req_dict[0]["nearfull"])
|
if not req_dict[x]:
|
||||||
kc_billigtype = int(req_dict[0]["billingtype"])
|
continue
|
||||||
kc_playcount = int(req_dict[0]["playcnt"])
|
|
||||||
kc_serial: str = req_dict[0]["keychipid"]
|
if x == 0:
|
||||||
kc_game: str = req_dict[0]["gameid"]
|
req = BillingInfo(req_dict[x])
|
||||||
kc_date = strptime(req_dict[0]["date"], "%Y%m%d%H%M%S")
|
continue
|
||||||
kc_serial_bytes = kc_serial.encode()
|
|
||||||
|
tmp = TraceData(req_dict[x])
|
||||||
|
if tmp.trace_type == TraceDataType.CHARGE:
|
||||||
|
tmp = TraceDataCharge(req_dict[x])
|
||||||
|
elif tmp.trace_type == TraceDataType.EVENT:
|
||||||
|
tmp = TraceDataEvent(req_dict[x])
|
||||||
|
elif tmp.trace_type == TraceDataType.CREDIT:
|
||||||
|
tmp = TraceDataCredit(req_dict[x])
|
||||||
|
|
||||||
|
traces.append(tmp)
|
||||||
|
|
||||||
|
kc_serial_bytes = req.keychipid.encode()
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
return f"result=5&linelimit=&message={e} field is missing".encode()
|
self.logger.error(f"Billing request failed to parse: {e}")
|
||||||
|
return f"result=5&linelimit=&message=field is missing or formatting is incorrect\r\n".encode()
|
||||||
|
|
||||||
machine = self.data.arcade.get_machine(kc_serial)
|
machine = self.data.arcade.get_machine(req.keychipid)
|
||||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||||
msg = f"Unrecognised serial {kc_serial} attempted billing checkin from {request_ip} for game {kc_game}."
|
msg = f"Unrecognised serial {req.keychipid} attempted billing checkin from {request_ip} for {req.gameid} v{req.gamever}."
|
||||||
self.data.base.log_event(
|
self.data.base.log_event(
|
||||||
"allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg
|
"allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg
|
||||||
)
|
)
|
||||||
self.logger.warn(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
resp = BillingResponse("", "", "", "")
|
return f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n".encode()
|
||||||
resp.result = "1"
|
|
||||||
return self.dict_to_http_form_string([vars(resp)])
|
|
||||||
|
|
||||||
msg = (
|
msg = (
|
||||||
f"Billing checkin from {request_ip}: game {kc_game} keychip {kc_serial} playcount "
|
f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount "
|
||||||
f"{kc_playcount} billing_type {kc_billigtype} nearfull {kc_nearfull} playlimit {kc_playlimit}"
|
f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}"
|
||||||
)
|
)
|
||||||
self.logger.info(msg)
|
self.logger.info(msg)
|
||||||
self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg)
|
self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg)
|
||||||
|
if req.traceleft > 0:
|
||||||
|
self.logger.warn(f"{req.traceleft} unsent tracelogs")
|
||||||
|
kc_playlimit = req.playlimit
|
||||||
|
kc_nearfull = req.nearfull
|
||||||
|
|
||||||
while kc_playcount > kc_playlimit:
|
while req.playcnt > req.playlimit:
|
||||||
kc_playlimit += 1024
|
kc_playlimit += 1024
|
||||||
kc_nearfull += 1024
|
kc_nearfull += 1024
|
||||||
|
|
||||||
playlimit = kc_playlimit
|
playlimit = kc_playlimit
|
||||||
nearfull = kc_nearfull + (kc_billigtype * 0x00010000)
|
nearfull = kc_nearfull + (req.billingtype.value * 0x00010000)
|
||||||
|
|
||||||
digest.update(playlimit.to_bytes(4, "little") + kc_serial_bytes)
|
digest.update(playlimit.to_bytes(4, "little") + kc_serial_bytes)
|
||||||
playlimit_sig = signer.sign(digest).hex()
|
playlimit_sig = signer.sign(digest).hex()
|
||||||
@@ -336,13 +446,16 @@ class AllnetServlet:
|
|||||||
|
|
||||||
# TODO: playhistory
|
# TODO: playhistory
|
||||||
|
|
||||||
resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig)
|
#resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig)
|
||||||
|
resp = BillingResponse(playlimit, playlimit_sig, nearfull, nearfull_sig, req.requestno, req.protocolver)
|
||||||
|
|
||||||
resp_str = self.dict_to_http_form_string([vars(resp)])
|
resp_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\r\n"
|
||||||
if resp_str is None:
|
|
||||||
self.logger.error(f"Failed to parse response {vars(resp)}")
|
|
||||||
|
|
||||||
self.logger.debug(f"response {vars(resp)}")
|
self.logger.debug(f"response {vars(resp)}")
|
||||||
|
if req.traceleft > 0:
|
||||||
|
self.logger.info(f"Requesting 20 more of {req.traceleft} unsent tracelogs")
|
||||||
|
return f"result=6&waittime=0&linelimit=20\r\n".encode()
|
||||||
|
|
||||||
return resp_str.encode("utf-8")
|
return resp_str.encode("utf-8")
|
||||||
|
|
||||||
def handle_naomitest(self, request: Request, _: Dict) -> bytes:
|
def handle_naomitest(self, request: Request, _: Dict) -> bytes:
|
||||||
@@ -354,9 +467,7 @@ class AllnetServlet:
|
|||||||
Parses an billing request string into a python dictionary
|
Parses an billing request string into a python dictionary
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
decomp = zlib.decompressobj(-zlib.MAX_WBITS)
|
sections = data.decode("ascii").split("\r\n")
|
||||||
unzipped = decomp.decompress(data)
|
|
||||||
sections = unzipped.decode("ascii").split("\r\n")
|
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
for x in sections:
|
for x in sections:
|
||||||
@@ -372,9 +483,7 @@ class AllnetServlet:
|
|||||||
Parses an allnet request string into a python dictionary
|
Parses an allnet request string into a python dictionary
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
zipped = base64.b64decode(data)
|
sections = data.split("\r\n")
|
||||||
unzipped = zlib.decompress(zipped)
|
|
||||||
sections = unzipped.decode("utf-8").split("\r\n")
|
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
for x in sections:
|
for x in sections:
|
||||||
@@ -385,35 +494,15 @@ class AllnetServlet:
|
|||||||
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
|
self.logger.error(f"allnet_req_to_dict: {e} while parsing {data}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def dict_to_http_form_string(
|
def from_dfi(self, data: bytes) -> str:
|
||||||
self,
|
zipped = base64.b64decode(data)
|
||||||
data: List[Dict[str, Any]],
|
unzipped = zlib.decompress(zipped)
|
||||||
crlf: bool = True,
|
return unzipped.decode("utf-8")
|
||||||
trailing_newline: bool = True,
|
|
||||||
) -> Optional[str]:
|
def to_dfi(self, data: str) -> bytes:
|
||||||
"""
|
unzipped = data.encode('utf-8')
|
||||||
Takes a python dictionary and parses it into an allnet response string
|
zipped = zlib.compress(unzipped)
|
||||||
"""
|
return base64.b64encode(zipped)
|
||||||
try:
|
|
||||||
urlencode = ""
|
|
||||||
for item in data:
|
|
||||||
for k, v in item.items():
|
|
||||||
if k is None or v is None:
|
|
||||||
continue
|
|
||||||
urlencode += f"{k}={v}&"
|
|
||||||
if crlf:
|
|
||||||
urlencode = urlencode[:-1] + "\r\n"
|
|
||||||
else:
|
|
||||||
urlencode = urlencode[:-1] + "\n"
|
|
||||||
if not trailing_newline:
|
|
||||||
if crlf:
|
|
||||||
urlencode = urlencode[:-2]
|
|
||||||
else:
|
|
||||||
urlencode = urlencode[:-1]
|
|
||||||
return urlencode
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"dict_to_http_form_string: {e} while parsing {data}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class AllnetPowerOnRequest:
|
class AllnetPowerOnRequest:
|
||||||
@@ -499,6 +588,114 @@ class AllnetDownloadOrderResponse:
|
|||||||
self.serial = serial
|
self.serial = serial
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
|
|
||||||
|
class TraceDataType(Enum):
|
||||||
|
CHARGE = 0
|
||||||
|
EVENT = 1
|
||||||
|
CREDIT = 2
|
||||||
|
|
||||||
|
class BillingType(Enum):
|
||||||
|
A = 1
|
||||||
|
B = 0
|
||||||
|
|
||||||
|
class float5:
|
||||||
|
def __init__(self, n: str = "0") -> None:
|
||||||
|
nf = float(n)
|
||||||
|
if nf > 999.9 or nf < 0:
|
||||||
|
raise ValueError('float5 must be between 0.000 and 999.9 inclusive')
|
||||||
|
|
||||||
|
return nf
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_str(cls, f: float):
|
||||||
|
return f"%.{2 - int(math.log10(f))+1}f" % f
|
||||||
|
|
||||||
|
class BillingInfo:
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
|
try:
|
||||||
|
self.keychipid = str(data.get("keychipid", None))
|
||||||
|
self.functype = int(data.get("functype", None))
|
||||||
|
self.gameid = str(data.get("gameid", None))
|
||||||
|
self.gamever = float(data.get("gamever", None))
|
||||||
|
self.boardid = str(data.get("boardid", None))
|
||||||
|
self.tenpoip = str(data.get("tenpoip", None))
|
||||||
|
self.libalibver = float(data.get("libalibver", None))
|
||||||
|
self.datamax = int(data.get("datamax", None))
|
||||||
|
self.billingtype = BillingType(int(data.get("billingtype", None)))
|
||||||
|
self.protocolver = float(data.get("protocolver", None))
|
||||||
|
self.operatingfix = bool(data.get("operatingfix", None))
|
||||||
|
self.traceleft = int(data.get("traceleft", None))
|
||||||
|
self.requestno = int(data.get("requestno", None))
|
||||||
|
self.datesync = bool(data.get("datesync", None))
|
||||||
|
self.timezone = str(data.get("timezone", None))
|
||||||
|
self.date = datetime.strptime(data.get("date", None), BILLING_DT_FORMAT)
|
||||||
|
self.crcerrcnt = int(data.get("crcerrcnt", None))
|
||||||
|
self.memrepair = bool(data.get("memrepair", None))
|
||||||
|
self.playcnt = int(data.get("playcnt", None))
|
||||||
|
self.playlimit = int(data.get("playlimit", None))
|
||||||
|
self.nearfull = int(data.get("nearfull", None))
|
||||||
|
except Exception as e:
|
||||||
|
raise KeyError(e)
|
||||||
|
|
||||||
|
class TraceData:
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
|
try:
|
||||||
|
self.crc_err_flg = bool(data.get("cs", None))
|
||||||
|
self.record_number = int(data.get("rn", None))
|
||||||
|
self.seq_number = int(data.get("sn", None))
|
||||||
|
self.trace_type = TraceDataType(int(data.get("tt", None)))
|
||||||
|
self.date_sync_flg = bool(data.get("ds", None))
|
||||||
|
self.date = datetime.strptime(data.get("dt", None), BILLING_DT_FORMAT)
|
||||||
|
self.keychip = str(data.get("kn", None))
|
||||||
|
self.lib_ver = float(data.get("alib", None))
|
||||||
|
except Exception as e:
|
||||||
|
raise KeyError(e)
|
||||||
|
|
||||||
|
class TraceDataCharge(TraceData):
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
try:
|
||||||
|
self.game_id = str(data.get("gi", None))
|
||||||
|
self.game_version = float(data.get("gv", None))
|
||||||
|
self.board_serial = str(data.get("bn", None))
|
||||||
|
self.shop_ip = str(data.get("ti", None))
|
||||||
|
self.play_count = int(data.get("pc", None))
|
||||||
|
self.play_limit = int(data.get("pl", None))
|
||||||
|
self.product_code = int(data.get("ic", None))
|
||||||
|
self.product_count = int(data.get("in", None))
|
||||||
|
self.func_type = int(data.get("kk", None))
|
||||||
|
self.player_number = int(data.get("playerno", None))
|
||||||
|
except Exception as e:
|
||||||
|
raise KeyError(e)
|
||||||
|
|
||||||
|
class TraceDataEvent(TraceData):
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
try:
|
||||||
|
self.message = str(data.get("me", None))
|
||||||
|
except Exception as e:
|
||||||
|
raise KeyError(e)
|
||||||
|
|
||||||
|
class TraceDataCredit(TraceData):
|
||||||
|
def __init__(self, data: Dict) -> None:
|
||||||
|
super().__init__(data)
|
||||||
|
try:
|
||||||
|
self.chute_type = int(data.get("cct", None))
|
||||||
|
self.service_type = int(data.get("cst", None))
|
||||||
|
self.operation_type = int(data.get("cop", None))
|
||||||
|
self.coin_rate0 = int(data.get("cr0", None))
|
||||||
|
self.coin_rate1 = int(data.get("cr1", None))
|
||||||
|
self.bonus_addition = int(data.get("cba", None))
|
||||||
|
self.credit_rate = int(data.get("ccr", None))
|
||||||
|
self.credit0 = int(data.get("cc0", None))
|
||||||
|
self.credit1 = int(data.get("cc1", None))
|
||||||
|
self.credit2 = int(data.get("cc2", None))
|
||||||
|
self.credit3 = int(data.get("cc3", None))
|
||||||
|
self.credit4 = int(data.get("cc4", None))
|
||||||
|
self.credit5 = int(data.get("cc5", None))
|
||||||
|
self.credit6 = int(data.get("cc6", None))
|
||||||
|
self.credit7 = int(data.get("cc7", None))
|
||||||
|
except Exception as e:
|
||||||
|
raise KeyError(e)
|
||||||
|
|
||||||
class BillingResponse:
|
class BillingResponse:
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -507,20 +704,22 @@ class BillingResponse:
|
|||||||
playlimit_sig: str = "",
|
playlimit_sig: str = "",
|
||||||
nearfull: str = "",
|
nearfull: str = "",
|
||||||
nearfull_sig: str = "",
|
nearfull_sig: str = "",
|
||||||
|
request_num: int = 1,
|
||||||
|
protocol_ver: float = 1.000,
|
||||||
playhistory: str = "000000/0:000000/0:000000/0",
|
playhistory: str = "000000/0:000000/0:000000/0",
|
||||||
) -> None:
|
) -> None:
|
||||||
self.result = "0"
|
self.result = 0
|
||||||
self.waitime = "100"
|
self.requestno = request_num
|
||||||
self.linelimit = "1"
|
self.traceerase = 1
|
||||||
self.message = ""
|
self.fixinterval = 120
|
||||||
|
self.fixlogcnt = 100
|
||||||
self.playlimit = playlimit
|
self.playlimit = playlimit
|
||||||
self.playlimitsig = playlimit_sig
|
self.playlimitsig = playlimit_sig
|
||||||
self.protocolver = "1.000"
|
self.playhistory = playhistory
|
||||||
self.nearfull = nearfull
|
self.nearfull = nearfull
|
||||||
self.nearfullsig = nearfull_sig
|
self.nearfullsig = nearfull_sig
|
||||||
self.fixlogincnt = "0"
|
self.linelimit = 100
|
||||||
self.fixinterval = "5"
|
self.protocolver = float5.to_str(protocol_ver)
|
||||||
self.playhistory = playhistory
|
|
||||||
# playhistory -> YYYYMM/C:...
|
# playhistory -> YYYYMM/C:...
|
||||||
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
|
# YYYY -> 4 digit year, MM -> 2 digit month, C -> Playcount during that period
|
||||||
|
|
||||||
@@ -529,3 +728,86 @@ class AllnetRequestException(Exception):
|
|||||||
def __init__(self, message="") -> None:
|
def __init__(self, message="") -> None:
|
||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
class DLReport:
|
||||||
|
def __init__(self, data: Dict, report_type: DLIMG_TYPE) -> None:
|
||||||
|
self.serial = data.get("serial")
|
||||||
|
self.dfl = data.get("dfl")
|
||||||
|
self.wfl = data.get("wfl")
|
||||||
|
self.tsc = data.get("tsc")
|
||||||
|
self.tdsc = data.get("tdsc")
|
||||||
|
self.at = data.get("at")
|
||||||
|
self.ot = data.get("ot")
|
||||||
|
self.rt = data.get("rt")
|
||||||
|
self.as_ = data.get("as")
|
||||||
|
self.rf_state = data.get("rf_state")
|
||||||
|
self.gd = data.get("gd")
|
||||||
|
self.dav = data.get("dav")
|
||||||
|
self.wdav = data.get("wdav") # app only
|
||||||
|
self.dov = data.get("dov")
|
||||||
|
self.wdov = data.get("wdov") # app only
|
||||||
|
self.__type = report_type
|
||||||
|
self.err = ""
|
||||||
|
|
||||||
|
def validate(self) -> bool:
|
||||||
|
if self.serial is None:
|
||||||
|
self.err = "serial not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.dfl is None:
|
||||||
|
self.err = "dfl not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.wfl is None:
|
||||||
|
self.err = "wfl not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.tsc is None:
|
||||||
|
self.err = "tsc not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.tdsc is None:
|
||||||
|
self.err = "tdsc not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.at is None:
|
||||||
|
self.err = "at not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.ot is None:
|
||||||
|
self.err = "ot not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.rt is None:
|
||||||
|
self.err = "rt not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.as_ is None:
|
||||||
|
self.err = "as not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.rf_state is None:
|
||||||
|
self.err = "rf_state not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.gd is None:
|
||||||
|
self.err = "gd not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.dav is None:
|
||||||
|
self.err = "dav not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.dov is None:
|
||||||
|
self.err = "dov not provided"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (self.wdav is None or self.wdov is None) and self.__type == DLIMG_TYPE.app:
|
||||||
|
self.err = "wdav or wdov not provided in app image"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (self.wdav is not None or self.wdov is not None) and self.__type == DLIMG_TYPE.opt:
|
||||||
|
self.err = "wdav or wdov provided in opt image"
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -48,6 +48,18 @@ class ServerConfig:
|
|||||||
self.__config, "core", "server", "log_dir", default="logs"
|
self.__config, "core", "server", "log_dir", default="logs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def check_arcade_ip(self) -> bool:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "check_arcade_ip", default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strict_ip_checking(self) -> bool:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "server", "strict_ip_checking", default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TitleConfig:
|
class TitleConfig:
|
||||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||||
@@ -188,6 +200,12 @@ class AllnetConfig:
|
|||||||
self.__config, "core", "allnet", "port", default=80
|
self.__config, "core", "allnet", "port", default=80
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip_check(self) -> bool:
|
||||||
|
return CoreConfig.get_config_field(
|
||||||
|
self.__config, "core", "allnet", "ip_check", default=False
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def allow_online_updates(self) -> int:
|
def allow_online_updates(self) -> int:
|
||||||
return CoreConfig.get_config_field(
|
return CoreConfig.get_config_field(
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from core.utils import Utils
|
|||||||
|
|
||||||
|
|
||||||
class Data:
|
class Data:
|
||||||
current_schema_version = 4
|
current_schema_version = 6
|
||||||
engine = None
|
engine = None
|
||||||
session = None
|
session = None
|
||||||
user = None
|
user = None
|
||||||
@@ -27,9 +27,9 @@ class Data:
|
|||||||
|
|
||||||
if self.config.database.sha2_password:
|
if self.config.database.sha2_password:
|
||||||
passwd = sha256(self.config.database.password.encode()).digest()
|
passwd = sha256(self.config.database.password.encode()).digest()
|
||||||
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4"
|
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
|
||||||
else:
|
else:
|
||||||
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}/{self.config.database.name}?charset=utf8mb4"
|
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
|
||||||
|
|
||||||
if Data.engine is None:
|
if Data.engine is None:
|
||||||
Data.engine = create_engine(self.__url, pool_recycle=3600)
|
Data.engine = create_engine(self.__url, pool_recycle=3600)
|
||||||
@@ -94,7 +94,7 @@ class Data:
|
|||||||
game_mod.database(self.config)
|
game_mod.database(self.config)
|
||||||
metadata.create_all(self.__engine.connect())
|
metadata.create_all(self.__engine.connect())
|
||||||
|
|
||||||
self.base.set_schema_ver(
|
self.base.touch_schema_ver(
|
||||||
game_mod.current_schema_version, game_mod.game_codes[0]
|
game_mod.current_schema_version, game_mod.game_codes[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ class Data:
|
|||||||
version = mod.current_schema_version
|
version = mod.current_schema_version
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"current_schema_version not found for {folder}"
|
f"current_schema_version not found for {folder}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ class Data:
|
|||||||
version = self.current_schema_version
|
version = self.current_schema_version
|
||||||
|
|
||||||
if version is None:
|
if version is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Could not determine latest version for {game}, please specify --version"
|
f"Could not determine latest version for {game}, please specify --version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ class Data:
|
|||||||
self.logger.error(f"Failed to create card for owner with id {user_id}")
|
self.logger.error(f"Failed to create card for owner with id {user_id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!"
|
f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ class Data:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not should_force:
|
if not should_force:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."
|
f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."
|
||||||
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
||||||
)
|
)
|
||||||
@@ -307,7 +307,7 @@ class Data:
|
|||||||
def autoupgrade(self) -> None:
|
def autoupgrade(self) -> None:
|
||||||
all_game_versions = self.base.get_all_schema_vers()
|
all_game_versions = self.base.get_all_schema_vers()
|
||||||
if all_game_versions is None:
|
if all_game_versions is None:
|
||||||
self.logger.warn("Failed to get schema versions")
|
self.logger.warning("Failed to get schema versions")
|
||||||
return
|
return
|
||||||
|
|
||||||
all_games = Utils.get_all_titles()
|
all_games = Utils.get_all_titles()
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from typing import Optional, Dict
|
from typing import Optional, Dict, List
|
||||||
from sqlalchemy import Table, Column
|
from sqlalchemy import Table, Column, and_, or_
|
||||||
from sqlalchemy.sql.schema import ForeignKey, PrimaryKeyConstraint
|
from sqlalchemy.sql.schema import ForeignKey, PrimaryKeyConstraint
|
||||||
from sqlalchemy.types import Integer, String, Boolean
|
from sqlalchemy.types import Integer, String, Boolean, JSON
|
||||||
from sqlalchemy.sql import func, select
|
from sqlalchemy.sql import func, select
|
||||||
from sqlalchemy.dialects.mysql import insert
|
from sqlalchemy.dialects.mysql import insert
|
||||||
|
from sqlalchemy.engine import Row
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from core.data.schema.base import BaseData, metadata
|
from core.data.schema.base import BaseData, metadata
|
||||||
@@ -21,6 +22,7 @@ arcade = Table(
|
|||||||
Column("city", String(255)),
|
Column("city", String(255)),
|
||||||
Column("region_id", Integer),
|
Column("region_id", Integer),
|
||||||
Column("timezone", String(255)),
|
Column("timezone", String(255)),
|
||||||
|
Column("ip", String(39)),
|
||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,6 +42,9 @@ machine = Table(
|
|||||||
Column("timezone", String(255)),
|
Column("timezone", String(255)),
|
||||||
Column("ota_enable", Boolean),
|
Column("ota_enable", Boolean),
|
||||||
Column("is_cab", Boolean),
|
Column("is_cab", Boolean),
|
||||||
|
Column("memo", String(255)),
|
||||||
|
Column("is_cab", Boolean),
|
||||||
|
Column("data", JSON),
|
||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -65,7 +70,7 @@ arcade_owner = Table(
|
|||||||
|
|
||||||
|
|
||||||
class ArcadeData(BaseData):
|
class ArcadeData(BaseData):
|
||||||
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
|
def get_machine(self, serial: str = None, id: int = None) -> Optional[Row]:
|
||||||
if serial is not None:
|
if serial is not None:
|
||||||
serial = serial.replace("-", "")
|
serial = serial.replace("-", "")
|
||||||
if len(serial) == 11:
|
if len(serial) == 11:
|
||||||
@@ -130,13 +135,20 @@ class ArcadeData(BaseData):
|
|||||||
f"Failed to update board id for machine {machine_id} -> {boardid}"
|
f"Failed to update board id for machine {machine_id} -> {boardid}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_arcade(self, id: int) -> Optional[Dict]:
|
def get_arcade(self, id: int) -> Optional[Row]:
|
||||||
sql = arcade.select(arcade.c.id == id)
|
sql = arcade.select(arcade.c.id == id)
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
|
def get_arcade_machines(self, id: int) -> Optional[List[Row]]:
|
||||||
|
sql = machine.select(machine.c.arcade == id)
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
def put_arcade(
|
def put_arcade(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
@@ -165,7 +177,21 @@ class ArcadeData(BaseData):
|
|||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
def get_arcade_owners(self, arcade_id: int) -> Optional[Dict]:
|
def get_arcades_managed_by_user(self, user_id: int) -> Optional[List[Row]]:
|
||||||
|
sql = select(arcade).join(arcade_owner, arcade_owner.c.arcade == arcade.c.id).where(arcade_owner.c.user == user_id)
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
def get_manager_permissions(self, user_id: int, arcade_id: int) -> Optional[int]:
|
||||||
|
sql = select(arcade_owner.c.permissions).where(and_(arcade_owner.c.user == user_id, arcade_owner.c.arcade == arcade_id))
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
return result.fetchone()
|
||||||
|
|
||||||
|
def get_arcade_owners(self, arcade_id: int) -> Optional[Row]:
|
||||||
sql = select(arcade_owner).where(arcade_owner.c.arcade == arcade_id)
|
sql = select(arcade_owner).where(arcade_owner.c.arcade == arcade_id)
|
||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
@@ -187,33 +213,14 @@ class ArcadeData(BaseData):
|
|||||||
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
|
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
|
||||||
|
|
||||||
def validate_keychip_format(self, serial: str) -> bool:
|
def validate_keychip_format(self, serial: str) -> bool:
|
||||||
serial = serial.replace("-", "")
|
if re.fullmatch(r"^A[0-9]{2}[E|X][-]?[0-9]{2}[A-HJ-NP-Z][0-9]{4}([0-9]{4})?$", serial) is None:
|
||||||
if len(serial) != 11 or len(serial) != 15:
|
|
||||||
self.logger.error(
|
|
||||||
f"Serial validate failed: Incorrect length for {serial} (len {len(serial)})"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
platform_code = serial[:4]
|
|
||||||
platform_rev = serial[4:6]
|
|
||||||
const_a = serial[6]
|
|
||||||
num = serial[7:11]
|
|
||||||
append = serial[11:15]
|
|
||||||
|
|
||||||
if re.match("A[7|6]\d[E|X][0|1][0|1|2]A\d{4,8}", serial) is None:
|
|
||||||
self.logger.error(f"Serial validate failed: {serial} failed regex")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len(append) != 0 or len(append) != 4:
|
|
||||||
self.logger.error(
|
|
||||||
f"Serial validate failed: {serial} had malformed append {append}"
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len(num) != 4:
|
|
||||||
self.logger.error(
|
|
||||||
f"Serial validate failed: {serial} had malformed number {num}"
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def find_arcade_by_name(self, name: str) -> List[Row]:
|
||||||
|
sql = arcade.select(or_(arcade.c.name.like(f"%{name}%"), arcade.c.nickname.like(f"%{name}%")))
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
return result.fetchall()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class BaseData:
|
|||||||
self.logger.error(f"UnicodeEncodeError error {e}")
|
self.logger.error(f"UnicodeEncodeError error {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
res = self.conn.execute(sql, opts)
|
res = self.conn.execute(sql, opts)
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class BaseData:
|
|||||||
self.logger.error(f"UnicodeEncodeError error {e}")
|
self.logger.error(f"UnicodeEncodeError error {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
self.logger.error(f"Unknown error")
|
self.logger.error(f"Unknown error")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -103,6 +103,18 @@ class BaseData:
|
|||||||
|
|
||||||
return row["version"]
|
return row["version"]
|
||||||
|
|
||||||
|
def touch_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
||||||
|
sql = insert(schema_ver).values(game=game, version=ver)
|
||||||
|
conflict = sql.on_duplicate_key_update(version=schema_ver.c.version)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
|
if result is None:
|
||||||
|
self.logger.error(
|
||||||
|
f"Failed to update schema version for game {game} (v{ver})"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
||||||
sql = insert(schema_ver).values(game=game, version=ver)
|
sql = insert(schema_ver).values(game=game, version=ver)
|
||||||
conflict = sql.on_duplicate_key_update(version=ver)
|
conflict = sql.on_duplicate_key_update(version=ver)
|
||||||
|
|||||||
@@ -107,3 +107,17 @@ class UserData(BaseData):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
|
def find_user_by_email(self, email: str) -> Row:
|
||||||
|
sql = select(aime_user).where(aime_user.c.email == email)
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
return result.fetchone()
|
||||||
|
|
||||||
|
def find_user_by_username(self, username: str) -> List[Row]:
|
||||||
|
sql = aime_user.select(aime_user.c.username.like(f"%{username}%"))
|
||||||
|
result = self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
return False
|
||||||
|
return result.fetchall()
|
||||||
|
|||||||
3
core/data/schema/versions/CORE_4_rollback.sql
Normal file
3
core/data/schema/versions/CORE_4_rollback.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE machine DROP COLUMN memo;
|
||||||
|
ALTER TABLE machine DROP COLUMN is_blacklisted;
|
||||||
|
ALTER TABLE machine DROP COLUMN `data`;
|
||||||
1
core/data/schema/versions/CORE_5_rollback.sql
Normal file
1
core/data/schema/versions/CORE_5_rollback.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE arcade DROP COLUMN 'ip';
|
||||||
3
core/data/schema/versions/CORE_5_upgrade.sql
Normal file
3
core/data/schema/versions/CORE_5_upgrade.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE machine ADD memo varchar(255) NULL;
|
||||||
|
ALTER TABLE machine ADD is_blacklisted tinyint(1) NULL;
|
||||||
|
ALTER TABLE machine ADD `data` longtext NULL;
|
||||||
1
core/data/schema/versions/CORE_6_upgrade.sql
Normal file
1
core/data/schema/versions/CORE_6_upgrade.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE arcade ADD ip varchar(39) NULL;
|
||||||
@@ -51,7 +51,7 @@ ALTER TABLE mai2_item_favorite MODIFY COLUMN itemKind int(11) NOT NULL;
|
|||||||
|
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN seasonId int(11) NOT NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN seasonId int(11) NOT NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN point int(11) NOT NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN point int(11) NOT NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rank int(11) NOT NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN `rank` int(11) NOT NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rewardGet tinyint(1) NOT NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rewardGet tinyint(1) NOT NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN userName varchar(8) NOT NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN userName varchar(8) NOT NULL;
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ ALTER TABLE mai2_item_favorite MODIFY COLUMN itemKind int(11) NULL;
|
|||||||
|
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN seasonId int(11) NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN seasonId int(11) NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN point int(11) NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN point int(11) NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rank int(11) NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN `rank` int(11) NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rewardGet tinyint(1) NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN rewardGet tinyint(1) NULL;
|
||||||
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN userName varchar(8) NULL;
|
ALTER TABLE mai2_item_friend_season_ranking MODIFY COLUMN userName varchar(8) NULL;
|
||||||
|
|
||||||
|
|||||||
257
core/frontend.py
257
core/frontend.py
@@ -9,21 +9,35 @@ from zope.interface import Interface, Attribute, implementer
|
|||||||
from twisted.python.components import registerAdapter
|
from twisted.python.components import registerAdapter
|
||||||
import jinja2
|
import jinja2
|
||||||
import bcrypt
|
import bcrypt
|
||||||
|
import re
|
||||||
|
from enum import Enum
|
||||||
|
from urllib import parse
|
||||||
|
|
||||||
from core import CoreConfig, Utils
|
from core import CoreConfig, Utils
|
||||||
from core.data import Data
|
from core.data import Data
|
||||||
|
|
||||||
|
|
||||||
class IUserSession(Interface):
|
class IUserSession(Interface):
|
||||||
userId = Attribute("User's ID")
|
user_id = Attribute("User's ID")
|
||||||
|
username = Attribute("User's username")
|
||||||
current_ip = Attribute("User's current ip address")
|
current_ip = Attribute("User's current ip address")
|
||||||
permissions = Attribute("User's permission level")
|
permissions = Attribute("User's permission level")
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionOffset(Enum):
|
||||||
|
USER = 0 # Regular user
|
||||||
|
USERMOD = 1 # Can moderate other users
|
||||||
|
ACMOD = 2 # Can add arcades and cabs
|
||||||
|
SYSADMIN = 3 # Can change settings
|
||||||
|
# 4 - 6 reserved for future use
|
||||||
|
OWNER = 7 # Can do anything
|
||||||
|
|
||||||
|
|
||||||
@implementer(IUserSession)
|
@implementer(IUserSession)
|
||||||
class UserSession(object):
|
class UserSession(object):
|
||||||
def __init__(self, session):
|
def __init__(self, session):
|
||||||
self.userId = 0
|
self.user_id = 0
|
||||||
|
self.username = None
|
||||||
self.current_ip = "0.0.0.0"
|
self.current_ip = "0.0.0.0"
|
||||||
self.permissions = 0
|
self.permissions = 0
|
||||||
|
|
||||||
@@ -80,6 +94,9 @@ class FrontendServlet(resource.Resource):
|
|||||||
self.environment.globals["game_list"] = self.game_list
|
self.environment.globals["game_list"] = self.game_list
|
||||||
self.putChild(b"gate", FE_Gate(cfg, self.environment))
|
self.putChild(b"gate", FE_Gate(cfg, self.environment))
|
||||||
self.putChild(b"user", FE_User(cfg, self.environment))
|
self.putChild(b"user", FE_User(cfg, self.environment))
|
||||||
|
self.putChild(b"sys", FE_System(cfg, self.environment))
|
||||||
|
self.putChild(b"arcade", FE_Arcade(cfg, self.environment))
|
||||||
|
self.putChild(b"cab", FE_Machine(cfg, self.environment))
|
||||||
self.putChild(b"game", fe_game)
|
self.putChild(b"game", fe_game)
|
||||||
|
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
@@ -93,6 +110,7 @@ class FrontendServlet(resource.Resource):
|
|||||||
server_name=self.config.server.name,
|
server_name=self.config.server.name,
|
||||||
title=self.config.server.name,
|
title=self.config.server.name,
|
||||||
game_list=self.game_list,
|
game_list=self.game_list,
|
||||||
|
active_page="/",
|
||||||
sesh=vars(IUserSession(request.getSession())),
|
sesh=vars(IUserSession(request.getSession())),
|
||||||
).encode("utf-16")
|
).encode("utf-16")
|
||||||
|
|
||||||
@@ -121,7 +139,7 @@ class FE_Gate(FE_Base):
|
|||||||
|
|
||||||
sesh = request.getSession()
|
sesh = request.getSession()
|
||||||
usr_sesh = IUserSession(sesh)
|
usr_sesh = IUserSession(sesh)
|
||||||
if usr_sesh.userId > 0:
|
if usr_sesh.user_id > 0:
|
||||||
return redirectTo(b"/user", request)
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
if uri.startswith("/gate/create"):
|
if uri.startswith("/gate/create"):
|
||||||
@@ -130,7 +148,7 @@ class FE_Gate(FE_Base):
|
|||||||
if b"e" in request.args:
|
if b"e" in request.args:
|
||||||
try:
|
try:
|
||||||
err = int(request.args[b"e"][0].decode())
|
err = int(request.args[b"e"][0].decode())
|
||||||
except:
|
except Exception:
|
||||||
err = 0
|
err = 0
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -140,6 +158,7 @@ class FE_Gate(FE_Base):
|
|||||||
return template.render(
|
return template.render(
|
||||||
title=f"{self.core_config.server.name} | Login Gate",
|
title=f"{self.core_config.server.name} | Login Gate",
|
||||||
error=err,
|
error=err,
|
||||||
|
active_page="gate",
|
||||||
sesh=vars(usr_sesh),
|
sesh=vars(usr_sesh),
|
||||||
).encode("utf-16")
|
).encode("utf-16")
|
||||||
|
|
||||||
@@ -148,12 +167,13 @@ class FE_Gate(FE_Base):
|
|||||||
ip = Utils.get_ip_addr(request)
|
ip = Utils.get_ip_addr(request)
|
||||||
|
|
||||||
if uri == "/gate/gate.login":
|
if uri == "/gate/gate.login":
|
||||||
access_code: str = request.args[b"access_code"][0].decode()
|
access_code: str = request.args[b"access-code"][0].decode()
|
||||||
passwd: bytes = request.args[b"passwd"][0]
|
passwd: bytes = request.args[b"passwd"][0]
|
||||||
if passwd == b"":
|
if passwd == b"":
|
||||||
passwd = None
|
passwd = None
|
||||||
|
|
||||||
uid = self.data.card.get_user_id_from_card(access_code)
|
uid = self.data.card.get_user_id_from_card(access_code)
|
||||||
|
user = self.data.user.get_user(uid)
|
||||||
if uid is None:
|
if uid is None:
|
||||||
return redirectTo(b"/gate?e=1", request)
|
return redirectTo(b"/gate?e=1", request)
|
||||||
|
|
||||||
@@ -173,8 +193,10 @@ class FE_Gate(FE_Base):
|
|||||||
|
|
||||||
sesh = request.getSession()
|
sesh = request.getSession()
|
||||||
usr_sesh = IUserSession(sesh)
|
usr_sesh = IUserSession(sesh)
|
||||||
usr_sesh.userId = uid
|
usr_sesh.user_id = uid
|
||||||
|
usr_sesh.username = user["username"]
|
||||||
usr_sesh.current_ip = ip
|
usr_sesh.current_ip = ip
|
||||||
|
usr_sesh.permissions = user["permissions"]
|
||||||
|
|
||||||
return redirectTo(b"/user", request)
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
@@ -192,7 +214,7 @@ class FE_Gate(FE_Base):
|
|||||||
hashed = bcrypt.hashpw(passwd, salt)
|
hashed = bcrypt.hashpw(passwd, salt)
|
||||||
|
|
||||||
result = self.data.user.create_user(
|
result = self.data.user.create_user(
|
||||||
uid, username, email, hashed.decode(), 1
|
uid, username, email.lower(), hashed.decode(), 1
|
||||||
)
|
)
|
||||||
if result is None:
|
if result is None:
|
||||||
return redirectTo(b"/gate?e=3", request)
|
return redirectTo(b"/gate?e=3", request)
|
||||||
@@ -210,39 +232,179 @@ class FE_Gate(FE_Base):
|
|||||||
return redirectTo(b"/gate?e=2", request)
|
return redirectTo(b"/gate?e=2", request)
|
||||||
|
|
||||||
ac = request.args[b"ac"][0].decode()
|
ac = request.args[b"ac"][0].decode()
|
||||||
|
card = self.data.card.get_card_by_access_code(ac)
|
||||||
|
if card is None:
|
||||||
|
return redirectTo(b"/gate?e=1", request)
|
||||||
|
|
||||||
|
user = self.data.user.get_user(card["user"])
|
||||||
|
if user is None:
|
||||||
|
self.logger.warning(
|
||||||
|
f"Card {ac} exists with no/invalid associated user ID {card['user']}"
|
||||||
|
)
|
||||||
|
return redirectTo(b"/gate?e=0", request)
|
||||||
|
|
||||||
|
if user["password"] is not None:
|
||||||
|
return redirectTo(b"/gate?e=1", request)
|
||||||
|
|
||||||
template = self.environment.get_template("core/frontend/gate/create.jinja")
|
template = self.environment.get_template("core/frontend/gate/create.jinja")
|
||||||
return template.render(
|
return template.render(
|
||||||
title=f"{self.core_config.server.name} | Create User",
|
title=f"{self.core_config.server.name} | Create User",
|
||||||
code=ac,
|
code=ac,
|
||||||
sesh={"userId": 0},
|
active_page="gate",
|
||||||
|
sesh={"user_id": 0, "permissions": 0},
|
||||||
).encode("utf-16")
|
).encode("utf-16")
|
||||||
|
|
||||||
|
|
||||||
class FE_User(FE_Base):
|
class FE_User(FE_Base):
|
||||||
def render_GET(self, request: Request):
|
def render_GET(self, request: Request):
|
||||||
|
uri = request.uri.decode()
|
||||||
template = self.environment.get_template("core/frontend/user/index.jinja")
|
template = self.environment.get_template("core/frontend/user/index.jinja")
|
||||||
|
|
||||||
sesh: Session = request.getSession()
|
sesh: Session = request.getSession()
|
||||||
usr_sesh = IUserSession(sesh)
|
usr_sesh = IUserSession(sesh)
|
||||||
if usr_sesh.userId == 0:
|
if usr_sesh.user_id == 0:
|
||||||
return redirectTo(b"/gate", request)
|
return redirectTo(b"/gate", request)
|
||||||
|
|
||||||
cards = self.data.card.get_user_cards(usr_sesh.userId)
|
m = re.match("\/user\/(\d*)", uri)
|
||||||
user = self.data.user.get_user(usr_sesh.userId)
|
|
||||||
card_data = []
|
|
||||||
for c in cards:
|
|
||||||
if c['is_locked']:
|
|
||||||
status = 'Locked'
|
|
||||||
elif c['is_banned']:
|
|
||||||
status = 'Banned'
|
|
||||||
else:
|
|
||||||
status = 'Active'
|
|
||||||
|
|
||||||
card_data.append({'access_code': c['access_code'], 'status': status})
|
if m is not None:
|
||||||
|
usrid = m.group(1)
|
||||||
|
if (
|
||||||
|
usr_sesh.permissions < 1 << PermissionOffset.USERMOD.value
|
||||||
|
or not usrid == usr_sesh.user_id
|
||||||
|
):
|
||||||
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
|
else:
|
||||||
|
usrid = usr_sesh.user_id
|
||||||
|
|
||||||
|
user = self.data.user.get_user(usrid)
|
||||||
|
if user is None:
|
||||||
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
|
cards = self.data.card.get_user_cards(usrid)
|
||||||
|
arcades = self.data.arcade.get_arcades_managed_by_user(usrid)
|
||||||
|
|
||||||
|
card_data = []
|
||||||
|
arcade_data = []
|
||||||
|
|
||||||
|
for i, c in enumerate(cards):
|
||||||
|
if c["is_locked"]:
|
||||||
|
status = "Locked"
|
||||||
|
elif c["is_banned"]:
|
||||||
|
status = "Banned"
|
||||||
|
else:
|
||||||
|
status = "Active"
|
||||||
|
|
||||||
|
card_data.append({"index": i+1, "access_code": c["access_code"], "status": status})
|
||||||
|
|
||||||
|
for i, a in enumerate(arcades):
|
||||||
|
arcade_data.append({"index": i+1, "id": a["id"], "name": a["name"]})
|
||||||
|
|
||||||
return template.render(
|
return template.render(
|
||||||
title=f"{self.core_config.server.name} | Account", sesh=vars(usr_sesh), cards=card_data, username=user['username']
|
title=f"{self.core_config.server.name} | Account",
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
cards=card_data,
|
||||||
|
arcades=arcade_data,
|
||||||
|
active_page="user",
|
||||||
|
).encode("utf-16")
|
||||||
|
|
||||||
|
def render_POST(self, request: Request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FE_System(FE_Base):
|
||||||
|
def render_GET(self, request: Request):
|
||||||
|
uri = request.uri.decode()
|
||||||
|
template = self.environment.get_template("core/frontend/sys/index.jinja")
|
||||||
|
usrlist = []
|
||||||
|
aclist = []
|
||||||
|
cablist = []
|
||||||
|
|
||||||
|
sesh: Session = request.getSession()
|
||||||
|
usr_sesh = IUserSession(sesh)
|
||||||
|
if (
|
||||||
|
usr_sesh.user_id == 0
|
||||||
|
or usr_sesh.permissions < 1 << PermissionOffset.USERMOD.value
|
||||||
|
):
|
||||||
|
return redirectTo(b"/gate", request)
|
||||||
|
|
||||||
|
if uri.startswith("/sys/lookup.user?"):
|
||||||
|
uri_parse = parse.parse_qs(
|
||||||
|
uri.replace("/sys/lookup.user?", "")
|
||||||
|
) # lop off the first bit
|
||||||
|
uid_search = uri_parse.get("usrId")
|
||||||
|
email_search = uri_parse.get("usrEmail")
|
||||||
|
uname_search = uri_parse.get("usrName")
|
||||||
|
|
||||||
|
if uid_search is not None:
|
||||||
|
u = self.data.user.get_user(uid_search[0])
|
||||||
|
if u is not None:
|
||||||
|
usrlist.append(u._asdict())
|
||||||
|
|
||||||
|
elif email_search is not None:
|
||||||
|
u = self.data.user.find_user_by_email(email_search[0])
|
||||||
|
if u is not None:
|
||||||
|
usrlist.append(u._asdict())
|
||||||
|
|
||||||
|
elif uname_search is not None:
|
||||||
|
ul = self.data.user.find_user_by_username(uname_search[0])
|
||||||
|
for u in ul:
|
||||||
|
usrlist.append(u._asdict())
|
||||||
|
|
||||||
|
elif uri.startswith("/sys/lookup.arcade?"):
|
||||||
|
uri_parse = parse.parse_qs(
|
||||||
|
uri.replace("/sys/lookup.arcade?", "")
|
||||||
|
) # lop off the first bit
|
||||||
|
ac_id_search = uri_parse.get("arcadeId")
|
||||||
|
ac_name_search = uri_parse.get("arcadeName")
|
||||||
|
ac_user_search = uri_parse.get("arcadeUser")
|
||||||
|
|
||||||
|
if ac_id_search is not None:
|
||||||
|
u = self.data.arcade.get_arcade(ac_id_search[0])
|
||||||
|
if u is not None:
|
||||||
|
aclist.append(u._asdict())
|
||||||
|
|
||||||
|
elif ac_name_search is not None:
|
||||||
|
ul = self.data.arcade.find_arcade_by_name(ac_name_search[0])
|
||||||
|
for u in ul:
|
||||||
|
aclist.append(u._asdict())
|
||||||
|
|
||||||
|
elif ac_user_search is not None:
|
||||||
|
ul = self.data.arcade.get_arcades_managed_by_user(ac_user_search[0])
|
||||||
|
for u in ul:
|
||||||
|
aclist.append(u._asdict())
|
||||||
|
|
||||||
|
elif uri.startswith("/sys/lookup.cab?"):
|
||||||
|
uri_parse = parse.parse_qs(
|
||||||
|
uri.replace("/sys/lookup.cab?", "")
|
||||||
|
) # lop off the first bit
|
||||||
|
cab_id_search = uri_parse.get("cabId")
|
||||||
|
cab_serial_search = uri_parse.get("cabSerial")
|
||||||
|
cab_acid_search = uri_parse.get("cabAcId")
|
||||||
|
|
||||||
|
if cab_id_search is not None:
|
||||||
|
u = self.data.arcade.get_machine(id=cab_id_search[0])
|
||||||
|
if u is not None:
|
||||||
|
cablist.append(u._asdict())
|
||||||
|
|
||||||
|
elif cab_serial_search is not None:
|
||||||
|
u = self.data.arcade.get_machine(serial=cab_serial_search[0])
|
||||||
|
if u is not None:
|
||||||
|
cablist.append(u._asdict())
|
||||||
|
|
||||||
|
elif cab_acid_search is not None:
|
||||||
|
ul = self.data.arcade.get_arcade_machines(cab_acid_search[0])
|
||||||
|
for u in ul:
|
||||||
|
cablist.append(u._asdict())
|
||||||
|
|
||||||
|
return template.render(
|
||||||
|
title=f"{self.core_config.server.name} | System",
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
usrlist=usrlist,
|
||||||
|
aclist=aclist,
|
||||||
|
cablist=cablist,
|
||||||
|
active_page="sys",
|
||||||
).encode("utf-16")
|
).encode("utf-16")
|
||||||
|
|
||||||
|
|
||||||
@@ -257,3 +419,56 @@ class FE_Game(FE_Base):
|
|||||||
|
|
||||||
def render_GET(self, request: Request) -> bytes:
|
def render_GET(self, request: Request) -> bytes:
|
||||||
return redirectTo(b"/user", request)
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
|
|
||||||
|
class FE_Arcade(FE_Base):
|
||||||
|
def render_GET(self, request: Request):
|
||||||
|
uri = request.uri.decode()
|
||||||
|
template = self.environment.get_template("core/frontend/arcade/index.jinja")
|
||||||
|
managed = []
|
||||||
|
|
||||||
|
sesh: Session = request.getSession()
|
||||||
|
usr_sesh = IUserSession(sesh)
|
||||||
|
if usr_sesh.user_id == 0:
|
||||||
|
return redirectTo(b"/gate", request)
|
||||||
|
|
||||||
|
m = re.match("\/arcade\/(\d*)", uri)
|
||||||
|
|
||||||
|
if m is not None:
|
||||||
|
arcadeid = m.group(1)
|
||||||
|
perms = self.data.arcade.get_manager_permissions(usr_sesh.user_id, arcadeid)
|
||||||
|
arcade = self.data.arcade.get_arcade(arcadeid)
|
||||||
|
|
||||||
|
if perms is None:
|
||||||
|
perms = 0
|
||||||
|
|
||||||
|
else:
|
||||||
|
return redirectTo(b"/user", request)
|
||||||
|
|
||||||
|
return template.render(
|
||||||
|
title=f"{self.core_config.server.name} | Arcade",
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
error=0,
|
||||||
|
perms=perms,
|
||||||
|
arcade=arcade._asdict(),
|
||||||
|
active_page="arcade",
|
||||||
|
).encode("utf-16")
|
||||||
|
|
||||||
|
|
||||||
|
class FE_Machine(FE_Base):
|
||||||
|
def render_GET(self, request: Request):
|
||||||
|
uri = request.uri.decode()
|
||||||
|
template = self.environment.get_template("core/frontend/machine/index.jinja")
|
||||||
|
|
||||||
|
sesh: Session = request.getSession()
|
||||||
|
usr_sesh = IUserSession(sesh)
|
||||||
|
if usr_sesh.user_id == 0:
|
||||||
|
return redirectTo(b"/gate", request)
|
||||||
|
|
||||||
|
return template.render(
|
||||||
|
title=f"{self.core_config.server.name} | Machine",
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
arcade={},
|
||||||
|
error=0,
|
||||||
|
active_page="machine",
|
||||||
|
).encode("utf-16")
|
||||||
|
|||||||
4
core/frontend/arcade/index.jinja
Normal file
4
core/frontend/arcade/index.jinja
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends "core/frontend/index.jinja" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ arcade.name }}</h1>
|
||||||
|
{% endblock content %}
|
||||||
@@ -1,24 +1,44 @@
|
|||||||
{% extends "core/frontend/index.jinja" %}
|
{% extends "core/frontend/index.jinja" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Create User</h1>
|
<div class="row justify-content-md-center form-signin">
|
||||||
<form id="create" style="max-width: 240px; min-width: 10%;" action="/gate/gate.create" method="post">
|
<div class="col col-lg-4">
|
||||||
<div class="form-group row">
|
<form id="create" action="/gate/gate.create" method="post">
|
||||||
<label for="access_code">Card Access Code</label><br>
|
<h1 class="h3 mb-3 fw-normal">Sign-up</h1>
|
||||||
<input class="form-control" name="access_code" id="access_code" type="text" placeholder="00000000000000000000" value={{ code }} maxlength="20" readonly>
|
|
||||||
|
<div class="form-floating mb-3">
|
||||||
|
<input class="form-control" name="access-code" id="access-code" type="text"
|
||||||
|
placeholder="00000000000000000000" value={{ code }} maxlength="20" required>
|
||||||
|
<label for="access-code">Access Code*</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-floating mb-3">
|
||||||
<label for="username">Username</label><br>
|
<input type="text" class="form-control" name="username" id="username" placeholder="username">
|
||||||
<input id="username" class="form-control" name="username" type="text" placeholder="username">
|
<label for="username">Username*</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-floating mb-3">
|
||||||
<label for="email">Email</label><br>
|
<input type="email" class="form-control" name="email" id="email" placeholder="example@example.com">
|
||||||
<input id="email" class="form-control" name="email" type="email" placeholder="example@example.com">
|
<label for="email">E-Mail*</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-floating mb-3">
|
||||||
<label for="passwd">Password</label><br>
|
<input type="password" class="form-control" name="passwd" id="passwd" placeholder="Password">
|
||||||
<input id="passwd" class="form-control" name="passwd" type="password" placeholder="password">
|
<label for="passwd">Password*</label>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
|
||||||
<input id="submit" class="btn btn-primary" style="display: block; margin: 0 auto;" type="submit" value="Create">
|
<!--
|
||||||
</form>
|
<div class="form-check text-start my-3">
|
||||||
|
<input class="form-check-input" type="checkbox" value="remember-me" id="remember-me">
|
||||||
|
<label class="form-check-label" for="remember-me">
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
If you have not registered a card with this server, you cannot create a WebUI account!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary w-100 py-2" type="submit">Sign-up</button>
|
||||||
|
<!-- <p class="mt-5 mb-3 text-body-secondary">© 2023 ARTEMiS</p>-->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -1,32 +1,52 @@
|
|||||||
{% extends "core/frontend/index.jinja" %}
|
{% extends "core/frontend/index.jinja" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Gate</h1>
|
|
||||||
{% include "core/frontend/widgets/err_banner.jinja" %}
|
|
||||||
<style>
|
<style>
|
||||||
/* Chrome, Safari, Edge, Opera */
|
.form-signin input[type="text"] {
|
||||||
input::-webkit-outer-spin-button,
|
margin-bottom: -1px;
|
||||||
input::-webkit-inner-spin-button {
|
border-bottom-right-radius: 0;
|
||||||
-webkit-appearance: none;
|
border-bottom-left-radius: 0;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Firefox */
|
.form-signin input[type="password"] {
|
||||||
input[type=number] {
|
margin-bottom: 10px;
|
||||||
-moz-appearance: textfield;
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<form id="login" style="max-width: 240px; min-width: 10%;" action="/gate/gate.login" method="post">
|
<div class="row justify-content-md-center form-signin">
|
||||||
<div class="form-group row">
|
<div class="col col-lg-4">
|
||||||
<label for="access_code">Card Access Code</label><br>
|
<form id="login" action="/gate/gate.login" method="post">
|
||||||
<input form="login" class="form-control" name="access_code" id="access_code" type="number" placeholder="00000000000000000000" maxlength="20" required>
|
<h1 class="h3 mb-3 fw-normal">Login</h1>
|
||||||
|
|
||||||
|
{% include "core/frontend/widgets/err_banner.jinja" %}
|
||||||
|
|
||||||
|
<div class="form-floating">
|
||||||
|
<input form="login" class="form-control" name="access-code" id="access-code" type="text"
|
||||||
|
placeholder="00000000000000000000" maxlength="20" required>
|
||||||
|
<label for="access-code">Access Code</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-floating">
|
||||||
<label for="passwd">Password</label><br>
|
<input form="login" type="password" class="form-control" name="passwd" id="passwd" placeholder="Password">
|
||||||
<input id="passwd" class="form-control" name="passwd" type="password" placeholder="password">
|
<label for="passwd">Password</label>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
|
||||||
<input id="submit" class="btn btn-primary" style="display: block; margin: 0 auto;" form="login" type="submit" value="Login">
|
<!--
|
||||||
</form>
|
<div class="form-check text-start my-3">
|
||||||
<h6>*To register for the webui, type in the access code of your card, as shown in a game, and leave the password field blank.</h6>
|
<input class="form-check-input" type="checkbox" value="remember-me" id="remember-me">
|
||||||
<h6>*If you have not registered a card with this server, you cannot create a webui account.</h6>
|
<label class="form-check-label" for="remember-me">
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
To register for the WebUI, type in the <strong>access code</strong> of your card, as shown in a game, and leave the
|
||||||
|
password field blank.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary w-100 py-2" type="submit">Sign in</button>
|
||||||
|
<!-- <p class="mt-5 mb-3 text-body-secondary">© 2023 ARTEMiS</p>-->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -1,88 +1,25 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en" data-bs-theme="dark">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
|
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
|
||||||
<style>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||||
html {
|
</head>
|
||||||
background-color: #181a1b !important;
|
|
||||||
margin: 10px;
|
<body>
|
||||||
}
|
|
||||||
html {
|
|
||||||
color-scheme: dark !important;
|
|
||||||
}
|
|
||||||
html, body, input, textarea, select, button, dialog {
|
|
||||||
background-color: #181a1b;
|
|
||||||
}
|
|
||||||
html, body, input, textarea, select, button {
|
|
||||||
border-color: #736b5e;
|
|
||||||
color: #e8e6e3;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #3391ff;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
border-color: #545b5e;
|
|
||||||
}
|
|
||||||
::placeholder {
|
|
||||||
color: #b2aba1;
|
|
||||||
}
|
|
||||||
input:-webkit-autofill,
|
|
||||||
textarea:-webkit-autofill,
|
|
||||||
select:-webkit-autofill {
|
|
||||||
background-color: #404400 !important;
|
|
||||||
color: #e8e6e3 !important;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
background-color: #202324;
|
|
||||||
color: #aba499;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #454a4d;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background-color: #575e62;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb:active {
|
|
||||||
background-color: #484e51;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-corner {
|
|
||||||
background-color: #181a1b;
|
|
||||||
}
|
|
||||||
* {
|
|
||||||
scrollbar-color: #454a4d #202324;
|
|
||||||
}
|
|
||||||
::selection {
|
|
||||||
background-color: #004daa !important;
|
|
||||||
color: #e8e6e3 !important;
|
|
||||||
}
|
|
||||||
::-moz-selection {
|
|
||||||
background-color: #004daa !important;
|
|
||||||
color: #e8e6e3 !important;
|
|
||||||
}
|
|
||||||
input[type="text"], input[type="text"]:focus, input[type="password"], input[type="password"]:focus, input[type="email"], input[type="email"]:focus {
|
|
||||||
background-color: #202324 !important;
|
|
||||||
color: #e8e6e3;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
outline: 1px solid grey;
|
|
||||||
padding: 20px;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
.err-banner {
|
|
||||||
background-color: #AA0000;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
width: 15%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% include "core/frontend/widgets/topbar.jinja" %}
|
{% include "core/frontend/widgets/topbar.jinja" %}
|
||||||
|
<div class="container">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{ server_name }}</h1>
|
<h1>{{ server_name }}</h1>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
</body>
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
5
core/frontend/machine/index.jinja
Normal file
5
core/frontend/machine/index.jinja
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends "core/frontend/index.jinja" %}
|
||||||
|
{% block content %}
|
||||||
|
{% include "core/frontend/widgets/err_banner.jinja" %}
|
||||||
|
<h1>Machine Management</h1>
|
||||||
|
{% endblock content %}
|
||||||
98
core/frontend/sys/index.jinja
Normal file
98
core/frontend/sys/index.jinja
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{% extends "core/frontend/index.jinja" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>System Management</h1>
|
||||||
|
|
||||||
|
<div class="row" id="rowForm">
|
||||||
|
{% if sesh.permissions >= 2 %}
|
||||||
|
<div class="col-sm-6" style="max-width: 25%;">
|
||||||
|
<form id="usrLookup" name="usrLookup" action="/sys/lookup.user" class="form-inline">
|
||||||
|
<h3>User Search</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="usrEmail">Email address</label>
|
||||||
|
<input type="email" class="form-control" id="usrEmail" name="usrEmail" aria-describedby="emailHelp">
|
||||||
|
</div>
|
||||||
|
OR
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="usrName">Username</label>
|
||||||
|
<input type="text" class="form-control" id="usrName" name="usrName">
|
||||||
|
</div>
|
||||||
|
OR
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="usrId">User ID</label>
|
||||||
|
<input type="number" class="form-control" id="usrId" name="usrId">
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if sesh.permissions >= 4 %}
|
||||||
|
<div class="col-sm-6" style="max-width: 25%;">
|
||||||
|
<form id="arcadeLookup" name="arcadeLookup" action="/sys/lookup.arcade" class="form-inline" >
|
||||||
|
<h3>Arcade Search</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="arcadeName">Arcade Name</label>
|
||||||
|
<input type="text" class="form-control" id="arcadeName" name="arcadeName">
|
||||||
|
</div>
|
||||||
|
OR
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="arcadeId">Arcade ID</label>
|
||||||
|
<input type="number" class="form-control" id="arcadeId" name="arcadeId">
|
||||||
|
</div>
|
||||||
|
OR
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="arcadeUser">Owner User ID</label>
|
||||||
|
<input type="number" class="form-control" id="arcadeUser" name="arcadeUser">
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6" style="max-width: 25%;">
|
||||||
|
<form id="cabLookup" name="cabLookup" action="/sys/lookup.cab" class="form-inline" >
|
||||||
|
<h3>Machine Search</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cabSerial">Machine Serial</label>
|
||||||
|
<input type="text" class="form-control" id="cabSerial" name="cabSerial">
|
||||||
|
</div>
|
||||||
|
OR
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cabId">Machine ID</label>
|
||||||
|
<input type="number" class="form-control" id="cabId" name="cabId">
|
||||||
|
</div>
|
||||||
|
OR
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cabAcId">Arcade ID</label>
|
||||||
|
<input type="number" class="form-control" id="cabAcId" name="cabAcId">
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row" id="rowResult" style="margin: 10px;">
|
||||||
|
{% if sesh.permissions >= 2 %}
|
||||||
|
<div id="userSearchResult" class="col-sm-6" style="max-width: 25%;">
|
||||||
|
{% for usr in usrlist %}
|
||||||
|
<pre><a href=/user/{{ usr.id }}>{{ usr.id }} | {{ usr.username }}</a></pre>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if sesh.permissions >= 4 %}
|
||||||
|
<div id="arcadeSearchResult" class="col-sm-6" style="max-width: 25%;">
|
||||||
|
{% for ac in aclist %}
|
||||||
|
<pre><a href=/arcade/{{ ac.id }}>{{ ac.id }} | {{ ac.name }}</a></pre>
|
||||||
|
{% endfor %}
|
||||||
|
</div
|
||||||
|
><div id="cabSearchResult" class="col-sm-6" style="max-width: 25%;">
|
||||||
|
{% for cab in cablist %}
|
||||||
|
<a href=/cab/{{ cab.id }}><pre>{{ cab.id }} | {{ cab.game if cab.game is defined else "ANY " }} | {{ cab.serial }}</pre></a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row" id="rowAdd">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
@@ -1,28 +1,123 @@
|
|||||||
{% extends "core/frontend/index.jinja" %}
|
{% extends "core/frontend/index.jinja" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Management for {{ username }}</h1>
|
<h1>{{ sesh["username"] }}'s Account</h1>
|
||||||
<h2>Cards <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#card_add">Add</button></h2>
|
|
||||||
<ul>
|
<div class="card mb-3">
|
||||||
{% for c in cards %}
|
<div class="card-body">
|
||||||
<li>{{ c.access_code }}: {{ c.status }} <button class="btn-danger btn">Delete</button></li>
|
<h3 class="card-title">Cards</h3>
|
||||||
{% endfor %}
|
<!--<h4 class="card-subtitle mb-2 text-body-secondary">Card subtitle</h4>-->
|
||||||
</ul>
|
<p class="card-text">All aime cards are listed here for the given user.</p>
|
||||||
<div class="modal fade" id="card_add" tabindex="-1" aria-labelledby="card_add_label" aria-hidden="true">
|
<a href="#" data-bs-toggle="modal" data-bs-target="#card-add" class="btn btn-primary mb-3">Add Card</a>
|
||||||
|
{% if cards is defined and cards|length > 0 %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">Access Code</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for c in cards %}
|
||||||
|
<tr class="align-middle">
|
||||||
|
<th scope="row">{{ c.index }}</th>
|
||||||
|
<td>{{ c.access_code }}</td>
|
||||||
|
<td>
|
||||||
|
{% if c.status == 'Active'%}
|
||||||
|
<span class="badge rounded-pill text-bg-success">Active</span>
|
||||||
|
{% elif c.status == 'Locked' %}
|
||||||
|
<span class="badge rounded-pill text-bg-warning">Locked</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if c.status == 'Active'%}
|
||||||
|
<button type="button" class="btn btn-warning btn-sm">Lock</i></button>
|
||||||
|
{% elif c.status == 'Locked' %}
|
||||||
|
<button type="button" class="btn btn-success btn-sm">Unlock</i></button>
|
||||||
|
{% endif %}
|
||||||
|
<button type="button" class="btn btn-danger btn-sm">Delete</i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
You have no cards registered to your account. Please add one.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<!--<a href="#" data-bs-toggle="modal" data-bs-target="#card-add" class="card-link">Add Card</a>-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">Arcades</h3>
|
||||||
|
<!--<h4 class="card-subtitle mb-2 text-body-secondary">Card subtitle</h4>-->
|
||||||
|
<p class="card-text">All arcades connected to the given account are listed here.</p>
|
||||||
|
{% if arcades is defined and arcades|length > 0 %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for a in arcades %}
|
||||||
|
<tr class="align-middle clickable-row" data-href=/arcade/{{ a.id }}>
|
||||||
|
<th scope="row">{{ a.index }}</th>
|
||||||
|
<td>{{ a.name }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
You have no arcades connected to your account.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="card-add" tabindex="-1" aria-labelledby="card-add-label" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="card_add_label">Add Card</h1>
|
<h1 class="modal-title fs-5" id="card-add-label">Add Card</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
HOW TO:<br>
|
<h3 class="fs-5">How to:</h3>
|
||||||
Scan your card on any networked game and press the "View Access Code" button (varies by game) and enter the 20 digit code below.<br>
|
Scan your card on any networked game and press the "View Access Code" button (varies by game) and enter
|
||||||
!!FOR AMUSEIC CARDS: DO NOT ENTER THE CODE SHOWN ON THE BACK OF THE CARD ITSELF OR IT WILL NOT WORK!!
|
the 20
|
||||||
<p /><label for="card_add_frm_access_code">Access Code: </label><input id="card_add_frm_access_code" maxlength="20" type="text" required>
|
digit code below.
|
||||||
|
<hr>
|
||||||
|
FOR AMUSE IC CARDS: DO NOT ENTER THE CODE SHOWN ON THE BACK OF THE CARD ITSELF OR IT WILL NOT WORK!
|
||||||
|
<form>
|
||||||
|
<div class="form-floating mt-3">
|
||||||
|
<label for="access-code" class="col-form-label">Access Code</label>
|
||||||
|
<input maxlength="20" type="text" required pattern="[0-9]{20}" class="form-control"
|
||||||
|
id="access-code">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-primary">Add</button>
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
{% if error > 0 %}
|
{% if error > 0 %}
|
||||||
<div class="err-banner">
|
<div class="alert alert-danger" role="alert">
|
||||||
<h3>Error</h3>
|
|
||||||
{% if error == 1 %}
|
{% if error == 1 %}
|
||||||
Card not registered, or wrong password
|
Card not registered, or wrong password
|
||||||
{% elif error == 2 %}
|
{% elif error == 2 %}
|
||||||
Missing or malformed access code
|
Missing or malformed access code
|
||||||
{% elif error == 3 %}
|
{% elif error == 3 %}
|
||||||
Failed to create user
|
Failed to create user
|
||||||
|
{% elif error == 4 %}
|
||||||
|
Arcade not found
|
||||||
|
{% elif error == 5 %}
|
||||||
|
Machine not found
|
||||||
{% else %}
|
{% else %}
|
||||||
An unknown error occoured
|
An unknown error occoured
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,18 +1,56 @@
|
|||||||
<div style="background: #333; color: #f9f9f9; width: 10%; height: 50px; line-height: 50px; text-align: center; float: left;">
|
<nav class="p-3 mb-3 navbar navbar-expand-lg bg-body-tertiary" aria-label="Thirteenth navbar example">
|
||||||
Navigation
|
<div class="container">
|
||||||
</div>
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample11"
|
||||||
<div style="background: #333; color: #f9f9f9; width: 80%; height: 50px; line-height: 50px; padding-left: 10px; float: left;">
|
aria-controls="navbarsExample11" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<a href=/><button class="btn btn-primary">Home</button></a>
|
<span class="navbar-toggler-icon"></span>
|
||||||
{% for game in game_list %}
|
</button>
|
||||||
<a href=/game/{{ game.url }}><button class="btn btn-success">{{ game.name }}</button></a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="background: #333; color: #f9f9f9; width: 10%; height: 50px; line-height: 50px; text-align: center; float: left;">
|
|
||||||
{% if sesh is defined and sesh["userId"] > 0 %}
|
|
||||||
<a href="/user"><button class="btn btn-primary">Account</button></a>
|
|
||||||
{% else %}
|
|
||||||
<a href="/gate"><button class="btn btn-primary">Gate</button></a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
<div class="collapse navbar-collapse d-lg-flex" id="navbarsExample11">
|
||||||
|
<a class="navbar-brand col-lg-3 me-0" href= />ARTEMiS</a>
|
||||||
|
<ul class="navbar-nav col-lg-6 justify-content-lg-center nav-pills">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == '/' %}active{% endif %}" aria-current="page" href= />Home</a>
|
||||||
|
</li>
|
||||||
|
{% for game in game_list %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if active_page == game.url %}active{% endif %}" href=/game/{{ game.url }}>{{ game.name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
<!--
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">Dropdown</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="#">Action</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#">Another action</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
-->
|
||||||
|
</ul>
|
||||||
|
<div class="d-lg-flex col-lg-3 justify-content-lg-end">
|
||||||
|
{% if sesh is defined and sesh["user_id"] > 0 %}
|
||||||
|
<div class="btn-group dropdown">
|
||||||
|
<a href="#" class="d-block link-body-emphasis text-decoration-none dropdown-toggle"
|
||||||
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
{{ sesh["username"] }}
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu text-small">
|
||||||
|
{% if sesh["permissions"] >= 2 %}
|
||||||
|
<li><a class="dropdown-item {% if active_page == 'sys' %}active{% endif %}" href="/sys">System</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><a class="dropdown-item" href="#">Settings</a></li>
|
||||||
|
<li><a class="dropdown-item {% if active_page == 'user' %}active{% endif %}" href="/user">Account</a></li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<li><a class="dropdown-item" href="#">Sign out</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<a href="/gate"><button class="btn {% if active_page == 'gate' %}btn-primary{% else %}btn-outline-primary{% endif %}">Login</button></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
158
core/mucha.py
158
core/mucha.py
@@ -4,6 +4,7 @@ from logging.handlers import TimedRotatingFileHandler
|
|||||||
from twisted.web import resource
|
from twisted.web import resource
|
||||||
from twisted.web.http import Request
|
from twisted.web.http import Request
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from Crypto.Cipher import Blowfish
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from core import CoreConfig
|
from core import CoreConfig
|
||||||
@@ -56,17 +57,24 @@ class MuchaServlet:
|
|||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"Error processing mucha request {request.content.getvalue()}"
|
f"Error processing mucha request {request.content.getvalue()}"
|
||||||
)
|
)
|
||||||
return b""
|
return b"RESULTS=000"
|
||||||
|
|
||||||
req = MuchaAuthRequest(req_dict)
|
req = MuchaAuthRequest(req_dict)
|
||||||
self.logger.debug(f"Mucha request {vars(req)}")
|
|
||||||
self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}")
|
self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}")
|
||||||
|
self.logger.debug(f"Mucha request {vars(req)}")
|
||||||
|
|
||||||
if req.gameCd not in self.mucha_registry:
|
if req.gameCd not in self.mucha_registry:
|
||||||
self.logger.warn(f"Unknown gameCd {req.gameCd}")
|
self.logger.warning(f"Unknown gameCd {req.gameCd}")
|
||||||
return b""
|
return b"RESULTS=000"
|
||||||
|
|
||||||
# TODO: Decrypt S/N
|
# TODO: Decrypt S/N
|
||||||
|
b_key = b""
|
||||||
|
for x in range(8):
|
||||||
|
b_key += req.sendDate[(x - 1) & 7].encode()
|
||||||
|
|
||||||
|
cipher = Blowfish.new(b_key, Blowfish.MODE_ECB)
|
||||||
|
sn_decrypt = cipher.decrypt(bytes.fromhex(req.serialNum))
|
||||||
|
self.logger.debug(f"Decrypt SN to {sn_decrypt.hex()}")
|
||||||
|
|
||||||
resp = MuchaAuthResponse(
|
resp = MuchaAuthResponse(
|
||||||
f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}"
|
f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}"
|
||||||
@@ -84,22 +92,37 @@ class MuchaServlet:
|
|||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"Error processing mucha request {request.content.getvalue()}"
|
f"Error processing mucha request {request.content.getvalue()}"
|
||||||
)
|
)
|
||||||
return b""
|
return b"RESULTS=000"
|
||||||
|
|
||||||
req = MuchaUpdateRequest(req_dict)
|
req = MuchaUpdateRequest(req_dict)
|
||||||
self.logger.debug(f"Mucha request {vars(req)}")
|
|
||||||
self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}")
|
self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}")
|
||||||
|
self.logger.debug(f"Mucha request {vars(req)}")
|
||||||
|
|
||||||
if req.gameCd not in self.mucha_registry:
|
if req.gameCd not in self.mucha_registry:
|
||||||
self.logger.warn(f"Unknown gameCd {req.gameCd}")
|
self.logger.warning(f"Unknown gameCd {req.gameCd}")
|
||||||
return b""
|
return b"RESULTS=000"
|
||||||
|
|
||||||
resp = MuchaUpdateResponseStub(req.gameVer)
|
resp = MuchaUpdateResponse(req.gameVer, f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}")
|
||||||
|
|
||||||
self.logger.debug(f"Mucha response {vars(resp)}")
|
self.logger.debug(f"Mucha response {vars(resp)}")
|
||||||
|
|
||||||
return self.mucha_postprocess(vars(resp))
|
return self.mucha_postprocess(vars(resp))
|
||||||
|
|
||||||
|
def handle_dlstate(self, request: Request, _: Dict) -> bytes:
|
||||||
|
req_dict = self.mucha_preprocess(request.content.getvalue())
|
||||||
|
client_ip = Utils.get_ip_addr(request)
|
||||||
|
|
||||||
|
if req_dict is None:
|
||||||
|
self.logger.error(
|
||||||
|
f"Error processing mucha request {request.content.getvalue()}"
|
||||||
|
)
|
||||||
|
return b""
|
||||||
|
|
||||||
|
req = MuchaDownloadStateRequest(req_dict)
|
||||||
|
self.logger.info(f"DownloadState request from {client_ip} for {req.gameCd} -> {req.updateVer}")
|
||||||
|
self.logger.debug(f"request {vars(req)}")
|
||||||
|
return b"RESULTS=001"
|
||||||
|
|
||||||
def mucha_preprocess(self, data: bytes) -> Optional[Dict]:
|
def mucha_preprocess(self, data: bytes) -> Optional[Dict]:
|
||||||
try:
|
try:
|
||||||
ret: Dict[str, Any] = {}
|
ret: Dict[str, Any] = {}
|
||||||
@@ -111,19 +134,17 @@ class MuchaServlet:
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
self.logger.error(f"Error processing mucha request {data}")
|
self.logger.error(f"Error processing mucha request {data}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def mucha_postprocess(self, data: dict) -> Optional[bytes]:
|
def mucha_postprocess(self, data: dict) -> Optional[bytes]:
|
||||||
try:
|
try:
|
||||||
urlencode = ""
|
urlencode = "&".join(f"{k}={v}" for k, v in data.items())
|
||||||
for k, v in data.items():
|
|
||||||
urlencode += f"{k}={v}&"
|
|
||||||
|
|
||||||
return urlencode.encode()
|
return urlencode.encode()
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
self.logger.error("Error processing mucha response")
|
self.logger.error("Error processing mucha response")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -203,21 +224,120 @@ class MuchaUpdateRequest:
|
|||||||
class MuchaUpdateResponse:
|
class MuchaUpdateResponse:
|
||||||
def __init__(self, game_ver: str, mucha_url: str) -> None:
|
def __init__(self, game_ver: str, mucha_url: str) -> None:
|
||||||
self.RESULTS = "001"
|
self.RESULTS = "001"
|
||||||
|
self.EXE_VER = game_ver
|
||||||
|
|
||||||
self.UPDATE_VER_1 = game_ver
|
self.UPDATE_VER_1 = game_ver
|
||||||
self.UPDATE_URL_1 = f"https://{mucha_url}/updUrl1/"
|
self.UPDATE_URL_1 = f"http://{mucha_url}/updUrl1/"
|
||||||
self.UPDATE_SIZE_1 = "0"
|
self.UPDATE_SIZE_1 = "20"
|
||||||
self.UPDATE_CRC_1 = "0000000000000000"
|
|
||||||
self.CHECK_URL_1 = f"https://{mucha_url}/checkUrl/"
|
self.CHECK_CRC_1 = "0000000000000000"
|
||||||
self.EXE_VER_1 = game_ver
|
self.CHECK_URL_1 = f"http://{mucha_url}/checkUrl/"
|
||||||
|
self.CHECK_SIZE_1 = "20"
|
||||||
|
|
||||||
self.INFO_SIZE_1 = "0"
|
self.INFO_SIZE_1 = "0"
|
||||||
self.COM_SIZE_1 = "0"
|
self.COM_SIZE_1 = "0"
|
||||||
self.COM_TIME_1 = "0"
|
self.COM_TIME_1 = "0"
|
||||||
self.LAN_INFO_SIZE_1 = "0"
|
self.LAN_INFO_SIZE_1 = "0"
|
||||||
|
|
||||||
self.USER_ID = ""
|
self.USER_ID = ""
|
||||||
self.PASSWORD = ""
|
self.PASSWORD = ""
|
||||||
|
|
||||||
|
"""
|
||||||
|
RESULTS
|
||||||
|
EXE_VER
|
||||||
|
|
||||||
|
UPDATE_VER_%d
|
||||||
|
UPDATE_URL_%d
|
||||||
|
UPDATE_SIZE_%d
|
||||||
|
|
||||||
|
CHECK_CRC_%d
|
||||||
|
CHECK_URL_%d
|
||||||
|
CHECK_SIZE_%d
|
||||||
|
|
||||||
|
INFO_SIZE_1
|
||||||
|
COM_SIZE_1
|
||||||
|
COM_TIME_1
|
||||||
|
LAN_INFO_SIZE_1
|
||||||
|
|
||||||
|
USER_ID
|
||||||
|
PASSWORD
|
||||||
|
"""
|
||||||
class MuchaUpdateResponseStub:
|
class MuchaUpdateResponseStub:
|
||||||
def __init__(self, game_ver: str) -> None:
|
def __init__(self, game_ver: str) -> None:
|
||||||
self.RESULTS = "001"
|
self.RESULTS = "001"
|
||||||
self.UPDATE_VER_1 = game_ver
|
self.UPDATE_VER_1 = game_ver
|
||||||
|
|
||||||
|
class MuchaDownloadStateRequest:
|
||||||
|
def __init__(self, request: Dict) -> None:
|
||||||
|
self.gameCd = request.get("gameCd", "")
|
||||||
|
self.updateVer = request.get("updateVer", "")
|
||||||
|
self.serialNum = request.get("serialNum", "")
|
||||||
|
self.fileSize = request.get("fileSize", "")
|
||||||
|
self.compFileSize = request.get("compFileSize", "")
|
||||||
|
self.boardId = request.get("boardId", "")
|
||||||
|
self.placeId = request.get("placeId", "")
|
||||||
|
self.storeRouterIp = request.get("storeRouterIp", "")
|
||||||
|
|
||||||
|
class MuchaDownloadErrorRequest:
|
||||||
|
def __init__(self, request: Dict) -> None:
|
||||||
|
self.gameCd = request.get("gameCd", "")
|
||||||
|
self.updateVer = request.get("updateVer", "")
|
||||||
|
self.serialNum = request.get("serialNum", "")
|
||||||
|
self.downloadUrl = request.get("downloadUrl", "")
|
||||||
|
self.errCd = request.get("errCd", "")
|
||||||
|
self.errMessage = request.get("errMessage", "")
|
||||||
|
self.boardId = request.get("boardId", "")
|
||||||
|
self.placeId = request.get("placeId", "")
|
||||||
|
self.storeRouterIp = request.get("storeRouterIp", "")
|
||||||
|
|
||||||
|
class MuchaRegiAuthRequest:
|
||||||
|
def __init__(self, request: Dict) -> None:
|
||||||
|
self.gameCd = request.get("gameCd", "")
|
||||||
|
self.serialNum = request.get("serialNum", "") # Encrypted
|
||||||
|
self.countryCd = request.get("countryCd", "")
|
||||||
|
self.registrationCd = request.get("registrationCd", "")
|
||||||
|
self.sendDate = request.get("sendDate", "")
|
||||||
|
self.useToken = request.get("useToken", "")
|
||||||
|
self.allToken = request.get("allToken", "")
|
||||||
|
self.placeId = request.get("placeId", "")
|
||||||
|
self.storeRouterIp = request.get("storeRouterIp", "")
|
||||||
|
|
||||||
|
class MuchaRegiAuthResponse:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.RESULTS = "001" # 001 = success, 099, 098, 097 = fail, others = fail
|
||||||
|
self.ALL_TOKEN = "0" # Encrypted
|
||||||
|
self.ADD_TOKEN = "0" # Encrypted
|
||||||
|
|
||||||
|
class MuchaTokenStateRequest:
|
||||||
|
def __init__(self, request: Dict) -> None:
|
||||||
|
self.gameCd = request.get("gameCd", "")
|
||||||
|
self.serialNum = request.get("serialNum", "")
|
||||||
|
self.countryCd = request.get("countryCd", "")
|
||||||
|
self.useToken = request.get("useToken", "")
|
||||||
|
self.allToken = request.get("allToken", "")
|
||||||
|
self.placeId = request.get("placeId", "")
|
||||||
|
self.storeRouterIp = request.get("storeRouterIp", "")
|
||||||
|
|
||||||
|
class MuchaTokenStateResponse:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.RESULTS = "001"
|
||||||
|
|
||||||
|
class MuchaTokenMarginStateRequest:
|
||||||
|
def __init__(self, request: Dict) -> None:
|
||||||
|
self.gameCd = request.get("gameCd", "")
|
||||||
|
self.serialNum = request.get("serialNum", "")
|
||||||
|
self.countryCd = request.get("countryCd", "")
|
||||||
|
self.placeId = request.get("placeId", "")
|
||||||
|
self.limitLowerToken = request.get("limitLowerToken", 0)
|
||||||
|
self.limitUpperToken = request.get("limitUpperToken", 0)
|
||||||
|
self.settlementMonth = request.get("settlementMonth", 0)
|
||||||
|
|
||||||
|
class MuchaTokenMarginStateResponse:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.RESULTS = "001"
|
||||||
|
self.LIMIT_LOWER_TOKEN = 0
|
||||||
|
self.LIMIT_UPPER_TOKEN = 0
|
||||||
|
self.LAST_SETTLEMENT_MONTH = 0
|
||||||
|
self.LAST_LIMIT_LOWER_TOKEN = 0
|
||||||
|
self.LAST_LIMIT_UPPER_TOKEN = 0
|
||||||
|
self.SETTLEMENT_MONTH = 0
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class TitleServlet:
|
|||||||
self.title_registry[code] = handler_cls
|
self.title_registry[code] = handler_cls
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Game {folder} has no get_allnet_info")
|
self.logger.warning(f"Game {folder} has no get_allnet_info")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"{folder} missing game_code or index in __init__.py")
|
self.logger.error(f"{folder} missing game_code or index in __init__.py")
|
||||||
@@ -74,13 +74,13 @@ class TitleServlet:
|
|||||||
def render_GET(self, request: Request, endpoints: dict) -> bytes:
|
def render_GET(self, request: Request, endpoints: dict) -> bytes:
|
||||||
code = endpoints["game"]
|
code = endpoints["game"]
|
||||||
if code not in self.title_registry:
|
if code not in self.title_registry:
|
||||||
self.logger.warn(f"Unknown game code {code}")
|
self.logger.warning(f"Unknown game code {code}")
|
||||||
request.setResponseCode(404)
|
request.setResponseCode(404)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
index = self.title_registry[code]
|
index = self.title_registry[code]
|
||||||
if not hasattr(index, "render_GET"):
|
if not hasattr(index, "render_GET"):
|
||||||
self.logger.warn(f"{code} does not dispatch GET")
|
self.logger.warning(f"{code} does not dispatch GET")
|
||||||
request.setResponseCode(405)
|
request.setResponseCode(405)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
@@ -89,13 +89,13 @@ class TitleServlet:
|
|||||||
def render_POST(self, request: Request, endpoints: dict) -> bytes:
|
def render_POST(self, request: Request, endpoints: dict) -> bytes:
|
||||||
code = endpoints["game"]
|
code = endpoints["game"]
|
||||||
if code not in self.title_registry:
|
if code not in self.title_registry:
|
||||||
self.logger.warn(f"Unknown game code {code}")
|
self.logger.warning(f"Unknown game code {code}")
|
||||||
request.setResponseCode(404)
|
request.setResponseCode(404)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
index = self.title_registry[code]
|
index = self.title_registry[code]
|
||||||
if not hasattr(index, "render_POST"):
|
if not hasattr(index, "render_POST"):
|
||||||
self.logger.warn(f"{code} does not dispatch POST")
|
self.logger.warning(f"{code} does not dispatch POST")
|
||||||
request.setResponseCode(405)
|
request.setResponseCode(405)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
elif args.action == "upgrade" or args.action == "rollback":
|
elif args.action == "upgrade" or args.action == "rollback":
|
||||||
if args.version is None:
|
if args.version is None:
|
||||||
data.logger.warn("No version set, upgrading to latest")
|
data.logger.warning("No version set, upgrading to latest")
|
||||||
|
|
||||||
if args.game is None:
|
if args.game is None:
|
||||||
data.logger.warn("No game set, upgrading core schema")
|
data.logger.warning("No game set, upgrading core schema")
|
||||||
data.migrate_database(
|
data.migrate_database(
|
||||||
"CORE",
|
"CORE",
|
||||||
int(args.version) if args.version is not None else None,
|
int(args.version) if args.version is not None else None,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Games listed below have been tested and confirmed working.
|
|||||||
In order to use the importer locate your game installation folder and execute:
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDBT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
python read.py --game SDBT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
||||||
```
|
```
|
||||||
|
|
||||||
The importer for Chunithm will import: Events, Music, Charge Items and Avatar Accesories.
|
The importer for Chunithm will import: Events, Music, Charge Items and Avatar Accesories.
|
||||||
@@ -105,7 +105,7 @@ After a failed Online Battle the room will be deleted. The host is used for the
|
|||||||
In order to use the importer you need to use the provided `Export.csv` file:
|
In order to use the importer you need to use the provided `Export.csv` file:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDCA --version <version ID> --binfolder titles/cxb/data
|
python read.py --game SDCA --version <version ID> --binfolder titles/cxb/data
|
||||||
```
|
```
|
||||||
|
|
||||||
The importer for crossbeats REV. will import Music.
|
The importer for crossbeats REV. will import Music.
|
||||||
@@ -160,11 +160,11 @@ For versions pre-dx
|
|||||||
In order to use the importer locate your game installation folder and execute:
|
In order to use the importer locate your game installation folder and execute:
|
||||||
DX:
|
DX:
|
||||||
```shell
|
```shell
|
||||||
python read.py --series <Game Code> --version <Version ID> --binfolder /path/to/StreamingAssets --optfolder /path/to/game/option/folder
|
python read.py --game <Game Code> --version <Version ID> --binfolder /path/to/StreamingAssets --optfolder /path/to/game/option/folder
|
||||||
```
|
```
|
||||||
Pre-DX:
|
Pre-DX:
|
||||||
```shell
|
```shell
|
||||||
python read.py --series <Game Code> --version <Version ID> --binfolder /path/to/data --optfolder /path/to/patch/data
|
python read.py --game <Game Code> --version <Version ID> --binfolder /path/to/data --optfolder /path/to/patch/data
|
||||||
```
|
```
|
||||||
The importer for maimai DX will import Events, Music and Tickets.
|
The importer for maimai DX will import Events, Music and Tickets.
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ Pre-Dx uses the same database as DX, so only upgrade using the SDEZ game code!
|
|||||||
In order to use the importer locate your game installation folder and execute:
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SBZV --version <version ID> --binfolder /path/to/game/data/diva --optfolder /path/to/game/data/diva/mdata
|
python read.py --game SBZV --version <version ID> --binfolder /path/to/game/data/diva --optfolder /path/to/game/data/diva/mdata
|
||||||
```
|
```
|
||||||
|
|
||||||
The importer for Project Diva Arcade will all required data in order to use
|
The importer for Project Diva Arcade will all required data in order to use
|
||||||
@@ -243,7 +243,7 @@ python dbutils.py --game SBZV upgrade
|
|||||||
In order to use the importer locate your game installation folder and execute:
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDDT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
python read.py --game SDDT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
||||||
```
|
```
|
||||||
|
|
||||||
The importer for O.N.G.E.K.I. will all all Cards, Music and Events.
|
The importer for O.N.G.E.K.I. will all all Cards, Music and Events.
|
||||||
@@ -299,19 +299,19 @@ In order to use the importer you need to use the provided `.csv` files (which ar
|
|||||||
option folders:
|
option folders:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDED --version <version ID> --binfolder titles/cm/cm_data --optfolder /path/to/cardmaker/option/folder
|
python read.py --game SDED --version <version ID> --binfolder titles/cm/cm_data --optfolder /path/to/cardmaker/option/folder
|
||||||
```
|
```
|
||||||
|
|
||||||
**If you haven't already executed the O.N.G.E.K.I. importer, make sure you import all cards!**
|
**If you haven't already executed the O.N.G.E.K.I. importer, make sure you import all cards!**
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDDT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
python read.py --game SDDT --version <version ID> --binfolder /path/to/game/folder --optfolder /path/to/game/option/folder
|
||||||
```
|
```
|
||||||
|
|
||||||
Also make sure to import all maimai DX and CHUNITHM data as well:
|
Also make sure to import all maimai DX and CHUNITHM data as well:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDED --version <version ID> --binfolder /path/to/cardmaker/CardMaker_Data
|
python read.py --game SDED --version <version ID> --binfolder /path/to/cardmaker/CardMaker_Data
|
||||||
```
|
```
|
||||||
|
|
||||||
The importer for Card Maker will import all required Gachas (Banners) and cards (for maimai DX/CHUNITHM) and the hardcoded
|
The importer for Card Maker will import all required Gachas (Banners) and cards (for maimai DX/CHUNITHM) and the hardcoded
|
||||||
@@ -404,7 +404,7 @@ Gacha IDs up to 1140 will be loaded for CM 1.34 and all gachas will be loaded fo
|
|||||||
In order to use the importer locate your game installation folder and execute:
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDFE --version <version ID> --binfolder /path/to/game/WindowsNoEditor/Mercury/Content
|
python read.py --game SDFE --version <version ID> --binfolder /path/to/game/WindowsNoEditor/Mercury/Content
|
||||||
```
|
```
|
||||||
|
|
||||||
The importer for WACCA will import all Music data.
|
The importer for WACCA will import all Music data.
|
||||||
@@ -478,7 +478,7 @@ Below is a list of VIP rewards. Currently, VIP is not implemented, and thus thes
|
|||||||
In order to use the importer locate your game installation folder and execute:
|
In order to use the importer locate your game installation folder and execute:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python read.py --series SDEW --version <version ID> --binfolder /path/to/game/extractedassets
|
python read.py --game SDEW --version <version ID> --binfolder /path/to/game/extractedassets
|
||||||
```
|
```
|
||||||
|
|
||||||
The importer for SAO will import all items, heroes, support skills and titles data.
|
The importer for SAO will import all items, heroes, support skills and titles data.
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ server:
|
|||||||
is_develop: True
|
is_develop: True
|
||||||
threading: False
|
threading: False
|
||||||
log_dir: "logs"
|
log_dir: "logs"
|
||||||
|
check_arcade_ip: False
|
||||||
|
strict_ip_checking: False
|
||||||
|
|
||||||
title:
|
title:
|
||||||
loglevel: "info"
|
loglevel: "info"
|
||||||
@@ -32,6 +34,7 @@ frontend:
|
|||||||
allnet:
|
allnet:
|
||||||
loglevel: "info"
|
loglevel: "info"
|
||||||
port: 80
|
port: 80
|
||||||
|
ip_check: False
|
||||||
allow_online_updates: False
|
allow_online_updates: False
|
||||||
update_cfg_folder: ""
|
update_cfg_folder: ""
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,9 @@ deliver:
|
|||||||
enable: False
|
enable: False
|
||||||
udbdl_enable: False
|
udbdl_enable: False
|
||||||
content_folder: ""
|
content_folder: ""
|
||||||
|
|
||||||
|
uploads:
|
||||||
|
photos: False
|
||||||
|
photos_dir: ""
|
||||||
|
movies: False
|
||||||
|
movies_dir: ""
|
||||||
|
|||||||
36
index.py
36
index.py
@@ -36,7 +36,7 @@ class HttpDispatcher(resource.Resource):
|
|||||||
|
|
||||||
self.map_post.connect(
|
self.map_post.connect(
|
||||||
"allnet_downloadorder_report",
|
"allnet_downloadorder_report",
|
||||||
"/dl/report",
|
"/report-api/Report",
|
||||||
controller="allnet",
|
controller="allnet",
|
||||||
action="handle_dlorder_report",
|
action="handle_dlorder_report",
|
||||||
conditions=dict(method=["POST"]),
|
conditions=dict(method=["POST"]),
|
||||||
@@ -99,6 +99,7 @@ class HttpDispatcher(resource.Resource):
|
|||||||
conditions=dict(method=["POST"]),
|
conditions=dict(method=["POST"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Maintain compatability
|
||||||
self.map_post.connect(
|
self.map_post.connect(
|
||||||
"mucha_boardauth",
|
"mucha_boardauth",
|
||||||
"/mucha/boardauth.do",
|
"/mucha/boardauth.do",
|
||||||
@@ -113,6 +114,35 @@ class HttpDispatcher(resource.Resource):
|
|||||||
action="handle_updatecheck",
|
action="handle_updatecheck",
|
||||||
conditions=dict(method=["POST"]),
|
conditions=dict(method=["POST"]),
|
||||||
)
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"mucha_dlstate",
|
||||||
|
"/mucha/downloadstate.do",
|
||||||
|
controller="mucha",
|
||||||
|
action="handle_dlstate",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.map_post.connect(
|
||||||
|
"mucha_boardauth",
|
||||||
|
"/mucha_front/boardauth.do",
|
||||||
|
controller="mucha",
|
||||||
|
action="handle_boardauth",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"mucha_updatacheck",
|
||||||
|
"/mucha_front/updatacheck.do",
|
||||||
|
controller="mucha",
|
||||||
|
action="handle_updatecheck",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
self.map_post.connect(
|
||||||
|
"mucha_dlstate",
|
||||||
|
"/mucha_front/downloadstate.do",
|
||||||
|
controller="mucha",
|
||||||
|
action="handle_dlstate",
|
||||||
|
conditions=dict(method=["POST"]),
|
||||||
|
)
|
||||||
|
|
||||||
self.map_get.connect(
|
self.map_get.connect(
|
||||||
"title_get",
|
"title_get",
|
||||||
@@ -186,11 +216,11 @@ class HttpDispatcher(resource.Resource):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
elif ret is None:
|
elif ret is None:
|
||||||
self.logger.warn(f"None returned by controller for {request.uri.decode()} endpoint")
|
self.logger.warning(f"None returned by controller for {request.uri.decode()} endpoint")
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Unknown data type returned by controller for {request.uri.decode()} endpoint")
|
self.logger.warning(f"Unknown data type returned by controller for {request.uri.decode()} endpoint")
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
8
read.py
8
read.py
@@ -43,11 +43,11 @@ class BaseReader:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Import Game Information")
|
parser = argparse.ArgumentParser(description="Import Game Information")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--series",
|
"--game",
|
||||||
action="store",
|
action="store",
|
||||||
type=str,
|
type=str,
|
||||||
required=True,
|
required=True,
|
||||||
help="The game series we are importing.",
|
help="The game we are importing.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--version",
|
"--version",
|
||||||
@@ -109,7 +109,7 @@ if __name__ == "__main__":
|
|||||||
logger.setLevel(log_lv)
|
logger.setLevel(log_lv)
|
||||||
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
|
coloredlogs.install(level=log_lv, logger=logger, fmt=log_fmt_str)
|
||||||
|
|
||||||
if args.series is None or args.version is None:
|
if args.game is None or args.version is None:
|
||||||
logger.error("Game or version not specified")
|
logger.error("Game or version not specified")
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
exit(1)
|
exit(1)
|
||||||
@@ -134,7 +134,7 @@ if __name__ == "__main__":
|
|||||||
titles = Utils.get_all_titles()
|
titles = Utils.get_all_titles()
|
||||||
|
|
||||||
for dir, mod in titles.items():
|
for dir, mod in titles.items():
|
||||||
if args.series in mod.game_codes:
|
if args.game in mod.game_codes:
|
||||||
handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra)
|
handler = mod.reader(config, args.version, bin_arg, opt_arg, args.extra)
|
||||||
handler.read()
|
handler.read()
|
||||||
|
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ bcrypt
|
|||||||
jinja2
|
jinja2
|
||||||
protobuf
|
protobuf
|
||||||
autobahn
|
autobahn
|
||||||
|
pillow
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class ChuniBase:
|
|||||||
|
|
||||||
# skip the current bonus preset if no boni were found
|
# skip the current bonus preset if no boni were found
|
||||||
if all_login_boni is None or len(all_login_boni) < 1:
|
if all_login_boni is None or len(all_login_boni) < 1:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"No bonus entries found for bonus preset {preset['presetId']}"
|
f"No bonus entries found for bonus preset {preset['presetId']}"
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@@ -149,7 +149,7 @@ class ChuniBase:
|
|||||||
game_events = self.data.static.get_enabled_events(self.version)
|
game_events = self.data.static.get_enabled_events(self.version)
|
||||||
|
|
||||||
if game_events is None or len(game_events) == 0:
|
if game_events is None or len(game_events) == 0:
|
||||||
self.logger.warn("No enabled events, did you run the reader?")
|
self.logger.warning("No enabled events, did you run the reader?")
|
||||||
return {
|
return {
|
||||||
"type": data["type"],
|
"type": data["type"],
|
||||||
"length": 0,
|
"length": 0,
|
||||||
@@ -644,7 +644,7 @@ class ChuniBase:
|
|||||||
upsert["userData"][0]["userName"] = self.read_wtf8(
|
upsert["userData"][0]["userName"] = self.read_wtf8(
|
||||||
upsert["userData"][0]["userName"]
|
upsert["userData"][0]["userName"]
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.data.profile.put_profile_data(
|
self.data.profile.put_profile_data(
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class ChuniReader(BaseReader):
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted login bonus preset {id}")
|
self.logger.info(f"Inserted login bonus preset {id}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Failed to insert login bonus preset {id}")
|
self.logger.warning(f"Failed to insert login bonus preset {id}")
|
||||||
|
|
||||||
for bonus in xml_root.find("infos").findall("LoginBonusDataInfo"):
|
for bonus in xml_root.find("infos").findall("LoginBonusDataInfo"):
|
||||||
for name in bonus.findall("loginBonusName"):
|
for name in bonus.findall("loginBonusName"):
|
||||||
@@ -113,7 +113,7 @@ class ChuniReader(BaseReader):
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted login bonus {bonus_id}")
|
self.logger.info(f"Inserted login bonus {bonus_id}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Failed to insert login bonus {bonus_id}"
|
f"Failed to insert login bonus {bonus_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ class ChuniReader(BaseReader):
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted event {id}")
|
self.logger.info(f"Inserted event {id}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Failed to insert event {id}")
|
self.logger.warning(f"Failed to insert event {id}")
|
||||||
|
|
||||||
def read_music(self, music_dir: str) -> None:
|
def read_music(self, music_dir: str) -> None:
|
||||||
for root, dirs, files in walk(music_dir):
|
for root, dirs, files in walk(music_dir):
|
||||||
@@ -200,7 +200,7 @@ class ChuniReader(BaseReader):
|
|||||||
f"Inserted music {song_id} chart {chart_id}"
|
f"Inserted music {song_id} chart {chart_id}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Failed to insert music {song_id} chart {chart_id}"
|
f"Failed to insert music {song_id} chart {chart_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ class ChuniReader(BaseReader):
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted charge {id}")
|
self.logger.info(f"Inserted charge {id}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Failed to insert charge {id}")
|
self.logger.warning(f"Failed to insert charge {id}")
|
||||||
|
|
||||||
def read_avatar(self, avatar_dir: str) -> None:
|
def read_avatar(self, avatar_dir: str) -> None:
|
||||||
for root, dirs, files in walk(avatar_dir):
|
for root, dirs, files in walk(avatar_dir):
|
||||||
@@ -259,4 +259,4 @@ class ChuniReader(BaseReader):
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
self.logger.info(f"Inserted avatarAccessory {id}")
|
self.logger.info(f"Inserted avatarAccessory {id}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Failed to insert avatarAccessory {id}")
|
self.logger.warning(f"Failed to insert avatarAccessory {id}")
|
||||||
|
|||||||
@@ -530,7 +530,7 @@ class ChuniItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
|
self.logger.warning(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -572,7 +572,7 @@ class ChuniItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_user_print_state: Failed to insert! aime_id: {aime_id}"
|
f"put_user_print_state: Failed to insert! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -589,7 +589,7 @@ class ChuniItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_profile_data: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -452,7 +452,7 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_data_ex: Failed to update! aime_id: {aime_id}"
|
f"put_profile_data_ex: Failed to update! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -479,7 +479,7 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_option: Failed to update! aime_id: {aime_id}"
|
f"put_profile_option: Failed to update! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -503,7 +503,7 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_option_ex: Failed to update! aime_id: {aime_id}"
|
f"put_profile_option_ex: Failed to update! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -527,7 +527,7 @@ class ChuniProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}"
|
f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -552,7 +552,7 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_activity: Failed to update! aime_id: {aime_id}"
|
f"put_profile_activity: Failed to update! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -578,7 +578,7 @@ class ChuniProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_charge: Failed to update! aime_id: {aime_id}"
|
f"put_profile_charge: Failed to update! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -302,14 +302,14 @@ class ChuniStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}"
|
f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
event = self.get_event(version, event_id)
|
event = self.get_event(version, event_id)
|
||||||
if event is None:
|
if event is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"update_event: failed to fetch event {event_id} after updating"
|
f"update_event: failed to fetch event {event_id} after updating"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -506,7 +506,7 @@ class ChuniStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert gacha! gacha_id {gacha_id}")
|
self.logger.warning(f"Failed to insert gacha! gacha_id {gacha_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -541,7 +541,7 @@ class ChuniStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert gacha card! gacha_id {gacha_id}")
|
self.logger.warning(f"Failed to insert gacha card! gacha_id {gacha_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -577,7 +577,7 @@ class ChuniStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert card! card_id {card_id}")
|
self.logger.warning(f"Failed to insert card! card_id {card_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,18 @@ class CardMakerVersionConfig:
|
|||||||
1: {"ongeki": 1.30.01, "chuni": 2.00.00, "maimai": 1.20.00}
|
1: {"ongeki": 1.30.01, "chuni": 2.00.00, "maimai": 1.20.00}
|
||||||
"""
|
"""
|
||||||
return CoreConfig.get_config_field(
|
return CoreConfig.get_config_field(
|
||||||
self.__config, "cardmaker", "version", default={}
|
self.__config, "cardmaker", "version", default={
|
||||||
|
0: {
|
||||||
|
"ongeki": "1.30.01",
|
||||||
|
"chuni": "2.00.00",
|
||||||
|
"maimai": "1.20.00"
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
"ongeki": "1.35.03",
|
||||||
|
"chuni": "2.10.00",
|
||||||
|
"maimai": "1.30.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
)[version]
|
)[version]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ class CardMakerServlet:
|
|||||||
endpoint = url_split[len(url_split) - 1]
|
endpoint = url_split[len(url_split) - 1]
|
||||||
client_ip = Utils.get_ip_addr(request)
|
client_ip = Utils.get_ip_addr(request)
|
||||||
|
|
||||||
print(f"version: {version}")
|
|
||||||
|
|
||||||
if version >= 130 and version < 135: # Card Maker
|
if version >= 130 and version < 135: # Card Maker
|
||||||
internal_ver = CardMakerConstants.VER_CARD_MAKER
|
internal_ver = CardMakerConstants.VER_CARD_MAKER
|
||||||
elif version >= 135 and version < 140: # Card Maker 1.35
|
elif version >= 135 and version < 140: # Card Maker 1.35
|
||||||
@@ -124,11 +122,12 @@ class CardMakerServlet:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
||||||
|
raise
|
||||||
return zlib.compress(b'{"stat": "0"}')
|
return zlib.compress(b'{"stat": "0"}')
|
||||||
|
|
||||||
if resp is None:
|
if resp is None:
|
||||||
resp = {"returnCode": 1}
|
resp = {"returnCode": 1}
|
||||||
|
|
||||||
self.logger.info(f"Response {resp}")
|
self.logger.debug(f"Response {resp}")
|
||||||
|
|
||||||
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class CardMakerReader(BaseReader):
|
|||||||
read_csv = getattr(CardMakerReader, func)
|
read_csv = getattr(CardMakerReader, func)
|
||||||
read_csv(self, f"{self.bin_dir}/MU3/{file}")
|
read_csv(self, f"{self.bin_dir}/MU3/{file}")
|
||||||
else:
|
else:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Couldn't find {file} file in {self.bin_dir}, skipping"
|
f"Couldn't find {file} file in {self.bin_dir}, skipping"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -89,8 +89,7 @@ class CardMakerReader(BaseReader):
|
|||||||
version_ids = {
|
version_ids = {
|
||||||
"v2_00": ChuniConstants.VER_CHUNITHM_NEW,
|
"v2_00": ChuniConstants.VER_CHUNITHM_NEW,
|
||||||
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
|
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
|
||||||
# Chunithm SUN, ignore for now
|
"v2_10": ChuniConstants.VER_CHUNITHM_SUN,
|
||||||
"v2_10": ChuniConstants.VER_CHUNITHM_NEW_PLUS + 1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for root, dirs, files in os.walk(base_dir):
|
for root, dirs, files in os.walk(base_dir):
|
||||||
@@ -138,8 +137,7 @@ class CardMakerReader(BaseReader):
|
|||||||
version_ids = {
|
version_ids = {
|
||||||
"v2_00": ChuniConstants.VER_CHUNITHM_NEW,
|
"v2_00": ChuniConstants.VER_CHUNITHM_NEW,
|
||||||
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
|
"v2_05": ChuniConstants.VER_CHUNITHM_NEW_PLUS,
|
||||||
# Chunithm SUN, ignore for now
|
"v2_10": ChuniConstants.VER_CHUNITHM_SUN,
|
||||||
"v2_10": ChuniConstants.VER_CHUNITHM_NEW_PLUS + 1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for root, dirs, files in os.walk(base_dir):
|
for root, dirs, files in os.walk(base_dir):
|
||||||
@@ -226,6 +224,12 @@ class CardMakerReader(BaseReader):
|
|||||||
True if troot.find("disable").text == "false" else False
|
True if troot.find("disable").text == "false" else False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# check if a date is part of the name and disable the
|
||||||
|
# card if it is
|
||||||
|
enabled = (
|
||||||
|
False if re.search(r"\d{2}/\d{2}/\d{2}", name) else enabled
|
||||||
|
)
|
||||||
|
|
||||||
self.mai2_data.static.put_card(
|
self.mai2_data.static.put_card(
|
||||||
version, card_id, name, enabled=enabled
|
version, card_id, name, enabled=enabled
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from titles.cxb.config import CxbConfig
|
|||||||
from titles.cxb.const import CxbConstants
|
from titles.cxb.const import CxbConstants
|
||||||
from titles.cxb.database import CxbData
|
from titles.cxb.database import CxbData
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
class CxbBase:
|
class CxbBase:
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
|
||||||
@@ -51,9 +52,154 @@ class CxbBase:
|
|||||||
self.logger.info(f"Login user {data['login']['authid']}")
|
self.logger.info(f"Login user {data['login']['authid']}")
|
||||||
return {"token": data["login"]["authid"], "uid": data["login"]["authid"]}
|
return {"token": data["login"]["authid"], "uid": data["login"]["authid"]}
|
||||||
|
|
||||||
self.logger.warn(f"User {data['login']['authid']} does not have a profile")
|
self.logger.warning(f"User {data['login']['authid']} does not have a profile")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def task_generateCoupon(index, data1):
|
||||||
|
# Coupons
|
||||||
|
for i in range(500, 510):
|
||||||
|
index.append(str(i))
|
||||||
|
couponid = int(i) - 500
|
||||||
|
dataValue = [
|
||||||
|
{
|
||||||
|
"couponId": str(couponid),
|
||||||
|
"couponNum": "1",
|
||||||
|
"couponLog": [],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def task_generateShopListTitle(index, data1):
|
||||||
|
# ShopList_Title
|
||||||
|
for i in range(200000, 201451):
|
||||||
|
index.append(str(i))
|
||||||
|
shopid = int(i) - 200000
|
||||||
|
dataValue = [
|
||||||
|
{
|
||||||
|
"shopId": shopid,
|
||||||
|
"shopState": "2",
|
||||||
|
"isDisable": "t",
|
||||||
|
"isDeleted": "f",
|
||||||
|
"isSpecialFlag": "f",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def task_generateShopListIcon(index, data1):
|
||||||
|
# ShopList_Icon
|
||||||
|
for i in range(202000, 202264):
|
||||||
|
index.append(str(i))
|
||||||
|
shopid = int(i) - 200000
|
||||||
|
dataValue = [
|
||||||
|
{
|
||||||
|
"shopId": shopid,
|
||||||
|
"shopState": "2",
|
||||||
|
"isDisable": "t",
|
||||||
|
"isDeleted": "f",
|
||||||
|
"isSpecialFlag": "f",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def task_generateStories(index, data1):
|
||||||
|
# Stories
|
||||||
|
for i in range(900000, 900003):
|
||||||
|
index.append(str(i))
|
||||||
|
storyid = int(i) - 900000
|
||||||
|
dataValue = [
|
||||||
|
{
|
||||||
|
"storyId": storyid,
|
||||||
|
"unlockState1": ["t"] * 10,
|
||||||
|
"unlockState2": ["t"] * 10,
|
||||||
|
"unlockState3": ["t"] * 10,
|
||||||
|
"unlockState4": ["t"] * 10,
|
||||||
|
"unlockState5": ["t"] * 10,
|
||||||
|
"unlockState6": ["t"] * 10,
|
||||||
|
"unlockState7": ["t"] * 10,
|
||||||
|
"unlockState8": ["t"] * 10,
|
||||||
|
"unlockState9": ["t"] * 10,
|
||||||
|
"unlockState10": ["t"] * 10,
|
||||||
|
"unlockState11": ["t"] * 10,
|
||||||
|
"unlockState12": ["t"] * 10,
|
||||||
|
"unlockState13": ["t"] * 10,
|
||||||
|
"unlockState14": ["t"] * 10,
|
||||||
|
"unlockState15": ["t"] * 10,
|
||||||
|
"unlockState16": ["t"] * 10,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def task_generateScoreData(song, index, data1):
|
||||||
|
song_data = song["data"]
|
||||||
|
songCode = []
|
||||||
|
|
||||||
|
songCode.append(
|
||||||
|
{
|
||||||
|
"mcode": song_data["mcode"],
|
||||||
|
"musicState": song_data["musicState"],
|
||||||
|
"playCount": song_data["playCount"],
|
||||||
|
"totalScore": song_data["totalScore"],
|
||||||
|
"highScore": song_data["highScore"],
|
||||||
|
"everHighScore": song_data["everHighScore"]
|
||||||
|
if "everHighScore" in song_data
|
||||||
|
else ["0", "0", "0", "0", "0"],
|
||||||
|
"clearRate": song_data["clearRate"],
|
||||||
|
"rankPoint": song_data["rankPoint"],
|
||||||
|
"normalCR": song_data["normalCR"]
|
||||||
|
if "normalCR" in song_data
|
||||||
|
else ["0", "0", "0", "0", "0"],
|
||||||
|
"survivalCR": song_data["survivalCR"]
|
||||||
|
if "survivalCR" in song_data
|
||||||
|
else ["0", "0", "0", "0", "0"],
|
||||||
|
"ultimateCR": song_data["ultimateCR"]
|
||||||
|
if "ultimateCR" in song_data
|
||||||
|
else ["0", "0", "0", "0", "0"],
|
||||||
|
"nohopeCR": song_data["nohopeCR"]
|
||||||
|
if "nohopeCR" in song_data
|
||||||
|
else ["0", "0", "0", "0", "0"],
|
||||||
|
"combo": song_data["combo"],
|
||||||
|
"coupleUserId": song_data["coupleUserId"],
|
||||||
|
"difficulty": song_data["difficulty"],
|
||||||
|
"isFullCombo": song_data["isFullCombo"],
|
||||||
|
"clearGaugeType": song_data["clearGaugeType"],
|
||||||
|
"fieldType": song_data["fieldType"],
|
||||||
|
"gameType": song_data["gameType"],
|
||||||
|
"grade": song_data["grade"],
|
||||||
|
"unlockState": song_data["unlockState"],
|
||||||
|
"extraState": song_data["extraState"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
index.append(song_data["index"])
|
||||||
|
data1.append(
|
||||||
|
b64encode(
|
||||||
|
bytes(json.dumps(songCode[0], separators=(",", ":")), "utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def task_generateIndexData(versionindex):
|
||||||
|
try:
|
||||||
|
v_profile = self.data.profile.get_profile_index(0, uid, self.version)
|
||||||
|
v_profile_data = v_profile["data"]
|
||||||
|
versionindex.append(int(v_profile_data["appVersion"]))
|
||||||
|
except Exception:
|
||||||
|
versionindex.append("10400")
|
||||||
|
|
||||||
def handle_action_loadrange_request(self, data: Dict) -> Dict:
|
def handle_action_loadrange_request(self, data: Dict) -> Dict:
|
||||||
range_start = data["loadrange"]["range"][0]
|
range_start = data["loadrange"]["range"][0]
|
||||||
range_end = data["loadrange"]["range"][1]
|
range_end = data["loadrange"]["range"][1]
|
||||||
@@ -107,146 +253,29 @@ class CxbBase:
|
|||||||
900000 = Stories
|
900000 = Stories
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Coupons
|
# Async threads to generate the response
|
||||||
for i in range(500, 510):
|
thread_Coupon = Thread(target=CxbBase.task_generateCoupon(index, data1))
|
||||||
index.append(str(i))
|
thread_ShopListTitle = Thread(target=CxbBase.task_generateShopListTitle(index, data1))
|
||||||
couponid = int(i) - 500
|
thread_ShopListIcon = Thread(target=CxbBase.task_generateShopListIcon(index, data1))
|
||||||
dataValue = [
|
thread_Stories = Thread(target=CxbBase.task_generateStories(index, data1))
|
||||||
{
|
|
||||||
"couponId": str(couponid),
|
|
||||||
"couponNum": "1",
|
|
||||||
"couponLog": [],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
data1.append(
|
|
||||||
b64encode(
|
|
||||||
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
|
||||||
).decode("utf-8")
|
|
||||||
)
|
|
||||||
|
|
||||||
# ShopList_Title
|
thread_Coupon.start()
|
||||||
for i in range(200000, 201451):
|
thread_ShopListTitle.start()
|
||||||
index.append(str(i))
|
thread_ShopListIcon.start()
|
||||||
shopid = int(i) - 200000
|
thread_Stories.start()
|
||||||
dataValue = [
|
|
||||||
{
|
|
||||||
"shopId": shopid,
|
|
||||||
"shopState": "2",
|
|
||||||
"isDisable": "t",
|
|
||||||
"isDeleted": "f",
|
|
||||||
"isSpecialFlag": "f",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
data1.append(
|
|
||||||
b64encode(
|
|
||||||
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
|
||||||
).decode("utf-8")
|
|
||||||
)
|
|
||||||
|
|
||||||
# ShopList_Icon
|
thread_Coupon.join()
|
||||||
for i in range(202000, 202264):
|
thread_ShopListTitle.join()
|
||||||
index.append(str(i))
|
thread_ShopListIcon.join()
|
||||||
shopid = int(i) - 200000
|
thread_Stories.join()
|
||||||
dataValue = [
|
|
||||||
{
|
|
||||||
"shopId": shopid,
|
|
||||||
"shopState": "2",
|
|
||||||
"isDisable": "t",
|
|
||||||
"isDeleted": "f",
|
|
||||||
"isSpecialFlag": "f",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
data1.append(
|
|
||||||
b64encode(
|
|
||||||
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
|
||||||
).decode("utf-8")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Stories
|
|
||||||
for i in range(900000, 900003):
|
|
||||||
index.append(str(i))
|
|
||||||
storyid = int(i) - 900000
|
|
||||||
dataValue = [
|
|
||||||
{
|
|
||||||
"storyId": storyid,
|
|
||||||
"unlockState1": ["t"] * 10,
|
|
||||||
"unlockState2": ["t"] * 10,
|
|
||||||
"unlockState3": ["t"] * 10,
|
|
||||||
"unlockState4": ["t"] * 10,
|
|
||||||
"unlockState5": ["t"] * 10,
|
|
||||||
"unlockState6": ["t"] * 10,
|
|
||||||
"unlockState7": ["t"] * 10,
|
|
||||||
"unlockState8": ["t"] * 10,
|
|
||||||
"unlockState9": ["t"] * 10,
|
|
||||||
"unlockState10": ["t"] * 10,
|
|
||||||
"unlockState11": ["t"] * 10,
|
|
||||||
"unlockState12": ["t"] * 10,
|
|
||||||
"unlockState13": ["t"] * 10,
|
|
||||||
"unlockState14": ["t"] * 10,
|
|
||||||
"unlockState15": ["t"] * 10,
|
|
||||||
"unlockState16": ["t"] * 10,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
data1.append(
|
|
||||||
b64encode(
|
|
||||||
bytes(json.dumps(dataValue[0], separators=(",", ":")), "utf-8")
|
|
||||||
).decode("utf-8")
|
|
||||||
)
|
|
||||||
|
|
||||||
for song in songs:
|
for song in songs:
|
||||||
song_data = song["data"]
|
thread_ScoreData = Thread(target=CxbBase.task_generateScoreData(song, index, data1))
|
||||||
songCode = []
|
thread_ScoreData.start()
|
||||||
|
|
||||||
songCode.append(
|
|
||||||
{
|
|
||||||
"mcode": song_data["mcode"],
|
|
||||||
"musicState": song_data["musicState"],
|
|
||||||
"playCount": song_data["playCount"],
|
|
||||||
"totalScore": song_data["totalScore"],
|
|
||||||
"highScore": song_data["highScore"],
|
|
||||||
"everHighScore": song_data["everHighScore"]
|
|
||||||
if "everHighScore" in song_data
|
|
||||||
else ["0", "0", "0", "0", "0"],
|
|
||||||
"clearRate": song_data["clearRate"],
|
|
||||||
"rankPoint": song_data["rankPoint"],
|
|
||||||
"normalCR": song_data["normalCR"]
|
|
||||||
if "normalCR" in song_data
|
|
||||||
else ["0", "0", "0", "0", "0"],
|
|
||||||
"survivalCR": song_data["survivalCR"]
|
|
||||||
if "survivalCR" in song_data
|
|
||||||
else ["0", "0", "0", "0", "0"],
|
|
||||||
"ultimateCR": song_data["ultimateCR"]
|
|
||||||
if "ultimateCR" in song_data
|
|
||||||
else ["0", "0", "0", "0", "0"],
|
|
||||||
"nohopeCR": song_data["nohopeCR"]
|
|
||||||
if "nohopeCR" in song_data
|
|
||||||
else ["0", "0", "0", "0", "0"],
|
|
||||||
"combo": song_data["combo"],
|
|
||||||
"coupleUserId": song_data["coupleUserId"],
|
|
||||||
"difficulty": song_data["difficulty"],
|
|
||||||
"isFullCombo": song_data["isFullCombo"],
|
|
||||||
"clearGaugeType": song_data["clearGaugeType"],
|
|
||||||
"fieldType": song_data["fieldType"],
|
|
||||||
"gameType": song_data["gameType"],
|
|
||||||
"grade": song_data["grade"],
|
|
||||||
"unlockState": song_data["unlockState"],
|
|
||||||
"extraState": song_data["extraState"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
index.append(song_data["index"])
|
|
||||||
data1.append(
|
|
||||||
b64encode(
|
|
||||||
bytes(json.dumps(songCode[0], separators=(",", ":")), "utf-8")
|
|
||||||
).decode("utf-8")
|
|
||||||
)
|
|
||||||
|
|
||||||
for v in index:
|
for v in index:
|
||||||
try:
|
thread_IndexData = Thread(target=CxbBase.task_generateIndexData(versionindex))
|
||||||
v_profile = self.data.profile.get_profile_index(0, uid, self.version)
|
thread_IndexData.start()
|
||||||
v_profile_data = v_profile["data"]
|
|
||||||
versionindex.append(int(v_profile_data["appVersion"]))
|
|
||||||
except:
|
|
||||||
versionindex.append("10400")
|
|
||||||
|
|
||||||
return {"index": index, "data": data1, "version": versionindex}
|
return {"index": index, "data": data1, "version": versionindex}
|
||||||
|
|
||||||
@@ -257,7 +286,7 @@ class CxbBase:
|
|||||||
# REV Omnimix Version Fetcher
|
# REV Omnimix Version Fetcher
|
||||||
gameversion = data["saveindex"]["data"][0][2]
|
gameversion = data["saveindex"]["data"][0][2]
|
||||||
self.logger.warning(f"Game Version is {gameversion}")
|
self.logger.warning(f"Game Version is {gameversion}")
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if "10205" in gameversion:
|
if "10205" in gameversion:
|
||||||
@@ -319,7 +348,7 @@ class CxbBase:
|
|||||||
# Sunrise
|
# Sunrise
|
||||||
try:
|
try:
|
||||||
profileIndex = save_data["index"].index("0")
|
profileIndex = save_data["index"].index("0")
|
||||||
except:
|
except Exception:
|
||||||
return {"data": ""} # Maybe
|
return {"data": ""} # Maybe
|
||||||
|
|
||||||
profile = json.loads(save_data["data"][profileIndex])
|
profile = json.loads(save_data["data"][profileIndex])
|
||||||
@@ -467,7 +496,7 @@ class CxbBase:
|
|||||||
score=int(rid["sc"][0]),
|
score=int(rid["sc"][0]),
|
||||||
clear=rid["clear"],
|
clear=rid["clear"],
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
self.data.score.put_ranking(
|
self.data.score.put_ranking(
|
||||||
user_id=uid,
|
user_id=uid,
|
||||||
rev_id=int(rid["rid"]),
|
rev_id=int(rid["rid"]),
|
||||||
@@ -485,7 +514,7 @@ class CxbBase:
|
|||||||
score=int(rid["sc"][0]),
|
score=int(rid["sc"][0]),
|
||||||
clear=0,
|
clear=0,
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
self.data.score.put_ranking(
|
self.data.score.put_ranking(
|
||||||
user_id=uid,
|
user_id=uid,
|
||||||
rev_id=int(rid["rid"]),
|
rev_id=int(rid["rid"]),
|
||||||
|
|||||||
@@ -123,13 +123,13 @@ class CxbServlet(resource.Resource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as f:
|
except Exception as f:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Error decoding json: {e} / {f} - {req_url} - {req_bytes}"
|
f"Error decoding json: {e} / {f} - {req_url} - {req_bytes}"
|
||||||
)
|
)
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
if req_json == {}:
|
if req_json == {}:
|
||||||
self.logger.warn(f"Empty json request to {req_url}")
|
self.logger.warning(f"Empty json request to {req_url}")
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
cmd = url_split[len(url_split) - 1]
|
cmd = url_split[len(url_split) - 1]
|
||||||
@@ -140,7 +140,7 @@ class CxbServlet(resource.Resource):
|
|||||||
not type(req_json["dldate"]) is dict
|
not type(req_json["dldate"]) is dict
|
||||||
or "filetype" not in req_json["dldate"]
|
or "filetype" not in req_json["dldate"]
|
||||||
):
|
):
|
||||||
self.logger.warn(f"Malformed dldate request: {req_url} {req_json}")
|
self.logger.warning(f"Malformed dldate request: {req_url} {req_json}")
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
filetype = req_json["dldate"]["filetype"]
|
filetype = req_json["dldate"]["filetype"]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class CxbReader(BaseReader):
|
|||||||
pull_bin_ram = True
|
pull_bin_ram = True
|
||||||
|
|
||||||
if not path.exists(f"{self.bin_dir}"):
|
if not path.exists(f"{self.bin_dir}"):
|
||||||
self.logger.warn(f"Couldn't find csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't find csv file in {self.bin_dir}, skipping")
|
||||||
pull_bin_ram = False
|
pull_bin_ram = False
|
||||||
|
|
||||||
if pull_bin_ram:
|
if pull_bin_ram:
|
||||||
@@ -123,5 +123,5 @@ class CxbReader(BaseReader):
|
|||||||
genre,
|
genre,
|
||||||
int(row["easy"].replace("Easy ", "").replace("N/A", "0")),
|
int(row["easy"].replace("Easy ", "").replace("N/A", "0")),
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Any, List, Dict
|
|||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import urllib
|
import urllib
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.diva.config import DivaConfig
|
from titles.diva.config import DivaConfig
|
||||||
@@ -663,11 +664,8 @@ class DivaBase:
|
|||||||
|
|
||||||
return pv_result
|
return pv_result
|
||||||
|
|
||||||
def handle_get_pv_pd_request(self, data: Dict) -> Dict:
|
def task_generateScoreData(self, data: Dict, pd_by_pv_id, song):
|
||||||
song_id = data["pd_pv_id_lst"].split(",")
|
|
||||||
pv = ""
|
|
||||||
|
|
||||||
for song in song_id:
|
|
||||||
if int(song) > 0:
|
if int(song) > 0:
|
||||||
# the request do not send a edition so just perform a query best score and ranking for each edition.
|
# the request do not send a edition so just perform a query best score and ranking for each edition.
|
||||||
# 0=ORIGINAL, 1=EXTRA
|
# 0=ORIGINAL, 1=EXTRA
|
||||||
@@ -702,11 +700,30 @@ class DivaBase:
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.logger.debug(f"pv_result = {pv_result}")
|
self.logger.debug(f"pv_result = {pv_result}")
|
||||||
|
pd_by_pv_id.append(urllib.parse.quote(pv_result))
|
||||||
pv += urllib.parse.quote(pv_result)
|
|
||||||
else:
|
else:
|
||||||
pv += urllib.parse.quote(f"{song}***")
|
pd_by_pv_id.append(urllib.parse.quote(f"{song}***"))
|
||||||
pv += ","
|
pd_by_pv_id.append(",")
|
||||||
|
|
||||||
|
def handle_get_pv_pd_request(self, data: Dict) -> Dict:
|
||||||
|
song_id = data["pd_pv_id_lst"].split(",")
|
||||||
|
pv = ""
|
||||||
|
|
||||||
|
threads = []
|
||||||
|
pd_by_pv_id = []
|
||||||
|
|
||||||
|
for song in song_id:
|
||||||
|
thread_ScoreData = Thread(target=self.task_generateScoreData(data, pd_by_pv_id, song))
|
||||||
|
threads.append(thread_ScoreData)
|
||||||
|
|
||||||
|
for x in threads:
|
||||||
|
x.start()
|
||||||
|
|
||||||
|
for x in threads:
|
||||||
|
x.join()
|
||||||
|
|
||||||
|
for x in pd_by_pv_id:
|
||||||
|
pv += x
|
||||||
|
|
||||||
response = ""
|
response = ""
|
||||||
response += f"&pd_by_pv_id={pv[:-1]}"
|
response += f"&pd_by_pv_id={pv[:-1]}"
|
||||||
|
|||||||
@@ -34,18 +34,18 @@ class DivaReader(BaseReader):
|
|||||||
pull_opt_rom = True
|
pull_opt_rom = True
|
||||||
|
|
||||||
if not path.exists(f"{self.bin_dir}/ram"):
|
if not path.exists(f"{self.bin_dir}/ram"):
|
||||||
self.logger.warn(f"Couldn't find ram folder in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't find ram folder in {self.bin_dir}, skipping")
|
||||||
pull_bin_ram = False
|
pull_bin_ram = False
|
||||||
|
|
||||||
if not path.exists(f"{self.bin_dir}/rom"):
|
if not path.exists(f"{self.bin_dir}/rom"):
|
||||||
self.logger.warn(f"Couldn't find rom folder in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't find rom folder in {self.bin_dir}, skipping")
|
||||||
pull_bin_rom = False
|
pull_bin_rom = False
|
||||||
|
|
||||||
if self.opt_dir is not None:
|
if self.opt_dir is not None:
|
||||||
opt_dirs = self.get_data_directories(self.opt_dir)
|
opt_dirs = self.get_data_directories(self.opt_dir)
|
||||||
else:
|
else:
|
||||||
pull_opt_rom = False
|
pull_opt_rom = False
|
||||||
self.logger.warn("No option directory specified, skipping")
|
self.logger.warning("No option directory specified, skipping")
|
||||||
|
|
||||||
if pull_bin_ram:
|
if pull_bin_ram:
|
||||||
self.read_ram(f"{self.bin_dir}/ram")
|
self.read_ram(f"{self.bin_dir}/ram")
|
||||||
@@ -139,7 +139,7 @@ class DivaReader(BaseReader):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Databank folder not found in {ram_root_dir}, skipping")
|
self.logger.warning(f"Databank folder not found in {ram_root_dir}, skipping")
|
||||||
|
|
||||||
def read_rom(self, rom_root_dir: str) -> None:
|
def read_rom(self, rom_root_dir: str) -> None:
|
||||||
self.logger.info(f"Read ROM from {rom_root_dir}")
|
self.logger.info(f"Read ROM from {rom_root_dir}")
|
||||||
@@ -150,7 +150,7 @@ class DivaReader(BaseReader):
|
|||||||
elif path.exists(f"{rom_root_dir}/pv_db.txt"):
|
elif path.exists(f"{rom_root_dir}/pv_db.txt"):
|
||||||
file_path = f"{rom_root_dir}/pv_db.txt"
|
file_path = f"{rom_root_dir}/pv_db.txt"
|
||||||
else:
|
else:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Cannot find pv_db.txt or mdata_pv_db.txt in {rom_root_dir}, skipping"
|
f"Cannot find pv_db.txt or mdata_pv_db.txt in {rom_root_dir}, skipping"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class IDZUserDBProtocol(Protocol):
|
|||||||
elif self.version == 230:
|
elif self.version == 230:
|
||||||
self.version_internal = IDZConstants.VER_IDZ_230
|
self.version_internal = IDZConstants.VER_IDZ_230
|
||||||
else:
|
else:
|
||||||
self.logger.warn(f"Bad version v{self.version}")
|
self.logger.warning(f"Bad version v{self.version}")
|
||||||
self.version = None
|
self.version = None
|
||||||
self.version_internal = None
|
self.version_internal = None
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ class IDZUserDBProtocol(Protocol):
|
|||||||
self.version_internal
|
self.version_internal
|
||||||
].get(cmd, None)
|
].get(cmd, None)
|
||||||
if handler_cls is None:
|
if handler_cls is None:
|
||||||
self.logger.warn(f"No handler for v{self.version} {hex(cmd)} cmd")
|
self.logger.warning(f"No handler for v{self.version} {hex(cmd)} cmd")
|
||||||
handler_cls = IDZHandlerBase
|
handler_cls = IDZHandlerBase
|
||||||
|
|
||||||
handler = handler_cls(self.core_config, self.game_config, self.version_internal)
|
handler = handler_cls(self.core_config, self.game_config, self.version_internal)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
import logging
|
import logging
|
||||||
|
from base64 import b64decode
|
||||||
|
from os import path, stat, remove
|
||||||
|
from PIL import ImageFile
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
@@ -54,7 +57,7 @@ class Mai2Base:
|
|||||||
events = self.data.static.get_enabled_events(self.version)
|
events = self.data.static.get_enabled_events(self.version)
|
||||||
events_lst = []
|
events_lst = []
|
||||||
if events is None or not events:
|
if events is None or not events:
|
||||||
self.logger.warn("No enabled events, did you run the reader?")
|
self.logger.warning("No enabled events, did you run the reader?")
|
||||||
return {"type": data["type"], "length": 0, "gameEventList": []}
|
return {"type": data["type"], "length": 0, "gameEventList": []}
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
@@ -89,7 +92,7 @@ class Mai2Base:
|
|||||||
for i, charge in enumerate(game_charge_list):
|
for i, charge in enumerate(game_charge_list):
|
||||||
charge_list.append(
|
charge_list.append(
|
||||||
{
|
{
|
||||||
"orderId": i,
|
"orderId": i + 1,
|
||||||
"chargeId": charge["ticketId"],
|
"chargeId": charge["ticketId"],
|
||||||
"price": charge["price"],
|
"price": charge["price"],
|
||||||
"startDate": "2017-12-05 07:00:00.0",
|
"startDate": "2017-12-05 07:00:00.0",
|
||||||
@@ -738,7 +741,7 @@ class Mai2Base:
|
|||||||
music_detail_list = []
|
music_detail_list = []
|
||||||
|
|
||||||
if user_id <= 0:
|
if user_id <= 0:
|
||||||
self.logger.warn("handle_get_user_music_api_request: Could not find userid in data, or userId is 0")
|
self.logger.warning("handle_get_user_music_api_request: Could not find userid in data, or userId is 0")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
songs = self.data.score.get_best_scores(user_id, is_dx=False)
|
songs = self.data.score.get_best_scores(user_id, is_dx=False)
|
||||||
@@ -773,4 +776,89 @@ class Mai2Base:
|
|||||||
self.logger.debug(data)
|
self.logger.debug(data)
|
||||||
|
|
||||||
def handle_upload_user_photo_api_request(self, data: Dict) -> Dict:
|
def handle_upload_user_photo_api_request(self, data: Dict) -> Dict:
|
||||||
self.logger.debug(data)
|
if not self.game_config.uploads.photos or not self.game_config.uploads.photos_dir:
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
photo = data.get("userPhoto", {})
|
||||||
|
|
||||||
|
if photo is None or not photo:
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
order_id = int(photo.get("orderId", -1))
|
||||||
|
user_id = int(photo.get("userId", -1))
|
||||||
|
div_num = int(photo.get("divNumber", -1))
|
||||||
|
div_len = int(photo.get("divLength", -1))
|
||||||
|
div_data = photo.get("divData", "")
|
||||||
|
playlog_id = int(photo.get("playlogId", -1))
|
||||||
|
track_num = int(photo.get("trackNo", -1))
|
||||||
|
upload_date = photo.get("uploadDate", "")
|
||||||
|
|
||||||
|
if order_id < 0 or user_id <= 0 or div_num < 0 or div_len <= 0 or not div_data or playlog_id < 0 or track_num <= 0 or not upload_date:
|
||||||
|
self.logger.warning(f"Malformed photo upload request")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
if order_id == 0 and div_num > 0:
|
||||||
|
self.logger.warning(f"Failed to set orderId properly (still 0 after first chunk)")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
if div_num == 0 and order_id > 0:
|
||||||
|
self.logger.warning(f"First chuck re-send, Ignore")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
if div_num >= div_len:
|
||||||
|
self.logger.warning(f"Sent extra chunks ({div_num} >= {div_len})")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
if div_len >= 100:
|
||||||
|
self.logger.warning(f"Photo too large ({div_len} * 10240 = {div_len * 10240} bytes)")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
ret_code = order_id + 1
|
||||||
|
photo_chunk = b64decode(div_data)
|
||||||
|
|
||||||
|
if len(photo_chunk) > 10240 or (len(photo_chunk) < 10240 and div_num + 1 != div_len):
|
||||||
|
self.logger.warning(f"Incorrect data size after decoding (Expected 10240, got {len(photo_chunk)})")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
out_name = f"{self.game_config.uploads.photos_dir}/{user_id}_{playlog_id}_{track_num}"
|
||||||
|
|
||||||
|
if not path.exists(f"{out_name}.bin") and div_num != 0:
|
||||||
|
self.logger.warning(f"Out of order photo upload (div_num {div_num})")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
if path.exists(f"{out_name}.bin") and div_num == 0:
|
||||||
|
self.logger.warning(f"Duplicate file upload")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
elif path.exists(f"{out_name}.bin"):
|
||||||
|
fstats = stat(f"{out_name}.bin")
|
||||||
|
if fstats.st_size != 10240 * div_num:
|
||||||
|
self.logger.warning(f"Out of order photo upload (trying to upload div {div_num}, expected div {fstats.st_size / 10240} for file sized {fstats.st_size} bytes)")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(f"{out_name}.bin", "ab") as f:
|
||||||
|
f.write(photo_chunk)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.logger.error(f"Failed writing to {out_name}.bin")
|
||||||
|
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
if div_num + 1 == div_len and path.exists(f"{out_name}.bin"):
|
||||||
|
try:
|
||||||
|
p = ImageFile.Parser()
|
||||||
|
with open(f"{out_name}.bin", "rb") as f:
|
||||||
|
p.feed(f.read())
|
||||||
|
|
||||||
|
im = p.close()
|
||||||
|
im.save(f"{out_name}.jpeg")
|
||||||
|
except Exception:
|
||||||
|
self.logger.error(f"File {out_name}.bin failed image validation")
|
||||||
|
|
||||||
|
try:
|
||||||
|
remove(f"{out_name}.bin")
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.logger.error(f"Failed to delete {out_name}.bin, please remove it manually")
|
||||||
|
|
||||||
|
return {'returnCode': ret_code, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class Mai2DeliverConfig:
|
|||||||
@property
|
@property
|
||||||
def content_folder(self) -> int:
|
def content_folder(self) -> int:
|
||||||
return CoreConfig.get_config_field(
|
return CoreConfig.get_config_field(
|
||||||
self.__config, "mai2", "server", "content_folder", default=""
|
self.__config, "mai2", "deliver", "content_folder", default=""
|
||||||
)
|
)
|
||||||
|
|
||||||
class Mai2UploadsConfig:
|
class Mai2UploadsConfig:
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class Mai2DX(Mai2Base):
|
|||||||
|
|
||||||
if "userGhost" in upsert:
|
if "userGhost" in upsert:
|
||||||
for ghost in upsert["userGhost"]:
|
for ghost in upsert["userGhost"]:
|
||||||
self.data.profile.put_profile_extend(user_id, self.version, ghost)
|
self.data.profile.put_profile_ghost(user_id, self.version, ghost)
|
||||||
|
|
||||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||||
self.data.profile.put_profile_option(
|
self.data.profile.put_profile_option(
|
||||||
@@ -217,9 +217,6 @@ class Mai2DX(Mai2Base):
|
|||||||
|
|
||||||
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
|
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
|
||||||
|
|
||||||
def handle_user_logout_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
||||||
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||||
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
|
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
@@ -548,196 +545,47 @@ class Mai2DX(Mai2Base):
|
|||||||
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
||||||
|
|
||||||
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||||
songs = self.data.score.get_best_scores(data["userId"])
|
user_id = data.get("userId", 0)
|
||||||
|
next_index = data.get("nextIndex", 0)
|
||||||
|
max_ct = data.get("maxCount", 50)
|
||||||
|
upper_lim = next_index + max_ct
|
||||||
music_detail_list = []
|
music_detail_list = []
|
||||||
next_index = 0
|
|
||||||
|
|
||||||
if songs is not None:
|
if user_id <= 0:
|
||||||
for song in songs:
|
self.logger.warning("handle_get_user_music_api_request: Could not find userid in data, or userId is 0")
|
||||||
tmp = song._asdict()
|
return {}
|
||||||
|
|
||||||
|
songs = self.data.score.get_best_scores(user_id)
|
||||||
|
if songs is None:
|
||||||
|
self.logger.debug("handle_get_user_music_api_request: get_best_scores returned None!")
|
||||||
|
return {
|
||||||
|
"userId": data["userId"],
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userMusicList": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
num_user_songs = len(songs)
|
||||||
|
|
||||||
|
for x in range(next_index, upper_lim):
|
||||||
|
if num_user_songs <= x:
|
||||||
|
break
|
||||||
|
|
||||||
|
tmp = songs[x]._asdict()
|
||||||
tmp.pop("id")
|
tmp.pop("id")
|
||||||
tmp.pop("user")
|
tmp.pop("user")
|
||||||
music_detail_list.append(tmp)
|
music_detail_list.append(tmp)
|
||||||
|
|
||||||
if len(music_detail_list) == data["maxCount"]:
|
next_index = 0 if len(music_detail_list) < max_ct or num_user_songs == upper_lim else upper_lim
|
||||||
next_index = data["maxCount"] + data["nextIndex"]
|
self.logger.info(f"Send songs {next_index}-{upper_lim} ({len(music_detail_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})")
|
||||||
break
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"userId": data["userId"],
|
"userId": data["userId"],
|
||||||
"nextIndex": next_index,
|
"nextIndex": next_index,
|
||||||
"userMusicList": [{"userMusicDetailList": music_detail_list}],
|
"userMusicList": [{"userMusicDetailList": music_detail_list}],
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
def handle_user_login_api_request(self, data: Dict) -> Dict:
|
||||||
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
ret = super().handle_user_login_api_request(data)
|
||||||
if p is None:
|
if ret is None or not ret:
|
||||||
return {}
|
return ret
|
||||||
|
ret['loginId'] = ret.get('loginCount', 0)
|
||||||
return {
|
return ret
|
||||||
"userName": p["userName"],
|
|
||||||
"rating": p["playerRating"],
|
|
||||||
# hardcode lastDataVersion for CardMaker 1.34
|
|
||||||
"lastDataVersion": "1.20.00",
|
|
||||||
"isLogin": False,
|
|
||||||
"isExistSellingCard": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
|
|
||||||
# user already exists, because the preview checks that already
|
|
||||||
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
|
||||||
|
|
||||||
cards = self.data.card.get_user_cards(data["userId"])
|
|
||||||
if cards is None or len(cards) == 0:
|
|
||||||
# This should never happen
|
|
||||||
self.logger.error(
|
|
||||||
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# get the dict representation of the row so we can modify values
|
|
||||||
user_data = p._asdict()
|
|
||||||
|
|
||||||
# remove the values the game doesn't want
|
|
||||||
user_data.pop("id")
|
|
||||||
user_data.pop("user")
|
|
||||||
user_data.pop("version")
|
|
||||||
|
|
||||||
return {"userId": data["userId"], "userData": user_data}
|
|
||||||
|
|
||||||
def handle_cm_login_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
||||||
def handle_cm_logout_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
||||||
def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
selling_cards = self.data.static.get_enabled_cards(self.version)
|
|
||||||
if selling_cards is None:
|
|
||||||
return {"length": 0, "sellingCardList": []}
|
|
||||||
|
|
||||||
selling_card_list = []
|
|
||||||
for card in selling_cards:
|
|
||||||
tmp = card._asdict()
|
|
||||||
tmp.pop("id")
|
|
||||||
tmp.pop("version")
|
|
||||||
tmp.pop("cardName")
|
|
||||||
tmp.pop("enabled")
|
|
||||||
|
|
||||||
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
tmp["noticeStartDate"] = datetime.strftime(
|
|
||||||
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S"
|
|
||||||
)
|
|
||||||
tmp["noticeEndDate"] = datetime.strftime(
|
|
||||||
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
|
|
||||||
)
|
|
||||||
|
|
||||||
selling_card_list.append(tmp)
|
|
||||||
|
|
||||||
return {"length": len(selling_card_list), "sellingCardList": selling_card_list}
|
|
||||||
|
|
||||||
def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
user_cards = self.data.item.get_cards(data["userId"])
|
|
||||||
if user_cards is None:
|
|
||||||
return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []}
|
|
||||||
|
|
||||||
max_ct = data["maxCount"]
|
|
||||||
next_idx = data["nextIndex"]
|
|
||||||
start_idx = next_idx
|
|
||||||
end_idx = max_ct + start_idx
|
|
||||||
|
|
||||||
if len(user_cards[start_idx:]) > max_ct:
|
|
||||||
next_idx += max_ct
|
|
||||||
else:
|
|
||||||
next_idx = 0
|
|
||||||
|
|
||||||
card_list = []
|
|
||||||
for card in user_cards:
|
|
||||||
tmp = card._asdict()
|
|
||||||
tmp.pop("id")
|
|
||||||
tmp.pop("user")
|
|
||||||
|
|
||||||
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
|
||||||
card_list.append(tmp)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"length": len(card_list[start_idx:end_idx]),
|
|
||||||
"nextIndex": next_idx,
|
|
||||||
"userCardList": card_list[start_idx:end_idx],
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
|
|
||||||
super().handle_get_user_item_api_request(data)
|
|
||||||
|
|
||||||
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
|
||||||
characters = self.data.item.get_characters(data["userId"])
|
|
||||||
|
|
||||||
chara_list = []
|
|
||||||
for chara in characters:
|
|
||||||
chara_list.append(
|
|
||||||
{
|
|
||||||
"characterId": chara["characterId"],
|
|
||||||
# no clue why those values are even needed
|
|
||||||
"point": 0,
|
|
||||||
"count": 0,
|
|
||||||
"level": chara["level"],
|
|
||||||
"nextAwake": 0,
|
|
||||||
"nextAwakePercent": 0,
|
|
||||||
"favorite": False,
|
|
||||||
"awakening": chara["awakening"],
|
|
||||||
"useCount": chara["useCount"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"length": len(chara_list),
|
|
||||||
"userCharacterList": chara_list,
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"length": 0, "userPrintDetailList": []}
|
|
||||||
|
|
||||||
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
|
||||||
user_id = data["userId"]
|
|
||||||
upsert = data["userPrintDetail"]
|
|
||||||
|
|
||||||
# set a random card serial number
|
|
||||||
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
|
|
||||||
|
|
||||||
user_card = upsert["userCard"]
|
|
||||||
self.data.item.put_card(
|
|
||||||
user_id,
|
|
||||||
user_card["cardId"],
|
|
||||||
user_card["cardTypeId"],
|
|
||||||
user_card["charaId"],
|
|
||||||
user_card["mapId"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# properly format userPrintDetail for the database
|
|
||||||
upsert.pop("userCard")
|
|
||||||
upsert.pop("serialId")
|
|
||||||
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
|
||||||
|
|
||||||
self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"orderId": 0,
|
|
||||||
"serialId": serial_id,
|
|
||||||
"startDate": "2018-01-01 00:00:00",
|
|
||||||
"endDate": "2038-01-01 00:00:00",
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"orderId": 0,
|
|
||||||
"serialId": data["userPrintlog"]["serialId"],
|
|
||||||
}
|
|
||||||
|
|
||||||
def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.dx import Mai2DX
|
from titles.mai2.universeplus import Mai2UniversePlus
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
class Mai2Festival(Mai2DX):
|
class Mai2Festival(Mai2UniversePlus):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||||
@@ -14,7 +14,7 @@ class Mai2Festival(Mai2DX):
|
|||||||
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
user_data = super().handle_cm_get_user_preview_api_request(data)
|
user_data = super().handle_cm_get_user_preview_api_request(data)
|
||||||
|
|
||||||
# hardcode lastDataVersion for CardMaker 1.36
|
# hardcode lastDataVersion for CardMaker 1.35
|
||||||
user_data["lastDataVersion"] = "1.30.00"
|
user_data["lastDataVersion"] = "1.30.00"
|
||||||
return user_data
|
return user_data
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import string
|
|||||||
import logging, coloredlogs
|
import logging, coloredlogs
|
||||||
import zlib
|
import zlib
|
||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
from os import path
|
from os import path, mkdir
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
@@ -109,6 +109,19 @@ class Mai2Servlet:
|
|||||||
f"{core_cfg.title.hostname}",
|
f"{core_cfg.title.hostname}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
if self.game_cfg.uploads.photos and self.game_cfg.uploads.photos_dir and not path.exists(self.game_cfg.uploads.photos_dir):
|
||||||
|
try:
|
||||||
|
mkdir(self.game_cfg.uploads.photos_dir)
|
||||||
|
except Exception:
|
||||||
|
self.logger.error(f"Failed to make photo upload directory at {self.game_cfg.uploads.photos_dir}")
|
||||||
|
|
||||||
|
if self.game_cfg.uploads.movies and self.game_cfg.uploads.movies_dir and not path.exists(self.game_cfg.uploads.movies_dir):
|
||||||
|
try:
|
||||||
|
mkdir(self.game_cfg.uploads.movies_dir)
|
||||||
|
except Exception:
|
||||||
|
self.logger.error(f"Failed to make movie upload directory at {self.game_cfg.uploads.movies_dir}")
|
||||||
|
|
||||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
if url_path.lower() == "ping":
|
if url_path.lower() == "ping":
|
||||||
return zlib.compress(b'{"returnCode": "1"}')
|
return zlib.compress(b'{"returnCode": "1"}')
|
||||||
@@ -168,11 +181,14 @@ class Mai2Servlet:
|
|||||||
elif version >= 197: # Finale
|
elif version >= 197: # Finale
|
||||||
internal_ver = Mai2Constants.VER_MAIMAI_FINALE
|
internal_ver = Mai2Constants.VER_MAIMAI_FINALE
|
||||||
|
|
||||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
if request.getHeader('Mai-Encoding') is not None or request.getHeader('X-Mai-Encoding') is not None:
|
||||||
# If we get a 32 character long hex string, it's a hash and we're
|
# The has is some flavor of MD5 of the endpoint with a constant bolted onto the end of it.
|
||||||
# doing encrypted. The likelyhood of false positives is low but
|
# See cake.dll's Obfuscator function for details. Hopefully most DLL edits will remove
|
||||||
# technically not 0
|
# these two(?) headers to not cause issues, but given the general quality of SEGA data...
|
||||||
self.logger.error("Encryption not supported at this time")
|
enc_ver = request.getHeader('Mai-Encoding')
|
||||||
|
if enc_ver is None:
|
||||||
|
enc_ver = request.getHeader('X-Mai-Encoding')
|
||||||
|
self.logger.debug(f"Encryption v{enc_ver} - User-Agent: {request.getHeader('User-Agent')}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
unzip = zlib.decompress(req_raw)
|
unzip = zlib.decompress(req_raw)
|
||||||
@@ -212,13 +228,17 @@ class Mai2Servlet:
|
|||||||
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
||||||
|
|
||||||
def render_GET(self, request: Request, version: int, url_path: str) -> bytes:
|
def render_GET(self, request: Request, version: int, url_path: str) -> bytes:
|
||||||
self.logger.info(f"v{version} GET {url_path}")
|
self.logger.debug(f"v{version} GET {url_path}")
|
||||||
url_split = url_path.split("/")
|
url_split = url_path.split("/")
|
||||||
|
|
||||||
if (url_split[0] == "api" and url_split[1] == "movie") or url_split[0] == "movie":
|
if (url_split[0] == "api" and url_split[1] == "movie") or url_split[0] == "movie":
|
||||||
if url_split[2] == "moviestart":
|
if url_split[2] == "moviestart":
|
||||||
return json.dumps({"moviestart":{"status":"OK"}}).encode()
|
return json.dumps({"moviestart":{"status":"OK"}}).encode()
|
||||||
|
|
||||||
|
else:
|
||||||
|
request.setResponseCode(404)
|
||||||
|
return b""
|
||||||
|
|
||||||
if url_split[0] == "old":
|
if url_split[0] == "old":
|
||||||
if url_split[1] == "ping":
|
if url_split[1] == "ping":
|
||||||
self.logger.info(f"v{version} old server ping")
|
self.logger.info(f"v{version} old server ping")
|
||||||
@@ -232,17 +252,35 @@ class Mai2Servlet:
|
|||||||
self.logger.info(f"v{version} old server friend inquire")
|
self.logger.info(f"v{version} old server friend inquire")
|
||||||
return zlib.compress(b"{}")
|
return zlib.compress(b"{}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
request.setResponseCode(404)
|
||||||
|
return b""
|
||||||
|
|
||||||
elif url_split[0] == "usbdl":
|
elif url_split[0] == "usbdl":
|
||||||
if url_split[1] == "CONNECTIONTEST":
|
if url_split[1] == "CONNECTIONTEST":
|
||||||
self.logger.info(f"v{version} usbdl server test")
|
self.logger.info(f"v{version} usbdl server test")
|
||||||
return zlib.compress(b"ok")
|
return b""
|
||||||
|
|
||||||
|
elif self.game_cfg.deliver.udbdl_enable and path.exists(f"{self.game_cfg.deliver.content_folder}/usb/{url_split[-1]}"):
|
||||||
|
with open(f"{self.game_cfg.deliver.content_folder}/usb/{url_split[-1]}", 'rb') as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
else:
|
||||||
|
request.setResponseCode(404)
|
||||||
|
return b""
|
||||||
|
|
||||||
elif url_split[0] == "deliver":
|
elif url_split[0] == "deliver":
|
||||||
file = url_split[len(url_split) - 1]
|
file = url_split[len(url_split) - 1]
|
||||||
self.logger.info(f"v{version} {file} deliver inquire")
|
self.logger.info(f"v{version} {file} deliver inquire")
|
||||||
|
self.logger.debug(f"{self.game_cfg.deliver.content_folder}/net_deliver/{file}")
|
||||||
|
|
||||||
if not self.game_cfg.deliver.enable or not path.exists(f"{self.game_cfg.deliver.content_folder}/{file}"):
|
if self.game_cfg.deliver.enable and path.exists(f"{self.game_cfg.deliver.content_folder}/net_deliver/{file}"):
|
||||||
return zlib.compress(b"")
|
with open(f"{self.game_cfg.deliver.content_folder}/net_deliver/{file}", 'rb') as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
else:
|
||||||
|
request.setResponseCode(404)
|
||||||
|
return b""
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return zlib.compress(b"{}")
|
return zlib.compress(b"{}")
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class Mai2Reader(BaseReader):
|
|||||||
|
|
||||||
def load_table_raw(self, dir: str, file: str, key: Optional[bytes]) -> Optional[List[Dict[str, str]]]:
|
def load_table_raw(self, dir: str, file: str, key: Optional[bytes]) -> Optional[List[Dict[str, str]]]:
|
||||||
if not os.path.exists(f"{dir}/{file}"):
|
if not os.path.exists(f"{dir}/{file}"):
|
||||||
self.logger.warn(f"file {file} does not exist in directory {dir}, skipping")
|
self.logger.warning(f"file {file} does not exist in directory {dir}, skipping")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.info(f"Load table {file} from {dir}")
|
self.logger.info(f"Load table {file} from {dir}")
|
||||||
@@ -100,7 +100,7 @@ class Mai2Reader(BaseReader):
|
|||||||
f_data = f.read()[0x10:]
|
f_data = f.read()[0x10:]
|
||||||
|
|
||||||
if f_data is None or not f_data:
|
if f_data is None or not f_data:
|
||||||
self.logger.warn(f"file {dir} could not be read, skipping")
|
self.logger.warning(f"file {dir} could not be read, skipping")
|
||||||
return
|
return
|
||||||
|
|
||||||
f_data_deflate = zlib.decompress(f_data, wbits = zlib.MAX_WBITS | 16)[0x12:] # lop off the junk at the beginning
|
f_data_deflate = zlib.decompress(f_data, wbits = zlib.MAX_WBITS | 16)[0x12:] # lop off the junk at the beginning
|
||||||
@@ -127,13 +127,13 @@ class Mai2Reader(BaseReader):
|
|||||||
try:
|
try:
|
||||||
struct_def.append(x[x.rindex(" ") + 2: -1])
|
struct_def.append(x[x.rindex(" ") + 2: -1])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.logger.warn(f"rindex failed on line {x}")
|
self.logger.warning(f"rindex failed on line {x}")
|
||||||
|
|
||||||
if is_struct:
|
if is_struct:
|
||||||
self.logger.warn("Struct not formatted properly")
|
self.logger.warning("Struct not formatted properly")
|
||||||
|
|
||||||
if not struct_def:
|
if not struct_def:
|
||||||
self.logger.warn("Struct def not found")
|
self.logger.warning("Struct def not found")
|
||||||
|
|
||||||
name = file[:file.index(".")]
|
name = file[:file.index(".")]
|
||||||
if "_" in name:
|
if "_" in name:
|
||||||
@@ -148,7 +148,7 @@ class Mai2Reader(BaseReader):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if not line_match.group(1) == name.upper():
|
if not line_match.group(1) == name.upper():
|
||||||
self.logger.warn(f"Strange regex match for line {x} -> {line_match}")
|
self.logger.warning(f"Strange regex match for line {x} -> {line_match}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
vals = line_match.group(2)
|
vals = line_match.group(2)
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class Mai2ItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}"
|
f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -261,7 +261,7 @@ class Mai2ItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}"
|
f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -312,7 +312,7 @@ class Mai2ItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}"
|
f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -341,7 +341,7 @@ class Mai2ItemData(BaseData):
|
|||||||
conflict = sql.on_duplicate_key_update(**char_data)
|
conflict = sql.on_duplicate_key_update(**char_data)
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_character_: failed to insert item! user_id: {user_id}"
|
f"put_character_: failed to insert item! user_id: {user_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -371,7 +371,7 @@ class Mai2ItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}"
|
f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -414,7 +414,7 @@ class Mai2ItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_friend_season_ranking: failed to insert",
|
f"put_friend_season_ranking: failed to insert",
|
||||||
f"friend_season_ranking! aime_id: {aime_id}",
|
f"friend_season_ranking! aime_id: {aime_id}",
|
||||||
)
|
)
|
||||||
@@ -432,7 +432,7 @@ class Mai2ItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}"
|
f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -477,7 +477,7 @@ class Mai2ItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_card: failed to insert card! user_id: {user_id}, kind: {card_kind}"
|
f"put_card: failed to insert card! user_id: {user_id}, kind: {card_kind}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -516,7 +516,7 @@ class Mai2ItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_card: failed to insert charge! user_id: {user_id}, chargeId: {charge_id}"
|
f"put_card: failed to insert charge! user_id: {user_id}, chargeId: {charge_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -541,7 +541,7 @@ class Mai2ItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile: Failed to create profile! user_id {user_id} is_dx {is_dx}"
|
f"put_profile: Failed to create profile! user_id {user_id} is_dx {is_dx}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -525,7 +525,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_ghost: failed to update! {user_id}")
|
self.logger.warning(f"put_profile_ghost: failed to update! {user_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -552,7 +552,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_extend: failed to update! {user_id}")
|
self.logger.warning(f"put_profile_extend: failed to update! {user_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -582,7 +582,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_option: failed to update! {user_id} is_dx {is_dx}")
|
self.logger.warning(f"put_profile_option: failed to update! {user_id} is_dx {is_dx}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -616,7 +616,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_rating: failed to update! {user_id}")
|
self.logger.warning(f"put_profile_rating: failed to update! {user_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -643,7 +643,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_region: failed to update! {user_id}")
|
self.logger.warning(f"put_region: failed to update! {user_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -668,7 +668,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_activity: failed to update! user_id: {user_id}"
|
f"put_profile_activity: failed to update! user_id: {user_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -698,7 +698,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_web_option: failed to update! user_id: {user_id}"
|
f"put_web_option: failed to update! user_id: {user_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -720,7 +720,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_grade_status: failed to update! user_id: {user_id}"
|
f"put_grade_status: failed to update! user_id: {user_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -742,7 +742,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_boss_list: failed to update! user_id: {user_id}"
|
f"put_boss_list: failed to update! user_id: {user_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -763,7 +763,7 @@ class Mai2ProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_recent_rating: failed to update! user_id: {user_id}"
|
f"put_recent_rating: failed to update! user_id: {user_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class Mai2StaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert song {song_id} chart {chart_id}")
|
self.logger.warning(f"Failed to insert song {song_id} chart {chart_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ class Mai2StaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}")
|
self.logger.warning(f"Failed to insert charge {ticket_id} type {ticket_type}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ class Mai2StaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert card {card_id}")
|
self.logger.warning(f"Failed to insert card {card_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import pytz
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.dx import Mai2DX
|
from titles.mai2.dxplus import Mai2DXPlus
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
|
|
||||||
|
|
||||||
class Mai2Splash(Mai2DX):
|
class Mai2Splash(Mai2DXPlus):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import pytz
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.dx import Mai2DX
|
from titles.mai2.splash import Mai2Splash
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
|
|
||||||
|
|
||||||
class Mai2SplashPlus(Mai2DX):
|
class Mai2SplashPlus(Mai2Splash):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import pytz
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.dx import Mai2DX
|
from titles.mai2.splashplus import Mai2SplashPlus
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
class Mai2Universe(Mai2DX):
|
class Mai2Universe(Mai2SplashPlus):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||||
@@ -70,13 +70,13 @@ class Mai2Universe(Mai2DX):
|
|||||||
tmp.pop("cardName")
|
tmp.pop("cardName")
|
||||||
tmp.pop("enabled")
|
tmp.pop("enabled")
|
||||||
|
|
||||||
tmp["startDate"] = datetime.strftime(tmp["startDate"], "%Y-%m-%d %H:%M:%S")
|
tmp["startDate"] = datetime.strftime(tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT)
|
||||||
tmp["endDate"] = datetime.strftime(tmp["endDate"], "%Y-%m-%d %H:%M:%S")
|
tmp["endDate"] = datetime.strftime(tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT)
|
||||||
tmp["noticeStartDate"] = datetime.strftime(
|
tmp["noticeStartDate"] = datetime.strftime(
|
||||||
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S"
|
tmp["noticeStartDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
)
|
)
|
||||||
tmp["noticeEndDate"] = datetime.strftime(
|
tmp["noticeEndDate"] = datetime.strftime(
|
||||||
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S"
|
tmp["noticeEndDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
)
|
)
|
||||||
|
|
||||||
selling_card_list.append(tmp)
|
selling_card_list.append(tmp)
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.dx import Mai2DX
|
from titles.mai2.universe import Mai2Universe
|
||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.config import Mai2Config
|
from titles.mai2.config import Mai2Config
|
||||||
|
|
||||||
|
|
||||||
class Mai2UniversePlus(Mai2DX):
|
class Mai2UniversePlus(Mai2Universe):
|
||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_card: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_card: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -346,7 +346,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_character: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_character: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -366,7 +366,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_deck: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_deck: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -394,7 +394,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_boss: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_boss: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -406,7 +406,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_story: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_story: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -426,7 +426,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_chapter: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_chapter: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -446,7 +446,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_item: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_item: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -479,7 +479,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_music_item: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_music_item: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_login_bonus: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_login_bonus: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -521,7 +521,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_mission_point: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_mission_point: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -541,7 +541,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_event_point: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_event_point: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -561,7 +561,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_scenerio: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_scenerio: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -581,7 +581,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_trade_item: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_trade_item: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -601,7 +601,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_event_music: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_event_music: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -621,7 +621,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_tech_event: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_tech_event: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -651,7 +651,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_memorychapter: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_memorychapter: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -694,7 +694,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
|
self.logger.warning(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -709,7 +709,7 @@ class OngekiItemData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class OngekiLogData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_gp_log: Failed to insert GP log! aime_id: {aime_id} kind {kind} pattern {pattern} current_gp {current_gp}"
|
f"put_gp_log: Failed to insert GP log! aime_id: {aime_id} kind {kind} pattern {pattern} current_gp {current_gp}"
|
||||||
)
|
)
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ class OngekiProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_profile_data: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ class OngekiProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_options: Failed to update! aime_id: {aime_id}"
|
f"put_profile_options: Failed to update! aime_id: {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -393,7 +393,7 @@ class OngekiProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_recent_rating: failed to update recent rating! aime_id {aime_id}"
|
f"put_profile_recent_rating: failed to update recent rating! aime_id {aime_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -415,7 +415,7 @@ class OngekiProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_rating_log: failed to update rating log! aime_id {aime_id} data_version {data_version} highest_rating {highest_rating}"
|
f"put_profile_rating_log: failed to update rating log! aime_id {aime_id} data_version {data_version} highest_rating {highest_rating}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -449,7 +449,7 @@ class OngekiProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_activity: failed to put activity! aime_id {aime_id} kind {kind} activity_id {activity_id}"
|
f"put_profile_activity: failed to put activity! aime_id {aime_id} kind {kind} activity_id {activity_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -466,7 +466,7 @@ class OngekiProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_profile_region: failed to update! aime_id {aime_id} region {region}"
|
f"put_profile_region: failed to update! aime_id {aime_id} region {region}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -480,7 +480,7 @@ class OngekiProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_best_score: Failed to add score! aime_id: {aime_id}")
|
self.logger.warning(f"put_best_score: Failed to add score! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -492,7 +492,7 @@ class OngekiProfileData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_kop: Failed to add score! aime_id: {aime_id}")
|
self.logger.warning(f"put_kop: Failed to add score! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ class OngekiProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_rival: failed to update! aime_id: {aime_id}, rival_id: {rival_id}"
|
f"put_rival: failed to update! aime_id: {aime_id}, rival_id: {rival_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class OngekiScoreData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_tech_count: Failed to update! aime_id: {aime_id}")
|
self.logger.warning(f"put_tech_count: Failed to update! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ class OngekiScoreData(BaseData):
|
|||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_best_score: Failed to add score! aime_id: {aime_id}")
|
self.logger.warning(f"put_best_score: Failed to add score! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -175,6 +175,6 @@ class OngekiScoreData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"put_playlog: Failed to add playlog! aime_id: {aime_id}")
|
self.logger.warning(f"put_playlog: Failed to add playlog! aime_id: {aime_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class OngekiStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert card! card_id {card_id}")
|
self.logger.warning(f"Failed to insert card! card_id {card_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ class OngekiStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert gacha! gacha_id {gacha_id}")
|
self.logger.warning(f"Failed to insert gacha! gacha_id {gacha_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ class OngekiStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert gacha card! gacha_id {gacha_id}")
|
self.logger.warning(f"Failed to insert gacha card! gacha_id {gacha_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -243,7 +243,7 @@ class OngekiStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert event! event_id {event_id}")
|
self.logger.warning(f"Failed to insert event! event_id {event_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -304,7 +304,7 @@ class OngekiStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Failed to insert chart! song_id: {song_id}, chart_id: {chart_id}"
|
f"Failed to insert chart! song_id: {song_id}, chart_id: {chart_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class PokkenConstants:
|
|||||||
AI = 2
|
AI = 2
|
||||||
LAN = 3
|
LAN = 3
|
||||||
WAN = 4
|
WAN = 4
|
||||||
|
TUTORIAL_3 = 7
|
||||||
|
|
||||||
class BATTLE_RESULT(Enum):
|
class BATTLE_RESULT(Enum):
|
||||||
WIN = 1
|
WIN = 1
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class PokkenFrontend(FE_Base):
|
|||||||
self.game_cfg.update(
|
self.game_cfg.update(
|
||||||
yaml.safe_load(open(f"{cfg_dir}/{PokkenConstants.CONFIG_NAME}"))
|
yaml.safe_load(open(f"{cfg_dir}/{PokkenConstants.CONFIG_NAME}"))
|
||||||
)
|
)
|
||||||
self.nav_name = "Pokken"
|
self.nav_name = "Pokkén"
|
||||||
|
|
||||||
def render_GET(self, request: Request) -> bytes:
|
def render_GET(self, request: Request) -> bytes:
|
||||||
template = self.environment.get_template(
|
template = self.environment.get_template(
|
||||||
@@ -35,5 +35,6 @@ class PokkenFrontend(FE_Base):
|
|||||||
return template.render(
|
return template.render(
|
||||||
title=f"{self.core_config.server.name} | {self.nav_name}",
|
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||||||
game_list=self.environment.globals["game_list"],
|
game_list=self.environment.globals["game_list"],
|
||||||
sesh=vars(usr_sesh)
|
sesh=vars(usr_sesh),
|
||||||
|
active_page="pokken",
|
||||||
).encode("utf-16")
|
).encode("utf-16")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends "core/frontend/index.jinja" %}
|
{% extends "core/frontend/index.jinja" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Pokken</h1>
|
<h1>Pokkén</h1>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -112,7 +112,7 @@ class PokkenServlet(resource.Resource):
|
|||||||
try:
|
try:
|
||||||
pokken_request.ParseFromString(content)
|
pokken_request.ParseFromString(content)
|
||||||
except DecodeError as e:
|
except DecodeError as e:
|
||||||
self.logger.warn(f"{e} {content}")
|
self.logger.warning(f"{e} {content}")
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
endpoint = jackal_pb2.MessageType.DESCRIPTOR.values_by_number[
|
endpoint = jackal_pb2.MessageType.DESCRIPTOR.values_by_number[
|
||||||
@@ -123,7 +123,7 @@ class PokkenServlet(resource.Resource):
|
|||||||
|
|
||||||
handler = getattr(self.base, f"handle_{endpoint}", None)
|
handler = getattr(self.base, f"handle_{endpoint}", None)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
self.logger.warn(f"No handler found for message type {endpoint}")
|
self.logger.warning(f"No handler found for message type {endpoint}")
|
||||||
return self.base.handle_noop(pokken_request)
|
return self.base.handle_noop(pokken_request)
|
||||||
|
|
||||||
self.logger.info(f"{endpoint} request from {Utils.get_ip_addr(request)}")
|
self.logger.info(f"{endpoint} request from {Utils.get_ip_addr(request)}")
|
||||||
@@ -157,7 +157,7 @@ class PokkenServlet(resource.Resource):
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"No handler found for message type {json_content['call']}"
|
f"No handler found for message type {json_content['call']}"
|
||||||
)
|
)
|
||||||
return json.dumps(self.base.handle_matching_noop()).encode()
|
return json.dumps(self.base.handle_matching_noop()).encode()
|
||||||
|
|||||||
@@ -39,8 +39,12 @@ class PokkenItemData(BaseData):
|
|||||||
type=item_type,
|
type=item_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(sql)
|
conflict = sql.on_duplicate_key_update(
|
||||||
|
content=content,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert reward for user {user_id}: {category}-{content}-{item_type}")
|
self.logger.warning(f"Failed to insert reward for user {user_id}: {category}-{content}-{item_type}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ class PokkenProfileData(BaseData):
|
|||||||
illustration_book_no=illust_no,
|
illustration_book_no=illust_no,
|
||||||
bp_point_atk=atk,
|
bp_point_atk=atk,
|
||||||
bp_point_res=res,
|
bp_point_res=res,
|
||||||
bp_point_defe=defe,
|
bp_point_def=defe,
|
||||||
bp_point_sp=sp,
|
bp_point_sp=sp,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -267,13 +267,13 @@ class PokkenProfileData(BaseData):
|
|||||||
illustration_book_no=illust_no,
|
illustration_book_no=illust_no,
|
||||||
bp_point_atk=atk,
|
bp_point_atk=atk,
|
||||||
bp_point_res=res,
|
bp_point_res=res,
|
||||||
bp_point_defe=defe,
|
bp_point_def=defe,
|
||||||
bp_point_sp=sp,
|
bp_point_sp=sp,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert pokemon ID {pokemon_id} for user {user_id}")
|
self.logger.warning(f"Failed to insert pokemon ID {pokemon_id} for user {user_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ class PokkenProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to add {xp} XP to pokemon ID {pokemon_id} for user {user_id}")
|
self.logger.warning(f"Failed to add {xp} XP to pokemon ID {pokemon_id} for user {user_id}")
|
||||||
|
|
||||||
def get_pokemon_data(self, user_id: int, pokemon_id: int) -> Optional[Row]:
|
def get_pokemon_data(self, user_id: int, pokemon_id: int) -> Optional[Row]:
|
||||||
pass
|
pass
|
||||||
@@ -319,7 +319,7 @@ class PokkenProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to record match stats for user {user_id}'s pokemon {pokemon_id} (type {match_type.name} | result {match_result.name})")
|
self.logger.warning(f"Failed to record match stats for user {user_id}'s pokemon {pokemon_id} (type {match_type.name} | result {match_result.name})")
|
||||||
|
|
||||||
def put_stats(
|
def put_stats(
|
||||||
self,
|
self,
|
||||||
@@ -345,9 +345,13 @@ class PokkenProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to update stats for user {user_id}")
|
self.logger.warning(f"Failed to update stats for user {user_id}")
|
||||||
|
|
||||||
def update_support_team(self, user_id: int, support_id: int, support1: int = 4294967295, support2: int = 4294967295) -> None:
|
def update_support_team(self, user_id: int, support_id: int, support1: int = None, support2: int = None) -> None:
|
||||||
|
if support1 == 4294967295:
|
||||||
|
support1 = None
|
||||||
|
if support2 == 4294967295:
|
||||||
|
support2 = None
|
||||||
sql = update(profile).where(profile.c.user==user_id).values(
|
sql = update(profile).where(profile.c.user==user_id).values(
|
||||||
support_set_1_1=support1 if support_id == 1 else profile.c.support_set_1_1,
|
support_set_1_1=support1 if support_id == 1 else profile.c.support_set_1_1,
|
||||||
support_set_1_2=support2 if support_id == 1 else profile.c.support_set_1_2,
|
support_set_1_2=support2 if support_id == 1 else profile.c.support_set_1_2,
|
||||||
@@ -359,4 +363,4 @@ class PokkenProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to update support team {support_id} for user {user_id}")
|
self.logger.warning(f"Failed to update support team {support_id} for user {user_id}")
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class SaoReader(BaseReader):
|
|||||||
pull_bin_ram = True
|
pull_bin_ram = True
|
||||||
|
|
||||||
if not path.exists(f"{self.bin_dir}"):
|
if not path.exists(f"{self.bin_dir}"):
|
||||||
self.logger.warn(f"Couldn't find csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't find csv file in {self.bin_dir}, skipping")
|
||||||
pull_bin_ram = False
|
pull_bin_ram = False
|
||||||
|
|
||||||
if pull_bin_ram:
|
if pull_bin_ram:
|
||||||
@@ -65,8 +65,8 @@ class SaoReader(BaseReader):
|
|||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(err)
|
print(err)
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|
||||||
self.logger.info("Now reading HeroLog.csv")
|
self.logger.info("Now reading HeroLog.csv")
|
||||||
try:
|
try:
|
||||||
@@ -99,8 +99,8 @@ class SaoReader(BaseReader):
|
|||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(err)
|
print(err)
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|
||||||
self.logger.info("Now reading Equipment.csv")
|
self.logger.info("Now reading Equipment.csv")
|
||||||
try:
|
try:
|
||||||
@@ -131,8 +131,8 @@ class SaoReader(BaseReader):
|
|||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(err)
|
print(err)
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|
||||||
self.logger.info("Now reading Item.csv")
|
self.logger.info("Now reading Item.csv")
|
||||||
try:
|
try:
|
||||||
@@ -161,8 +161,8 @@ class SaoReader(BaseReader):
|
|||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(err)
|
print(err)
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|
||||||
self.logger.info("Now reading SupportLog.csv")
|
self.logger.info("Now reading SupportLog.csv")
|
||||||
try:
|
try:
|
||||||
@@ -193,8 +193,8 @@ class SaoReader(BaseReader):
|
|||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(err)
|
print(err)
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|
||||||
self.logger.info("Now reading Title.csv")
|
self.logger.info("Now reading Title.csv")
|
||||||
try:
|
try:
|
||||||
@@ -226,8 +226,8 @@ class SaoReader(BaseReader):
|
|||||||
print(err)
|
print(err)
|
||||||
elif len(titleId) < 6: # current server code cannot have multiple lengths for the id
|
elif len(titleId) < 6: # current server code cannot have multiple lengths for the id
|
||||||
continue
|
continue
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|
||||||
self.logger.info("Now reading RareDropTable.csv")
|
self.logger.info("Now reading RareDropTable.csv")
|
||||||
try:
|
try:
|
||||||
@@ -250,5 +250,5 @@ class SaoReader(BaseReader):
|
|||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(err)
|
print(err)
|
||||||
except:
|
except Exception:
|
||||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
self.logger.warning(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ class WaccaBase:
|
|||||||
else:
|
else:
|
||||||
profile = self.data.profile.get_profile(req.userId)
|
profile = self.data.profile.get_profile(req.userId)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Unknown user id {req.userId} attempted login from {req.chipId}"
|
f"Unknown user id {req.userId} attempted login from {req.chipId}"
|
||||||
)
|
)
|
||||||
return resp.make()
|
return resp.make()
|
||||||
@@ -282,7 +282,7 @@ class WaccaBase:
|
|||||||
|
|
||||||
profile = self.data.profile.get_profile(req.userId)
|
profile = self.data.profile.get_profile(req.userId)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
self.logger.warn(f"Unknown profile {req.userId}")
|
self.logger.warning(f"Unknown profile {req.userId}")
|
||||||
return resp.make()
|
return resp.make()
|
||||||
|
|
||||||
self.logger.info(f"Get detail for profile {req.userId}")
|
self.logger.info(f"Get detail for profile {req.userId}")
|
||||||
@@ -426,7 +426,7 @@ class WaccaBase:
|
|||||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
||||||
resp.userItems.noteSounds.append(itm_send)
|
resp.userItems.noteSounds.append(itm_send)
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}"
|
f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}"
|
||||||
)
|
)
|
||||||
@@ -709,7 +709,7 @@ class WaccaBase:
|
|||||||
profile = self.data.profile.get_profile(req.profileId)
|
profile = self.data.profile.get_profile(req.profileId)
|
||||||
|
|
||||||
if profile is None:
|
if profile is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"handle_user_music_update_request: No profile for game_id {req.profileId}"
|
f"handle_user_music_update_request: No profile for game_id {req.profileId}"
|
||||||
)
|
)
|
||||||
return resp.make()
|
return resp.make()
|
||||||
@@ -1003,7 +1003,7 @@ class WaccaBase:
|
|||||||
|
|
||||||
profile = self.data.profile.get_profile(req.profileId)
|
profile = self.data.profile.get_profile(req.profileId)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"handle_user_vip_get_request no profile with ID {req.profileId}"
|
f"handle_user_vip_get_request no profile with ID {req.profileId}"
|
||||||
)
|
)
|
||||||
return BaseResponse().make()
|
return BaseResponse().make()
|
||||||
|
|||||||
@@ -221,5 +221,5 @@ class WaccaConstants:
|
|||||||
cls.Region.YAMANASHI,
|
cls.Region.YAMANASHI,
|
||||||
cls.Region.WAKAYAMA,
|
cls.Region.WAKAYAMA,
|
||||||
][region]
|
][region]
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class WaccaFrontend(FE_Base):
|
|||||||
self.game_cfg.update(
|
self.game_cfg.update(
|
||||||
yaml.safe_load(open(f"{cfg_dir}/{WaccaConstants.CONFIG_NAME}"))
|
yaml.safe_load(open(f"{cfg_dir}/{WaccaConstants.CONFIG_NAME}"))
|
||||||
)
|
)
|
||||||
self.nav_name = "Wacca"
|
self.nav_name = "WACCA"
|
||||||
|
|
||||||
def render_GET(self, request: Request) -> bytes:
|
def render_GET(self, request: Request) -> bytes:
|
||||||
template = self.environment.get_template(
|
template = self.environment.get_template(
|
||||||
@@ -34,5 +34,6 @@ class WaccaFrontend(FE_Base):
|
|||||||
return template.render(
|
return template.render(
|
||||||
title=f"{self.core_config.server.name} | {self.nav_name}",
|
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||||||
game_list=self.environment.globals["game_list"],
|
game_list=self.environment.globals["game_list"],
|
||||||
sesh=vars(usr_sesh)
|
sesh=vars(usr_sesh),
|
||||||
|
active_page="wacca",
|
||||||
).encode("utf-16")
|
).encode("utf-16")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends "core/frontend/index.jinja" %}
|
{% extends "core/frontend/index.jinja" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Wacca</h1>
|
<h1>WACCA</h1>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
@@ -93,7 +93,7 @@ class WaccaServlet:
|
|||||||
try:
|
try:
|
||||||
req_json = json.loads(request.content.getvalue())
|
req_json = json.loads(request.content.getvalue())
|
||||||
version_full = Version(req_json["appVersion"])
|
version_full = Version(req_json["appVersion"])
|
||||||
except:
|
except Exception:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"Failed to parse request to {url_path} -> {request.content.getvalue()}"
|
f"Failed to parse request to {url_path} -> {request.content.getvalue()}"
|
||||||
)
|
)
|
||||||
@@ -146,7 +146,7 @@ class WaccaServlet:
|
|||||||
self.logger.debug(req_json)
|
self.logger.debug(req_json)
|
||||||
|
|
||||||
if not hasattr(self.versions[internal_ver], func_to_find):
|
if not hasattr(self.versions[internal_ver], func_to_find):
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"{req_json['appVersion']} has no handler for {func_to_find}"
|
f"{req_json['appVersion']} has no handler for {func_to_find}"
|
||||||
)
|
)
|
||||||
resp = BaseResponse().make()
|
resp = BaseResponse().make()
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class WaccaLily(WaccaS):
|
|||||||
else:
|
else:
|
||||||
profile = self.data.profile.get_profile(req.userId)
|
profile = self.data.profile.get_profile(req.userId)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Unknown user id {req.userId} attempted login from {req.chipId}"
|
f"Unknown user id {req.userId} attempted login from {req.chipId}"
|
||||||
)
|
)
|
||||||
return resp.make()
|
return resp.make()
|
||||||
@@ -198,7 +198,7 @@ class WaccaLily(WaccaS):
|
|||||||
|
|
||||||
profile = self.data.profile.get_profile(req.userId)
|
profile = self.data.profile.get_profile(req.userId)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
self.logger.warn(f"Unknown profile {req.userId}")
|
self.logger.warning(f"Unknown profile {req.userId}")
|
||||||
return resp.make()
|
return resp.make()
|
||||||
|
|
||||||
self.logger.info(f"Get detail for profile {req.userId}")
|
self.logger.info(f"Get detail for profile {req.userId}")
|
||||||
@@ -424,7 +424,7 @@ class WaccaLily(WaccaS):
|
|||||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
||||||
resp.userItems.noteSounds.append(itm_send)
|
resp.userItems.noteSounds.append(itm_send)
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}"
|
f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class WaccaReader(BaseReader):
|
|||||||
|
|
||||||
def read_music(self, base_dir: str, table: str) -> None:
|
def read_music(self, base_dir: str, table: str) -> None:
|
||||||
if not self.check_valid_pair(base_dir, table):
|
if not self.check_valid_pair(base_dir, table):
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"Cannot find {table} uasset/uexp pair at {base_dir}, music will not be read"
|
f"Cannot find {table} uasset/uexp pair at {base_dir}, music will not be read"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class WaccaReverse(WaccaLilyR):
|
|||||||
|
|
||||||
profile = self.data.profile.get_profile(req.userId)
|
profile = self.data.profile.get_profile(req.userId)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
self.logger.warn(f"Unknown profile {req.userId}")
|
self.logger.warning(f"Unknown profile {req.userId}")
|
||||||
return resp.make()
|
return resp.make()
|
||||||
|
|
||||||
self.logger.info(f"Get detail for profile {req.userId}")
|
self.logger.info(f"Get detail for profile {req.userId}")
|
||||||
@@ -289,7 +289,7 @@ class WaccaReverse(WaccaLilyR):
|
|||||||
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
elif item["type"] == WaccaConstants.ITEM_TYPES["note_sound"]:
|
||||||
resp.userItems.noteSounds.append(itm_send)
|
resp.userItems.noteSounds.append(itm_send)
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}"
|
f"{__name__} Failed to load item {item['item_id']} for user {profile['user']}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class WaccaItemData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to delete ticket id {id}")
|
self.logger.warning(f"Failed to delete ticket id {id}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_trophies(self, user_id: int, season: int = None) -> Optional[List[Row]]:
|
def get_trophies(self, user_id: int, season: int = None) -> Optional[List[Row]]:
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ class WaccaProfileData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(sql)
|
result = self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"update_profile_dan: Failed to update! profile {profile_id}"
|
f"update_profile_dan: Failed to update! profile {profile_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ class WaccaScoreData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(
|
self.logger.warning(
|
||||||
f"put_stageup: failed to update! user_id: {user_id} version: {version} stage_id: {stage_id}"
|
f"put_stageup: failed to update! user_id: {user_id} version: {version} stage_id: {stage_id}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class WaccaStaticData(BaseData):
|
|||||||
|
|
||||||
result = self.execute(conflict)
|
result = self.execute(conflict)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}")
|
self.logger.warning(f"Failed to insert music {song_id} chart {chart_id}")
|
||||||
return None
|
return None
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user