If firmware page 0 was writable, I would have just flashed a modified version of the code used for other Sitronix frames. Instead, I extended the "get version" command to enable displaying the USB DMA buffer for data sent by the host, and running code there. The command is parsed at flash offset $A40B, which is at $640B in firmware page 2. At that point code has access to the first 16 bytes of the sector at $381, and the last 64 bytes of the sector in the DMA buffer at $200. I used the first 4 parameter bytes at $382-$385 to trigger my code. Here is an example of the display function:
I displayed the text by calling the same function used by the message command. This is not practically useful for displaying messages, but it can confirm that my code works, and that data in the USB DMA buffer can be used.
(After the first 64 bytes, you see the DMA buffer for data being sent to the host, which starts with USBS because it is a USB mass storage status reply. The green and pink corruption outside is because the firmware upgrade code overwrites the second picture and pictures are stored in a compressed format. To prevent overwriting the code doing the firmware upgrade, the new firmware is first written there, and then copied to its final position using code copied to RAM.)
Then it was time to run code. It worked, but 64 bytes isn't much space. It wasn't even enough for the LCD set window code. At $580-$67F there is 512 bytes of RAM where flash writing procedures are copied. Since code is always copied there immediately before being executed, that area is free when not writing to flash. I wrote a small program which copied part of the USB DMA buffer to that free area. Then I used this code appended with small chunks of a longer program to assemble a longer program at $580.
Finally, this allowed me to display images. I wrote code which interacts with the USB interrupt handler to read packets sent to the data port. This is very simple. I just had to deal with a few flags and call one function after I received a packet. For faster performance, it should be possible to use polling for everything except the start and end of a packet.
Probing the device using code
The ability to run code and dump RAM also allowed me to probe the device and discover information about its hardware. By looking at flash ID bytes and the Common Flash Interface (CFI), I found that the chip is an Eon EN29LV320B in bottom boot configuration. It reports that the first two sectors are protected, but I don't know if that's due to a protection setting stored in flash or a result the WP#/ACC pin, which protects the first two sectors when low. I suspect it's due to the pin. Maybe it's connected to a GPIO pin? I also probed the LCD controller and found that it is the Ilitek IL9320.
Port A is connected to the buttons, with bits being normally high and going low when the button is pressed:
PA & 1 = left
PA & 2 = pause
PA & 4 = right
PA & 8 = menu
The slide switch seems to simply cut power, so there is no way to read it. The pause button is read at startup, changing program flow. It might be for entering a recovery mode, not calling photo frame code and immediately going into USB mode.
This is still a work in progress and I'm not releasing any code now. If you want some code, feel free to ask.