Major rework (WARNING: do not use on unofficial cards)#5
Conversation
23d0332 to
a1509aa
Compare
Read card identifier to know its parameters (size, turn-around byte count, sector size). Implement card unlocking support, allowing access to official cards. Use the same frequencies as in libogc: 1MHz to read card identifier, 12MHz for all other operations. Measured average speed will likely always be lower as top clock speed only happens when the host is purely shifting bits in/out, and not moving data in/out of its shift register. Implement event-based wait for card operations using INT line. The INT line is an input on the host (output on the card), which allows the card to let the host know when it is done without needing constant polling. This makes more sense than driving the INT line from the host, even though both would look identical on the bus. This also allows getting rid of all sleeps. Still retain polling support in order to make the INT pin optional (making the reader usable to SPI-only, GPIO-less hosts). Pythonify the source: - declare a few classes - use enum and ctypes for flag constants and structure definitions Unhardcode SPI bus and GPIO for INT signal, to be board-independent. Switch dependencies to pure-python and board-independent modules. Decode and validate stored header serial by comparing it with the one obtained when unlocking the card. Check header block CRC. Make -r/-w optional, to only print card information.
Intended to be used with stripped wires as contacts. Should be 3D-printable without any support nor raft. Beware of thin walls which may be discarded by the slicer, causing overhangs. Assembly notes: - beware of shorts when using multi-fibre wires - Kapton tape strongly advised to avoid the wires shorting against the card's shield - not all contacts are necessary to read/write a card, refer to a pinout to know which places to populate - contacts will likely not be perfect, be careful when inserting in the card. Some squeezing of the card may help.
|
Updated output sample: I believe I am mostly done with this rework (it achieves what I need and the code, if never perfect, seems clean-enough). Review & testing welcome. |
|
thank you so much for bringing the unlock sequence into this much more readable form. I was wondering about the LFSR, as after the initialisation it could be possible that there's a collision between the lowest bit of the seed and the next new value. Due to the seed always being Thinking about it a bit more, if I'm not at err, I'm wondering whether it's not a 31-bit LFSR and not a 32-bit as the next value is effectively inserted into the second bit? |
Indeed, and this is arguably a bug in my implementation if this class is taken out of context.
IIRC I took this value from libogc's source, which uses a lot of statements to produce this value with a super-tiny amount of entropy (less than 8 bits IIRC). I believe the randomness is useless in such implementation, as this code is not trying to confuse a data sniffer (while I guess the GC's firmware is trying to do, and by extension libogc). So I just picked one of the values Also, it is not just the last bit which is always initialised to zero, but the 12 last bits. My guess is that the card hard the very same LFSR and loads in it the address of the first So setting any of the 12 LSbs will desynchronise the ciphers, breaking the handshake, as the card has no way of knowing those bits.
This is correct: the new bit is OR'ed on bit 0 and immediately shifted left to be ready for the next round. It may be easier to think about it in hardware-description terms: this is a 31bits register (one of which is the output) fed from one combinatorial bit. The production of the input bit is "permanent" and it only gets fixed when shifted in the first register bit. In this implementation I picked a 32bits value as it seems less surprising as a software implementation. BTW, my code is not efficient: it reads only the MSb to produce the key stream. It could instead read the entire state of the LFSR right after producing the 32nd bit to get 32bits worth of key stream. It could then do 32 rounds to get the next 32 bits only taking care of the taps, then reading the entire state, and so on. I guess the current implementation is a bit more readable (it uses a more familiar LFSR access pattern and does not need extra code to handle less-than-32bits reads), and this is not performance-critical: this is only needed to unlock the card, and then the slow part is actually reading/writing the content of the card. |
|
@vpelletier with the 3D model you included, what parts end up filling it out? |
|
I use simple wires, stripped so they make contact with the memory card's connector, and with a piece of tape on the other side so the wires cannot touch the card's shield. I'm not sure I can recommend multi-strand wires, as I used in the picture below... Beware of loose strands shorting stuff. I almost wired the minimum number of pins, the red wire is not connected anywhere. It's fiddly to put together, it does not always work, but I think it beats having to solder to the card's pads, and does not require salvaging a connector from a console. Note: the pictured model was an earlier iteration, with wire guides coming through the connector at 45°. It did not print cleanly and I had to use a drill bit to open the holes and get the wires through. The 90° angles of the included model should print cleaner. Also, I used Kapton as tape, but any thin tape should work - just make sure the wires do not punch through. |
|
I have never used an rp2040, so I have no idea. All I can say is that the card unlocking handshake does not rely on fancy operations (no >32bits values, simple arithmetic & logic). The card header decryption is a different beast, my code mostly deals with it only as a sanity check for the unlocking handshake. |
|
@vpelletier I didn't notice any header decryption needed after the dump is formed, what part are you talking about? |
|
That was about the |
|
WARNING: do not use on unofficial cards So far, 3 unofficial cards are known to have been somehow bricked while using this code:
Unofficial memory cards report themselves as unlocked, so the unlocking handshake should not even run, and should probably not be related to this issue. The rest of the code comes from my understanding of There is of course the possibility that something else happened (I initially suspected a bad wiring in my case, which could have sent 5V to the 3.3V logic), but at three bricked cards I think this is looking quite bad for my code. So it looks like some work reversing unofficial cards is needed. Hopefully there are not many variants. Mine is apparently (I threw away the packaging long ago) a Mcbazel MT-000002, which is a "256MB" (that's 256Mb in proper non-scoundrel units, which is truly 32MB) card, with a button switching between two internal banks. It has some extra pads internally, probably for in-system programming. The flash chip has still-visible markings, but the main chip (some MCU, certainly) has been sanded flat. |
|
For documentation sake, the memory cards I was using were/are Mcbazel 1024MB(16344 Blocks) Memory Card (US Amazon Link). |


Should hopefully fix #4, #2, #1 .
The included 3D-printable model may help with #3.
On my pi (an old B+) 16MHz SPI is working perfectly. I connected the SPI signals to SPI bus 0 with CS 0, and the INT pin on GPIO 25 just because it is right next to the SPI pins.
Sample output:
Some disclaimers:
write_bufferanderase_card. I do not understand what some of these are even supposed to do (write_buffer,get_id,clear_status,wake_up).