3
2
forked from Dniel97/artemis

Initial Card Maker ONGEKI support

This commit is contained in:
Dniel97 2023-03-03 23:46:29 +01:00
parent 8fe0acae93
commit 3acc2dc197
Signed by untrusted user: Dniel97
GPG Key ID: 6180B3C768FB2E08
22 changed files with 3582 additions and 13 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 '2018-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,26 @@
server:
enable: True
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

View File

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

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

@ -0,0 +1,15 @@
from titles.cm.index import CardMakerServlet
from titles.cm.const import CardMakerConstants
from titles.cm.read import CardMakerReader
index = CardMakerServlet
reader = CardMakerReader
use_default_title = True
include_protocol = True
title_secure = False
game_codes = [CardMakerConstants.GAME_CODE]
trailing_slash = True
use_default_host = True
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"}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,336 @@
"gachaId","cardId","rarity","weight","isPickup","isSelect"
1070,100984,4,1,0,1
1070,100997,3,2,0,1
1070,100998,3,2,0,1
1070,101020,2,3,0,1
1070,101021,2,3,0,1
1070,101022,2,3,0,1
1067,100982,4,1,0,0
1067,100983,4,1,0,0
1067,100996,3,2,0,0
1068,100075,2,3,0,0
1068,100182,2,3,0,0
1068,100348,2,3,0,0
1068,100232,2,3,0,0
1068,100417,2,3,0,0
1068,100755,2,3,0,0
1068,100077,3,2,0,0
1068,100271,3,2,0,0
1068,100425,3,2,0,0
1068,100758,3,2,0,0
1068,101000,3,2,0,0
1068,100284,4,1,0,0
1068,100767,4,1,0,0
1068,101293,4,1,0,0
1069,100069,2,3,0,0
1069,100183,2,3,0,0
1069,100349,2,3,0,0
1069,100233,2,3,0,0
1069,100416,2,3,0,0
1069,100071,3,2,0,0
1069,100272,3,2,0,0
1069,100427,3,2,0,0
1069,100805,3,2,0,0
1069,101300,3,2,0,0
1069,100285,4,1,0,0
1069,100768,4,1,0,0
1069,100988,4,1,0,0
1071,100275,4,1,0,0
1071,100437,4,1,0,0
1071,100780,4,1,0,0
1071,100006,3,2,0,0
1071,100007,3,2,0,0
1071,100249,3,2,0,0
1071,100262,3,2,0,0
1071,100418,3,2,0,0
1071,100003,2,3,0,0
1071,100004,2,3,0,0
1071,100173,2,3,0,0
1071,100223,2,3,0,0
1071,100339,2,3,0,0
1071,100692,2,3,0,0
1072,100017,4,1,0,0
1072,100276,4,1,0,0
1072,100760,4,1,0,0
1072,100015,3,2,0,0
1072,100016,3,2,0,0
1072,100250,3,2,0,0
1072,100263,3,2,0,0
1072,100423,3,2,0,0
1072,100765,3,2,0,0
1072,100012,2,3,0,0
1072,100013,2,3,0,0
1072,100174,2,3,0,0
1072,100224,2,3,0,0
1072,100340,2,3,0,0
1072,100693,2,3,0,0
1073,100026,4,1,0,0
1073,100277,4,1,0,0
1073,100761,4,1,0,0
1073,100024,3,2,0,0
1073,100025,3,2,0,0
1073,100251,3,2,0,0
1073,100264,3,2,0,0
1073,100430,3,2,0,0
1073,100021,2,3,0,0
1073,100022,2,3,0,0
1073,100175,2,3,0,0
1073,100225,2,3,0,0
1073,100341,2,3,0,0
1073,100694,2,3,0,0
1011,100454,4,1,0,0
1011,100980,4,1,0,0
1011,101553,4,1,0,0
1011,100253,3,1,0,0
1011,100241,3,1,0,0
1011,100240,3,1,0,0
1011,100239,3,1,0,0
1011,100238,3,1,0,0
1011,100237,3,1,0,0
1011,100236,3,1,0,0
1011,100261,3,1,0,0
1011,100246,3,1,0,0
1011,100245,3,1,0,0
1011,100242,3,1,0,0
1011,100243,3,1,0,0
1011,100254,3,1,0,0
1011,100338,3,1,0,0
1011,100337,3,1,0,0
1011,100336,3,1,0,0
1011,100248,3,1,0,0
1011,100247,3,1,0,0
1011,100244,3,1,0,0
1011,100259,3,1,0,0
1011,100257,3,1,0,0
1011,100258,3,1,0,0
1011,100636,3,1,0,0
1011,100634,3,1,0,0
1011,100255,3,1,0,0
1011,100256,3,1,0,0
1011,100252,3,1,0,0
1011,100638,3,1,0,0
1011,100639,3,1,0,0
1011,100637,3,1,0,0
1011,100772,3,1,0,0
1011,100667,3,1,0,0
1011,100666,3,1,0,0
1011,100665,3,1,0,0
1011,100643,3,1,0,0
1011,100640,3,1,0,0
1011,100641,3,1,0,0
1011,100642,3,1,0,0
1011,100688,3,1,0,0
1011,100645,3,1,0,0
1011,100646,3,1,0,0
1011,100644,3,1,0,0
1012,100644,3,1,0,0
1012,100646,3,1,0,0
1012,100645,3,1,0,0
1012,100688,3,1,0,0
1012,100642,3,1,0,0
1012,100641,3,1,0,0
1012,100640,3,1,0,0
1012,100643,3,1,0,0
1012,100665,3,1,0,0
1012,100666,3,1,0,0
1012,100667,3,1,0,0
1012,100634,3,1,0,0
1012,100636,3,1,0,0
1012,100772,3,1,0,0
1012,100638,3,1,0,0
1012,100637,3,1,0,0
1012,100639,3,1,0,0
1012,100252,3,1,0,0
1012,100256,3,1,0,0
1012,100255,3,1,0,0
1012,100258,3,1,0,0
1012,100257,3,1,0,0
1012,100259,3,1,0,0
1012,100244,3,1,0,0
1012,100247,3,1,0,0
1012,100248,3,1,0,0
1012,100336,3,1,0,0
1012,100337,3,1,0,0
1012,100338,3,1,0,0
1012,100254,3,1,0,0
1012,100243,3,1,0,0
1012,100242,3,1,0,0
1012,100245,3,1,0,0
1012,100246,3,1,0,0
1012,100261,3,1,0,0
1012,100236,3,1,0,0
1012,100237,3,1,0,0
1012,100238,3,1,0,0
1012,100239,3,1,0,0
1012,100240,3,1,0,0
1012,100241,3,1,0,0
1012,100253,3,1,0,0
1012,100454,4,1,0,0
1012,100980,4,1,0,0
1012,101553,4,1,0,0
1074,100985,4,1,0,0
1074,100999,3,1,0,0
1074,101000,3,1,0,0
1074,101023,2,1,0,0
1074,101024,2,1,0,0
1075,100060,4,1,0,0
1075,100434,4,1,0,0
1075,100059,3,1,0,0
1075,100268,3,1,0,0
1075,100420,3,1,0,0
1075,100763,3,1,0,0
1075,101003,3,1,0,0
1075,100057,2,1,0,0
1075,100179,2,1,0,0
1075,100229,2,1,0,0
1075,100345,2,1,0,0
1075,100415,2,1,0,0
1076,100054,4,1,0,0
1076,100282,4,1,0,0
1076,100726,4,1,0,0
1076,100053,3,1,0,0
1076,100269,3,1,0,0
1076,100422,3,1,0,0
1076,100757,3,1,0,0
1076,100051,2,1,0,0
1076,100180,2,1,0,0
1076,100230,2,1,0,0
1076,100346,2,1,0,0
1076,100414,2,1,0,0
1077,100984,4,1,0,1
1077,100997,3,1,0,1
1077,100998,3,1,0,1
1077,100986,4,1,0,1
1077,101001,3,1,0,1
1077,101002,3,1,0,1
1077,101025,2,1,0,1
1077,101026,2,1,0,1
1077,101027,2,1,0,1
1081,100987,4,1,0,0
1081,100988,4,1,0,0
1081,101003,3,1,0,0
1085,100008,4,1,0,1
1085,100017,4,1,0,1
1085,100026,4,1,0,1
1085,100034,4,1,0,1
1085,100041,4,1,0,1
1085,100048,4,1,0,1
1085,100054,4,1,0,1
1085,100060,4,1,0,1
1085,100066,4,1,0,1
1085,100078,4,1,0,1
1085,100072,4,1,0,1
1085,100084,4,1,0,1
1085,100090,4,1,0,1
1085,100282,4,1,0,1
1085,100285,4,1,0,1
1085,100284,4,1,0,1
1085,100286,4,1,0,1
1085,100280,4,1,0,1
1085,100276,4,1,0,1
1085,100277,4,1,0,1
1085,100275,4,1,0,1
1085,100278,4,1,0,1
1085,100431,4,1,0,1
1085,100407,4,1,0,1
1085,100432,4,1,0,1
1085,100433,4,1,0,1
1085,100434,4,1,0,1
1085,100435,4,1,0,1
1085,100436,4,1,0,1
1085,100437,4,1,0,1
1085,100438,4,1,0,1
1085,100439,4,1,0,1
1085,100760,4,1,0,1
1085,100761,4,1,0,1
1085,100779,4,1,0,1
1085,100767,4,1,0,1
1085,100780,4,1,0,1
1085,100784,4,1,0,1
1085,100768,4,1,0,1
1085,100725,4,1,0,1
1085,100726,4,1,0,1
1085,100984,4,1,0,1
1085,100985,4,1,0,1
1085,100987,4,1,0,1
1085,100988,4,1,0,1
1085,100986,4,1,0,1
1085,100989,4,1,0,1
1085,100982,4,1,0,1
1085,100983,4,1,0,1
1085,100787,4,1,0,1
1085,101293,4,1,0,1
1085,101294,4,1,0,1
1085,101295,4,1,0,1
1085,101296,4,1,0,1
1085,101297,4,1,0,1
1085,101320,4,1,0,1
1085,101567,4,1,0,1
1085,101592,4,1,0,1
1085,101593,4,1,0,1
1085,101594,4,1,0,1
1085,101595,4,1,0,1
1089,100989,4,1,0,0
1089,101004,3,1,0,0
1089,101005,3,1,0,0
1104,101293,4,1,0,0
1104,101294,4,1,0,0
1104,101298,3,1,0,0
1111,100008,4,1,0,1
1111,100017,4,1,0,1
1111,100026,4,1,0,1
1111,100034,4,1,0,1
1111,100041,4,1,0,1
1111,100048,4,1,0,1
1111,100054,4,1,0,1
1111,100060,4,1,0,1
1111,100066,4,1,0,1
1111,100078,4,1,0,1
1111,100072,4,1,0,1
1111,100084,4,1,0,1
1111,100090,4,1,0,1
1111,100282,4,1,0,1
1111,100285,4,1,0,1
1111,100284,4,1,0,1
1111,100286,4,1,0,1
1111,100280,4,1,0,1
1111,100276,4,1,0,1
1111,100277,4,1,0,1
1111,100275,4,1,0,1
1111,100278,4,1,0,1
1111,100431,4,1,0,1
1111,100407,4,1,0,1
1111,100432,4,1,0,1
1111,100433,4,1,0,1
1111,100434,4,1,1,1
1111,100435,4,1,1,1
1111,100436,4,1,0,1
1111,100437,4,1,0,1
1111,100438,4,1,0,1
1111,100439,4,1,0,1
1111,100760,4,1,1,1
1111,100761,4,1,0,1
1111,100779,4,1,0,1
1111,100767,4,1,0,1
1111,100780,4,1,1,1
1111,100784,4,1,1,1
1111,100768,4,1,0,1
1111,100725,4,1,1,1
1111,100726,4,1,1,1
1111,100985,4,1,1,1
1111,100988,4,1,1,1
1111,100989,4,1,1,1
1111,100982,4,1,1,1
1111,100983,4,1,1,1
1111,101293,4,1,1,1
1111,101294,4,1,1,1
1111,101295,4,1,1,1
1111,101320,4,1,1,1
1135,101567,4,1,0,0
1135,101592,4,1,0,0
1135,101594,4,1,0,0
1135,101595,4,1,0,0
1135,101566,3,1,0,0
1135,101602,3,1,0,0
1135,101603,3,1,0,0
1135,101619,2,1,0,0
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

View File

@ -0,0 +1,104 @@
"version","gachaId","gachaName","type","kind","isCeiling","maxSelectPoint","ceilingCnt","changeRateCnt1","changeRateCnt2"
6,1011,"無料ガチャ",0,3,0,0,10,0,0
6,1012,"無料ガチャSR確定",0,3,0,0,10,0,0
6,1043,"レギュラーガチャ",0,0,0,0,10,0,0
6,1067,"例えるなら大人のパッションフルーツ
リゾートプールガチャ",0,1,0,0,10,0,0
6,1068,"柏木 咲姫
ピックアップガチャ",0,2,0,0,10,0,0
6,1069,"井之原 小星
ピックアップガチャ",0,2,0,0,10,0,0
6,1070,"目指すは優勝!
炎の体育祭リミテッドガチャ",0,1,1,110,10,0,0
6,1071,"星咲 あかり
ピックアップガチャ",0,2,0,0,10,0,0
6,1072,"藤沢 柚子
ピックアップガチャ",0,2,0,0,10,0,0
6,1073,"三角 葵
ピックアップガチャ",0,2,0,0,10,0,0
6,1074,"おくれてきた
Halloweenガチャ",0,1,0,0,10,0,0
6,1075,"早乙女 彩華
ピックアップガチャ",0,2,0,0,10,0,0
6,1076,"桜井 春菜
ピックアップガチャ",0,2,0,0,10,0,0
6,1077,"ふわふわすぺーす
お仕事体験リミテッドガチャ",0,1,1,110,10,0,0
6,1078,"高瀬 梨緒
ピックアップガチャ",0,2,0,0,10,0,0
6,1079,"結城 莉玖
ピックアップガチャ",0,2,0,0,10,0,0
6,1080,"藍原 椿
ピックアップガチャ",0,2,0,0,10,0,0
6,1081,"今夜はおうちでパーティ☆
メリクリガチャ",0,1,0,0,10,0,0
6,1082,"日向 千夏
ピックアップガチャ",0,2,0,0,10,0,0
6,1083,"柏木 美亜
ピックアップガチャ",0,2,0,0,10,0,0
6,1084,"東雲 つむぎ
ピックアップガチャ",0,2,0,0,10,0,0
6,1085,"謹賀新年
福袋ガチャ",0,0,1,33,10,0,0
6,1086,"逢坂 茜
ピックアップガチャ",0,2,0,0,10,0,0
6,1087,"珠洲島 有栖
ピックアップガチャ",0,2,0,0,10,0,0
6,1088,"九條 楓
ピックアップガチャ",0,2,0,0,10,0,0
6,1089,"冬の魔法
スーパーウルトラウィンターガチャ",0,1,0,0,10,0,0
6,1093,"高瀬 梨緒ピックアップガチャ",0,2,0,0,10,0,0
6,1094,"結城 莉玖ピックアップガチャ",0,2,0,0,10,0,0
6,1095,"藍原 椿ピックアップガチャ",0,2,0,0,10,0,0
6,1096,"早乙女 彩華ピックアップガチャ",0,2,0,0,10,0,0
6,1097,"桜井 春菜ピックアップガチャ",0,2,0,0,10,0,0
6,1098,"逢坂 茜ピックアップガチャ",0,2,0,0,10,0,0
6,1099,"九條 楓ピックアップガチャ",0,2,0,0,10,0,0
6,1100,"珠洲島 有栖ピックアップガチャ",0,2,0,0,10,0,0
6,1101,"LEAF属性オンリーガチャ",0,2,0,0,10,0,0
6,1102,"AQUA属性オンリーガチャ",0,2,0,0,10,0,0
6,1103,"FIRE属性オンリーガチャ",0,2,0,0,10,0,0
6,1104,"夜明け前の双星ガチャ",0,1,0,0,10,0,0
6,1105,"謎の洞窟 黄金は実在した!!ガチャ",0,1,0,0,10,0,0
6,1106,"スウィートブライダルリミテッドガチャ",0,1,0,0,10,0,0
6,1107,"忘れられない、愛(ピュア)とロックがここにある。ガチャ",0,1,0,0,10,0,0
6,1108,"メルティ夜ふかしガチャ",0,1,0,0,10,0,0
6,1109,"絵本の国のシューターズガチャ",0,1,0,0,10,0,0
6,1110,"オンゲキ R.E.D. PLUS 大感謝祭ガチャ",0,1,0,0,10,0,0
6,1111,"オンゲキ 3rd Anniversaryガチャ",0,1,1,33,10,0,0
6,1113,"柏木 咲姫ピックアップガチャ",0,2,0,0,10,0,0
6,1114,"井之原 小星ピックアップガチャ",0,2,0,0,10,0,0
6,1115,"星咲 あかりピックアップガチャ",0,2,0,0,10,0,0
6,1116,"藤沢 柚子ピックアップガチャ",0,2,0,0,10,0,0
6,1117,"三角 葵ピックアップガチャ",0,2,0,0,10,0,0
6,1118,"日向 千夏ピックアップガチャ",0,2,0,0,10,0,0
6,1119,"柏木 美亜ピックアップガチャ",0,2,0,0,10,0,0
6,1120,"東雲 つむぎピックアップガチャ",0,2,0,0,10,0,0
6,1121,"LEAF属性オンリーガチャ",0,2,0,0,10,0,0
6,1122,"FIRE属性オンリーガチャ",0,2,0,0,10,0,0
6,1123,"AQUA属性オンリーガチャ",0,2,0,0,10,0,0
6,1125,"Let`s SHOOTガチャ",0,1,0,0,10,0,0
6,1126,"ぽかぽか""温""ゲキ!いい湯だな リミテッドガチャ",0,1,0,0,10,0,0
6,1127,"聖夜に煌めく イルミネーションガチャ",0,1,0,0,10,0,0
6,1128,"bitter chocolate kiss ガチャ",0,1,0,0,10,0,0
6,1134,"謹賀新年福袋ガチャ",0,1,0,0,10,0,0
6,1135,"オンゲキ bright 大感謝祭ガチャ",0,1,0,0,10,0,0
7,1140,"カラフルアンブレラガチャ",0,0,0,0,10,0,0
7,1141,"It's Showtimeワンダフルサーカスガチャ",0,0,0,0,10,0,0
7,1147,"R.B.P. ピックアップガチャ",0,0,0,0,10,0,0
7,1148,"皇城 セツナ ピックアップガチャ",0,0,0,0,10,0,0
7,1149,"ASTERISM ピックアップガチャ",0,0,0,0,10,0,0
7,1153,"Memories of O.N.G.E.K.I.打ち上げガチャ",0,0,0,0,10,0,0
7,1156,"bright memory振り返りガチャ",0,0,0,0,10,0,0
7,1158,"レギュラーガチャ",0,0,0,100,0,0,0
7,1159,"オンゲキ&オンゲキ PLUS ピックアップガチャ",0,2,0,100,0,0,0
7,1160,"SUMMER SUMMER PLUS ピックアップガチャ",0,2,0,100,0,0,0
7,1161,"R.E.D. & R.E.D. PLUS ピックアップガチャ",0,2,0,100,0,0,0
7,1162,"bright & bright MEMORY ピックアップガチャ",0,2,0,100,0,0,0
7,1163,"4周年記念!! 4rd Anniversaryセレクトガチャ",0,1,0,100,0,0,0
7,1164,"2023謹賀新年福袋ガチャ",0,1,0,100,0,0,0
7,1165,"5周年記念!! 5rd Anniversaryセレクトガチャ",0,1,0,100,0,0,0
7,1166,"2024謹賀新年福袋ガチャ",0,1,0,100,0,0,0
7,1167,"6周年記念!! 6rd Anniversaryセレクトガチャ",0,1,0,100,0,0,0
7,1168,"2025謹賀新年福袋ガチャ",0,1,0,100,0,0,0
1 version gachaId gachaName type kind isCeiling maxSelectPoint ceilingCnt changeRateCnt1 changeRateCnt2
2 6 1011 無料ガチャ 0 3 0 0 10 0 0
3 6 1012 無料ガチャ(SR確定) 0 3 0 0 10 0 0
4 6 1043 レギュラーガチャ 0 0 0 0 10 0 0
5 6 1067 例えるなら大人のパッションフルーツ リゾートプールガチャ 0 1 0 0 10 0 0
6 6 1068 柏木 咲姫 ピックアップガチャ 0 2 0 0 10 0 0
7 6 1069 井之原 小星 ピックアップガチャ 0 2 0 0 10 0 0
8 6 1070 目指すは優勝! 炎の体育祭リミテッドガチャ 0 1 1 110 10 0 0
9 6 1071 星咲 あかり ピックアップガチャ 0 2 0 0 10 0 0
10 6 1072 藤沢 柚子 ピックアップガチャ 0 2 0 0 10 0 0
11 6 1073 三角 葵 ピックアップガチャ 0 2 0 0 10 0 0
12 6 1074 おくれてきた Halloweenガチャ 0 1 0 0 10 0 0
13 6 1075 早乙女 彩華 ピックアップガチャ 0 2 0 0 10 0 0
14 6 1076 桜井 春菜 ピックアップガチャ 0 2 0 0 10 0 0
15 6 1077 ふわふわすぺーす お仕事体験リミテッドガチャ 0 1 1 110 10 0 0
16 6 1078 高瀬 梨緒 ピックアップガチャ 0 2 0 0 10 0 0
17 6 1079 結城 莉玖 ピックアップガチャ 0 2 0 0 10 0 0
18 6 1080 藍原 椿 ピックアップガチャ 0 2 0 0 10 0 0
19 6 1081 今夜はおうちでパーティ☆ メリクリガチャ 0 1 0 0 10 0 0
20 6 1082 日向 千夏 ピックアップガチャ 0 2 0 0 10 0 0
21 6 1083 柏木 美亜 ピックアップガチャ 0 2 0 0 10 0 0
22 6 1084 東雲 つむぎ ピックアップガチャ 0 2 0 0 10 0 0
23 6 1085 謹賀新年 福袋ガチャ 0 0 1 33 10 0 0
24 6 1086 逢坂 茜 ピックアップガチャ 0 2 0 0 10 0 0
25 6 1087 珠洲島 有栖 ピックアップガチャ 0 2 0 0 10 0 0
26 6 1088 九條 楓 ピックアップガチャ 0 2 0 0 10 0 0
27 6 1089 冬の魔法 スーパーウルトラウィンターガチャ 0 1 0 0 10 0 0
28 6 1093 高瀬 梨緒ピックアップガチャ 0 2 0 0 10 0 0
29 6 1094 結城 莉玖ピックアップガチャ 0 2 0 0 10 0 0
30 6 1095 藍原 椿ピックアップガチャ 0 2 0 0 10 0 0
31 6 1096 早乙女 彩華ピックアップガチャ 0 2 0 0 10 0 0
32 6 1097 桜井 春菜ピックアップガチャ 0 2 0 0 10 0 0
33 6 1098 逢坂 茜ピックアップガチャ 0 2 0 0 10 0 0
34 6 1099 九條 楓ピックアップガチャ 0 2 0 0 10 0 0
35 6 1100 珠洲島 有栖ピックアップガチャ 0 2 0 0 10 0 0
36 6 1101 LEAF属性オンリーガチャ 0 2 0 0 10 0 0
37 6 1102 AQUA属性オンリーガチャ 0 2 0 0 10 0 0
38 6 1103 FIRE属性オンリーガチャ 0 2 0 0 10 0 0
39 6 1104 夜明け前の双星ガチャ 0 1 0 0 10 0 0
40 6 1105 謎の洞窟 黄金は実在した!!ガチャ 0 1 0 0 10 0 0
41 6 1106 スウィートブライダルリミテッドガチャ 0 1 0 0 10 0 0
42 6 1107 忘れられない、愛(ピュア)とロックがここにある。ガチャ 0 1 0 0 10 0 0
43 6 1108 メルティ夜ふかしガチャ 0 1 0 0 10 0 0
44 6 1109 絵本の国のシューターズガチャ 0 1 0 0 10 0 0
45 6 1110 オンゲキ R.E.D. PLUS 大感謝祭ガチャ 0 1 0 0 10 0 0
46 6 1111 オンゲキ 3rd Anniversaryガチャ 0 1 1 33 10 0 0
47 6 1113 柏木 咲姫ピックアップガチャ 0 2 0 0 10 0 0
48 6 1114 井之原 小星ピックアップガチャ 0 2 0 0 10 0 0
49 6 1115 星咲 あかりピックアップガチャ 0 2 0 0 10 0 0
50 6 1116 藤沢 柚子ピックアップガチャ 0 2 0 0 10 0 0
51 6 1117 三角 葵ピックアップガチャ 0 2 0 0 10 0 0
52 6 1118 日向 千夏ピックアップガチャ 0 2 0 0 10 0 0
53 6 1119 柏木 美亜ピックアップガチャ 0 2 0 0 10 0 0
54 6 1120 東雲 つむぎピックアップガチャ 0 2 0 0 10 0 0
55 6 1121 LEAF属性オンリーガチャ 0 2 0 0 10 0 0
56 6 1122 FIRE属性オンリーガチャ 0 2 0 0 10 0 0
57 6 1123 AQUA属性オンリーガチャ 0 2 0 0 10 0 0
58 6 1125 Let`s SHOOT!ガチャ 0 1 0 0 10 0 0
59 6 1126 ぽかぽか"温"ゲキ!いい湯だな リミテッドガチャ 0 1 0 0 10 0 0
60 6 1127 聖夜に煌めく イルミネーションガチャ 0 1 0 0 10 0 0
61 6 1128 bitter chocolate kiss ガチャ 0 1 0 0 10 0 0
62 6 1134 謹賀新年福袋ガチャ 0 1 0 0 10 0 0
63 6 1135 オンゲキ bright 大感謝祭ガチャ 0 1 0 0 10 0 0
64 7 1140 カラフルアンブレラガチャ 0 0 0 0 10 0 0
65 7 1141 It's Showtime!ワンダフルサーカスガチャ 0 0 0 0 10 0 0
66 7 1147 R.B.P. ピックアップガチャ 0 0 0 0 10 0 0
67 7 1148 皇城 セツナ ピックアップガチャ 0 0 0 0 10 0 0
68 7 1149 ASTERISM ピックアップガチャ 0 0 0 0 10 0 0
69 7 1153 Memories of O.N.G.E.K.I.打ち上げガチャ 0 0 0 0 10 0 0
70 7 1156 bright memory振り返りガチャ 0 0 0 0 10 0 0
71 7 1158 レギュラーガチャ 0 0 0 100 0 0 0
72 7 1159 オンゲキ&オンゲキ PLUS ピックアップガチャ 0 2 0 100 0 0 0
73 7 1160 SUMMER & SUMMER PLUS ピックアップガチャ 0 2 0 100 0 0 0
74 7 1161 R.E.D. & R.E.D. PLUS ピックアップガチャ 0 2 0 100 0 0 0
75 7 1162 bright & bright MEMORY ピックアップガチャ 0 2 0 100 0 0 0
76 7 1163 4周年記念!! 4rd Anniversaryセレクトガチャ 0 1 0 100 0 0 0
77 7 1164 2023謹賀新年福袋ガチャ 0 1 0 100 0 0 0
78 7 1165 5周年記念!! 5rd Anniversaryセレクトガチャ 0 1 0 100 0 0 0
79 7 1166 2024謹賀新年福袋ガチャ 0 1 0 100 0 0 0
80 7 1167 6周年記念!! 6rd Anniversaryセレクトガチャ 0 1 0 100 0 0 0
81 7 1168 2025謹賀新年福袋ガチャ 0 1 0 100 0 0 0

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)

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

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

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

@ -0,0 +1,91 @@
from twisted.web.http import Request
import json
import inflection
import yaml
import string
import logging
import coloredlogs
import zlib
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
class CardMakerServlet():
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
self.core_cfg = core_cfg
self.game_cfg = CardMakerConfig()
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/cardmaker.yaml")))
self.versions = [
CardMakerBase(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)
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
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"))

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

@ -0,0 +1,199 @@
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 ongeki version {version}")
exit(1)
def read(self) -> None:
static_datas = {
"static_cards.csv": "read_ongeki_card_csv",
"static_gachas.csv": "read_ongeki_gacha_csv",
"static_gacha_cards.csv": "read_ongeki_gacha_card_csv"
}
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:
dir = 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
self.read_gacha(f"{dir}/MU3/gacha")
self.read_card(f"{dir}/MU3/card")
def read_ongeki_card_csv(self, file_path: str) -> None:
self.logger.info(f"Reading 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_card(
row["version"],
row["cardId"],
name=row["name"],
charaId=row["charaId"],
nickName=row["nickName"] if row["nickName"] != "" else None,
school=row["school"],
attribute=row["attribute"],
gakunen=row["gakunen"],
rarity=row["rarity"],
levelParam=row["levelParam"],
skillId=row["skillId"],
choKaikaSkillId=row["choKaikaSkillId"],
cardNumber=row["cardNumber"] if row["cardNumber"] != "" else None
)
self.logger.info(f"Added card {row['cardId']}")
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"],
ceilingCnt=row["ceilingCnt"],
changeRateCnt1=row["changeRateCnt1"],
changeRateCnt2=row["changeRateCnt2"]
)
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_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)
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.ongeki_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_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
id = int(troot.find('Name').find('id').text)
gacha_kind = OngekiConstants.CM_GACHA_KINDS[
type_to_kind[troot.find('Type').text]].value
self.ongeki_data.static.put_gacha(
self.version, id, name, gacha_kind)
self.logger.info(f"Added gacha {id}")

View File

@ -16,6 +16,9 @@ class Mai2Base():
self.data = Mai2Data(cfg)
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):
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)
@ -202,9 +205,9 @@ class Mai2Base():
for fav in upsert["userFavoriteList"]:
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
if "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0:
for fsr in upsert["userFriendSeasonRankingList"]:
pass
# if "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0:
# for fsr in upsert["userFriendSeasonRankingList"]:
# pass
def handle_user_logout_api_request(self, data: Dict) -> Dict:
pass

View File

@ -1,5 +1,6 @@
from datetime import date, datetime, timedelta
from typing import Any, Dict
from random import randint
import pytz
import json
@ -8,8 +9,8 @@ from titles.ongeki.base import OngekiBase
from titles.ongeki.const import OngekiConstants
from titles.ongeki.config import OngekiConfig
class OngekiBright(OngekiBase):
class OngekiBright(OngekiBase):
def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None:
super().__init__(core_cfg, game_cfg)
self.version = OngekiConstants.VER_ONGEKI_BRIGHT
@ -19,3 +20,609 @@ class OngekiBright(OngekiBase):
ret["gameSetting"]["dataVersion"] = "1.30.00"
ret["gameSetting"]["onlineDataVersion"] = "1.30.00"
return ret
def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# first check for a bright memory profile after that check for a
# bright profile
p = (self.data.profile.get_profile_data(data["userId"], self.version+1)
or 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
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 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+1)
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version+1, 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+1)
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version+1, 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+1)
if p is not None:
# save the bright memory profile
self.data.profile.put_profile_data(
user_id, self.version+1, 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

@ -1,17 +1,31 @@
from typing import List
from core.config import CoreConfig
class OngekiServerConfig():
def __init__(self, parent_config: "OngekiConfig") -> None:
self.__config = parent_config
@property
def enable(self) -> bool:
return CoreConfig.get_config_field(self.__config, 'ongeki', 'server', 'enable', default=True)
@property
def loglevel(self) -> int:
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):
def __init__(self) -> None:
self.server = OngekiServerConfig(self)
self.gachas = OngekiGachaConfig(self)

View File

@ -1,5 +1,7 @@
from typing import Final, Dict
from enum import Enum
class OngekiConstants():
GAME_CODE = "SDDT"
@ -35,6 +37,20 @@ class OngekiConstants():
'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
class DIFF_NAME(Enum):
Basic = 0
@ -48,4 +64,4 @@ class OngekiConstants():
@classmethod
def game_ver_to_string(cls, ver: int):
return cls.VERSION_NAMES[ver]
return cls.VERSION_NAMES[ver]

View File

@ -2,6 +2,7 @@ from typing import Dict, Optional, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
from sqlalchemy.schema import ForeignKey
from sqlalchemy.engine import Row
from sqlalchemy.sql import func, select
from sqlalchemy.dialects.mysql import insert
@ -242,6 +243,63 @@ tech_event = Table(
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):
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
return result.fetchall()
def put_memorychapter(self, aime_id: int, memorychapter_data: Dict) -> Optional[int]:
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}")
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()
if result is None:
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("bestBattlePoint", 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"),
mysql_charset='utf8mb4'
)

View File

@ -37,7 +37,210 @@ music = Table(
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):
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]:
sql = insert(events).values(
version = version,