Merge branch 'develop' into idac

This commit is contained in:
Dniel97 2024-01-03 18:30:42 +01:00
commit 0b38778c19
Signed by: Dniel97
GPG Key ID: 6180B3C768FB2E08
22 changed files with 264 additions and 116 deletions

View File

@ -157,6 +157,8 @@ class BaseData:
def fix_bools(self, data: Dict) -> Dict: def fix_bools(self, data: Dict) -> Dict:
for k, v in data.items(): for k, v in data.items():
if k == "userName" or k == "teamName":
continue
if type(v) == str and v.lower() == "true": if type(v) == str and v.lower() == "true":
data[k] = True data[k] = True
elif type(v) == str and v.lower() == "false": elif type(v) == str and v.lower() == "false":

View File

@ -0,0 +1,12 @@
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE chuni_score_playlog
CHANGE COLUMN isClear isClear TINYINT(1) NULL DEFAULT NULL;
ALTER TABLE chuni_score_best
CHANGE COLUMN isSuccess isSuccess TINYINT(1) NULL DEFAULT NULL ;
ALTER TABLE chuni_score_playlog
DROP COLUMN ticketId;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -0,0 +1,12 @@
SET FOREIGN_KEY_CHECKS = 0;
ALTER TABLE chuni_score_playlog
CHANGE COLUMN isClear isClear TINYINT(6) NULL DEFAULT NULL;
ALTER TABLE chuni_score_best
CHANGE COLUMN isSuccess isSuccess INT(11) NULL DEFAULT NULL ;
ALTER TABLE chuni_score_playlog
ADD COLUMN ticketId INT(11) NULL AFTER machineType;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -54,6 +54,7 @@ Games listed below have been tested and confirmed working.
| 11 | CHUNITHM NEW!! | | 11 | CHUNITHM NEW!! |
| 12 | CHUNITHM NEW PLUS!! | | 12 | CHUNITHM NEW PLUS!! |
| 13 | CHUNITHM SUN | | 13 | CHUNITHM SUN |
| 14 | CHUNITHM SUN PLUS |
### Importer ### Importer
@ -73,10 +74,9 @@ The importer for Chunithm will import: Events, Music, Charge Items and Avatar Ac
Config file is located in `config/chuni.yaml`. Config file is located in `config/chuni.yaml`.
| Option | Info | | Option | Info |
| ----------------- | ------------------------------------------------------------------------------------------------------- | |------------------|----------------------------------------------------------------------------------------------------------------|
| `news_msg` | If this is set, the news at the top of the main screen will be displayed (up to Chunithm Paradise Lost) | | `news_msg` | If this is set, the news at the top of the main screen will be displayed (up to Chunithm Paradise Lost) |
| `name` | If this is set, all players that are not on a team will use this one by default. | | `name` | If this is set, all players that are not on a team will use this one by default. |
| `rank_scale` | Scales the in-game ranking based on the number of teams within the database |
| `use_login_bonus`| This is used to enable the login bonuses | | `use_login_bonus`| This is used to enable the login bonuses |
| `crypto` | This option is used to enable the TLS Encryption | | `crypto` | This option is used to enable the TLS Encryption |
@ -134,12 +134,6 @@ INSERT INTO aime.chuni_profile_team (teamName) VALUES (<teamName>);
``` ```
Team names can be regular ASCII, and they will be displayed ingame. Team names can be regular ASCII, and they will be displayed ingame.
On smaller installations, you may also wish to enable scaled team rankings. By default, Chunithm determines team ranking within the first 100 teams. This can be configured in the YAML:
```yaml
team:
rank_scale: True # Scales the in-game ranking based on the number of teams within the database, rather than the default scale of ~100 that the game normally uses.
```
### Favorite songs ### Favorite songs
You can set the songs that will be in a user's Favorite Songs category using the following SQL entries: You can set the songs that will be in a user's Favorite Songs category using the following SQL entries:
```sql ```sql
@ -173,15 +167,6 @@ The importer for crossbeats REV. will import Music.
Config file is located in `config/cxb.yaml`. Config file is located in `config/cxb.yaml`.
| Option | Info |
| --------------------- | ---------------------------------------------------------- |
| `hostname` | Requires a proper `hostname` (not localhost!) to run |
| `ssl_enable` | Enables/Disables the use of the `ssl_cert` and `ssl_key` |
| `port` | Set your unsecure port number |
| `port_secure` | Set your secure/SSL port number |
| `ssl_cert`, `ssl_key` | Enter your SSL certificate (requires not self signed cert) |
## maimai DX ## maimai DX
### Versions ### Versions

View File

@ -19,6 +19,9 @@ version:
13: 13:
rom: 2.10.00 rom: 2.10.00
data: 2.10.00 data: 2.10.00
14:
rom: 2.15.00
data: 2.15.00
crypto: crypto:
encrypted_only: False encrypted_only: False

View File

@ -5,7 +5,7 @@ A network service emulator for games running SEGA'S ALL.NET service, and similar
Games listed below have been tested and confirmed working. Only game versions older then the version currently active in arcades, or games versions that have not recieved a major update in over one year, are supported. Games listed below have been tested and confirmed working. Only game versions older then the version currently active in arcades, or games versions that have not recieved a major update in over one year, are supported.
+ CHUNITHM + CHUNITHM
+ All versions up to SUN + All versions up to SUN PLUS
+ crossbeats REV. + crossbeats REV.
+ All versions + omnimix + All versions + omnimix

View File

@ -6,5 +6,5 @@ from titles.chuni.read import ChuniReader
index = ChuniServlet index = ChuniServlet
database = ChuniData database = ChuniData
reader = ChuniReader reader = ChuniReader
game_codes = [ChuniConstants.GAME_CODE, ChuniConstants.GAME_CODE_NEW] game_codes = [ChuniConstants.GAME_CODE, ChuniConstants.GAME_CODE_NEW, ChuniConstants.GAME_CODE_INT]
current_schema_version = 4 current_schema_version = 5

View File

@ -240,7 +240,6 @@ class ChuniBase:
"isDumpUpload": "false", "isDumpUpload": "false",
"isAou": "false", "isAou": "false",
} }
def handle_get_user_activity_api_request(self, data: Dict) -> Dict: def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
user_activity_list = self.data.profile.get_profile_activity( user_activity_list = self.data.profile.get_profile_activity(
data["userId"], data["kind"] data["userId"], data["kind"]
@ -420,13 +419,13 @@ class ChuniBase:
all_entries = self.data.score.get_rival_music(rival_id) all_entries = self.data.score.get_rival_music(rival_id)
# Process the entries based on max_count and nextIndex # Process the entries based on max_count and nextIndex
for music in all_entries[next_index:]: for music in all_entries:
music_id = music["musicId"] music_id = music["musicId"]
level = music["level"] level = music["level"]
score = music["scoreMax"] score = music["scoreMax"]
rank = music["scoreRank"] rank = music["scoreRank"]
# Create a music entry for the current music_id # Create a music entry for the current music_id if it's unique
music_entry = next((entry for entry in user_rival_music_list if entry["musicId"] == music_id), None) music_entry = next((entry for entry in user_rival_music_list if entry["musicId"] == music_id), None)
if music_entry is None: if music_entry is None:
music_entry = { music_entry = {
@ -436,15 +435,20 @@ class ChuniBase:
} }
user_rival_music_list.append(music_entry) user_rival_music_list.append(music_entry)
# Create a level entry for the current level # Create a level entry for the current level if it's unique or has a higher score
level_entry = next((entry for entry in music_entry["userRivalMusicDetailList"] if entry["level"] == level), None)
if level_entry is None:
level_entry = { level_entry = {
"level": level, "level": level,
"scoreMax": score, "scoreMax": score,
"scoreRank": rank "scoreRank": rank
} }
music_entry["userRivalMusicDetailList"].append(level_entry) music_entry["userRivalMusicDetailList"].append(level_entry)
elif score > level_entry["scoreMax"]:
level_entry["scoreMax"] = score
level_entry["scoreRank"] = rank
# Calculate the length for each "musicId" by counting the levels # Calculate the length for each "musicId" by counting the unique levels
for music_entry in user_rival_music_list: for music_entry in user_rival_music_list:
music_entry["length"] = len(music_entry["userRivalMusicDetailList"]) music_entry["length"] = len(music_entry["userRivalMusicDetailList"])
@ -452,12 +456,12 @@ class ChuniBase:
result = { result = {
"userId": data["userId"], "userId": data["userId"],
"rivalId": data["rivalId"], "rivalId": data["rivalId"],
"nextIndex": str(next_index + len(all_entries) if len(all_entries) <= len(user_rival_music_list) else -1), "nextIndex": str(next_index + len(user_rival_music_list[next_index: next_index + max_count]) if max_count <= len(user_rival_music_list[next_index: next_index + max_count]) else -1),
"userRivalMusicList": user_rival_music_list[:max_count] "userRivalMusicList": user_rival_music_list[next_index: next_index + max_count]
} }
return result return result
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict: def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
user_fav_item_list = [] user_fav_item_list = []
@ -873,8 +877,11 @@ class ChuniBase:
if "userPlaylogList" in upsert: if "userPlaylogList" in upsert:
for playlog in upsert["userPlaylogList"]: for playlog in upsert["userPlaylogList"]:
# convert the player names to utf-8 # convert the player names to utf-8
if playlog["playedUserName1"] is not None:
playlog["playedUserName1"] = self.read_wtf8(playlog["playedUserName1"]) playlog["playedUserName1"] = self.read_wtf8(playlog["playedUserName1"])
if playlog["playedUserName2"] is not None:
playlog["playedUserName2"] = self.read_wtf8(playlog["playedUserName2"]) playlog["playedUserName2"] = self.read_wtf8(playlog["playedUserName2"])
if playlog["playedUserName3"] is not None:
playlog["playedUserName3"] = self.read_wtf8(playlog["playedUserName3"]) playlog["playedUserName3"] = self.read_wtf8(playlog["playedUserName3"])
self.data.score.put_playlog(user_id, playlog, self.version) self.data.score.put_playlog(user_id, playlog, self.version)

View File

@ -1,6 +1,7 @@
class ChuniConstants: class ChuniConstants:
GAME_CODE = "SDBT" GAME_CODE = "SDBT"
GAME_CODE_NEW = "SDHD" GAME_CODE_NEW = "SDHD"
GAME_CODE_INT = "SDGS"
CONFIG_NAME = "chuni.yaml" CONFIG_NAME = "chuni.yaml"
@ -18,7 +19,7 @@ class ChuniConstants:
VER_CHUNITHM_NEW = 11 VER_CHUNITHM_NEW = 11
VER_CHUNITHM_NEW_PLUS = 12 VER_CHUNITHM_NEW_PLUS = 12
VER_CHUNITHM_SUN = 13 VER_CHUNITHM_SUN = 13
VER_CHUNITHM_SUN_PLUS = 14
VERSION_NAMES = [ VERSION_NAMES = [
"CHUNITHM", "CHUNITHM",
"CHUNITHM PLUS", "CHUNITHM PLUS",
@ -33,7 +34,8 @@ class ChuniConstants:
"CHUNITHM PARADISE", "CHUNITHM PARADISE",
"CHUNITHM NEW!!", "CHUNITHM NEW!!",
"CHUNITHM NEW PLUS!!", "CHUNITHM NEW PLUS!!",
"CHUNITHM SUN" "CHUNITHM SUN",
"CHUNITHM SUN PLUS"
] ]
@classmethod @classmethod

View File

@ -31,6 +31,7 @@ from .paradise import ChuniParadise
from .new import ChuniNew from .new import ChuniNew
from .newplus import ChuniNewPlus from .newplus import ChuniNewPlus
from .sun import ChuniSun from .sun import ChuniSun
from .sunplus import ChuniSunPlus
class ChuniServlet(BaseServlet): class ChuniServlet(BaseServlet):
@ -58,6 +59,7 @@ class ChuniServlet(BaseServlet):
ChuniNew, ChuniNew,
ChuniNewPlus, ChuniNewPlus,
ChuniSun, ChuniSun,
ChuniSunPlus,
] ]
self.logger = logging.getLogger("chuni") self.logger = logging.getLogger("chuni")
@ -99,8 +101,14 @@ class ChuniServlet(BaseServlet):
] ]
for method in method_list: for method in method_list:
method_fixed = inflection.camelize(method)[6:-7] method_fixed = inflection.camelize(method)[6:-7]
# number of iterations was changed to 70 in SUN # number of iterations was changed to 70 in SUN and then to 36
iter_count = 70 if version >= ChuniConstants.VER_CHUNITHM_SUN else 44 if version == ChuniConstants.VER_CHUNITHM_SUN_PLUS:
iter_count = 36
elif version == ChuniConstants.VER_CHUNITHM_SUN:
iter_count = 70
else:
iter_count = 44
hash = PBKDF2( hash = PBKDF2(
method_fixed, method_fixed,
bytes.fromhex(keys[2]), bytes.fromhex(keys[2]),
@ -120,8 +128,8 @@ class ChuniServlet(BaseServlet):
return ( return (
[], [],
[ [
("render_POST", "/{version}/ChuniServlet/{endpoint}", {}), ("render_POST", "/{game}/{version}/ChuniServlet/{endpoint}", {}),
("render_POST", "/{version}/ChuniServlet/MatchingServer/{endpoint}", {}) ("render_POST", "/{game}/{version}/ChuniServlet/MatchingServer/{endpoint}", {})
] ]
) )
@ -142,22 +150,25 @@ class ChuniServlet(BaseServlet):
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]: def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80: if not self.core_cfg.server.is_using_proxy and Utils.get_title_port(self.core_cfg) != 80:
return (f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_ver}/", self.core_cfg.title.hostname) return (f"http://{self.core_cfg.title.hostname}:{Utils.get_title_port(self.core_cfg)}/{game_code}/{game_ver}/", self.core_cfg.title.hostname)
return (f"http://{self.core_cfg.title.hostname}/{game_ver}/", self.core_cfg.title.hostname) return (f"http://{self.core_cfg.title.hostname}/{game_code}/{game_ver}/", self.core_cfg.title.hostname)
def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes: def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
endpoint = matchers['endpoint'] endpoint = matchers['endpoint']
version = int(matchers['version']) version = int(matchers['version'])
game_code = matchers['game']
if endpoint.lower() == "ping": if endpoint.lower() == "ping":
return zlib.compress(b'{"returnCode": "1"}') return zlib.compress(b'{"returnCode": "1"}')
req_raw = request.content.getvalue() req_raw = request.content.getvalue()
encrtped = False encrtped = False
internal_ver = 0 internal_ver = 0
client_ip = Utils.get_ip_addr(request) client_ip = Utils.get_ip_addr(request)
if game_code == "SDHD" or game_code == "SDBT": # JP
if version < 105: # 1.0 if version < 105: # 1.0
internal_ver = ChuniConstants.VER_CHUNITHM internal_ver = ChuniConstants.VER_CHUNITHM
elif version >= 105 and version < 110: # PLUS elif version >= 105 and version < 110: # PLUS
@ -184,8 +195,21 @@ class ChuniServlet(BaseServlet):
internal_ver = ChuniConstants.VER_CHUNITHM_NEW internal_ver = ChuniConstants.VER_CHUNITHM_NEW
elif version >= 205 and version < 210: # NEW PLUS!! elif version >= 205 and version < 210: # NEW PLUS!!
internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS internal_ver = ChuniConstants.VER_CHUNITHM_NEW_PLUS
elif version >= 210: # SUN elif version >= 210 and version < 215: # SUN
internal_ver = ChuniConstants.VER_CHUNITHM_SUN internal_ver = ChuniConstants.VER_CHUNITHM_SUN
elif version >= 215: # SUN
internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS
elif game_code == "SDGS": # Int
if version < 110: # SUPERSTAR
internal_ver = ChuniConstants.PARADISE
elif version >= 110 and version < 115: # NEW
internal_ver = ChuniConstants.VER_CHUNITHM_NEW
elif version >= 115 and version < 120: # NEW PLUS!!
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
internal_ver = ChuniConstants.VER_CHUNITHM_SUN_PLUS
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: 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 # If we get a 32 character long hex string, it's a hash and we're
@ -250,6 +274,7 @@ class ChuniServlet(BaseServlet):
self.logger.info(f"v{version} {endpoint} request from {client_ip}") self.logger.info(f"v{version} {endpoint} request from {client_ip}")
self.logger.debug(req_data) self.logger.debug(req_data)
endpoint = endpoint.replace("C3Exp", "") if game_code == "SDGS" else endpoint
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg) handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg)

View File

@ -71,11 +71,11 @@ class ChuniNew(ChuniBase):
"matchErrorLimit": 9999, "matchErrorLimit": 9999,
"romVersion": self.game_cfg.version.version(self.version)["rom"], "romVersion": self.game_cfg.version.version(self.version)["rom"],
"dataVersion": self.game_cfg.version.version(self.version)["data"], "dataVersion": self.game_cfg.version.version(self.version)["data"],
"matchingUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/200/ChuniServlet/", "matchingUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
"matchingUriX": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/200/ChuniServlet/", "matchingUriX": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
# might be really important for online battle to connect the cabs via UDP port 50201 # might be really important for online battle to connect the cabs via UDP port 50201
"udpHolePunchUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/200/ChuniServlet/", "udpHolePunchUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
"reflectorUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/200/ChuniServlet/", "reflectorUri": f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/200/ChuniServlet/",
}, },
"isDumpUpload": False, "isDumpUpload": False,
"isAou": False, "isAou": False,

View File

@ -21,16 +21,16 @@ class ChuniNewPlus(ChuniNew):
] ]
ret["gameSetting"][ ret["gameSetting"][
"matchingUri" "matchingUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/205/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
ret["gameSetting"][ ret["gameSetting"][
"matchingUriX" "matchingUriX"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/205/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
ret["gameSetting"][ ret["gameSetting"][
"udpHolePunchUri" "udpHolePunchUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/205/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
ret["gameSetting"][ ret["gameSetting"][
"reflectorUri" "reflectorUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/205/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/205/ChuniServlet/"
return ret return ret
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:

View File

@ -24,7 +24,7 @@ course = Table(
Column("scoreMax", Integer), Column("scoreMax", Integer),
Column("isFullCombo", Boolean), Column("isFullCombo", Boolean),
Column("isAllJustice", Boolean), Column("isAllJustice", Boolean),
Column("isSuccess", Boolean), Column("isSuccess", Integer),
Column("scoreRank", Integer), Column("scoreRank", Integer),
Column("eventId", Integer), Column("eventId", Integer),
Column("lastPlayDate", String(25)), Column("lastPlayDate", String(25)),
@ -32,7 +32,7 @@ course = Table(
Column("param2", Integer), Column("param2", Integer),
Column("param3", Integer), Column("param3", Integer),
Column("param4", Integer), Column("param4", Integer),
Column("isClear", Boolean), Column("isClear", Integer),
Column("theoryCount", Integer), Column("theoryCount", Integer),
Column("orderId", Integer), Column("orderId", Integer),
Column("playerRating", Integer), Column("playerRating", Integer),
@ -60,7 +60,7 @@ best_score = Table(
Column("maxComboCount", Integer), Column("maxComboCount", Integer),
Column("isFullCombo", Boolean), Column("isFullCombo", Boolean),
Column("isAllJustice", Boolean), Column("isAllJustice", Boolean),
Column("isSuccess", Boolean), Column("isSuccess", Integer),
Column("fullChain", Integer), Column("fullChain", Integer),
Column("maxChain", Integer), Column("maxChain", Integer),
Column("scoreRank", Integer), Column("scoreRank", Integer),
@ -125,7 +125,7 @@ playlog = Table(
Column("characterId", Integer), Column("characterId", Integer),
Column("skillId", Integer), Column("skillId", Integer),
Column("playKind", Integer), Column("playKind", Integer),
Column("isClear", Boolean), Column("isClear", Integer),
Column("skillLevel", Integer), Column("skillLevel", Integer),
Column("skillEffect", Integer), Column("skillEffect", Integer),
Column("placeName", String(255)), Column("placeName", String(255)),
@ -136,6 +136,7 @@ playlog = Table(
Column("judgeHeaven", Integer), Column("judgeHeaven", Integer),
Column("regionId", Integer), Column("regionId", Integer),
Column("machineType", Integer), Column("machineType", Integer),
Column("ticketId", Integer),
mysql_charset="utf8mb4" mysql_charset="utf8mb4"
) )
@ -255,4 +256,3 @@ class ChuniScoreData(BaseData):
if result is None: if result is None:
return None return None
return result.fetchall() return result.fetchall()

View File

@ -17,16 +17,16 @@ class ChuniSun(ChuniNewPlus):
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)["data"] ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)["data"]
ret["gameSetting"][ ret["gameSetting"][
"matchingUri" "matchingUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/210/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/210/ChuniServlet/"
ret["gameSetting"][ ret["gameSetting"][
"matchingUriX" "matchingUriX"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/210/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/210/ChuniServlet/"
ret["gameSetting"][ ret["gameSetting"][
"udpHolePunchUri" "udpHolePunchUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/210/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/210/ChuniServlet/"
ret["gameSetting"][ ret["gameSetting"][
"reflectorUri" "reflectorUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/210/ChuniServlet/" ] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/210/ChuniServlet/"
return ret return ret
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict: def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:

37
titles/chuni/sunplus.py Normal file
View File

@ -0,0 +1,37 @@
from typing import Dict, Any
from core.config import CoreConfig
from titles.chuni.sun import ChuniSun
from titles.chuni.const import ChuniConstants
from titles.chuni.config import ChuniConfig
class ChuniSunPlus(ChuniSun):
def __init__(self, core_cfg: CoreConfig, game_cfg: ChuniConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = ChuniConstants.VER_CHUNITHM_SUN_PLUS
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
ret = super().handle_get_game_setting_api_request(data)
ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)["rom"]
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)["data"]
ret["gameSetting"][
"matchingUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/215/ChuniServlet/"
ret["gameSetting"][
"matchingUriX"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/215/ChuniServlet/"
ret["gameSetting"][
"udpHolePunchUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/215/ChuniServlet/"
ret["gameSetting"][
"reflectorUri"
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/215/ChuniServlet/"
return ret
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
# I don't know if lastDataVersion is going to matter, I don't think CardMaker 1.35 works this far up
user_data["lastDataVersion"] = "2.15.00"
return user_data

View File

@ -45,7 +45,7 @@ class CardMakerBase:
{ {
"modelKind": 0, "modelKind": 0,
"type": 1, "type": 1,
"titleUri": f"{uri}/{self._parse_int_ver(games_ver['chuni'])}/ChuniServlet/", "titleUri": f"{uri}/SDHD/{self._parse_int_ver(games_ver['chuni'])}/ChuniServlet/",
}, },
# maimai DX # maimai DX
{ {

View File

@ -76,14 +76,22 @@ class CxbServlet(BaseServlet):
return True return True
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]: def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port_ssl(self.core_cfg): title_port_int = Utils.get_title_port(self.core_cfg)
title_port_ssl_int = Utils.get_title_port_ssl(self.core_cfg)
proto = "https" if title_port_ssl_int != 443 else "http"
if proto == "https":
t_port = f":{title_port_ssl_int}" if title_port_ssl_int and not self.core_cfg.server.is_using_proxy else ""
else:
t_port = f":{title_port_int}" if title_port_int and not self.core_cfg.server.is_using_proxy else ""
return ( return (
f"https://{self.core_cfg.title.hostname}:{self.core_cfg.title.port_ssl}", f"{proto}://{self.core_cfg.title.hostname}{t_port}",
"", "",
) )
return (f"https://{self.core_cfg.title.hostname}", "")
def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]: def get_endpoint_matchers(self) -> Tuple[List[Tuple[str, str, Dict]], List[Tuple[str, str, Dict]]]:
return ( return (
[], [],

View File

@ -238,6 +238,10 @@ class Mai2Base:
user_id = data["userId"] user_id = data["userId"]
upsert = data["upsertUserAll"] upsert = data["upsertUserAll"]
if int(user_id) & 1000000000001 == 1000000000001:
self.logger.info("Guest play, ignoring.")
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
if "userData" in upsert and len(upsert["userData"]) > 0: if "userData" in upsert and len(upsert["userData"]) > 0:
upsert["userData"][0].pop("accessCode") upsert["userData"][0].pop("accessCode")
upsert["userData"][0].pop("userId") upsert["userData"][0].pop("userId")

View File

@ -97,6 +97,10 @@ class Mai2DX(Mai2Base):
user_id = data["userId"] user_id = data["userId"]
upsert = data["upsertUserAll"] upsert = data["upsertUserAll"]
if int(user_id) & 1000000000001 == 1000000000001:
self.logger.info("Guest play, ignoring.")
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
if "userData" in upsert and len(upsert["userData"]) > 0: if "userData" in upsert and len(upsert["userData"]) > 0:
upsert["userData"][0]["isNetMember"] = 1 upsert["userData"][0]["isNetMember"] = 1
upsert["userData"][0].pop("accessCode") upsert["userData"][0].pop("accessCode")

View File

@ -29,6 +29,12 @@ class SaoServerConfig:
self.__config, "sao", "server", "auto_register", default=True self.__config, "sao", "server", "auto_register", default=True
) )
@property
def use_https(self) -> bool:
return CoreConfig.get_config_field(
self.__config, "sao", "server", "use_https", default=False
)
class SaoCryptConfig: class SaoCryptConfig:
def __init__(self, parent_config: "SaoConfig"): def __init__(self, parent_config: "SaoConfig"):
self.__config = parent_config self.__config = parent_config

View File

@ -76,13 +76,17 @@ class SaoServlet(BaseServlet):
return True return True
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]: def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
if not self.core_cfg.server.is_using_proxy and Utils.get_title_port_ssl(self.core_cfg): port_ssl = Utils.get_title_port_ssl(self.core_cfg)
return ( port_normal = Utils.get_title_port(self.core_cfg)
f"https://{self.core_cfg.title.hostname}:{self.core_cfg.title.port_ssl}/",
f"{self.core_cfg.title.hostname}/",
)
return (f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/", "") proto = "http"
port = f":{port_normal}" if not self.core_cfg.server.is_using_proxy and port_normal != 80 else ""
if self.game_cfg.server.use_https:
proto = "https"
port = f":{port_ssl}" if not self.core_cfg.server.is_using_proxy and port_ssl != 443 else ""
return (f"{proto}://{self.core_cfg.title.hostname}{port}/", "")
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]: def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]:
if not self.game_cfg.server.enable: if not self.game_cfg.server.enable:
@ -96,7 +100,9 @@ class SaoServlet(BaseServlet):
iv = b"" iv = b""
req_raw = request.content.read() req_raw = request.content.read()
sao_request = req_raw.hex() if len(req_raw) < 40:
self.logger.warn(f"Malformed request to {endpoint} - {req_raw.hex()}")
return b""
req_header = SaoRequestHeader(req_raw) req_header = SaoRequestHeader(req_raw)
cmd_str = f"{req_header.cmd:04x}" cmd_str = f"{req_header.cmd:04x}"

View File

@ -3,11 +3,14 @@ from datetime import datetime, timedelta
import json import json
from core.config import CoreConfig from core.config import CoreConfig
from titles.wacca.handlers import Dict
from titles.wacca.lily import WaccaLily from titles.wacca.lily import WaccaLily
from titles.wacca.config import WaccaConfig from titles.wacca.config import WaccaConfig
from titles.wacca.const import WaccaConstants from titles.wacca.const import WaccaConstants
from titles.wacca.handlers import * from titles.wacca.handlers import *
from core.const import AllnetCountryCode
class WaccaLilyR(WaccaLily): class WaccaLilyR(WaccaLily):
def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None: def __init__(self, cfg: CoreConfig, game_cfg: WaccaConfig) -> None:
@ -36,6 +39,38 @@ class WaccaLilyR(WaccaLily):
(210003, 0), (210003, 0),
] ]
def handle_housing_start_request(self, data: Dict) -> Dict:
req = HousingStartRequestV2(data)
allnet_region_id = None
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 == AllnetCountryCode.JAPAN.value:
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 = region
else:
region_id = self.region_id
elif req.appVersion.country in WaccaConstants.VALID_COUNTRIES:
region_id = WaccaConstants.Region[req.appVersion.country]
else:
region_id = WaccaConstants.Region.NONE
resp = HousingStartResponseV1(region_id)
return resp.make()
def handle_user_status_create_request(self, data: Dict) -> Dict: def handle_user_status_create_request(self, data: Dict) -> Dict:
req = UserStatusCreateRequest(data) req = UserStatusCreateRequest(data)
resp = super().handle_user_status_create_request(data) resp = super().handle_user_status_create_request(data)