mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 19:44:05 +09:00
working redeem code generator
This commit is contained in:
@@ -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'))
|
||||||
|
|||||||
15
src/net/torvald/terrarum/tests/RedeemCodeGenTest.kt
Normal file
15
src/net/torvald/terrarum/tests/RedeemCodeGenTest.kt
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user