Decoding the ImageWriter II/LQ LocalTalk Option card NVSRAM chip

eric

Administrator
Staff member
Sep 2, 2021
1,016
1,674
113
MN
bluescsi.com
As some of you know @polpo is RE the Image Writer Apple Talk option card. I was in a chat with him and he shared a dump of a 32 byte NVSRAM chip on the board. It appeared to contain the contents of the printers name on the Appletalk/LocalTalk network - but was in no discernible format.

1738332195477.png

This contains the String "Golden Software" - can you see it? We couldn't at first...

Many random attempts were had. Was it a Pascal string? No that would still overlap with ASCII except for the first char being the len.
Was it a different encoding? little endien or big? etc etc. Nothing was making sense.

I threw it into a few AI models to see if they saw pattern, no dice.

Our first break was talking while Joe from JCM was there - he mentioned that Apple II's stored their text with bit flags such as bit 8 denoting end of string. This _almost_ made sense, except for another example hex dump that flag was set at the 31st bit on a 32 bit string.. which was odd. This also changed our perspective from Mac centric strings to Apple II (as the ImageWriter can be used on an Apple II as well)

Nothing was making sense - no patterns emerging. We even dumped the ROM and found the name of the Apple employee in 1984 who wrote this and @tom_B was poking around at Linkedin/etc to see if they were still around.

Then polpo had a realization:
here's something I just realized about this NVSRAM chip - it's organized as 16x16 so writes are done 16 bits at a time. and I think they're written little endian. so A is 82 and B is 42, and "AB" gets written to a cell as "4282"

This was the break needed! With those two pieces of information we can now decode. The rules are

Bit 8 denotes a end of string
Bit 7 is always 1
Bit 6 is a lower case flag
Bits 1-5 are the character data offset from asci ord(a|A) - 1

Decoded ASCII String: Golden Software

Success!


To decode we have to take the hex, flip every other bytes position, then decode with those rules. I made a very quick and dirty python script in ~30 minutes.

Useful? No. An interesting puzzle to spend a few hours on, for sure. And now we know how the network name is stored on the LocalTalk option card.

Python:
def bit_reverse(i, n):
    return int(format(i, '0%db' % n)[::-1], 2)

def hex_to_ascii(hex_input):
    byte_pairs = [hex_input[i:i + 2] for i in range(0, len(hex_input), 2)]

    sorted_bytes = []
    i = 0
    while i < len(byte_pairs):
        sorted_bytes.append(byte_pairs[i + 1])
        sorted_bytes.append(byte_pairs[i])
        i = i + 2
    result = []

    for byte_pair in sorted_bytes:
        byte = int(byte_pair, 16)
        print(f'h: {byte_pair} - {byte:b}')
        end_of_string = (byte >> 0) & 1
        lowercase_flag = (byte >> 2) & 1
        numeric_flag = (byte >> 3) & 1
        char_offset = byte >> 3
        char_offset = bit_reverse(char_offset, 5)

        if char_offset == 0:
            char = " " # special case, space
        elif lowercase_flag:
            char = chr(ord('a') + char_offset - 1)
        else:
            char = chr(ord('A') + char_offset - 1)
        # TODO: numbers, other mac roman stuff

        result.append(char)
        if end_of_string:
            break
    return ''.join(result)

hex_input = "F6E2263676A6CA0466F6EE2E4E8604A7"
ascii_output = hex_to_ascii(hex_input)

print("Decoded ASCII String:", ascii_output)
 
Last edited:

NJRoadfan

New Tinkerer
Feb 6, 2022
30
9
8
Hopefully this results in the terribly broken firmware in these devices getting fixed. There were rumors that a later revision existed to fix the problems, but it hasn't shown up.
 

David Cook

Tinkerer
Jul 20, 2023
33
34
18
Bit 8 denotes a end of string
Bit 7 is always 1
Bit 6 is a lower case flag
Bit 5 is a numeric flag
Bits 1-4 are the character data offset from asci ord(a|A) - 1

Very cool detective work. However, I don't quite understand the bit pattern. In your post, you say the char data offset is bits 1-4. That only allows for 16 characters. in your code, you seem to be using bits 1-5 (aka 0-4) which would provide 32 characters (minus 1 for the special case space), which is plenty.

Is bit 5 really a numeric flag?
 

eric

Administrator
Staff member
Sep 2, 2021
1,016
1,674
113
MN
bluescsi.com
Ahh that was a copy/paste error from an earlier doc i was working on, python code is right. I'm not 100% sure about how numeric is handled yet.

If you're interested and want to add in numeric here's a dump of numbers
Code:
$ hexdump 012345678987654321.bin
0000000 0c8c 4ccc 2cac 6cec 1c9c 1cec 6cac 2ccc
0000010 4c8c 0d42 4242 4242 4242 4242 4242 4243
 

David Cook

Tinkerer
Jul 20, 2023
33
34
18
I have good news, but also something weird that probably has a simpler solution.

First, after reversing the bit order for LSB/MSB, the data is just the lower 128 characters of ASCII. Nothing fancy. Just mask out the top bit (end of string bit) and print the character. You don't need special cases for lowercase, numeric, etc.

it's organized as 16x16 so writes are done 16 bits at a time. and I think they're written little endia

This is your breakthrough. If you first account for this, everything becomes easy
so A is 82 and B is 42, and "AB" gets written to a cell as "4282"
Nope. A is ASCII 0x41 and B is ASCII 0x42. Which is 0x4142.

Flip the bit order in writing to the chip becomes:
0x4142 = 0100 0001 0100 0010
0x4282 = 0100 0010 1000 0010

So, to decode the NVRAM, you simply need to flip every 16 bits in place, and print out the individual bytes with a 0x7F mask (to remove the end of string bit).

012345678987654321.bin

I can't tell if you are tricking me ;) or if there is one more twist to the algorithm. First, I think you forgot the 0 at the end of the filename. It actually contains 0123456789876543210.bin, correct?

Secondly, did you use the official code to set the name of the ImageWriter printer to 0123456789876543210? Because for whatever reason every other byte of this NRVAM data is not swapped.

The following code works for the two examples you've provided.

void ImageWriterNameDecode(char* hexCStringPtr) { #define ImageWriterNVRAMSizeInBytes 32 byte nvram[ImageWriterNVRAMSizeInBytes]; long dataLength = ByteBufferFromHexCString(nvram, ImageWriterNVRAMSizeInBytes, hexCStringPtr, true); short index; printf("Source data: \"%s\"\n", hexCStringPtr); printf("Raw data: "); PrintHexData(nvram, dataLength); { byte* nvramPtr = nvram; byte* nvramEndPtr = nvram + dataLength - 1; while(nvramPtr <= nvramEndPtr) { byte firstByte = Reverse8Bits(*nvramPtr); byte secondByte = Reverse8Bits(*(nvramPtr+1)); if (((firstByte & 0x40) || (secondByte & 0x40)) && (firstByte & 0x80) == 0 && nvramPtr != nvramEndPtr) { byte swap = firstByte; firstByte = secondByte; secondByte = swap; } *nvramPtr++ = firstByte; *nvramPtr++ = secondByte; } } printf("MSB data: "); PrintHexData(nvram, dataLength); printf("ASCII data: "); for (index = 0; index < dataLength; index++) { byte value = nvram[index]; printf("%c", value & 0x7F); if (value & 0x80) { break; } } printf("\n"); }

Source data: "F6E2263676A6CA0466F6EE2E4E8604A7"
Raw data: 16 [F6 E2 26 36 76 A6 CA 04 66 F6 EE 2E 4E 86 04 A7]
MSB data: 16 [47 6F 6C 64 65 6E 20 53 6F 66 74 77 61 72 E5 20]
ASCII data: Golden Software


Source data: "0c8c4ccc2cac6cec1c9c1cec6cac2ccc4c8c0d"
Raw data: 19 [0C 8C 4C CC 2C AC 6C EC 1C 9C 1C EC 6C AC 2C CC 4C 8C 0D]
MSB data: 19 [30 31 32 33 34 35 36 37 38 39 38 37 36 35 34 33 32 31 B0]
ASCII data: 0123456789876543210
 
  • Like
Reactions: Trash80toG4

eric

Administrator
Staff member
Sep 2, 2021
1,016
1,674
113
MN
bluescsi.com
Nope. A is ASCII 0x41 and B is ASCII 0x42. Which is 0x4142.
This is not what is in the example data though, it is 4282.
Code:
$ hexdump X24C44-alphabet.bin
0000000 *8242* c222 a262 e212 9252 d232 b272 f20a
0000010 8646 c626 a666 e616 9656 d636 b676 f60f

I can't tell if you are tricking me
I am just passing along the data as I received it as I don't have this chip/reader. Maybe a typo, after I got the process for alpha chars I did not proceed further.

If it's that simple, great - but you can see how focusing on the Apple II encoding and it (oddly) matching this pattern worked. Kinda neat it overlapped.
 

David Cook

Tinkerer
Jul 20, 2023
33
34
18
Kinda neat it overlapped.

Yes. Agreed.

All three examples print as straight 7-bit ascii after having the bits reversed in each byte. However, the first example "Golden Software" needs its byte pairs swapped as well. I don't know if that got written using a different process.

In any case, cool work on REing the NVRAM.
 

polpo

New Tinkerer
Oct 30, 2021
15
17
3
Yes. Agreed.

All three examples print as straight 7-bit ascii after having the bits reversed in each byte. However, the first example "Golden Software" needs its byte pairs swapped as well. I don't know if that got written using a different process.

In any case, cool work on REing the NVRAM.
All of the dumps I've made from the chip need their byte pairs swapped. Eric's output of running hexdump on 012345678987654321.bin shows the bytes swapped because that's what hexdump does by default (!). And it's my fault about the naming of 012345678987654321.bin - you're right, there's an extra 0 at the end of the name.

Here's a simple python program to decode a dump:
Python:
#!/usr/bin/env python3

import sys

def decode_lt(filename):
    string = ''
    with open(filename, 'rb') as f:
        while chars := f.read(2):
            for char in reversed(chars):
                revchar = int('{:08b}'.format(char)[::-1], 2)
                string += chr(revchar & 0x7f)
                if revchar & 0x80:
                    return string

if __name__ == '__main__':
    print(decode_lt(sys.argv[1]))

And here's the output on those bin files:
Code:
ian@MBP-2022 tmp % xxd X24C44-Golden\ Software.bin
00000000: f6e2 2636 76a6 ca04 66f6 ee2e 4e86 04a7  ..&6v...f...N...
00000010: 0404 0404 0404 0404 ac04 8cf4 1cf4 5aed  ..............Z.
ian@MBP-2022 tmp % ./decode_lt_option.py X24C44-Golden\ Software.bin
Golden Software
ian@MBP-2022 tmp % xxd X24C44-012345678987654321.bin
00000000: 8c0c cc4c ac2c ec6c 9c1c ec1c ac6c cc2c  ...L.,.l.....l.,
00000010: 8c4c 420d 4242 4242 4242 4242 4242 4342  .LB.BBBBBBBBBBCB
ian@MBP-2022 tmp % ./decode_lt_option.py X24C44-012345678987654321.bin
0123456789876543210
 
Last edited:

David Cook

Tinkerer
Jul 20, 2023
33
34
18
All of the dumps I've made from the chip need their byte pairs swapped.

Thank you for the follow up. This makes a lot of sense, since the chip is written to 16-bits at a time. To reverse the process, either flip 16-bits at a time, or flip the bits of each byte and flip byte pairs.

That Python code is nice and simple.
 

croissantking

Tinkerer
Feb 7, 2023
94
43
18
Just to say, as an IWII owner I’m very excited to see this project in development. Is the plan to combine the features of both the AppleTalk card and the memory expansion card that were originally available?