mungewell / zoom-zt2

Python script to install/remove effects from the Zoom G1Four pedal
MIT License
50 stars 10 forks source link

ZD2: icons for G1/B1/A1 models #52

Closed nomadbyte closed 1 year ago

nomadbyte commented 1 year ago

Has anyone figured out the location of the single-width icons (assuming these are encoded in the ZD2 file)? This is the icon that appears on the G1/B1/A1 FOUR screens.

Just to make it clear, the BM-section (@0x88) is not the icon that is being shown on-the-device, at least in the case of G1/B1/A1 models.

For example, for MATCH30 (MACH301U.ZD2) the on-device icon has wording MTCH30 and some accent details, while the BM-section shows an icon (302 bytes) with MATCH30 wording, and it's wider.

The on-device icon (about 23x30px): g1four-mach301u-icon

NOT (the BM-section icon: 47x30px):

mach301u-zd2-bm

mungewell commented 1 year ago

For ZD2 effects the Icon is contained within the effect file, and can be extracted with the decode_effect.py script.

https://github.com/mungewell/zoom-zt2/blob/master/decode_effect.py#L30

Are you saying that the unit display a different icon, maybe the one in the ICON section is just got GuitarLab. You can also extract the CODE section which is an ELF binary.... you could check that for the "narrow" bitmap.

mungewell commented 1 year ago

Also double check that you are looking at the right file, seems that there are several with similar naming...

zoom_fx_AllZDL7$ find . -name 'MACH301U.ZD2' -exec md5sum {} \;
1557365fe45e0153cdf59e4144474bdc  ./G1X FOUR/unzipped/MACH301U.ZD2
1557365fe45e0153cdf59e4144474bdc  ./G1 FOUR/unzipped/MACH301U.ZD2
1557365fe45e0153cdf59e4144474bdc  ./R20/unzipped/MACH301U.ZD2
1557365fe45e0153cdf59e4144474bdc  ./H8/unzipped/MACH301U.ZD2

zoom_fx_AllZDL7$ grep "MATCH" master.txt 
0x040000a0 : MATCH30 (v1.10, 34.86%), 0x4e53b71acf5e94bfa85c648dd285e5a5
0x040000a1 : MATCH30 (v1.00, 34.86%), 0x1557365fe45e0153cdf59e4144474bdc
0x040000a2 : MATCH30 (v1.00, 31.92%), 0xd01bd654453b2648839acac29255f104
nomadbyte commented 1 year ago

Well, if you look in my message above, the icon in the ZD2 header (BM-section) is different from the one on the device (the blue-shaded pic above). The actual ZD2 file on the device is:

1557365fe45e0153cdf59e4144474bdc  ./G1 FOUR/unzipped/MACH301U.ZD2

It must have the other icon encoded somewhere other than the BM-section in the header, hard to say in which format though (?? ICO, the size is about 23x30 dots or more if there's any padding around). The ToneLib editor appears to show the BM-icon not the icon on the device.

mungewell commented 1 year ago

Yes, I did read you post, and suggest a place to look (in the CODE section of the ZD2). Anyhow, now I am home I was able to confirm your observation. The 'on device' icon does NOT match the ICON for MATCH30 on my G1X Four.

The CODE section can be extracted with, and the processed with arm-linux-gnueabi-objdump.

zoom_fx_AllZDL7$ python3 ../decode_effect.py -c test.code ./G1\ FOUR/unzipped/MACH301U.ZD2

It may be that the bitmap is just a 'raw' section of data. That was the case for the pedal name/icon/boot screen. See: https://github.com/mungewell/zoom-zt2/issues/18#issuecomment-748546089

For reference the other ZD2's I mentioned are used on the following devices.

zoom_fx_AllZDL7$ find . -name 'list_sorted.txt' -exec grep 0x4e53b71acf5e94bfa85c648dd285e5a5 {} \;
0x040000a0 : MATCH30 (v1.10, 34.86%), 0x4e53b71acf5e94bfa85c648dd285e5a5, ./G3n/unzipped/MATCH_30.ZD2
0x040000a0 : MATCH30 (v1.10, 34.86%), 0x4e53b71acf5e94bfa85c648dd285e5a5, ./G5n/unzipped/MATCH_30.ZD2
0x040000a0 : MATCH30 (v1.10, 34.86%), 0x4e53b71acf5e94bfa85c648dd285e5a5, ./G3Xn/unzipped/MATCH_30.ZD2

zoom_fx_AllZDL7$ find . -name 'list_sorted.txt' -exec grep 0x1557365fe45e0153cdf59e4144474bdc {} \;
0x040000a1 : MATCH30 (v1.00, 34.86%), 0x1557365fe45e0153cdf59e4144474bdc, ./G1X FOUR/unzipped/MACH301U.ZD2
0x040000a1 : MATCH30 (v1.00, 34.86%), 0x1557365fe45e0153cdf59e4144474bdc, ./G1 FOUR/unzipped/MACH301U.ZD2
0x040000a1 : MATCH30 (v1.00, 34.86%), 0x1557365fe45e0153cdf59e4144474bdc, ./R20/unzipped/MACH301U.ZD2
0x040000a1 : MATCH30 (v1.00, 34.86%), 0x1557365fe45e0153cdf59e4144474bdc, ./H8/unzipped/MACH301U.ZD2

zoom_fx_AllZDL7$ find . -name 'list_sorted.txt' -exec grep 0xd01bd654453b2648839acac29255f104 {} \;
0x040000a2 : MATCH30 (v1.00, 31.92%), 0xd01bd654453b2648839acac29255f104, ./G11/unzipped/MACH30AU.ZD2
0x040000a2 : MATCH30 (v1.00, 31.92%), 0xd01bd654453b2648839acac29255f104, ./G6/unzipped/MACH30AU.ZD2
mungewell commented 1 year ago

There are a few ZD2 effects which have updated revisions, perhaps these would be a good way to compare the CODE sections... assuming that the bitmap is unchanged. You might be able to hunt down a section which is not changed between the two files.

I have previously found this util to be quiet helpful... https://github.com/Sepero/SearchBin

mungewell commented 1 year ago

The icon is in the CODE section, although I have not tracked down exactly where... using objcopy to extract the .const section and then converting that as a 8x3200 monochrome image.

$ arm-linux-gnueabi-objcopy -j .const -S -w -K effectTypeImageInfo* -I elf32-little test.code -O elf32-little test_mod.code
$ arm-linux-gnueabi-objcopy -I elf32-little test_mod.code -O binary test.bin
$ convert -monochrome -size 8x3200 -depth 1 MONO:test.bin test.png

and you get: Screenshot_2023-01-01_16-57-00

mungewell commented 1 year ago

Couldn't figure out how to extract just one symbol, so have to do some tricks with dd.

$ arm-linux-gnueabi-objcopy -j .const -S -w -K picTotalDisplay_* -I elf32-little test.code -O elf32-little test_mod.code
$ arm-linux-gnueabi-objcopy -I elf32-little test_mod.code -O binary test.bin
$ arm-linux-gnueabi-objdump -t test_mod.code

test_mod.code:     file format elf32-little

SYMBOL TABLE:
80000000 l    d  .const 00000000 .const
800006b0 g     O .const 0000005c .hidden picTotalDisplay_MATCH_30

$ dd if=test.bin of=test2.bin bs=1 skip=1712 count=108
$ convert -monochrome -size 8x108 -depth 1 MONO:test2.bin -transpose -crop 23x8+0+0 stripe1.png
$ convert -monochrome -size 8x108 -depth 1 MONO:test2.bin -transpose -crop 23x8+23+0 stripe2.png
$ convert -monochrome -size 8x108 -depth 1 MONO:test2.bin -transpose -crop 23x8+46+0 stripe3.png
$ convert -monochrome -size 8x108 -depth 1 MONO:test2.bin -transpose -crop 23x8+69+0 stripe4.png
$ convert stripe1.png stripe2.png stripe3.png stripe4.png -append icon.png
$ display icon.png

icon_bigger

nomadbyte commented 1 year ago

What a find!!

I too realized that the icon must be encoded in the ELF somewhere but short of knowing the format it's was next to impossible to spot it in hex. I almost suspected that these could be literally drawn on-device using some primitives.... luckily it was not the case (would be quite wasteful too).

I'm impressed!! (and quite a bit relieved, knowing that indeed the icon data is there). Trying to make sense out of your commands.

nomadbyte commented 1 year ago

Just to give you some context of my inquiry. The MACH301U.ZD2 icon that you extracted is a 1U (single-unit width) icon, for a wide (aka two-slot, 8-param) G5n module -- MATCH_30.ZD2 and which, I guess, contains the "wide" 2U version of the icon. The 1U-variants also restructure the parameter sets to accommodate the paging (by inserting Dummy params). As such 1U icons fit properly on G1/G1X type of displays which allow only 5 icons, all 1U sized.

So, I can't prove that the MATCH_30.ZD2 contains a wide on-device icon, just a guess. You may readily extract the icon to see if this is the case indeed.

Meanwhile, another 1U module variant EGFLTR1U.ZD2 (it's targeted for G6, the G5n version is EGFILTER.ZD2), appears to be a hybrid -- it has 1U set of parameters, yet it displays a wide two-slot on-device icon, which kinda messes up the look on G1/G1X-sized display.

So, just an idea, I was wondering if it would make any sense to try to locate and replace the on-device icon, thus fully adapting the EGFLTR1U.ZD2 for G1/G1X use. Nothing special about it, just a curious module meant to operate in STOMP mode, it selectively engages HFP/LFP filters.

nomadbyte commented 1 year ago

Ok, now this all looks familiar. The bytes corresponding to the on-device icon seem to begin with fe 0.... I did see them before, but the bit-order did not look anything resembling the expected pixels... No wonder, as the bitmap is sliced by byte-wide fragments and flipped, not sure why. There must be some transformation to apply, so that it could be stitched together.

From your steps I gather that the data is in the .const section of the .ELF module and is referenced to picTotalDisplay_${module-name}. So the section could then be extracted, and the icon-bytes will be at their offset in the section. Convert (ImageMagick) then is used to normalize the image.

I kind of rehashed the steps using more familiar to me tools and applied it to the G5n MATCH_30.ZD2 variant (wide). It all comes down to combining the referenced offsets (ZD2[ELF]:0x240, ELF[.const]:0x49a0, .const[picTotalDisplay_]:0x6b0) and the object size:188)

$ hd MATCH_30.ZD2 | grep "ELF"
00000240  7f 45 4c 46 01 01 01 40  00 00 00 00 00 00 00 00  |.ELF...@........|

$ tail -c +$((16#240+1)) MATCH_30.ZD2 >tmp.elf ; readelf -a tmp.elf | grep -e "\] .const" -e " [_]*picTotalDisplay_" ; rm tmp.elf
  [13] .const            PROGBITS        80000000 0049a0 000d0d 00   A  0   0  8
   275: 800006b0   188 OBJECT  GLOBAL HIDDEN    13 picTotalDisplay_MATCH_30

$ xxd -p -s $((16#240+16#049a0+16#06b0)) -l 188 MATCH_30.ZD2 | xxd -r -ps > icon.bin

$ convert -monochrome -size 8x188 -depth 1 MONO:icon.bin icon.png

The G5n icon is wider (188 bytes vs 92 for 1U-size), it's split into 4 vertical slices, so the stitched width is 188/4=47px, and height 8*4=32px, thus 47x32px. The 1U icon is 23x32px.

Below are the extracted slices (convert can normalize it too, perhaps even in one shot, but it's too much to RTFM for now). As could be noticed, this icon looks just like the one from the ZD2 BM-section, except for the 2px whitespace crop at the bottom to make it 47x30px.

match_30-zd2-mod-icon

nomadbyte commented 1 year ago

Applying the same steps to the mentioned EGFLTR1U.ZD2 (G6 targeted), yields an even wider icon 50x32px, it's more than twice wider than 1U. By the way, in this case the .const object name is with a leading underscore _picTotalDisplay. . Not sure if it's possible at all to stuff a smaller 1U icon into the ELF and then recraft the ZD2 module, so that it would be fully compatible with G1/B1/A1 FOUR models?

mungewell commented 1 year ago

picTotalDisplay is a 'private' reference to the location of a blob of raw pixel data. There is likely a 'public' function that makes this available to host OS, or performs the writing to the LCD.

I don't think that the effort is worth it, but you could look further into the ELF to try to find it.

On the arrangement (slicing) of the pixels, this is probably just how the LCD screen is memory mapped. So code just has to copy chunks of memory around.

mungewell commented 1 year ago

cleaned up the process with a little automation.... icon_it.sh.txt

$ bash icon_it.sh ./G1\ FOUR/unzipped/BLACKOPT.ZD2
mungewell commented 1 year ago

Another interesting observation. The first byte of the effectTypeImageInfo block appears to be the width of the 'on-device' icon.

./G11/unzipped/MACH30AU.ZD2
800005b0 l     O .const 00000130 .hidden effectTypeImageInfo
000005b0  2f 00 00 00 1e 00 00 00  e0 06 00 80 00 00 00 00  |/...............|
./G5n/unzipped/MATCH_30.ZD2
80000580 l     O .const 00000130 .hidden effectTypeImageInfo
00000580  2f 00 00 00 1e 00 00 00  b0 06 00 80 00 00 00 00  |/...............|
./G1\ FOUR/unzipped/MACH301U.ZD2
80000580 l     O .const 00000130 .hidden effectTypeImageInfo
00000580  17 00 00 00 1e 00 00 00  b0 06 00 80 00 00 00 00  |................|

Probably need to validate this across more ZD2s.

mungewell commented 1 year ago
./G6/unzipped/EGFLTR1U.ZD2
80000268 l     O .const 00000130 .hidden effectTypeImageInfo
00000260  00 00 00 00 00 00 00 00  32 00 00 00 1e 00 00 00  |........2.......|

0x32 = 50

Icon is 50 pixels wide.

icon

nomadbyte commented 1 year ago

I really appreciate your effort, it's fun working with you on this!

... looks like, it's the first 4-byte int for width ([17]=23, [2f]=47, [32]=50), the second 4-byte int for height: [1e]=30, even though the encoded image (in 4 slices) adds up to height 32pixels, apparently it's sized to 30, just as the icon in the BM-section of the ZD2. If you look next, what follows, the bytes 1U:[b0 06]=0x06b0, AU:[e0 06]=0x06e0, EGFLTR1U:[98 03]=0x0398, appear to refer to the offset of the picture data from the start of the .const section.

NOTE: both MATCH 30 versions (2U and 1U) have the same offset for the picture data 0x06b0 (picTotalDisplay_MATCH_30).

There are more details follow that part, just a guess, those bytes describe the picture slices. I'll try to compare those between 2U/1U, if there is any pattern.

Also, if you examine the actual bytes following the on-device icon, it looks like there's another chunk(s) of graphical data encoded there, my guess it may be the "selected effect" icon, that one which is shaded when pressing the left-pedal foot-switch, just a guess, but it does not anything recognizable.

nomadbyte commented 1 year ago

I wonder, if indeed, the picTotalDisplay/effectTypeImageInfo logic does rely on the stated ints for sizing the icon data, then supplanting it for narrower icon may be rather simple by fixing the width value and overwriting the icon's bytes, padding the rest with [FF] for white color. I guess a test may be just to fix the width and see if the wide icon shows up only to the set size.

nomadbyte commented 1 year ago

Here it is. Patched the 1U icon from RNDMFLTR.ZD2 (22x32px, 88bytes) over the existing icon bytes in EGFLTR1U.ZD2, the remaining bytes of the original 2U+ icon just as they were, and also patched the sizing info bytes (width:[16], height:[20]).

Next, will try my hand at pixel-art to etch EG legibly instead of RNDM :) Not sure if there's any image format that's easy to transpose into the raw bytes.

g1-four-egfltr1u-rndm-icon

mungewell commented 1 year ago

Cool that it worked OK. I assume you hand patch the bytes, rather than using a tool to re-write the ELF file?

It should be fairly easy to use convert to go from PNG to RAW bits (with the striping/rotation).

Realized that the 'extract' script didn't need a special ARM objdump/objcopy, so I checked it in here: https://github.com/mungewell/zoom-zt2/blob/master/extract_device_icon.sh

nomadbyte commented 1 year ago

... I assume you hand patch the bytes, rather than using a tool to re-write the ELF file?

How would you go about rewriting the ELF to pack in only the part we figured out, objcopy? Or you meant repacking the patched ELF part into ZD2? I just patched the ZD2 directly skipping the intermediary step, loosely scripted.

I guess, this could be fully scripted to copy an icon from a given input ZD2 into a compatible output ZD2, well, maybe it would be the next step, though not sure how many 1U ZD2 need such adaptation. My next step will be etching that "EG" nicely... by hand, though I'm looking if the byte-sequence for letters E and G could be lifted from another icon.

nomadbyte commented 1 year ago

Got the E and G (my guess, it stands for Envelope Generator). Edited the sliced PNG in graphics editor, then converted back into monochrome bytes (sliced and transposed as original):

convert icon.png -depth 1 MONO:icon.bin

Here's the stitched together 22x32px PNG:

g1-four-egfltr1u-icon

and the sliced 8x88px PNG:

g1-four-egfltr1u-icon-8x88

mungewell commented 1 year ago

Yes, it has been fun. Although this specific application is perhaps not so useful to me, I think that the effectTypeImageInfo block may contain whether the effect is mono or stereo input/output... something that I have been looking for .

Just for more 'fun' I checked in a little tool to convert PNG to raw striped bytes,

$ bash extract_device_icon.sh zoom_fx_AllZDL7/R20/unzipped/EQ_AVN.ZD2
objcopy: test.code: warning: Empty loadable segment detected at vaddr=0, is this intentional?

test_mod.code:     file format elf32-little

SYMBOL TABLE:
80000398 l     O .const 0000005c .hidden picTotalDisplay_GraphicEQ
80000398 l    d  .const 00000000 .hidden .const
80000000 l    d  .const 00000000 .const

92+0 records in
92+0 records out
92 bytes copied, 0.000277523 s, 332 kB/s
icon width is 23

$ bash restripe_device_icon.sh icon.png
Files icon.raw and icon2.raw are identical

Do we think that this bug is now closed?

nomadbyte commented 1 year ago

Great work! Thanks.

mungewell commented 1 year ago

Added the -E flag to help pushing modified ELF code directly into the ZD2 file....

$ python3 decode_effect.py --help
usage: decode_effect [-h] [-d] [-s] [-m] [-b BITMAP] [-i INFO] [-c CODE] [-j]
                     [-x XML] [-t TEXT] [-D DONOR] [-B] [-T] [-I] [-C] [-X]
                     [-F] [-E] [-V] [-o OUTPUT]
                     FILE

positional arguments:
  FILE                  File to process

optional arguments:
  -h, --help            show this help message and exit
  -d, --dump            dump configuration to text
  -s, --summary         summarized configuration in human readable form
  -m, --md5sum          include md5sum of file in summary report
  -b BITMAP, --bitmap BITMAP
                        extract Icon/Bitmap to FILE
  -i INFO, --info INFO  extract Info to FILE
  -c CODE, --code CODE  extract Code to FILE

Language:
  Extract either English or Japanese segments

  -j, --japan           select Japanese version for export
  -x XML, --xml XML     extract XML to FILE
  -t TEXT, --text TEXT  extract Text to FILE

Donor:
  Take replacement sections from a donor ZD2

  -D DONOR, --donor DONOR
                        specify donor ZD2 FILE
  -B, --donor-bitmap    extract Icon/Bitmap from donor
  -T, --donor-text      extract Text from donor
  -I, --donor-info      extract Info from donor
  -C, --donor-code      extract Code from donor
  -X, --donor-xml       extract XML from donor
  -F, --donor-final     extract FinalBytes from donor
  -E, --donor-elf       replace Code with ELF file (ie whole file)
  -V, --crc             validate CRC32 checksum
  -o OUTPUT, --output OUTPUT
                        output combined result to FILE

$ python3 decode_effect.py -D MODIFED.code -E -o ZNR_MOD.ZD2 ZNR.ZD2
Checksum Recalculated: 0xca8343be
mungewell commented 1 year ago

Looks like there a Python project which has tools for directly (binary) editing ELF files. https://github.com/Gallopsled/pwntools http://docs.pwntools.com/en/latest/elf/elf.html

mungewell commented 1 year ago

Well that turned out well.....

$ python3 decode_effect.py -c ZNR.ZD2.code ZNR.ZD2

$ python3
Python 3.6.9 (default, Nov 25 2022, 14:10:45) 
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> from hexdump import *
>>> code = ELF("ZNR.ZD2.code")
[!] Could not populate PLT: 'int' object has no attribute 'lower'
[*] 'ZNR.ZD2.code'
    Arch:     140-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
>>> hexdump(code.read(code.symbols["effectTypeImageInfo"], 16))
00000000: 17 00 00 00 1E 00 00 00  80 02 00 80 14 00 00 00  ................
>>> code.write(code.symbols["effectTypeImageInfo"], bytes([0x16, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00]))
>>> code.save()
>>> quit()

$ python3
Python 3.6.9 (default, Nov 25 2022, 14:10:45) 
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> from hexdump import *
>>> code = ELF("ZNR.ZD2.code")
[!] Could not populate PLT: 'int' object has no attribute 'lower'
[*] '/home/simon/zoom-zt2-sdw-github/ZNR.ZD2.code'
    Arch:     140-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
>>> hexdump(code.read(code.symbols["effectTypeImageInfo"], 16))
00000000: 16 00 00 00 1D 00 00 00  80 02 00 80 14 00 00 00  ................
>>> quit()
mungewell commented 1 year ago

Seems our Russian friend has a break-down/explanation of the "effect type info" used in ZDL effects. May give us some hints. https://github.com/ELynx/zoom-fx-modding/blob/main/library/CH_1.md#optional-knob-positions