117 lines
3.4 KiB
Python
117 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
# Convert ILDEC .oct format files to PDP-8 .rim format
|
||
|
|
# Created by Luxferre in 2026, released into the public domain
|
||
|
|
|
||
|
|
import sys
|
||
|
|
|
||
|
|
def print_error(msg):
|
||
|
|
sys.stderr.write(f"Error: {msg}\n")
|
||
|
|
|
||
|
|
def convert_octaddr_to_rim(input_path, output_path):
|
||
|
|
pairs = []
|
||
|
|
line_num = 0
|
||
|
|
try:
|
||
|
|
with open(input_path, 'r') as f:
|
||
|
|
for line in f:
|
||
|
|
line_num += 1
|
||
|
|
# Strip comments starting with /, #, ;
|
||
|
|
clean_line = line
|
||
|
|
for comment_char in ('/', '#', ';'):
|
||
|
|
if comment_char in clean_line:
|
||
|
|
clean_line = clean_line.split(comment_char, 1)[0]
|
||
|
|
|
||
|
|
clean_line = clean_line.strip()
|
||
|
|
if not clean_line:
|
||
|
|
continue
|
||
|
|
|
||
|
|
if ':' not in clean_line:
|
||
|
|
print_error(f"Line {line_num}: Missing colon separator (format is 'addr: data').")
|
||
|
|
return False
|
||
|
|
|
||
|
|
parts = clean_line.split(':', 1)
|
||
|
|
addr_str = parts[0].strip()
|
||
|
|
data_str = parts[1].strip()
|
||
|
|
|
||
|
|
try:
|
||
|
|
addr = int(addr_str, 8)
|
||
|
|
except ValueError:
|
||
|
|
print_error(f"Line {line_num}: Invalid octal address '{addr_str}'.")
|
||
|
|
return False
|
||
|
|
|
||
|
|
try:
|
||
|
|
data = int(data_str, 8)
|
||
|
|
except ValueError:
|
||
|
|
print_error(f"Line {line_num}: Invalid octal data '{data_str}'.")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if not (0 <= addr <= 0o7777):
|
||
|
|
print_error(f"Line {line_num}: Address '{addr_str}' ({oct(addr)}) out of 12-bit range (0-7777).")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if not (0 <= data <= 0o7777):
|
||
|
|
print_error(f"Line {line_num}: Data '{data_str}' ({oct(data)}) out of 12-bit range (0-7777).")
|
||
|
|
return False
|
||
|
|
|
||
|
|
pairs.append((addr, data))
|
||
|
|
except Exception as e:
|
||
|
|
print_error(f"Failed to read input file '{input_path}': {e}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if not pairs:
|
||
|
|
print_error("No valid address:data pairs found in the input file.")
|
||
|
|
return False
|
||
|
|
|
||
|
|
# Write RIM format
|
||
|
|
# Leader: 2 bytes of 0xFF
|
||
|
|
# For each pair:
|
||
|
|
# addr high: ((addr >> 6) & 0x3F) | 0x40
|
||
|
|
# addr low: addr & 0x3F
|
||
|
|
# data high: (data >> 6) & 0x3F
|
||
|
|
# data low: data & 0x3F
|
||
|
|
# Trailer: 2 bytes of 0xFF
|
||
|
|
try:
|
||
|
|
with open(output_path, 'wb') as f:
|
||
|
|
# Write leader
|
||
|
|
f.write(bytes([0xFF] * 2))
|
||
|
|
|
||
|
|
for addr, data in pairs:
|
||
|
|
addr_hi = ((addr >> 6) & 0x3F) | 0x40
|
||
|
|
addr_lo = addr & 0x3F
|
||
|
|
data_hi = (data >> 6) & 0x3F
|
||
|
|
data_lo = data & 0x3F
|
||
|
|
f.write(bytes([addr_hi, addr_lo, data_hi, data_lo]))
|
||
|
|
|
||
|
|
# Write trailer
|
||
|
|
f.write(bytes([0xFF] * 2))
|
||
|
|
except Exception as e:
|
||
|
|
print_error(f"Failed to write output file: {e}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print(f"Successfully converted '{input_path}' to '{output_path}' ({len(pairs)} words).")
|
||
|
|
return True
|
||
|
|
|
||
|
|
def main():
|
||
|
|
args = sys.argv if hasattr(sys, 'argv') else []
|
||
|
|
if len(args) < 2 or len(args) > 3:
|
||
|
|
print("Usage: python3 oct2rim.py <input_octaddr_file> [output_rim_file]")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
input_path = args[1]
|
||
|
|
if len(args) == 3:
|
||
|
|
output_path = args[2]
|
||
|
|
else:
|
||
|
|
# Generate default output name by replacing extension with .rim
|
||
|
|
dot_idx = input_path.rfind('.')
|
||
|
|
slash_idx = max(input_path.rfind('/'), input_path.rfind('\\'))
|
||
|
|
if dot_idx > slash_idx:
|
||
|
|
output_path = input_path[:dot_idx] + ".rim"
|
||
|
|
else:
|
||
|
|
output_path = input_path + ".rim"
|
||
|
|
|
||
|
|
success = convert_octaddr_to_rim(input_path, output_path)
|
||
|
|
if not success:
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
main()
|