Skip to content

Instantly share code, notes, and snippets.

@irismessage
Created June 30, 2022 15:54
Show Gist options
  • Select an option

  • Save irismessage/d97b775c07ba6fcd90972a8f0049f084 to your computer and use it in GitHub Desktop.

Select an option

Save irismessage/d97b775c07ba6fcd90972a8f0049f084 to your computer and use it in GitHub Desktop.
import argparse
import io
from pathlib import Path
READ_PATH = Path('AUDIO001.002')
WRITE_PATH = Path('AUDIO001-shifted.002')
# windows displays kibibytes
KB = 1024
SHIFT = KB * -62
ARG_HELP_DEFAULT = 'default: %(default)s'
# Resource header
# http://sci.sierrahelp.com/Documentation/SCISpecifications/09-SCI1Resources.html
PRESERVE_BYTES = 9
# todo: determine if/how the different file size from the sierraoriginals version affects it
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument('-i', dest='read_path', type=Path, default=READ_PATH, help=ARG_HELP_DEFAULT)
parser.add_argument('-o', dest='write_path', type=Path, default=WRITE_PATH, help=ARG_HELP_DEFAULT)
parser.add_argument('-s', dest='shift', type=int, default=SHIFT, help=ARG_HELP_DEFAULT)
parser.add_argument('-p', dest='file_header_size', type=int, default=PRESERVE_BYTES, help=ARG_HELP_DEFAULT)
return parser
def shift_file(read_path: Path, write_path: Path, shift: int = SHIFT, preserve: int = PRESERVE_BYTES):
if 0 <= shift:
positive_shift = True
else:
positive_shift = False
with io.BytesIO() as buffer:
with open(read_path, 'rb') as file:
buffer.write(file.read(preserve))
if positive_shift:
file.seek(shift + preserve)
buffer.write(file.read())
file.seek(preserve)
buffer.write(file.read(shift))
else:
file.seek(shift, io.SEEK_END)
shift_end = file.tell()
buffer.write(file.read())
file.seek(preserve)
buffer.write(file.read(shift_end - preserve))
buffer.seek(0)
with open(write_path, 'wb') as file:
file.write(buffer.read())
def append_stem(path: Path, string: str) -> Path:
return path.with_stem(path.stem + string)
def test():
read_path = Path('84-0.txt')
shift_file(read_path, append_stem(read_path, '-shift-none'), 0)
shift_file(read_path, append_stem(read_path, '-shift-pos'), SHIFT)
shift_file(read_path, append_stem(read_path, '-shift-neg'), -SHIFT)
def main():
parser = get_parser()
args = parser.parse_args()
print('shift_file.py: in:', args.read_path)
print('shift_file.py: shift:', args.shift)
print('shift_file.py: preserve:', args.file_header_size)
shift_file(args.read_path, args.write_path, args.shift, args.file_header_size)
print('shift_file.py: out:', args.write_path)
if __name__ == '__main__':
main()
import argparse
import io
import shutil
import struct
from pathlib import Path
# http://sci.sierrahelp.com/Documentation/SCISpecifications/09-SCI1Resources.html
# todo: feature to re-shuffle in place every so often
ENTRY_3_BYTE = struct.Struct('<sH')
ENTRY_6_BYTE = struct.Struct('<HL')
READ_PATH_DEFAULT = Path('AUDIO001.MAP')
SHIFT_DEFAULT = 1024 * 256
ARG_HELP_DEFAULT = 'default: %(default)s'
ABSOLUTE_OFFSET_MIN = 9
# ABSOLUTE_OFFSET_MAX = (2 ** 28) - 1
ABSOLUTE_OFFSET_MAX = 14179531
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument('-i', dest='read_path', type=Path, default=READ_PATH_DEFAULT, help=ARG_HELP_DEFAULT)
parser.add_argument('-o', dest='write_path', type=Path, default=READ_PATH_DEFAULT, help=ARG_HELP_DEFAULT)
parser.add_argument('-s', dest='shift', type=int, default=SHIFT_DEFAULT, help=ARG_HELP_DEFAULT)
return parser
def overflow_add(a: int, b: int, floor: int, ceiling: int) -> int:
c = a + b
if c < floor:
c += ceiling + 1
elif ceiling < c:
c -= ceiling + 1
return c
def edit_offset(unpacked: tuple, shift: int) -> tuple:
# nasty little goblin ms-dos file specification using individual bits
component = unpacked[1]
component_binary = '{:>32b}'.format(component)
offset_binary = component_binary[4:]
offset = int(offset_binary, base=2)
# the important part!
offset_new = overflow_add(offset, shift, floor=ABSOLUTE_OFFSET_MIN, ceiling=ABSOLUTE_OFFSET_MAX)
offset_binary_new = '{:0>28b}'.format(offset_new)
component_binary_new = component_binary[:4] + offset_binary_new
component_new = int(component_binary_new, base=2)
to_pack = (unpacked[0], component_new)
return to_pack
def shift_map(read_path: Path, shift: int) -> bytes:
with io.BytesIO() as buffer:
with open(read_path, 'rb') as read_file:
while True:
read_3_bytes = read_file.read(3)
unpacked = ENTRY_3_BYTE.unpack(read_3_bytes)
buffer.write(read_3_bytes)
if unpacked[0] == b'\xFF': # termination sequence
break
while True:
read_6_bytes = read_file.read(6)
unpacked = ENTRY_6_BYTE.unpack(read_6_bytes)
if unpacked[0] == 65535:
buffer.write(read_6_bytes)
buffer.write(read_file.read())
break
to_pack = edit_offset(unpacked, shift)
write_6_bytes = ENTRY_6_BYTE.pack(*to_pack)
buffer.write(write_6_bytes)
buffer.seek(0)
return buffer.read()
def sanity_check():
parser = get_parser()
args = parser.parse_args()
new_bytes = shift_map(args.read_path, 0)
# todo: debug this
if new_bytes == args.read_path.read_bytes():
print('shift_map.py: sanity: pass')
else:
print('shift_map.py: sanity: fail')
def main():
parser = get_parser()
args = parser.parse_args()
print('shift_map.py: in:', args.read_path)
print('shift_map.py: shift:', args.shift)
new_bytes = shift_map(args.read_path, args.shift)
if args.write_path == args.read_path:
backup_path = args.read_path.with_stem(args.read_path.stem + '-backup')
shutil.copyfile(args.read_path, backup_path)
print('shift_map.py: backup:', backup_path)
with open(args.write_path, 'wb') as write_file:
write_file.write(new_bytes)
print('shift_map.py: out:', args.write_path)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment