diff --git a/src/main/java/ext/Ext.kt b/src/main/java/ext/Ext.kt index c7278dcc..b2b10881 100644 --- a/src/main/java/ext/Ext.kt +++ b/src/main/java/ext/Ext.kt @@ -192,8 +192,7 @@ val Any?.str get() = toString() // Collections fun ls(vararg args: T) = args.toList() inline fun arr(vararg args: T) = arrayOf(*args) -operator fun Map.plus(map: Map) = - (if (this is MutableMap) this else mut).apply { putAll(map) } +operator fun Map.plus(map: Map) = mut.apply { putAll(map) } operator fun MutableMap.plusAssign(map: Map) { putAll(map) } fun Map.vNotNull(): Map = filterValues { it != null }.mapValues { it.value!! } fun MutableList.popAll(list: List) = list.also { removeAll(it) } diff --git a/src/main/java/ext/Http.kt b/src/main/java/ext/Http.kt index 8271ed4f..83016a3d 100644 --- a/src/main/java/ext/Http.kt +++ b/src/main/java/ext/Http.kt @@ -10,6 +10,7 @@ val client = HttpClient.newBuilder().build() fun HttpRequest.Builder.send() = client.send(this.build(), HttpResponse.BodyHandlers.ofString()) fun HttpRequest.Builder.header(pair: Pair) = this.header(pair.first.toString(), pair.second.toString()) fun String.request() = HttpRequest.newBuilder(URI.create(this)) +inline fun String.postJson(body: Any) = request().post(body).json() fun HttpRequest.Builder.post(body: Any? = null) = this.POST(when (body) { is ByteArray -> HttpRequest.BodyPublishers.ofByteArray(body) @@ -17,3 +18,6 @@ fun HttpRequest.Builder.post(body: Any? = null) = this.POST(when (body) { is HttpRequest.BodyPublisher -> body else -> throw Exception("Unsupported body type") }).send() + +inline fun HttpResponse.json(): T = body().json() + diff --git a/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt index 1dded556..157f6301 100644 --- a/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt +++ b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt @@ -12,8 +12,6 @@ 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 java.time.LocalDateTime @@ -127,8 +125,7 @@ class AimeDB( */ fun doFelicaLookupV2(msg: ByteBuf): ByteBuf { val idm = msg.slice(0x30, 0x38 - 0x30).getLong(0) - val dfc = msg.slice(0x38, 0x40 - 0x38).getLong(0) - logger.info("> Felica Lookup v2 (idm $idm, dfc $dfc)") + logger.info("> Felica Lookup v2 (idm $idm)") // Get the decimal represent of the hex value, same from minime val accessCode = idm.toString().replace("-", "").padStart(20, '0') diff --git a/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDbClient.kt b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDbClient.kt new file mode 100644 index 00000000..603d4250 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDbClient.kt @@ -0,0 +1,60 @@ +package icu.samnyan.aqua.sega.aimedb + +import icu.samnyan.aqua.sega.aimedb.AimeDbClient.Companion.sendAimePacket +import io.netty.buffer.ByteBuf +import io.netty.buffer.ByteBufUtil +import io.netty.buffer.Unpooled +import java.net.Socket + +class AimeDbClient(val gameId: String, val keychipShort: String) { + // https://sega.bsnk.me/allnet/aimedb/common/#packet-header + fun createRequest(type: UShort, writer: ByteBuf.() -> Unit): ByteBuf + = AimeDbEncryption.encrypt(Unpooled.buffer(1024).clear().run { + writeShortLE(0xa13e) // 00 2b: Magic + writeShortLE(0x3087) // 02 2b: Version + writeShortLE(type.toInt()) // 04 2b: Type + writeShortLE(0) // 06 2b: Length + writeShortLE(0) // 08 2b: Result + writeAscii(gameId, 6) // 0A 6b: Game ID + writeIntLE(0) // 10 4b: Store ID (Place ID) + writeAscii(keychipShort, 12) // 14 12b: Keychip ID + writer() // Write Payload + setShortLE(6, writerIndex()) // Update Length + copy(0, writerIndex()) // Trim unused bytes + }) + + private fun ByteBuf.writeAscii(value: String, length: Int) + = writeBytes(value.toByteArray(Charsets.US_ASCII).copyOf(length)) + + fun createReqLookupV2(accessCode: String) + = createRequest(0x0fu) { + // Access code is a 20-digit number, should be converted to a 10-byte array + writeBytes(ByteBufUtil.decodeHexDump(accessCode.padStart(20, '0'))) + writeByte(0) // 0A 1b: Company code + writeByte(0) // 0B 1b: R/W Firmware version + writeIntLE(0) // 0C 4b: Serial number + } + + fun createReqFelicaLookupV2(felicaIdm: String) + = createRequest(0x11u) { + writeBytes(ByteArray(16)) // 00 16b: Random Challenge + // 10 8b: Felica IDm + writeBytes(ByteBufUtil.decodeHexDump(felicaIdm.padStart(16, '0'))) + writeBytes(ByteArray(8)) // 18 8b: Felica PMm + writeBytes(ByteArray(16)) // 20 16b: Card key version + writeBytes(ByteArray(16)) // 30 16b: Write count + writeBytes(ByteArray(8)) // 40 8b: MACA + writeByte(0) // 48 1b: Company code + writeByte(0) // 49 1b: R/W Firmware version + writeShortLE(0) // 4A 2b: DFC + writeIntLE(0) // 4C 4b: Unknown padding + } + + companion object { + fun ByteBuf.sendAimePacket(server: String): ByteBuf + = Unpooled.wrappedBuffer(Socket(server, 22345).use { + it.getOutputStream().write(array()) + it.getInputStream().readBytes() + }) + } +}