Author SHA1 Message Date
asterisk727 4a0254ec19 chore: update readme for ongeki 2026-09-25 12:58:18 -05:00
crxmsxnandRaymond c3661d3c94 feat: ongeki 1.60 support (#240)
Co-authored-by: Raymond <101374892+raymonable@users.noreply.github.com>
2026-09-24 23:17:01 -04:00
topty bcbc770ca7 feat(mai2): stub MAGiCAL API responses (#246) 2026-09-22 16:54:23 -04:00
27 changed files with 3756 additions and 1658 deletions
+9 -9
View File
@@ -32,15 +32,15 @@ Multipurpose game server for ALL.Net games.
> [!WARNING] > [!WARNING]
> CHUNITHM pre-NEW!! and maimai pre-DX are no longer supported after February 6th, 2026 and all associated data will be removed. > CHUNITHM pre-NEW!! and maimai pre-DX are no longer supported after February 6th, 2026 and all associated data will be removed.
| Game | Latest Ver. | Initial Ver. | Notes | Web UI | Import | AES | | Game | Latest Ver. | Initial Ver. | Notes | Web UI | Import | AES |
|------------------------|--------------------|---------------------|-------------------------------------------------------------|--------|-------------|---------| |------------------------|-----------------------|---------------------|-------------------------------------------------------------|--------|-------------|-----|
| SDHD: CHUNITHM | 2.50 (MATE) | 2.00 (NEW) | | ✅ | ✅ | ✅ | | SDHD: CHUNITHM | 2.50 (Mate) | 2.00 (NEW) | | ✅ | ✅ | ✅ |
| SDEZ: maimai DX | 1.65 (CiRCLE PLUS) | 1.00 (DX) | Thanks [@Menci](https://github.com/Menci) | ✅ | ✅ | ✅ | | SDEZ: maimai DX | 1.65 (CiRCLE PLUS) | 1.00 (DX) | Thanks [@Menci](https://github.com/Menci) | ✅ | ✅ | ✅ |
| SDGA: maimai DX (Intl) | 1.60 (CiRCLE) | 1.00 (DX) | Thanks [@Clansty](https://github.com/clansty) | ✅ | ✅ | ✅ | | SDGA: maimai DX (Intl) | 1.60 (CiRCLE) | 1.00 (DX) | Thanks [@Clansty](https://github.com/clansty) | ✅ | ✅ | ✅ |
| SDED: Card Maker | 1.39 | N/A | Thanks [@Becods](https://github.com/Becods) | ❌ | ❌ | ❌ | | SDED: Card Maker | 1.39 | N/A | Thanks [@Becods](https://github.com/Becods) | ❌ | ❌ | ❌ |
| SDDT: O.N.G.E.K.I. | 1.50 (Re:Fresh) | N/A | Thanks [@PenguinCaptain](https://github.com/PenguinCaptain) | ✅ | ✅ | ✅ | | SDDT: O.N.G.E.K.I. | 1.60 (Re:Fresh Act 2) | N/A | Thanks [@PenguinCaptain](https://github.com/PenguinCaptain) | ✅ | ✅ | ❌ |
| SBZV: Project DIVA | 7.10 | N/A | Minimal support | ❌ | ❌ | 🔓 | | SBZV: Project DIVA | 7.10 | N/A | Minimal support | ❌ | ❌ | 🔓 |
| SDFE: Wacca | 3.07 (REVERSE) | N/A | Later versions are EOS patches | ✅ | ❌ | 🔓 | | SDFE: Wacca | 3.07 (REVERSE) | N/A | Later versions are EOS patches | ✅ | ❌ | 🔓 |
<!-- A majority of them have been left as N/A for initial version as they do not appear to have restrictions --> <!-- A majority of them have been left as N/A for initial version as they do not appear to have restrictions -->
+6 -1
View File
@@ -32,6 +32,7 @@ import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import java.util.concurrent.locks.Lock import java.util.concurrent.locks.Lock
import kotlin.math.floor
import kotlin.reflect.KCallable import kotlin.reflect.KCallable
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
@@ -199,6 +200,10 @@ val Any?.truthy get() = when (this) {
} }
val Any?.str get() = toString() val Any?.str get() = toString()
fun truncateVersion(version: Number): Number {
return (floor(version.toFloat() / 5.0) * 5).toInt();
}
// Collections // Collections
fun <T> ls(vararg args: T) = args.toList() fun <T> ls(vararg args: T) = args.toList()
inline fun <reified T> arr(vararg args: T) = arrayOf(*args) inline fun <reified T> arr(vararg args: T) = arrayOf(*args)
@@ -277,4 +282,4 @@ fun List<List<Any?>>.numCsv(vararg head: Str) = head.joinToString(",") + "\n" +
joinToString("\n") { it.joinToString(",") } joinToString("\n") { it.joinToString(",") }
// DI // DI
inline fun <reified T : Any> ApplicationContext.lazy() = kotlin.lazy { getBean(T::class.java) } inline fun <reified T : Any> ApplicationContext.lazy() = kotlin.lazy { getBean(T::class.java) }
@@ -3,6 +3,7 @@ package icu.samnyan.aqua.sega.general.filter
import ext.details import ext.details
import ext.logger import ext.logger
import ext.toJson import ext.toJson
import ext.truncateVersion
import icu.samnyan.aqua.net.components.GeoIP import icu.samnyan.aqua.net.components.GeoIP
import icu.samnyan.aqua.sega.allnet.TokenChecker import icu.samnyan.aqua.sega.allnet.TokenChecker
import icu.samnyan.aqua.sega.general.model.GameEncryptionKey import icu.samnyan.aqua.sega.general.model.GameEncryptionKey
@@ -36,9 +37,6 @@ class CompressionFilter(
} }
fun truncateVersion(version: Number): Number {
return (floor(version.toFloat() / 5.0) * 5).toInt();
}
fun getVersion(req: HttpServletRequest, game: String): Int { fun getVersion(req: HttpServletRequest, game: String): Int {
// NOTE: this should help with some issues regarding mismatched data // NOTE: this should help with some issues regarding mismatched data
val expectedVersion = (req.getHeader("Mai-Encoding") ?: val expectedVersion = (req.getHeader("Mai-Encoding") ?:
@@ -7,11 +7,6 @@ import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusic import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusic
import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusicDetail import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusicDetail
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserKaleidx import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserKaleidx
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2ItemKind
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserItem
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPass
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserTicketLimitDate
import icu.samnyan.aqua.sega.maimai2.model.userdata.MAIMAI_DATETIME
import icu.samnyan.aqua.sega.maimai2.model.userdata.UserRegions import icu.samnyan.aqua.sega.maimai2.model.userdata.UserRegions
import java.time.LocalDate import java.time.LocalDate
import kotlin.random.Random import kotlin.random.Random
@@ -60,82 +55,13 @@ fun Maimai2ServletController.initApis() {
// Maimai only request for event type 1 // Maimai only request for event type 1
"GetGameEvent" static { mapOf("type" to 1, "gameEventList" to db.gameEvent.findAll()) } "GetGameEvent" static { mapOf("type" to 1, "gameEventList" to db.gameEvent.findAll()) }
"GetGameCharge" static { "GetGameCharge" static {
db.gameCharge.findAll().let { db.gameCharge.findAll().let {
mapOf( mapOf("length" to it.size, "gameChargeList" to it, "gamePassChargeList" to empty)
"length" to it.size,
"gameChargeList" to it,
"gamePassChargeList" to db.gamePassCharge.findAll()
)
} }
} }
"GetGameSellingPassPack" static {
mapOf("gameSellingPassPackList" to db.gameSellingPassPack.findAll())
}
"GetUserPass" {
db.userData.findByCardExtId(uid) ?: (404 - "User not found")
mapOf("userId" to uid, "userPassList" to db.userPass.findByUser_Card_ExtId(uid))
}
"GetUserTicketLimitDate" {
db.userData.findByCardExtId(uid) ?: (404 - "User not found")
mapOf("userTicketLimitDateList" to db.userTicketLimitDate.findByUser_Card_ExtId(uid))
}
"UserPassModeEnter" static { mapOf("returnCode" to 1) }
"UserPassModeExit" static { mapOf("returnCode" to 1) }
"UpsertUserPassMode" {
val user = db.userData.findByCardExtId(uid) ?: (404 - "User not found")
val pass = (data["userPassList"] as List<*>).first() as Map<*, *>
val passTypeId = (pass["passTypeId"] as Number).toInt()
val passPackId = (pass["passPackId"] as Number).toInt()
val passCharaId = (pass["passCharaId"] as Number).toInt()
val now = jstNow()
val startDate = now.format(MAIMAI_DATETIME)
val endDate = now.plusDays(14).format(MAIMAI_DATETIME)
// store the new pass
val storedPass = (db.userPass.findByUserAndPassTypeId(user, passTypeId) ?: Mai2UserPass()).apply {
this.user = user
this.passTypeId = passTypeId
this.passPackId = passPackId
this.passCharaId = passCharaId
this.startDate = startDate
this.endDate = endDate
}
db.userPass.save(storedPass)
// attempt to give user ticket 40001 (+1 track ticket) up to a max of 10
val ticketId = 40001
val ticket = (db.userItem.findByUserAndItemKindAndItemId(user, Mai2ItemKind.ticket.id, ticketId)
?: Mai2UserItem().apply {
this.user = user
itemKind = Mai2ItemKind.ticket.id
itemId = ticketId
}).apply {
stock = (stock + 1).coerceAtMost(10)
isValid = true
}
db.userItem.save(ticket)
// set expiry date for newly granted ticket
val limit = (db.userTicketLimitDate.findByUserAndItemId(user, ticketId)
?: Mai2UserTicketLimitDate().apply { this.user = user; itemId = ticketId }).apply {
limitDate = endDate
}
db.userTicketLimitDate.save(limit)
mapOf(
"returnCode" to 1,
"userPassList" to db.userPass.findByUser_Card_ExtId(uid),
"userItemList" to listOf(ticket),
"userTicketLimitDateList" to db.userTicketLimitDate.findByUser_Card_ExtId(uid)
)
}
"GetUserOption" { mapOf( "GetUserOption" { mapOf(
"userId" to uid, "userId" to uid,
"userOption" to (db.userOption.findSingleByUser_Card_ExtId(uid) ?: (404 - "User not found")) "userOption" to (db.userOption.findSingleByUser_Card_ExtId(uid) ?: (404 - "User not found"))
@@ -462,19 +388,14 @@ fun Maimai2ServletController.initApis() {
"aggrDate" to "" "aggrDate" to ""
) } ) }
"GetUserCircleData" { "GetUserCircleData" static { mapOf(
mapOf(
"userId" to uid,
"circleId" to 0, "circleId" to 0,
"circleName" to "一緒に歌おう!", "circleName" to "一緒に歌おう!",
"isPlace" to false, "isPlace" to false,
"circleClass" to 0, "circleClass" to 0,
"lastLoginDate" to "", "lastLoginDate" to "",
"mapBonusId" to 0,
"lastMapBonusDate" to (db.userData.findByCardExtId(uid)?.lastMapBonusDate ?: ""),
"circlePointRankingList" to empty "circlePointRankingList" to empty
) ) }
}
"GetUserCircleChallenge" { mapOf( "GetUserCircleChallenge" { mapOf(
"userId" to uid, "userId" to uid,
@@ -522,6 +443,12 @@ fun Maimai2ServletController.initApis() {
) )
) } ) }
"GetGameSellingPassPack" static { mapOf("gameSellingPassPackList" to empty) }
"GetUserPass" { mapOf("userId" to uid, "userPassList" to empty) }
"GetUserTicketLimitDate" static { mapOf("userTicketLimitDateList" to empty) }
// NOTE: no-op APIs moved to respect encryption // NOTE: no-op APIs moved to respect encryption
"UpsertUserPlaceCircleRegist" static { mapOf( "returnCode" to 0, "apiName" to "com.sega.maimai2servlet.api.UpsertUserPlaceCircleRegistApi") } "UpsertUserPlaceCircleRegist" static { mapOf( "returnCode" to 0, "apiName" to "com.sega.maimai2servlet.api.UpsertUserPlaceCircleRegistApi") }
"GetUserScoreRanking" static { """{"returnCode":1, "apiName":"com.sega.maimai2servlet.api.GetUserScoreRankingApi"}""" } "GetUserScoreRanking" static { """{"returnCode":1, "apiName":"com.sega.maimai2servlet.api.GetUserScoreRankingApi"}""" }
@@ -75,7 +75,7 @@ class UpsertUserAllHandler(
listOfNotNull( listOfNotNull(
userExtend, userOption, userCharacterList, userMapList, userLoginBonusList, userItemList, userExtend, userOption, userCharacterList, userMapList, userLoginBonusList, userItemList,
userMusicDetailList, userCourseList, userFriendSeasonRankingList, userFavoriteList, userMusicDetailList, userCourseList, userFriendSeasonRankingList, userFavoriteList,
userKaleidxScopeList, userIntimateList, userTicketLimitDateList, upsertUserAll.userPlaylogList userKaleidxScopeList, userIntimateList, upsertUserAll.userPlaylogList
) )
}.flatten().forEach { it.user = u } }.flatten().forEach { it.user = u }
@@ -142,17 +142,6 @@ class UpsertUserAllHandler(
repos.userIntimate.saveAll(lst.mapApply { repos.userIntimate.saveAll(lst.mapApply {
id = repos.userIntimate.findByUserAndPartnerId(u, partnerId)?.id ?: 0 }) } id = repos.userIntimate.findByUserAndPartnerId(u, partnerId)?.id ?: 0 }) }
req.userTicketLimitDateList?.unique { it.itemId }?.let { lst ->
repos.userTicketLimitDate.saveAll(lst.mapApply {
id = repos.userTicketLimitDate.findByUserAndItemId(u, itemId)?.id ?: 0
})
}
req.userCircleData?.lastMapBonusDate?.let {
u.lastMapBonusDate = it
repos.userData.save(u)
}
// Added on SDGA 1.65 / SDGB 1.53 // Added on SDGA 1.65 / SDGB 1.53
upsertUserAll.userPlaylogList?.let(repos.userPlaylog::saveAll) upsertUserAll.userPlaylogList?.let(repos.userPlaylog::saveAll)
@@ -20,22 +20,6 @@ class Mai2GameCharge {
var endDate: String? = "2029-01-01 00:00:00.000000" var endDate: String? = "2029-01-01 00:00:00.000000"
} }
class Mai2GamePassCharge {
var orderId: Long = 0
var passTypeId = 0
var price = 0
var startDate: String? = "2019-01-01 00:00:00.000000"
var endDate: String? = "2099-01-01 00:00:00.000000"
}
class Mai2GameSellingPassPack {
var passPackId = 0
var startDate: String? = "2019-01-01 00:00:00.000000"
var endDate: String? = "2099-01-01 00:00:00.000000"
var noticeStartDate: String? = "2019-01-01 00:00:00.000000"
var noticeEndDate: String? = "2099-01-01 00:00:00.000000"
}
class Mai2GameSellingCard { class Mai2GameSellingCard {
var cardId = 0L var cardId = 0L
var startDate: String? = "2019-01-01 00:00:00.000000" var startDate: String? = "2019-01-01 00:00:00.000000"
@@ -42,14 +42,6 @@ interface Mai2UserCharacterRepo : Mai2UserLinked<Mai2UserCharacter> {
interface Mai2UserChargeRepo : Mai2UserLinked<Mai2UserCharge> interface Mai2UserChargeRepo : Mai2UserLinked<Mai2UserCharge>
interface Mai2UserPassRepo : Mai2UserLinked<Mai2UserPass> {
fun findByUserAndPassTypeId(user: Mai2UserDetail, passTypeId: Int): Mai2UserPass?
}
interface Mai2UserTicketLimitDateRepo : Mai2UserLinked<Mai2UserTicketLimitDate> {
fun findByUserAndItemId(user: Mai2UserDetail, itemId: Int): Mai2UserTicketLimitDate?
}
interface Mai2UserCourseRepo : Mai2UserLinked<Mai2UserCourse> { interface Mai2UserCourseRepo : Mai2UserLinked<Mai2UserCourse> {
fun findByUserAndCourseId(user: Mai2UserDetail, courseId: Int): Mai2UserCourse? fun findByUserAndCourseId(user: Mai2UserDetail, courseId: Int): Mai2UserCourse?
} }
@@ -138,8 +130,6 @@ class Mai2Repos(
val userCard: Mai2UserCardRepo, val userCard: Mai2UserCardRepo,
val userCharacter: Mai2UserCharacterRepo, val userCharacter: Mai2UserCharacterRepo,
val userCharge: Mai2UserChargeRepo, val userCharge: Mai2UserChargeRepo,
val userPass: Mai2UserPassRepo,
val userTicketLimitDate: Mai2UserTicketLimitDateRepo,
val userCourse: Mai2UserCourseRepo, val userCourse: Mai2UserCourseRepo,
val userData: Mai2UserDataRepo, val userData: Mai2UserDataRepo,
val userExtend: Mai2UserExtendRepo, val userExtend: Mai2UserExtendRepo,
@@ -160,8 +150,6 @@ class Mai2Repos(
val gameData: GameDataService val gameData: GameDataService
) { ) {
val gameCharge = StaticRepo(gameData.mai2Charges) { it.orderId } val gameCharge = StaticRepo(gameData.mai2Charges) { it.orderId }
val gamePassCharge = StaticRepo(gameData.mai2PassCharges) { it.passTypeId.toLong() }
val gameSellingPassPack = StaticRepo(gameData.mai2SellingPassPacks) { it.passPackId.toLong() }
val gameEvent = StaticRepo(gameData.mai2Events) { it.id } val gameEvent = StaticRepo(gameData.mai2Events) { it.id }
val gameSellingCard = StaticRepo(gameData.mai2SellingCards) { it.cardId } val gameSellingCard = StaticRepo(gameData.mai2SellingCards) { it.cardId }
} }
@@ -30,8 +30,6 @@ class Mai2UserAll {
var userFavoritemusicList: List<Mai2UserFavoriteItem>? = null var userFavoritemusicList: List<Mai2UserFavoriteItem>? = null
var userKaleidxScopeList: List<Mai2UserKaleidx>? = null var userKaleidxScopeList: List<Mai2UserKaleidx>? = null
var userIntimateList: List<Mai2UserIntimate>? = null var userIntimateList: List<Mai2UserIntimate>? = null
var userTicketLimitDateList: List<Mai2UserTicketLimitDate>? = null
var userCircleData: Mai2UserCircleData? = null
var isNewCharacterList: String? = null var isNewCharacterList: String? = null
var isNewMapList: String? = null var isNewMapList: String? = null
var isNewLoginBonusList: String? = null var isNewLoginBonusList: String? = null
@@ -44,11 +42,6 @@ class Mai2UserAll {
var isNewKaleidxScopeList: String? = null var isNewKaleidxScopeList: String? = null
} }
class Mai2UserCircleData {
var lastLoginDate: String = ""
var lastMapBonusDate: String = ""
}
class Mai2UserFavoriteItem { class Mai2UserFavoriteItem {
var orderId = 0 var orderId = 0
var id = 0 var id = 0
@@ -59,3 +52,4 @@ class Mai2UserActivity {
var musicList: List<Mai2UserAct> = emptyList() var musicList: List<Mai2UserAct> = emptyList()
} }
@@ -87,7 +87,6 @@ class Mai2UserDetail(
var dailyCourseBonusDate: String = "", var dailyCourseBonusDate: String = "",
var lastPairLoginDate: String = "", var lastPairLoginDate: String = "",
var lastTrialPlayDate: String = "", var lastTrialPlayDate: String = "",
var lastMapBonusDate: String = "",
var playVsCount: Int = 0, var playVsCount: Int = 0,
var playSyncCount: Int = 0, var playSyncCount: Int = 0,
var winCount: Int = 0, var winCount: Int = 0,
@@ -111,25 +111,6 @@ class Mai2UserCharge : Mai2UserEntity() {
var validDate: String = "" var validDate: String = ""
} }
@Table(name = "maimai2_user_pass", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "pass_type_id"])])
@Entity
class Mai2UserPass : Mai2UserEntity() {
var passTypeId = 0
var passPackId = 0
var passCharaId = 0
var mapId = 0
var startDate: String = ""
var endDate: String = ""
}
@Table(name = "maimai2_user_ticket_limit_date", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "item_id"])])
@Entity
class Mai2UserTicketLimitDate : Mai2UserEntity() {
var itemId = 0
var limitDate: String = ""
var lastUsedDate: String = ""
}
@Table(name = "maimai2_user_course") @Table(name = "maimai2_user_course")
@Entity @Entity
class Mai2UserCourse : Mai2UserEntity() { class Mai2UserCourse : Mai2UserEntity() {
@@ -4,6 +4,8 @@ import ext.empty
import ext.int import ext.int
import ext.parsing import ext.parsing
import ext.plus import ext.plus
import ext.str
import ext.truncateVersion
fun OngekiController.ongekiInit() { fun OngekiController.ongekiInit() {
fun <T> List<T>.staticLst(key: String) = mapOf("length" to size, key to this) fun <T> List<T>.staticLst(key: String) = mapOf("length" to size, key to this)
@@ -13,18 +15,21 @@ fun OngekiController.ongekiInit() {
initUpsertAll() initUpsertAll()
// Has type, but type is always 1 // Has type, but type is always 1
"GetGameEvent".static { "GetGameEvent" {
gdb.event.findAll().map { // val trunkVer = truncateVersion((data["version"] ?: "1.50.00").str.split(".").take(2).joinToString("").int)
mapOf("id" to it.id, "type" to 1, "startDate" to "2005-01-01 00:00:00.0", "endDate" to "2099-01-01 05:00:00.0") val events = gdb.event.findAll()
}.staticLst("gameEventList") + mapOf("type" to 1) // .filter{ it.id.str.startsWith(trunkVer.str) }
.map {
mapOf("id" to it.id, "type" to 1, "startDate" to "2005-01-01 00:00:00.0", "endDate" to "2099-01-01 05:00:00.0")
}
mapOf("gameEventList" to events, "length" to events.size, "type" to 1)
} }
"GetGamePoint".static { gdb.point.findAll().staticLst("gamePointList") } "GetGamePoint".static { gdb.point.findAll().staticLst("gamePointList") }
"GetGamePresent".static { gdb.present.findAll().staticLst("gamePresentList") } "GetGamePresent".static { gdb.present.findAll().staticLst("gamePresentList") }
"GetGameReward".static { gdb.reward.findAll().staticLst("gameRewardList") } "GetGameReward".static { gdb.reward.findAll().staticLst("gameRewardList") }
// Dummy endpoints "GetGameTechMusic".static { gdb.gameData.ogkGameTechMusics.staticLst("gameTechMusicList") }
"GetGameTechMusic".static { empty.staticLst("gameTechMusicList") }
"GetGameMessage" { mapOf("type" to data["type"], "length" to 0, "gameMessageList" to empty) } "GetGameMessage" { mapOf("type" to data["type"], "length" to 0, "gameMessageList" to empty) }
"GetGameMusicReleaseState".static { mapOf("techScore" to 0, "cardNum" to 0) } "GetGameMusicReleaseState".static { mapOf("techScore" to 0, "cardNum" to 0) }
@@ -82,4 +87,4 @@ fun OngekiController.ongekiInit() {
"UpsertClientTestmode" static { """{"returnCode":1, "apiName":"UpsertClientTestmodeApi"}""" } "UpsertClientTestmode" static { """{"returnCode":1, "apiName":"UpsertClientTestmodeApi"}""" }
"UpsertUserGplog" static { """{"returnCode":1, "apiName":"UpsertUserGplogApi"}""" } "UpsertUserGplog" static { """{"returnCode":1, "apiName":"UpsertUserGplogApi"}""" }
"Ping" static { """{"returnCode":1, "apiName":"Ping"}""" } "Ping" static { """{"returnCode":1, "apiName":"Ping"}""" }
} }
@@ -54,11 +54,12 @@ interface OgkUserDeckRepo : OngekiUserLinked<UserDeck> {
} }
interface OgkUserEventMusicRepo : OngekiUserLinked<UserEventMusic> { interface OgkUserEventMusicRepo : OngekiUserLinked<UserEventMusic> {
fun findByUserAndEventIdAndTypeAndMusicId( fun findByUserAndEventIdAndTypeAndMusicIdAndLevel(
userData: UserData, userData: UserData,
eventId: Int, eventId: Int,
type: Int, type: Int,
musicId: Int musicId: Int,
level: Int
): UserEventMusic? ): UserEventMusic?
} }
@@ -162,7 +163,10 @@ interface OgkUserGachaRepo : OngekiUserLinked<UserGacha> {
} }
// Re:Fresh // Re:Fresh
interface OgkUserEventMapRepo : OngekiUserLinked<UserEventMap> interface OgkUserEventMapRepo : OngekiUserLinked<UserEventMap> {
fun findByUserAndEventIdAndMapId(user: UserData, eventId: Int, mapId: Int): UserEventMap?
fun findByUser_Card_ExtIdAndEventIdAndMapId(extId: Long, eventId: Int, mapId: Int): UserEventMap?
}
interface OgkUserSkinRepo : OngekiUserLinked<UserSkin> interface OgkUserSkinRepo : OngekiUserLinked<UserSkin>
@@ -74,7 +74,9 @@ fun OngekiController.initUpsertAll() {
// UserEventMap // UserEventMap
userEventMap?.let { userEventMap?.let {
db.eventMap.save(it.apply { db.eventMap.save(it.apply {
id = db.eventMap.findSingleByUser(u)?.id ?: 0 }) } id = db.eventMap.findByUserAndEventIdAndMapId(u, it.eventId, it.mapId)?.id ?: 0
})
}
// UserPlaylogList // UserPlaylogList
userPlaylogList?.let { db.playlog.saveAll(it) } userPlaylogList?.let { db.playlog.saveAll(it) }
@@ -195,8 +197,9 @@ fun OngekiController.initUpsertAll() {
// UserEventMusicList // UserEventMusicList
userEventMusicList?.let { list -> userEventMusicList?.let { list ->
db.eventMusic.saveAll(list.distinctBy { it.eventId to it.type to it.musicId }.mapApply { db.eventMusic.saveAll(list.distinctBy { it.eventId to it.type to it.musicId to it.level }.mapApply {
id = db.eventMusic.findByUserAndEventIdAndTypeAndMusicId(u, eventId, type, musicId)?.id ?: 0 }) } id = db.eventMusic.findByUserAndEventIdAndTypeAndMusicIdAndLevel(u, eventId, type, musicId, level)?.id ?: 0
}) }
// UserTechEventList // UserTechEventList
userTechEventList?.let { list -> userTechEventList?.let { list ->
@@ -7,6 +7,7 @@ import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.general.model.UserRecentRating import icu.samnyan.aqua.sega.general.model.UserRecentRating
import icu.samnyan.aqua.sega.ongeki.model.OgkItemType import icu.samnyan.aqua.sega.ongeki.model.OgkItemType
import icu.samnyan.aqua.sega.ongeki.model.UserItem import icu.samnyan.aqua.sega.ongeki.model.UserItem
import icu.samnyan.aqua.sega.ongeki.model.UserEventMap
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort import org.springframework.data.domain.Sort
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -14,10 +15,25 @@ import java.time.format.DateTimeFormatter
fun OngekiController.initUser() { fun OngekiController.initUser() {
val emptyEventMapData = """{"unlockPoints":0,"_unlockedTileIDs":[1],"_rewardScenarioIDs":[],"_rewardRewardKeyIDs":[],"endless":false,"loopCount":0}"""
"GetUserData" { mapOf("userId" to uid, "userData" to db.data.findByCard_ExtId(uid)) } "GetUserData" { mapOf("userId" to uid, "userData" to db.data.findByCard_ExtId(uid)) }
"GetUserOption" { mapOf("userId" to uid, "userOption" to db.option.findSingleByUser_Card_ExtId(uid)) } "GetUserOption" { mapOf("userId" to uid, "userOption" to db.option.findSingleByUser_Card_ExtId(uid)) }
"GetUserEventMap" { mapOf("userId" to uid, "userEventMap" to db.eventMap.findSingleByUser_Card_ExtId(uid)) } "GetUserEventMap" {
val eventId = data["eventId"]?.int ?: 0
val mapId = data["mapId"]?.int ?: 0
val userEventMap = db.eventMap.findByUser_Card_ExtIdAndEventIdAndMapId(uid, eventId, mapId)
?: UserEventMap().apply {
this.eventId = eventId
this.mapId = mapId
this.mapData = emptyEventMapData
}
mapOf(
"userId" to uid,
"userEventMap" to userEventMap
)
}
"GetUserTechEvent".unpaged { db.techEvent.findByUser_Card_ExtId(uid) } "GetUserTechEvent".unpaged { db.techEvent.findByUser_Card_ExtId(uid) }
"GetUserBoss".unpaged { db.boss.findByUser_Card_ExtId(uid) } "GetUserBoss".unpaged { db.boss.findByUser_Card_ExtId(uid) }
@@ -29,6 +29,12 @@ class GameEvent {
var id: Long = 0 var id: Long = 0
} }
class GameTechMusic {
var eventId = 0
var musicId = 0
var level = 0
}
class GameMusic { class GameMusic {
var id: Long = 0 var id: Long = 0
var name: String = "" var name: String = ""
@@ -105,4 +111,4 @@ class GameGacha {
@JsonProperty("isCeiling") @JsonProperty("isCeiling")
var isCeiling: Boolean = false var isCeiling: Boolean = false
} }
@@ -166,6 +166,7 @@ class UserChapter : OngekiUserEntity() {
var jewelCount = 0 var jewelCount = 0
var lastPlayMusicCategory = 0 var lastPlayMusicCategory = 0
var lastPlayMusicId = 0 var lastPlayMusicId = 0
var lastPlayMusicMinorCategory: String = ""
var lastPlayMusicLevel = 0 var lastPlayMusicLevel = 0
var isStoryWatched = false var isStoryWatched = false
var isClear = false var isClear = false
@@ -197,7 +198,10 @@ class UserDeck : OngekiUserEntity() {
} }
@Entity(name = "OngekiUserEventMusic") @Entity(name = "OngekiUserEventMusic")
@Table(name = "ongeki_user_event_music") @Table(
name = "ongeki_user_event_music",
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "event_id", "type", "music_id", "level"])]
)
class UserEventMusic : OngekiUserEntity() { class UserEventMusic : OngekiUserEntity() {
var eventId = 0 var eventId = 0
var type = 0 var type = 0
@@ -318,6 +322,7 @@ class UserMusicItem : OngekiUserEntity() {
class UserOption : OngekiUserEntity() { class UserOption : OngekiUserEntity() {
var optionSet = 0 var optionSet = 0
var speed = 0 var speed = 0
var fieldWall = 0
var mirror = 0 var mirror = 0
var judgeTiming = 0 var judgeTiming = 0
var judgeAdjustment = 0 var judgeAdjustment = 0
@@ -342,20 +347,19 @@ class UserOption : OngekiUserEntity() {
var colorSide = 0 var colorSide = 0
var effectDamage = 0 var effectDamage = 0
var effectPos = 0 var effectPos = 0
var effectAttack = 0
var judgeDisp = 0 var judgeDisp = 0
var judgePos = 0 var judgePos = 0
var judgeBreak = 0 var judgeBreak = 0
var judgeHit = 0 var judgeHit = 0
var platinumBreakDisp = 0
var judgeCriticalBreak = 0 var judgeCriticalBreak = 0
var platinumBreakDisp = 0
var matching = 0 var matching = 0
var dispPlayerLv = 0 var dispPlayerLv = 0
var dispRating = 0 var dispRating = 0
var dispBP = 0 var dispBP = 0
var headphone = 0 var headphone = 0
// Re:Fresh
var effectAttack = 0
} }
@@ -457,6 +461,7 @@ class UserStory : OngekiUserEntity() {
var jewelCount = 0 var jewelCount = 0
var lastPlayMusicId = 0 var lastPlayMusicId = 0
var lastPlayMusicCategory = 0 var lastPlayMusicCategory = 0
var lastPlayMusicMinorCategory: String = ""
var lastPlayMusicLevel = 0 var lastPlayMusicLevel = 0
} }
@@ -498,7 +503,10 @@ class UserTrainingRoom : OngekiUserEntity() {
// Re:Fresh // Re:Fresh
@Entity(name = "OngekiUserEventMap") @Entity(name = "OngekiUserEventMap")
@Table(name = "ongeki_user_event_map") @Table(
name = "ongeki_user_event_map",
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "event_id", "map_id"])]
)
class UserEventMap : OngekiUserEntity() { class UserEventMap : OngekiUserEntity() {
var eventId = 0 var eventId = 0
var mapId = 0 var mapId = 0
@@ -13,14 +13,13 @@ import icu.samnyan.aqua.sega.chusan.model.GameLoginBonus as Chu3GameLoginBonus
import icu.samnyan.aqua.sega.chusan.model.GameLoginBonusPreset as Chu3GameLoginBonusPreset import icu.samnyan.aqua.sega.chusan.model.GameLoginBonusPreset as Chu3GameLoginBonusPreset
import icu.samnyan.aqua.sega.maimai2.model.Mai2GameCharge import icu.samnyan.aqua.sega.maimai2.model.Mai2GameCharge
import icu.samnyan.aqua.sega.maimai2.model.Mai2GameEvent import icu.samnyan.aqua.sega.maimai2.model.Mai2GameEvent
import icu.samnyan.aqua.sega.maimai2.model.Mai2GamePassCharge
import icu.samnyan.aqua.sega.maimai2.model.Mai2GameSellingPassPack
import icu.samnyan.aqua.sega.maimai2.model.Mai2GameSellingCard import icu.samnyan.aqua.sega.maimai2.model.Mai2GameSellingCard
import icu.samnyan.aqua.sega.ongeki.model.GameCard as OgkGameCard import icu.samnyan.aqua.sega.ongeki.model.GameCard as OgkGameCard
import icu.samnyan.aqua.sega.ongeki.model.GameChara as OgkGameChara import icu.samnyan.aqua.sega.ongeki.model.GameChara as OgkGameChara
import icu.samnyan.aqua.sega.ongeki.model.GameEvent as OgkGameEvent import icu.samnyan.aqua.sega.ongeki.model.GameEvent as OgkGameEvent
import icu.samnyan.aqua.sega.ongeki.model.GameGacha as OgkGameGacha import icu.samnyan.aqua.sega.ongeki.model.GameGacha as OgkGameGacha
import icu.samnyan.aqua.sega.ongeki.model.GameGachaCard as OgkGameGachaCard import icu.samnyan.aqua.sega.ongeki.model.GameGachaCard as OgkGameGachaCard
import icu.samnyan.aqua.sega.ongeki.model.GameTechMusic as OgkGameTechMusic
import icu.samnyan.aqua.sega.ongeki.model.GameMusic as OgkGameMusic import icu.samnyan.aqua.sega.ongeki.model.GameMusic as OgkGameMusic
import icu.samnyan.aqua.sega.ongeki.model.GamePoint as OgkGamePoint import icu.samnyan.aqua.sega.ongeki.model.GamePoint as OgkGamePoint
import icu.samnyan.aqua.sega.ongeki.model.GamePresent as OgkGamePresent import icu.samnyan.aqua.sega.ongeki.model.GamePresent as OgkGamePresent
@@ -52,13 +51,12 @@ class GameDataService() {
log.warn("Game data file $f or resource $resPath not found, using empty list") log.warn("Game data file $f or resource $resPath not found, using empty list")
return emptyList() return emptyList()
} }
} }
// maimai2 // maimai2
lateinit var mai2Events: List<Mai2GameEvent> lateinit var mai2Events: List<Mai2GameEvent>
lateinit var mai2Charges: List<Mai2GameCharge> lateinit var mai2Charges: List<Mai2GameCharge>
lateinit var mai2PassCharges: List<Mai2GamePassCharge>
lateinit var mai2SellingPassPacks: List<Mai2GameSellingPassPack>
lateinit var mai2SellingCards: List<Mai2GameSellingCard> lateinit var mai2SellingCards: List<Mai2GameSellingCard>
lateinit var mai2GameEncryption: List<GameEncryptionKey> lateinit var mai2GameEncryption: List<GameEncryptionKey>
@@ -76,6 +74,7 @@ class GameDataService() {
lateinit var ogkGameCards: List<OgkGameCard> lateinit var ogkGameCards: List<OgkGameCard>
lateinit var ogkGameCharas: List<OgkGameChara> lateinit var ogkGameCharas: List<OgkGameChara>
lateinit var ogkGameEvents: List<OgkGameEvent> lateinit var ogkGameEvents: List<OgkGameEvent>
lateinit var ogkGameTechMusics: List<OgkGameTechMusic>
lateinit var ogkGameMusics: List<OgkGameMusic> lateinit var ogkGameMusics: List<OgkGameMusic>
lateinit var ogkGamePoints: List<OgkGamePoint> lateinit var ogkGamePoints: List<OgkGamePoint>
lateinit var ogkGamePresents: List<OgkGamePresent> lateinit var ogkGamePresents: List<OgkGamePresent>
@@ -92,8 +91,6 @@ class GameDataService() {
fun load() { fun load() {
mai2Events = load("maimai2", "game_event.json") mai2Events = load("maimai2", "game_event.json")
mai2Charges = load("maimai2", "game_charge.json") mai2Charges = load("maimai2", "game_charge.json")
mai2PassCharges = load("maimai2", "game_pass_charge.json")
mai2SellingPassPacks = load("maimai2", "game_selling_pass_pack.json")
mai2SellingCards = load("maimai2", "game_selling_card.json") mai2SellingCards = load("maimai2", "game_selling_card.json")
mai2GameEncryption = load(game = "maimai2", file = "game_encryption.json") mai2GameEncryption = load(game = "maimai2", file = "game_encryption.json")
@@ -109,6 +106,7 @@ class GameDataService() {
ogkGameCards = load("ongeki", "game_card.json") ogkGameCards = load("ongeki", "game_card.json")
ogkGameCharas = load("ongeki", "game_chara.json") ogkGameCharas = load("ongeki", "game_chara.json")
ogkGameEvents = load("ongeki", "game_event.json") ogkGameEvents = load("ongeki", "game_event.json")
ogkGameTechMusics = load("ongeki", "game_tech_music.json")
ogkGameMusics = load("ongeki", "game_music.json") ogkGameMusics = load("ongeki", "game_music.json")
ogkGamePoints = load("ongeki", "game_point.json") ogkGamePoints = load("ongeki", "game_point.json")
ogkGamePresents = load("ongeki", "game_present.json") ogkGamePresents = load("ongeki", "game_present.json")
@@ -1,4 +0,0 @@
[
{"orderId": 0, "passTypeId": 2, "price": 2, "startDate": "2026-09-17 00:00:00.000000", "endDate": "2099-01-01 00:00:00.000000"},
{"orderId": 0, "passTypeId": 3, "price": 3, "startDate": "2026-09-17 00:00:00.000000", "endDate": "2099-01-01 00:00:00.000000"}
]
@@ -1,5 +0,0 @@
[
{"passPackId": 7001, "startDate": "2026-09-17 00:00:00.000000", "endDate": "2026-11-06 00:00:00.000000", "noticeStartDate": "2026-09-03 00:00:00.000000", "noticeEndDate": "2026-11-06 00:00:00.000000"},
{"passPackId": 7002, "startDate": "2026-11-06 00:00:00.000000", "endDate": "2027-01-08 00:00:00.000000", "noticeStartDate": "2026-10-23 00:00:00.000000", "noticeEndDate": "2027-01-08 00:00:00.000000"},
{"passPackId": 7003, "startDate": "2027-01-08 00:00:00.000000", "endDate": "2099-01-01 00:00:00.000000", "noticeStartDate": "2026-12-25 00:00:00.000000", "noticeEndDate": "2099-01-01 00:00:00.000000"}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,31 +0,0 @@
ALTER TABLE maimai2_user_detail
ADD COLUMN last_map_bonus_date VARCHAR(64) NOT NULL DEFAULT '';
CREATE TABLE maimai2_user_pass
(
id BIGINT AUTO_INCREMENT NOT NULL,
user_id BIGINT NOT NULL,
pass_type_id INT NOT NULL,
pass_pack_id INT NOT NULL,
pass_chara_id INT NOT NULL,
map_id INT NOT NULL DEFAULT 0,
start_date VARCHAR(64) NOT NULL DEFAULT '',
end_date VARCHAR(64) NOT NULL DEFAULT '',
CONSTRAINT pk_maimai2_user_pass PRIMARY KEY (id),
CONSTRAINT uc_maimai2_user_pass_type UNIQUE (user_id, pass_type_id),
CONSTRAINT fk_maimai2_user_pass_user FOREIGN KEY (user_id)
REFERENCES maimai2_user_detail (id) ON DELETE CASCADE
);
CREATE TABLE maimai2_user_ticket_limit_date
(
id BIGINT AUTO_INCREMENT NOT NULL,
user_id BIGINT NOT NULL,
item_id INT NOT NULL,
limit_date VARCHAR(64) NOT NULL DEFAULT '',
last_used_date VARCHAR(64) NOT NULL DEFAULT '',
CONSTRAINT pk_maimai2_user_ticket_limit_date PRIMARY KEY (id),
CONSTRAINT uc_maimai2_user_ticket_limit_date_item UNIQUE (user_id, item_id),
CONSTRAINT fk_maimai2_user_ticket_limit_date_user FOREIGN KEY (user_id)
REFERENCES maimai2_user_detail (id) ON DELETE CASCADE
);
@@ -0,0 +1,20 @@
-- should have been not nullable in the first place smh
DELETE FROM ongeki_user_event_map
WHERE user_id IS NULL;
ALTER TABLE ongeki_user_event_map
MODIFY user_id BIGINT NOT NULL;
-- dedupe before adding new constraint just in case
DELETE FROM ongeki_user_event_map
WHERE id NOT IN (
SELECT id
FROM (
SELECT MAX(id) AS id
FROM ongeki_user_event_map
GROUP BY user_id, event_id, map_id
) AS rows_to_keep
);
ALTER TABLE ongeki_user_event_map
ADD UNIQUE KEY uniq_ongeki_user_event_map (user_id, event_id, map_id);
@@ -0,0 +1,23 @@
-- allow separate Technical Challenge records for each chart difficulty
-- drop the old constraint and dedupe with the new constraint just in case again
ALTER TABLE ongeki_user_event_music
DROP INDEX IF EXISTS UKo5PU3BgZeTKB8SNykq9U7xjfshp;
DELETE FROM ongeki_user_event_music
WHERE user_id IS NULL;
ALTER TABLE ongeki_user_event_music
MODIFY user_id BIGINT NOT NULL;
DELETE FROM ongeki_user_event_music
WHERE id NOT IN (
SELECT id
FROM (
SELECT MAX(id) AS id
FROM ongeki_user_event_music
GROUP BY user_id, event_id, type, music_id, level
) AS rows_to_keep
);
ALTER TABLE ongeki_user_event_music
ADD UNIQUE KEY uniq_ongeki_user_event_music (user_id, event_id, type, music_id, level);
@@ -0,0 +1,5 @@
ALTER TABLE ongeki_user_chapter
ADD COLUMN last_play_music_minor_category VARCHAR(255) NOT NULL DEFAULT '';
ALTER TABLE ongeki_user_story
ADD COLUMN last_play_music_minor_category VARCHAR(255) NOT NULL DEFAULT '';
@@ -0,0 +1,7 @@
-- new field wall option field
ALTER TABLE ongeki_user_option
ADD COLUMN field_wall INT NOT NULL DEFAULT 0;
-- varchar(255) not enough for event map storage
ALTER TABLE ongeki_user_event_map
MODIFY COLUMN map_data TEXT NULL;
+1 -1
View File
@@ -59,7 +59,7 @@ class Mai2Test : StringSpec({
liveTest("GetGameChargeApi") { liveTest("GetGameChargeApi") {
post("GetGameChargeApi", """{"isAll":false}""").let { (_, result) -> post("GetGameChargeApi", """{"isAll":false}""").let { (_, result) ->
result shouldBe """{"length":5,"gameChargeList":[{"chargeId":0,"orderId":0,"price":1,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":2,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":3,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":4,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":5,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"}]}""".jsonMap() result shouldBe """{"length":5,"gameChargeList":[{"chargeId":0,"orderId":0,"price":1,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":2,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":3,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":4,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":5,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"}],"gamePassChargeList":[]}""".jsonMap()
} }
} }