Last active
July 10, 2025 22:46
-
-
Save shadwork/c6425d91026b9d11d53d68f19c3f934d to your computer and use it in GitHub Desktop.
Python script to open vice 64 emulator snapshot with vsf extension, based on v2 specification
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import sys | |
| import os | |
| import struct | |
| ram_dump = bytearray() | |
| def dword(byte_array): | |
| return struct.unpack('I',byte_array)[0] | |
| def word(byte_array): | |
| return struct.unpack('H',byte_array)[0] | |
| def parse_maincpu(byte_array): | |
| # Read the CPU clock value (lo/hi) | |
| cpu_clock_value = dword(byte_array[0x00:0x04]) # Bytes 0-3 | |
| # Read registers | |
| a_register = byte_array[0x04] # Byte 4 | |
| x_register = byte_array[0x05] # Byte 5 | |
| y_register = byte_array[0x06] # Byte 6 | |
| stack_pointer = byte_array[0x07] # Byte 7 | |
| # Read program counter | |
| program_counter = word(byte_array[0x08:0x0A]) # Bytes 8-9 | |
| # Read status register | |
| status_register = byte_array[0x0A] # Byte 10 | |
| # Read last opcode | |
| last_opcode = dword(byte_array[0x0B:0x0F]) # Bytes 11-14 | |
| # Read clock values for IRQ and NMI | |
| clock_irq_active = dword(byte_array[0x0F:0x13]) # Bytes 15-18 | |
| clock_nmi_active = dword(byte_array[0x13:0x17]) # Bytes 19-22 | |
| # Read unknown fields | |
| unknown_1 = byte_array[0x17:0x1B] # Bytes 23-26 | |
| unknown_2 = byte_array[0x1B:0x1F] # Bytes 27-30 | |
| # Print parsed information with 4-space indentation | |
| indent = " " | |
| print(f"{indent}CPU Clock Value: {cpu_clock_value} Hz") | |
| print(f"{indent}A Register (Accumulator): {a_register:02X}") | |
| print(f"{indent}X Register: {x_register:02X}") | |
| print(f"{indent}Y Register: {y_register:02X}") | |
| print(f"{indent}Stack Pointer: {stack_pointer:02X}") | |
| print(f"{indent}Program Counter: {program_counter:04X}") | |
| print(f"{indent}Status Register: {status_register:02X}") | |
| print(f"{indent}Last Opcode: {last_opcode}") | |
| print(f"{indent}Clock IRQ Active: {clock_irq_active:04X} Hz") | |
| print(f"{indent}Clock NMI Active: {clock_nmi_active:04X} Hz") | |
| def parse_c64mem(byte_array): | |
| cpu_data = byte_array[0] # First byte: CPUDATA | |
| cpu_dir = byte_array[1] # Second byte: CPUDIR | |
| exrom = byte_array[2] # Third byte: EXROM | |
| game = byte_array[3] # Fourth byte: GAME | |
| # Extract the remaining bytes as RAM (from index 4 to the end) | |
| global ram_dump | |
| ram_dump = byte_array[4:4 + 65536] | |
| indent = " " | |
| print(f"{indent}CPU Data: {cpu_data:02X}") | |
| print(f"{indent}CPU Dir: {cpu_dir:02X}") | |
| print(f"{indent}EXRom: {exrom:02X}") | |
| print(f"{indent}GAME: {game:02X}") | |
| def read_vice_snapshot_header(file_path): | |
| try: | |
| file_size = os.stat(file_path).st_size | |
| # Open the binary file in read mode ('rb' - read binary) | |
| with open(file_path, 'rb') as binary_file: | |
| # Read bytes 0-18 for the signature (includes the null padding) | |
| signature = binary_file.read(19) # "VICE Snapshot File" + $1A (0x1A) | |
| # Read bytes 19-20 for the snapshot version (major/minor) | |
| version_major_minor = binary_file.read(2) | |
| # Read bytes 21-30 for the name of the emulated machine (padded with $00) | |
| machine_name = binary_file.read(16) | |
| # "VICE Version\032", padded with 0 | |
| version_magic = binary_file.read(13) | |
| # Release version (major, minor, micro). Byte 4 is always zero. | |
| version_release = binary_file.read(4) | |
| # SVNVERSION DWORD | |
| version_svn = binary_file.read(4) | |
| # Convert binary data to string (for display purposes) | |
| signature_str = signature.decode('ascii').strip('\x00') | |
| version_major = version_major_minor[0] | |
| version_minor = version_major_minor[1] | |
| machine_name_str = machine_name.decode('ascii').strip('\x00') | |
| version_magic_str = version_magic.decode('ascii').strip('\x00') | |
| # Output the results | |
| print(f"Signature: {signature_str}") | |
| print(f"Version: {version_major}.{version_minor}") | |
| print(f"Emulated Machine: {machine_name_str}") | |
| print(f"Version : {version_magic_str}") | |
| while True: | |
| if binary_file.tell() == file_size: | |
| break | |
| module_name = binary_file.read(16) | |
| module_name_str = module_name.decode('ascii').strip('\x00') | |
| module_version = binary_file.read(2) | |
| module_size = dword(binary_file.read(4)) - (16+2+4) | |
| print(f"Found Module: {module_name_str} Version: {module_version[0]}.{module_version[1]} Size: {module_size}") | |
| module_content = binary_file.read(module_size) | |
| match module_name_str: | |
| case "MAINCPU": | |
| parse_maincpu(module_content) | |
| case "C64MEM": | |
| parse_c64mem(module_content) | |
| except FileNotFoundError: | |
| print(f"Error: File '{file_path}' not found.") | |
| except IOError: | |
| print(f"Error: File corrupted or format is unknown.") | |
| except Exception as e: | |
| print(f"An error occurred: {e}") | |
| if __name__ == "__main__": | |
| # Check if a file path was provided as an argument | |
| if len(sys.argv) < 2: | |
| print("Usage: python read_snapshot.py <path_to_binary_file> ") | |
| print("and optional, third parameter for memory dump <path_to_binary_file> ") | |
| else: | |
| # Get the file path from command line arguments | |
| file_path = sys.argv[1] | |
| # Read and display the snapshot header | |
| read_vice_snapshot_header(file_path) | |
| if len(sys.argv) > 2: | |
| with open(sys.argv[2], 'wb') as file: | |
| file.write(ram_dump) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment