Skip to content

Instantly share code, notes, and snippets.

@shadwork
Last active July 10, 2025 22:46
Show Gist options
  • Select an option

  • Save shadwork/c6425d91026b9d11d53d68f19c3f934d to your computer and use it in GitHub Desktop.

Select an option

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
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