mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-04 18:07:26 +08:00
[+] Chusan export tool
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
package icu.samnyan.aqua.sega.aimedb
|
package icu.samnyan.aqua.sega.aimedb
|
||||||
|
|
||||||
import icu.samnyan.aqua.sega.aimedb.AimeDbClient.Companion.sendAimePacket
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.buffer.ByteBufUtil
|
import io.netty.buffer.ByteBufUtil
|
||||||
import io.netty.buffer.Unpooled
|
import io.netty.buffer.Unpooled
|
||||||
@@ -55,6 +54,6 @@ class AimeDbClient(val gameId: String, val keychipShort: String) {
|
|||||||
= Unpooled.wrappedBuffer(Socket(server, 22345).use {
|
= Unpooled.wrappedBuffer(Socket(server, 22345).use {
|
||||||
it.getOutputStream().write(array())
|
it.getOutputStream().write(array())
|
||||||
it.getInputStream().readBytes()
|
it.getInputStream().readBytes()
|
||||||
})
|
}).let { AimeDbEncryption.decrypt(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
package icu.samnyan.aqua.sega.chusan
|
package icu.samnyan.aqua.sega.chusan
|
||||||
|
|
||||||
import ext.header
|
import ext.*
|
||||||
import ext.post
|
import icu.samnyan.aqua.sega.aimedb.AimeDbClient
|
||||||
import ext.request
|
import icu.samnyan.aqua.sega.aimedb.AimeDbClient.Companion.sendAimePacket
|
||||||
import icu.samnyan.aqua.api.model.resp.sega.chuni.v2.external.Chu3DataExport
|
import icu.samnyan.aqua.sega.chusan.model.request.UpsertUserAll
|
||||||
|
import icu.samnyan.aqua.sega.chusan.model.userdata.UserActivity
|
||||||
|
import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem
|
||||||
|
import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail
|
||||||
import icu.samnyan.aqua.sega.util.AllNetBillingDecoder
|
import icu.samnyan.aqua.sega.util.AllNetBillingDecoder
|
||||||
|
import icu.samnyan.aqua.sega.util.jackson.StringMapper
|
||||||
|
import lombok.AllArgsConstructor
|
||||||
|
import lombok.Data
|
||||||
|
import lombok.NoArgsConstructor
|
||||||
|
|
||||||
|
|
||||||
val keychipPattern = Regex("([A-Z\\d]{4}-[A-Z\\d]{11}|[A-Z\\d]{11})")
|
val keychipPattern = Regex("([A-Z\\d]{4}-[A-Z\\d]{11}|[A-Z\\d]{11})")
|
||||||
@@ -16,11 +23,12 @@ class AllNetHost(val dns: String, val keychip: String, val game: String, val ver
|
|||||||
if (!keychipPattern.matches(keychip)) throw Exception("Invalid keychip")
|
if (!keychipPattern.matches(keychip)) throw Exception("Invalid keychip")
|
||||||
}
|
}
|
||||||
|
|
||||||
val requestKeychip by lazy {
|
val keychipShort by lazy {
|
||||||
// A123-45678901337 -> A1234567890
|
// A123-45678901337 -> A1234567890
|
||||||
if (keychip.length == 11) keychip
|
if (keychip.length == 11) keychip
|
||||||
else keychip.substring(0, 4) + keychip.substring(5, 11)
|
else keychip.substring(0, 4) + keychip.substring(5, 11)
|
||||||
}
|
}
|
||||||
|
val aime by lazy { AimeDbClient(game, keychipShort) }
|
||||||
|
|
||||||
// Send AllNet PowerOn request to obtain game URL
|
// Send AllNet PowerOn request to obtain game URL
|
||||||
val gameUrl by lazy {
|
val gameUrl by lazy {
|
||||||
@@ -29,25 +37,77 @@ class AllNetHost(val dns: String, val keychip: String, val game: String, val ver
|
|||||||
.post(AllNetBillingDecoder.encodeAllNet(mapOf(
|
.post(AllNetBillingDecoder.encodeAllNet(mapOf(
|
||||||
"game_id" to game,
|
"game_id" to game,
|
||||||
"ver" to version,
|
"ver" to version,
|
||||||
"serial" to requestKeychip,
|
"serial" to keychipShort,
|
||||||
"ip" to "127.0.0.1", "firm_ver" to "60001", "boot_ver" to "0000",
|
"ip" to "127.0.0.1", "firm_ver" to "60001", "boot_ver" to "0000",
|
||||||
"encode" to "UTF-8", "format_ver" to "3", "hops" to "1", "token" to "2864179931"
|
"encode" to "UTF-8", "format_ver" to "3", "hops" to "1", "token" to "2864179931"
|
||||||
)))
|
)))
|
||||||
.body()
|
.bodyString()
|
||||||
.split("&")
|
.split("&")
|
||||||
.map { it.split("=") }
|
.map { it.split("=") }
|
||||||
.filter { it.size == 2 }
|
.filter { it.size == 2 }
|
||||||
.associate { it[0] to it[1] }["uri"]
|
.associate { it[0] to it[1] }["uri"]
|
||||||
?: throw Exception("PowerOn Failed: No game URL returned")
|
?: throw Exception("PowerOn Failed: No game URL returned")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
class ChusanDataBroker {
|
|
||||||
|
|
||||||
fun pull(host: AllNetHost): Chu3DataExport {
|
val userId by lazy {
|
||||||
// Send AllNet PowerOn request to obtain game URL
|
when (card.length) {
|
||||||
|
20 -> aime.createReqLookupV2(card)
|
||||||
return Chu3DataExport()
|
16 -> aime.createReqFelicaLookupV2(card)
|
||||||
|
else -> throw Exception("Invalid card. Please input either 20-digit numeric access code (e.g. 5010000...0) or 16-digit hex Felica ID (e.g. 012E123456789ABC).")
|
||||||
|
}.sendAimePacket(dns.substringAfter("://")).getLongLE(0x20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserMusicWrapper {
|
||||||
|
var userMusicDetailList: List<UserMusicDetail> = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChusanDataBroker(val host: AllNetHost) {
|
||||||
|
val mapper = StringMapper()
|
||||||
|
val url by lazy { "${host.gameUrl.ensureEndingSlash()}ChuniServlet" }
|
||||||
|
val log = logger()
|
||||||
|
|
||||||
|
inline fun <reified T> String.get(key: String, data: JDict) = "$url/$this".request()
|
||||||
|
.postZ(mapper.write(data))
|
||||||
|
.bodyMaybeZ()
|
||||||
|
.jsonMap()[key]
|
||||||
|
.let { mapper.convert<T>(it) }
|
||||||
|
.also {
|
||||||
|
if (it is List<*>) log.info("✅ $this: ${it.size}")
|
||||||
|
else log.info("✅ $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pull(): String {
|
||||||
|
log.info("Game URL: ${host.gameUrl}")
|
||||||
|
log.info("User ID: ${host.userId}")
|
||||||
|
|
||||||
|
val userId = mapOf("userId" to host.userId)
|
||||||
|
val paged = userId + mapOf("nextIndex" to 0, "maxCount" to 10000000)
|
||||||
|
|
||||||
|
return mapper.write(UpsertUserAll().apply {
|
||||||
|
userData = ls("GetUserDataApi".get("userData", userId))
|
||||||
|
userGameOption = ls("GetUserOptionApi".get("userGameOption", userId))
|
||||||
|
userCharacterList = "GetUserCharacterApi".get("userCharacterList", paged)
|
||||||
|
userActivityList = (1..5).flatMap {
|
||||||
|
"GetUserActivityApi".get<List<UserActivity>>("userActivityList", userId + mapOf("kind" to it))
|
||||||
|
}
|
||||||
|
userItemList = (1..12).flatMap {
|
||||||
|
"GetUserItemApi".get<List<UserItem>>(
|
||||||
|
"userItemList",
|
||||||
|
userId + mapOf("nextIndex" to 10000000000 * it, "maxCount" to 10000000)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
userRecentRatingList = "GetUserRecentRatingApi".get("userRecentRatingList", userId)
|
||||||
|
userMusicDetailList = "GetUserMusicApi".get<List<UserMusicWrapper>>("userMusicList", paged)
|
||||||
|
.flatMap { it.userMusicDetailList }
|
||||||
|
userCourseList = "GetUserCourseApi".get("userCourseList", paged)
|
||||||
|
userFavoriteMusicList = "GetUserFavoriteItemApi".get("userFavoriteItemList", paged + mapOf("kind" to 1))
|
||||||
|
// TODO userMapAreaList = "GetUserMapAreaApi"
|
||||||
|
// TODO userNetBattleData = ls("GetUserNetBattleDataApi".get("userNetBattleData", userId))
|
||||||
|
userUnlockChallengeList = "GetUserUCApi".get("userUnlockChallengeList", userId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user