diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e34dfec --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM python:3.9.15-slim-bullseye + +RUN apt update && apt install default-libmysqlclient-dev build-essential libtk nodejs npm -y + +WORKDIR /app +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt +RUN npm i -g nodemon + +COPY entrypoint.sh entrypoint.sh +RUN chmod +x entrypoint.sh + +COPY index.py index.py +COPY dbutils.py dbutils.py +ADD core core +ADD titles titles +ADD config config +ADD log log +ADD cert cert + +ENTRYPOINT [ "/app/entrypoint.sh" ] \ No newline at end of file diff --git a/core/allnet.py b/core/allnet.py index 119f0ae..ab435e7 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -249,14 +249,18 @@ class AllnetServlet: signer = PKCS1_v1_5.new(rsa) digest = SHA.new() - kc_playlimit = int(req_dict[0]["playlimit"]) - kc_nearfull = int(req_dict[0]["nearfull"]) - kc_billigtype = int(req_dict[0]["billingtype"]) - kc_playcount = int(req_dict[0]["playcnt"]) - kc_serial: str = req_dict[0]["keychipid"] - kc_game: str = req_dict[0]["gameid"] - kc_date = strptime(req_dict[0]["date"], "%Y%m%d%H%M%S") - kc_serial_bytes = kc_serial.encode() + try: + kc_playlimit = int(req_dict[0]["playlimit"]) + kc_nearfull = int(req_dict[0]["nearfull"]) + kc_billigtype = int(req_dict[0]["billingtype"]) + kc_playcount = int(req_dict[0]["playcnt"]) + kc_serial: str = req_dict[0]["keychipid"] + kc_game: str = req_dict[0]["gameid"] + kc_date = strptime(req_dict[0]["date"], "%Y%m%d%H%M%S") + kc_serial_bytes = kc_serial.encode() + + except KeyError as e: + return f"result=5&linelimit=&message={e} field is missing".encode() machine = self.data.arcade.get_machine(kc_serial) if machine is None and not self.config.server.allow_unregistered_serials: diff --git a/core/data/schema/versions/SBZV_4_rollback.sql b/core/data/schema/versions/SBZV_4_rollback.sql new file mode 100644 index 0000000..f56327e --- /dev/null +++ b/core/data/schema/versions/SBZV_4_rollback.sql @@ -0,0 +1,9 @@ +ALTER TABLE diva_profile + DROP cnp_cid, + DROP cnp_val, + DROP cnp_rr, + DROP cnp_sp, + DROP btn_se_eqp, + DROP sld_se_eqp, + DROP chn_sld_se_eqp, + DROP sldr_tch_se_eqp; \ No newline at end of file diff --git a/core/data/schema/versions/SBZV_5_upgrade.sql b/core/data/schema/versions/SBZV_5_upgrade.sql new file mode 100644 index 0000000..7e29f7b --- /dev/null +++ b/core/data/schema/versions/SBZV_5_upgrade.sql @@ -0,0 +1,9 @@ +ALTER TABLE diva_profile + ADD cnp_cid INT NOT NULL DEFAULT -1, + ADD cnp_val INT NOT NULL DEFAULT -1, + ADD cnp_rr INT NOT NULL DEFAULT -1, + ADD cnp_sp VARCHAR(255) NOT NULL DEFAULT "", + ADD btn_se_eqp INT NOT NULL DEFAULT -1, + ADD sld_se_eqp INT NOT NULL DEFAULT -1, + ADD chn_sld_se_eqp INT NOT NULL DEFAULT -1, + ADD sldr_tch_se_eqp INT NOT NULL DEFAULT -1; \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c43b6e5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,57 @@ +version: "3.9" +services: + app: + hostname: ma.app + build: . + volumes: + - ./aime:/app/aime + + environment: + CFG_DEV: 1 + CFG_CORE_SERVER_HOSTNAME: 0.0.0.0 + CFG_CORE_DATABASE_HOST: ma.db + CFG_CORE_MEMCACHED_HOSTNAME: ma.memcached + CFG_CORE_AIMEDB_KEY: keyhere + CFG_CHUNI_SERVER_LOGLEVEL: debug + + ports: + - "80:80" + - "8443:8443" + - "22345:22345" + + - "8080:8080" + - "8090:8090" + + depends_on: + db: + condition: service_healthy + + db: + hostname: ma.db + image: mysql:8.0.31-debian + environment: + MYSQL_DATABASE: aime + MYSQL_USER: aime + MYSQL_PASSWORD: aime + MYSQL_ROOT_PASSWORD: AimeRootPassword + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + timeout: 5s + retries: 5 + + + memcached: + hostname: ma.memcached + image: memcached:1.6.17-bullseye + + phpmyadmin: + hostname: ma.phpmyadmin + image: phpmyadmin:latest + environment: + PMA_HOSTS: ma.db + PMA_USER: root + PMA_PASSWORD: AimeRootPassword + APACHE_PORT: 8080 + ports: + - "8080:8080" + diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..aa95ca8 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [[ -z "${CFG_DEV}" ]]; then + echo Production mode + python3 index.py +else + echo Development mode + python3 dbutils.py create + nodemon -w aime --legacy-watch index.py +fi + diff --git a/index.py b/index.py index 11fad94..7199dbe 100644 --- a/index.py +++ b/index.py @@ -42,7 +42,7 @@ class HttpDispatcher(resource.Resource): conditions=dict(method=["POST"]), ) - self.map_post.connect( + self.map_get.connect( "allnet_ping", "/naomitest.html", controller="allnet", diff --git a/titles/diva/__init__.py b/titles/diva/__init__.py index 3f193db..46ea090 100644 --- a/titles/diva/__init__.py +++ b/titles/diva/__init__.py @@ -7,4 +7,4 @@ index = DivaServlet database = DivaData reader = DivaReader game_codes = [DivaConstants.GAME_CODE] -current_schema_version = 4 +current_schema_version = 5 diff --git a/titles/diva/base.py b/titles/diva/base.py index 9e58269..d7303e7 100644 --- a/titles/diva/base.py +++ b/titles/diva/base.py @@ -266,16 +266,17 @@ class DivaBase: def handle_festa_info_request(self, data: Dict) -> Dict: encoded = "&" params = { - "fi_id": "1,-1", - "fi_name": f"{self.core_cfg.server.name} Opening,xxx", - "fi_kind": "0,0", + "fi_id": "1,2", + "fi_name": f"{self.core_cfg.server.name} Opening,Project DIVA Festa", + # 0=PINK, 1=GREEN + "fi_kind": "1,0", "fi_difficulty": "-1,-1", "fi_pv_id_lst": "ALL,ALL", "fi_attr": "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - "fi_add_vp": "20,0", - "fi_mul_vp": "1,1", - "fi_st": "2022-06-17 17:00:00.0,2014-07-08 18:10:11.0", - "fi_et": "2029-01-01 10:00:00.0,2014-07-08 18:10:11.0", + "fi_add_vp": "20,5", + "fi_mul_vp": "1,2", + "fi_st": "2019-01-01 00:00:00.0,2019-01-01 00:00:00.0", + "fi_et": "2029-01-01 00:00:00.0,2029-01-01 00:00:00.0", "fi_lut": "{self.time_lut}", } @@ -401,10 +402,10 @@ class DivaBase: response += f"&lv_pnt={profile['lv_pnt']}" response += f"&vcld_pts={profile['vcld_pts']}" response += f"&skn_eqp={profile['use_pv_skn_eqp']}" - response += f"&btn_se_eqp={profile['use_pv_btn_se_eqp']}" - response += f"&sld_se_eqp={profile['use_pv_sld_se_eqp']}" - response += f"&chn_sld_se_eqp={profile['use_pv_chn_sld_se_eqp']}" - response += f"&sldr_tch_se_eqp={profile['use_pv_sldr_tch_se_eqp']}" + response += f"&btn_se_eqp={profile['btn_se_eqp']}" + response += f"&sld_se_eqp={profile['sld_se_eqp']}" + response += f"&chn_sld_se_eqp={profile['chn_sld_se_eqp']}" + response += f"&sldr_tch_se_eqp={profile['sldr_tch_se_eqp']}" response += f"&passwd_stat={profile['passwd_stat']}" # Store stuff to add to rework @@ -478,6 +479,21 @@ class DivaBase: response += f"&dsp_clr_sts={profile['dsp_clr_sts']}" response += f"&rgo_sts={profile['rgo_sts']}" + # Contest progress + response += f"&cv_cid=-1,-1,-1,-1" + response += f"&cv_sc=-1,-1,-1,-1" + response += f"&cv_bv=-1,-1,-1,-1" + response += f"&cv_bv=-1,-1,-1,-1" + response += f"&cv_bf=-1,-1,-1,-1" + + # Contest now playing id, return -1 if no current playing contest + response += f"&cnp_cid={profile['cnp_cid']}" + response += f"&cnp_val={profile['cnp_val']}" + # border can be 0=bronzem 1=silver, 2=gold + response += f"&cnp_rr={profile['cnp_rr']}" + # only show contest specifier if it is not empty + response += f"&cnp_sp={profile['cnp_sp']}" if profile["cnp_sp"] != "" else "" + # To be fully fixed if "my_qst_id" not in profile: response += f"&my_qst_id=-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" @@ -488,7 +504,63 @@ class DivaBase: response += f"&my_qst_prgrs=0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" response += f"&my_qst_et=2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2100-01-01%2008%3A59%3A59.0,2100-01-01%2008%3A59%3A59.0,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx" - response += f"&clr_sts=0,0,0,0,0,0,0,0,56,52,35,6,6,3,1,0,0,0,0,0" + + # define a helper class to store all counts for clear, great, + # excellent and perfect + class ClearSet: + def __init__(self): + self.clear = 0 + self.great = 0 + self.excellent = 0 + self.perfect = 0 + + # create a dict to store the ClearSets per difficulty + clear_set_dict = { + 0: ClearSet(), # easy + 1: ClearSet(), # normal + 2: ClearSet(), # hard + 3: ClearSet(), # extreme + 4: ClearSet(), # exExtreme + } + + # get clear status from user scores + pv_records = self.data.score.get_best_scores(data["pd_id"]) + clear_status = "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0" + + if pv_records is not None: + for score in pv_records: + if score["edition"] == 0: + # cheap and standard both count to "clear" + if score["clr_kind"] in {1, 2}: + clear_set_dict[score["difficulty"]].clear += 1 + elif score["clr_kind"] == 3: + clear_set_dict[score["difficulty"]].great += 1 + elif score["clr_kind"] == 4: + clear_set_dict[score["difficulty"]].excellent += 1 + elif score["clr_kind"] == 5: + clear_set_dict[score["difficulty"]].perfect += 1 + else: + # 4=ExExtreme + if score["clr_kind"] in {1, 2}: + clear_set_dict[4].clear += 1 + elif score["clr_kind"] == 3: + clear_set_dict[4].great += 1 + elif score["clr_kind"] == 4: + clear_set_dict[4].excellent += 1 + elif score["clr_kind"] == 5: + clear_set_dict[4].perfect += 1 + + # now add all values to a list + clear_list = [] + for clear_set in clear_set_dict.values(): + clear_list.append(clear_set.clear) + clear_list.append(clear_set.great) + clear_list.append(clear_set.excellent) + clear_list.append(clear_set.perfect) + + clear_status = ",".join(map(str, clear_list)) + + response += f"&clr_sts={clear_status}" # Store stuff to add to rework response += f"&mdl_eqp_tm={self.time_lut}" diff --git a/titles/diva/schema/profile.py b/titles/diva/schema/profile.py index 1a498e2..7107068 100644 --- a/titles/diva/schema/profile.py +++ b/titles/diva/schema/profile.py @@ -34,9 +34,17 @@ profile = Table( Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"), Column("use_pv_chn_sld_se_eqp", Boolean, nullable=False, server_default="0"), Column("use_pv_sldr_tch_se_eqp", Boolean, nullable=False, server_default="0"), + Column("btn_se_eqp", Integer, nullable=False, server_default="-1"), + Column("sld_se_eqp", Integer, nullable=False, server_default="-1"), + Column("chn_sld_se_eqp", Integer, nullable=False, server_default="-1"), + Column("sldr_tch_se_eqp", Integer, nullable=False, server_default="-1"), Column("nxt_pv_id", Integer, nullable=False, server_default="708"), Column("nxt_dffclty", Integer, nullable=False, server_default="2"), Column("nxt_edtn", Integer, nullable=False, server_default="0"), + Column("cnp_cid", Integer, nullable=False, server_default="-1"), + Column("cnp_val", Integer, nullable=False, server_default="-1"), + Column("cnp_rr", Integer, nullable=False, server_default="-1"), + Column("cnp_sp", String(255), nullable=False, server_default=""), Column("dsp_clr_brdr", Integer, nullable=False, server_default="7"), Column("dsp_intrm_rnk", Integer, nullable=False, server_default="1"), Column("dsp_clr_sts", Integer, nullable=False, server_default="1"), diff --git a/titles/diva/schema/score.py b/titles/diva/schema/score.py index 2d86925..2171659 100644 --- a/titles/diva/schema/score.py +++ b/titles/diva/schema/score.py @@ -3,6 +3,7 @@ from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select from sqlalchemy.dialects.mysql import insert +from sqlalchemy.engine import Row from typing import Optional, List, Dict, Any from core.data.schema import BaseData, metadata @@ -167,7 +168,7 @@ class DivaScoreData(BaseData): def get_best_user_score( self, user_id: int, pv_id: int, difficulty: int, edition: int - ) -> Optional[Dict]: + ) -> Optional[Row]: sql = score.select( and_( score.c.user == user_id, @@ -184,7 +185,7 @@ class DivaScoreData(BaseData): def get_top3_scores( self, pv_id: int, difficulty: int, edition: int - ) -> Optional[List[Dict]]: + ) -> Optional[List[Row]]: sql = ( score.select( and_( @@ -204,7 +205,7 @@ class DivaScoreData(BaseData): def get_global_ranking( self, user_id: int, pv_id: int, difficulty: int, edition: int - ) -> Optional[List]: + ) -> Optional[List[Row]]: # get the subquery max score of a user with pv_id, difficulty and # edition sql_sub = ( @@ -231,7 +232,7 @@ class DivaScoreData(BaseData): return None return result.fetchone() - def get_best_scores(self, user_id: int) -> Optional[List]: + def get_best_scores(self, user_id: int) -> Optional[List[Row]]: sql = score.select(score.c.user == user_id) result = self.execute(sql) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index 171378c..44ec60d 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -16,8 +16,15 @@ class Mai2Base: self.version = Mai2Constants.VER_MAIMAI_DX self.data = Mai2Data(cfg) self.logger = logging.getLogger("mai2") + + if self.core_config.server.is_develop and self.core_config.title.port > 0: + self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEY/100/" + + else: + self.old_server = f"http://{self.core_config.title.hostname}/SDEY/100/" def handle_get_game_setting_api_request(self, data: Dict): + # TODO: See if making this epoch 0 breaks things reboot_start = date.strftime( datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT ) @@ -34,7 +41,7 @@ class Mai2Base: "movieStatus": 0, "movieServerUri": "", "deliverServerUri": "", - "oldServerUri": "", + "oldServerUri": self.old_server, "usbDlServerUri": "", "rebootInterval": 0, }, diff --git a/titles/mai2/index.py b/titles/mai2/index.py index 1b92842..3326843 100644 --- a/titles/mai2/index.py +++ b/titles/mai2/index.py @@ -82,13 +82,13 @@ class Mai2Servlet: return ( True, f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/", - f"{core_cfg.title.hostname}:{core_cfg.title.port}/", + f"{core_cfg.title.hostname}:{core_cfg.title.port}", ) return ( True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", - f"{core_cfg.title.hostname}/", + f"{core_cfg.title.hostname}", ) def render_POST(self, request: Request, version: int, url_path: str) -> bytes: