1
0
mirror of synced 2024-11-28 07:50:48 +01:00

Merge branch 'cardmaker_ongeki' into fork_develop

This commit is contained in:
Dniel97 2023-03-06 17:08:51 +01:00
commit 44c75d0156
No known key found for this signature in database
GPG Key ID: 6180B3C768FB2E08
25 changed files with 1662 additions and 40 deletions

View File

@ -0,0 +1 @@
ALTER TABLE ongeki_profile_data DROP COLUMN lastEmoneyCredit;

View File

@ -0,0 +1 @@
ALTER TABLE ongeki_profile_data ADD COLUMN lastEmoneyCredit INTEGER DEFAULT 0;

View File

@ -0,0 +1,99 @@
CREATE TABLE ongeki_user_gacha (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
user INT NOT NULL,
gachaId INT NOT NULL,
totalGachaCnt INT DEFAULT 0,
ceilingGachaCnt INT DEFAULT 0,
selectPoint INT DEFAULT 0,
useSelectPoint INT DEFAULT 0,
dailyGachaCnt INT DEFAULT 0,
fiveGachaCnt INT DEFAULT 0,
elevenGachaCnt INT DEFAULT 0,
dailyGachaDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT ongeki_user_gacha_uk UNIQUE (user, gachaId),
CONSTRAINT ongeki_user_gacha_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE ongeki_user_gacha_supply (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
user INT NOT NULL,
cardId INT NOT NULL,
CONSTRAINT ongeki_user_gacha_supply_uk UNIQUE (user, cardId),
CONSTRAINT ongeki_user_gacha_supply_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE ongeki_static_gachas (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
version INT NOT NULL,
gachaId INT NOT NULL,
gachaName VARCHAR(255) NOT NULL,
kind INT NOT NULL,
type INT DEFAULT 0,
isCeiling BOOLEAN DEFAULT 0,
maxSelectPoint INT DEFAULT 0,
ceilingCnt INT DEFAULT 10,
changeRateCnt1 INT DEFAULT 0,
changeRateCnt2 INT DEFAULT 0,
startDate TIMESTAMP DEFAULT '2018-01-01 00:00:00.0',
endDate TIMESTAMP DEFAULT '2038-01-01 00:00:00.0',
noticeStartDate TIMESTAMP DEFAULT '2018-01-01 00:00:00.0',
noticeEndDate TIMESTAMP DEFAULT '2038-01-01 00:00:00.0',
convertEndDate TIMESTAMP DEFAULT '2038-01-01 00:00:00.0',
CONSTRAINT ongeki_static_gachas_uk UNIQUE (version, gachaId, gachaName)
);
CREATE TABLE ongeki_static_gacha_cards (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
gachaId INT NOT NULL,
cardId INT NOT NULL,
rarity INT NOT NULL,
weight INT DEFAULT 1,
isPickup BOOLEAN DEFAULT 0,
isSelect BOOLEAN DEFAULT 1,
CONSTRAINT ongeki_static_gacha_cards_uk UNIQUE (gachaId, cardId)
);
CREATE TABLE ongeki_static_cards (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
version INT NOT NULL,
cardId INT NOT NULL,
name VARCHAR(255) NOT NULL,
charaId INT NOT NULL,
nickName VARCHAR(255),
school VARCHAR(255) NOT NULL,
attribute VARCHAR(5) NOT NULL,
gakunen VARCHAR(255) NOT NULL,
rarity INT NOT NULL,
levelParam VARCHAR(255) NOT NULL,
skillId INT NOT NULL,
choKaikaSkillId INT NOT NULL,
cardNumber VARCHAR(255),
CONSTRAINT ongeki_static_cards_uk UNIQUE (version, cardId)
) CHARACTER SET utf8mb4;
CREATE TABLE ongeki_user_print_detail (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user INT NOT NULL,
cardId INT NOT NULL,
cardType INT DEFAULT 0,
printDate TIMESTAMP NOT NULL,
serialId VARCHAR(20) NOT NULL,
placeId INT NOT NULL,
clientId VARCHAR(11) NOT NULL,
printerSerialId VARCHAR(20) NOT NULL,
isHolograph BOOLEAN DEFAULT 0,
isAutographed BOOLEAN DEFAULT 0,
printOption1 BOOLEAN DEFAULT 1,
printOption2 BOOLEAN DEFAULT 1,
printOption3 BOOLEAN DEFAULT 1,
printOption4 BOOLEAN DEFAULT 1,
printOption5 BOOLEAN DEFAULT 1,
printOption6 BOOLEAN DEFAULT 1,
printOption7 BOOLEAN DEFAULT 1,
printOption8 BOOLEAN DEFAULT 1,
printOption9 BOOLEAN DEFAULT 1,
printOption10 BOOLEAN DEFAULT 0,
FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT ongeki_user_print_detail_uk UNIQUE (serialId)
) CHARACTER SET utf8mb4;

View File

@ -0,0 +1,3 @@
server:
enable: True
loglevel: "info"

View File

@ -1,3 +1,31 @@
server: server:
enable: True enable: True
loglevel: "info" loglevel: "info"
gachas:
enabled_gachas:
- 1011
- 1012
- 1043
- 1067
- 1068
- 1069
- 1070
- 1071
- 1072
- 1073
- 1074
- 1075
- 1076
- 1077
- 1081
- 1085
- 1089
- 1104
- 1111
- 1135
# can be used for Card Maker 1.35 and up, else will be ignored
- 1149
- 1156
- 1163
- 1164

View File

@ -15,6 +15,10 @@ Games listed below have been tested and confirmed working. Only game versions ol
+ Hatsune Miku Arcade + Hatsune Miku Arcade
+ All versions + All versions
+ Card Maker
+ 1.34.xx
+ 1.36.xx
+ Ongeki + Ongeki
+ All versions up to Bright Memory + All versions up to Bright Memory

View File

@ -20,7 +20,10 @@ class ChuniBase():
self.logger = logging.getLogger("chuni") self.logger = logging.getLogger("chuni")
self.game = ChuniConstants.GAME_CODE self.game = ChuniConstants.GAME_CODE
self.version = ChuniConstants.VER_CHUNITHM self.version = ChuniConstants.VER_CHUNITHM
def handle_ping_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
def handle_game_login_api_request(self, data: Dict) -> Dict: def handle_game_login_api_request(self, data: Dict) -> Dict:
#self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]}) #self.data.base.log_event("chuni", "login", logging.INFO, {"version": self.version, "user": data["userId"]})
return { "returnCode": 1 } return { "returnCode": 1 }

10
titles/cm/__init__.py Normal file
View File

@ -0,0 +1,10 @@
from titles.cm.index import CardMakerServlet
from titles.cm.const import CardMakerConstants
from titles.cm.read import CardMakerReader
index = CardMakerServlet
reader = CardMakerReader
game_codes = [CardMakerConstants.GAME_CODE]
current_schema_version = 1

84
titles/cm/base.py Normal file
View File

@ -0,0 +1,84 @@
from datetime import date, datetime, timedelta
from typing import Any, Dict, List
import json
import logging
from enum import Enum
from core.config import CoreConfig
from core.data.cache import cached
from titles.cm.const import CardMakerConstants
from titles.cm.config import CardMakerConfig
class CardMakerBase():
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
self.core_cfg = core_cfg
self.game_cfg = game_cfg
self.date_time_format = "%Y-%m-%d %H:%M:%S"
self.date_time_format_ext = "%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5]
self.date_time_format_short = "%Y-%m-%d"
self.logger = logging.getLogger("cardmaker")
self.game = CardMakerConstants.GAME_CODE
self.version = CardMakerConstants.VER_CARD_MAKER
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}"
# CHUNITHM = 0, maimai = 1, ONGEKI = 2
return {
"length": 3,
"gameConnectList": [
{
"modelKind": 0,
"type": 1,
"titleUri": f"{uri}/SDHD/200/"
},
{
"modelKind": 1,
"type": 1,
"titleUri": f"{uri}/SDEZ/120/"
},
{
"modelKind": 2,
"type": 1,
"titleUri": f"{uri}/SDDT/130/"
}
]
}
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), self.date_time_format)
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), self.date_time_format)
return {
"gameSetting": {
"dataVersion": "1.30.00",
"ongekiCmVersion": "1.30.01",
"chuniCmVersion": "2.00.00",
"maimaiCmVersion": "1.20.00",
"requestInterval": 10,
"rebootStartTime": reboot_start,
"rebootEndTime": reboot_end,
"maxCountCharacter": 100,
"maxCountItem": 100,
"maxCountCard": 100,
"watermark": False,
"isMaintenance": False,
"isBackgroundDistribute": False
},
"isDumpUpload": False,
"isAou": False
}
def handle_get_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {
"placeId": data["placeId"],
"length": 0,
"clientBookkeepingList": []
}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1, "apiName": "UpsertClientBookkeepingApi"}

50
titles/cm/cm136.py Normal file
View File

@ -0,0 +1,50 @@
from datetime import date, datetime, timedelta
from typing import Any, Dict, List
import json
import logging
from enum import Enum
from core.config import CoreConfig
from core.data.cache import cached
from titles.cm.base import CardMakerBase
from titles.cm.const import CardMakerConstants
from titles.cm.config import CardMakerConfig
class CardMaker136(CardMakerBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: CardMakerConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = CardMakerConstants.VER_CARD_MAKER_136
def handle_get_game_connect_api_request(self, data: Dict) -> Dict:
uri = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}"
# CHUNITHM = 0, maimai = 1, ONGEKI = 2
return {
"length": 3,
"gameConnectList": [
{
"modelKind": 0,
"type": 1,
"titleUri": f"{uri}/SDHD/205/"
},
{
"modelKind": 1,
"type": 1,
"titleUri": f"{uri}/SDEZ/125/"
},
{
"modelKind": 2,
"type": 1,
"titleUri": f"{uri}/SDDT/135/"
}
]
}
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"]["ongekiCmVersion"] = "1.35.04"
ret["gameSetting"]["chuniCmVersion"] = "2.05.00"
ret["gameSetting"]["maimaiCmVersion"] = "1.25.00"
return ret

Binary file not shown.
1 gachaId cardId rarity weight isPickup isSelect
2 1070 100984 4 1 0 1
3 1070 100997 3 2 0 1
4 1070 100998 3 2 0 1
5 1070 101020 2 3 0 1
6 1070 101021 2 3 0 1
7 1070 101022 2 3 0 1
8 1067 100982 4 1 0 0
9 1067 100983 4 1 0 0
10 1067 100996 3 2 0 0
11 1068 100075 2 3 0 0
12 1068 100182 2 3 0 0
13 1068 100348 2 3 0 0
14 1068 100232 2 3 0 0
15 1068 100417 2 3 0 0
16 1068 100755 2 3 0 0
17 1068 100077 3 2 0 0
18 1068 100271 3 2 0 0
19 1068 100425 3 2 0 0
20 1068 100758 3 2 0 0
21 1068 101000 3 2 0 0
22 1068 100284 4 1 0 0
23 1068 100767 4 1 0 0
24 1068 101293 4 1 0 0
25 1069 100069 2 3 0 0
26 1069 100183 2 3 0 0
27 1069 100349 2 3 0 0
28 1069 100233 2 3 0 0
29 1069 100416 2 3 0 0
30 1069 100071 3 2 0 0
31 1069 100272 3 2 0 0
32 1069 100427 3 2 0 0
33 1069 100805 3 2 0 0
34 1069 101300 3 2 0 0
35 1069 100285 4 1 0 0
36 1069 100768 4 1 0 0
37 1069 100988 4 1 0 0
38 1071 100275 4 1 0 0
39 1071 100437 4 1 0 0
40 1071 100780 4 1 0 0
41 1071 100006 3 2 0 0
42 1071 100007 3 2 0 0
43 1071 100249 3 2 0 0
44 1071 100262 3 2 0 0
45 1071 100418 3 2 0 0
46 1071 100003 2 3 0 0
47 1071 100004 2 3 0 0
48 1071 100173 2 3 0 0
49 1071 100223 2 3 0 0
50 1071 100339 2 3 0 0
51 1071 100692 2 3 0 0
52 1072 100017 4 1 0 0
53 1072 100276 4 1 0 0
54 1072 100760 4 1 0 0
55 1072 100015 3 2 0 0
56 1072 100016 3 2 0 0
57 1072 100250 3 2 0 0
58 1072 100263 3 2 0 0
59 1072 100423 3 2 0 0
60 1072 100765 3 2 0 0
61 1072 100012 2 3 0 0
62 1072 100013 2 3 0 0
63 1072 100174 2 3 0 0
64 1072 100224 2 3 0 0
65 1072 100340 2 3 0 0
66 1072 100693 2 3 0 0
67 1073 100026 4 1 0 0
68 1073 100277 4 1 0 0
69 1073 100761 4 1 0 0
70 1073 100024 3 2 0 0
71 1073 100025 3 2 0 0
72 1073 100251 3 2 0 0
73 1073 100264 3 2 0 0
74 1073 100430 3 2 0 0
75 1073 100021 2 3 0 0
76 1073 100022 2 3 0 0
77 1073 100175 2 3 0 0
78 1073 100225 2 3 0 0
79 1073 100341 2 3 0 0
80 1073 100694 2 3 0 0
81 1011 100454 4 1 0 0
82 1011 100980 4 1 0 0
83 1011 101553 4 1 0 0
84 1011 100253 3 1 0 0
85 1011 100241 3 1 0 0
86 1011 100240 3 1 0 0
87 1011 100239 3 1 0 0
88 1011 100238 3 1 0 0
89 1011 100237 3 1 0 0
90 1011 100236 3 1 0 0
91 1011 100261 3 1 0 0
92 1011 100246 3 1 0 0
93 1011 100245 3 1 0 0
94 1011 100242 3 1 0 0
95 1011 100243 3 1 0 0
96 1011 100254 3 1 0 0
97 1011 100338 3 1 0 0
98 1011 100337 3 1 0 0
99 1011 100336 3 1 0 0
100 1011 100248 3 1 0 0
101 1011 100247 3 1 0 0
102 1011 100244 3 1 0 0
103 1011 100259 3 1 0 0
104 1011 100257 3 1 0 0
105 1011 100258 3 1 0 0
106 1011 100636 3 1 0 0
107 1011 100634 3 1 0 0
108 1011 100255 3 1 0 0
109 1011 100256 3 1 0 0
110 1011 100252 3 1 0 0
111 1011 100638 3 1 0 0
112 1011 100639 3 1 0 0
113 1011 100637 3 1 0 0
114 1011 100772 3 1 0 0
115 1011 100667 3 1 0 0
116 1011 100666 3 1 0 0
117 1011 100665 3 1 0 0
118 1011 100643 3 1 0 0
119 1011 100640 3 1 0 0
120 1011 100641 3 1 0 0
121 1011 100642 3 1 0 0
122 1011 100688 3 1 0 0
123 1011 100645 3 1 0 0
124 1011 100646 3 1 0 0
125 1011 100644 3 1 0 0
126 1012 100644 3 1 0 0
127 1012 100646 3 1 0 0
128 1012 100645 3 1 0 0
129 1012 100688 3 1 0 0
130 1012 100642 3 1 0 0
131 1012 100641 3 1 0 0
132 1012 100640 3 1 0 0
133 1012 100643 3 1 0 0
134 1012 100665 3 1 0 0
135 1012 100666 3 1 0 0
136 1012 100667 3 1 0 0
137 1012 100634 3 1 0 0
138 1012 100636 3 1 0 0
139 1012 100772 3 1 0 0
140 1012 100638 3 1 0 0
141 1012 100637 3 1 0 0
142 1012 100639 3 1 0 0
143 1012 100252 3 1 0 0
144 1012 100256 3 1 0 0
145 1012 100255 3 1 0 0
146 1012 100258 3 1 0 0
147 1012 100257 3 1 0 0
148 1012 100259 3 1 0 0
149 1012 100244 3 1 0 0
150 1012 100247 3 1 0 0
151 1012 100248 3 1 0 0
152 1012 100336 3 1 0 0
153 1012 100337 3 1 0 0
154 1012 100338 3 1 0 0
155 1012 100254 3 1 0 0
156 1012 100243 3 1 0 0
157 1012 100242 3 1 0 0
158 1012 100245 3 1 0 0
159 1012 100246 3 1 0 0
160 1012 100261 3 1 0 0
161 1012 100236 3 1 0 0
162 1012 100237 3 1 0 0
163 1012 100238 3 1 0 0
164 1012 100239 3 1 0 0
165 1012 100240 3 1 0 0
166 1012 100241 3 1 0 0
167 1012 100253 3 1 0 0
168 1012 100454 4 1 0 0
169 1012 100980 4 1 0 0
170 1012 101553 4 1 0 0
171 1074 100985 4 1 0 0
172 1074 100999 3 1 0 0
173 1074 101000 3 1 0 0
174 1074 101023 2 1 0 0
175 1074 101024 2 1 0 0
176 1075 100060 4 1 0 0
177 1075 100434 4 1 0 0
178 1075 100059 3 1 0 0
179 1075 100268 3 1 0 0
180 1075 100420 3 1 0 0
181 1075 100763 3 1 0 0
182 1075 101003 3 1 0 0
183 1075 100057 2 1 0 0
184 1075 100179 2 1 0 0
185 1075 100229 2 1 0 0
186 1075 100345 2 1 0 0
187 1075 100415 2 1 0 0
188 1076 100054 4 1 0 0
189 1076 100282 4 1 0 0
190 1076 100726 4 1 0 0
191 1076 100053 3 1 0 0
192 1076 100269 3 1 0 0
193 1076 100422 3 1 0 0
194 1076 100757 3 1 0 0
195 1076 100051 2 1 0 0
196 1076 100180 2 1 0 0
197 1076 100230 2 1 0 0
198 1076 100346 2 1 0 0
199 1076 100414 2 1 0 0
200 1077 100984 4 1 0 1
201 1077 100997 3 1 0 1
202 1077 100998 3 1 0 1
203 1077 100986 4 1 0 1
204 1077 101001 3 1 0 1
205 1077 101002 3 1 0 1
206 1077 101025 2 1 0 1
207 1077 101026 2 1 0 1
208 1077 101027 2 1 0 1
209 1081 100987 4 1 0 0
210 1081 100988 4 1 0 0
211 1081 101003 3 1 0 0
212 1085 100008 4 1 0 1
213 1085 100017 4 1 0 1
214 1085 100026 4 1 0 1
215 1085 100034 4 1 0 1
216 1085 100041 4 1 0 1
217 1085 100048 4 1 0 1
218 1085 100054 4 1 0 1
219 1085 100060 4 1 0 1
220 1085 100066 4 1 0 1
221 1085 100078 4 1 0 1
222 1085 100072 4 1 0 1
223 1085 100084 4 1 0 1
224 1085 100090 4 1 0 1
225 1085 100282 4 1 0 1
226 1085 100285 4 1 0 1
227 1085 100284 4 1 0 1
228 1085 100286 4 1 0 1
229 1085 100280 4 1 0 1
230 1085 100276 4 1 0 1
231 1085 100277 4 1 0 1
232 1085 100275 4 1 0 1
233 1085 100278 4 1 0 1
234 1085 100431 4 1 0 1
235 1085 100407 4 1 0 1
236 1085 100432 4 1 0 1
237 1085 100433 4 1 0 1
238 1085 100434 4 1 0 1
239 1085 100435 4 1 0 1
240 1085 100436 4 1 0 1
241 1085 100437 4 1 0 1
242 1085 100438 4 1 0 1
243 1085 100439 4 1 0 1
244 1085 100760 4 1 0 1
245 1085 100761 4 1 0 1
246 1085 100779 4 1 0 1
247 1085 100767 4 1 0 1
248 1085 100780 4 1 0 1
249 1085 100784 4 1 0 1
250 1085 100768 4 1 0 1
251 1085 100725 4 1 0 1
252 1085 100726 4 1 0 1
253 1085 100984 4 1 0 1
254 1085 100985 4 1 0 1
255 1085 100987 4 1 0 1
256 1085 100988 4 1 0 1
257 1085 100986 4 1 0 1
258 1085 100989 4 1 0 1
259 1085 100982 4 1 0 1
260 1085 100983 4 1 0 1
261 1085 100787 4 1 0 1
262 1085 101293 4 1 0 1
263 1085 101294 4 1 0 1
264 1085 101295 4 1 0 1
265 1085 101296 4 1 0 1
266 1085 101297 4 1 0 1
267 1085 101320 4 1 0 1
268 1085 101567 4 1 0 1
269 1085 101592 4 1 0 1
270 1085 101593 4 1 0 1
271 1085 101594 4 1 0 1
272 1085 101595 4 1 0 1
273 1089 100989 4 1 0 0
274 1089 101004 3 1 0 0
275 1089 101005 3 1 0 0
276 1104 101293 4 1 0 0
277 1104 101294 4 1 0 0
278 1104 101298 3 1 0 0
279 1111 100008 4 1 0 1
280 1111 100017 4 1 0 1
281 1111 100026 4 1 0 1
282 1111 100034 4 1 0 1
283 1111 100041 4 1 0 1
284 1111 100048 4 1 0 1
285 1111 100054 4 1 0 1
286 1111 100060 4 1 0 1
287 1111 100066 4 1 0 1
288 1111 100078 4 1 0 1
289 1111 100072 4 1 0 1
290 1111 100084 4 1 0 1
291 1111 100090 4 1 0 1
292 1111 100282 4 1 0 1
293 1111 100285 4 1 0 1
294 1111 100284 4 1 0 1
295 1111 100286 4 1 0 1
296 1111 100280 4 1 0 1
297 1111 100276 4 1 0 1
298 1111 100277 4 1 0 1
299 1111 100275 4 1 0 1
300 1111 100278 4 1 0 1
301 1111 100431 4 1 0 1
302 1111 100407 4 1 0 1
303 1111 100432 4 1 0 1
304 1111 100433 4 1 0 1
305 1111 100434 4 1 1 1
306 1111 100435 4 1 1 1
307 1111 100436 4 1 0 1
308 1111 100437 4 1 0 1
309 1111 100438 4 1 0 1
310 1111 100439 4 1 0 1
311 1111 100760 4 1 1 1
312 1111 100761 4 1 0 1
313 1111 100779 4 1 0 1
314 1111 100767 4 1 0 1
315 1111 100780 4 1 1 1
316 1111 100784 4 1 1 1
317 1111 100768 4 1 0 1
318 1111 100725 4 1 1 1
319 1111 100726 4 1 1 1
320 1111 100985 4 1 1 1
321 1111 100988 4 1 1 1
322 1111 100989 4 1 1 1
323 1111 100982 4 1 1 1
324 1111 100983 4 1 1 1
325 1111 101293 4 1 1 1
326 1111 101294 4 1 1 1
327 1111 101295 4 1 1 1
328 1111 101320 4 1 1 1
329 1135 101567 4 1 0 0
330 1135 101592 4 1 0 0
331 1135 101594 4 1 0 0
332 1135 101595 4 1 0 0
333 1135 101566 3 1 0 0
334 1135 101602 3 1 0 0
335 1135 101603 3 1 0 0
336 1135 101619 2 1 0 0
337 1156 101604 3 1 0 0
338 1156 101605 3 1 0 0
339 1156 101607 3 1 0 0
340 1156 101608 3 1 0 0
341 1156 101596 4 1 0 0
342 1156 101597 4 1 0 0
343 1156 101599 4 1 0 0
344 1156 101600 4 1 0 0
345 1149 100003 2 1 0 0
346 1149 100004 2 1 0 0
347 1149 100012 2 1 0 0
348 1149 100013 2 1 0 0
349 1149 100021 2 1 0 0
350 1149 100022 2 1 0 0
351 1149 100173 2 1 0 0
352 1149 100174 2 1 0 0
353 1149 100175 2 1 0 0
354 1149 100339 2 1 0 0
355 1149 100340 2 1 0 0
356 1149 100341 2 1 0 0
357 1149 100223 2 1 0 0
358 1149 100224 2 1 0 0
359 1149 100225 2 1 0 0
360 1149 100692 2 1 0 0
361 1149 100693 2 1 0 0
362 1149 100694 2 1 0 0
363 1149 101020 2 1 0 0
364 1149 101025 2 1 0 0
365 1149 100418 3 1 0 0
366 1149 101005 3 1 0 0
367 1149 100785 3 1 0 0
368 1149 100786 3 1 0 0
369 1149 101602 3 1 0 0
370 1149 101604 3 1 0 0
371 1149 100760 4 1 0 0
372 1149 100780 4 1 0 0
373 1149 100987 4 1 0 0
374 1149 101295 4 1 0 0
375 1149 101296 4 1 0 0
376 1149 101592 4 1 0 0
377 1163 100008 4 1 0 1
378 1163 100017 4 1 0 1
379 1163 100026 4 1 0 1
380 1163 100034 4 1 0 1
381 1163 100041 4 1 0 1
382 1163 100048 4 1 0 1
383 1163 100054 4 1 0 1
384 1163 100060 4 1 0 1
385 1163 100066 4 1 0 1
386 1163 100078 4 1 0 1
387 1163 100072 4 1 0 1
388 1163 100084 4 1 0 1
389 1163 100090 4 1 0 1
390 1163 100282 4 1 0 1
391 1163 100285 4 1 0 1
392 1163 100284 4 1 0 1
393 1163 100286 4 1 0 1
394 1163 100280 4 1 0 1
395 1163 100276 4 1 0 1
396 1163 100277 4 1 0 1
397 1163 100275 4 1 0 1
398 1163 100278 4 1 0 1
399 1163 100431 4 1 0 1
400 1163 100407 4 1 0 1
401 1163 100432 4 1 0 1
402 1163 100433 4 1 0 1
403 1163 100434 4 1 0 1
404 1163 100435 4 1 0 1
405 1163 100436 4 1 0 1
406 1163 100437 4 1 0 1
407 1163 100438 4 1 0 1
408 1163 100439 4 1 0 1
409 1163 100760 4 1 0 1
410 1163 100761 4 1 0 1
411 1163 100779 4 1 0 1
412 1163 100767 4 1 0 1
413 1163 100780 4 1 0 1
414 1163 100784 4 1 0 1
415 1163 100768 4 1 0 1
416 1163 100725 4 1 0 1
417 1163 100726 4 1 0 1
418 1163 100984 4 1 0 1
419 1163 100985 4 1 0 1
420 1163 100987 4 1 0 1
421 1163 100988 4 1 0 1
422 1163 100986 4 1 0 1
423 1163 100989 4 1 0 1
424 1163 100982 4 1 0 1
425 1163 100983 4 1 0 1
426 1163 100787 4 1 0 1
427 1163 101293 4 1 0 1
428 1163 101294 4 1 0 1
429 1163 101295 4 1 0 1
430 1163 101296 4 1 0 1
431 1163 101297 4 1 0 1
432 1163 101320 4 1 0 1
433 1163 101567 4 1 0 1
434 1164 100008 4 1 0 1
435 1164 100017 4 1 0 1
436 1164 100026 4 1 0 1
437 1164 100034 4 1 0 1
438 1164 100041 4 1 0 1
439 1164 100048 4 1 0 1
440 1164 100054 4 1 0 1
441 1164 100060 4 1 0 1
442 1164 100066 4 1 0 1
443 1164 100078 4 1 0 1
444 1164 100072 4 1 0 1
445 1164 100084 4 1 0 1
446 1164 100090 4 1 0 1
447 1164 100282 4 1 0 1
448 1164 100285 4 1 0 1
449 1164 100284 4 1 0 1
450 1164 100286 4 1 0 1
451 1164 100280 4 1 0 1
452 1164 100276 4 1 0 1
453 1164 100277 4 1 0 1
454 1164 100275 4 1 0 1
455 1164 100278 4 1 0 1
456 1164 100431 4 1 0 1
457 1164 100407 4 1 0 1
458 1164 100432 4 1 0 1
459 1164 100433 4 1 0 1
460 1164 100434 4 1 0 1
461 1164 100435 4 1 0 1
462 1164 100436 4 1 0 1
463 1164 100437 4 1 0 1
464 1164 100438 4 1 0 1
465 1164 100439 4 1 0 1
466 1164 100760 4 1 0 1
467 1164 100761 4 1 0 1
468 1164 100779 4 1 0 1
469 1164 100767 4 1 0 1
470 1164 100780 4 1 0 1
471 1164 100784 4 1 0 1
472 1164 100768 4 1 0 1
473 1164 100725 4 1 0 1
474 1164 100726 4 1 0 1
475 1164 100984 4 1 0 1
476 1164 100985 4 1 0 1
477 1164 100987 4 1 0 1
478 1164 100988 4 1 0 1
479 1164 100986 4 1 0 1
480 1164 100989 4 1 0 1
481 1164 100982 4 1 0 1
482 1164 100983 4 1 0 1
483 1164 100787 4 1 0 1
484 1164 101293 4 1 0 1
485 1164 101294 4 1 0 1
486 1164 101295 4 1 0 1
487 1164 101296 4 1 0 1
488 1164 101297 4 1 0 1
489 1164 101320 4 1 0 1
490 1164 101567 4 1 0 1
491 1164 101592 4 1 0 1
492 1164 101593 4 1 0 1
493 1164 101594 4 1 0 1
494 1164 101595 4 1 0 1
495 1164 101598 4 1 0 1
496 1164 101596 4 1 0 1
497 1164 101597 4 1 0 1
498 1164 101599 4 1 0 1
499 1164 101600 4 1 0 1
500 1141 101600 4 1 0 1
501 1141 101608 3 1 0 1

Binary file not shown.
1 version gachaId gachaName type kind isCeiling maxSelectPoint
2 6 1011 無料ガチャ 0 3 0 0
3 6 1012 無料ガチャ(SR確定) 0 3 0 0
4 6 1043 レギュラーガチャ 0 0 0 0
5 6 1067 例えるなら大人のパッションフルーツ リゾートプールガチャ 0 1 0 0
6 6 1068 柏木 咲姫 ピックアップガチャ 0 2 0 0
7 6 1069 井之原 小星 ピックアップガチャ 0 2 0 0
8 6 1070 目指すは優勝! 炎の体育祭リミテッドガチャ 0 1 1 110
9 6 1071 星咲 あかり ピックアップガチャ 0 2 0 0
10 6 1072 藤沢 柚子 ピックアップガチャ 0 2 0 0
11 6 1073 三角 葵 ピックアップガチャ 0 2 0 0
12 6 1074 おくれてきた Halloweenガチャ 0 1 0 0
13 6 1075 早乙女 彩華 ピックアップガチャ 0 2 0 0
14 6 1076 桜井 春菜 ピックアップガチャ 0 2 0 0
15 6 1077 ふわふわすぺーす お仕事体験リミテッドガチャ 0 1 1 110
16 6 1078 高瀬 梨緒 ピックアップガチャ 0 2 0 0
17 6 1079 結城 莉玖 ピックアップガチャ 0 2 0 0
18 6 1080 藍原 椿 ピックアップガチャ 0 2 0 0
19 6 1081 今夜はおうちでパーティ☆ メリクリガチャ 0 1 0 0
20 6 1082 日向 千夏 ピックアップガチャ 0 2 0 0
21 6 1083 柏木 美亜 ピックアップガチャ 0 2 0 0
22 6 1084 東雲 つむぎ ピックアップガチャ 0 2 0 0
23 6 1085 謹賀新年 福袋ガチャ 0 0 1 33
24 6 1086 逢坂 茜 ピックアップガチャ 0 2 0 0
25 6 1087 珠洲島 有栖 ピックアップガチャ 0 2 0 0
26 6 1088 九條 楓 ピックアップガチャ 0 2 0 0
27 6 1089 冬の魔法 スーパーウルトラウィンターガチャ 0 1 0 0
28 6 1093 高瀬 梨緒ピックアップガチャ 0 2 0 0
29 6 1094 結城 莉玖ピックアップガチャ 0 2 0 0
30 6 1095 藍原 椿ピックアップガチャ 0 2 0 0
31 6 1096 早乙女 彩華ピックアップガチャ 0 2 0 0
32 6 1097 桜井 春菜ピックアップガチャ 0 2 0 0
33 6 1098 逢坂 茜ピックアップガチャ 0 2 0 0
34 6 1099 九條 楓ピックアップガチャ 0 2 0 0
35 6 1100 珠洲島 有栖ピックアップガチャ 0 2 0 0
36 6 1101 LEAF属性オンリーガチャ 0 2 0 0
37 6 1102 AQUA属性オンリーガチャ 0 2 0 0
38 6 1103 FIRE属性オンリーガチャ 0 2 0 0
39 6 1104 夜明け前の双星ガチャ 0 1 0 0
40 6 1105 謎の洞窟 黄金は実在した!!ガチャ 0 1 0 0
41 6 1106 スウィートブライダルリミテッドガチャ 0 1 0 0
42 6 1107 忘れられない、愛(ピュア)とロックがここにある。ガチャ 0 1 0 0
43 6 1108 メルティ夜ふかしガチャ 0 1 0 0
44 6 1109 絵本の国のシューターズガチャ 0 1 0 0
45 6 1110 オンゲキ R.E.D. PLUS 大感謝祭ガチャ 0 1 0 0
46 6 1111 オンゲキ 3rd Anniversaryガチャ 0 1 1 33

19
titles/cm/config.py Normal file
View File

@ -0,0 +1,19 @@
from core.config import CoreConfig
class CardMakerServerConfig():
def __init__(self, parent_config: "CardMakerConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'cardmaker', 'server', 'enable', default=True)
@property
def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'cardmaker', 'server', 'loglevel', default="info"))
class CardMakerConfig(dict):
def __init__(self) -> None:
self.server = CardMakerServerConfig(self)

13
titles/cm/const.py Normal file
View File

@ -0,0 +1,13 @@
class CardMakerConstants():
GAME_CODE = "SDED"
CONFIG_NAME = "cardmaker.yaml"
VER_CARD_MAKER = 0
VER_CARD_MAKER_136 = 1
VERSION_NAMES = ("Card Maker 1.34", "Card Maker 1.36")
@classmethod
def game_ver_to_string(cls, ver: int):
return cls.VERSION_NAMES[ver]

117
titles/cm/index.py Normal file
View File

@ -0,0 +1,117 @@
import json
import inflection
import yaml
import string
import logging
import coloredlogs
import zlib
from os import path
from typing import Tuple
from twisted.web.http import Request
from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig
from titles.cm.config import CardMakerConfig
from titles.cm.const import CardMakerConstants
from titles.cm.base import CardMakerBase
from titles.cm.cm136 import CardMaker136
class CardMakerServlet():
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = CardMakerConfig()
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}")))
self.versions = [
CardMakerBase(core_cfg, self.game_cfg),
CardMaker136(core_cfg, self.game_cfg)
]
self.logger = logging.getLogger("cardmaker")
log_fmt_str = "[%(asctime)s] Card Maker | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "cardmaker"), encoding='utf8',
when="d", backupCount=10)
fileHandler.setFormatter(log_fmt)
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
self.logger.setLevel(self.game_cfg.server.loglevel)
coloredlogs.install(level=self.game_cfg.server.loglevel,
logger=self.logger, fmt=log_fmt_str)
@classmethod
def get_allnet_info(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str, str]:
game_cfg = CardMakerConfig()
if path.exists(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}"):
game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{CardMakerConstants.CONFIG_NAME}")))
if not game_cfg.server.enable:
return (False, "", "")
if core_cfg.server.is_develop:
return (True, f"http://{core_cfg.title.hostname}:{core_cfg.title.port}/{game_code}/$v/", "")
return (True, f"http://{core_cfg.title.hostname}/{game_code}/$v/", "")
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
req_raw = request.content.getvalue()
url_split = url_path.split("/")
internal_ver = 0
endpoint = url_split[len(url_split) - 1]
print(f"version: {version}")
if version >= 130 and version < 135: # Card Maker
internal_ver = CardMakerConstants.VER_CARD_MAKER
elif version >= 135 and version < 140: # Card Maker
internal_ver = CardMakerConstants.VER_CARD_MAKER_136
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
# doing encrypted. The likelyhood of false positives is low but
# technically not 0
self.logger.error("Encryption not supported at this time")
try:
unzip = zlib.decompress(req_raw)
except zlib.error as e:
self.logger.error(
f"Failed to decompress v{version} {endpoint} request -> {e}")
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}")
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
try:
handler = getattr(self.versions[internal_ver], func_to_find)
resp = handler(req_data)
except AttributeError as e:
self.logger.warning(
f"Unhandled v{version} request {endpoint} - {e}")
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
except Exception as e:
self.logger.error(
f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
if resp is None:
resp = {'returnCode': 1}
self.logger.info(f"Response {resp}")
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))

138
titles/cm/read.py Normal file
View File

@ -0,0 +1,138 @@
from decimal import Decimal
import logging
import os
import re
import csv
import xml.etree.ElementTree as ET
from typing import Any, Dict, List, Optional
from read import BaseReader
from core.config import CoreConfig
from titles.ongeki.database import OngekiData
from titles.cm.const import CardMakerConstants
from titles.ongeki.const import OngekiConstants
from titles.ongeki.config import OngekiConfig
class CardMakerReader(BaseReader):
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str],
opt_dir: Optional[str], extra: Optional[str]) -> None:
super().__init__(config, version, bin_dir, opt_dir, extra)
self.ongeki_data = OngekiData(config)
try:
self.logger.info(
f"Start importer for {CardMakerConstants.game_ver_to_string(version)}")
except IndexError:
self.logger.error(f"Invalid Card Maker version {version}")
exit(1)
def read(self) -> None:
static_datas = {
"static_gachas.csv": "read_ongeki_gacha_csv",
"static_gacha_cards.csv": "read_ongeki_gacha_card_csv"
}
data_dirs = []
if self.bin_dir is not None:
for file, func in static_datas.items():
if os.path.exists(f"{self.bin_dir}/MU3/{file}"):
read_csv = getattr(CardMakerReader, func)
read_csv(self, f"{self.bin_dir}/MU3/{file}")
else:
self.logger.warn(f"Couldn't find {file} file in {self.bin_dir}, skipping")
if self.opt_dir is not None:
data_dirs += self.get_data_directories(self.opt_dir)
# ONGEKI (MU3) cnnot easily access the bin data(A000.pac)
# so only opt_dir will work for now
for dir in data_dirs:
self.read_ongeki_gacha(f"{dir}/MU3/gacha")
def read_ongeki_gacha_csv(self, file_path: str) -> None:
self.logger.info(f"Reading gachas from {file_path}...")
with open(file_path, encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
self.ongeki_data.static.put_gacha(
row["version"],
row["gachaId"],
row["gachaName"],
row["kind"],
type=row["type"],
isCeiling=True if row["isCeiling"] == "1" else False,
maxSelectPoint=row["maxSelectPoint"]
)
self.logger.info(f"Added gacha {row['gachaId']}")
def read_ongeki_gacha_card_csv(self, file_path: str) -> None:
self.logger.info(f"Reading gacha cards from {file_path}...")
with open(file_path, encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
self.ongeki_data.static.put_gacha_card(
row["gachaId"],
row["cardId"],
rarity=row["rarity"],
weight=row["weight"],
isPickup=True if row["isPickup"] == "1" else False,
isSelect=True if row["isSelect"] == "1" else False
)
self.logger.info(f"Added card {row['cardId']} to gacha")
def read_ongeki_gacha(self, base_dir: str) -> None:
self.logger.info(f"Reading gachas from {base_dir}...")
# assuming some GachaKinds based on the GachaType
type_to_kind = {
"Normal": "Normal",
"Pickup": "Pickup",
"RecoverFiveShotFlag": "BonusRestored",
"Free": "Free",
"FreeSR": "Free"
}
for root, dirs, files in os.walk(base_dir):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/Gacha.xml"):
with open(f"{root}/{dir}/Gacha.xml", "r", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
name = troot.find('Name').find('str').text
gacha_id = int(troot.find('Name').find('id').text)
# skip already existing gachas
if self.ongeki_data.static.get_gacha(
OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, gacha_id) is not None:
self.logger.info(f"Gacha {gacha_id} already added, skipping")
continue
# 1140 is the first bright memory gacha
if gacha_id < 1140:
version = OngekiConstants.VER_ONGEKI_BRIGHT
else:
version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
gacha_kind = OngekiConstants.CM_GACHA_KINDS[
type_to_kind[troot.find('Type').text]].value
# hardcode which gachas get "Select Gacha" with 33 points
is_ceiling, max_select_point = 0, 0
if gacha_id in {1163, 1164, 1165, 1166, 1167, 1168}:
is_ceiling = 1
max_select_point = 33
self.ongeki_data.static.put_gacha(
version,
gacha_id,
name,
gacha_kind,
isCeiling=is_ceiling,
maxSelectPoint=max_select_point)
self.logger.info(f"Added gacha {gacha_id}")

View File

@ -16,6 +16,9 @@ class Mai2Base():
self.data = Mai2Data(cfg) self.data = Mai2Data(cfg)
self.logger = logging.getLogger("mai2") self.logger = logging.getLogger("mai2")
def handle_ping_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
def handle_get_game_setting_api_request(self, data: Dict): def handle_get_game_setting_api_request(self, data: Dict):
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT) reboot_start = date.strftime(datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT)
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT) reboot_end = date.strftime(datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT)
@ -202,9 +205,9 @@ class Mai2Base():
for fav in upsert["userFavoriteList"]: for fav in upsert["userFavoriteList"]:
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"]) self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
if "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0: # if "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0:
for fsr in upsert["userFriendSeasonRankingList"]: # for fsr in upsert["userFriendSeasonRankingList"]:
pass # pass
def handle_user_logout_api_request(self, data: Dict) -> Dict: def handle_user_logout_api_request(self, data: Dict) -> Dict:
pass pass

View File

@ -1,5 +1,6 @@
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from typing import Any, Dict from typing import Any, Dict
from random import randint
import pytz import pytz
import json import json
@ -8,8 +9,8 @@ from titles.ongeki.base import OngekiBase
from titles.ongeki.const import OngekiConstants from titles.ongeki.const import OngekiConstants
from titles.ongeki.config import OngekiConfig from titles.ongeki.config import OngekiConfig
class OngekiBright(OngekiBase):
class OngekiBright(OngekiBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_BRIGHT self.version = OngekiConstants.VER_ONGEKI_BRIGHT
@ -19,3 +20,611 @@ class OngekiBright(OngekiBase):
ret["gameSetting"]["dataVersion"] = "1.30.00" ret["gameSetting"]["dataVersion"] = "1.30.00"
ret["gameSetting"]["onlineDataVersion"] = "1.30.00" ret["gameSetting"]["onlineDataVersion"] = "1.30.00"
return ret return ret
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# check for a bright profile
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is None:
return {}
cards = self.data.card.get_user_cards(data["userId"])
if cards is None or len(cards) == 0:
# This should never happen
self.logger.error(
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}")
return {}
# get the dict representation of the row so we can modify values
user_data = p._asdict()
# remove the values the game doesn't want
user_data.pop("id")
user_data.pop("user")
user_data.pop("version")
# TODO: replace datetime objects with strings
# add access code that we don't store
user_data["accessCode"] = cards[0]["access_code"]
# hardcode Card Maker version for now
# Card Maker 1.34.00 = 1.30.01
# Card Maker 1.36.00 = 1.35.04
user_data["compatibleCmVersion"] = "1.30.01"
return {"userId": data["userId"], "userData": user_data}
def handle_printer_login_api_request(self, data: Dict):
return {"returnCode": 1}
def handle_printer_logout_api_request(self, data: Dict):
return {"returnCode": 1}
def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None:
return {}
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
if len(user_cards[start_idx:]) > max_ct:
next_idx += max_ct
else:
next_idx = -1
card_list = []
for card in user_cards:
tmp = card._asdict()
tmp.pop("id")
tmp.pop("user")
card_list.append(tmp)
return {
"userId": data["userId"],
"length": len(card_list[start_idx:end_idx]),
"nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx]
}
def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
user_characters = self.data.item.get_characters(data["userId"])
if user_characters is None:
return {}
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
if len(user_characters[start_idx:]) > max_ct:
next_idx += max_ct
else:
next_idx = -1
character_list = []
for character in user_characters:
tmp = character._asdict()
tmp.pop("id")
tmp.pop("user")
character_list.append(tmp)
return {
"userId": data["userId"],
"length": len(character_list[start_idx:end_idx]),
"nextIndex": next_idx,
"userCharacterList": character_list[start_idx:end_idx]
}
def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:
user_gachas = self.data.item.get_user_gachas(data["userId"])
if user_gachas is None:
return {
"userId": data["userId"],
"length": 0,
"userGachaList": []
}
user_gacha_list = []
for gacha in user_gachas:
tmp = gacha._asdict()
tmp.pop("id")
tmp.pop("user")
tmp["dailyGachaDate"] = datetime.strftime(
tmp["dailyGachaDate"], "%Y-%m-%d")
user_gacha_list.append(tmp)
return {
"userId": data["userId"],
"length": len(user_gacha_list),
"userGachaList": user_gacha_list
}
def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
return self.handle_get_user_item_api_request(data)
def handle_cm_get_user_gacha_supply_api_request(self, data: Dict) -> Dict:
# not used for now? not sure what it even does
user_gacha_supplies = self.data.item.get_user_gacha_supplies(
data["userId"])
if user_gacha_supplies is None:
return {
"supplyId": 1,
"length": 0,
"supplyCardList": []
}
supply_list = [gacha["cardId"] for gacha in user_gacha_supplies]
return {
"supplyId": 1,
"length": len(supply_list),
"supplyCardList": supply_list
}
def handle_get_game_gacha_api_request(self, data: Dict) -> Dict:
"""
returns all current active banners (gachas)
"Select Gacha" requires maxSelectPoint set and isCeiling set to 1
"""
game_gachas = []
# for every gacha_id in the OngekiConfig, grab the banner from the db
for gacha_id in self.game_cfg.gachas.enabled_gachas:
game_gacha = self.data.static.get_gacha(self.version, gacha_id)
if game_gacha:
game_gachas.append(game_gacha)
# clean the database rows
game_gacha_list = []
for gacha in game_gachas:
tmp = gacha._asdict()
tmp.pop("id")
tmp.pop("version")
tmp["startDate"] = datetime.strftime(
tmp["startDate"], "%Y-%m-%d %H:%M:%S")
tmp["endDate"] = datetime.strftime(
tmp["endDate"], "%Y-%m-%d %H:%M:%S")
tmp["noticeStartDate"] = datetime.strftime(
tmp["noticeStartDate"], "%Y-%m-%d %H:%M:%S")
tmp["noticeEndDate"] = datetime.strftime(
tmp["noticeEndDate"], "%Y-%m-%d %H:%M:%S")
tmp["convertEndDate"] = datetime.strftime(
tmp["convertEndDate"], "%Y-%m-%d %H:%M:%S")
# make sure to only show gachas for the current version
# so only up to bright, 1140 is the first bright memory gacha
if self.version == OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY:
game_gacha_list.append(tmp)
elif self.version == OngekiConstants.VER_ONGEKI_BRIGHT and tmp["gachaId"] < 1140:
game_gacha_list.append(tmp)
return {
"length": len(game_gacha_list),
"gameGachaList": game_gacha_list,
# no clue
"registIdList": []
}
def handle_roll_gacha_api_request(self, data: Dict) -> Dict:
"""
Handle a gacha roll API request
"""
gacha_id = data["gachaId"]
num_rolls = data["times"]
# change_rate is the 5 gacha rool SR gurantee once a week
change_rate = data["changeRate"]
# SSR book which guarantees a SSR card, itemKind=15, itemId=1
book_used = data["bookUseCount"]
if num_rolls not in {1, 5, 11}:
return {}
# https://gamerch.com/ongeki/entry/462978
# 77% chance of gett ing a R card
# 20% chance of getting a SR card
# 3% chance of getting a SSR card
rarity = [1 for _ in range(77)]
rarity += [2 for _ in range(20)]
rarity += [3 for _ in range(3)]
# gachaId 1011 is "無料ガチャ" (free gacha), which requires GatchaTickets
# itemKind=11, itemId=1 and appearenty sucks
# 94% chance of getting a R card
# 5% chance of getting a SR card
# 1% chance of getting a SSR card
if gacha_id == 1011:
rarity = [1 for _ in range(94)]
rarity += [2 for _ in range(5)]
rarity += [3 for _ in range(1)]
# gachaId 1012 is "無料ガチャSR確定" (SR confirmed! free gacha), which
# requires GatchaTickets itemKind=11, itemId=4 and always guarantees
# a SR card or higher
# 92% chance of getting a SR card
# 8% chance of getting a SSR card
elif gacha_id == 1012:
rarity = [2 for _ in range(92)]
rarity += [3 for _ in range(8)]
assert len(rarity) == 100
# uniform distribution to get the rarity of the card
rolls = [rarity[randint(0, len(rarity)-1)] for _ in range(num_rolls)]
# if SSR book used, make sure you always get one SSR
if book_used == 1:
if rolls.count(3) == 0:
# if there is no SSR, re-roll
return self.handle_roll_gacha_api_request(data)
# make sure that 11 rolls always have at least 1 SR or SSR
elif (num_rolls == 5 and change_rate is True) or num_rolls == 11:
if rolls.count(2) == 0 and rolls.count(3) == 0:
# if there is no SR or SSR, re-roll
return self.handle_roll_gacha_api_request(data)
# get a list of cards for each rarity
cards_r = self.data.static.get_cards_by_rarity(self.version, 1)
cards_sr, cards_ssr = [], []
# free gachas are only allowed to get their specific cards! (R irrelevant)
if gacha_id in {1011, 1012}:
gacha_cards = self.data.static.get_gacha_cards(gacha_id)
for card in gacha_cards:
if card["rarity"] == 3:
cards_sr.append({
"cardId": card["cardId"],
"rarity": 2
})
elif card["rarity"] == 4:
cards_ssr.append({
"cardId": card["cardId"],
"rarity": 3
})
else:
cards_sr = self.data.static.get_cards_by_rarity(self.version, 2)
cards_ssr = self.data.static.get_cards_by_rarity(self.version, 3)
# get the promoted cards for that gacha and add them multiple
# times to increase chances by factor chances
chances = 10
gacha_cards = self.data.static.get_gacha_cards(gacha_id)
for card in gacha_cards:
# make sure to add the cards to the corresponding rarity
if card["rarity"] == 2:
cards_r += [{
"cardId": card["cardId"],
"rarity": 1
}] * chances
if card["rarity"] == 3:
cards_sr += [{
"cardId": card["cardId"],
"rarity": 2
}] * chances
elif card["rarity"] == 4:
cards_ssr += [{
"cardId": card["cardId"],
"rarity": 3
}] * chances
# get the card id for each roll
rolled_cards = []
for i in range(len(rolls)):
if rolls[i] == 1:
rolled_cards.append(cards_r[randint(0, len(cards_r)-1)])
elif rolls[i] == 2:
rolled_cards.append(cards_sr[randint(0, len(cards_sr)-1)])
elif rolls[i] == 3:
rolled_cards.append(cards_ssr[randint(0, len(cards_ssr)-1)])
game_gacha_card_list = []
for card in rolled_cards:
game_gacha_card_list.append({
"gachaId": data["gachaId"],
"cardId": card["cardId"],
# +1 because Card Maker is weird
"rarity": card["rarity"] + 1,
"weight": 1,
"isPickup": False,
"isSelect": False
})
return {
"length": len(game_gacha_card_list),
"gameGachaCardList": game_gacha_card_list
}
def handle_cm_upsert_user_gacha_api_request(self, data: Dict):
upsert = data["cmUpsertUserGacha"]
user_id = data["userId"]
gacha_id = data["gachaId"]
gacha_count = data["gachaCnt"]
play_date = datetime.strptime(data["playDate"][:10], '%Y-%m-%d')
select_point = data["selectPoint"]
total_gacha_count, ceiling_gacha_count = 0, 0
daily_gacha_cnt, five_gacha_cnt, eleven_gacha_cnt = 0, 0, 0
daily_gacha_date = datetime.strptime('2000-01-01', '%Y-%m-%d')
# check if the user previously rolled the exact same gacha
user_gacha = self.data.item.get_user_gacha(user_id, gacha_id)
if user_gacha:
total_gacha_count = user_gacha["totalGachaCnt"]
ceiling_gacha_count = user_gacha["ceilingGachaCnt"]
daily_gacha_cnt = user_gacha["dailyGachaCnt"]
five_gacha_cnt = user_gacha["fiveGachaCnt"]
eleven_gacha_cnt = user_gacha["elevenGachaCnt"]
# parse just the year, month and date
daily_gacha_date = user_gacha["dailyGachaDate"]
# if the saved dailyGachaDate is different from the roll,
# reset dailyGachaCnt and change the date
if daily_gacha_date != play_date:
daily_gacha_date = play_date
daily_gacha_cnt = 0
self.data.item.put_user_gacha(
user_id,
gacha_id,
totalGachaCnt=total_gacha_count + gacha_count,
ceilingGachaCnt=ceiling_gacha_count + gacha_count,
selectPoint=select_point,
useSelectPoint=0,
dailyGachaCnt=daily_gacha_cnt + gacha_count,
fiveGachaCnt=five_gacha_cnt+1 if gacha_count == 5 else five_gacha_cnt,
elevenGachaCnt=eleven_gacha_cnt+1 if gacha_count == 11 else eleven_gacha_cnt,
dailyGachaDate=daily_gacha_date
)
if "userData" in upsert and len(upsert["userData"]) > 0:
# check if the profile is a bright memory profile
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
else:
# save the bright profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]:
self.data.item.put_character(user_id, x)
if "userItemList" in upsert:
for x in upsert["userItemList"]:
self.data.item.put_item(user_id, x)
if "userCardList" in upsert:
for x in upsert["userCardList"]:
self.data.item.put_card(user_id, x)
# TODO?
# if "gameGachaCardList" in upsert:
# for x in upsert["gameGachaCardList"]:
return {'returnCode': 1, 'apiName': 'CMUpsertUserGachaApi'}
def handle_cm_upsert_user_select_gacha_api_request(self, data: Dict) -> Dict:
upsert = data["cmUpsertUserSelectGacha"]
user_id = data["userId"]
if "userData" in upsert and len(upsert["userData"]) > 0:
# check if the profile is a bright memory profile
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
else:
# save the bright profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
if "userCharacterList" in upsert:
for x in upsert["userCharacterList"]:
self.data.item.put_character(user_id, x)
if "userCardList" in upsert:
for x in upsert["userCardList"]:
self.data.item.put_card(user_id, x)
if "selectGachaLogList" in data:
for x in data["selectGachaLogList"]:
self.data.item.put_user_gacha(
user_id,
x["gachaId"],
selectPoint=0,
useSelectPoint=x["useSelectPoint"]
)
return {'returnCode': 1, 'apiName': 'cmUpsertUserSelectGacha'}
def handle_get_game_gacha_card_by_id_api_request(self, data: Dict) -> Dict:
game_gacha_cards = self.data.static.get_gacha_cards(data["gachaId"])
if game_gacha_cards == []:
# fallback to be at least able to select that gacha
return {
"gachaId": data["gachaId"],
"length": 6,
"isPickup": False,
"gameGachaCardList": [
{
"gachaId": data["gachaId"],
"cardId": 100984,
"rarity": 4,
"weight": 1,
"isPickup": False,
"isSelect": True
},
{
"gachaId": data["gachaId"],
"cardId": 100997,
"rarity": 3,
"weight": 2,
"isPickup": False,
"isSelect": True
},
{
"gachaId": data["gachaId"],
"cardId": 100998,
"rarity": 3,
"weight": 2,
"isPickup": False,
"isSelect": True
},
{
"gachaId": data["gachaId"],
"cardId": 101020,
"rarity": 2,
"weight": 3,
"isPickup": False,
"isSelect": True
},
{
"gachaId": data["gachaId"],
"cardId": 101021,
"rarity": 2,
"weight": 3,
"isPickup": False,
"isSelect": True
},
{
"gachaId": data["gachaId"],
"cardId": 101022,
"rarity": 2,
"weight": 3,
"isPickup": False,
"isSelect": True
}
],
"emissionList": [],
"afterCalcList": [],
"ssrBookCalcList": []
}
game_gacha_card_list = []
for gacha_card in game_gacha_cards:
tmp = gacha_card._asdict()
tmp.pop("id")
game_gacha_card_list.append(tmp)
return {
"gachaId": data["gachaId"],
"length": len(game_gacha_card_list),
"isPickup": False,
"gameGachaCardList": game_gacha_card_list,
# again no clue
"emissionList": [],
"afterCalcList": [],
"ssrBookCalcList": []
}
def handle_get_game_theater_api_request(self, data: Dict) -> Dict:
"""
shows a banner after every print, not sure what its used for
"""
"""
return {
"length": 1,
"gameTheaterList": [{
"theaterId": 1,
"theaterName": "theaterName",
"startDate": "2018-01-01 00:00:00.0",
"endDate": "2038-01-01 00:00:00.0",
"gameSubTheaterList": [{
"theaterId": 1,
"id": 2,
"no": 4
}]
}
],
"registIdList": []
}
"""
return {
"length": 0,
"gameTheaterList": [],
"registIdList": []
}
def handle_cm_upsert_user_print_playlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
"serialId": "11111111111111111111",
"apiName": "CMUpsertUserPrintPlaylogApi"
}
def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
"serialId": "11111111111111111111",
"apiName": "CMUpsertUserPrintlogApi"
}
def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
user_print_detail = data["userPrintDetail"]
# generate random serial id
serial_id = ''.join([str(randint(0, 9)) for _ in range(20)])
# not needed because are either zero or unset
user_print_detail.pop("orderId")
user_print_detail.pop("printNumber")
user_print_detail.pop("serialId")
user_print_detail["printDate"] = datetime.strptime(
user_print_detail["printDate"], '%Y-%m-%d'
)
# add the entry to the user print table with the random serialId
self.data.item.put_user_print_detail(
data["userId"],
serial_id,
user_print_detail
)
return {
"returnCode": 1,
"serialId": serial_id,
"apiName": "CMUpsertUserPrintApi"
}
def handle_cm_upsert_user_all_api_request(self, data: Dict) -> Dict:
upsert = data["cmUpsertUserAll"]
user_id = data["userId"]
if "userData" in upsert and len(upsert["userData"]) > 0:
# check if the profile is a bright memory profile
p = self.data.profile.get_profile_data(data["userId"], self.version)
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
else:
# save the bright profile
self.data.profile.put_profile_data(
user_id, self.version, upsert["userData"][0])
if "userActivityList" in upsert:
for act in upsert["userActivityList"]:
self.data.profile.put_profile_activity(
user_id, act["kind"], act["id"], act["sortNumber"],
act["param1"], act["param2"], act["param3"], act["param4"])
if "userItemList" in upsert:
for x in upsert["userItemList"]:
self.data.item.put_item(user_id, x)
if "userCardList" in upsert:
for x in upsert["userCardList"]:
self.data.item.put_card(user_id, x)
return {'returnCode': 1, 'apiName': 'cmUpsertUserAll'}

View File

@ -5,11 +5,12 @@ import json
from core.config import CoreConfig from core.config import CoreConfig
from titles.ongeki.base import OngekiBase from titles.ongeki.base import OngekiBase
from titles.ongeki.bright import OngekiBright
from titles.ongeki.const import OngekiConstants from titles.ongeki.const import OngekiConstants
from titles.ongeki.config import OngekiConfig from titles.ongeki.config import OngekiConfig
class OngekiBrightMemory(OngekiBase):
class OngekiBrightMemory(OngekiBright):
def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None: def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None:
super().__init__(core_cfg, game_cfg) super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY self.version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
@ -28,7 +29,7 @@ class OngekiBrightMemory(OngekiBase):
def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict: def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict:
memories = self.data.item.get_memorychapters(data["userId"]) memories = self.data.item.get_memorychapters(data["userId"])
if not memories: if not memories:
return {"userId": data["userId"], "length":6, "userMemoryChapterList":[ 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": 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": 70002, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0},
@ -37,17 +38,17 @@ class OngekiBrightMemory(OngekiBase):
{"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": 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} {"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 = [] memory_chp = []
for chp in memories: for chp in memories:
tmp = chp._asdict() tmp = chp._asdict()
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
memory_chp.append(tmp) memory_chp.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(memory_chp), "length": len(memory_chp),
"userMemoryChapterList": memory_chp "userMemoryChapterList": memory_chp
} }
@ -55,4 +56,15 @@ class OngekiBrightMemory(OngekiBase):
return { return {
"techScore": 0, "techScore": 0,
"cardNum": 0 "cardNum": 0
} }
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# check for a bright memory profile
user_data = super().handle_cm_get_user_data_api_request(data)
# hardcode Card Maker version for now
# Card Maker 1.34.00 = 1.30.01
# Card Maker 1.36.00 = 1.35.04
user_data["userData"]["compatibleCmVersion"] = "1.35.04"
return user_data

View File

@ -1,17 +1,31 @@
from typing import List
from core.config import CoreConfig from core.config import CoreConfig
class OngekiServerConfig(): class OngekiServerConfig():
def __init__(self, parent_config: "OngekiConfig") -> None: def __init__(self, parent_config: "OngekiConfig") -> None:
self.__config = parent_config self.__config = parent_config
@property @property
def enable(self) -> bool: def enable(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'ongeki', 'server', 'enable', default=True) return CoreConfig.get_config_field(self.__config, 'ongeki', 'server', 'enable', default=True)
@property @property
def loglevel(self) -> int: def loglevel(self) -> int:
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'ongeki', 'server', 'loglevel', default="info")) return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'ongeki', 'server', 'loglevel', default="info"))
class OngekiGachaConfig():
def __init__(self, parent_config: "OngekiConfig") -> None:
self.__config = parent_config
@property
def enabled_gachas(self) -> List[int]:
return CoreConfig.get_config_field(self.__config, 'ongeki', 'gachas', 'enabled_gachas', default=[])
class OngekiConfig(dict): class OngekiConfig(dict):
def __init__(self) -> None: def __init__(self) -> None:
self.server = OngekiServerConfig(self) self.server = OngekiServerConfig(self)
self.gachas = OngekiGachaConfig(self)

View File

@ -1,5 +1,7 @@
from typing import Final, Dict from typing import Final, Dict
from enum import Enum from enum import Enum
class OngekiConstants(): class OngekiConstants():
GAME_CODE = "SDDT" GAME_CODE = "SDDT"
@ -37,6 +39,20 @@ class OngekiConstants():
'SilverJewelEvent', 'SilverJewelEvent',
]) ])
class CM_GACHA_KINDS(Enum):
Normal = 0
Pickup = 1
BonusRestored = 2
Free = 3
PickupBonusRestored = 4
class RARITY_TYPES(Enum):
N = 0
R = 1
SR = 2
SSR = 3
SRPlus = 12
# The game expects the server to give Lunatic an ID of 10, while the game uses 4 internally... except in Music.xml # The game expects the server to give Lunatic an ID of 10, while the game uses 4 internally... except in Music.xml
class DIFF_NAME(Enum): class DIFF_NAME(Enum):
Basic = 0 Basic = 0
@ -50,4 +66,4 @@ class OngekiConstants():
@classmethod @classmethod
def game_ver_to_string(cls, ver: int): def game_ver_to_string(cls, ver: int):
return cls.VERSION_NAMES[ver] return cls.VERSION_NAMES[ver]

View File

@ -11,29 +11,99 @@ from titles.ongeki.database import OngekiData
from titles.ongeki.const import OngekiConstants from titles.ongeki.const import OngekiConstants
from titles.ongeki.config import OngekiConfig from titles.ongeki.config import OngekiConfig
class OngekiReader(BaseReader): class OngekiReader(BaseReader):
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None: def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str],
opt_dir: Optional[str], extra: Optional[str]) -> None:
super().__init__(config, version, bin_dir, opt_dir, extra) super().__init__(config, version, bin_dir, opt_dir, extra)
self.data = OngekiData(config) self.data = OngekiData(config)
try: try:
self.logger.info(f"Start importer for {OngekiConstants.game_ver_to_string(version)}") self.logger.info(
f"Start importer for {OngekiConstants.game_ver_to_string(version)}")
except IndexError: except IndexError:
self.logger.error(f"Invalid ongeki version {version}") self.logger.error(f"Invalid ongeki version {version}")
exit(1) exit(1)
def read(self) -> None: def read(self) -> None:
data_dirs = [] data_dirs = []
if self.bin_dir is not None: if self.bin_dir is not None:
data_dirs += self.get_data_directories(self.bin_dir) data_dirs += self.get_data_directories(self.bin_dir)
if self.opt_dir is not None: if self.opt_dir is not None:
data_dirs += self.get_data_directories(self.opt_dir) data_dirs += self.get_data_directories(self.opt_dir)
for dir in data_dirs: for dir in data_dirs:
self.read_events(f"{dir}/event") self.read_events(f"{dir}/event")
self.read_music(f"{dir}/music") self.read_music(f"{dir}/music")
self.read_card(f"{dir}/card")
def read_card(self, base_dir: str) -> None:
self.logger.info(f"Reading cards from {base_dir}...")
version_ids = {
'1000': OngekiConstants.VER_ONGEKI,
'1005': OngekiConstants.VER_ONGEKI_PLUS,
'1010': OngekiConstants.VER_ONGEKI_SUMMER,
'1015': OngekiConstants.VER_ONGEKI_SUMMER_PLUS,
'1020': OngekiConstants.VER_ONGEKI_RED,
'1025': OngekiConstants.VER_ONGEKI_RED_PLUS,
'1030': OngekiConstants.VER_ONGEKI_BRIGHT,
'1035': OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
}
for root, dirs, files in os.walk(base_dir):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/Card.xml"):
with open(f"{root}/{dir}/Card.xml", "r", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
card_id = int(troot.find('Name').find('id').text)
# skip already existing cards
if self.data.static.get_card(
OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY, card_id) is not None:
self.logger.info(f"Card {card_id} already added, skipping")
continue
name = troot.find('Name').find('str').text
chara_id = int(troot.find('CharaID').find('id').text)
nick_name = troot.find('NickName').text
school = troot.find('School').find('str').text
attribute = troot.find('Attribute').text
gakunen = troot.find('Gakunen').find('str').text
rarity = OngekiConstants.RARITY_TYPES[
troot.find('Rarity').text].value
level_param = []
for lvl in troot.find('LevelParam').findall('int'):
level_param.append(lvl.text)
skill_id = int(troot.find('SkillID').find('id').text)
cho_kai_ka_skill_id = int(troot.find('ChoKaikaSkillID').find('id').text)
version = version_ids[
troot.find('VersionID').find('id').text]
card_number = troot.find('CardNumberString').text
self.data.static.put_card(
version,
card_id,
name=name,
charaId=chara_id,
nickName=nick_name,
school=school,
attribute=attribute,
gakunen=gakunen,
rarity=rarity,
levelParam=','.join(level_param),
skillId=skill_id,
choKaikaSkillId=cho_kai_ka_skill_id,
cardNumber=card_number
)
self.logger.info(f"Added card {card_id}")
def read_events(self, base_dir: str) -> None: def read_events(self, base_dir: str) -> None:
self.logger.info(f"Reading events from {base_dir}...") self.logger.info(f"Reading events from {base_dir}...")
@ -45,12 +115,13 @@ class OngekiReader(BaseReader):
name = troot.find('Name').find('str').text name = troot.find('Name').find('str').text
id = int(troot.find('Name').find('id').text) id = int(troot.find('Name').find('id').text)
event_type = OngekiConstants.EVT_TYPES[troot.find('EventType').text].value event_type = OngekiConstants.EVT_TYPES[troot.find(
'EventType').text].value
self.data.static.put_event(self.version, id, event_type, name) self.data.static.put_event(
self.version, id, event_type, name)
self.logger.info(f"Added event {id}") self.logger.info(f"Added event {id}")
def read_music(self, base_dir: str) -> None: def read_music(self, base_dir: str) -> None:
self.logger.info(f"Reading music from {base_dir}...") self.logger.info(f"Reading music from {base_dir}...")
@ -72,9 +143,9 @@ class OngekiReader(BaseReader):
title = name.find('str').text title = name.find('str').text
artist = troot.find('ArtistName').find('str').text artist = troot.find('ArtistName').find('str').text
genre = troot.find('Genre').find('str').text genre = troot.find('Genre').find('str').text
fumens = troot.find("FumenData") fumens = troot.find("FumenData")
for fumens_data in fumens.findall('FumenData'): for fumens_data in fumens.findall('FumenData'):
path = fumens_data.find('FumenFile').find('path').text path = fumens_data.find('FumenFile').find('path').text
if path is None or not os.path.exists(f"{root}/{dir}/{path}"): if path is None or not os.path.exists(f"{root}/{dir}/{path}"):
continue continue
@ -82,8 +153,9 @@ class OngekiReader(BaseReader):
chart_id = int(path.split(".")[0].split("_")[1]) chart_id = int(path.split(".")[0].split("_")[1])
level = float( level = float(
f"{fumens_data.find('FumenConstIntegerPart').text}.{fumens_data.find('FumenConstFractionalPart').text}" f"{fumens_data.find('FumenConstIntegerPart').text}.{fumens_data.find('FumenConstFractionalPart').text}"
) )
self.data.static.put_chart(self.version, song_id, chart_id, title, artist, genre, level)
self.logger.info(f"Added song {song_id} chart {chart_id}")
self.data.static.put_chart(
self.version, song_id, chart_id, title, artist, genre, level)
self.logger.info(
f"Added song {song_id} chart {chart_id}")

View File

@ -2,6 +2,7 @@ from typing import Dict, Optional, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_ from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
from sqlalchemy.schema import ForeignKey from sqlalchemy.schema import ForeignKey
from sqlalchemy.engine import Row
from sqlalchemy.sql import func, select from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert from sqlalchemy.dialects.mysql import insert
@ -242,6 +243,63 @@ tech_event = Table(
mysql_charset='utf8mb4' mysql_charset='utf8mb4'
) )
gacha = Table(
"ongeki_user_gacha",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column("gachaId", Integer, nullable=False),
Column("totalGachaCnt", Integer, server_default="0"),
Column("ceilingGachaCnt", Integer, server_default="0"),
Column("selectPoint", Integer, server_default="0"),
Column("useSelectPoint", Integer, server_default="0"),
Column("dailyGachaCnt", Integer, server_default="0"),
Column("fiveGachaCnt", Integer, server_default="0"),
Column("elevenGachaCnt", Integer, server_default="0"),
Column("dailyGachaDate", TIMESTAMP, nullable=False, server_default=func.now()),
UniqueConstraint("user", "gachaId", name="ongeki_user_gacha_uk"),
mysql_charset='utf8mb4'
)
gacha_supply = Table(
"ongeki_user_gacha_supply",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column("cardId", Integer, nullable=False),
UniqueConstraint("user", "cardId", name="ongeki_user_gacha_supply_uk"),
mysql_charset='utf8mb4'
)
print_detail = Table(
"ongeki_user_print_detail",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
Column("cardId", Integer, nullable=False),
Column("cardType", Integer, server_default="0"),
Column("printDate", TIMESTAMP, nullable=False),
Column("serialId", String(20), nullable=False),
Column("placeId", Integer, nullable=False),
Column("clientId", String(11), nullable=False),
Column("printerSerialId", String(20), nullable=False),
Column("isHolograph", Boolean, server_default="0"),
Column("isAutographed", Boolean, server_default="0"),
Column("printOption1", Boolean, server_default="1"),
Column("printOption2", Boolean, server_default="1"),
Column("printOption3", Boolean, server_default="1"),
Column("printOption4", Boolean, server_default="1"),
Column("printOption5", Boolean, server_default="1"),
Column("printOption6", Boolean, server_default="1"),
Column("printOption7", Boolean, server_default="1"),
Column("printOption8", Boolean, server_default="1"),
Column("printOption9", Boolean, server_default="1"),
Column("printOption10", Boolean, server_default="0"),
UniqueConstraint("serialId", name="ongeki_user_print_detail_uk"),
mysql_charset='utf8mb4'
)
class OngekiItemData(BaseData): class OngekiItemData(BaseData):
def put_card(self, aime_id: int, card_data: Dict) -> Optional[int]: def put_card(self, aime_id: int, card_data: Dict) -> Optional[int]:
@ -545,7 +603,7 @@ class OngekiItemData(BaseData):
if result is None: return None if result is None: return None
return result.fetchall() return result.fetchall()
def put_memorychapter(self, aime_id: int, memorychapter_data: Dict) -> Optional[int]: def put_memorychapter(self, aime_id: int, memorychapter_data: Dict) -> Optional[int]:
memorychapter_data["user"] = aime_id memorychapter_data["user"] = aime_id
@ -557,10 +615,73 @@ class OngekiItemData(BaseData):
self.logger.warn(f"put_memorychapter: Failed to update! aime_id: {aime_id}") self.logger.warn(f"put_memorychapter: Failed to update! aime_id: {aime_id}")
return None return None
return result.lastrowid return result.lastrowid
def get_memorychapters(self, aime_id: int) -> Optional[List[Dict]]: def get_memorychapters(self, aime_id: int) -> Optional[List[Dict]]:
sql = select(memorychapter).where(memorychapter.c.user == aime_id) sql = select(memorychapter).where(memorychapter.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None:
return result.fetchall() return None
return result.fetchall()
def get_user_gacha(self, aime_id: int, gacha_id: int) -> Optional[Row]:
sql = gacha.select(and_(
gacha.c.user == aime_id,
gacha.c.gachaId == gacha_id
))
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_user_gachas(self, aime_id: int) -> Optional[List[Row]]:
sql = gacha.select(gacha.c.user == aime_id)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def get_user_gacha_supplies(self, aime_id: int) -> Optional[List[Row]]:
sql = gacha_supply.select(gacha_supply.c.user == aime_id)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def put_user_gacha(self, aime_id: int, gacha_id: int, **data) -> Optional[int]:
sql = insert(gacha).values(
user=aime_id,
gachaId=gacha_id,
**data)
conflict = sql.on_duplicate_key_update(
user=aime_id,
gachaId=gacha_id,
**data)
result = self.execute(conflict)
if result is None:
self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
return None
return result.lastrowid
def put_user_print_detail(self, aime_id: int, serial_id: str,
user_print_data: Dict) -> Optional[int]:
sql = insert(print_detail).values(
user=aime_id,
serialId=serial_id,
**user_print_data)
conflict = sql.on_duplicate_key_update(
user=aime_id,
serialId=serial_id,
**user_print_data)
result = self.execute(conflict)
if result is None:
self.logger.warn(f"put_user_print_detail: Failed to insert! aime_id: {aime_id}")
return None
return result.lastrowid

View File

@ -78,7 +78,8 @@ profile = Table(
Column("overDamageBattlePoint", Integer, server_default="0"), Column("overDamageBattlePoint", Integer, server_default="0"),
Column("bestBattlePoint", Integer, server_default="0"), Column("bestBattlePoint", Integer, server_default="0"),
Column("lastEmoneyBrand", Integer, server_default="0"), Column("lastEmoneyBrand", Integer, server_default="0"),
Column("isDialogWatchedSuggestMemory", Boolean), Column("lastEmoneyCredit", Integer, server_default="0"),
Column("isDialogWatchedSuggestMemory", Boolean, server_default="0"),
UniqueConstraint("user", "version", name="ongeki_profile_profile_uk"), UniqueConstraint("user", "version", name="ongeki_profile_profile_uk"),
mysql_charset='utf8mb4' mysql_charset='utf8mb4'
) )
@ -179,7 +180,7 @@ region = Table(
mysql_charset='utf8mb4' mysql_charset='utf8mb4'
) )
training_room = Table ( training_room = Table(
"ongeki_profile_training_room", "ongeki_profile_training_room",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
@ -192,7 +193,7 @@ training_room = Table (
mysql_charset='utf8mb4' mysql_charset='utf8mb4'
) )
kop = Table ( kop = Table(
"ongeki_profile_kop", "ongeki_profile_kop",
metadata, metadata,
Column("id", Integer, primary_key=True, nullable=False), Column("id", Integer, primary_key=True, nullable=False),
@ -218,6 +219,7 @@ rival = Table(
mysql_charset='utf8mb4' mysql_charset='utf8mb4'
) )
class OngekiProfileData(BaseData): class OngekiProfileData(BaseData):
def __init__(self, cfg: CoreConfig, conn: Connection) -> None: def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
super().__init__(cfg, conn) super().__init__(cfg, conn)
@ -286,14 +288,14 @@ class OngekiProfileData(BaseData):
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None: return None
return result.fetchall() return result.fetchall()
def get_kop(self, aime_id: int) -> Optional[List[Row]]: def get_kop(self, aime_id: int) -> Optional[List[Row]]:
sql = select(kop).where(kop.c.user == aime_id) sql = select(kop).where(kop.c.user == aime_id)
result = self.execute(sql) result = self.execute(sql)
if result is None: return None if result is None: return None
return result.fetchall() return result.fetchall()
def get_rivals(self, aime_id: int) -> Optional[List[Row]]: def get_rivals(self, aime_id: int) -> Optional[List[Row]]:
sql = select(rival.c.rivalUserId).where(rival.c.user == aime_id) sql = select(rival.c.rivalUserId).where(rival.c.user == aime_id)

View File

@ -37,7 +37,210 @@ music = Table(
mysql_charset='utf8mb4' mysql_charset='utf8mb4'
) )
gachas = Table(
"ongeki_static_gachas",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("version", Integer, nullable=False),
Column("gachaId", Integer, nullable=False),
Column("gachaName", String(255), nullable=False),
Column("type", Integer, nullable=False, server_default="0"),
Column("kind", Integer, nullable=False, server_default="0"),
Column("isCeiling", Boolean, server_default="0"),
Column("maxSelectPoint", Integer, server_default="0"),
Column("ceilingCnt", Integer, server_default="10"),
Column("changeRateCnt1", Integer, server_default="0"),
Column("changeRateCnt2", Integer, server_default="0"),
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
Column("convertEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
UniqueConstraint("version", "gachaId", "gachaName", name="ongeki_static_gachas_uk"),
mysql_charset='utf8mb4'
)
gacha_cards = Table(
"ongeki_static_gacha_cards",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("gachaId", Integer, nullable=False),
Column("cardId", Integer, nullable=False),
Column("rarity", Integer, nullable=False),
Column("weight", Integer, server_default="1"),
Column("isPickup", Boolean, server_default="0"),
Column("isSelect", Boolean, server_default="0"),
UniqueConstraint("gachaId", "cardId", name="ongeki_static_gacha_cards_uk"),
mysql_charset='utf8mb4'
)
cards = Table(
"ongeki_static_cards",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column("version", Integer, nullable=False),
Column("cardId", Integer, nullable=False),
Column("name", String(255), nullable=False),
Column("charaId", Integer, nullable=False),
Column("nickName", String(255)),
Column("school", String(255), nullable=False),
Column("attribute", String(5), nullable=False),
Column("gakunen", String(255), nullable=False),
Column("rarity", Integer, nullable=False),
Column("levelParam", String(255), nullable=False),
Column("skillId", Integer, nullable=False),
Column("choKaikaSkillId", Integer, nullable=False),
Column("cardNumber", String(255)),
UniqueConstraint("version", "cardId", name="ongeki_static_cards_uk"),
mysql_charset='utf8mb4'
)
class OngekiStaticData(BaseData): class OngekiStaticData(BaseData):
def put_card(self, version: int, card_id: int, **card_data) -> Optional[int]:
sql = insert(cards).values(
version=version,
cardId=card_id,
**card_data)
conflict = sql.on_duplicate_key_update(**card_data)
result = self.execute(conflict)
if result is None:
self.logger.warn(f"Failed to insert card! card_id {card_id}")
return None
return result.lastrowid
def get_card(self, version: int, card_id: int) -> Optional[Dict]:
sql = cards.select(and_(
cards.c.version <= version,
cards.c.cardId == card_id
))
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_card_by_card_number(self, version: int, card_number: str) -> Optional[Dict]:
if not card_number.startswith("[O.N.G.E.K.I.]"):
card_number = f"[O.N.G.E.K.I.]{card_number}"
sql = cards.select(and_(
cards.c.version <= version,
cards.c.cardNumber == card_number
))
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_card_by_name(self, version: int, name: str) -> Optional[Dict]:
sql = cards.select(and_(
cards.c.version <= version,
cards.c.name == name
))
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_cards(self, version: int) -> Optional[List[Dict]]:
sql = cards.select(cards.c.version <= version)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def get_cards_by_rarity(self, version: int, rarity: int) -> Optional[List[Dict]]:
sql = cards.select(and_(
cards.c.version <= version,
cards.c.rarity == rarity
))
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def put_gacha(self, version: int, gacha_id: int, gacha_name: int,
gacha_kind: int, **gacha_data) -> Optional[int]:
sql = insert(gachas).values(
version=version,
gachaId=gacha_id,
gachaName=gacha_name,
kind=gacha_kind,
**gacha_data
)
conflict = sql.on_duplicate_key_update(
version=version,
gachaId=gacha_id,
gachaName=gacha_name,
kind=gacha_kind,
**gacha_data
)
result = self.execute(conflict)
if result is None:
self.logger.warn(f"Failed to insert gacha! gacha_id {gacha_id}")
return None
return result.lastrowid
def get_gacha(self, version: int, gacha_id: int) -> Optional[Dict]:
sql = gachas.select(and_(
gachas.c.version <= version,
gachas.c.gachaId == gacha_id
))
result = self.execute(sql)
if result is None:
return None
return result.fetchone()
def get_gachas(self, version: int) -> Optional[List[Dict]]:
sql = gachas.select(
gachas.c.version == version).order_by(
gachas.c.gachaId.asc()
)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def put_gacha_card(self, gacha_id: int, card_id: int, **gacha_card) -> Optional[int]:
sql = insert(gacha_cards).values(
gachaId=gacha_id,
cardId=card_id,
**gacha_card
)
conflict = sql.on_duplicate_key_update(
gachaId=gacha_id,
cardId=card_id,
**gacha_card
)
result = self.execute(conflict)
if result is None:
self.logger.warn(f"Failed to insert gacha card! gacha_id {gacha_id}")
return None
return result.lastrowid
def get_gacha_cards(self, gacha_id: int) -> Optional[List[Dict]]:
sql = gacha_cards.select(
gacha_cards.c.gachaId == gacha_id
)
result = self.execute(sql)
if result is None:
return None
return result.fetchall()
def put_event(self, version: int, event_id: int, event_type: int, event_name: str) -> Optional[int]: def put_event(self, version: int, event_id: int, event_type: int, event_name: str) -> Optional[int]:
sql = insert(events).values( sql = insert(events).values(
version = version, version = version,