-
-
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])) |
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/
Good finds :)
Using the same version
honestly I dont know why, but seems like when loaded in ida base address is actually
0x08010000
not0x08000000
Let me check again :)