mungewell / A5Voice

HydraSynth util to replace/alter the Waveforms in the 'A5Voice' segment
MIT License
5 stars 1 forks source link

What is the communication protocol? #2

Open mungewell opened 3 weeks ago

mungewell commented 3 weeks ago

Screenshot (24)

Screenshot (25)

short.pcapng.tar.gz

mungewell commented 3 weeks ago

Kinda looks like this....

:hello
host,1.13.2     15      0200 0500 01000000 2cb4 68656c6c6f
1.13.2,host     0
host,1.13.1     0
1.13.1,host     25      0200 0f00 01000000 2cb4 68656c6c6f2c6920616d204853334d
:hello,I am HS3M

get the data.... command, msg length, seq counter, ????, address, count
host,1.13.2     18      0206 0800 02000000 b6dd 00002040 f6010000
1.13.2,host     0       
host,1.13.1     0       
1.13.1,host     512     0206 f601 00000000 b6dd c202000070563412e0340...
chunk

tshark.txt

mungewell commented 3 weeks ago

Well I suspect that "????" is a 16bit checksum...

I can talk to keyboard (in Bootloader mode) via libUSB

$ sudo python3 hydra.py
00000000: 01 02 00 00 01 00 00 00  86 38                    .........8
None
00000000: 02 00 0F 00 01 00 00 00  2C B4 68 65 6C 6C 6F 2C  ........,.hello,
00000010: 69 20 61 6D 20 48 53 33  4D                       i am HS3M
None

But with checksum Zero-ed out...

$ sudo python3 hydra.py
00000000: 01 02 00 00 01 00 00 00  00 00                    ..........
None
00000000: 02 02 00 00 02 00 00 00  00 00                    ..........
None
mungewell commented 3 weeks ago

Oh well, that's nice... :-)

a=b"\x02\x00\x05\x00\x01\x00\x00\x00\x2c\xb4\x68\x65\x6c\x6c\x6f"
a=b"\x02\x00\x05\x00\x01\x00\x00\x00\x00\x01\x68\x65\x6c\x6c\x6f"

b=b"\x02\x06\x08\x00\x02\x00\x00\x00\xb6\xdd\x00\x00\x20\x40\xf6\x01\x00\x00"
b=b"\x02\x06\x08\x00\x02\x00\x00\x00\x00\x01\x00\x00\x20\x40\xf6\x01\x00\x00"
# Appears to be 'TCP' checksum!       ^^  ^^ preset checksum = 0x00 0x01

def carry_around_add(a, b):
    c = a + b
    return (c & 0xffff) + (c >> 16)

def checksum(msg):
    if len(msg) & 1:
        msg += b"\x00"
    s = 0
    for i in range(0, len(msg), 2):
        w = msg[i] + (msg[i+1] << 8)
        s = carry_around_add(s, w)
    return ~s & 0xffff

print("%4.4x" % checksum(a))
print("%4.4x" % checksum(b))

Which gives the correct answers

$ python3 checksum.py
2cb4
b6dd
mungewell commented 3 weeks ago

That 'checksum()` is not quiet right, I can receive a few packets (~1500 bytes), but fails at 'seq=5'

hello,i am HS3M
Writing data to file: test.bin
00000000: 02 06 08 00 02 00 00 00  B6 DD 00 00 20 40 F6 01  ............ @..
00000010: 00 00                                             ..
None
........
00000000: 02 06 08 00 03 00 00 00  B4 E6 F6 01 20 40 F6 01  ............ @..
00000010: 00 00                                             ..
None
........
00000000: 02 06 08 00 04 00 00 00  B2 EF EC 03 20 40 F6 01  ............ @..
00000010: 00 00                                             ..
None
........
00000000: 02 06 08 00 05 00 00 00  B0 F8 E2 05 20 40 F6 01  ............ @..
00000010: 00 00                                             ..
None
.Traceback (most recent call last):

Whereas the keyboard wants:

host,1.13.2     18      02060800 05000000 b1f8 e2052040 f6010000
mungewell commented 3 weeks ago
$ sudo python3 retrieve_voice.py -v -s 1506 test.bin
  CONFIGURATION 1: 30 mA ===================================
   bLength              :    0x9 (9 bytes)
   bDescriptorType      :    0x2 Configuration
   wTotalLength         :   0x20 (32 bytes)
   bNumInterfaces       :    0x1
   bConfigurationValue  :    0x1
   iConfiguration       :    0x0 
   bmAttributes         :   0x80 Bus Powered
   bMaxPower            :    0xf (30 mA)
    INTERFACE 0: Reserved ==================================
     bLength            :    0x9 (9 bytes)
     bDescriptorType    :    0x4 Interface
     bInterfaceNumber   :    0x0
     bAlternateSetting  :    0x0
     bNumEndpoints      :    0x2
     bInterfaceClass    :    0x0 Reserved
     bInterfaceSubClass :    0x0
     bInterfaceProtocol :    0x0
     iInterface         :    0x0 
      ENDPOINT 0x81: Bulk IN ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :   0x40 (64 bytes)
       bInterval        :    0x5
      ENDPOINT 0x2: Bulk OUT ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :    0x2 OUT
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :  0x200 (512 bytes)
       bInterval        :    0x5
hello,i am HS3M
Writing data to file: test.bin
........
........
........
bye

$ hexdump -C test.bin | head
00000000  c2 02 00 00 70 56 34 12  e0 34 06 00 dc 00 00 00  |....pV4..4......|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  89 00 00 00 42 02 00 00  43 02 00 00 44 02 00 00  |....B...C...D...|
00000030  45 02 00 00 46 02 00 00  47 02 00 00 48 02 00 00  |E...F...G...H...|
00000040  49 02 00 00 4a 02 00 00  4b 02 00 00 4c 02 00 00  |I...J...K...L...|
00000050  4d 02 00 00 4e 02 00 00  4f 02 00 00 50 02 00 00  |M...N...O...P...|
00000060  51 02 00 00 52 02 00 00  53 02 00 00 54 02 00 00  |Q...R...S...T...|
00000070  55 02 00 00 56 02 00 00  57 02 00 00 58 02 00 00  |U...V...W...X...|
00000080  59 02 00 00 5a 02 00 00  5b 02 00 00 5c 02 00 00  |Y...Z...[...\...|
00000090  5d 02 00 00 5e 02 00 00  5f 02 00 00 60 02 00 00  |]...^..._...`...|
mungewell commented 3 weeks ago

Short script to validate 'checksum()', currently fails on a couple of observed messages. checksum.py.txt

$ python3 checksum.py 
b'01030300000000008234627965'
Checksum failed, expected 0x86 0x38
b'02000500010000002cb468656c6c6f'
b'0206080002000000b6dd00002040f6010000'
b'0206080003000000b4e6f6012040f6010000'
b'0206080004000000b2efec032040f6010000'
b'0206080005000000b0f8e2052040f6010000'
Checksum failed, expected 0xb1 0xf8
b'0206080006000000af01d8072040f6010000'
b'0206080007000000ad0ace092040f6010000'
b'0206080008000000ab13c40b2040f6010000'
b'0206080009000000a91cba0d2040f6010000'
b'020608000a000000a8cbb00f204050000000'
b'020303000b0000008228627965'
mungewell commented 3 weeks ago

Saved a full (0x0160000 long) pcap and noticed some discontuities when analysing the data...

b"\x02\x06\x08\x00\x0e\x00\x00\x00\x9f\x49\x88\x17\x20\x40\xf6\x01\x00\x00",
b"\x02\x06\x08\x00\x0f\x00\x00\x00\x9d\x52\x7e\x19\x20\x40\xf6\x01\x00\x00",
b"\x02\x06\x08\x00\x00\x01\x00\x00\xc3\xc9\x14\xf2\x21\x40\xf6\x01\x00\x00",
                    ^^ ^^                           ^^     
b"\x02\x06\x08\x00\x0e\x01\x00\x00\xa8\x47\x88\x0d\x22\x40\xf6\x01\x00\x00",
b"\x02\x06\x08\x00\x0f\x01\x00\x00\xa6\x50\x7e\x0f\x22\x40\xf6\x01\x00\x00",
b"\x02\x06\x08\x00\x00\x02\x00\x00\xcc\xc7\x14\xe8\x23\x40\xf6\x01\x00\x00",

b"\x02\x06\x08\x00\x0e\x02\x00\x00\xb1\x45\x88\x03\x24\x40\xf6\x01\x00\x00",
b"\x02\x06\x08\x00\x0f\x02\x00\x00\xaf\x4e\x7e\x05\x24\x40\xf6\x01\x00\x00",
b"\x02\x06\x08\x00\x00\x03\x00\x00\xd5\xc5\x14\xde\x25\x40\xf6\x01\x00\x00",
etc...

Looking at the cheksums for all the reads. I managed to figure a 'fix' for a few more, but it still fails starting from:

Start: 0x00000000
0000:  0x00000206
0002:  0x00000a06
0004:  0x0000100f
0006:  0x0000100f
0008:  0x0000100f
0010:  0x0000e8bc
0012:  0x000119fc
0014:  0x00020ffd
0016:  0x00020ffd
End:   0x00021000 0x-00021001 0x0002efff
b'0206080006090000ffefd8ad3140f6010000'
Checksum failed, expected 0x00 0xf0
--
Start: 0x00000000
0000:  0x00000206
0002:  0x00000a06
0004:  0x00001114
0006:  0x00001114
0008:  0x00001114
0010:  0x0000df91
0012:  0x00011ad1
0014:  0x000210d2
0016:  0x000210d2
End:   0x000210d5 0x-000210D6 0x0002ef2a
b'02060800070e00002aefce7d3b40f6010000'
Checksum failed, expected 0x2b 0xef
--
...

checksum.py.txt

With these changes we can read 74296 Bytes, which is small chunk of the table but enough to decode the first few waveforms (ie should tell us a bit more about the Keyboard/Desktop/Delux).

mungewell commented 3 weeks ago

Spent way too long/many late nights on this... but here's some conclusions:

In pictorial form the init for the first 256 blocks (each of 502 byte), referenced to the 'sequence' number

0000 : 0000100000000000
1000 : 0000000000000111
2000 : 1100000000000000
3000 : 0000000111111100
4000 : 0000000000000000
5000 : 0111111111100000
6000 : 0000000000111111
7000 : 1111111000000000
8000 : 0000111111111111
9000 : 1111000000000111
a000 : 1111111111111111
b000 : 0000000111111111
c000 : 1111111111111000
d000 : 0111111111111111
e000 : 1111111110111111
f000 : 1111100000000000

In order to download the whole voice segment you'd need nearly 46,000 blocks - with the equation (or pseudo random sequence) known.... and I couldn't figure it out.

Then I released... pick one init value, use that until you get an error (ie no response from Keyboard, as a USB timeout) and then restart the connection (send "bye" and "hello" again), flip the init value and continue where you got to/left off.

SUCCESS, painfully slow due to the USB time-outs, but success all the same. It took close to an hour with the Explorer and would probably be longer for Keyboard/Desktop/Delux.

$ time sudo python3 retrieve_voice.py -v test.bin > test.log

real    58m51.587s
user    0m0.006s
sys 0m0.006s

Also note that you only have to do this once (and if you can't find the ASM dev tool), to get the factory waveforms. Once you have that file, you can patch with your own samples and upload as a '.dat' using the regular (non developer) tools from ASM.