diff --git a/core/data/alembic/versions/5cf98cfe52ad_mai2_prism_support.py b/core/data/alembic/versions/5cf98cfe52ad_mai2_prism_support.py index 77ca08a..d3296cd 100644 --- a/core/data/alembic/versions/5cf98cfe52ad_mai2_prism_support.py +++ b/core/data/alembic/versions/5cf98cfe52ad_mai2_prism_support.py @@ -10,6 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. + revision = '5cf98cfe52ad' down_revision = '263884e774cc' branch_labels = None diff --git a/core/data/alembic/versions/bdf710616ba4_mai2_add_prism_plus_support.py b/core/data/alembic/versions/bdf710616ba4_mai2_add_prism_plus_support.py new file mode 100644 index 0000000..30e736a --- /dev/null +++ b/core/data/alembic/versions/bdf710616ba4_mai2_add_prism_plus_support.py @@ -0,0 +1,29 @@ +"""Mai2 add PRiSM+ playlog support + +Revision ID: bdf710616ba4 +Revises: 16f34bf7b968 +Create Date: 2025-04-02 12:42:08.981516 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bdf710616ba4' + +down_revision = '49c295e89cd4' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('mai2_playlog', sa.Column('extBool3', sa.Boolean(), nullable=True,server_default=sa.text("NULL"))) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('mai2_playlog', 'extBool3') + # ### end Alembic commands ### diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md index d2b14a2..423e36d 100644 --- a/docs/game_specific_info.md +++ b/docs/game_specific_info.md @@ -205,32 +205,32 @@ Presents are items given to the user when they login, with a little animation (f ### Versions | Game Code | Version ID | Version Name | -|-----------|------------|-------------------------| -| SBXL | 0 | maimai | -| SBXL | 1 | maimai PLUS | -| SBZF | 2 | maimai GreeN | -| SBZF | 3 | maimai GreeN PLUS | -| SDBM | 4 | maimai ORANGE | -| SDBM | 5 | maimai ORANGE PLUS | -| SDCQ | 6 | maimai PiNK | -| SDCQ | 7 | maimai PiNK PLUS | -| SDDK | 8 | maimai MURASAKi | -| SDDK | 9 | maimai MURASAKi PLUS | -| SDDZ | 10 | maimai MiLK | -| SDDZ | 11 | maimai MiLK PLUS | -| SDEY | 12 | maimai FiNALE | -| SDEZ | 13 | maimai DX | -| SDEZ | 14 | maimai DX PLUS | -| SDEZ | 15 | maimai DX Splash | -| SDEZ | 16 | maimai DX Splash PLUS | -| SDEZ | 17 | maimai DX UNiVERSE | -| SDEZ | 18 | maimai DX UNiVERSE PLUS | -| SDEZ | 19 | maimai DX FESTiVAL | -| SDEZ | 20 | maimai DX FESTiVAL PLUS | -| SDEZ | 21 | maimai DX BUDDiES | -| SDEZ | 22 | maimai DX BUDDiES PLUS | -| SDEZ | 23 | maimai DX PRiSM | - +|----------|------------|-------------------------| +| SBXL | 0 | maimai | +| SBXL | 1 | maimai PLUS | +| SBZF | 2 | maimai GreeN | +| SBZF | 3 | maimai GreeN PLUS | +| SDBM | 4 | maimai ORANGE | +| SDBM | 5 | maimai ORANGE PLUS | +| SDCQ | 6 | maimai PiNK | +| SDCQ | 7 | maimai PiNK PLUS | +| SDDK | 8 | maimai MURASAKi | +| SDDK | 9 | maimai MURASAKi PLUS | +| SDDZ | 10 | maimai MiLK | +| SDDZ | 11 | maimai MiLK PLUS | +| SDEY | 12 | maimai FiNALE | +| SDEZ | 13 | maimai DX | +| SDEZ | 14 | maimai DX PLUS | +| SDEZ | 15 | maimai DX Splash | +| SDEZ | 16 | maimai DX Splash PLUS | +| SDEZ | 17 | maimai DX UNiVERSE | +| SDEZ | 18 | maimai DX UNiVERSE PLUS | +| SDEZ | 19 | maimai DX FESTiVAL | +| SDEZ | 20 | maimai DX FESTiVAL PLUS | +| SDEZ | 21 | maimai DX BUDDiES | +| SDEZ | 22 | maimai DX BUDDiES PLUS | +| SDEZ | 23 | maimai DX PRiSM | +| SDEZ | 24 | maimai DX PRiSM PLUS | ### Importer diff --git a/readme.md b/readme.md index 527414a..055bd81 100644 --- a/readme.md +++ b/readme.md @@ -83,6 +83,7 @@ Games listed below have been tested and confirmed working. Only game versions ol + BUDDiES + BUDDiES PLUS + PRiSM + + PRiSM PLUS + O.N.G.E.K.I. + SUMMER diff --git a/titles/cm/read.py b/titles/cm/read.py index 8a1bb84..5eb8632 100644 --- a/titles/cm/read.py +++ b/titles/cm/read.py @@ -208,7 +208,8 @@ class CardMakerReader(BaseReader): "1.35": Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS, "1.40": Mai2Constants.VER_MAIMAI_DX_BUDDIES, "1.45": Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS, - "1.50": Mai2Constants.VER_MAIMAI_DX_PRISM + "1.50": Mai2Constants.VER_MAIMAI_DX_PRISM, + "1.55": Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS } for root, dirs, files in os.walk(base_dir): diff --git a/titles/mai2/const.py b/titles/mai2/const.py index 47e5cbd..117ba6f 100644 --- a/titles/mai2/const.py +++ b/titles/mai2/const.py @@ -61,6 +61,7 @@ class Mai2Constants: VER_MAIMAI_DX_BUDDIES = 21 VER_MAIMAI_DX_BUDDIES_PLUS = 22 VER_MAIMAI_DX_PRISM = 23 + VER_MAIMAI_DX_PRISM_PLUS = 24 VERSION_STRING = ( "maimai", @@ -86,7 +87,8 @@ class Mai2Constants: "maimai DX FESTiVAL PLUS", "maimai DX BUDDiES", "maimai DX BUDDiES PLUS", - "maimai DX PRiSM" + "maimai DX PRiSM", + "maimai DX PRiSM PLUS" ) KALEIDXSCOPE_KEY_CONDITION={ 1: [11009, 11008, 11100, 11097, 11098, 11099, 11163, 11162, 11161, 11228, 11229, 11231, 11463, 11464, 11465, 11538, 11539, 11541, 11620, 11622, 11623, 11737, 11738, 11164, 11230, 11466, 11540, 11621, 11739], @@ -94,9 +96,21 @@ class Mai2Constants: 2: [11102, 11234, 11300, 11529, 11542, 11612], #白の扉: set Frame as "Latent Kingdom" (459504), play 3 or 4 songs by the composer 大国奏音 in 1 pc 3: [], - #紫の扉: need to enter redeem code 51090942171709440000 + #紫の扉: JP: need to enter redeem code 51090942171709440000 4: [11023, 11106, 11221, 11222, 11300, 11374, 11458, 11523, 11619, 11663, 11746], - #青の扉: Played 11 songs + #黑の扉: Played 11 songs + 5: [11003, 11095, 11152, 11224, 11296, 11375, 11452, 11529, 11608, 11669, 11736, 11806], + #黄の扉: Use random selection to play one of the songs + 6: [212, 213, 337, 270, 271, 11504, 339, 453, 11336, 11852], + #赤の扉: Played 10 songs + 7: [], + #PRISM TOWER: Get the key after clearing six doors. + 8: [], + #KALEIDXSCOPE_FIRST_STAGE: Clear Prism Tower + 9: [], + #希望の扉: CLEAR KALEIDXSCOPE_FIRST_STAGE + 10: [] + #KALEIDXSCOPE_SECOND_STAGE: JP: scan the DXPASS of 希望の鍵, will automatically unlock after clearing 希望の扉 in artemis } MAI_VERSION_LUT = { "100": VER_MAIMAI, @@ -125,7 +139,8 @@ class Mai2Constants: "135": VER_MAIMAI_DX_FESTIVAL_PLUS, "140": VER_MAIMAI_DX_BUDDIES, "145": VER_MAIMAI_DX_BUDDIES_PLUS, - "150": VER_MAIMAI_DX_PRISM + "150": VER_MAIMAI_DX_PRISM, + "155": VER_MAIMAI_DX_PRISM_PLUS } @classmethod diff --git a/titles/mai2/index.py b/titles/mai2/index.py index b302985..d753379 100644 --- a/titles/mai2/index.py +++ b/titles/mai2/index.py @@ -32,6 +32,7 @@ from .festivalplus import Mai2FestivalPlus from .buddies import Mai2Buddies from .buddiesplus import Mai2BuddiesPlus from .prism import Mai2Prism +from .prismplus import Mai2PrismPlus class Mai2Servlet(BaseServlet): @@ -68,7 +69,8 @@ class Mai2Servlet(BaseServlet): Mai2FestivalPlus, Mai2Buddies, Mai2BuddiesPlus, - Mai2Prism + Mai2Prism, + Mai2PrismPlus ] self.logger = logging.getLogger("mai2") @@ -195,7 +197,7 @@ class Mai2Servlet(BaseServlet): if proto == "" or proto == "https://": t_port = f":{title_port_ssl_int}" if title_port_ssl_int != 443 else "" - else: + else: t_port = f":{title_port_int}" if title_port_int != 80 else "" return ( @@ -343,10 +345,12 @@ class Mai2Servlet(BaseServlet): internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS elif version >= 140 and version < 145: # BUDDiES internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES - elif version >= 145 and version <150: # BUDDiES PLUS + elif version >= 145 and version < 150: # BUDDiES PLUS internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS - elif version >= 150: # PRiSM + elif version >= 150 and version < 155: internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM + elif version >= 155: + internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS elif game_code == "SDGA": # Int if version < 105: # 1.0 @@ -367,10 +371,12 @@ class Mai2Servlet(BaseServlet): internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS elif version >= 140 and version < 145: # BUDDiES internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES - elif version >= 145 and version <150: # BUDDiES PLUS + elif version >= 145 and version < 150: # BUDDiES PLUS internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS - elif version >= 150: # PRiSM + elif version >= 150 and version < 155: internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM + elif version >= 155: + internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS elif game_code == "SDGB": # Chn if version < 110: # Muji @@ -386,6 +392,7 @@ class Mai2Servlet(BaseServlet): elif version >= 150: # PRiSM internal_ver = Mai2Constants.VER_MAIMAI_DX_PRISM + if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: if game_code == "SDGA": diff --git a/titles/mai2/prismplus.py b/titles/mai2/prismplus.py new file mode 100644 index 0000000..04b8bc4 --- /dev/null +++ b/titles/mai2/prismplus.py @@ -0,0 +1,141 @@ +from typing import Dict + +from core.config import CoreConfig +from titles.mai2.prism import Mai2Prism +from titles.mai2.const import Mai2Constants +from titles.mai2.config import Mai2Config + + + +class Mai2PrismPlus(Mai2Prism): + def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: + super().__init__(cfg, game_cfg) + self.version = Mai2Constants.VER_MAIMAI_DX_PRISM_PLUS + + async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: + user_data = await super().handle_cm_get_user_preview_api_request(data) + + # hardcode lastDataVersion for CardMaker + user_data["lastDataVersion"] = "1.55.00" + return user_data + + async def handle_upsert_client_play_time_api_request(self, data: Dict) -> Dict: + return{ + "returnCode": 1, + "apiName": "UpsertClientPlayTimeApi" + } + async def handle_get_game_kaleidx_scope_api_request(self, data: Dict) -> Dict: + return { + "gameKaleidxScopeList": [ + {"gateId": 1, "phaseId": 6}, + {"gateId": 2, "phaseId": 6}, + {"gateId": 3, "phaseId": 6}, + {"gateId": 4, "phaseId": 6}, + {"gateId": 5, "phaseId": 6}, + {"gateId": 6, "phaseId": 6}, + {"gateId": 7, "phaseId": 6}, + {"gateId": 8, "phaseId": 6}, + {"gateId": 9, "phaseId": 6}, + {"gateId": 10, "phaseId": 13} + ] + } + + async def handle_get_user_kaleidx_scope_api_request(self, data: Dict) -> Dict: + # kaleidxscope keyget condition judgement + # player may get key before GateFound + for gate in range(1,11): + if gate == 1 or gate == 4 or gate == 6: + condition_satisfy = 0 + for condition in Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[gate]: + score_list = await self.data.score.get_best_scores(user_id=data["userId"], song_id=condition) + if score_list: + condition_satisfy = condition_satisfy + 1 + if len(Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[gate]) == condition_satisfy: + new_kaleidxscope = {'gateId': gate, "isKeyFound": True} + await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope) + + elif gate == 2: + user_profile = await self.data.profile.get_profile_detail(user_id=data["userId"], version=self.version) + user_frame = user_profile["frameId"] + if user_frame == 459504: + playlogs = await self.data.score.get_playlogs(user_id=data["userId"], idx=0, limit=0) + + playlog_dict = {} + for playlog in playlogs: + playlog_id = playlog["playlogId"] + if playlog_id not in playlog_dict: + playlog_dict[playlog_id] = [] + playlog_dict[playlog_id].append(playlog["musicId"]) + valid_playlogs = [] + allowed_music = set(Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[2]) + for playlog_id, music_ids in playlog_dict.items(): + + if len(music_ids) != len(set(music_ids)): + continue + all_valid = True + for mid in music_ids: + if mid not in allowed_music: + all_valid = False + break + if all_valid: + valid_playlogs.append(playlog_id) + + if valid_playlogs: + new_kaleidxscope = {'gateId': 2, "isKeyFound": True} + await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope) + + + elif gate == 5: + + playlogs = await self.data.score.get_playlogs(user_id=data["userId"], idx=0, limit=0) + allowed_music = set(Mai2Constants.KALEIDXSCOPE_KEY_CONDITION[5]) + valid_playlogs = [] + + for playlog in playlogs: + if playlog["extBool2"] == 1 and playlog["musicId"] in allowed_music: + valid_playlogs.append(playlog["playlogId"]) # 直接记录 playlogId + if valid_playlogs: + new_kaleidxscope = {'gateId': 5, "isKeyFound": True} + await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope) + + elif gate == 7: + + played_kaleidxscope_list = await self.data.score.get_user_kaleidxscope_list(data["userId"]) + check_results = {} + for i in range(1,7): + check_results[i] = False + for played_kaleidxscope in played_kaleidxscope_list: + if played_kaleidxscope[2] == i and played_kaleidxscope[5] == True: + check_results[i] = True + break + all_true = all(check_results.values()) + + if all_true: + new_kaleidxscope = {'gateId': 7, "isKeyFound": True} + await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope) + + elif gate == 10: + + played_kaleidxscope_list = await self.data.score.get_user_kaleidxscope_list(data["userId"]) + for played_kaleidxscope in played_kaleidxscope_list: + if played_kaleidxscope[2] == 9 and played_kaleidxscope[5] == True: + new_kaleidxscope = {'gateId': 10, "isGateFound": True, "isKeyFound": True} + await self.data.score.put_user_kaleidxscope(data["userId"], new_kaleidxscope) + + + + kaleidxscope = await self.data.score.get_user_kaleidxscope_list(data["userId"]) + + if kaleidxscope is None: + return {"userId": data["userId"], "userKaleidxScopeList":[]} + + kaleidxscope_list = [] + for kaleidxscope_data in kaleidxscope: + tmp = kaleidxscope_data._asdict() + tmp.pop("user") + tmp.pop("id") + kaleidxscope_list.append(tmp) + return { + "userId": data["userId"], + "userKaleidxScopeList": kaleidxscope_list + } \ No newline at end of file diff --git a/titles/mai2/schema/score.py b/titles/mai2/schema/score.py index d03dba4..804cbaa 100644 --- a/titles/mai2/schema/score.py +++ b/titles/mai2/schema/score.py @@ -148,7 +148,8 @@ playlog = Table( Column("extNum2", Integer), Column("extNum4", Integer), Column("extBool1", Boolean), # new with buddies - Column("extBool2", Boolean), # new with prism + Column("extBool2", Boolean), # new with prism IsRandomSelect + Column("extBool3", Boolean), # new with prism+ IsTrackSkip Column("trialPlayAchievement", Integer), mysql_charset="utf8mb4", )