From 97aeba20e5aab5fabe6bb76ce1b13baffaebea60 Mon Sep 17 00:00:00 2001 From: Raymonf Date: Wed, 1 Mar 2023 16:08:36 -0500 Subject: [PATCH 01/37] chuni: add missing columns for course mode --- core/data/schema/versions/SDHD_2_rollback.sql | 1 + core/data/schema/versions/SDHD_3_upgrade.sql | 1 + titles/chuni/schema/score.py | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 core/data/schema/versions/SDHD_2_rollback.sql create mode 100644 core/data/schema/versions/SDHD_3_upgrade.sql diff --git a/core/data/schema/versions/SDHD_2_rollback.sql b/core/data/schema/versions/SDHD_2_rollback.sql new file mode 100644 index 0000000..de11416 --- /dev/null +++ b/core/data/schema/versions/SDHD_2_rollback.sql @@ -0,0 +1 @@ +ALTER TABLE chuni_score_course DROP COLUMN theoryCount, DROP COLUMN orderId, DROP COLUMN playerRating; diff --git a/core/data/schema/versions/SDHD_3_upgrade.sql b/core/data/schema/versions/SDHD_3_upgrade.sql new file mode 100644 index 0000000..57498cc --- /dev/null +++ b/core/data/schema/versions/SDHD_3_upgrade.sql @@ -0,0 +1 @@ +ALTER TABLE chuni_score_course ADD theoryCount int(11), ADD orderId int(11), ADD playerRating int(11); diff --git a/titles/chuni/schema/score.py b/titles/chuni/schema/score.py index 353401f..81f3212 100644 --- a/titles/chuni/schema/score.py +++ b/titles/chuni/schema/score.py @@ -29,6 +29,9 @@ course = Table( Column("param3", Integer), Column("param4", Integer), Column("isClear", Boolean), + Column("theoryCount", Integer), + Column("orderId", Integer), + Column("playerRating", Integer), UniqueConstraint("user", "courseId", name="chuni_score_course_uk"), mysql_charset='utf8mb4' ) From b0bf151c9f868d3fca2eebc3ea43c8dd7941436e Mon Sep 17 00:00:00 2001 From: Raymonf Date: Wed, 1 Mar 2023 20:04:34 -0500 Subject: [PATCH 02/37] chuni: SDHD -> SDBT for upgrade scripts --- .../schema/versions/{SDHD_1_rollback.sql => SDBT_1_rollback.sql} | 0 .../schema/versions/{SDHD_2_rollback.sql => SDBT_2_rollback.sql} | 0 .../schema/versions/{SDHD_2_upgrade.sql => SDBT_2_upgrade.sql} | 0 .../schema/versions/{SDHD_3_upgrade.sql => SDBT_3_upgrade.sql} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename core/data/schema/versions/{SDHD_1_rollback.sql => SDBT_1_rollback.sql} (100%) rename core/data/schema/versions/{SDHD_2_rollback.sql => SDBT_2_rollback.sql} (100%) rename core/data/schema/versions/{SDHD_2_upgrade.sql => SDBT_2_upgrade.sql} (100%) rename core/data/schema/versions/{SDHD_3_upgrade.sql => SDBT_3_upgrade.sql} (100%) diff --git a/core/data/schema/versions/SDHD_1_rollback.sql b/core/data/schema/versions/SDBT_1_rollback.sql similarity index 100% rename from core/data/schema/versions/SDHD_1_rollback.sql rename to core/data/schema/versions/SDBT_1_rollback.sql diff --git a/core/data/schema/versions/SDHD_2_rollback.sql b/core/data/schema/versions/SDBT_2_rollback.sql similarity index 100% rename from core/data/schema/versions/SDHD_2_rollback.sql rename to core/data/schema/versions/SDBT_2_rollback.sql diff --git a/core/data/schema/versions/SDHD_2_upgrade.sql b/core/data/schema/versions/SDBT_2_upgrade.sql similarity index 100% rename from core/data/schema/versions/SDHD_2_upgrade.sql rename to core/data/schema/versions/SDBT_2_upgrade.sql diff --git a/core/data/schema/versions/SDHD_3_upgrade.sql b/core/data/schema/versions/SDBT_3_upgrade.sql similarity index 100% rename from core/data/schema/versions/SDHD_3_upgrade.sql rename to core/data/schema/versions/SDBT_3_upgrade.sql From 88f6eba30bf1c86f53623cfbe0c28b98ed20a0da Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Wed, 1 Mar 2023 21:48:43 -0500 Subject: [PATCH 03/37] wacca: add region IDs and version helper classes --- titles/wacca/base.py | 30 ++-------- titles/wacca/const.py | 64 +++++++++++++++++--- titles/wacca/handlers/base.py | 3 +- titles/wacca/handlers/helpers.py | 88 ++++++++++++++++++++++++++++ titles/wacca/handlers/user_status.py | 4 +- titles/wacca/index.py | 7 +-- titles/wacca/lily.py | 35 +++-------- 7 files changed, 165 insertions(+), 66 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 5bb83ae..535048f 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -110,7 +110,6 @@ class WaccaBase(): def handle_user_status_get_request(self, data: Dict)-> Dict: req = UserStatusGetRequest(data) resp = UserStatusGetV1Response() - ver_split = req.appVersion.split(".") profile = self.data.profile.get_profile(aime_id=req.aimeId) if profile is None: @@ -118,14 +117,11 @@ class WaccaBase(): resp.profileStatus = ProfileStatus.ProfileRegister return resp.make() - self.logger.info(f"User preview for {req.aimeId} from {req.chipId}") if profile["last_game_ver"] is None: - profile_ver_split = ver_split - resp.lastGameVersion = req.appVersion + resp.lastGameVersion = ShortVersion(str(req.appVersion)) else: - profile_ver_split = profile["last_game_ver"].split(".") - resp.lastGameVersion = profile["last_game_ver"] + resp.lastGameVersion = ShortVersion(profile["last_game_ver"]) resp.userStatus.userId = profile["id"] resp.userStatus.username = profile["username"] @@ -145,27 +141,11 @@ class WaccaBase(): set_icon_id = self.OPTIONS_DEFAULTS["set_icon_id"] resp.setIconId = set_icon_id - - if int(ver_split[0]) > int(profile_ver_split[0]): + if req.appVersion > resp.lastGameVersion: resp.versionStatus = PlayVersionStatus.VersionUpgrade - - elif int(ver_split[0]) < int(profile_ver_split[0]): - resp.versionStatus = PlayVersionStatus.VersionTooNew - else: - if int(ver_split[1]) > int(profile_ver_split[1]): - resp.versionStatus = PlayVersionStatus.VersionUpgrade - - elif int(ver_split[1]) < int(profile_ver_split[1]): - resp.versionStatus = PlayVersionStatus.VersionTooNew - - else: - if int(ver_split[2]) > int(profile_ver_split[2]): - resp.versionStatus = PlayVersionStatus.VersionUpgrade - - - elif int(ver_split[2]) < int(profile_ver_split[2]): - resp.versionStatus = PlayVersionStatus.VersionTooNew + elif req.appVersion < resp.lastGameVersion: + resp.versionStatus = PlayVersionStatus.VersionTooNew return resp.make() diff --git a/titles/wacca/const.py b/titles/wacca/const.py index 910f313..0590f5f 100644 --- a/titles/wacca/const.py +++ b/titles/wacca/const.py @@ -95,18 +95,68 @@ class WaccaConstants(): "set_plate_id": 1005, # ID } - DIFFICULTIES = { - "Normal": 1, - "Hard": 2, - "Expert": 3, - "Inferno": 4, - } - class Difficulty(Enum): NORMAL = 1 HARD = 2 EXPERT = 3 INFERNO = 4 + + class Region(Enum): + NONE = 0 + HOKKAIDO = 1 + AOMORI = 2 + IWATE = 3 + MIYAGI = 4 + AKITA = 5 + YAMAGATA = 6 + FUKUSHIMA = 7 + IBARAKI = 8 + TOCHIGI = 9 + GUNMA = 10 + SAITAMA = 11 + CHIBA = 12 + TOKYO = 13 + KANAGAWA = 14 + NIIGATA = 15 + TOYAMA = 16 + ISHIKAWA = 17 + FUKUI = 18 + YAMANASHI = 19 + NAGANO = 20 + GIFU = 21 + SHIZUOKA = 22 + AICHI = 23 + MIE = 24 + SHIGA = 25 + KYOTO = 26 + OSAKA = 27 + HYOGO = 28 + NARA = 29 + WAKAYAMA = 30 + TOTTORI = 31 + SHIMANE = 32 + OKAYAMA = 33 + HIROSHIMA = 34 + YAMAGUCHI = 35 + TOKUSHIMA = 36 + KAGAWA = 37 + EHIME = 38 + KOCHI = 39 + FUKUOKA = 40 + SAGA = 41 + NAGASAKI = 42 + KUMAMOTO = 43 + OITA = 44 + MIYAZAKI = 45 + KAGOSHIMA = 46 + OKINAWA = 47 + UNITED_STATES = 48 + TAIWAN = 49 + HONG_KONG = 50 + SINGAPORE = 51 + KOREA = 52 + + VALID_COUNTRIES = set(["JPN", "USA", "KOR", "HKG", "SGP"]) @classmethod def game_ver_to_string(cls, ver: int): diff --git a/titles/wacca/handlers/base.py b/titles/wacca/handlers/base.py index 1e1197b..d7a2fb2 100644 --- a/titles/wacca/handlers/base.py +++ b/titles/wacca/handlers/base.py @@ -1,10 +1,11 @@ from typing import Dict, List +from titles.wacca.handlers.helpers import Version from datetime import datetime class BaseRequest(): def __init__(self, data: Dict) -> None: self.requestNo: int = data["requestNo"] - self.appVersion: str = data["appVersion"] + self.appVersion: Version = Version(data["appVersion"]) self.boardId: str = data["boardId"] self.chipId: str = data["chipId"] self.params: List = data["params"] diff --git a/titles/wacca/handlers/helpers.py b/titles/wacca/handlers/helpers.py index f476216..752a656 100644 --- a/titles/wacca/handlers/helpers.py +++ b/titles/wacca/handlers/helpers.py @@ -3,6 +3,94 @@ from enum import Enum from titles.wacca.const import WaccaConstants +class ShortVersion: + def __init__(self, version: str = "", major = 1, minor = 0, patch = 0) -> None: + split = version.split(".") + if len(split) >= 3: + self.major = int(split[0]) + self.minor = int(split[1]) + self.patch = int(split[2]) + + else: + self.major = major + self.minor = minor + self.patch = patch + + def __str__(self) -> str: + return f"{self.major}.{self.minor}.{self.patch}" + + def __int__(self) -> int: + return (self.major * 10000) + (self.minor * 100) + self.patch + + def __eq__(self, other: "ShortVersion"): + return self.major == other.major and self.minor == other.minor and self.patch == other.patch + + def __gt__(self, other: "ShortVersion"): + if self.major > other.major: + return True + elif self.major == other.major: + if self.minor > other.minor: + return True + elif self.minor == other.minor: + if self.patch > other.patch: + return True + + return False + + def __ge__(self, other: "ShortVersion"): + if self.major > other.major: + return True + elif self.major == other.major: + if self.minor > other.minor: + return True + elif self.minor == other.minor: + if self.patch > other.patch or self.patch == other.patch: + return True + + return False + + def __lt__(self, other: "ShortVersion"): + if self.major < other.major: + return True + elif self.major == other.major: + if self.minor < other.minor: + return True + elif self.minor == other.minor: + if self.patch < other.patch: + return True + + return False + + def __le__(self, other: "ShortVersion"): + if self.major < other.major: + return True + elif self.major == other.major: + if self.minor < other.minor: + return True + elif self.minor == other.minor: + if self.patch < other.patch or self.patch == other.patch: + return True + + return False + +class Version(ShortVersion): + def __init__(self, version = "", major = 1, minor = 0, patch = 0, country = "JPN", build = 0, role = "C") -> None: + super().__init__(version, major, minor, patch) + split = version.split(".") + if len(split) >= 6: + self.country = split[3] + self.build = int(split[4]) + self.role = split[5] + + else: + self.country = country + self.build = build + self.role = role + + def __str__(self) -> str: + return f"{self.major}.{self.minor}.{self.patch}.{self.country}.{self.role}.{self.build}" + + class HousingInfo(): """ 1 is lan install role, 2 is country diff --git a/titles/wacca/handlers/user_status.py b/titles/wacca/handlers/user_status.py index 103f878..b852dd1 100644 --- a/titles/wacca/handlers/user_status.py +++ b/titles/wacca/handlers/user_status.py @@ -19,7 +19,7 @@ class UserStatusGetV1Response(BaseResponse): self.setIconId: int = 0 self.profileStatus: ProfileStatus = ProfileStatus.ProfileGood self.versionStatus: PlayVersionStatus = PlayVersionStatus.VersionGood - self.lastGameVersion: str = "" + self.lastGameVersion: ShortVersion = ShortVersion() def make(self) -> Dict: self.params = [ @@ -29,7 +29,7 @@ class UserStatusGetV1Response(BaseResponse): self.profileStatus.value, [ self.versionStatus.value, - self.lastGameVersion + str(self.lastGameVersion) ] ] diff --git a/titles/wacca/index.py b/titles/wacca/index.py index 963fa7a..37d3f9e 100644 --- a/titles/wacca/index.py +++ b/titles/wacca/index.py @@ -18,6 +18,7 @@ from titles.wacca.lily import WaccaLily from titles.wacca.s import WaccaS from titles.wacca.base import WaccaBase from titles.wacca.handlers.base import BaseResponse +from titles.wacca.handlers.helpers import Version class WaccaServlet(): def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: @@ -55,12 +56,10 @@ class WaccaServlet(): hash = md5(json.dumps(resp, ensure_ascii=False).encode()).digest() request.responseHeaders.addRawHeader(b"X-Wacca-Hash", hash.hex().encode()) return json.dumps(resp).encode() - - version_full = [] try: req_json = json.loads(request.content.getvalue()) - version_full = req_json["appVersion"].split(".") + version_full = Version(req_json["appVersion"]) except: self.logger.error(f"Failed to parse request toi {request.uri} -> {request.content.getvalue()}") resp = BaseResponse() @@ -76,7 +75,7 @@ class WaccaServlet(): func_to_find += f"{url_split[x + start_req_idx]}_" func_to_find += "request" - ver_search = (int(version_full[0]) * 10000) + (int(version_full[1]) * 100) + int(version_full[2]) + ver_search = int(version_full) if ver_search < 15000: internal_ver = WaccaConstants.VER_WACCA diff --git a/titles/wacca/lily.py b/titles/wacca/lily.py index cf5fd43..77d37de 100644 --- a/titles/wacca/lily.py +++ b/titles/wacca/lily.py @@ -59,7 +59,6 @@ class WaccaLily(WaccaS): def handle_user_status_get_request(self, data: Dict)-> Dict: req = UserStatusGetRequest(data) resp = UserStatusGetV2Response() - ver_split = req.appVersion.split(".") profile = self.data.profile.get_profile(aime_id=req.aimeId) if profile is None: @@ -69,11 +68,9 @@ class WaccaLily(WaccaS): self.logger.info(f"User preview for {req.aimeId} from {req.chipId}") if profile["last_game_ver"] is None: - profile_ver_split = ver_split - resp.lastGameVersion = req.appVersion + resp.lastGameVersion = ShortVersion(str(req.appVersion)) else: - profile_ver_split = profile["last_game_ver"].split(".") - resp.lastGameVersion = profile["last_game_ver"] + resp.lastGameVersion = ShortVersion(profile["last_game_ver"]) resp.userStatus.userId = profile["id"] resp.userStatus.username = profile["username"] @@ -103,26 +100,11 @@ class WaccaLily(WaccaS): if profile["last_login_date"].timestamp() < int((datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) - timedelta(days=1)).timestamp()): resp.userStatus.loginConsecutiveDays = 0 - if int(ver_split[0]) > int(profile_ver_split[0]): + if req.appVersion > resp.lastGameVersion: resp.versionStatus = PlayVersionStatus.VersionUpgrade - - elif int(ver_split[0]) < int(profile_ver_split[0]): - resp.versionStatus = PlayVersionStatus.VersionTooNew - else: - if int(ver_split[1]) > int(profile_ver_split[1]): - resp.versionStatus = PlayVersionStatus.VersionUpgrade - - elif int(ver_split[1]) < int(profile_ver_split[1]): - resp.versionStatus = PlayVersionStatus.VersionTooNew - - else: - if int(ver_split[2]) > int(profile_ver_split[2]): - resp.versionStatus = PlayVersionStatus.VersionUpgrade - - - elif int(ver_split[2]) < int(profile_ver_split[2]): - resp.versionStatus = PlayVersionStatus.VersionTooNew + elif req.appVersion < resp.lastGameVersion: + resp.versionStatus = PlayVersionStatus.VersionTooNew if profile["vip_expire_time"] is not None: resp.userStatus.vipExpireTime = int(profile["vip_expire_time"].timestamp()) @@ -178,8 +160,7 @@ class WaccaLily(WaccaS): def handle_user_status_getDetail_request(self, data: Dict)-> Dict: req = UserStatusGetDetailRequest(data) - ver_split = req.appVersion.split(".") - if int(ver_split[1]) >= 53: + if req.appVersion.minor >= 53: resp = UserStatusGetDetailResponseV3() else: resp = UserStatusGetDetailResponseV2() @@ -252,7 +233,7 @@ class WaccaLily(WaccaS): for user_gate in profile_gates: if user_gate["gate_id"] == gate: - if int(ver_split[1]) >= 53: + if req.appVersion.minor >= 53: resp.gateInfo.append(GateDetailV2(user_gate["gate_id"],user_gate["page"],user_gate["progress"], user_gate["loops"],int(user_gate["last_used"].timestamp()),user_gate["mission_flag"])) @@ -266,7 +247,7 @@ class WaccaLily(WaccaS): break if not added_gate: - if int(ver_split[1]) >= 53: + if req.appVersion.minor >= 53: resp.gateInfo.append(GateDetailV2(gate)) else: From 382e36e60f76b69249876d493c6d18f54e347fda Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Wed, 1 Mar 2023 21:49:00 -0500 Subject: [PATCH 04/37] core: Add county codes and Japanese region IDs --- core/const.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/core/const.py b/core/const.py index f5979e8..21c2da2 100644 --- a/core/const.py +++ b/core/const.py @@ -31,6 +31,64 @@ class KeychipPlatformsCodes(): NU = ("A60E", "A60E", "A60E") NUSX = ("A61X", "A69X") ALLS = "A63E" - + +class AllnetCountryCode(Enum): + JAPAN = "JPN" + UNITED_STATES = "USA" + HONG_KONG = "HKG" + SINGAPORE = "SGP" + SOUTH_KOREA = "KOR" + CHINA = "CHN" + +class AllnetJapanRegionId(Enum): + NONE = 0 + AICHI = 1 + AOMORI = 2 + AKITA = 3 + ISHIKAWA = 4 + IBARAKI = 5 + IWATE = 6 + EHIME = 7 + OITA = 8 + OSAKA = 9 + OKAYAMA = 10 + OKINAWA = 11 + KAGAWA = 12 + KAGOSHIMA = 13 + KANAGAWA = 14 + GIFU = 15 + KYOTO = 16 + KUMAMOTO = 17 + GUNMA = 18 + KOCHI = 19 + SAITAMA = 20 + SAGA = 21 + SHIGA = 22 + SHIZUOKA = 23 + SHIMANE = 24 + CHIBA = 25 + TOKYO = 26 + TOKUSHIMA = 27 + TOCHIGI = 28 + TOTTORI = 29 + TOYAMA = 30 + NAGASAKI = 31 + NAGANO = 32 + NARA = 33 + NIIGATA = 34 + HYOGO = 35 + HIROSHIMA = 36 + FUKUI = 37 + FUKUOKA = 38 + FUKUSHIMA = 39 + HOKKAIDO = 40 + MIE = 41 + MIYAGI = 42 + MIYAZAKI = 43 + YAMAGATA = 44 + YAMAGUCHI = 45 + YAMANASHI = 46 + WAKAYAMA = 47 + class RegionIDs(Enum): pass \ No newline at end of file From 078059f54e1ec9972c384429aac8a997a8ba0072 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Wed, 1 Mar 2023 21:51:52 -0500 Subject: [PATCH 05/37] core: remove unused class from const --- core/const.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/const.py b/core/const.py index 21c2da2..3a2c116 100644 --- a/core/const.py +++ b/core/const.py @@ -89,6 +89,3 @@ class AllnetJapanRegionId(Enum): YAMAGUCHI = 45 YAMANASHI = 46 WAKAYAMA = 47 - -class RegionIDs(Enum): - pass \ No newline at end of file From 379388c74912de15f205fe81641a2b6673f730e9 Mon Sep 17 00:00:00 2001 From: Raymonf Date: Wed, 1 Mar 2023 22:01:35 -0500 Subject: [PATCH 06/37] wacca: allow setting prefecture in config by name --- example_config/wacca.yaml | 1 + titles/wacca/base.py | 102 ++++++++++++++++++++------------------ titles/wacca/config.py | 4 ++ titles/wacca/const.py | 1 + titles/wacca/lily.py | 2 +- 5 files changed, 61 insertions(+), 49 deletions(-) diff --git a/example_config/wacca.yaml b/example_config/wacca.yaml index 4c898a4..aea4f16 100644 --- a/example_config/wacca.yaml +++ b/example_config/wacca.yaml @@ -1,6 +1,7 @@ server: enable: True loglevel: "info" + prefecture_name: "Hokkaido" mods: always_vip: True diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 535048f..7d6424e 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -1,9 +1,8 @@ from typing import Any, List, Dict import logging +import inflection from math import floor - from datetime import datetime, timedelta - from core.config import CoreConfig from titles.wacca.config import WaccaConfig from titles.wacca.const import WaccaConstants @@ -23,53 +22,60 @@ class WaccaBase(): self.season = 1 self.OPTIONS_DEFAULTS: Dict[str, Any] = { - "note_speed": 5, - "field_mask": 0, - "note_sound": 105001, - "note_color": 203001, - "bgm_volume": 10, - "bg_video": 0, - - "mirror": 0, - "judge_display_pos": 0, - "judge_detail_display": 0, - "measure_guidelines": 1, - "guideline_mask": 1, - "judge_line_timing_adjust": 10, - "note_design": 3, - "bonus_effect": 1, - "chara_voice": 1, - "score_display_method": 0, - "give_up": 0, - "guideline_spacing": 1, - "center_display": 1, - "ranking_display": 1, - "stage_up_icon_display": 1, - "rating_display": 1, - "player_level_display": 1, - "touch_effect": 1, - "guide_sound_vol": 3, - "touch_note_vol": 8, - "hold_note_vol": 8, - "slide_note_vol": 8, - "snap_note_vol": 8, - "chain_note_vol": 8, - "bonus_note_vol": 8, - "gate_skip": 0, - "key_beam_display": 1, + "note_speed": 5, + "field_mask": 0, + "note_sound": 105001, + "note_color": 203001, + "bgm_volume": 10, + "bg_video": 0, - "left_slide_note_color": 4, - "right_slide_note_color": 3, - "forward_slide_note_color": 1, - "back_slide_note_color": 2, - - "master_vol": 3, - "set_title_id": 104001, - "set_icon_id": 102001, - "set_nav_id": 210001, - "set_plate_id": 211001 - } + "mirror": 0, + "judge_display_pos": 0, + "judge_detail_display": 0, + "measure_guidelines": 1, + "guideline_mask": 1, + "judge_line_timing_adjust": 10, + "note_design": 3, + "bonus_effect": 1, + "chara_voice": 1, + "score_display_method": 0, + "give_up": 0, + "guideline_spacing": 1, + "center_display": 1, + "ranking_display": 1, + "stage_up_icon_display": 1, + "rating_display": 1, + "player_level_display": 1, + "touch_effect": 1, + "guide_sound_vol": 3, + "touch_note_vol": 8, + "hold_note_vol": 8, + "slide_note_vol": 8, + "snap_note_vol": 8, + "chain_note_vol": 8, + "bonus_note_vol": 8, + "gate_skip": 0, + "key_beam_display": 1, + + "left_slide_note_color": 4, + "right_slide_note_color": 3, + "forward_slide_note_color": 1, + "back_slide_note_color": 2, + + "master_vol": 3, + "set_title_id": 104001, + "set_icon_id": 102001, + "set_nav_id": 210001, + "set_plate_id": 211001 + } self.allowed_stages = [] + + prefecture_name = inflection.underscore(game_cfg.server.prefecture_name).replace(' ', '_').upper() + if prefecture_name not in [region.name for region in WaccaConstants.Region]: + self.logger.warning(f"Invalid prefecture name {game_cfg.server.prefecture_name} in config file") + self.region_id = 1 + else: + self.region_id = int(WaccaConstants.Region[prefecture_name].value) def handle_housing_get_request(self, data: Dict) -> Dict: req = BaseRequest(data) @@ -86,7 +92,7 @@ class WaccaBase(): req = HousingStartRequestV1(data) resp = HousingStartResponseV1( - 1, + self.region_id, [ # Recomended songs 1269,1007,1270,1002,1020,1003,1008,1211,1018,1092,1056,32, 1260,1230,1258,1251,2212,1264,1125,1037,2001,1272,1126,1119, diff --git a/titles/wacca/config.py b/titles/wacca/config.py index f5bc235..fc03dd8 100644 --- a/titles/wacca/config.py +++ b/titles/wacca/config.py @@ -13,6 +13,10 @@ class WaccaServerConfig(): def loglevel(self) -> int: return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'wacca', 'server', 'loglevel', default="info")) + @property + def prefecture_name(self) -> str: + return CoreConfig.get_config_field(self.__config, 'wacca', 'server', 'prefecture_name', default="Hokkaido") + class WaccaModsConfig(): def __init__(self, parent_config: "WaccaConfig") -> None: self.__config = parent_config diff --git a/titles/wacca/const.py b/titles/wacca/const.py index 0590f5f..0cca096 100644 --- a/titles/wacca/const.py +++ b/titles/wacca/const.py @@ -151,6 +151,7 @@ class WaccaConstants(): KAGOSHIMA = 46 OKINAWA = 47 UNITED_STATES = 48 + USA = 48 TAIWAN = 49 HONG_KONG = 50 SINGAPORE = 51 diff --git a/titles/wacca/lily.py b/titles/wacca/lily.py index 77d37de..ac924b7 100644 --- a/titles/wacca/lily.py +++ b/titles/wacca/lily.py @@ -44,7 +44,7 @@ class WaccaLily(WaccaS): req = HousingStartRequestV2(data) resp = HousingStartResponseV1( - 1, + self.region_id, [ # Recomended songs 1269,1007,1270,1002,1020,1003,1008,1211,1018,1092,1056,32, 1260,1230,1258,1251,2212,1264,1125,1037,2001,1272,1126,1119, From e961c1dfb3f24d96bc90fe15c9179e4e79ac67ad Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Wed, 1 Mar 2023 22:27:33 -0500 Subject: [PATCH 07/37] wacca: add region logic --- titles/wacca/base.py | 21 ++++------- titles/wacca/const.py | 4 ++ titles/wacca/handlers/helpers.py | 63 ++++++++++++++++---------------- titles/wacca/handlers/housing.py | 17 +++++++-- titles/wacca/lily.py | 17 +++------ 5 files changed, 63 insertions(+), 59 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 7d6424e..984cdad 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -73,9 +73,9 @@ class WaccaBase(): prefecture_name = inflection.underscore(game_cfg.server.prefecture_name).replace(' ', '_').upper() if prefecture_name not in [region.name for region in WaccaConstants.Region]: self.logger.warning(f"Invalid prefecture name {game_cfg.server.prefecture_name} in config file") - self.region_id = 1 + self.region_id = WaccaConstants.Region.HOKKAIDO else: - self.region_id = int(WaccaConstants.Region[prefecture_name].value) + self.region_id = WaccaConstants.Region[prefecture_name] def handle_housing_get_request(self, data: Dict) -> Dict: req = BaseRequest(data) @@ -91,17 +91,12 @@ class WaccaBase(): def handle_housing_start_request(self, data: Dict) -> Dict: req = HousingStartRequestV1(data) - resp = HousingStartResponseV1( - self.region_id, - [ # Recomended songs - 1269,1007,1270,1002,1020,1003,1008,1211,1018,1092,1056,32, - 1260,1230,1258,1251,2212,1264,1125,1037,2001,1272,1126,1119, - 1104,1070,1047,1044,1027,1004,1001,24,2068,2062,2021,1275, - 1249,1207,1203,1107,1021,1009,9,4,3,23,22,2014,13,1276,1247, - 1240,1237,1128,1114,1110,1109,1102,1045,1043,1036,1035,1030, - 1023,1015 - ] - ) + if req.appVersion.country != "JPN" and req.appVersion.country in [region.name for region in WaccaConstants.Region]: + region_id = WaccaConstants.Region[req.appVersion.country] + else: + region_id = self.region_id + + resp = HousingStartResponseV1(region_id) return resp.make() def handle_advertise_GetNews_request(self, data: Dict) -> Dict: diff --git a/titles/wacca/const.py b/titles/wacca/const.py index 0cca096..a984682 100644 --- a/titles/wacca/const.py +++ b/titles/wacca/const.py @@ -153,9 +153,13 @@ class WaccaConstants(): UNITED_STATES = 48 USA = 48 TAIWAN = 49 + TWN = 49 HONG_KONG = 50 + HKG = 50 SINGAPORE = 51 + SGP = 51 KOREA = 52 + KOR = 52 VALID_COUNTRIES = set(["JPN", "USA", "KOR", "HKG", "SGP"]) diff --git a/titles/wacca/handlers/helpers.py b/titles/wacca/handlers/helpers.py index 752a656..9f6ba8b 100644 --- a/titles/wacca/handlers/helpers.py +++ b/titles/wacca/handlers/helpers.py @@ -90,8 +90,7 @@ class Version(ShortVersion): def __str__(self) -> str: return f"{self.major}.{self.minor}.{self.patch}.{self.country}.{self.role}.{self.build}" - -class HousingInfo(): +class HousingInfo: """ 1 is lan install role, 2 is country """ @@ -105,7 +104,7 @@ class HousingInfo(): def make(self) -> List: return [ self.id, self.val ] -class Notice(): +class Notice: name: str = "" title: str = "" message: str = "" @@ -128,7 +127,7 @@ class Notice(): return [ self.name, self.title, self.message, self.unknown3, self.unknown4, int(self.showTitleScreen), int(self.showWelcomeScreen), self.startTime, self.endTime, self.voiceline] -class UserOption(): +class UserOption: def __init__(self, opt_id: int = 0, opt_val: Any = 0) -> None: self.opt_id = opt_id self.opt_val = opt_val @@ -136,7 +135,7 @@ class UserOption(): def make(self) -> List: return [self.opt_id, self.opt_val] -class UserStatusV1(): +class UserStatusV1: def __init__(self) -> None: self.userId: int = 0 self.username: str = "" @@ -194,7 +193,7 @@ class PlayVersionStatus(Enum): VersionTooNew = 1 VersionUpgrade = 2 -class PlayModeCounts(): +class PlayModeCounts: seasonId: int = 0 modeId: int = 0 playNum: int = 0 @@ -211,7 +210,7 @@ class PlayModeCounts(): self.playNum ] -class SongUnlock(): +class SongUnlock: songId: int = 0 difficulty: int = 0 whenAppeared: int = 0 @@ -231,7 +230,7 @@ class SongUnlock(): self.whenUnlocked ] -class GenericItemRecv(): +class GenericItemRecv: def __init__(self, item_type: int = 1, item_id: int = 1, quantity: int = 1) -> None: self.itemId = item_id self.itemType = item_type @@ -240,7 +239,7 @@ class GenericItemRecv(): def make(self) -> List: return [ self.itemType, self.itemId, self.quantity ] -class GenericItemSend(): +class GenericItemSend: def __init__(self, itemId: int, itemType: int, whenAcquired: int) -> None: self.itemId = itemId self.itemType = itemType @@ -268,7 +267,7 @@ class IconItem(GenericItemSend): self.whenAcquired ] -class TrophyItem(): +class TrophyItem: trophyId: int = 0 season: int = 1 progress: int = 0 @@ -288,7 +287,7 @@ class TrophyItem(): self.badgeType ] -class TicketItem(): +class TicketItem: userTicketId: int = 0 ticketId: int = 0 whenExpires: int = 0 @@ -321,7 +320,7 @@ class NavigatorItem(IconItem): self.usesToday ] -class SkillItem(): +class SkillItem: skill_type: int level: int flag: int @@ -335,7 +334,7 @@ class SkillItem(): self.badge ] -class UserItemInfoV1(): +class UserItemInfoV1: def __init__(self) -> None: self.songUnlocks: List[SongUnlock] = [] self.titles: List[GenericItemSend] = [] @@ -419,7 +418,7 @@ class UserItemInfoV3(UserItemInfoV2): ret.append(effect) return ret -class SongDetailClearCounts(): +class SongDetailClearCounts: def __init__(self, play_ct: int = 0, clear_ct: int = 0, ml_ct: int = 0, fc_ct: int = 0, am_ct: int = 0, counts: Optional[List[int]] = None) -> None: if counts is None: @@ -439,7 +438,7 @@ class SongDetailClearCounts(): def make(self) -> List: return [self.playCt, self.clearCt, self.misslessCt, self.fullComboCt, self.allMarvelousCt] -class SongDetailGradeCountsV1(): +class SongDetailGradeCountsV1: dCt: int cCt: int bCt: int @@ -501,7 +500,7 @@ class SongDetailGradeCountsV2(SongDetailGradeCountsV1): def make(self) -> List: return super().make() + [self.spCt, self.sspCt, self.ssspCt] -class BestScoreDetailV1(): +class BestScoreDetailV1: songId: int = 0 difficulty: int = 1 clearCounts: SongDetailClearCounts = SongDetailClearCounts() @@ -534,7 +533,7 @@ class BestScoreDetailV1(): class BestScoreDetailV2(BestScoreDetailV1): gradeCounts: SongDetailGradeCountsV2 = SongDetailGradeCountsV2() -class SongUpdateJudgementCounts(): +class SongUpdateJudgementCounts: marvCt: int greatCt: int goodCt: int @@ -549,7 +548,7 @@ class SongUpdateJudgementCounts(): def make(self) -> List: return [self.marvCt, self.greatCt, self.goodCt, self.missCt] -class SongUpdateDetailV1(): +class SongUpdateDetailV1: def __init__(self, data: List) -> None: if data is not None: self.songId = data[0] @@ -579,7 +578,7 @@ class SongUpdateDetailV2(SongUpdateDetailV1): self.slowCt = data[14] self.flagNewRecord = False if data[15] == 0 else True -class SeasonalInfoV1(): +class SeasonalInfoV1: def __init__(self) -> None: self.level: int = 0 self.wpObtained: int = 0 @@ -613,7 +612,7 @@ class SeasonalInfoV2(SeasonalInfoV1): def make(self) -> List: return super().make() + [self.platesObtained, self.cumulativeGatePts] -class BingoPageStatus(): +class BingoPageStatus: id = 0 location = 1 progress = 0 @@ -626,7 +625,7 @@ class BingoPageStatus(): def make(self) -> List: return [self.id, self.location, self.progress] -class BingoDetail(): +class BingoDetail: def __init__(self, pageNumber: int) -> None: self.pageNumber = pageNumber self.pageStatus: List[BingoPageStatus] = [] @@ -641,7 +640,7 @@ class BingoDetail(): status ] -class GateDetailV1(): +class GateDetailV1: def __init__(self, gate_id: int = 1, page: int = 1, progress: int = 0, loops: int = 0, last_used: int = 0, mission_flg = 0) -> None: self.id = gate_id self.page = page @@ -657,11 +656,11 @@ class GateDetailV2(GateDetailV1): def make(self) -> List: return super().make() + [self.missionFlg] -class GachaInfo(): +class GachaInfo: def make(self) -> List: return [] -class LastSongDetail(): +class LastSongDetail: lastSongId = 90 lastSongDiff = 1 lastFolderOrd = 1 @@ -680,11 +679,11 @@ class LastSongDetail(): return [self.lastSongId, self.lastSongDiff, self.lastFolderOrd, self.lastFolderId, self.lastSongOrd] -class FriendDetail(): +class FriendDetail: def make(self) -> List: return [] -class LoginBonusInfo(): +class LoginBonusInfo: def __init__(self) -> None: self.tickets: List[TicketItem] = [] self.items: List[GenericItemRecv] = [] @@ -702,7 +701,7 @@ class LoginBonusInfo(): return [ tks, itms, self.message ] -class VipLoginBonus(): +class VipLoginBonus: id = 1 unknown = 0 item: GenericItemRecv @@ -715,7 +714,7 @@ class VipLoginBonus(): def make(self) -> List: return [ self.id, self.unknown, self.item.make() ] -class VipInfo(): +class VipInfo: def __init__(self, year: int = 2019, month: int = 1, day: int = 1, num_item: int = 1) -> None: self.pageYear = year self.pageMonth = month @@ -746,7 +745,7 @@ class PlayType(Enum): PlayTypeCoop = 3 PlayTypeStageup = 4 -class StageInfo(): +class StageInfo: danId: int = 0 danLevel: int = 0 clearStatus: int = 0 @@ -780,7 +779,7 @@ class StageupClearType(Enum): CLEAR_SILVER = 2 CLEAR_GOLD = 3 -class MusicUpdateDetailV1(): +class MusicUpdateDetailV1: def __init__(self) -> None: self.songId = 0 self.difficulty = 1 @@ -818,7 +817,7 @@ class MusicUpdateDetailV3(MusicUpdateDetailV2): super().__init__() self.grades = SongDetailGradeCountsV2() -class SongRatingUpdate(): +class SongRatingUpdate: def __init__(self, song_id: int = 0, difficulty: int = 1, new_rating: int = 0) -> None: self.songId = song_id self.difficulty = difficulty @@ -831,7 +830,7 @@ class SongRatingUpdate(): self.rating, ] -class GateTutorialFlag(): +class GateTutorialFlag: def __init__(self, tutorial_id: int = 1, flg_watched: bool = False) -> None: self.tutorialId = tutorial_id self.flagWatched = flg_watched diff --git a/titles/wacca/handlers/housing.py b/titles/wacca/handlers/housing.py index f49f1c2..1669cac 100644 --- a/titles/wacca/handlers/housing.py +++ b/titles/wacca/handlers/housing.py @@ -2,6 +2,7 @@ from typing import List, Dict from titles.wacca.handlers.base import BaseRequest, BaseResponse from titles.wacca.handlers.helpers import HousingInfo +from titles.wacca.const import WaccaConstants # ---housing/get---- class HousingGetResponse(BaseResponse): @@ -37,12 +38,22 @@ class HousingStartRequestV2(HousingStartRequestV1): self.info.append(HousingInfo(info[0], info[1])) class HousingStartResponseV1(BaseResponse): - def __init__(self, regionId: int, songList: List[int]) -> None: + def __init__(self, regionId: WaccaConstants.Region = WaccaConstants.Region.HOKKAIDO, songList: List[int] = []) -> None: super().__init__() self.regionId = regionId - self.songList = songList + self.songList = songList # Recomended songs + + if not self.songList: + self.songList = [ + 1269,1007,1270,1002,1020,1003,1008,1211,1018,1092,1056,32, + 1260,1230,1258,1251,2212,1264,1125,1037,2001,1272,1126,1119, + 1104,1070,1047,1044,1027,1004,1001,24,2068,2062,2021,1275, + 1249,1207,1203,1107,1021,1009,9,4,3,23,22,2014,13,1276,1247, + 1240,1237,1128,1114,1110,1109,1102,1045,1043,1036,1035,1030, + 1023,1015 + ] def make(self) -> Dict: - self.params = [self.regionId, self.songList] + self.params = [self.regionId.value, self.songList] return super().make() diff --git a/titles/wacca/lily.py b/titles/wacca/lily.py index ac924b7..d15a504 100644 --- a/titles/wacca/lily.py +++ b/titles/wacca/lily.py @@ -42,18 +42,13 @@ class WaccaLily(WaccaS): def handle_housing_start_request(self, data: Dict) -> Dict: req = HousingStartRequestV2(data) + + if req.appVersion.country != "JPN" and req.appVersion.country in [region.name for region in WaccaConstants.Region]: + region_id = WaccaConstants.Region[req.appVersion.country] + else: + region_id = self.region_id - resp = HousingStartResponseV1( - self.region_id, - [ # Recomended songs - 1269,1007,1270,1002,1020,1003,1008,1211,1018,1092,1056,32, - 1260,1230,1258,1251,2212,1264,1125,1037,2001,1272,1126,1119, - 1104,1070,1047,1044,1027,1004,1001,24,2068,2062,2021,1275, - 1249,1207,1203,1107,1021,1009,9,4,3,23,22,2014,13,1276,1247, - 1240,1237,1128,1114,1110,1109,1102,1045,1043,1036,1035,1030, - 1023,1015 - ] - ) + resp = HousingStartResponseV1(region_id) return resp.make() def handle_user_status_get_request(self, data: Dict)-> Dict: From a0739436cc228337dfec78eee581126b1b0abfb8 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Wed, 1 Mar 2023 23:03:29 -0500 Subject: [PATCH 08/37] Wacca: Fix stageup order, fixes #3 --- titles/wacca/base.py | 13 ++++++++----- titles/wacca/lily.py | 26 +++++++++++++------------- titles/wacca/lilyr.py | 26 +++++++++++++------------- titles/wacca/reverse.py | 26 +++++++++++++------------- titles/wacca/s.py | 24 ++++++++++++------------ 5 files changed, 59 insertions(+), 56 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 984cdad..2bf47fd 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -361,7 +361,8 @@ class WaccaBase(): if stages is None: stages = [] - add_next = True + tmp: List[StageInfo] = [] + for d in self.allowed_stages: stage_info = StageInfo(d[0], d[1]) @@ -374,11 +375,13 @@ class WaccaBase(): stage_info.song3BestScore = score["song3_score"] break - if add_next or stage_info.danLevel < 9: - resp.stageList.append(stage_info) + tmp.append(stage_info) - if stage_info.danLevel >= 9 and stage_info.clearStatus < 1: - add_next = False + for x in range(len(resp.stageList)): + if resp.stageList[x].danLevel >= 10 and (resp.stageList[x + 1].clearStatus >= 1 or resp.stageList[x].clearStatus >= 1): + resp.stageList.append(tmp[x]) + elif resp.stageList[x].danLevel < 10: + resp.stageList.append(tmp[x]) return resp.make() diff --git a/titles/wacca/lily.py b/titles/wacca/lily.py index d15a504..66a4bc8 100644 --- a/titles/wacca/lily.py +++ b/titles/wacca/lily.py @@ -17,20 +17,20 @@ class WaccaLily(WaccaS): self.OPTIONS_DEFAULTS["set_nav_id"] = 210002 self.allowed_stages = [ - (2001, 1), - (2002, 2), - (2003, 3), - (2004, 4), - (2005, 5), - (2006, 6), - (2007, 7), - (2008, 8), - (2009, 9), - (2010, 10), - (2011, 11), - (2012, 12), - (2013, 13), (2014, 14), + (2013, 13), + (2012, 12), + (2011, 11), + (2010, 10), + (2009, 9), + (2008, 8), + (2007, 7), + (2006, 6), + (2005, 5), + (2004, 4), + (2003, 3), + (2002, 2), + (2001, 1), (210001, 0), (210002, 0), (210003, 0), diff --git a/titles/wacca/lilyr.py b/titles/wacca/lilyr.py index 559c61d..893a8f3 100644 --- a/titles/wacca/lilyr.py +++ b/titles/wacca/lilyr.py @@ -16,20 +16,20 @@ class WaccaLilyR(WaccaLily): self.OPTIONS_DEFAULTS["set_nav_id"] = 210002 self.allowed_stages = [ - (2501, 1), - (2502, 2), - (2503, 3), - (2504, 4), - (2505, 5), - (2506, 6), - (2507, 7), - (2508, 8), - (2509, 9), - (2510, 10), - (2511, 11), - (2512, 12), - (2513, 13), (2514, 14), + (2513, 13), + (2512, 12), + (2511, 11), + (2510, 10), + (2509, 9), + (2508, 8), + (2507, 7), + (2506, 6), + (2505, 5), + (2504, 4), + (2503, 3), + (2501, 2), + (2501, 1), (210001, 0), (210002, 0), (210003, 0), diff --git a/titles/wacca/reverse.py b/titles/wacca/reverse.py index 78db7e4..100ffb1 100644 --- a/titles/wacca/reverse.py +++ b/titles/wacca/reverse.py @@ -18,20 +18,20 @@ class WaccaReverse(WaccaLilyR): self.OPTIONS_DEFAULTS["set_nav_id"] = 310001 self.allowed_stages = [ - (3001, 1), - (3002, 2), - (3003, 3), - (3004, 4), - (3005, 5), - (3006, 6), - (3007, 7), - (3008, 8), - (3009, 9), - (3010, 10), - (3011, 11), - (3012, 12), - (3013, 13), (3014, 14), + (3013, 13), + (3012, 12), + (3011, 11), + (3010, 10), + (3009, 9), + (3008, 8), + (3007, 7), + (3006, 6), + (3005, 5), + (3004, 4), + (3003, 3), + (3002, 2), + (3001, 1), # Touhou (210001, 0), (210002, 0), diff --git a/titles/wacca/s.py b/titles/wacca/s.py index 9f23367..d4c2881 100644 --- a/titles/wacca/s.py +++ b/titles/wacca/s.py @@ -11,19 +11,19 @@ from titles.wacca.handlers import * class WaccaS(WaccaBase): allowed_stages = [ - (1501, 1), - (1502, 2), - (1503, 3), - (1504, 4), - (1505, 5), - (1506, 6), - (1507, 7), - (1508, 8), - (1509, 9), - (1510, 10), - (1511, 11), - (1512, 12), (1513, 13), + (1512, 12), + (1511, 11), + (1510, 10), + (1509, 9), + (1508, 8), + (1507, 7), + (1506, 6), + (1505, 5), + (1514, 4), + (1513, 3), + (1512, 2), + (1511, 1), ] def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: From 1567ec23ab791e4481e11ac51c26fac965cea538 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Wed, 1 Mar 2023 23:24:36 -0500 Subject: [PATCH 09/37] wacca: fix stageup list not populating correctly, fix #3 --- titles/wacca/base.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 2bf47fd..9df231e 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -356,7 +356,6 @@ class WaccaBase(): return resp.make() self.logger.info(f"Get trial info for user {req.profileId}") - stages = self.data.score.get_stageup(user_id, self.version) if stages is None: stages = [] @@ -377,10 +376,10 @@ class WaccaBase(): tmp.append(stage_info) - for x in range(len(resp.stageList)): - if resp.stageList[x].danLevel >= 10 and (resp.stageList[x + 1].clearStatus >= 1 or resp.stageList[x].clearStatus >= 1): + for x in range(len(tmp)): + if tmp[x].danLevel >= 10 and (tmp[x + 1].clearStatus >= 1 or tmp[x].clearStatus >= 1): resp.stageList.append(tmp[x]) - elif resp.stageList[x].danLevel < 10: + elif tmp[x].danLevel < 10: resp.stageList.append(tmp[x]) return resp.make() From 6b0838062eff5e73402b1df3dcb4ec0569ccd077 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Wed, 1 Mar 2023 23:55:29 -0500 Subject: [PATCH 10/37] wacca: add lily to list of items given on profile create, fixes #4 --- titles/wacca/lily.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/titles/wacca/lily.py b/titles/wacca/lily.py index 66a4bc8..e67b25c 100644 --- a/titles/wacca/lily.py +++ b/titles/wacca/lily.py @@ -50,6 +50,14 @@ class WaccaLily(WaccaS): resp = HousingStartResponseV1(region_id) return resp.make() + + def handle_user_status_create_request(self, data: Dict)-> Dict: + req = UserStatusCreateRequest(data) + resp = super().handle_user_status_create_request(data) + + self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210002) # Lily, Added Lily + + return resp def handle_user_status_get_request(self, data: Dict)-> Dict: req = UserStatusGetRequest(data) From 5965362a0f612600840366cbf010643d6bb83d8c Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 00:14:13 -0500 Subject: [PATCH 11/37] title: add 405 and 404 error responses --- core/title.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/title.py b/core/title.py index 6c54450..252a130 100644 --- a/core/title.py +++ b/core/title.py @@ -53,10 +53,13 @@ class TitleServlet(): code = endpoints["game"] if code not in self.title_registry: self.logger.warn(f"Unknown game code {code}") + request.setResponseCode(404) + return b"" index = self.title_registry[code] if not hasattr(index, "render_GET"): self.logger.warn(f"{code} does not dispatch GET") + request.setResponseCode(405) return b"" return index.render_GET(request, endpoints["version"], endpoints["endpoint"]) @@ -65,10 +68,13 @@ class TitleServlet(): code = endpoints["game"] if code not in self.title_registry: self.logger.warn(f"Unknown game code {code}") + request.setResponseCode(404) + return b"" index = self.title_registry[code] if not hasattr(index, "render_POST"): self.logger.warn(f"{code} does not dispatch POST") + request.setResponseCode(405) return b"" return index.render_POST(request, int(endpoints["version"]), endpoints["endpoint"]) From 7071ab0bd907f1c06c9b1dd771580913882220a2 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 00:24:04 -0500 Subject: [PATCH 12/37] chuni: add IP logging, clean up logs --- titles/chuni/index.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/titles/chuni/index.py b/titles/chuni/index.py index 25f31b5..a0f8b55 100644 --- a/titles/chuni/index.py +++ b/titles/chuni/index.py @@ -135,7 +135,8 @@ class ChuniServlet(): req_data = json.loads(unzip) - self.logger.info(f"v{version} {endpoint} request - {req_data}") + self.logger.info(f"v{version} {endpoint} request from {request.getClientAddress().host}") + self.logger.debug(req_data) func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" @@ -154,7 +155,7 @@ class ChuniServlet(): if resp == None: resp = {'returnCode': 1} - self.logger.info(f"Response {resp}") + self.logger.debug(f"Response {resp}") zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) From 846a556c5bcafa2c879453f46327a31086601370 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 10:28:49 -0500 Subject: [PATCH 13/37] fix nginx example --- example_config/nginx_example.conf | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/example_config/nginx_example.conf b/example_config/nginx_example.conf index 096a072..30c733c 100644 --- a/example_config/nginx_example.conf +++ b/example_config/nginx_example.conf @@ -99,26 +99,6 @@ server { } } -# CXB, comment this out if you don't plan on serving crossbeats. -server { - listen 443 ssl; - server_name cxb.hostname.here; - - ssl_certificate /path/to/cert/cxb.pem; - ssl_certificate_key /path/to/cert/cxb.key; - ssl_session_timeout 1d; - ssl_session_cache shared:MozSSL:10m; - ssl_session_tickets off; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; - ssl_ciphers "ALL:@SECLEVEL=1"; - ssl_prefer_server_ciphers off; - - location / { - proxy_pass http://localhost:8080/SDBT/104/; - } -} - # Frontend, set to redirect to HTTPS. Comment out if you don't intend to use the frontend server { listen 80; From d5a7247a7f9d0fa87f51b1d53a0596bde9e4bc75 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 11:47:40 -0500 Subject: [PATCH 14/37] add production config --- docs/prod.md | 35 +++++++++++++++++++++++++++++++ example_config/nginx_example.conf | 1 + 2 files changed, 36 insertions(+) create mode 100644 docs/prod.md diff --git a/docs/prod.md b/docs/prod.md new file mode 100644 index 0000000..2967679 --- /dev/null +++ b/docs/prod.md @@ -0,0 +1,35 @@ +# ARTEMiS Production mode +Production mode is a configuration option that changes how the server listens to be more friendly to a production environment. This mode assumes that a proxy (for this guide, nginx) is standing in front of the server to handle port mapping and TLS. In order to activate production mode, simply change `is_develop` to `False` in `core.yaml`. Next time you start the server, you should see "Starting server in production mode". + +## Nginx Configuration +### Port forwarding +Artemis requires that the following ports be forwarded to allow internet traffic to access the server. This will not change regardless of what you set in the config, as many of these ports are hard-coded in the games. +`tcp:80` all.net, non-ssl titles +`tcp:8443` billing +`tcp:22345` aimedb +`tcp:443` frontend, SSL titles + +### A note about external proxy services (cloudflare, etc) +Due to the way that artemis functions, it is currently not possible to put the server behind something like Cloudflare. Cloudflare only proxies web traffic on the standard ports (80, 443) and, as shown above, this does not work with artemis. Server administrators should seek other means to protect their network (VPS hosting, VPN, etc) + +### Port mappings +An example config is provided in the `config` folder called `nginx_example.conf`. It is set up for the following: +`naominet.jp:tcp:80` -> `localhost:tcp:8000` for allnet +`ib.naominet.jp:ssl:8443` -> `localhost:tcp:8444` for the billing server +`your.hostname.here:ssl:443` -> `localhost:tcp:8080` for the SSL title server +`your.hostname.here:tcp:80` -> `localhost:tcp:8080` for the non-SSL title server +`cxb.hostname.here:ssl:443` -> `localhost:tcp:8080` for crossbeats (appends /SDCA/104/ to the request) +`pokken.hostname.here:ssl:443` -> `localhost:tcp:8080` for pokken +`frontend.hostname.here:ssl:443` -> `localhost:tcp:8090` for the frontend, includes https redirection + +If you're using this as a guide, be sure to replace your.hostname.here with the hostname you specified in core.yaml under `titles->hostname`. Do *not* change naominet.jp, or allnet/billing will fail. Also remember to specifiy certificat paths correctly, as in the example they are simply placeholders. + +### Multi-service ports +It is possible to use nginx to redirect billing and title server requests to the same port that all.net uses. By setting `port` to 0 under billing and title server, you can change the nginx config to serve the following (entries not shown here should be the same) +`ib.naominet.jp:ssl:8443` -> `localhost:tcp:8000` for the billing server +`your.hostname.here:ssl:443` -> `localhost:tcp:8000` for the SSL title server +`your.hostname.here:tcp:80` -> `localhost:tcp:8000` for the non-SSL title server +`cxb.hostname.here:ssl:443` -> `localhost:tcp:8000` for crossbeats (appends /SDCA/104/ to the request) +`pokken.hostname.here:ssl:443` -> `localhost:tcp:8000` for pokken + +This will allow you to only use 3 ports locally, but you will still need to forward the same internet-facing ports as before. \ No newline at end of file diff --git a/example_config/nginx_example.conf b/example_config/nginx_example.conf index 30c733c..fe6f7a7 100644 --- a/example_config/nginx_example.conf +++ b/example_config/nginx_example.conf @@ -114,6 +114,7 @@ server { # Frontend HTTPS. Comment out if you on't intend to use the frontend server { listen 443 ssl; + server_name frontend.hostname.here; ssl_certificate /path/to/cert/frontend.pem; ssl_certificate_key /path/to/cert/frontend.key; From 99881ea2208defc8303c9ef090ef9d743901646e Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 11:54:50 -0500 Subject: [PATCH 15/37] docs: Add note about SSL certs to prod.md --- docs/prod.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/prod.md b/docs/prod.md index 2967679..35785f2 100644 --- a/docs/prod.md +++ b/docs/prod.md @@ -12,6 +12,12 @@ Artemis requires that the following ports be forwarded to allow internet traffic ### A note about external proxy services (cloudflare, etc) Due to the way that artemis functions, it is currently not possible to put the server behind something like Cloudflare. Cloudflare only proxies web traffic on the standard ports (80, 443) and, as shown above, this does not work with artemis. Server administrators should seek other means to protect their network (VPS hosting, VPN, etc) +### SSL Certificates +You will need to generate SSL certificates for some games. The certificates vary in security and validity requirements. Please see the general guide below +- General Title: The certificate for the general title server should be valid, not self-signed and match the CN that the game will be reaching out to (e.i if your games are reaching out to titles.hostname.here, your ssl certificate should be valid for titles.hostname.here, or *.hostname.here) +- CXB: Same requires as the title server. It must not be self-signed, and CN must match. Recomended to get a wildcard cert if possible, and use it for both Title and CXB +- Pokken: Pokken can be self-signed, and the CN doesn't have to match, but it MUST use 2048-bit RSA. Due to the games age, andthing stronger then that will be rejected. + ### Port mappings An example config is provided in the `config` folder called `nginx_example.conf`. It is set up for the following: `naominet.jp:tcp:80` -> `localhost:tcp:8000` for allnet From 4d9ae19cb295c2e7f38194496d1a6bab503caf86 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 11:59:52 -0500 Subject: [PATCH 16/37] update readme --- readme.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/readme.md b/readme.md index 50bf933..1e05d5a 100644 --- a/readme.md +++ b/readme.md @@ -29,10 +29,8 @@ Games listed below have been tested and confirmed working. Only game versions ol - memcached (for non-windows platforms) - mysql/mariadb server -## Quick start guide -1) Clone this repository -2) Install requirements (see the platform-specific guides for instructions) -3) Install python libraries via `pip` -4) Copy the example configuration files into another folder (by default the server looks for the `config` directory) -5) Edit the newly copied configuration files to your liking, using [this](docs/config.md) doc as a guide. -6) Run the server by invoking `index.py` ex. `python3 index.py` \ No newline at end of file +## Setup guides +Follow the platform-specific guides for [windows](docs/INSTALL_WINDOWS.md) and [ubuntu](docs/INSTALL_UBUNTU.md) to setup and run the server. + +## Production guide +See the [production guide](docs/prod.md) for running a production server. From e205777693e2e229ea7c7dcbb0fd5b819b3d47d0 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 12:05:38 -0500 Subject: [PATCH 17/37] docs: fix typo in prod.md --- docs/prod.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/prod.md b/docs/prod.md index 35785f2..de79b99 100644 --- a/docs/prod.md +++ b/docs/prod.md @@ -28,7 +28,7 @@ An example config is provided in the `config` folder called `nginx_example.conf` `pokken.hostname.here:ssl:443` -> `localhost:tcp:8080` for pokken `frontend.hostname.here:ssl:443` -> `localhost:tcp:8090` for the frontend, includes https redirection -If you're using this as a guide, be sure to replace your.hostname.here with the hostname you specified in core.yaml under `titles->hostname`. Do *not* change naominet.jp, or allnet/billing will fail. Also remember to specifiy certificat paths correctly, as in the example they are simply placeholders. +If you're using this as a guide, be sure to replace your.hostname.here with the hostname you specified in core.yaml under `titles->hostname`. Do *not* change naominet.jp, or allnet/billing will fail. Also remember to specifiy certificate paths correctly, as in the example they are simply placeholders. ### Multi-service ports It is possible to use nginx to redirect billing and title server requests to the same port that all.net uses. By setting `port` to 0 under billing and title server, you can change the nginx config to serve the following (entries not shown here should be the same) From c5fc879af669251462d268eb4992a0044ec53b89 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 2 Mar 2023 13:02:43 -0500 Subject: [PATCH 18/37] core: add taiwan to AllnetCountryCode --- core/const.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/const.py b/core/const.py index 3a2c116..da018ee 100644 --- a/core/const.py +++ b/core/const.py @@ -38,6 +38,7 @@ class AllnetCountryCode(Enum): HONG_KONG = "HKG" SINGAPORE = "SGP" SOUTH_KOREA = "KOR" + TAIWAN = "TWN" CHINA = "CHN" class AllnetJapanRegionId(Enum): From 937dba20cafdc4ab47d1b4aff686e84d223a8aec Mon Sep 17 00:00:00 2001 From: Midorica Date: Thu, 2 Mar 2023 22:13:18 -0500 Subject: [PATCH 19/37] Fixing billing that was failing for Chunithm --- core/allnet.py | 2 +- index.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/allnet.py b/core/allnet.py index c938a88..587aeab 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -191,7 +191,7 @@ class AllnetServlet: self.logger.debug(f"request {req_dict}") - rsa = RSA.import_key(open(self.config.billing.sign_key, 'rb').read()) + rsa = RSA.import_key(open(self.config.billing.signing_key, 'rb').read()) signer = PKCS1_v1_5.new(rsa) digest = SHA.new() diff --git a/index.py b/index.py index e514b10..51842bd 100644 --- a/index.py +++ b/index.py @@ -29,6 +29,7 @@ class HttpDispatcher(resource.Resource): self.map_post.connect('allnet_poweron', '/sys/servlet/PowerOn', controller="allnet", action='handle_poweron', conditions=dict(method=['POST'])) self.map_post.connect('allnet_downloadorder', '/sys/servlet/DownloadOrder', controller="allnet", action='handle_dlorder', conditions=dict(method=['POST'])) self.map_post.connect('allnet_billing', '/request', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST'])) + self.map_post.connect('allnet_billing', '/request/', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST'])) self.map_post.connect('mucha_boardauth', '/mucha/boardauth.do', controller="mucha", action='handle_boardauth', conditions=dict(method=['POST'])) self.map_post.connect('mucha_updatacheck', '/mucha/updatacheck.do', controller="mucha", action='handle_updatacheck', conditions=dict(method=['POST'])) From 3791b2b2383c9a13aa896b6cc821522d9f0a0ee4 Mon Sep 17 00:00:00 2001 From: Midorica Date: Fri, 3 Mar 2023 00:00:22 -0500 Subject: [PATCH 20/37] Adding Ongeki Bright Memory support --- core/data/schema/versions/SDDT_2_rollback.sql | 7 +++ core/data/schema/versions/SDDT_3_upgrade.sql | 27 +++++++++ readme.md | 2 +- titles/ongeki/base.py | 4 ++ titles/ongeki/brightmemory.py | 58 +++++++++++++++++++ titles/ongeki/const.py | 3 +- titles/ongeki/index.py | 6 +- titles/ongeki/schema/item.py | 40 +++++++++++++ titles/ongeki/schema/profile.py | 1 + titles/ongeki/schema/score.py | 3 + 10 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 core/data/schema/versions/SDDT_2_rollback.sql create mode 100644 core/data/schema/versions/SDDT_3_upgrade.sql create mode 100644 titles/ongeki/brightmemory.py diff --git a/core/data/schema/versions/SDDT_2_rollback.sql b/core/data/schema/versions/SDDT_2_rollback.sql new file mode 100644 index 0000000..5ddf1b6 --- /dev/null +++ b/core/data/schema/versions/SDDT_2_rollback.sql @@ -0,0 +1,7 @@ +SET FOREIGN_KEY_CHECKS=0; +ALTER TABLE ongeki_profile_data DROP COLUMN isDialogWatchedSuggestMemory; +ALTER TABLE ongeki_score_best DROP COLUMN platinumScoreMax; +ALTER TABLE ongeki_score_playlog DROP COLUMN platinumScore; +ALTER TABLE ongeki_score_playlog DROP COLUMN platinumScoreMax; +DROP TABLE IF EXISTS `ongeki_user_memorychapter`; +SET FOREIGN_KEY_CHECKS=1; diff --git a/core/data/schema/versions/SDDT_3_upgrade.sql b/core/data/schema/versions/SDDT_3_upgrade.sql new file mode 100644 index 0000000..26c5c30 --- /dev/null +++ b/core/data/schema/versions/SDDT_3_upgrade.sql @@ -0,0 +1,27 @@ +SET FOREIGN_KEY_CHECKS=0; + +ALTER TABLE ongeki_profile_data ADD COLUMN isDialogWatchedSuggestMemory BOOLEAN; +ALTER TABLE ongeki_score_best ADD COLUMN platinumScoreMax INTEGER; +ALTER TABLE ongeki_score_playlog ADD COLUMN platinumScore INTEGER; +ALTER TABLE ongeki_score_playlog ADD COLUMN platinumScoreMax INTEGER; + +CREATE TABLE ongeki_user_memorychapter ( + id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, + user INT NOT NULL, + chapterId INT NOT NULL, + gaugeId INT NOT NULL, + gaugeNum INT NOT NULL, + jewelCount INT NOT NULL, + isStoryWatched BOOLEAN NOT NULL, + isBossWatched BOOLEAN NOT NULL, + isDialogWatched BOOLEAN NOT NULL, + isEndingWatched BOOLEAN NOT NULL, + isClear BOOLEAN NOT NULL, + lastPlayMusicId INT NOT NULL, + lastPlayMusicLevel INT NOT NULL, + lastPlayMusicCategory INT NOT NULL, + UNIQUE KEY ongeki_user_memorychapter_uk (user, chapterId), + CONSTRAINT ongeki_user_memorychapter_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE +); + +SET FOREIGN_KEY_CHECKS=1; diff --git a/readme.md b/readme.md index 1e05d5a..24440fc 100644 --- a/readme.md +++ b/readme.md @@ -16,7 +16,7 @@ Games listed below have been tested and confirmed working. Only game versions ol + All versions + Ongeki - + All versions up to Bright + + All versions up to Bright Memory + Wacca + Lily R diff --git a/titles/ongeki/base.py b/titles/ongeki/base.py index 8778645..3f6dc7a 100644 --- a/titles/ongeki/base.py +++ b/titles/ongeki/base.py @@ -787,6 +787,10 @@ class OngekiBase(): if "userChapterList" in upsert: for x in upsert["userChapterList"]: self.data.item.put_chapter(user_id, x) + + if "userMemoryChapterList" in upsert: + for x in upsert["userMemoryChapterList"]: + self.data.item.put_memorychapter(user_id, x) if "userItemList" in upsert: for x in upsert["userItemList"]: diff --git a/titles/ongeki/brightmemory.py b/titles/ongeki/brightmemory.py new file mode 100644 index 0000000..a99f806 --- /dev/null +++ b/titles/ongeki/brightmemory.py @@ -0,0 +1,58 @@ +from datetime import date, datetime, timedelta +from typing import Any, Dict +import pytz +import json + +from core.config import CoreConfig +from titles.ongeki.base import OngekiBase +from titles.ongeki.const import OngekiConstants +from titles.ongeki.config import OngekiConfig + +class OngekiBrightMemory(OngekiBase): + + def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: + super().__init__(core_cfg, game_cfg) + self.version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY + + def handle_get_game_setting_api_request(self, data: Dict) -> Dict: + ret = super().handle_get_game_setting_api_request(data) + ret["gameSetting"]["dataVersion"] = "1.35.00" + ret["gameSetting"]["onlineDataVersion"] = "1.35.00" + ret["gameSetting"]["maxCountCharacter"] = 50 + ret["gameSetting"]["maxCountCard"] = 300 + ret["gameSetting"]["maxCountItem"] = 300 + ret["gameSetting"]["maxCountMusic"] = 50 + ret["gameSetting"]["maxCountMusicItem"] = 300 + ret["gameSetting"]["maxCountRivalMusic"] = 300 + return ret + + def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict: + memories = self.data.item.get_memorychapters(data["userId"]) + if not memories: + return {"userId": data["userId"], "length":6, "userMemoryChapterList":[ + {"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70001, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, + {"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70002, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, + {"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70003, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, + {"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70004, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, + {"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70005, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}, + {"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70099, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0} + ]} + + memory_chp = [] + for chp in memories: + tmp = chp._asdict() + tmp.pop("id") + tmp.pop("user") + memory_chp.append(tmp) + + return { + "userId": data["userId"], + "length": len(memory_chp), + "userMemoryChapterList": memory_chp + } + + def handle_get_game_music_release_state_api_request(self, data: Dict) -> Dict: + return { + "techScore": 0, + "cardNum": 0 + } \ No newline at end of file diff --git a/titles/ongeki/const.py b/titles/ongeki/const.py index da9614e..dd6c0f5 100644 --- a/titles/ongeki/const.py +++ b/titles/ongeki/const.py @@ -10,6 +10,7 @@ class OngekiConstants(): VER_ONGEKI_RED = 4 VER_ONGEKI_RED_PLUS = 5 VER_ONGEKI_BRIGHT = 6 + VER_ONGEKI_BRIGHT_MEMORY = 7 EVT_TYPES: Enum = Enum('EVT_TYPES', [ 'None', @@ -43,7 +44,7 @@ class OngekiConstants(): Lunatic = 10 VERSION_NAMES = ("ONGEKI", "ONGEKI+", "ONGEKI Summer", "ONGEKI Summer+", "ONGEKI Red", "ONGEKI Red+", - "ONGEKI Bright") + "ONGEKI Bright", "ONGEKI Bright Memory") @classmethod def game_ver_to_string(cls, ver: int): diff --git a/titles/ongeki/index.py b/titles/ongeki/index.py index d3b107e..c636659 100644 --- a/titles/ongeki/index.py +++ b/titles/ongeki/index.py @@ -17,6 +17,7 @@ from titles.ongeki.summerplus import OngekiSummerPlus from titles.ongeki.red import OngekiRed from titles.ongeki.redplus import OngekiRedPlus from titles.ongeki.bright import OngekiBright +from titles.ongeki.brightmemory import OngekiBrightMemory class OngekiServlet(): def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None: @@ -32,6 +33,7 @@ class OngekiServlet(): OngekiRed(core_cfg, self.game_cfg), OngekiRedPlus(core_cfg, self.game_cfg), OngekiBright(core_cfg, self.game_cfg), + OngekiBrightMemory(core_cfg, self.game_cfg), ] self.logger = logging.getLogger("ongeki") @@ -69,8 +71,10 @@ class OngekiServlet(): internal_ver = OngekiConstants.VER_ONGEKI_RED elif version >= 125 and version < 130: # Red Plus internal_ver = OngekiConstants.VER_ONGEKI_RED_PLUS - elif version >= 130 and version < 135: # Red Plus + elif version >= 130 and version < 135: # Bright internal_ver = OngekiConstants.VER_ONGEKI_BRIGHT + elif version >= 135 and version < 140: # Bright Memory + internal_ver = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: # If we get a 32 character long hex string, it's a hash and we're diff --git a/titles/ongeki/schema/item.py b/titles/ongeki/schema/item.py index cc09fda..c3f8c7e 100644 --- a/titles/ongeki/schema/item.py +++ b/titles/ongeki/schema/item.py @@ -107,6 +107,27 @@ chapter = Table( mysql_charset='utf8mb4' ) +memorychapter = Table( + "ongeki_user_memorychapter", + metadata, + Column("id", Integer, primary_key=True, nullable=False), + Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")), + Column("chapterId", Integer), + Column("gaugeId", Integer), + Column("gaugeNum", Integer), + Column("jewelCount", Integer), + Column("isStoryWatched", Boolean), + Column("isBossWatched", Boolean), + Column("isDialogWatched", Boolean), + Column("isEndingWatched", Boolean), + Column("isClear", Boolean), + Column("lastPlayMusicId", Integer), + Column("lastPlayMusicLevel", Integer), + Column("lastPlayMusicCategory", Integer), + UniqueConstraint("user", "chapterId", name="ongeki_user_memorychapter_uk"), + mysql_charset='utf8mb4' +) + item = Table( "ongeki_user_item", metadata, @@ -522,5 +543,24 @@ class OngekiItemData(BaseData): sql = select(boss).where(boss.c.user == aime_id) result = self.execute(sql) + if result is None: return None + return result.fetchall() + + def put_memorychapter(self, aime_id: int, memorychapter_data: Dict) -> Optional[int]: + memorychapter_data["user"] = aime_id + + sql = insert(memorychapter).values(**memorychapter_data) + conflict = sql.on_duplicate_key_update(**memorychapter_data) + result = self.execute(conflict) + + if result is None: + self.logger.warn(f"put_memorychapter: Failed to update! aime_id: {aime_id}") + return None + return result.lastrowid + + def get_memorychapters(self, aime_id: int) -> Optional[List[Dict]]: + sql = select(memorychapter).where(memorychapter.c.user == aime_id) + + result = self.execute(sql) if result is None: return None return result.fetchall() \ No newline at end of file diff --git a/titles/ongeki/schema/profile.py b/titles/ongeki/schema/profile.py index bdff67d..26c79b5 100644 --- a/titles/ongeki/schema/profile.py +++ b/titles/ongeki/schema/profile.py @@ -78,6 +78,7 @@ profile = Table( Column("overDamageBattlePoint", Integer, server_default="0"), Column("bestBattlePoint", Integer, server_default="0"), Column("lastEmoneyBrand", Integer, server_default="0"), + Column("isDialogWatchedSuggestMemory", Boolean), UniqueConstraint("user", "version", name="ongeki_profile_profile_uk"), mysql_charset='utf8mb4' ) diff --git a/titles/ongeki/schema/score.py b/titles/ongeki/schema/score.py index 08a6a86..e526005 100644 --- a/titles/ongeki/schema/score.py +++ b/titles/ongeki/schema/score.py @@ -28,6 +28,7 @@ score_best = Table( Column("isLock", Boolean, nullable=False), Column("clearStatus", Boolean, nullable=False), Column("isStoryWatched", Boolean, nullable=False), + Column("platinumScoreMax", Integer), UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"), mysql_charset='utf8mb4' ) @@ -96,6 +97,8 @@ playlog = Table( Column("isAllBreak", Boolean), Column("playerRating", Integer), Column("battlePoint", Integer), + Column("platinumScore", Integer), + Column("platinumScoreMax", Integer), mysql_charset='utf8mb4' ) From 4626ec36cd2426be2f1da71073a38bdc0416b544 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 12:40:03 -0500 Subject: [PATCH 21/37] wacca: fix options not saving --- titles/wacca/base.py | 2 +- titles/wacca/schema/profile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 9df231e..25ec33b 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -746,7 +746,7 @@ class WaccaBase(): user_id = self.data.profile.profile_to_aime_user(req.profileId) for opt in req.optsUpdated: - self.data.profile.update_option(user_id, opt.id, opt.val) + self.data.profile.update_option(user_id, opt.opt_id, opt.opt_val) for update in req.datesUpdated: pass diff --git a/titles/wacca/schema/profile.py b/titles/wacca/schema/profile.py index 7237149..c2a15f6 100644 --- a/titles/wacca/schema/profile.py +++ b/titles/wacca/schema/profile.py @@ -211,7 +211,7 @@ class WaccaProfileData(BaseData): ) conflict = sql.on_duplicate_key_update( - value = sql.inserted.value + value = value ) result = self.execute(conflict) From 7953519e683b46e026fa0d362ad71081d575df7e Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 13:03:48 -0500 Subject: [PATCH 22/37] wacca: fix UserInfoUpdateRequest, per #5 --- titles/wacca/handlers/helpers.py | 8 ++++++++ titles/wacca/handlers/user_info.py | 10 +++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/titles/wacca/handlers/helpers.py b/titles/wacca/handlers/helpers.py index 9f6ba8b..1192237 100644 --- a/titles/wacca/handlers/helpers.py +++ b/titles/wacca/handlers/helpers.py @@ -840,3 +840,11 @@ class GateTutorialFlag: self.tutorialId, int(self.flagWatched) ] + +class DateUpdate: + def __init__(self, date_id: int = 0, timestamp: int = 0) -> None: + self.id = date_id + self.timestamp = timestamp + + def make(self) -> List: + return [self.id, self.timestamp] diff --git a/titles/wacca/handlers/user_info.py b/titles/wacca/handlers/user_info.py index 6498488..5665676 100644 --- a/titles/wacca/handlers/user_info.py +++ b/titles/wacca/handlers/user_info.py @@ -1,7 +1,7 @@ from typing import List, Dict from titles.wacca.handlers.base import BaseRequest, BaseResponse -from titles.wacca.handlers.helpers import UserOption +from titles.wacca.handlers.helpers import UserOption, DateUpdate # ---user/info/update--- class UserInfoUpdateRequest(BaseRequest): @@ -9,12 +9,16 @@ class UserInfoUpdateRequest(BaseRequest): super().__init__(data) self.profileId = int(self.params[0]) self.optsUpdated: List[UserOption] = [] - self.datesUpdated: List = self.params[3] + self.unknown2: List = self.params[2] + self.datesUpdated: List[DateUpdate] = [] self.favoritesAdded: List[int] = self.params[4] self.favoritesRemoved: List[int] = self.params[5] - for x in self.params[2]: + for x in self.params[1]: self.optsUpdated.append(UserOption(x[0], x[1])) + + for x in self.params[3]: + self.datesUpdated.append(DateUpdate[x[0], x[1]]) # ---user/info/getMyroom--- TODO: Understand this better class UserInfogetMyroomRequest(BaseRequest): From cd78ecd7ea6cd4b7449c657a52cc528396d4ec72 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 13:07:10 -0500 Subject: [PATCH 23/37] wacca: fix typo in UserInfoUpdateRequest --- titles/wacca/handlers/user_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/wacca/handlers/user_info.py b/titles/wacca/handlers/user_info.py index 5665676..287b742 100644 --- a/titles/wacca/handlers/user_info.py +++ b/titles/wacca/handlers/user_info.py @@ -18,7 +18,7 @@ class UserInfoUpdateRequest(BaseRequest): self.optsUpdated.append(UserOption(x[0], x[1])) for x in self.params[3]: - self.datesUpdated.append(DateUpdate[x[0], x[1]]) + self.datesUpdated.append(DateUpdate([x[0], x[1]])) # ---user/info/getMyroom--- TODO: Understand this better class UserInfogetMyroomRequest(BaseRequest): From 2da12e515e35ef2abd7581d761a96c66d8b67306 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 13:07:36 -0500 Subject: [PATCH 24/37] wacca: see previous --- titles/wacca/handlers/user_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/wacca/handlers/user_info.py b/titles/wacca/handlers/user_info.py index 287b742..d30da39 100644 --- a/titles/wacca/handlers/user_info.py +++ b/titles/wacca/handlers/user_info.py @@ -18,7 +18,7 @@ class UserInfoUpdateRequest(BaseRequest): self.optsUpdated.append(UserOption(x[0], x[1])) for x in self.params[3]: - self.datesUpdated.append(DateUpdate([x[0], x[1]])) + self.datesUpdated.append(DateUpdate(x[0], x[1])) # ---user/info/getMyroom--- TODO: Understand this better class UserInfogetMyroomRequest(BaseRequest): From 45fedd8425d6fc8e29e0bcc4f671a374d381c3bb Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 13:12:06 -0500 Subject: [PATCH 25/37] wacca: tidy up UserStatusUpdateRequestV2 --- titles/wacca/handlers/user_status.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/titles/wacca/handlers/user_status.py b/titles/wacca/handlers/user_status.py index b852dd1..5874fec 100644 --- a/titles/wacca/handlers/user_status.py +++ b/titles/wacca/handlers/user_status.py @@ -275,11 +275,6 @@ class UserStatusUpdateRequestV1(BaseRequest): self.itemsRecieved.append(GenericItemRecv(itm[0], itm[1], itm[2])) class UserStatusUpdateRequestV2(UserStatusUpdateRequestV1): - isContinue = False - isFirstPlayFree = False - itemsUsed = [] - lastSongInfo: LastSongDetail - def __init__(self, data: Dict) -> None: super().__init__(data) self.isContinue = bool(data["params"][3]) From 4c64554383c5de1b9b87583f7a4896c1794df828 Mon Sep 17 00:00:00 2001 From: Midorica Date: Fri, 3 Mar 2023 13:27:22 -0500 Subject: [PATCH 26/37] pushing small typo for title port on both guides --- docs/INSTALL_UBUNTU.md | 2 +- docs/INSTALL_WINDOWS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/INSTALL_UBUNTU.md b/docs/INSTALL_UBUNTU.md index eaabb0c..710c757 100644 --- a/docs/INSTALL_UBUNTU.md +++ b/docs/INSTALL_UBUNTU.md @@ -96,7 +96,7 @@ sudo ufw allow 8443 sudo ufw allow 22345 sudo ufw allow 8090 sudo ufw allow 8444 -sudo ufw allow 9000 +sudo ufw allow 8080 ``` ## Running the ARTEMiS instance diff --git a/docs/INSTALL_WINDOWS.md b/docs/INSTALL_WINDOWS.md index abd1e43..e88def3 100644 --- a/docs/INSTALL_WINDOWS.md +++ b/docs/INSTALL_WINDOWS.md @@ -57,7 +57,7 @@ title: ## Firewall Adjustements Make sure the following ports are open both on your router and local Windows firewall in case you want to use this for public use (NOT recommended): -> Port 80 (TCP), 443 (TCP), 8443 (TCP), 22345 (TCP), 8090 (TCP) **webui, 8444 (TCP) **mucha, 9000 (TCP) +> Port 80 (TCP), 443 (TCP), 8443 (TCP), 22345 (TCP), 8080 (TCP), 8090 (TCP) **webui, 8444 (TCP) **mucha ## Running the ARTEMiS instance > python index.py From fae6b77403926ed0b7e83e60022579effc6a4597 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 15:03:57 -0500 Subject: [PATCH 27/37] core: TESTING fix for get_machine --- core/data/schema/arcade.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/data/schema/arcade.py b/core/data/schema/arcade.py index 117c3fe..921e5b5 100644 --- a/core/data/schema/arcade.py +++ b/core/data/schema/arcade.py @@ -50,9 +50,20 @@ arcade_owner = Table( class ArcadeData(BaseData): def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]: if serial is not None: - sql = machine.select(machine.c.serial == serial) + serial = serial.replace("-", "") + if len(serial) == 11: + sql = machine.select(machine.c.serial.like(f"{serial}%")) + + elif len(serial) == 15: + sql = machine.select(machine.c.serial == serial) + + else: + self.logger.error(f"{__name__ }: Malformed serial {serial}") + return None + elif id is not None: sql = machine.select(machine.c.id == id) + else: self.logger.error(f"{__name__ }: Need either serial or ID to look up!") return None @@ -110,4 +121,4 @@ class ArcadeData(BaseData): return result.lastrowid def generate_keychip_serial(self, platform_id: int) -> str: - pass \ No newline at end of file + pass From 101b966e3a4f2f80f8acaab331b1297a22e51e02 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 15:39:14 -0500 Subject: [PATCH 28/37] add allnet request debug logging --- core/allnet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/allnet.py b/core/allnet.py index 587aeab..01a3610 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -115,6 +115,7 @@ class AllnetServlet: else: resp = AllnetPowerOnResponse2() + self.logger.debug(f"Allnet request: {vars(req)}") if req.game_id not in self.uri_registry: 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) @@ -154,6 +155,7 @@ class AllnetServlet: msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}" self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg) self.logger.info(msg) + self.logger.debug(f"Allnet response: {vars(resp)}") return self.dict_to_http_form_string([vars(resp)]).encode("utf-8") From f6cfb9e36dbdf3f56896a978bf57326067291019 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 15:45:21 -0500 Subject: [PATCH 29/37] allnet: fix "none" in response --- core/allnet.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/allnet.py b/core/allnet.py index 01a3610..4f886f9 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -14,6 +14,7 @@ from time import strptime from core.config import CoreConfig from core.data import Data from core.utils import Utils +from core.const import * class AllnetServlet: def __init__(self, core_cfg: CoreConfig, cfg_folder: str): @@ -137,15 +138,15 @@ class AllnetServlet: if machine is not None: arcade = self.data.arcade.get_arcade(machine["arcade"]) - resp.country = arcade["country"] if machine["country"] is None else machine["country"] + resp.country = arcade["country"] if machine["country"] is None else machine["country"] if machine["country"] is None else AllnetCountryCode.JAPAN.value resp.place_id = arcade["id"] resp.allnet_id = machine["id"] - resp.name = arcade["name"] - resp.nickname = arcade["nickname"] - resp.region0 = arcade["region_id"] - resp.region_name0 = arcade["country"] - resp.region_name1 = arcade["state"] - resp.region_name2 = arcade["city"] + resp.name = arcade["name"] if arcade["name"] is not None else "" + resp.nickname = arcade["nickname"] if arcade["nickname"] is not None else "" + resp.region0 = arcade["region_id"] if arcade["region_id"] is not None else AllnetJapanRegionId.AICHI.value + resp.region_name0 = arcade["country"] if arcade["country"] is not None else AllnetCountryCode.JAPAN.value + resp.region_name1 = arcade["state"] if arcade["state"] is not None else AllnetJapanRegionId.AICHI.name + resp.region_name2 = arcade["city"] if arcade["city"] is not None else "" resp.client_timezone = arcade["timezone"] if arcade["timezone"] is not None else "+0900" int_ver = req.ver.replace(".", "") From b35e7d69831ee6ae13c3080a6bd16343e05ce4ed Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 15:49:33 -0500 Subject: [PATCH 30/37] allnet: hotfix for country --- core/allnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/allnet.py b/core/allnet.py index 4f886f9..233469a 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -138,7 +138,7 @@ class AllnetServlet: if machine is not None: arcade = self.data.arcade.get_arcade(machine["arcade"]) - resp.country = arcade["country"] if machine["country"] is None else machine["country"] if machine["country"] is None else AllnetCountryCode.JAPAN.value + resp.country = arcade["country"] if machine["country"] is None else machine["country"] if machine["country"] is not None else AllnetCountryCode.JAPAN.value resp.place_id = arcade["id"] resp.allnet_id = machine["id"] resp.name = arcade["name"] if arcade["name"] is not None else "" From 34e2c50fb5b1fad897c6f69ba60f5687fe3f3029 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 15:52:58 -0500 Subject: [PATCH 31/37] allnet: see previous --- core/allnet.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/allnet.py b/core/allnet.py index 233469a..71eeb33 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -138,7 +138,11 @@ class AllnetServlet: if machine is not None: arcade = self.data.arcade.get_arcade(machine["arcade"]) - resp.country = arcade["country"] if machine["country"] is None else machine["country"] if machine["country"] is not None else AllnetCountryCode.JAPAN.value + country = arcade["country"] if machine["country"] is None else machine["country"] + if country is None: + country = AllnetCountryCode.JAPAN.value + + resp.country = country resp.place_id = arcade["id"] resp.allnet_id = machine["id"] resp.name = arcade["name"] if arcade["name"] is not None else "" From f24d554a448673d67dfdc52286e5301a7832f278 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 16:26:07 -0500 Subject: [PATCH 32/37] wacca: pull region_id from allnet if available --- titles/wacca/base.py | 21 +++++++++++++++++++-- titles/wacca/const.py | 23 ++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 25ec33b..64b4248 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -91,10 +91,27 @@ class WaccaBase(): def handle_housing_start_request(self, data: Dict) -> Dict: req = HousingStartRequestV1(data) - if req.appVersion.country != "JPN" and req.appVersion.country in [region.name for region in WaccaConstants.Region]: + machine = self.data.arcade.get_machine(req.chipId) + if machine is not None: + arcade = self.data.arcade.get_arcade(machine["arcade"]) + + allnet_region_id = arcade["region_id"] + + if req.appVersion.country == "JPN": + if allnet_region_id is not None: + region = WaccaConstants.allnet_region_id_to_wacca_region(allnet_region_id) + + if region is None: + region_id = self.region_id + + else: + region_id = self.region_id + + elif req.appVersion.country in WaccaConstants.VALID_COUNTRIES: region_id = WaccaConstants.Region[req.appVersion.country] + else: - region_id = self.region_id + region_id = 0 resp = HousingStartResponseV1(region_id) return resp.make() diff --git a/titles/wacca/const.py b/titles/wacca/const.py index a984682..f072143 100644 --- a/titles/wacca/const.py +++ b/titles/wacca/const.py @@ -1,4 +1,7 @@ from enum import Enum +from typing import Optional + +from core.const import AllnetJapanRegionId class WaccaConstants(): CONFIG_NAME = "wacca.yaml" @@ -165,4 +168,22 @@ class WaccaConstants(): @classmethod def game_ver_to_string(cls, ver: int): - return cls.VERSION_NAMES[ver] \ No newline at end of file + return cls.VERSION_NAMES[ver] + + @classmethod + def allnet_region_id_to_wacca_region(cls, region: int) -> Optional[Region]: + try: + return [ + cls.Region.NONE, cls.Region.AICHI, cls.Region.AOMORI, cls.Region.AKITA, cls.Region.ISHIKAWA, + cls.Region.IBARAKI, cls.Region.IWATE, cls.Region.EHIME, cls.Region.OITA, cls.Region.OSAKA, + cls.Region.OKAYAMA, cls.Region.OKINAWA, cls.Region.KAGAWA, cls.Region.KAGOSHIMA, cls.Region.KANAGAWA, + cls.Region.GIFU, cls.Region.KYOTO, cls.Region.KUMAMOTO, cls.Region.GUNMA, cls.Region.KOCHI, + cls.Region.SAITAMA, cls.Region.SAGA, cls.Region.SHIGA, cls.Region.SHIZUOKA, cls.Region.SHIMANE, + cls.Region.CHIBA, cls.Region.TOKYO, cls.Region.TOKUSHIMA, cls.Region.TOCHIGI, cls.Region.TOTTORI, + cls.Region.TOYAMA, cls.Region.NAGASAKI, cls.Region.NAGANO, cls.Region.NARA, cls.Region.NIIGATA, + cls.Region.HYOGO, cls.Region.HIROSHIMA, cls.Region.FUKUI, cls.Region.FUKUOKA, cls.Region.FUKUSHIMA, + cls.Region.HOKKAIDO, cls.Region.MIE, cls.Region.MIYAGI, cls.Region.MIYAZAKI, cls.Region.YAMAGATA, + cls.Region.YAMAGUCHI, cls.Region.YAMANASHI, cls.Region.WAKAYAMA, + ][region] + except: return None + From 9ad724d64beee2f0c27891acfe1d8452b614dc7c Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 16:28:42 -0500 Subject: [PATCH 33/37] wacca: fix edge case in handle_housing_start_request --- titles/wacca/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 64b4248..9867d5a 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -103,6 +103,8 @@ class WaccaBase(): if region is None: region_id = self.region_id + else: + region_id = region else: region_id = self.region_id @@ -111,7 +113,7 @@ class WaccaBase(): region_id = WaccaConstants.Region[req.appVersion.country] else: - region_id = 0 + region_id = WaccaConstants.Region.NONE resp = HousingStartResponseV1(region_id) return resp.make() From c26f6b7b1d500b052140079dcad99ca2630e3c70 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 16:31:42 -0500 Subject: [PATCH 34/37] wacca: fix typing --- titles/wacca/base.py | 5 +++-- titles/wacca/handlers/helpers.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/titles/wacca/base.py b/titles/wacca/base.py index 9867d5a..2e5001f 100644 --- a/titles/wacca/base.py +++ b/titles/wacca/base.py @@ -9,6 +9,7 @@ from titles.wacca.const import WaccaConstants from titles.wacca.database import WaccaData from titles.wacca.handlers import * +from core.const import AllnetCountryCode class WaccaBase(): def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: @@ -74,6 +75,7 @@ class WaccaBase(): if prefecture_name not in [region.name for region in WaccaConstants.Region]: self.logger.warning(f"Invalid prefecture name {game_cfg.server.prefecture_name} in config file") self.region_id = WaccaConstants.Region.HOKKAIDO + else: self.region_id = WaccaConstants.Region[prefecture_name] @@ -94,10 +96,9 @@ class WaccaBase(): machine = self.data.arcade.get_machine(req.chipId) if machine is not None: arcade = self.data.arcade.get_arcade(machine["arcade"]) - allnet_region_id = arcade["region_id"] - if req.appVersion.country == "JPN": + if req.appVersion.country == AllnetCountryCode.JAPAN.value: if allnet_region_id is not None: region = WaccaConstants.allnet_region_id_to_wacca_region(allnet_region_id) diff --git a/titles/wacca/handlers/helpers.py b/titles/wacca/handlers/helpers.py index 1192237..b96b3dd 100644 --- a/titles/wacca/handlers/helpers.py +++ b/titles/wacca/handlers/helpers.py @@ -78,9 +78,9 @@ class Version(ShortVersion): super().__init__(version, major, minor, patch) split = version.split(".") if len(split) >= 6: - self.country = split[3] + self.country: str = split[3] self.build = int(split[4]) - self.role = split[5] + self.role: str = split[5] else: self.country = country From 2a6842db245745d7b2167235466e52c03802bf4e Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 17:03:19 -0500 Subject: [PATCH 35/37] remove db old-to-new migration --- core/data/database.py | 1614 ----------------------------------------- 1 file changed, 1614 deletions(-) diff --git a/core/data/database.py b/core/data/database.py index 70fc3e0..65b01aa 100644 --- a/core/data/database.py +++ b/core/data/database.py @@ -138,1617 +138,3 @@ class Data: return None self.logger.info(f"Successfully migrated {game} to schema version {version}") - - def dump_db(self): - dbname = self.config.database.name - - self.logger.info("Database dumper for use with the reworked schema") - self.logger.info("Dumping users...") - - sql = f"SELECT * FROM `{dbname}`.`user`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - users = result.fetchall() - - user_list: List[Dict[str, Any]] = [] - for usr in users: - user_list.append({ - "id": usr["id"], - "username": usr["username"], - "email": usr["email"], - "password": usr["password"], - "permissions": usr["permissions"], - "created_date": datetime.strftime(usr["created_date"], "%Y-%m-%d %H:%M:%S"), - "last_login_date": datetime.strftime(usr["accessed_date"], "%Y-%m-%d %H:%M:%S"), - }) - - self.logger.info(f"Done, found {len(user_list)} users") - with open("dbdump-user.json", "w", encoding="utf-8") as f: - f.write(json.dumps(user_list)) - self.logger.info(f"Saved as dbdump-user.json") - - self.logger.info("Dumping cards...") - - sql = f"SELECT * FROM `{dbname}`.`card`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - cards = result.fetchall() - - card_list: List[Dict[str, Any]] = [] - for crd in cards: - card_list.append({ - "id": crd["id"], - "user": crd["user"], - "access_code": crd["access_code"], - "is_locked": crd["is_locked"], - "is_banned": crd["is_banned"], - "created_date": datetime.strftime(crd["created_date"], "%Y-%m-%d %H:%M:%S"), - "last_login_date": datetime.strftime(crd["accessed_date"], "%Y-%m-%d %H:%M:%S"), - }) - - self.logger.info(f"Done, found {len(card_list)} cards") - with open("dbdump-card.json", "w", encoding="utf-8") as f: - f.write(json.dumps(card_list)) - self.logger.info(f"Saved as dbdump-card.json") - - self.logger.info("Dumping arcades...") - - sql = f"SELECT * FROM `{dbname}`.`arcade`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - arcades = result.fetchall() - - arcade_list: List[Dict[str, Any]] = [] - for arc in arcades: - arcade_list.append({ - "id": arc["id"], - "name": arc["name"], - "nickname": arc["name"], - "country": None, - "country_id": None, - "state": None, - "city": None, - "region_id": None, - "timezone": None, - }) - - self.logger.info(f"Done, found {len(arcade_list)} arcades") - with open("dbdump-arcade.json", "w", encoding="utf-8") as f: - f.write(json.dumps(arcade_list)) - self.logger.info(f"Saved as dbdump-arcade.json") - - self.logger.info("Dumping machines...") - - sql = f"SELECT * FROM `{dbname}`.`machine`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - machines = result.fetchall() - - machine_list: List[Dict[str, Any]] = [] - for mech in machines: - if "country" in mech["data"]: - country = mech["data"]["country"] - else: - country = None - - if "ota_enable" in mech["data"]: - ota_enable = mech["data"]["ota_enable"] - else: - ota_enable = None - - machine_list.append({ - "id": mech["id"], - "arcade": mech["arcade"], - "serial": mech["keychip"], - "game": mech["game"], - "board": None, - "country": country, - "timezone": None, - "ota_enable": ota_enable, - "is_cab": False, - }) - - self.logger.info(f"Done, found {len(machine_list)} machines") - with open("dbdump-machine.json", "w", encoding="utf-8") as f: - f.write(json.dumps(machine_list)) - self.logger.info(f"Saved as dbdump-machine.json") - - self.logger.info("Dumping arcade owners...") - - sql = f"SELECT * FROM `{dbname}`.`arcade_owner`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - arcade_owners = result.fetchall() - - owner_list: List[Dict[str, Any]] = [] - for owner in owner_list: - owner_list.append(owner._asdict()) - - self.logger.info(f"Done, found {len(owner_list)} arcade owners") - with open("dbdump-arcade_owner.json", "w", encoding="utf-8") as f: - f.write(json.dumps(owner_list)) - self.logger.info(f"Saved as dbdump-arcade_owner.json") - - self.logger.info("Dumping profiles...") - - sql = f"SELECT * FROM `{dbname}`.`profile`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - profiles = result.fetchall() - - profile_list: Dict[List[Dict[str, Any]]] = {} - for pf in profiles: - game = pf["game"] - - if game not in profile_list: - profile_list[game] = [] - - profile_list[game].append({ - "id": pf["id"], - "user": pf["user"], - "version": pf["version"], - "use_count": pf["use_count"], - "name": pf["name"], - "game_id": pf["game_id"], - "mods": pf["mods"], - "data": pf["data"], - }) - - self.logger.info(f"Done, found profiles for {len(profile_list)} games") - with open("dbdump-profile.json", "w", encoding="utf-8") as f: - f.write(json.dumps(profile_list)) - self.logger.info(f"Saved as dbdump-profile.json") - - self.logger.info("Dumping scores...") - - sql = f"SELECT * FROM `{dbname}`.`score`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - scores = result.fetchall() - - score_list: Dict[List[Dict[str, Any]]] = {} - for sc in scores: - game = sc["game"] - - if game not in score_list: - score_list[game] = [] - - score_list[game].append({ - "id": sc["id"], - "user": sc["user"], - "version": sc["version"], - "song_id": sc["song_id"], - "chart_id": sc["chart_id"], - "score1": sc["score1"], - "score2": sc["score2"], - "fc1": sc["fc1"], - "fc2": sc["fc2"], - "cleared": sc["cleared"], - "grade": sc["grade"], - "data": sc["data"], - }) - - self.logger.info(f"Done, found scores for {len(score_list)} games") - with open("dbdump-score.json", "w", encoding="utf-8") as f: - f.write(json.dumps(score_list)) - self.logger.info(f"Saved as dbdump-score.json") - - self.logger.info("Dumping achievements...") - - sql = f"SELECT * FROM `{dbname}`.`achievement`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - achievements = result.fetchall() - - achievement_list: Dict[List[Dict[str, Any]]] = {} - for ach in achievements: - game = ach["game"] - - if game not in achievement_list: - achievement_list[game] = [] - - achievement_list[game].append({ - "id": ach["id"], - "user": ach["user"], - "version": ach["version"], - "type": ach["type"], - "achievement_id": ach["achievement_id"], - "data": ach["data"], - }) - - self.logger.info(f"Done, found achievements for {len(achievement_list)} games") - with open("dbdump-achievement.json", "w", encoding="utf-8") as f: - f.write(json.dumps(achievement_list)) - self.logger.info(f"Saved as dbdump-achievement.json") - - self.logger.info("Dumping items...") - - sql = f"SELECT * FROM `{dbname}`.`item`" - - result = self.base.execute(sql) - if result is None: - self.logger.error("Failed") - return None - items = result.fetchall() - - item_list: Dict[List[Dict[str, Any]]] = {} - for itm in items: - game = itm["game"] - - if game not in item_list: - item_list[game] = [] - - item_list[game].append({ - "id": itm["id"], - "user": itm["user"], - "version": itm["version"], - "type": itm["type"], - "item_id": itm["item_id"], - "data": ach["data"], - }) - - self.logger.info(f"Done, found items for {len(item_list)} games") - with open("dbdump-item.json", "w", encoding="utf-8") as f: - f.write(json.dumps(item_list)) - self.logger.info(f"Saved as dbdump-item.json") - - def restore_from_old_schema(self): - # Import the tables we expect to be there - from core.data.schema.user import aime_user - from core.data.schema.card import aime_card - from core.data.schema.arcade import arcade, machine, arcade_owner - from sqlalchemy.dialects.mysql import Insert - - # Make sure that all the tables we're trying to access exist - self.create_database() - - # Import the data, making sure that dependencies are accounted for - if os.path.exists("dbdump-user.json"): - users = [] - with open("dbdump-user.json", "r", encoding="utf-8") as f: - users = json.load(f) - - self.logger.info(f"Load {len(users)} users") - - for user in users: - sql = Insert(aime_user).values(**user) - - conflict = sql.on_duplicate_key_update(**user) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert user {user['id']}") - continue - self.logger.info(f"Inserted user {user['id']} -> {result.lastrowid}") - - if os.path.exists("dbdump-card.json"): - cards = [] - with open("dbdump-card.json", "r", encoding="utf-8") as f: - cards = json.load(f) - - self.logger.info(f"Load {len(cards)} cards") - - for card in cards: - sql = Insert(aime_card).values(**card) - - conflict = sql.on_duplicate_key_update(**card) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert card {card['id']}") - continue - self.logger.info(f"Inserted card {card['id']} -> {result.lastrowid}") - - if os.path.exists("dbdump-arcade.json"): - arcades = [] - with open("dbdump-arcade.json", "r", encoding="utf-8") as f: - arcades = json.load(f) - - self.logger.info(f"Load {len(arcades)} arcades") - - for ac in arcades: - sql = Insert(arcade).values(**ac) - - conflict = sql.on_duplicate_key_update(**ac) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert arcade {ac['id']}") - continue - self.logger.info(f"Inserted arcade {ac['id']} -> {result.lastrowid}") - - if os.path.exists("dbdump-arcade_owner.json"): - ac_owners = [] - with open("dbdump-arcade_owner.json", "r", encoding="utf-8") as f: - ac_owners = json.load(f) - - self.logger.info(f"Load {len(ac_owners)} arcade owners") - - for owner in ac_owners: - sql = Insert(arcade_owner).values(**owner) - - conflict = sql.on_duplicate_key_update(**owner) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert arcade_owner {owner['user']}") - continue - self.logger.info(f"Inserted arcade_owner {owner['user']} -> {result.lastrowid}") - - if os.path.exists("dbdump-machine.json"): - mechs = [] - with open("dbdump-machine.json", "r", encoding="utf-8") as f: - mechs = json.load(f) - - self.logger.info(f"Load {len(mechs)} machines") - - for mech in mechs: - sql = Insert(machine).values(**mech) - - conflict = sql.on_duplicate_key_update(**mech) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert machine {mech['id']}") - continue - self.logger.info(f"Inserted machine {mech['id']} -> {result.lastrowid}") - - # Now the fun part, grabbing all our scores, profiles, items, and achievements and trying - # to conform them to our current, freeform schema. This will be painful... - profiles = {} - items = {} - scores = {} - achievements = {} - - if os.path.exists("dbdump-profile.json"): - with open("dbdump-profile.json", "r", encoding="utf-8") as f: - profiles = json.load(f) - - self.logger.info(f"Load {len(profiles)} profiles") - - if os.path.exists("dbdump-item.json"): - with open("dbdump-item.json", "r", encoding="utf-8") as f: - items = json.load(f) - - self.logger.info(f"Load {len(items)} items") - - if os.path.exists("dbdump-score.json"): - with open("dbdump-score.json", "r", encoding="utf-8") as f: - scores = json.load(f) - - self.logger.info(f"Load {len(scores)} scores") - - if os.path.exists("dbdump-achievement.json"): - with open("dbdump-achievement.json", "r", encoding="utf-8") as f: - achievements = json.load(f) - - self.logger.info(f"Load {len(achievements)} achievements") - - # Chuni / Chusan - if os.path.exists("titles/chuni/schema"): - from titles.chuni.schema.item import character, item, duel, map, map_area - from titles.chuni.schema.profile import profile, profile_ex, option, option_ex - from titles.chuni.schema.profile import recent_rating, activity, charge, emoney - from titles.chuni.schema.profile import overpower - from titles.chuni.schema.score import best_score, course - - chuni_profiles = [] - chuni_items = [] - chuni_scores = [] - - if "SDBT" in profiles: - chuni_profiles = profiles["SDBT"] - if "SDBT" in items: - chuni_items = items["SDBT"] - if "SDBT" in scores: - chuni_scores = scores["SDBT"] - if "SDHD" in profiles: - chuni_profiles += profiles["SDHD"] - if "SDHD" in items: - chuni_items += items["SDHD"] - if "SDHD" in scores: - chuni_scores += scores["SDHD"] - - self.logger.info(f"Importing {len(chuni_profiles)} chunithm/chunithm new profiles") - - for pf in chuni_profiles: - if type(pf["data"]) is not dict: - pf["data"] = json.loads(pf["data"]) - pf_data = pf["data"] - - # data - if "userData" in pf_data: - pf_data["userData"]["userName"] = bytes([ord(c) for c in pf_data["userData"]["userName"]]).decode("utf-8") - pf_data["userData"]["user"] = pf["user"] - pf_data["userData"]["version"] = pf["version"] - pf_data["userData"].pop("accessCode") - - if pf_data["userData"]["lastRomVersion"].startswith("2."): - pf_data["userData"]["version"] += 10 - - pf_data["userData"] = self.base.fix_bools(pf_data["userData"]) - - sql = Insert(profile).values(**pf_data["userData"]) - conflict = sql.on_duplicate_key_update(**pf_data["userData"]) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile data for {pf['user']}") - continue - self.logger.info(f"Inserted chuni profile for {pf['user']} ->{result.lastrowid}") - - # data_ex - if "userDataEx" in pf_data and len(pf_data["userDataEx"]) > 0: - pf_data["userDataEx"][0]["user"] = pf["user"] - pf_data["userDataEx"][0]["version"] = pf["version"] - - pf_data["userDataEx"] = self.base.fix_bools(pf_data["userDataEx"][0]) - - sql = Insert(profile_ex).values(**pf_data["userDataEx"]) - conflict = sql.on_duplicate_key_update(**pf_data["userDataEx"]) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile data_ex for {pf['user']}") - continue - self.logger.info(f"Inserted chuni profile data_ex for {pf['user']} ->{result.lastrowid}") - - # option - if "userGameOption" in pf_data: - pf_data["userGameOption"]["user"] = pf["user"] - - pf_data["userGameOption"] = self.base.fix_bools(pf_data["userGameOption"]) - - sql = Insert(option).values(**pf_data["userGameOption"]) - conflict = sql.on_duplicate_key_update(**pf_data["userGameOption"]) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile options for {pf['user']}") - continue - self.logger.info(f"Inserted chuni profile options for {pf['user']} ->{result.lastrowid}") - - # option_ex - if "userGameOptionEx" in pf_data and len(pf_data["userGameOptionEx"]) > 0: - pf_data["userGameOptionEx"][0]["user"] = pf["user"] - - pf_data["userGameOptionEx"] = self.base.fix_bools(pf_data["userGameOptionEx"][0]) - - sql = Insert(option_ex).values(**pf_data["userGameOptionEx"]) - conflict = sql.on_duplicate_key_update(**pf_data["userGameOptionEx"]) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile option_ex for {pf['user']}") - continue - self.logger.info(f"Inserted chuni profile option_ex for {pf['user']} ->{result.lastrowid}") - - # recent_rating - if "userRecentRatingList" in pf_data: - rr = { - "user": pf["user"], - "recentRating": pf_data["userRecentRatingList"] - } - - sql = Insert(recent_rating).values(**rr) - conflict = sql.on_duplicate_key_update(**rr) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile recent_rating for {pf['user']}") - continue - self.logger.info(f"Inserted chuni profile recent_rating for {pf['user']} ->{result.lastrowid}") - - # activity - if "userActivityList" in pf_data: - for act in pf_data["userActivityList"]: - act["user"] = pf["user"] - - sql = Insert(activity).values(**act) - conflict = sql.on_duplicate_key_update(**act) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile activity for {pf['user']}") - else: - self.logger.info(f"Inserted chuni profile activity for {pf['user']} ->{result.lastrowid}") - - # charge - if "userChargeList" in pf_data: - for cg in pf_data["userChargeList"]: - cg["user"] = pf["user"] - - cg = self.base.fix_bools(cg) - - sql = Insert(charge).values(**cg) - conflict = sql.on_duplicate_key_update(**cg) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile charge for {pf['user']}") - else: - self.logger.info(f"Inserted chuni profile charge for {pf['user']} ->{result.lastrowid}") - - # emoney - if "userEmoneyList" in pf_data: - for emon in pf_data["userEmoneyList"]: - emon["user"] = pf["user"] - - sql = Insert(emoney).values(**emon) - conflict = sql.on_duplicate_key_update(**emon) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile emoney for {pf['user']}") - else: - self.logger.info(f"Inserted chuni profile emoney for {pf['user']} ->{result.lastrowid}") - - # overpower - if "userOverPowerList" in pf_data: - for op in pf_data["userOverPowerList"]: - op["user"] = pf["user"] - - sql = Insert(overpower).values(**op) - conflict = sql.on_duplicate_key_update(**op) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni profile overpower for {pf['user']}") - else: - self.logger.info(f"Inserted chuni profile overpower for {pf['user']} ->{result.lastrowid}") - - # map_area - if "userMapAreaList" in pf_data: - for ma in pf_data["userMapAreaList"]: - ma["user"] = pf["user"] - - ma = self.base.fix_bools(ma) - - sql = Insert(map_area).values(**ma) - conflict = sql.on_duplicate_key_update(**ma) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni map_area for {pf['user']}") - else: - self.logger.info(f"Inserted chuni map_area for {pf['user']} ->{result.lastrowid}") - - #duel - if "userDuelList" in pf_data: - for ma in pf_data["userDuelList"]: - ma["user"] = pf["user"] - - ma = self.base.fix_bools(ma) - - sql = Insert(duel).values(**ma) - conflict = sql.on_duplicate_key_update(**ma) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni duel for {pf['user']}") - else: - self.logger.info(f"Inserted chuni duel for {pf['user']} ->{result.lastrowid}") - - # map - if "userMapList" in pf_data: - for ma in pf_data["userMapList"]: - ma["user"] = pf["user"] - - ma = self.base.fix_bools(ma) - - sql = Insert(map).values(**ma) - conflict = sql.on_duplicate_key_update(**ma) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni map for {pf['user']}") - else: - self.logger.info(f"Inserted chuni map for {pf['user']} ->{result.lastrowid}") - - self.logger.info(f"Importing {len(chuni_items)} chunithm/chunithm new items") - - for i in chuni_items: - if type(i["data"]) is not dict: - i["data"] = json.loads(i["data"]) - i_data = i["data"] - - i_data["user"] = i["user"] - - i_data = self.base.fix_bools(i_data) - - try: i_data.pop("assignIllust") - except: pass - - try: i_data.pop("exMaxLv") - except: pass - - if i["type"] == 20: #character - sql = Insert(character).values(**i_data) - else: - sql = Insert(item).values(**i_data) - - conflict = sql.on_duplicate_key_update(**i_data) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert chuni item for user {i['user']}") - - else: - self.logger.info(f"Inserted chuni item for user {i['user']} {i['item_id']} -> {result.lastrowid}") - - self.logger.info(f"Importing {len(chuni_scores)} chunithm/chunithm new scores") - - for sc in chuni_scores: - if type(sc["data"]) is not dict: - sc["data"] = json.loads(sc["data"]) - - score_data = self.base.fix_bools(sc["data"]) - - try: score_data.pop("theoryCount") - except: pass - - try: score_data.pop("ext1") - except: pass - - score_data["user"] = sc["user"] - - sql = Insert(best_score).values(**score_data) - conflict = sql.on_duplicate_key_update(**score_data) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to put chuni score for user {sc['user']}") - else: - self.logger.info(f"Inserted chuni score for user {sc['user']} {sc['song_id']}/{sc['chart_id']} -> {result.lastrowid}") - - else: - self.logger.info(f"Chuni/Chusan not found, skipping...") - - # CXB - if os.path.exists("titles/cxb/schema"): - from titles.cxb.schema.item import energy - from titles.cxb.schema.profile import profile - from titles.cxb.schema.score import score, ranking - - cxb_profiles = [] - cxb_items = [] - cxb_scores = [] - - if "SDCA" in profiles: - cxb_profiles = profiles["SDCA"] - if "SDCA" in items: - cxb_items = items["SDCA"] - if "SDCA" in scores: - cxb_scores = scores["SDCA"] - - self.logger.info(f"Importing {len(cxb_profiles)} CXB profiles") - - for pf in cxb_profiles: - user = pf["user"] - version = pf["version"] - pf_data = pf["data"]["data"] - pf_idx = pf["data"]["index"] - - for x in range(len(pf_data)): - sql = Insert(profile).values( - user = user, - version = version, - index = int(pf_idx[x]), - data = json.loads(pf_data[x]) if type(pf_data[x]) is not dict else pf_data[x] - ) - - conflict = sql.on_duplicate_key_update( - user = user, - version = version, - index = int(pf_idx[x]), - data = json.loads(pf_data[x]) if type(pf_data[x]) is not dict else pf_data[x] - ) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert CXB profile for user {user} Index {pf_idx[x]}") - - self.logger.info(f"Importing {len(cxb_scores)} CXB scores") - - for sc in cxb_scores: - user = sc["user"] - version = sc["version"] - mcode = sc["data"]["mcode"] - index = sc["data"]["index"] - - sql = Insert(score).values( - user = user, - game_version = version, - song_mcode = mcode, - song_index = index, - data = sc["data"] - ) - - conflict = sql.on_duplicate_key_update( - user = user, - game_version = version, - song_mcode = mcode, - song_index = index, - data = sc["data"] - ) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert CXB score for user {user} mcode {mcode}") - - self.logger.info(f"Importing {len(cxb_items)} CXB items") - - for it in cxb_items: - user = it["user"] - - if it["type"] == 3: # energy - sql = Insert(energy).values( - user = user, - energy = it["data"]["total"] - ) - - conflict = sql.on_duplicate_key_update( - user = user, - energy = it["data"]["total"] - ) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert CXB energy for user {user}") - - elif it["type"] == 2: - sql = Insert(ranking).values( - user = user, - rev_id = it["data"]["rid"], - song_id = it["data"]["sc"][1] if len(it["data"]["sc"]) > 1 else None, - score = it["data"]["sc"][0], - clear = it["data"]["clear"], - ) - - conflict = sql.on_duplicate_key_update( - user = user, - rev_id = it["data"]["rid"], - song_id = it["data"]["sc"][1] if len(it["data"]["sc"]) > 1 else None, - score = it["data"]["sc"][0], - clear = it["data"]["clear"], - ) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert CXB ranking for user {user}") - - else: - self.logger.error(f"Unknown CXB item type {it['type']} for user {user}") - - else: - self.logger.info(f"CXB not found, skipping...") - - # Diva - if os.path.exists("titles/diva/schema"): - from titles.diva.schema.profile import profile - from titles.diva.schema.score import score - from titles.diva.schema.item import shop - - diva_profiles = [] - diva_scores = [] - - if "SBZV" in profiles: - diva_profiles = profiles["SBZV"] - if "SBZV" in scores: - diva_scores = scores["SBZV"] - - self.logger.info(f"Importing {len(diva_profiles)} Diva profiles") - - for pf in diva_profiles: - pf["data"]["user"] = pf["user"] - pf["data"]["version"] = pf["version"] - pf_data = pf["data"] - - if "mdl_eqp_ary" in pf["data"]: - sql = Insert(shop).values( - user = user, - version = version, - mdl_eqp_ary = pf["data"]["mdl_eqp_ary"], - ) - conflict = sql.on_duplicate_key_update( - user = user, - version = version, - mdl_eqp_ary = pf["data"]["mdl_eqp_ary"] - ) - self.base.execute(conflict) - pf["data"].pop("mdl_eqp_ary") - - sql = Insert(profile).values(**pf_data) - conflict = sql.on_duplicate_key_update(**pf_data) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert diva profile for {pf['user']}") - - self.logger.info(f"Importing {len(diva_scores)} Diva scores") - - for sc in diva_scores: - user = sc["user"] - - clr_kind = -1 - for x in sc["data"]["stg_clr_kind"].split(","): - if x != "-1": - clr_kind = x - - cool_ct = 0 - for x in sc["data"]["stg_cool_cnt"].split(","): - if x != "0": - cool_ct = x - - fine_ct = 0 - for x in sc["data"]["stg_fine_cnt"].split(","): - if x != "0": - fine_ct = x - - safe_ct = 0 - for x in sc["data"]["stg_safe_cnt"].split(","): - if x != "0": - safe_ct = x - - sad_ct = 0 - for x in sc["data"]["stg_sad_cnt"].split(","): - if x != "0": - sad_ct = x - - worst_ct = 0 - for x in sc["data"]["stg_wt_wg_cnt"].split(","): - if x != "0": - worst_ct = x - - max_cmb = 0 - for x in sc["data"]["stg_max_cmb"].split(","): - if x != "0": - max_cmb = x - - sql = Insert(score).values( - user = user, - version = sc["version"], - pv_id = sc["song_id"], - difficulty = sc["chart_id"], - score = sc["score1"], - atn_pnt = sc["score2"], - clr_kind = clr_kind, - sort_kind = sc["data"]["sort_kind"], - cool = cool_ct, - fine = fine_ct, - safe = safe_ct, - sad = sad_ct, - worst = worst_ct, - max_combo = max_cmb, - ) - - conflict = sql.on_duplicate_key_update(user = user, - version = sc["version"], - pv_id = sc["song_id"], - difficulty = sc["chart_id"], - score = sc["score1"], - atn_pnt = sc["score2"], - clr_kind = clr_kind, - sort_kind = sc["data"]["sort_kind"], - cool = cool_ct, - fine = fine_ct, - safe = safe_ct, - sad = sad_ct, - worst = worst_ct, - max_combo = max_cmb - ) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert diva score for {pf['user']}") - - else: - self.logger.info(f"Diva not found, skipping...") - - # Ongeki - if os.path.exists("titles/ongeki/schema"): - from titles.ongeki.schema.item import card, deck, character, boss, story - from titles.ongeki.schema.item import chapter, item, music_item, login_bonus - from titles.ongeki.schema.item import event_point, mission_point, scenerio - from titles.ongeki.schema.item import trade_item, event_music, tech_event - from titles.ongeki.schema.profile import profile, option, activity, recent_rating - from titles.ongeki.schema.profile import rating_log, training_room, kop - from titles.ongeki.schema.score import score_best, tech_count, playlog - from titles.ongeki.schema.log import session_log - - item_types = { - "character": 20, - "story": 21, - "card": 22, - "deck": 23, - "login": 24, - "chapter": 25 - } - - ongeki_profiles = [] - ongeki_items = [] - ongeki_scores = [] - - if "SDDT" in profiles: - ongeki_profiles = profiles["SDDT"] - if "SDDT" in items: - ongeki_items = items["SDDT"] - if "SDDT" in scores: - ongeki_scores = scores["SDDT"] - - self.logger.info(f"Importing {len(ongeki_profiles)} ongeki profiles") - - for pf in ongeki_profiles: - user = pf["user"] - version = pf["version"] - pf_data = pf["data"] - - pf_data["userData"]["user"] = user - pf_data["userData"]["version"] = version - pf_data["userData"].pop("accessCode") - - sql = Insert(profile).values(**pf_data["userData"]) - conflict = sql.on_duplicate_key_update(**pf_data["userData"]) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki profile data for user {pf['user']}") - continue - - pf_data["userOption"]["user"] = user - - sql = Insert(option).values(**pf_data["userOption"]) - conflict = sql.on_duplicate_key_update(**pf_data["userOption"]) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki profile options for user {pf['user']}") - continue - - for pf_list in pf_data["userActivityList"]: - pf_list["user"] = user - - sql = Insert(activity).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki profile activity for user {pf['user']}") - continue - - sql = Insert(recent_rating).values( - user = user, - recentRating = pf_data["userRecentRatingList"] - ) - - conflict = sql.on_duplicate_key_update( - user = user, - recentRating = pf_data["userRecentRatingList"] - ) - result = self.base.execute(conflict) - - if result is None: - self.logger.error(f"Failed to insert ongeki profile recent rating for user {pf['user']}") - continue - - for pf_list in pf_data["userRatinglogList"]: - pf_list["user"] = user - - sql = Insert(rating_log).values(**pf_list) - - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki profile rating log for user {pf['user']}") - continue - - for pf_list in pf_data["userTrainingRoomList"]: - pf_list["user"] = user - - sql = Insert(training_room).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki profile training room for user {pf['user']}") - continue - - if "userKopList" in pf_data: - for pf_list in pf_data["userKopList"]: - pf_list["user"] = user - - sql = Insert(kop).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki profile training room for user {pf['user']}") - continue - - for pf_list in pf_data["userBossList"]: - pf_list["user"] = user - - sql = Insert(boss).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item boss for user {pf['user']}") - continue - - for pf_list in pf_data["userDeckList"]: - pf_list["user"] = user - - sql = Insert(deck).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item deck for user {pf['user']}") - continue - - for pf_list in pf_data["userStoryList"]: - pf_list["user"] = user - - sql = Insert(story).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item story for user {pf['user']}") - continue - - for pf_list in pf_data["userChapterList"]: - pf_list["user"] = user - - sql = Insert(chapter).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item chapter for user {pf['user']}") - continue - - for pf_list in pf_data["userPlaylogList"]: - pf_list["user"] = user - - sql = Insert(playlog).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki score playlog for user {pf['user']}") - continue - - for pf_list in pf_data["userMusicItemList"]: - pf_list["user"] = user - - sql = Insert(music_item).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item music item for user {pf['user']}") - continue - - for pf_list in pf_data["userTechCountList"]: - pf_list["user"] = user - - sql = Insert(tech_count).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item tech count for user {pf['user']}") - continue - - if "userTechEventList" in pf_data: - for pf_list in pf_data["userTechEventList"]: - pf_list["user"] = user - - sql = Insert(tech_event).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item tech event for user {pf['user']}") - continue - - for pf_list in pf_data["userTradeItemList"]: - pf_list["user"] = user - - sql = Insert(trade_item).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item trade item for user {pf['user']}") - continue - - if "userEventMusicList" in pf_data: - for pf_list in pf_data["userEventMusicList"]: - pf_list["user"] = user - - sql = Insert(event_music).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item event music for user {pf['user']}") - continue - - if "userEventPointList" in pf_data: - for pf_list in pf_data["userEventPointList"]: - pf_list["user"] = user - - sql = Insert(event_point).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item event point for user {pf['user']}") - continue - - for pf_list in pf_data["userLoginBonusList"]: - pf_list["user"] = user - - sql = Insert(login_bonus).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item login bonus for user {pf['user']}") - continue - - for pf_list in pf_data["userMissionPointList"]: - pf_list["user"] = user - - sql = Insert(mission_point).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item mission point for user {pf['user']}") - continue - - for pf_list in pf_data["userScenarioList"]: - pf_list["user"] = user - - sql = Insert(scenerio).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item scenerio for user {pf['user']}") - continue - - if "userSessionlogList" in pf_data: - for pf_list in pf_data["userSessionlogList"]: - pf_list["user"] = user - - sql = Insert(session_log).values(**pf_list) - conflict = sql.on_duplicate_key_update(**pf_list) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki log session for user {pf['user']}") - continue - - self.logger.info(f"Importing {len(ongeki_items)} ongeki items") - - for it in ongeki_items: - user = it["user"] - it_type = it["type"] - it_id = it["item_id"] - it_data = it["data"] - it_data["user"] = user - - if it_type == item_types["character"] and "characterId" in it_data: - sql = Insert(character).values(**it_data) - - elif it_type == item_types["story"]: - sql = Insert(story).values(**it_data) - - elif it_type == item_types["card"]: - sql = Insert(card).values(**it_data) - - elif it_type == item_types["deck"]: - sql = Insert(deck).values(**it_data) - - elif it_type == item_types["login"]: # login bonus - sql = Insert(login_bonus).values(**it_data) - - elif it_type == item_types["chapter"]: - sql = Insert(chapter).values(**it_data) - - else: - sql = Insert(item).values(**it_data) - - conflict = sql.on_duplicate_key_update(**it_data) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki item {it_id} kind {it_type} for user {user}") - - self.logger.info(f"Importing {len(ongeki_scores)} ongeki scores") - - for sc in ongeki_scores: - user = sc["user"] - sc_data = sc["data"] - sc_data["user"] = user - - sql = Insert(score_best).values(**sc_data) - conflict = sql.on_duplicate_key_update(**sc_data) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert ongeki score for user {user}: {sc['song_id']}/{sc['chart_id']}") - - else: - self.logger.info(f"Ongeki not found, skipping...") - - # Wacca - if os.path.exists("titles/wacca/schema"): - from titles.wacca.schema.profile import profile, option, bingo, gate, favorite - from titles.wacca.schema.item import item, ticket, song_unlock, trophy - from titles.wacca.schema.score import best_score, stageup - from titles.wacca.reverse import WaccaReverse - from titles.wacca.const import WaccaConstants - - default_opts = WaccaReverse.OPTIONS_DEFAULTS - opts = WaccaConstants.OPTIONS - item_types = WaccaConstants.ITEM_TYPES - - wacca_profiles = [] - wacca_items = [] - wacca_scores = [] - wacca_achievements = [] - - if "SDFE" in profiles: - wacca_profiles = profiles["SDFE"] - if "SDFE" in items: - wacca_items = items["SDFE"] - if "SDFE" in scores: - wacca_scores = scores["SDFE"] - if "SDFE" in achievements: - wacca_achievements = achievements["SDFE"] - - self.logger.info(f"Importing {len(wacca_profiles)} wacca profiles") - - for pf in wacca_profiles: - if pf["version"] == 0 or pf["version"] == 1: - season = 1 - elif pf["version"] == 2 or pf["version"] == 3: - season = 2 - elif pf["version"] >= 4: - season = 3 - - if type(pf["data"]) is not dict: - pf["data"] = json.loads(pf["data"]) - - try: - sql = Insert(profile).values( - id = pf["id"], - user = pf["user"], - version = pf["version"], - season = season, - username = pf["data"]["profile"]["username"] if "username" in pf["data"]["profile"] else pf["name"], - xp = pf["data"]["profile"]["xp"], - xp_season = pf["data"]["profile"]["xp"], - wp = pf["data"]["profile"]["wp"], - wp_season = pf["data"]["profile"]["wp"], - wp_total = pf["data"]["profile"]["total_wp_gained"], - dan_type = pf["data"]["profile"]["dan_type"], - dan_level = pf["data"]["profile"]["dan_level"], - title_0 = pf["data"]["profile"]["title_part_ids"][0], - title_1 = pf["data"]["profile"]["title_part_ids"][1], - title_2 = pf["data"]["profile"]["title_part_ids"][2], - rating = pf["data"]["profile"]["rating"], - vip_expire_time = datetime.fromtimestamp(pf["data"]["profile"]["vip_expire_time"]) if "vip_expire_time" in pf["data"]["profile"] else None, - login_count = pf["use_count"], - playcount_single = pf["use_count"], - playcount_single_season = pf["use_count"], - last_game_ver = pf["data"]["profile"]["last_game_ver"], - last_song_id = pf["data"]["profile"]["last_song_info"][0] if "last_song_info" in pf["data"]["profile"] else 0, - last_song_difficulty = pf["data"]["profile"]["last_song_info"][1] if "last_song_info" in pf["data"]["profile"] else 0, - last_folder_order = pf["data"]["profile"]["last_song_info"][2] if "last_song_info" in pf["data"]["profile"] else 0, - last_folder_id = pf["data"]["profile"]["last_song_info"][3] if "last_song_info" in pf["data"]["profile"] else 0, - last_song_order = pf["data"]["profile"]["last_song_info"][4] if "last_song_info" in pf["data"]["profile"] else 0, - last_login_date = datetime.fromtimestamp(pf["data"]["profile"]["last_login_timestamp"]), - ) - - conflict = sql.on_duplicate_key_update( - id = pf["id"], - user = pf["user"], - version = pf["version"], - season = season, - username = pf["data"]["profile"]["username"] if "username" in pf["data"]["profile"] else pf["name"], - xp = pf["data"]["profile"]["xp"], - xp_season = pf["data"]["profile"]["xp"], - wp = pf["data"]["profile"]["wp"], - wp_season = pf["data"]["profile"]["wp"], - wp_total = pf["data"]["profile"]["total_wp_gained"], - dan_type = pf["data"]["profile"]["dan_type"], - dan_level = pf["data"]["profile"]["dan_level"], - title_0 = pf["data"]["profile"]["title_part_ids"][0], - title_1 = pf["data"]["profile"]["title_part_ids"][1], - title_2 = pf["data"]["profile"]["title_part_ids"][2], - rating = pf["data"]["profile"]["rating"], - vip_expire_time = datetime.fromtimestamp(pf["data"]["profile"]["vip_expire_time"]) if "vip_expire_time" in pf["data"]["profile"] else None, - login_count = pf["use_count"], - playcount_single = pf["use_count"], - playcount_single_season = pf["use_count"], - last_game_ver = pf["data"]["profile"]["last_game_ver"], - last_song_id = pf["data"]["profile"]["last_song_info"][0] if "last_song_info" in pf["data"]["profile"] else 0, - last_song_difficulty = pf["data"]["profile"]["last_song_info"][1] if "last_song_info" in pf["data"]["profile"] else 0, - last_folder_order = pf["data"]["profile"]["last_song_info"][2] if "last_song_info" in pf["data"]["profile"] else 0, - last_folder_id = pf["data"]["profile"]["last_song_info"][3] if "last_song_info" in pf["data"]["profile"] else 0, - last_song_order = pf["data"]["profile"]["last_song_info"][4] if "last_song_info" in pf["data"]["profile"] else 0, - last_login_date = datetime.fromtimestamp(pf["data"]["profile"]["last_login_timestamp"]), - ) - - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert wacca profile for user {pf['user']}") - continue - - for opt, val in pf["data"]["option"].items(): - if val != default_opts[opt]: - opt_id = opts[opt] - sql = Insert(option).values( - user = pf["user"], - opt_id = opt_id, - value = val, - ) - - conflict = sql.on_duplicate_key_update( - user = pf["user"], - opt_id = opt_id, - value = val, - ) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert wacca option for user {pf['user']} {opt} -> {val}") - - except KeyError as e: - self.logger.warn(f"Outdated wacca profile, skipping: {e}") - - if "gate" in pf["data"]: - for profile_gate in pf["data"]["gate"]: - sql = Insert(gate).values( - user = pf["user"], - gate_id = profile_gate["id"], - page = profile_gate["page"], - loops = profile_gate["loops"], - progress = profile_gate["progress"], - last_used = datetime.fromtimestamp(profile_gate["last_used"]), - mission_flag = profile_gate["mission_flag"], - total_points = profile_gate["total_points"], - ) - - conflict = sql.on_duplicate_key_update( - user = pf["user"], - gate_id = profile_gate["id"], - page = profile_gate["page"], - loops = profile_gate["loops"], - progress = profile_gate["progress"], - last_used = datetime.fromtimestamp(profile_gate["last_used"]), - mission_flag = profile_gate["mission_flag"], - total_points = profile_gate["total_points"], - ) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert wacca gate for user {pf['user']} -> {profile_gate['id']}") - continue - - if "favorite" in pf["data"]: - for profile_favorite in pf["data"]["favorite"]: - sql = Insert(favorite).values( - user = pf["user"], - song_id = profile_favorite - ) - - conflict = sql.on_duplicate_key_update( - user = pf["user"], - song_id = profile_favorite - ) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert wacca favorite songs for user {pf['user']} -> {profile_favorite}") - continue - - for it in wacca_items: - user = it["user"] - item_type = it["type"] - item_id = it["item_id"] - - if type(it["data"]) is not dict: - it["data"] = json.loads(it["data"]) - - if item_type == item_types["ticket"]: - if "quantity" in it["data"]: - for x in range(it["data"]["quantity"]): - sql = Insert(ticket).values( - user = user, - ticket_id = item_id, - ) - - conflict = sql.on_duplicate_key_update( - user = user, - ticket_id = item_id, - ) - result = self.base.execute(conflict) - if result is None: - self.logger.warn(f"Wacca: Failed to insert ticket {item_id} for user {user}") - - elif item_type == item_types["music_unlock"] or item_type == item_types["music_difficulty_unlock"]: - diff = 0 - if "difficulty" in it["data"]: - for x in it["data"]["difficulty"]: - if x == 1: - diff += 1 - else: - break - - sql = Insert(song_unlock).values( - user = user, - song_id = item_id, - highest_difficulty = diff, - ) - - conflict = sql.on_duplicate_key_update( - user = user, - song_id = item_id, - highest_difficulty = diff, - ) - result = self.base.execute(conflict) - if result is None: - self.logger.warn(f"Wacca: Failed to insert song unlock {item_id} {diff} for user {user}") - - elif item_type == item_types["trophy"]: - season = int(item_id / 100000) - sql = Insert(trophy).values( - user = user, - trophy_id = item_id, - season = season, - progress = 0 if "progress" not in it["data"] else it["data"]["progress"], - badge_type = 0 # ??? - ) - - conflict = sql.on_duplicate_key_update( - user = user, - trophy_id = item_id, - season = season, - progress = 0 if "progress" not in it["data"] else it["data"]["progress"], - badge_type = 0 # ??? - ) - result = self.base.execute(conflict) - if result is None: - self.logger.warn(f"Wacca: Failed to insert trophy {item_id} for user {user}") - - else: - sql = Insert(item).values( - user = user, - item_id = item_id, - type = item_type, - acquire_date = datetime.fromtimestamp(it["data"]["obtainedDate"]) if "obtainedDate" in it["data"] else datetime.now(), - use_count = it["data"]["uses"] if "uses" in it["data"] else 0, - use_count_season = it["data"]["season_uses"] if "season_uses" in it["data"] else 0 - ) - - conflict = sql.on_duplicate_key_update( - user = user, - item_id = item_id, - type = item_type, - acquire_date = datetime.fromtimestamp(it["data"]["obtainedDate"]) if "obtainedDate" in it["data"] else datetime.now(), - use_count = it["data"]["uses"] if "uses" in it["data"] else 0, - use_count_season = it["data"]["season_uses"] if "season_uses" in it["data"] else 0 - ) - result = self.base.execute(conflict) - if result is None: - self.logger.warn(f"Wacca: Failed to insert trophy {item_id} for user {user}") - - for sc in wacca_scores: - if type(sc["data"]) is not dict: - sc["data"] = json.loads(sc["data"]) - - sql = Insert(best_score).values( - user = sc["user"], - song_id = int(sc["song_id"]), - chart_id = sc["chart_id"], - score = sc["score1"], - play_ct = 1 if "play_count" not in sc["data"] else sc["data"]["play_count"], - clear_ct = 1 if sc["cleared"] & 0x01 else 0, - missless_ct = 1 if sc["cleared"] & 0x02 else 0, - fullcombo_ct = 1 if sc["cleared"] & 0x04 else 0, - allmarv_ct = 1 if sc["cleared"] & 0x08 else 0, - grade_d_ct = 1 if sc["grade"] & 0x01 else 0, - grade_c_ct = 1 if sc["grade"] & 0x02 else 0, - grade_b_ct = 1 if sc["grade"] & 0x04 else 0, - grade_a_ct = 1 if sc["grade"] & 0x08 else 0, - grade_aa_ct = 1 if sc["grade"] & 0x10 else 0, - grade_aaa_ct = 1 if sc["grade"] & 0x20 else 0, - grade_s_ct = 1 if sc["grade"] & 0x40 else 0, - grade_ss_ct = 1 if sc["grade"] & 0x80 else 0, - grade_sss_ct = 1 if sc["grade"] & 0x100 else 0, - grade_master_ct = 1 if sc["grade"] & 0x200 else 0, - grade_sp_ct = 1 if sc["grade"] & 0x400 else 0, - grade_ssp_ct = 1 if sc["grade"] & 0x800 else 0, - grade_sssp_ct = 1 if sc["grade"] & 0x1000 else 0, - best_combo = 0 if "max_combo" not in sc["data"] else sc["data"]["max_combo"], - lowest_miss_ct = 0 if "lowest_miss_count" not in sc["data"] else sc["data"]["lowest_miss_count"], - rating = 0 if "rating" not in sc["data"] else sc["data"]["rating"], - ) - - conflict = sql.on_duplicate_key_update( - user = sc["user"], - song_id = int(sc["song_id"]), - chart_id = sc["chart_id"], - score = sc["score1"], - play_ct = 1 if "play_count" not in sc["data"] else sc["data"]["play_count"], - clear_ct = 1 if sc["cleared"] & 0x01 else 0, - missless_ct = 1 if sc["cleared"] & 0x02 else 0, - fullcombo_ct = 1 if sc["cleared"] & 0x04 else 0, - allmarv_ct = 1 if sc["cleared"] & 0x08 else 0, - grade_d_ct = 1 if sc["grade"] & 0x01 else 0, - grade_c_ct = 1 if sc["grade"] & 0x02 else 0, - grade_b_ct = 1 if sc["grade"] & 0x04 else 0, - grade_a_ct = 1 if sc["grade"] & 0x08 else 0, - grade_aa_ct = 1 if sc["grade"] & 0x10 else 0, - grade_aaa_ct = 1 if sc["grade"] & 0x20 else 0, - grade_s_ct = 1 if sc["grade"] & 0x40 else 0, - grade_ss_ct = 1 if sc["grade"] & 0x80 else 0, - grade_sss_ct = 1 if sc["grade"] & 0x100 else 0, - grade_master_ct = 1 if sc["grade"] & 0x200 else 0, - grade_sp_ct = 1 if sc["grade"] & 0x400 else 0, - grade_ssp_ct = 1 if sc["grade"] & 0x800 else 0, - grade_sssp_ct = 1 if sc["grade"] & 0x1000 else 0, - best_combo = 0 if "max_combo" not in sc["data"] else sc["data"]["max_combo"], - lowest_miss_ct = 0 if "lowest_miss_count" not in sc["data"] else sc["data"]["lowest_miss_count"], - rating = 0 if "rating" not in sc["data"] else sc["data"]["rating"], - ) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert wacca score for user {sc['user']} {int(sc['song_id'])} {sc['chart_id']}") - - for ach in wacca_achievements: - if ach["version"] == 0 or ach["version"] == 1: - season = 1 - elif ach["version"] == 2 or ach["version"] == 3: - season = 2 - elif ach["version"] >= 4: - season = 3 - - if type(ach["data"]) is not dict: - ach["data"] = json.loads(ach["data"]) - - sql = Insert(stageup).values( - user = ach["user"], - season = season, - stage_id = ach["achievement_id"], - clear_status = 0 if "clear" not in ach["data"] else ach["data"]["clear"], - clear_song_ct = 0 if "clear_song_ct" not in ach["data"] else ach["data"]["clear_song_ct"], - song1_score = 0 if "score1" not in ach["data"] else ach["data"]["score1"], - song2_score = 0 if "score2" not in ach["data"] else ach["data"]["score2"], - song3_score = 0 if "score3" not in ach["data"] else ach["data"]["score3"], - play_ct = 1 if "attemps" not in ach["data"] else ach["data"]["attemps"], - ) - - conflict = sql.on_duplicate_key_update( - user = ach["user"], - season = season, - stage_id = ach["achievement_id"], - clear_status = 0 if "clear" not in ach["data"] else ach["data"]["clear"], - clear_song_ct = 0 if "clear_song_ct" not in ach["data"] else ach["data"]["clear_song_ct"], - song1_score = 0 if "score1" not in ach["data"] else ach["data"]["score1"], - song2_score = 0 if "score2" not in ach["data"] else ach["data"]["score2"], - song3_score = 0 if "score3" not in ach["data"] else ach["data"]["score3"], - play_ct = 1 if "attemps" not in ach["data"] else ach["data"]["attemps"], - ) - result = self.base.execute(conflict) - if result is None: - self.logger.error(f"Failed to insert wacca achievement for user {ach['user']}") - - else: - self.logger.info(f"Wacca not found, skipping...") From 102bf3b5a49d0e8539f7c7cc07bb2667a3d5fd02 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 17:04:26 -0500 Subject: [PATCH 36/37] database: remove functions that no longer exist --- dbutils.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/dbutils.py b/dbutils.py index d52128e..4500a13 100644 --- a/dbutils.py +++ b/dbutils.py @@ -32,16 +32,5 @@ if __name__=='__main__': else: data.migrate_database(args.game, int(args.version), args.action) - - elif args.action == "migrate": - data.logger.info("Migrating from old schema to new schema") - data.restore_from_old_schema() - - elif args.action == "dump": - data.logger.info("Dumping old schema to migrate to new schema") - data.dump_db() - - elif args.action == "generate": - pass data.logger.info("Done") From 02e1838d95c2498f438835aa106ad64dcc167232 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Fri, 3 Mar 2023 17:05:16 -0500 Subject: [PATCH 37/37] database: add format_serial, validate_keychip_format, set_machine_boardid, set_machine_serial --- core/data/schema/arcade.py | 48 +++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/core/data/schema/arcade.py b/core/data/schema/arcade.py index 921e5b5..af4069d 100644 --- a/core/data/schema/arcade.py +++ b/core/data/schema/arcade.py @@ -4,8 +4,10 @@ from sqlalchemy.sql.schema import ForeignKey, PrimaryKeyConstraint from sqlalchemy.types import Integer, String, Boolean from sqlalchemy.sql import func, select from sqlalchemy.dialects.mysql import insert +import re from core.data.schema.base import BaseData, metadata +from core.const import * arcade = Table( "arcade", @@ -72,20 +74,28 @@ class ArcadeData(BaseData): if result is None: return None return result.fetchone() - def put_machine(self, arcade_id: int, serial: str = None, board: str = None, game: str = None, is_cab: bool = False) -> Optional[int]: + def put_machine(self, arcade_id: int, serial: str = "", board: str = None, game: str = None, is_cab: bool = False) -> Optional[int]: if arcade_id: self.logger.error(f"{__name__ }: Need arcade id!") return None - if serial is None: - pass - sql = machine.insert().values(arcade = arcade_id, keychip = serial, board = board, game = game, is_cab = is_cab) result = self.execute(sql) if result is None: return None return result.lastrowid + def set_machine_serial(self, machine_id: int, serial: str) -> None: + result = self.execute(machine.update(machine.c.id == machine_id).values(keychip = serial)) + if result is None: + self.logger.error(f"Failed to update serial for machine {machine_id} -> {serial}") + return result.lastrowid + + def set_machine_boardid(self, machine_id: int, boardid: str) -> None: + result = self.execute(machine.update(machine.c.id == machine_id).values(board = boardid)) + if result is None: + self.logger.error(f"Failed to update board id for machine {machine_id} -> {boardid}") + def get_arcade(self, id: int) -> Optional[Dict]: sql = arcade.select(arcade.c.id == id) result = self.execute(sql) @@ -120,5 +130,31 @@ class ArcadeData(BaseData): if result is None: return None return result.lastrowid - def generate_keychip_serial(self, platform_id: int) -> str: - pass + def format_serial(self, platform_code: str, platform_rev: int, serial_num: int, append: int = 4152) -> str: + 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: + serial = serial.replace("-", "") + 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 True