android: CRITICAL fix - commands were byte-reversed (ByteBuffer big-endian); official app sends little-endian, palindrome 66b masked it for 16 rounds

This commit is contained in:
ZXCLI
2026-09-10 02:39:19 +08:00
parent b29d7fd377
commit 623d62b641
3 changed files with 61 additions and 12 deletions
@@ -1,7 +1,5 @@
package com.mag160c.thermal.usb package com.mag160c.thermal.usb
import java.nio.ByteBuffer
/** /**
* Vendor command/response protocol — VERBATIM from the official MAG-Cx app's * Vendor command/response protocol — VERBATIM from the official MAG-Cx app's
* Java source (sdk/UsbCommunication.java + P2DCmd.java/D2PCmd.java, jadx). * Java source (sdk/UsbCommunication.java + P2DCmd.java/D2PCmd.java, jadx).
@@ -33,17 +31,25 @@ object MagProtocol {
const val RSP_SEND_CALI_FILE = 0x5BB5B55D const val RSP_SEND_CALI_FILE = 0x5BB5B55D
const val RSP_SEND_CALI_INFO = 0x5BB5B55E const val RSP_SEND_CALI_INFO = 0x5BB5B55E
fun cmd4(magic: Int): ByteArray { /**
val b = ByteBuffer.allocate(4) * 4-byte LITTLE-ENDIAN command — byte-identical to the official app's
b.putInt(magic) * GlobalFunc.intToByteArray (a&255 first). CRITICAL: ByteBuffer.putInt
return b.array() * defaults to BIG_ENDIAN and once reversed every non-palindrome magic
} * (66c/66f/670/672/673/674) — the camera only understood the palindrome
* 0x6BB6B66B, which masked this for 16 rounds.
*/
fun cmd4(magic: Int): ByteArray = byteArrayOf(
(magic and 0xFF).toByte(),
((magic shr 8) and 0xFF).toByte(),
((magic shr 16) and 0xFF).toByte(),
((magic shr 24) and 0xFF).toByte(),
)
/** 8-byte {u32 magic LE, u32 param LE} (official SetShutterState packet). */
fun cmd8(magic: Int, param: Int): ByteArray { fun cmd8(magic: Int, param: Int): ByteArray {
val b = ByteBuffer.allocate(8) val lo = cmd4(magic)
b.putInt(magic) val hi = cmd4(param)
b.putInt(param) return byteArrayOf(lo[0], lo[1], lo[2], lo[3], hi[0], hi[1], hi[2], hi[3])
return b.array()
} }
/** Camera info block (0x5BB5B55B, 0x38 bytes): +0x00 pid, +0x08 serial, /** Camera info block (0x5BB5B55B, 0x38 bytes): +0x00 pid, +0x08 serial,
@@ -0,0 +1,43 @@
package com.mag160c.thermal.usb
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* Wire-format regression lock: commands MUST be little-endian, byte-identical
* to the official MAG-Cx app (GlobalFunc.intToByteArray: a&255 goes first).
* ByteBuffer.putInt defaults to BIG_ENDIAN and once reversed every
* non-palindrome command magic — the camera answered only 0x6BB6B66B (a
* palindrome) which hid the bug for 16 rounds of black-screen debugging.
*/
class MagProtocolTest {
@Test
fun cmd4IsLittleEndian() {
// official: intToByteArray(1807136364) == {6C, B6, B6, 6B}
val b = MagProtocol.cmd4(0x6BB6B66C)
assertEquals(0x6C.toByte(), b[0])
assertEquals(0xB6.toByte(), b[1])
assertEquals(0xB6.toByte(), b[2])
assertEquals(0x6B.toByte(), b[3])
}
@Test
fun cmd8MatchesOfficialSetShutterStatePacket() {
// official setShutterState_(on): {72, B6, B6, 6B, on, on>>8, on>>16, on>>24}
val b = MagProtocol.cmd8(0x6BB6B672, 1)
assertEquals(0x72.toByte(), b[0])
assertEquals(0xB6.toByte(), b[1])
assertEquals(0xB6.toByte(), b[2])
assertEquals(0x6B.toByte(), b[3])
assertEquals(0x01.toByte(), b[4])
assertEquals(0x00.toByte(), b[5])
assertEquals(0x00.toByte(), b[6])
assertEquals(0x00.toByte(), b[7])
}
@Test
fun startTransferImgPacketMatchesOfficial() {
val b = MagProtocol.cmd4(0x6BB6B673)
assertEquals(listOf<Byte>(0x73, 0xB6.toByte(), 0xB6.toByte(), 0x6B), b.toList())
}
}
Binary file not shown.