working redeem code generator

This commit is contained in:
minjaesong
2025-02-15 23:25:30 +09:00
parent 4aae0bf733
commit ad68e04b83
2 changed files with 52 additions and 24 deletions

View File

@@ -4,6 +4,7 @@ import javazoom.jl.decoder.Crc16
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.serialise.toBig64 import net.torvald.terrarum.serialise.toBig64
import net.torvald.terrarum.serialise.toUint import net.torvald.terrarum.serialise.toUint
import net.torvald.terrarum.toHex
import net.torvald.terrarum.toInt import net.torvald.terrarum.toInt
import net.torvald.terrarum.utils.PasswordBase32 import net.torvald.terrarum.utils.PasswordBase32
import net.torvald.unicode.CURRENCY import net.torvald.unicode.CURRENCY
@@ -46,9 +47,9 @@ object RedeemCodeMachine {
"umonataj kunvenauw, sed nature a" "umonataj kunvenauw, sed nature a"
).map { MessageDigest.getInstance("SHA-256").digest(it.toByteArray()) } ).map { MessageDigest.getInstance("SHA-256").digest(it.toByteArray()) }
fun encode(itemID: ItemID, amountIndex: Int, isUnique: Boolean, receiver: UUID? = null, msgType: Int = 0, args: String = ""): String { fun encode(itemID: ItemID, amountIndex: Int, isReusable: Boolean, receiver: UUID? = null, msgType: Int = 0, args: String = ""): String {
// filter item ID // filter item ID
val itemType = itemID.substringBefore("@") val itemType = if (itemID.contains('@')) itemID.substringBefore("@") else ""
val (itemModule, itemNumber0) = itemID.substringAfter("@").split(":") val (itemModule, itemNumber0) = itemID.substringAfter("@").split(":")
val itemNumber = itemNumber0.toInt() val itemNumber = itemNumber0.toInt()
@@ -59,12 +60,14 @@ object RedeemCodeMachine {
if (itemNumber !in 0..65535) if (itemNumber !in 0..65535)
throw IllegalArgumentException("Unsupported item number for ItemID: $itemID") throw IllegalArgumentException("Unsupported item number for ItemID: $itemID")
val isShortCode = true val unpaddedStr = asciiToBaudot(args)
val bytes = ByteArray(isShortCode.toInt().plus(1) * 15) val isShortCode = (unpaddedStr.length <= 60)
val bytes = ByteArray(if (isShortCode) 15 else 30)
// sync pattern and flags // sync pattern and flags
bytes[0] = (isUnique.toInt() or 0xA4).toByte() bytes[0] = (isReusable.toInt() or 0xA4).toByte()
bytes[1] = 0xA5.toByte() bytes[1] = 0xA5.toByte()
// compressed item name // compressed item name
// 0b nnnn mm cc // 0b nnnn mm cc
@@ -73,11 +76,12 @@ object RedeemCodeMachine {
bytes[4] = itemNumber.ushr(8).toByte()// 0b item number high bytes[4] = itemNumber.ushr(8).toByte()// 0b item number high
// convert ascii to baudot // convert ascii to baudot
val paddedTextBits = (asciiToBaudot(args) + "00000").let { // always end the message with NUL val paddedTextBits = (unpaddedStr).let {
// then fill the remainder with random bits // then fill the remainder with random bits
val remaining = (if (isShortCode) 60 else 180) - it.length val remaining = (if (isShortCode) 60 else 180) - it.length
it + StringBuilder().let { sb -> it + StringBuilder().also { sb ->
repeat(remaining) { sb.append((Math.random() < 0.5).toInt()) } sb.append("00000") // add null terminator
repeat(remaining - 5) { sb.append((Math.random() < 0.5).toInt()) } // add random bits
}.toString() }.toString()
} }
@@ -102,7 +106,7 @@ object RedeemCodeMachine {
bytes[bytes.size - 1] = crc16.toByte() bytes[bytes.size - 1] = crc16.toByte()
val basePwd = initialPassword[bytes[bytes.size - 1].toUint().shr(2).and(7)] val basePwd = initialPassword.random()
val receiverPwd = receiver?.toByteArray() ?: ByteArray(16) // 128 bits of something val receiverPwd = receiver?.toByteArray() ?: ByteArray(16) // 128 bits of something
// xor basePWD with receiverPwd // xor basePWD with receiverPwd
@@ -120,30 +124,39 @@ object RedeemCodeMachine {
fun decode(codeStr: String, decoderUUID: UUID? = null): RedeemVoucher? { fun decode(codeStr: String, decoderUUID: UUID? = null): RedeemVoucher? {
val receiverPwd = decoderUUID?.toByteArray() ?: ByteArray(16) // 128 bits of something val receiverPwd = decoderUUID?.toByteArray() ?: ByteArray(16) // 128 bits of something
// 0x [byte 29] [byte 30] val passwords = initialPassword.map { basePwd ->
val crc = PasswordBase32.decode(codeStr.substring(codeStr.length - 8), 5).let {
it[3].toUint().shl(8) or it[4].toUint()
}
val basePwdIndex = crc.shr(2).and(7)
val password = initialPassword[basePwdIndex].let { basePwd ->
ByteArray(32) { i -> ByteArray(32) { i ->
basePwd[i] xor receiverPwd[i % 16] basePwd[i] xor receiverPwd[i % 16]
} }
} }
// try to decode the input string // try to decode the input string by just trying all 8 possible keys
val decoded = PasswordBase32.decode(codeStr, if (codeStr.length > 24) 30 else 15, password) val decodeds = passwords.map {
PasswordBase32.decode(codeStr, if (codeStr.length > 24) 30 else 15, it)
}
// check CRC // check which one of the 8 keys passes CRC test
val crc2 = decoded[decoded.size - 2].toUint().shl(8) or decoded[decoded.size - 1].toUint() val crcResults = decodeds.map { decoded ->
val crc = Crc16().let {
for (i in 0 until decoded.size - 2) {
it.add_bits(decoded[i].toInt(), 8)
}
it.checksum().toInt().and(0xFFFF)
}
val crc2 = decoded[decoded.size - 2].toUint().shl(8) or decoded[decoded.size - 1].toUint()
// if CRC fails... (crc == crc2)
if (crc != crc2) }
// if all CRC fails...
if (crcResults.none()) {
return null return null
}
val decoded = decodeds[crcResults.indexOf(true)]
val reusable = (decoded[0] and 1) != 0.toByte() val reusable = (decoded[0] and 1) != 0.toByte()
val itemCategory = itemTypeTableReverse[decoded[2].toUint() and 3]!! val itemCategory = itemTypeTableReverse[decoded[2].toUint() and 3]!!
@@ -156,7 +169,7 @@ object RedeemCodeMachine {
val messageTemplateIndex = decoded[5].toUint() and 15 val messageTemplateIndex = decoded[5].toUint() and 15
// baudot to ascii // baudot to ascii
val baudotBits = StringBuilder().let { val baudotBits = StringBuilder().also {
it.append(decoded[5].toUint().ushr(4).toString(2).padStart(4,'0')) it.append(decoded[5].toUint().ushr(4).toString(2).padStart(4,'0'))
for (i in 6 until decoded.size - 2) { for (i in 6 until decoded.size - 2) {
it.append(decoded[i].toUint().toString(2).padStart(8,'0')) it.append(decoded[i].toUint().toString(2).padStart(8,'0'))

View File

@@ -0,0 +1,15 @@
import net.torvald.terrarum.modulebasegame.redeemable.RedeemCodeMachine
fun main() {
val code = RedeemCodeMachine.encode(
"item@basegame:65511",
6,
true
)
println(code)
val voucher = RedeemCodeMachine.decode(code)
println(voucher)
}