-
-
Save userx14/664f5e74cc7ced8c29d4a0434ab7be98 to your computer and use it in GitHub Desktop.
| import numpy as np | |
| from pathlib import Path | |
| import crcmod | |
| import sys | |
| def extract_sysex_messages(syx_path): | |
| commands_list = dict([ | |
| (0x71, "UPDATE_INIT"), | |
| (0x72, "UPDATE_WRITE"), | |
| (0x73, "UPDATE_FINISH"), | |
| (0x76, "UPDATE_FOOTER"), | |
| (0x7c, "UPDATE_HEADER"), | |
| ]) | |
| def parse_nibble(data): | |
| result = 0 | |
| for byte_value in data: | |
| result = result << 4 | |
| result |= byte_value | |
| return result | |
| with open(syx_path, 'rb') as syx_file: | |
| data = syx_file.read() | |
| finalFirmwareFile = np.empty([0],dtype=np.uint8) | |
| i = 0 | |
| while i < len(data): | |
| if data[i] == 0xF0: #SysEx start byte | |
| end_index = data.find(0xF7, i) #SysEx end byte | |
| if end_index == -1: | |
| raise ValueError("Missing SysEx end byte") | |
| novation_header = bytes([0x00, 0x20, 0x29, 0x00]) | |
| if data[i+1:i+5] != novation_header: | |
| raise ValueError("File is missing novation header") | |
| command = commands_list[data[i+5]] | |
| cropped_data = data[i+6:end_index] | |
| if command == "UPDATE_INIT": | |
| version = parse_nibble(cropped_data[2:8]) | |
| if cropped_data[1] == 0x64: | |
| print("target: circuit tracks") | |
| elif cropped_data[1] == 0x63: | |
| print("target: circuit rhythm") | |
| else: | |
| print(hex(cropped_data)) | |
| print(0x1d) | |
| print(f"init version: {version}") | |
| elif command == "UPDATE_HEADER": | |
| version = parse_nibble(cropped_data[1:7]) | |
| print(f"header version: {version}") | |
| filesize = parse_nibble(cropped_data[7:15]) | |
| print(f"header filesize: {hex(filesize)}") | |
| checksum = parse_nibble(cropped_data[15:23]) | |
| print(f"header checksum: {hex(checksum)}") | |
| elif command in ["UPDATE_WRITE", "UPDATE_FINISH"]: | |
| #need to tightly pack 7bit MIDI bytes into 8bit firmware file | |
| packed = np.frombuffer(cropped_data, dtype=np.uint8) | |
| unpacked = np.unpackbits(packed[:, np.newaxis], axis=1) | |
| unpacked = unpacked[:, 1:].reshape(-1) #discard first bit of each byte and make continuous array | |
| repacked = np.packbits(unpacked[:-3]) #last three bits are just padding | |
| if command == "UPDATE_FINISH": | |
| finalFirmwareFile = np.concatenate([repacked, finalFirmwareFile]) | |
| break | |
| else: | |
| finalFirmwareFile = np.concatenate([finalFirmwareFile, repacked]) | |
| i = end_index + 1 | |
| else: | |
| i += 1 | |
| finalFirmwareFile = bytes(finalFirmwareFile[:filesize]) | |
| crc32_non_reflected = crcmod.mkCrcFun(0x104C11DB7, rev=False, initCrc=0xFFFFFFFF, xorOut=0x00000000) | |
| calculated_checksum = crc32_non_reflected(finalFirmwareFile) | |
| if calculated_checksum != checksum: | |
| raise ValueError(f"File checksum {calculated_checksum} does not match header checksum {checksum}") | |
| with open(syx_path.with_suffix(".bin"), 'wb') as f: | |
| f.write(finalFirmwareFile) | |
| if len(sys.argv)!=2: | |
| print("need to give a path to a .syx file") | |
| extract_sysex_messages(Path(sys.argv[1])) |
Good finds :)
Which binary firmware file version are you analyzing? When I look at the firmware file for most recent "1.2.1" / 4486 for circuit tracks,
Using the same version
the addresses 0x08051B8C where you find the max sample size check,
honestly I dont know why, but seems like when loaded in ida base address is actually 0x08010000 not 0x08000000
Also I'm really unsure about the conclusion here, for the behavior of the pitch shifting of the drum tracks. From hardware testing with a single sample, when adjusting the pitch with midi cc command or with the knob, the samples are not chromatically played and do not fall onto semitones. So either this behavior is non used code or something funky is going on when it is played with the DSP.
Let me check again :)
I did a dump of the flash memory and your offset of 0x08010000 is correct. Before that there is a bootloader, that is not included in the firmware update file. The function locations make sense with this.
Is it possible that you found the place that handles the different musical scales that the synth supports, instead of some sample playback selection? You identified that the semitone offset are stored at 0x0805a4c4 and beyond. If I check there I see 15 similar entries followed by an entry with just zeros, which would match nicely the 16 scales that circuit tracks supports and the zeros would be major scale.
Is the order of notes maybe flipped (B A# A...) instead of (C C# D ...) for the pitch table analysis? Just from the shift in the function I would have guessed that e.g. note C corresponds to the two lowest value bits.
Also I think you are correct that Sample_LoadToDSP does not modify the pitch. My guess is that they just change the playback speed on the DSP, after upload. Unfortunately, I did not find where they do this in the firmware.
Anyway I'm impressed with how fast you did the analysis of these functions, the assembly code that does the modulo 12 part took me quiet a while to understand.
Together with pr0ximaMusic, I started a github repo with a ghidra project and collected some other resources like the debug header pinout for stlink connection. If you like to be added as collaborator to this one, please let me know.
Together with pr0ximaMusic, I started a github repo with a ghidra project and collected some other resources like the debug header pinout for stlink connection. If you like to be added as collaborator to this one, please let me know.
Sure add me
I did a dump of the flash memory and your offset of 0x08010000 is correct. Before that there is a bootloader, that is not included in the firmware update file. The function locations make sense with this.
I will try to look into pitch shifting again, honestly I have very little experience with all the DSP stuff so most of the heavy lifting was done by using this method https://wilgibbs.com/blog/defcon-finals-mcp/
Doing the same for the strings in
circuitrhythm-firmware-5706.binreturns basically identical strings at slightly different offsets. There is even some "Circuit Tracks" string still in the firmware for rhythm. Some additional strings for rhythm that are not present for tracks are:Even more hint that the hardware platform between rhythm and tracks is shared.