forked from Hay1tsme/artemis
Merge remote-tracking branch 'origin/develop' into fork_develop
This commit is contained in:
commit
8fe0acae93
@ -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):
|
||||
@ -115,6 +116,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)
|
||||
@ -136,15 +138,19 @@ 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"]
|
||||
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"]
|
||||
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(".", "")
|
||||
@ -154,6 +160,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")
|
||||
|
||||
@ -191,7 +198,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()
|
||||
|
||||
|
@ -31,6 +31,62 @@ class KeychipPlatformsCodes():
|
||||
NU = ("A60E", "A60E", "A60E")
|
||||
NUSX = ("A61X", "A69X")
|
||||
ALLS = "A63E"
|
||||
|
||||
class RegionIDs(Enum):
|
||||
pass
|
||||
|
||||
class AllnetCountryCode(Enum):
|
||||
JAPAN = "JPN"
|
||||
UNITED_STATES = "USA"
|
||||
HONG_KONG = "HKG"
|
||||
SINGAPORE = "SGP"
|
||||
SOUTH_KOREA = "KOR"
|
||||
TAIWAN = "TWN"
|
||||
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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
@ -50,9 +52,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
|
||||
@ -61,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)
|
||||
@ -109,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
|
||||
|
1
core/data/schema/versions/SDBT_2_rollback.sql
Normal file
1
core/data/schema/versions/SDBT_2_rollback.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE chuni_score_course DROP COLUMN theoryCount, DROP COLUMN orderId, DROP COLUMN playerRating;
|
1
core/data/schema/versions/SDBT_3_upgrade.sql
Normal file
1
core/data/schema/versions/SDBT_3_upgrade.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE chuni_score_course ADD theoryCount int(11), ADD orderId int(11), ADD playerRating int(11);
|
7
core/data/schema/versions/SDDT_2_rollback.sql
Normal file
7
core/data/schema/versions/SDDT_2_rollback.sql
Normal file
@ -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;
|
27
core/data/schema/versions/SDDT_3_upgrade.sql
Normal file
27
core/data/schema/versions/SDDT_3_upgrade.sql
Normal file
@ -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;
|
@ -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"])
|
||||
|
11
dbutils.py
11
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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
41
docs/prod.md
Normal file
41
docs/prod.md
Normal file
@ -0,0 +1,41 @@
|
||||
# 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)
|
||||
|
||||
### 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
|
||||
`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 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)
|
||||
`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.
|
@ -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;
|
||||
@ -134,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;
|
||||
|
@ -1,6 +1,7 @@
|
||||
server:
|
||||
enable: True
|
||||
loglevel: "info"
|
||||
prefecture_name: "Hokkaido"
|
||||
|
||||
mods:
|
||||
always_vip: True
|
||||
|
1
index.py
1
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']))
|
||||
|
14
readme.md
14
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
|
||||
@ -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`
|
||||
## 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.
|
||||
|
@ -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"))
|
||||
|
||||
|
@ -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'
|
||||
)
|
||||
|
@ -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"]:
|
||||
|
58
titles/ongeki/brightmemory.py
Normal file
58
titles/ongeki/brightmemory.py
Normal file
@ -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
|
||||
}
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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()
|
@ -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'
|
||||
)
|
||||
|
@ -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'
|
||||
)
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
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
|
||||
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:
|
||||
@ -23,53 +23,61 @@ 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 = WaccaConstants.Region.HOKKAIDO
|
||||
|
||||
else:
|
||||
self.region_id = WaccaConstants.Region[prefecture_name]
|
||||
|
||||
def handle_housing_get_request(self, data: Dict) -> Dict:
|
||||
req = BaseRequest(data)
|
||||
@ -85,17 +93,30 @@ class WaccaBase():
|
||||
def handle_housing_start_request(self, data: Dict) -> Dict:
|
||||
req = HousingStartRequestV1(data)
|
||||
|
||||
resp = HousingStartResponseV1(
|
||||
1,
|
||||
[ # 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
|
||||
]
|
||||
)
|
||||
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_advertise_GetNews_request(self, data: Dict) -> Dict:
|
||||
@ -110,7 +131,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 +138,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 +162,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()
|
||||
|
||||
@ -375,12 +376,12 @@ 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 = []
|
||||
|
||||
add_next = True
|
||||
tmp: List[StageInfo] = []
|
||||
|
||||
for d in self.allowed_stages:
|
||||
stage_info = StageInfo(d[0], d[1])
|
||||
|
||||
@ -393,11 +394,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(tmp)):
|
||||
if tmp[x].danLevel >= 10 and (tmp[x + 1].clearStatus >= 1 or tmp[x].clearStatus >= 1):
|
||||
resp.stageList.append(tmp[x])
|
||||
elif tmp[x].danLevel < 10:
|
||||
resp.stageList.append(tmp[x])
|
||||
|
||||
return resp.make()
|
||||
|
||||
@ -763,7 +766,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
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,7 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from core.const import AllnetJapanRegionId
|
||||
|
||||
class WaccaConstants():
|
||||
CONFIG_NAME = "wacca.yaml"
|
||||
@ -95,19 +98,92 @@ 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
|
||||
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"])
|
||||
|
||||
@classmethod
|
||||
def game_ver_to_string(cls, ver: int):
|
||||
return cls.VERSION_NAMES[ver]
|
||||
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
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -3,7 +3,94 @@ from enum import Enum
|
||||
|
||||
from titles.wacca.const import WaccaConstants
|
||||
|
||||
class HousingInfo():
|
||||
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: str = split[3]
|
||||
self.build = int(split[4])
|
||||
self.role: str = 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
|
||||
"""
|
||||
@ -17,7 +104,7 @@ class HousingInfo():
|
||||
def make(self) -> List:
|
||||
return [ self.id, self.val ]
|
||||
|
||||
class Notice():
|
||||
class Notice:
|
||||
name: str = ""
|
||||
title: str = ""
|
||||
message: str = ""
|
||||
@ -40,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
|
||||
@ -48,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 = ""
|
||||
@ -106,7 +193,7 @@ class PlayVersionStatus(Enum):
|
||||
VersionTooNew = 1
|
||||
VersionUpgrade = 2
|
||||
|
||||
class PlayModeCounts():
|
||||
class PlayModeCounts:
|
||||
seasonId: int = 0
|
||||
modeId: int = 0
|
||||
playNum: int = 0
|
||||
@ -123,7 +210,7 @@ class PlayModeCounts():
|
||||
self.playNum
|
||||
]
|
||||
|
||||
class SongUnlock():
|
||||
class SongUnlock:
|
||||
songId: int = 0
|
||||
difficulty: int = 0
|
||||
whenAppeared: int = 0
|
||||
@ -143,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
|
||||
@ -152,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
|
||||
@ -180,7 +267,7 @@ class IconItem(GenericItemSend):
|
||||
self.whenAcquired
|
||||
]
|
||||
|
||||
class TrophyItem():
|
||||
class TrophyItem:
|
||||
trophyId: int = 0
|
||||
season: int = 1
|
||||
progress: int = 0
|
||||
@ -200,7 +287,7 @@ class TrophyItem():
|
||||
self.badgeType
|
||||
]
|
||||
|
||||
class TicketItem():
|
||||
class TicketItem:
|
||||
userTicketId: int = 0
|
||||
ticketId: int = 0
|
||||
whenExpires: int = 0
|
||||
@ -233,7 +320,7 @@ class NavigatorItem(IconItem):
|
||||
self.usesToday
|
||||
]
|
||||
|
||||
class SkillItem():
|
||||
class SkillItem:
|
||||
skill_type: int
|
||||
level: int
|
||||
flag: int
|
||||
@ -247,7 +334,7 @@ class SkillItem():
|
||||
self.badge
|
||||
]
|
||||
|
||||
class UserItemInfoV1():
|
||||
class UserItemInfoV1:
|
||||
def __init__(self) -> None:
|
||||
self.songUnlocks: List[SongUnlock] = []
|
||||
self.titles: List[GenericItemSend] = []
|
||||
@ -331,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:
|
||||
@ -351,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
|
||||
@ -413,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()
|
||||
@ -446,7 +533,7 @@ class BestScoreDetailV1():
|
||||
class BestScoreDetailV2(BestScoreDetailV1):
|
||||
gradeCounts: SongDetailGradeCountsV2 = SongDetailGradeCountsV2()
|
||||
|
||||
class SongUpdateJudgementCounts():
|
||||
class SongUpdateJudgementCounts:
|
||||
marvCt: int
|
||||
greatCt: int
|
||||
goodCt: int
|
||||
@ -461,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]
|
||||
@ -491,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
|
||||
@ -525,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
|
||||
@ -538,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] = []
|
||||
@ -553,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
|
||||
@ -569,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
|
||||
@ -592,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] = []
|
||||
@ -614,7 +701,7 @@ class LoginBonusInfo():
|
||||
|
||||
return [ tks, itms, self.message ]
|
||||
|
||||
class VipLoginBonus():
|
||||
class VipLoginBonus:
|
||||
id = 1
|
||||
unknown = 0
|
||||
item: GenericItemRecv
|
||||
@ -627,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
|
||||
@ -658,7 +745,7 @@ class PlayType(Enum):
|
||||
PlayTypeCoop = 3
|
||||
PlayTypeStageup = 4
|
||||
|
||||
class StageInfo():
|
||||
class StageInfo:
|
||||
danId: int = 0
|
||||
danLevel: int = 0
|
||||
clearStatus: int = 0
|
||||
@ -692,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
|
||||
@ -730,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
|
||||
@ -743,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
|
||||
@ -753,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]
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
]
|
||||
]
|
||||
|
||||
@ -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])
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
@ -42,24 +42,26 @@ 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(
|
||||
1,
|
||||
[ # 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_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)
|
||||
resp = UserStatusGetV2Response()
|
||||
ver_split = req.appVersion.split(".")
|
||||
|
||||
profile = self.data.profile.get_profile(aime_id=req.aimeId)
|
||||
if profile is None:
|
||||
@ -69,11 +71,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 +103,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 +163,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 +236,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 +250,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:
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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:
|
||||
|
@ -211,7 +211,7 @@ class WaccaProfileData(BaseData):
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
value = sql.inserted.value
|
||||
value = value
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
|
Loading…
Reference in New Issue
Block a user