Files
AquaDX/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt
2024-02-23 02:14:33 -05:00

234 lines
7.5 KiB
Kotlin

package icu.samnyan.aqua.sega.aimedb
import ext.toHex
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService
import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufUtil
import io.netty.buffer.Unpooled
import io.netty.channel.ChannelHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import java.nio.charset.StandardCharsets
import kotlin.jvm.optionals.getOrNull
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
@ChannelHandler.Sharable
class AimeDB(
val cardService: CardService
): ChannelInboundHandlerAdapter() {
val logger: Logger = LoggerFactory.getLogger(AimeDB::class.java)
data class AimeBaseInfo(val gameId: String, val keychipId: String)
fun getBaseInfo(input: ByteBuf) = AimeBaseInfo(
gameId = input.toString(0x0a, 0x0e - 0x0a, StandardCharsets.US_ASCII),
keychipId = input.toString(0x14, 0x1f - 0x14, StandardCharsets.US_ASCII)
)
data class Handler(val name: String, val fn: (ByteBuf) -> ByteBuf?)
final val handlers = mapOf(
0x01 to ::doFelicaLookup,
0x04 to ::doLookup,
0x05 to ::doRegister,
0x09 to ::doLog,
0x0b to ::doCampaign,
0x0d to ::doTouch,
0x0f to ::doLookupV2,
0x11 to ::doFelicaLookupV2,
0x13 to ::doUnknown19,
0x64 to ::doHello,
0x66 to ::doGoodbye
).map { (k, v) -> k to Handler(v.toString().substringBefore('(').substringAfterLast('.').substring(2), v) }.toMap()
/**
* Handle the incoming request
*/
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg is Map<*, *>) {
val type = msg["type"] as Int
val data = msg["data"] as ByteBuf
val base = getBaseInfo(data)
val handler = handlers[type] ?: let {
logger.error("AimeDB: Unknown request type 0x${type.toString(16)}")
ctx.flush()
return
}
logger.info("AimeDB /${handler.name} : (game ${base.gameId}, keychip ${base.keychipId})")
handler.fn(data)?.let { ctx.write(it) }
ctx.flush()
}
}
@Deprecated("Deprecated in Netty 5") // TODO: Move this to ChannelInboundHandler
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
logger.error("AimeDB: Error", cause)
ctx.close()
}
override fun channelInactive(ctx: ChannelHandlerContext) {
super.channelInactive(ctx)
logger.debug("AimeDB: Connection closed")
}
/**
* Felica Lookup v1: Return the Felica IDm as-is
*/
fun doFelicaLookup(msg: ByteBuf): ByteBuf {
val idm = msg.slice(0x20, 0x28 - 0x20).getLong(0)
val pmm = msg.slice(0x28, 0x30 - 0x28).getLong(0)
logger.info("> Felica Lookup v1 (idm ${idm.toHex()}, pmm ${pmm.toHex()})")
// Get the decimal represent of the hex value, same from minime
val accessCode = idm.toString().replace("-", "").padStart(20, '0')
logger.info("> Response: $accessCode")
return Unpooled.copiedBuffer(ByteArray(0x30)).apply {
setShortLE(0x04, 0x03)
setShortLE(0x08, 1)
setBytes(0x24, ByteBufUtil.decodeHexDump(accessCode))
}
}
/**
* Felica Lookup v2: Look up the card in the card repository, return the External ID
*/
fun doFelicaLookupV2(msg: ByteBuf): ByteBuf {
val idm = msg.slice(0x20, 0x28 - 0x20).getLong(0)
val pmm = msg.slice(0x28, 0x30 - 0x28).getLong(0)
logger.info("> Felica Lookup v2 (idm $idm, pmm $pmm)")
// Get the decimal represent of the hex value, same from minime
val accessCode = idm.toString().replace("-", "").padStart(20, '0')
val aimeId = cardService.getCardByAccessCode(accessCode).getOrNull()?.extId ?: -1
logger.info("> Response: $accessCode, $aimeId")
return Unpooled.copiedBuffer(ByteArray(0x0140)).apply {
setShortLE(0x04, 0x12)
setShortLE(0x08, 1)
setLongLE(0x20, aimeId)
setIntLE(0x24, -0x1) // 0xFFFFFFFF
setIntLE(0x28, -0x1) // 0xFFFFFFFF
setBytes(0x2c, ByteBufUtil.decodeHexDump(accessCode))
setShortLE(0x37, 0x01)
}
}
/**
* Lookup v1: Find the LUID in the database and return the External ID
*/
fun doLookup(msg: ByteBuf): ByteBuf {
val luid = ByteBufUtil.hexDump(msg.slice(0x20, 0x2a - 0x20))
logger.info("> Lookup v1 (luid $luid)")
val aimeId = cardService.getCardByAccessCode(luid).getOrNull()?.extId ?: -1
logger.info("> Response: $aimeId")
return Unpooled.copiedBuffer(ByteArray(0x0130)).apply {
setShortLE(0x04, 0x06)
setShortLE(0x08, 1)
setLongLE(0x20, aimeId)
setByte(0x24, 0)
}
}
fun doLookupV2(msg: ByteBuf): ByteBuf {
val luid = ByteBufUtil.hexDump(msg.slice(0x20, 0x2a - 0x20))
logger.info("> Lookup v2 (luid $luid)")
val aimeId = cardService.getCardByAccessCode(luid).getOrNull()?.extId ?: -1
logger.info("> Response: $aimeId")
return Unpooled.copiedBuffer(ByteArray(0x0130)).apply {
setShortLE(0x04, 0x10)
setShortLE(0x08, 1)
setLongLE(0x20, aimeId)
setByte(0x24, 0)
}
}
/**
* Register: Register a new card by access code
*/
fun doRegister(msg: ByteBuf): ByteBuf {
val luid = ByteBufUtil.hexDump(msg.slice(0x20, 0x2a - 0x20))
logger.info("> Register (luid $luid)")
var status = 0
var aimeId = 0L
if (cardService.getCardByAccessCode(luid).isEmpty) {
val card: Card = cardService.registerByAccessCode(luid)
status = 1
aimeId = card.extId
}
else logger.warn("> Duplicated Aime Card Register detected, access code: $luid")
logger.info("> Response: $status, $aimeId")
return Unpooled.copiedBuffer(ByteArray(0x30)).apply {
setShortLE(0x04, 0x06)
setShortLE(0x08, status)
setLongLE(0x20, aimeId)
}
}
/**
* Log: Just log the request and return a status 1
*/
fun doLog(msg: ByteBuf) = Unpooled.copiedBuffer(ByteArray(0x20)).apply {
setShortLE(0x04, 0x0a)
setShortLE(0x08, 1)
}
/**
* Campaign: Just return a status 1
*/
fun doCampaign(msg: ByteBuf) = Unpooled.copiedBuffer(ByteArray(0x0200)).apply {
setShortLE(0x04, 0x0c)
setShortLE(0x08, 1)
}
/**
* Touch: Just return a status 1
*/
fun doTouch(msg: ByteBuf): ByteBuf {
val luid = msg.getUnsignedIntLE(0x20)
logger.info("> Touch (luid $luid)")
return Unpooled.copiedBuffer(ByteArray(0x50)).apply {
setShortLE(0x04, 0x0e)
setShortLE(0x08, 1)
setShortLE(0x20, 0x6f)
setShortLE(0x24, 0x01)
}
}
/**
* We don't know what this is, just return a status 1
*/
fun doUnknown19(msg: ByteBuf) = Unpooled.copiedBuffer(ByteArray(0x40)).apply {
setShortLE(0x04, 0x14)
setShortLE(0x08, 1)
}
/**
* Ping: Just return a status 1
*/
fun doHello(msg: ByteBuf) = Unpooled.copiedBuffer(ByteArray(0x20)).apply {
setShortLE(0x04, 0x65)
setShortLE(0x08, 1)
}
fun doGoodbye(msg: ByteBuf) = null
}