diff --git a/changelog.md b/changelog.md
index 3f8c6ba..07f17c8 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,10 @@
# Changelog
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
+## 20240620
+### CHUNITHM
++ CHUNITHM LUMINOUS support
+
## 20240530
### DIVA
+ Fix reader for when dificulty is not a int
diff --git a/core/data/alembic/versions/b23f985100ba_chunithm_luminous.py b/core/data/alembic/versions/b23f985100ba_chunithm_luminous.py
new file mode 100644
index 0000000..dd52974
--- /dev/null
+++ b/core/data/alembic/versions/b23f985100ba_chunithm_luminous.py
@@ -0,0 +1,87 @@
+"""CHUNITHM LUMINOUS
+
+Revision ID: b23f985100ba
+Revises: 3657efefc5a4
+Create Date: 2024-06-20 08:08:08.759261
+
+"""
+from alembic import op
+from sqlalchemy import Column, Integer, Boolean, UniqueConstraint
+
+
+# revision identifiers, used by Alembic.
+revision = 'b23f985100ba'
+down_revision = '3657efefc5a4'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ op.create_table(
+ "chuni_profile_net_battle",
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", Integer, nullable=False),
+ Column("isRankUpChallengeFailed", Boolean),
+ Column("highestBattleRankId", Integer),
+ Column("battleIconId", Integer),
+ Column("battleIconNum", Integer),
+ Column("avatarEffectPoint", Integer),
+ mysql_charset="utf8mb4",
+ )
+ op.create_foreign_key(
+ None,
+ "chuni_profile_net_battle",
+ "aime_user",
+ ["user"],
+ ["id"],
+ ondelete="cascade",
+ onupdate="cascade",
+ )
+
+ op.create_table(
+ "chuni_item_cmission",
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", Integer, nullable=False),
+ Column("missionId", Integer, nullable=False),
+ Column("point", Integer),
+ UniqueConstraint("user", "missionId", name="chuni_item_cmission_uk"),
+ mysql_charset="utf8mb4",
+ )
+ op.create_foreign_key(
+ None,
+ "chuni_item_cmission",
+ "aime_user",
+ ["user"],
+ ["id"],
+ ondelete="cascade",
+ onupdate="cascade",
+ )
+
+ op.create_table(
+ "chuni_item_cmission_progress",
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", Integer, nullable=False),
+ Column("missionId", Integer, nullable=False),
+ Column("order", Integer),
+ Column("stage", Integer),
+ Column("progress", Integer),
+ UniqueConstraint(
+ "user", "missionId", "order", name="chuni_item_cmission_progress_uk"
+ ),
+ mysql_charset="utf8mb4",
+ )
+ op.create_foreign_key(
+ None,
+ "chuni_item_cmission_progress",
+ "aime_user",
+ ["user"],
+ ["id"],
+ ondelete="cascade",
+ onupdate="cascade",
+ )
+
+
+def downgrade():
+ op.drop_table("chuni_profile_net_battle")
+ op.drop_table("chuni_item_cmission")
+ op.drop_table("chuni_item_cmission_progress")
diff --git a/core/data/schema/base.py b/core/data/schema/base.py
index e315964..d74198b 100644
--- a/core/data/schema/base.py
+++ b/core/data/schema/base.py
@@ -109,7 +109,7 @@ class BaseData:
return result.lastrowid
async def get_event_log(self, entries: int = 100) -> Optional[List[Row]]:
- sql = event_log.select().limit(entries)
+ sql = event_log.select().order_by(event_log.c.id.desc()).limit(entries)
result = await self.execute(sql)
if result is None:
diff --git a/core/templates/sys/logs.jinja b/core/templates/sys/logs.jinja
index 3dfe531..9b7a987 100644
--- a/core/templates/sys/logs.jinja
+++ b/core/templates/sys/logs.jinja
@@ -6,6 +6,7 @@
Severity |
+ Timestamp |
System |
Name |
User |
@@ -19,7 +20,7 @@
{% if events is not defined or events|length == 0 %}
- No Events |
+ No Events |
{% endif %}
@@ -66,7 +67,11 @@ function update_tbl() {
for (var i = 0; i < per_page; i++) {
let off = (page * per_page) + i;
- if (off >= TBL_DATA.length ) {
+ if (off >= TBL_DATA.length) {
+ if (page != 0) {
+ document.getElementById("btn_next").disabled = true;
+ document.getElementById("btn_prev").disabled = false;
+ }
break;
}
@@ -99,56 +104,59 @@ function update_tbl() {
row.classList.add("table-primary");
break;
}
+
+ var cell_ts = row.insertCell(1);
+ cell_ts.innerHTML = data.when_logged;
- var cell_mod = row.insertCell(1);
+ var cell_mod = row.insertCell(2);
cell_mod.innerHTML = data.system;
- var cell_name = row.insertCell(2);
+ var cell_name = row.insertCell(3);
cell_name.innerHTML = data.type;
- var cell_usr = row.insertCell(3);
+ var cell_usr = row.insertCell(4);
if (data.user == 'NONE') {
cell_usr.innerHTML = "---";
} else {
cell_usr.innerHTML = "" + data.user + "";
}
- var cell_arcade = row.insertCell(4);
+ var cell_arcade = row.insertCell(5);
if (data.arcade == 'NONE') {
cell_arcade.innerHTML = "---";
} else {
cell_arcade.innerHTML = "" + data.arcade + "";
}
- var cell_machine = row.insertCell(5);
+ var cell_machine = row.insertCell(6);
if (data.arcade == 'NONE') {
cell_machine.innerHTML = "---";
} else {
cell_machine.innerHTML = "" + data.machine + "";
}
- var cell_game = row.insertCell(6);
+ var cell_game = row.insertCell(7);
if (data.game == 'NONE') {
cell_game.innerHTML = "---";
} else {
cell_game.innerHTML = data.game;
}
- var cell_version = row.insertCell(7);
+ var cell_version = row.insertCell(8);
if (data.version == 'NONE') {
cell_version.innerHTML = "---";
} else {
cell_version.innerHTML = data.version;
}
- var cell_msg = row.insertCell(8);
+ var cell_msg = row.insertCell(9);
if (data.message == '') {
cell_msg.innerHTML = "---";
} else {
cell_msg.innerHTML = data.message;
}
- var cell_deets = row.insertCell(9);
+ var cell_deets = row.insertCell(10);
if (data.details == '{}') {
cell_deets.innerHTML = "---";
} else {
@@ -160,9 +168,11 @@ function update_tbl() {
function chg_page(num) {
var max_page = TBL_DATA.length / per_page;
+ console.log(max_page);
page = page + num;
- if (page > max_page) {
+
+ if (page > max_page && max_page >= 1) {
page = max_page;
document.getElementById("btn_next").disabled = true;
document.getElementById("btn_prev").disabled = false;
@@ -172,6 +182,12 @@ function chg_page(num) {
document.getElementById("btn_next").disabled = false;
document.getElementById("btn_prev").disabled = true;
return;
+ } else if (page == 0) {
+ document.getElementById("btn_next").disabled = false;
+ document.getElementById("btn_prev").disabled = true;
+ } else {
+ document.getElementById("btn_next").disabled = false;
+ document.getElementById("btn_prev").disabled = false;
}
update_tbl();
diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md
index a8e63c5..53c076c 100644
--- a/docs/game_specific_info.md
+++ b/docs/game_specific_info.md
@@ -63,6 +63,7 @@ Games listed below have been tested and confirmed working.
| 12 | CHUNITHM NEW PLUS!! |
| 13 | CHUNITHM SUN |
| 14 | CHUNITHM SUN PLUS |
+| 15 | CHUNITHM LUMINOUS |
### Importer
diff --git a/example_config/chuni.yaml b/example_config/chuni.yaml
index 53da186..4855fa1 100644
--- a/example_config/chuni.yaml
+++ b/example_config/chuni.yaml
@@ -22,6 +22,9 @@ version:
14:
rom: 2.15.00
data: 2.15.00
+ 15:
+ rom: 2.20.00
+ data: 2.20.00
crypto:
encrypted_only: False
diff --git a/titles/chuni/base.py b/titles/chuni/base.py
index 9e8a634..2a662d7 100644
--- a/titles/chuni/base.py
+++ b/titles/chuni/base.py
@@ -941,6 +941,31 @@ class ChuniBase:
rating_type,
upsert[rating_type],
)
+
+ # added in LUMINOUS
+ if "userCMissionList" in upsert:
+ for cmission in upsert["userCMissionList"]:
+ mission_id = cmission["missionId"]
+
+ await self.data.item.put_cmission(
+ user_id,
+ {
+ "missionId": mission_id,
+ "point": cmission["point"],
+ },
+ )
+
+ for progress in cmission["userCMissionProgressList"]:
+ await self.data.item.put_cmission_progress(user_id, mission_id, progress)
+
+ if "userNetBattleData" in upsert:
+ net_battle = upsert["userNetBattleData"][0]
+
+ # fix the boolean
+ net_battle["isRankUpChallengeFailed"] = (
+ False if net_battle["isRankUpChallengeFailed"] == "false" else True
+ )
+ await self.data.profile.put_net_battle(user_id, net_battle)
return {"returnCode": "1"}
@@ -969,4 +994,4 @@ class ChuniBase:
return {
"userId": data["userId"],
"userNetBattleData": {"recentNBSelectMusicList": []},
- }
\ No newline at end of file
+ }
diff --git a/titles/chuni/const.py b/titles/chuni/const.py
index 3e83378..2b79582 100644
--- a/titles/chuni/const.py
+++ b/titles/chuni/const.py
@@ -1,3 +1,6 @@
+from enum import Enum
+
+
class ChuniConstants:
GAME_CODE = "SDBT"
GAME_CODE_NEW = "SDHD"
@@ -20,6 +23,7 @@ class ChuniConstants:
VER_CHUNITHM_NEW_PLUS = 12
VER_CHUNITHM_SUN = 13
VER_CHUNITHM_SUN_PLUS = 14
+ VER_CHUNITHM_LUMINOUS = 15
VERSION_NAMES = [
"CHUNITHM",
"CHUNITHM PLUS",
@@ -35,9 +39,21 @@ class ChuniConstants:
"CHUNITHM NEW!!",
"CHUNITHM NEW PLUS!!",
"CHUNITHM SUN",
- "CHUNITHM SUN PLUS"
+ "CHUNITHM SUN PLUS",
+ "CHUNITHM LUMINOUS",
]
@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]
+
+
+class MapAreaConditionType(Enum):
+ UNLOCKED = "0"
+ MAP_AREA_CLEARED = "2"
+ TROPHY_OBTAINED = "3"
+
+
+class MapAreaConditionLogicalOperator(Enum):
+ OR = "0"
+ AND = "1"
diff --git a/titles/chuni/index.py b/titles/chuni/index.py
index f0f1eac..39dd9ba 100644
--- a/titles/chuni/index.py
+++ b/titles/chuni/index.py
@@ -1,7 +1,8 @@
from starlette.requests import Request
from starlette.routing import Route
from starlette.responses import Response
-import logging, coloredlogs
+import logging
+import coloredlogs
from logging.handlers import TimedRotatingFileHandler
import zlib
import yaml
@@ -34,6 +35,7 @@ from .new import ChuniNew
from .newplus import ChuniNewPlus
from .sun import ChuniSun
from .sunplus import ChuniSunPlus
+from .luminous import ChuniLuminous
class ChuniServlet(BaseServlet):
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
@@ -61,6 +63,7 @@ class ChuniServlet(BaseServlet):
ChuniNewPlus,
ChuniSun,
ChuniSunPlus,
+ ChuniLuminous,
]
self.logger = logging.getLogger("chuni")
@@ -103,7 +106,9 @@ class ChuniServlet(BaseServlet):
for method in method_list:
method_fixed = inflection.camelize(method)[6:-7]
# number of iterations was changed to 70 in SUN and then to 36
- if version == ChuniConstants.VER_CHUNITHM_SUN_PLUS:
+ if version == ChuniConstants.VER_CHUNITHM_LUMINOUS:
+ iter_count = 8
+ elif version == ChuniConstants.VER_CHUNITHM_SUN_PLUS:
iter_count = 36
elif version == ChuniConstants.VER_CHUNITHM_SUN:
iter_count = 70
@@ -195,8 +200,10 @@ class ChuniServlet(BaseServlet):
internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS
elif version >= 210 and version < 215: # SUN
internal_ver = ChuniConstants.VER_CHUNITHM_SUN
- elif version >= 215: # SUN PLUS
+ elif version >= 215 and version < 220: # SUN PLUS
internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS
+ elif version >= 220: # LUMINOUS
+ internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS
elif game_code == "SDGS": # Int
if version < 110: # SUPERSTAR / SUPERSTAR PLUS
internal_ver = ChuniConstants.VER_CHUNITHM_PARADISE # SUPERSTAR / SUPERSTAR PLUS worked fine with it
@@ -206,8 +213,10 @@ class ChuniServlet(BaseServlet):
internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS
elif version >= 120 and version < 125: # SUN
internal_ver = ChuniConstants.VER_CHUNITHM_SUN
- elif version >= 125: # SUN PLUS
+ elif version >= 125 and version < 130: # SUN PLUS
internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS
+ elif version >= 130: # LUMINOUS
+ internal_ver = ChuniConstants.VER_CHUNITHM_LUMINOUS
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
@@ -295,7 +304,7 @@ class ChuniServlet(BaseServlet):
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return Response(zlib.compress(b'{"stat": "0"}'))
- if resp == None:
+ if resp is None:
resp = {"returnCode": 1}
self.logger.debug(f"Response {resp}")
@@ -313,4 +322,4 @@ class ChuniServlet(BaseServlet):
bytes.fromhex(self.game_cfg.crypto.keys[internal_ver][1]),
)
- return Response(crypt.encrypt(padded))
\ No newline at end of file
+ return Response(crypt.encrypt(padded))
diff --git a/titles/chuni/luminous.py b/titles/chuni/luminous.py
new file mode 100644
index 0000000..2ced55b
--- /dev/null
+++ b/titles/chuni/luminous.py
@@ -0,0 +1,244 @@
+from typing import Dict
+
+from core.config import CoreConfig
+from titles.chuni.sunplus import ChuniSunPlus
+from titles.chuni.const import ChuniConstants, MapAreaConditionLogicalOperator, MapAreaConditionType
+from titles.chuni.config import ChuniConfig
+
+
+class ChuniLuminous(ChuniSunPlus):
+ def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
+ super().__init__(core_cfg, game_cfg)
+ self.version = ChuniConstants.VER_CHUNITHM_LUMINOUS
+
+ 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)
+
+ # Does CARD MAKER 1.35 work this far up?
+ user_data["lastDataVersion"] = "2.20.00"
+ return user_data
+
+ async def handle_get_user_c_mission_api_request(self, data: Dict) -> Dict:
+ user_id = data["userId"]
+ mission_id = data["missionId"]
+
+ progress_list = []
+ point = 0
+
+ mission_data = await self.data.item.get_cmission(user_id, mission_id)
+ progress_data = await self.data.item.get_cmission_progress(user_id, mission_id)
+
+ if mission_data and progress_data:
+ point = mission_data["point"]
+
+ for progress in progress_data:
+ progress_list.append(
+ {
+ "order": progress["order"],
+ "stage": progress["stage"],
+ "progress": progress["progress"],
+ }
+ )
+
+ return {
+ "userId": user_id,
+ "missionId": mission_id,
+ "point": point,
+ "userCMissionProgressList": progress_list,
+ }
+
+ async def handle_get_user_net_battle_ranking_info_api_request(self, data: Dict) -> Dict:
+ user_id = data["userId"]
+
+ net_battle = {}
+ net_battle_data = await self.data.profile.get_net_battle(user_id)
+
+ if net_battle_data:
+ net_battle = {
+ "isRankUpChallengeFailed": net_battle_data["isRankUpChallengeFailed"],
+ "highestBattleRankId": net_battle_data["highestBattleRankId"],
+ "battleIconId": net_battle_data["battleIconId"],
+ "battleIconNum": net_battle_data["battleIconNum"],
+ "avatarEffectPoint": net_battle_data["avatarEffectPoint"],
+ }
+
+ return {
+ "userId": user_id,
+ "userNetBattleData": net_battle,
+ }
+
+ async def handle_get_game_map_area_condition_api_request(self, data: Dict) -> Dict:
+ # There is no game data for this, everything is server side.
+ # TODO: Figure out conditions for 1UM1N0US ep.111
+ return {
+ "length": "7",
+ "gameMapAreaConditionList": [
+ # Secret AREA: MUSIC GAME
+ {
+ "mapAreaId": "2206201", # BlythE ULTIMA
+ "length": "1",
+ # Obtain the trophy "MISSION in progress", which is only available
+ # when running the first CHUNITHM mission
+ "mapAreaConditionList": [
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6832",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2024-01-25 02:00:00.0",
+ }
+ ],
+ },
+ {
+ "mapAreaId": "2206202", # PRIVATE SERVICE ULTIMA
+ "length": "1",
+ # Obtain the trophy "MISSION in progress", which is only available
+ # when running the first CHUNITHM mission
+ "mapAreaConditionList": [
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6832",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2024-01-25 02:00:00.0",
+ }
+ ],
+ },
+ {
+ "mapAreaId": "2206203", # New York Back Raise
+ "length": "1",
+ # SS NightTheater's EXPERT chart and get the title
+ # "今宵、劇場に映し出される景色とは――――。"
+ "mapAreaConditionList": [
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6833",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2099-12-31 00:00:00.0",
+ },
+ ],
+ },
+ {
+ "mapAreaId": "2206204", # Spasmodic
+ "length": "2",
+ # - Get 1 miss on Random (any difficulty) and get the title "当たり待ち"
+ # - Get 1 miss on 花たちに希望を (any difficulty) and get the title "花たちに希望を"
+ "mapAreaConditionList": [
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6834",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2099-12-31 00:00:00.0",
+ },
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6835",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2099-12-31 00:00:00.0",
+ },
+ ],
+ },
+ {
+ "mapAreaId": "2206205", # ΩΩPARTS
+ "length": "2",
+ # - S Sage EXPERT to get the title "マターリ進行キボンヌ"
+ # - Equip this title and play cab-to-cab with another person with this title
+ # to get "マターリしようよ"
+ "mapAreaConditionList": [
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6836",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2099-12-31 00:00:00.0",
+ },
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6837",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2024-01-25 02:00:00.0",
+ },
+ ],
+ },
+ {
+ "mapAreaId": "2206206", # Blow My Mind
+ "length": "1",
+ # SS on CHAOS EXPERT, Hydra EXPERT, Surive EXPERT and Jakarta PROGRESSION EXPERT
+ # to get the title "Can you hear me?"
+ "mapAreaConditionList": [
+ {
+ "type": MapAreaConditionType.TROPHY_OBTAINED.value,
+ "conditionId": "6838",
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2099-12-31 00:00:00.0",
+ },
+ ],
+ },
+ {
+ "mapAreaId": "2206207", # VALLIS-NERIA
+ "length": "6",
+ # Finish the 6 other areas
+ "mapAreaConditionList": [
+ {
+ "type": MapAreaConditionType.MAP_AREA_CLEARED.value,
+ "conditionId": str(x),
+ "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ "startDate": "2023-12-14 07:00:00.0",
+ "endDate": "2099-12-31 00:00:00.0",
+ }
+ for x in range(2206201, 2206207)
+ ],
+ },
+ # {
+ # "mapAreaId": "3229301", # Mystic Rainbow of LUMINOUS Area 1
+ # "length": "1",
+ # # Unlocks when any of the mainline LUMINOUS maps are completed?
+ # "mapAreaConditionList": [
+ # # TODO
+ # ]
+ # },
+ # {
+ # "mapAreaId": "3229302", # Mystic Rainbow of LUMINOUS Area 2
+ # "length": "5",
+ # # Unlocks when LUMINOUS ep. I is completed
+ # "mapAreaConditionList": [
+ # {
+ # "type": MapAreaConditionType.MAP_AREA_CLEARED.value,
+ # "conditionId": str(x),
+ # "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ # "startDate": "2023-12-14 07:00:00.0",
+ # "endDate": "2099-12-31 00:00:00.0",
+ # }
+ # for x in range(3220101, 3220106)
+ # ]
+ # },
+ # {
+ # "mapAreaId": "3229303", # Mystic Rainbow of LUMINOUS Area 3
+ # "length": "5",
+ # # Unlocks when LUMINOUS ep. II is completed
+ # "mapAreaConditionList": [
+ # {
+ # "type": MapAreaConditionType.MAP_AREA_CLEARED.value,
+ # "conditionId": str(x),
+ # "logicalOpe": MapAreaConditionLogicalOperator.AND.value,
+ # "startDate": "2023-12-14 07:00:00.0",
+ # "endDate": "2099-12-31 00:00:00.0",
+ # }
+ # for x in range(3220201, 3220206)
+ # ]
+ # },
+ # {
+ # "mapAreaId": "3229304", # Mystic Rainbow of LUMINOUS Area 4
+ # "length": "5",
+ # # Unlocks when LUMINOUS ep. III is completed
+ # "mapAreaConditionList": [
+
+ # ]
+ # }
+ ],
+ }
diff --git a/titles/chuni/new.py b/titles/chuni/new.py
index 9709f00..2275a6e 100644
--- a/titles/chuni/new.py
+++ b/titles/chuni/new.py
@@ -32,6 +32,8 @@ class ChuniNew(ChuniBase):
return "210"
if self.version == ChuniConstants.VER_CHUNITHM_SUN_PLUS:
return "215"
+ if self.version == ChuniConstants.VER_CHUNITHM_LUMINOUS:
+ return "220"
async def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
# use UTC time and convert it to JST time by adding +9
diff --git a/titles/chuni/schema/item.py b/titles/chuni/schema/item.py
index 5077e14..2f386ef 100644
--- a/titles/chuni/schema/item.py
+++ b/titles/chuni/schema/item.py
@@ -243,6 +243,36 @@ matching = Table(
mysql_charset="utf8mb4",
)
+cmission = Table(
+ "chuni_item_cmission",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("missionId", Integer, nullable=False),
+ Column("point", Integer),
+ UniqueConstraint("user", "missionId", name="chuni_item_cmission_uk"),
+ mysql_charset="utf8mb4",
+)
+
+cmission_progress = Table(
+ "chuni_item_cmission_progress",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
+ Column("missionId", Integer, nullable=False),
+ Column("order", Integer),
+ Column("stage", Integer),
+ Column("progress", Integer),
+ UniqueConstraint(
+ "user", "missionId", "order", name="chuni_item_cmission_progress_uk"
+ ),
+ mysql_charset="utf8mb4",
+)
+
class ChuniItemData(BaseData):
async def get_oldest_free_matching(self, version: int) -> Optional[Row]:
@@ -594,3 +624,66 @@ class ChuniItemData(BaseData):
)
return None
return result.lastrowid
+
+ async def put_cmission_progress(
+ self, user_id: int, mission_id: int, progress_data: Dict
+ ) -> Optional[int]:
+ progress_data["user"] = user_id
+ progress_data["missionId"] = mission_id
+
+ sql = insert(cmission_progress).values(**progress_data)
+ conflict = sql.on_duplicate_key_update(**progress_data)
+ result = await self.execute(conflict)
+
+ if result is None:
+ return None
+
+ return result.lastrowid
+
+ async def get_cmission_progress(
+ self, user_id: int, mission_id: int
+ ) -> Optional[List[Row]]:
+ sql = cmission_progress.select(
+ and_(
+ cmission_progress.c.user == user_id,
+ cmission_progress.c.missionId == mission_id,
+ )
+ ).order_by(cmission_progress.c.order.asc())
+ result = await self.execute(sql)
+
+ if result is None:
+ return None
+
+ return result.fetchall()
+
+ async def get_cmission(self, user_id: int, mission_id: int) -> Optional[Row]:
+ sql = cmission.select(
+ and_(cmission.c.user == user_id, cmission.c.missionId == mission_id)
+ )
+ result = await self.execute(sql)
+
+ if result is None:
+ return None
+
+ return result.fetchone()
+
+ async def put_cmission(self, user_id: int, mission_data: Dict) -> Optional[int]:
+ mission_data["user"] = user_id
+
+ sql = insert(cmission).values(**mission_data)
+ conflict = sql.on_duplicate_key_update(**mission_data)
+ result = await self.execute(conflict)
+
+ if result is None:
+ return None
+
+ return result.lastrowid
+
+ async def get_cmissions(self, user_id: int) -> Optional[List[Row]]:
+ sql = cmission.select(cmission.c.user == user_id)
+ result = await self.execute(sql)
+
+ if result is None:
+ return None
+
+ return result.fetchall()
diff --git a/titles/ongeki/index.py b/titles/ongeki/index.py
index d7f3e0e..3bd0e15 100644
--- a/titles/ongeki/index.py
+++ b/titles/ongeki/index.py
@@ -216,16 +216,20 @@ class OngekiServlet(BaseServlet):
)
return Response(zlib.compress(b'{"stat": "0"}'))
- try:
- unzip = zlib.decompress(req_raw)
+ if version < 105:
+ # O.N.G.E.K.I base don't use zlib
+ req_data = json.loads(req_raw)
+ else:
+ try:
+ unzip = zlib.decompress(req_raw)
+
+ except zlib.error as e:
+ self.logger.error(
+ f"Failed to decompress v{version} {endpoint} request -> {e}"
+ )
+ return Response(zlib.compress(b'{"stat": "0"}'))
- except zlib.error as e:
- self.logger.error(
- f"Failed to decompress v{version} {endpoint} request -> {e}"
- )
- return Response(zlib.compress(b'{"stat": "0"}'))
-
- req_data = json.loads(unzip)
+ req_data = json.loads(unzip)
self.logger.info(
f"v{version} {endpoint} request from {client_ip}"
@@ -251,9 +255,12 @@ class OngekiServlet(BaseServlet):
self.logger.debug(f"Response {resp}")
- zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
+ resp_raw = json.dumps(resp, ensure_ascii=False).encode("utf-8")
+ zipped = zlib.compress(resp_raw)
if not encrtped or version < 120:
+ if version < 105:
+ return Response(resp_raw)
return Response(zipped)
padded = pad(zipped, 16)
diff --git a/titles/pokken/base.py b/titles/pokken/base.py
index fe11e99..f864824 100644
--- a/titles/pokken/base.py
+++ b/titles/pokken/base.py
@@ -226,7 +226,7 @@ class PokkenBase:
load_usr.rankmatch_success = profile_dict.get("rankmatch_success", 0)
load_usr.beat_num = profile_dict.get("beat_num", 0)
load_usr.title_text_id = profile_dict.get("title_text_id", 2)
- load_usr.title_plate_id = profile_dict.get("title_plate_id", 1)
+ load_usr.title_plate_id = profile_dict.get("title_plate_id", 31)
load_usr.title_decoration_id = profile_dict.get("title_decoration_id", 1)
load_usr.navi_trainer = profile_dict.get("navi_trainer", 0)
load_usr.navi_version_id = profile_dict.get("navi_version_id", 0)
diff --git a/titles/wacca/base.py b/titles/wacca/base.py
index 8a37c2b..58b1ba9 100644
--- a/titles/wacca/base.py
+++ b/titles/wacca/base.py
@@ -833,14 +833,14 @@ class WaccaBase:
# TODO: Coop and vs data
async def handle_user_music_updateCoop_request(self, data: Dict) -> Dict:
coop_info = data["params"][4]
- return self.handle_user_music_update_request(data)
+ return await self.handle_user_music_update_request(data)
async def handle_user_music_updateVersus_request(self, data: Dict) -> Dict:
vs_info = data["params"][4]
- return self.handle_user_music_update_request(data)
+ return await self.handle_user_music_update_request(data)
async def handle_user_music_updateTrial_request(self, data: Dict) -> Dict:
- return self.handle_user_music_update_request(data)
+ return await self.handle_user_music_update_request(data)
async def handle_user_mission_update_request(self, data: Dict) -> Dict:
req = UserMissionUpdateRequest(data)