diff --git a/generator/generator.py b/generator/generator.py index a025163..bb7be74 100644 --- a/generator/generator.py +++ b/generator/generator.py @@ -3,6 +3,7 @@ """ import os from datetime import datetime +from typing import List, Tuple import struct import keystone from generator import strings, trampoline, pnach @@ -40,7 +41,7 @@ class Generator: verbose = False debug = False - def __init__(self, region="ntsc", lang=None, strings_address=None, code_address=None): + def __init__(self, region: str = "ntsc", lang: str = None, strings_address: int = None, code_address: int = None): """ Initializes the generator with the specified region and addresses """ @@ -48,16 +49,16 @@ def __init__(self, region="ntsc", lang=None, strings_address=None, code_address= self.region = region.lower() # Set code + strings addresses and lang based on region - if (region == "ntsc"): + if region == "ntsc": self.strings_adr = SLY2_NTSC_STRINGS_DEFAULT if strings_address is None else strings_address self.code_address = SLY2_NTSC_ASM_DEFAULT if code_address is None else code_address - - if (lang is not None): + + if lang is not None: print("Warning: Language selection is not supported for NTSC region, using English") self.lang = LANGUAGE_IDS["en"] else: self.lang = None - elif (region == "pal"): + elif region == "pal": self.strings_adr = SLY2_PAL_STRINGS_DEFAULT if strings_address is None else strings_address self.code_address = SLY2_PAL_ASM_DEFAULT if code_address is None else code_address self.lang = LANGUAGE_IDS[lang.lower()] if lang is not None else None @@ -68,34 +69,34 @@ def __init__(self, region="ntsc", lang=None, strings_address=None, code_address= self.hook_adr = SLY2_NTSC_HOOK if self.region == "ntsc" else SLY2_PAL_HOOK # Ensure addresses are ints - if (type(self.strings_adr) == str): + if isinstance(self.strings_adr, str): self.strings_adr = int(self.strings_adr, 16) - if (type(self.code_address) == str): + if isinstance(self.code_address, str): self.code_address = int(self.code_address, 16) - if (type(self.hook_adr) == str): + if isinstance(self.hook_adr, str): self.hook_adr = int(self.hook_adr, 16) @staticmethod - def set_verbose(verbose): + def set_verbose(verbose: bool) -> None: """ Sets the verbose flag """ Generator.verbose = verbose - if (verbose): + if verbose: print("Verbose output enabled") @staticmethod - def set_debug(debug): + def set_debug(debug: bool) -> None: """ Sets the debug flag """ Generator.debug = debug - if (debug): + if debug: print("Debug output enabled") @staticmethod - def assemble(asm_code): + def assemble(asm_code: str) -> Tuple[bytes, int]: """ Assembles the given assembly code to binary and converts it to a byte array """ @@ -107,35 +108,35 @@ def assemble(asm_code): machine_code_bytes = struct.pack('B' * len(binary), *binary) # Print machine code bytes if verbose - if (Generator.verbose): + if Generator.verbose: print(f"Assembled {count} bytes of machine code") print("Machine code bytes:") print(binary) # Write machine code bytes to file if debug - if (Generator.debug): + if Generator.debug: print("Writing binary code to file...") with open("./out/mod.bin", "wb+") as file: file.write(machine_code_bytes) return machine_code_bytes, count - def _gen_strings_from_csv(self, csv_file, csv_encoding="utf-8"): + def _gen_strings_from_csv(self, csv_file: str, csv_encoding: str = "utf-8") -> Tuple[pnach.Chunk, List[pnach.Chunk], List[Tuple[int, int]]]: """ Generates the strings pnach and populate string pointers """ - if (self.verbose): + if self.verbose: print(f"Reading strings from {csv_file} to 0x{self.strings_adr:X}...") # Ensure file exists - if (not os.path.isfile(csv_file)): + if not os.path.isfile(csv_file): raise Exception(f"Error: File {csv_file} does not exist") strings_obj = strings.Strings(csv_file, self.strings_adr, csv_encoding) auto_strings_chunk, manual_string_chunks, string_pointers = strings_obj.gen_pnach_chunks() # Print string pointers if verbose - if (self.verbose): + if self.verbose: print("String pointers:") for string_id, string_ptr in string_pointers: print(f"ID: {hex(string_id)} | Ptr: {hex(string_ptr)}") @@ -146,34 +147,34 @@ def _gen_strings_from_csv(self, csv_file, csv_encoding="utf-8"): return auto_strings_chunk, manual_string_chunks, string_pointers - def _gen_asm(self, string_pointers): + def _gen_asm(self, string_pointers: list) -> str: """ Generates the mod assembly code """ - if (self.verbose): + if self.verbose: print("Generating assembly code...") trampoline_obj = trampoline.Trampoline(string_pointers) mips_code = trampoline_obj.gen_asm() # Print assembly code if verbose - if (self.verbose): + if self.verbose: print("Assembly code:") print(mips_code) # Write assembly code to file if debug - if (self.debug): + if self.debug: print("Writing assembly code to file...") with open("./out/mod.asm", "w+", encoding="utf-8") as file: file.write(mips_code) return mips_code - def _gen_code_pnach(self, machine_code_bytes): + def _gen_code_pnach(self, machine_code_bytes: bytes) -> Tuple[pnach.Chunk, pnach.Chunk]: """ Generates the pnach object for the mod and hook code """ - if (self.verbose): + if self.verbose: print("Generating pnach file...") # Generate mod pnach code @@ -181,7 +182,7 @@ def _gen_code_pnach(self, machine_code_bytes): mod_chunk.set_header(f"comment=Writing {len(machine_code_bytes)} bytes of machine code at {hex(self.code_address)}") # Print mod pnach code if verbose - if (self.verbose): + if self.verbose: print("Mod pnach:") print(mod_chunk) @@ -193,14 +194,14 @@ def _gen_code_pnach(self, machine_code_bytes): hook_chunk.set_header(f"comment=Hooking string load function at {hex(self.hook_adr)}") # Print hook pnach code if verbose - if (self.verbose): + if self.verbose: print("Hook pnach:") print(hook_chunk) - return mod_chunk, hook_chunk + return (mod_chunk, hook_chunk) - def generate_pnach_str(self, input_file, mod_name=None, author="Sly String Toolkit", csv_encoding="utf-8"): + def generate_pnach_str(self, input_file: str, mod_name: str = None, author: str = "Sly String Toolkit", csv_encoding: str = "utf-8") -> str: """ Generates the mod pnach text from the given input file """ @@ -233,7 +234,7 @@ def generate_pnach_str(self, input_file, mod_name=None, author="Sly String Toolk final_mod_pnach.add_chunk(chunk) # Print final pnach if verbose - if (self.verbose): + if self.verbose: print("Final mod pnach:") print(final_mod_pnach) @@ -260,20 +261,20 @@ def generate_pnach_str(self, input_file, mod_name=None, author="Sly String Toolk # Add conditional to cancel the function hook if game is set to the wrong language cancel_hook_pnach.add_conditional(0x2E9254, self.lang, 'neq') - if (self.verbose): + if self.verbose: print("Cancel hook pnach:") print(cancel_hook_pnach) return str(final_mod_pnach) + str(cancel_hook_pnach) - def generate_pnach_file(self, input_file, output_dir="./out/", mod_name=None, author="Sly String Toolkit"): + def generate_pnach_file(self, input_file: str, output_dir: str = "./out/", mod_name: str = None, author: str = "Sly String Toolkit", csv_encoding: str = "utf-8") -> None: """ Generates a mod pnach and writes it to a file """ # Create the out folder if it doesn't exist - if (not os.path.exists(output_dir)): - if (self.verbose): + if not os.path.exists(output_dir): + if self.verbose: print("Output directory doesn't exist, creating it...") os.mkdir(output_dir) @@ -285,7 +286,7 @@ def generate_pnach_file(self, input_file, output_dir="./out/", mod_name=None, au mod_name = os.path.splitext(os.path.basename(input_file))[0] # Generate the pnach - pnach_lines = self.generate_pnach_str(input_file, mod_name, author) + pnach_lines = self.generate_pnach_str(input_file, mod_name, author, csv_encoding) # Write the final pnach file outfile = os.path.join(output_dir, f"{crc}.{mod_name}.pnach") diff --git a/generator/pnach.py b/generator/pnach.py index 5b404bd..1ad67f0 100644 --- a/generator/pnach.py +++ b/generator/pnach.py @@ -1,85 +1,86 @@ """ This file contains the Pnach and Chunk classes which are used to generate pnach files. """ +from typing import Type, List class Chunk: """ Chunk class, used to generate chunks of code lines for pnach files. """ - def __init__(self, address, data=b"", header=""): + def __init__(self, address: int, data: bytes = b"", header: str = ""): """ Constructor for the Chunk class. """ self._address = address self._bytes = data self._header = header - + # Getter and setter for address - def get_address(self): + def get_address(self) -> int: """ Returns the address of the chunk. """ return self._address - - def set_address(self, address): + + def set_address(self, address: int) -> None: """ Sets the address of the chunk. """ self._address = address - + # Getter and setter for bytes - def get_bytes(self): + def get_bytes(self) -> bytes: """ Returns the bytes of the chunk. """ return self._bytes - def set_bytes(self, bytes): + def set_bytes(self, new_bytes: bytes) -> None: """ Sets the bytes of the chunk. """ - self._bytes = bytes - + self._bytes = new_bytes + # Getter and setter for header - def get_header(self): + def get_header(self) -> str: """ Returns the header of the chunk. """ return self._header - - def set_header(self, header): + + def set_header(self, header : str) -> None: """ Sets the header of the chunk. """ self._header = header # Getter for size - def get_size(self): + def get_size(self) -> int: """ Returns the size of the chunk in bytes. """ return len(self._bytes) - + size = property(get_size) # Get code lines as array - def get_code_lines(self): + def get_code_lines(self) -> List[str]: """ Returns the code lines of the chunk as an array. """ code_lines = [] for i in range(0, self.size, 4): address = self._address + i - bytes = self._bytes[i:i+4] + word = self._bytes[i:i+4] # reverse byte order - bytes = bytes[::-1] + word = word[::-1] - value = int.from_bytes(bytes, 'big') + value = int.from_bytes(word, 'big') line = f"patch=1,EE,{address:X},extended,{value:08X}" code_lines.append(line) return code_lines - - def __str__(self): + + def __str__(self) -> str: """ Returns a string with the chunk's header + code lines. """ @@ -88,19 +89,19 @@ def __str__(self): chunk_str += self._header + '\n' chunk_str += '\n'.join(self.get_code_lines()) return chunk_str - - def __repr__(self): + + def __repr__(self) -> str: """ Returns a string representation of the chunk. """ return f"Chunk: {self._bytes} bytes at address {self._address:X}" - + class Pnach: """ Pnach class, used to generate pnach files. """ - def __init__(self, address="", data=b"", header=""): + def __init__(self, address: str = "", data: bytes = b"", header: str = ""): """ Constructor for the Pnach class. """ @@ -111,7 +112,7 @@ def __init__(self, address="", data=b"", header=""): self.create_chunk(address, data, header) # Getter for array of lines (no setter) - def get_code_lines(self): + def get_code_lines(self) -> List[str]: """ Returns an array of lines for the pnach file. """ @@ -119,19 +120,19 @@ def get_code_lines(self): # Write each chunk of lines for chunk in self._chunks: code_lines += chunk.get_code_lines() - + return code_lines code_lines = property(get_code_lines) # Getter and setter for header - def get_header(self): + def get_header(self) -> str: """ Returns the header for the pnach file. """ return self._header - def set_header(self, header): + def set_header(self, header: str) -> None: """ Sets the header for the pnach file. """ @@ -140,53 +141,53 @@ def set_header(self, header): header = property(get_header, set_header) # Get and add conditionals - def get_conditionals(self): + def get_conditionals(self) -> dict: """ Returns the conditional for the pnach file. """ return self._conditionals - - def add_conditional(self, address, value, type="eq"): + + def add_conditional(self, address: int, value: int, condition: str = "eq") -> None: """ Sets the conditional for the pnach file. The pnach will only be applied if the given address has the given value. """ # Switch the type to the correct value - if type == "eq": - type = 0 - elif type == "neq": - type = 1 - + if condition == "eq": + condition = 0 + elif condition == "neq": + condition = 1 + # Add the conditional - self._conditionals.update({ "address": address, "value": value, "type": type }) + self._conditionals.update({ "address": address, "value": value, "type": condition }) # Chunk methods - def create_chunk(self, address, data, header=""): + def create_chunk(self, address: int, data: bytes, header: str = "") -> None: """ Adds a chunk of data to the pnach starting at the given address. """ new_chunk = Chunk(address, data, header) self._chunks.append(new_chunk) - def add_chunk(self, chunk): + def add_chunk(self, chunk: Chunk) -> None: """ Adds a chunk to the pnach. """ self._chunks.append(chunk) - def get_chunks(self): + def get_chunks(self) -> List[Chunk]: """ Returns the array of chunks. """ return self._chunks - def merge_chunks(self, other): + def merge_chunks(self, other: Type['Pnach']) -> None: """ Merges another pnach object's chunks into this one. """ self._chunks += other.get_chunks() - def get_chunk_bytes(self): + def get_chunk_bytes(self) -> bytes: """ Returns all the bytes from each chunk. """ @@ -196,7 +197,7 @@ def get_chunk_bytes(self): return chunk_bytes # Write pnach to file - def write_file(self, filename): + def write_file(self, filename) -> None: """ Writes the pnach lines to a file with header. """ @@ -208,7 +209,7 @@ def write_file(self, filename): f.write("\n") # String from pnach lines - def __str__(self): + def __str__(self) -> str: """ Returns a string with the pnach lines. """ @@ -227,7 +228,7 @@ def __str__(self): # If there are conditionals, write conditional lines # Conditionals can only check 0xFF lines at a time, # so we need to split up chunks into groups of 0xFF lines - + # Get conditional values cond_address = self._conditionals['address'] cond_value = self._conditionals['value'] @@ -254,19 +255,25 @@ def __str__(self): pnach_str += f"patch=1,EE,E0{num_lines_to_write:02X}{cond_value:04X},extended,{cond_type:1X}{cond_address:07X}\n" # Write lines to pnach pnach_str += '\n'.join(lines[i:i + num_lines_to_write]) + "\n" - + return pnach_str # String representation of pnach object - def __repr__(self): + def __repr__(self) -> str: """ Returns a string representation of the pnach object. """ return f"Pnach: {len(self._chunks)} chunks ({sum(len(chunk) for chunk in self._chunks)} bytes)" -if __name__ == "__main__": +def main(): + """ + Main function for testing. + """ sample_bytes = b"" with open("./out/mod.bin", "rb") as sample_file: sample_bytes = sample_file.read() - sample_pnach = Pnach(0x202E60B0, bytes) + sample_pnach = Pnach(0x202E60B0, sample_bytes) print(sample_pnach) + +if __name__ == "__main__": + main() diff --git a/generator/strings.py b/generator/strings.py index 7891977..ff7872c 100644 --- a/generator/strings.py +++ b/generator/strings.py @@ -3,6 +3,7 @@ and returns a tuple with the pnach object and the array of pointers to the strings. """ import csv +from typing import List, Tuple from generator import pnach class Strings: @@ -10,7 +11,7 @@ class Strings: This class reads a csv file and generates a pnach file with the strings and popoulates an array of pointers to the strings """ - def __init__(self, csv_file, start_address, csv_encoding='utf-8'): + def __init__(self, csv_file: str, start_address: int, csv_encoding: str = 'utf-8'): """ Initializes the Strings object """ @@ -18,11 +19,11 @@ def __init__(self, csv_file, start_address, csv_encoding='utf-8'): self.csv_encoding = csv_encoding self.start_address = start_address - def gen_pnach_chunks(self): + def gen_pnach_chunks(self) -> Tuple[pnach.Chunk, List[pnach.Chunk], List[Tuple[int, int]]]: """ Generates a pnach file with the strings from the csv file and returns a tuple with the pnach object and the array of pointers to the strings - + CSV rows are in the following format: ,, """ @@ -75,7 +76,7 @@ def gen_pnach_chunks(self): auto_chunk.set_header(f"comment=Writing {len(id_string_pointer_pairs)} strings ({len(auto_chunk.get_bytes())} bytes) at {hex(self.start_address)}") for chunk in manual_chunks: chunk.set_header(f"comment=Writing 1 string ({len(chunk.get_bytes())} bytes) at {hex(self.start_address)}") - + # 4 - Return the pnach lines and the array of pointers return (auto_chunk, manual_chunks, id_string_pointer_pairs) diff --git a/generator/trampoline.py b/generator/trampoline.py index da99e54..8a2cce8 100644 --- a/generator/trampoline.py +++ b/generator/trampoline.py @@ -8,7 +8,7 @@ class Trampoline: """ Trampoline class """ - def __init__(self, id_string_pairs=None): + def __init__(self, id_string_pairs: list = None): """ Initializes the trampoline with the specified ID/string pairs """ @@ -17,7 +17,7 @@ def __init__(self, id_string_pairs=None): else: self.id_string_pairs = id_string_pairs - def gen_asm(self): + def gen_asm(self) -> str: """ Generates the trampoline assembly code from the ID/string pairs on the object """ @@ -41,7 +41,7 @@ def gen_asm(self): return asm - def gen_asm_from_csv(self, filename): + def gen_asm_from_csv(self, filename: str) -> str: """ Read the ID/string pairs from a csv and generates the assembly code """ @@ -55,7 +55,13 @@ def gen_asm_from_csv(self, filename): asm = self.gen_asm() return asm -if __name__ == "__main__": +def main(): + """ + Main function for testing + """ trampoline = Trampoline() trampoline.gen_asm_from_csv("strings.csv") print(trampoline.gen_asm()) + +if __name__ == "__main__": + main() diff --git a/main.py b/main.py index b8c3c16..0f69848 100644 --- a/main.py +++ b/main.py @@ -30,12 +30,12 @@ def main(): args = parser.parse_args() # Make sure the input file exists - if (not os.path.exists(args.input_file)): + if not os.path.exists(args.input_file): print("Usage: python main.py [input_file] [-o output_dir] [-n mod_name]") return - + # Make sure input file is a complete path - if (not os.path.isabs(args.input_file)): + if not os.path.isabs(args.input_file): args.input_file = os.path.abspath(args.input_file) # Set verbose and debug flags on generator @@ -53,7 +53,7 @@ def main(): event_handler = FileSystemEventHandler() event_handler.on_modified = lambda event: generator.generate_pnach_file(args.input_file, args.output_dir, args.mod_name, args.author, args.csv_encoding) observer.schedule(event_handler, path=os.path.dirname(args.input_file), recursive=False) - + # Start the observer and wait for keyboard interrupt observer.start() try: @@ -68,4 +68,5 @@ def main(): generator.generate_pnach_file(args.input_file, args.output_dir, args.mod_name, args.author) if __name__ == "__main__": - main() \ No newline at end of file + main() + \ No newline at end of file diff --git a/utils/assembler.py b/utils/assembler.py index 106dc45..5c29967 100644 --- a/utils/assembler.py +++ b/utils/assembler.py @@ -1,39 +1,46 @@ +""" +This file contains the Assembler class which uses Keystone to turn assembly code into binary +""" import os import struct -import keystone import argparse +from typing import Tuple +import keystone class Assembler(): """ Assembler class, used to translate assembly code to binary """ - def __init__(self, arch, mode): + def __init__(self, arch: int, mode: int): self.arch = arch self.mode = mode self.ks = keystone.Ks(self.arch, self.mode) - def assemble(self, code): + def assemble(self, code: str) -> Tuple[bytes, int]: """ Assembles the given assembly code to binary and converts it to a byte array """ # Assemble the code to binary - encoding, count = self.ks.asm(code) + encoding, count = self.ks.asm(code) # Convert the binary to bytes - bytes = struct.pack('B' * len(encoding), *encoding) + byte_string = struct.pack('B' * len(encoding), *encoding) - return bytes, count - - def assemble_to_file(self, code, output_file): + return byte_string, count + + def assemble_to_file(self, code: str, output_file: str) -> None: """ Assembles the given assembly code to binary and writes it to a file """ - bytes = self.assemble(code) + machine_code_bytes = self.assemble(code) with open(output_file, "wb+") as file: - file.write(bytes) + file.write(machine_code_bytes) def main(): + """ + Main function, calls the assembler with the given arguments + """ # parse command line arguments parser = argparse.ArgumentParser(description='Assemble MIPS assembly code to binary.') parser.add_argument('input_file', type=str, help='input assembly file name') @@ -41,13 +48,13 @@ def main(): args = parser.parse_args() # Make sure the input file exists - if (not os.path.exists(args.input_file)): + if not os.path.exists(args.input_file): print("Usage: python assembler.py [input_file] [-o output_file]") return - + # Init the assembler assembler = Assembler(keystone.KS_ARCH_MIPS, keystone.KS_MODE_MIPS32 + keystone.KS_MODE_LITTLE_ENDIAN) - + # Assemble the code to file code = "" with open (args.input_file, "r") as file: diff --git a/utils/check_pnach_compat.py b/utils/check_pnach_compat.py index 5b68a4c..85bdd60 100644 --- a/utils/check_pnach_compat.py +++ b/utils/check_pnach_compat.py @@ -2,8 +2,9 @@ Script for checking if two pnach files are compatible with each other. """ import argparse +from typing import List -def get_addresses(pnach): +def get_addresses(pnach: str) -> List[int]: """ Gets the memory addresses that are written to by a pnach file. """ @@ -31,10 +32,10 @@ def get_addresses(pnach): address = int(code_part_1[1:8], 16) value = int(code_part_2[0:8], 16) addresses.append(address) - + return addresses -def check_compatiblity(pnach_1, pnach_2): +def check_compatiblity(pnach_1: str, pnach_2: str): """ Checks if two pnach files are compatible with each other. Two pnach files are compatible if they don't both write to the same memory addresses (unless @@ -46,13 +47,13 @@ def check_compatiblity(pnach_1, pnach_2): # get the memory addresses that are written to by each pnach file pnach_1_addresses = get_addresses(pnach_1) pnach_2_addresses = get_addresses(pnach_2) - + # check if any of the addresses are written to by both pnach files matches = [] for address in pnach_1_addresses: if address in pnach_2_addresses: matches.append(address) - + if len(matches) > 0: matches_string = ", ".join([hex(match) for match in matches]) return f"The following {len(matches)} addresses are written to by both pnach files: {matches_string}" @@ -60,6 +61,9 @@ def check_compatiblity(pnach_1, pnach_2): return "The pnach files are compatible!" def main(): + """ + Calls the check_compatiblity function with test pnach files. + """ # get the command line arguments parser = argparse.ArgumentParser(description="Checks if two pnach files are compatible with each other.") parser.add_argument("pnach_file_1", help="The first pnach file.") @@ -79,4 +83,4 @@ def main(): print(result) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/utils/dump_string_table.py b/utils/dump_string_table.py index d249642..e983d49 100644 --- a/utils/dump_string_table.py +++ b/utils/dump_string_table.py @@ -3,7 +3,7 @@ """ import argparse -def dump_string_table(mem, string_table_start, mem_dump_start=0x00000000): +def dump_string_table(mem: bytes, string_table_start: int, mem_dump_start: int = 0x00000000): """ Dumps the string table from a memory dump to a csv file. The original string table is an array list of id/string pointer pairs. @@ -15,27 +15,27 @@ def dump_string_table(mem, string_table_start, mem_dump_start=0x00000000): while True: i += 1 - id, pointer = int.from_bytes(mem[cur:cur+4], "little"), int.from_bytes(mem[cur+4:cur+8], "little") - print(f"{id:X}, {pointer:X}") - if (pointer == 0x000000) or (id > 0xFFFF) or (i > 1000): + string_id, string_pointer = int.from_bytes(mem[cur:cur+4], "little"), int.from_bytes(mem[cur+4:cur+8], "little") + print(f"{string_id:X}, {string_pointer:X}") + if (string_pointer == 0x000000) or (string_id > 0xFFFF) or (i > 1000): break - pointer = pointer - mem_dump_start - + string_pointer = string_pointer - mem_dump_start + string = "" while True: - byte = mem[pointer:pointer+1] + byte = mem[string_pointer:string_pointer+1] if byte == b"\x00" or byte == b'': break char = byte.decode("iso-8859-1") string += char - pointer += 1 + string_pointer += 1 # check if string contains quotation marks, commas, or newlines if "\"" in string or "," in string or "\n" in string: # if so, wrap the string in quotation marks string = f"\"{string}\"" - file.write(f"{id},{string}\n") - cur += 8 + file.write(f"{string_id},{string}\n") + cur += 8 if __name__ == "__main__": # get the command line arguments @@ -47,8 +47,8 @@ def dump_string_table(mem, string_table_start, mem_dump_start=0x00000000): # read the memory dump memory = None - with open(args.mem_file, "rb") as file: - memory = file.read() + with open(args.mem_file, "rb") as mem_file: + memory = mem_file.read() # dump the string table - dump_string_table(memory, args.offset, args.start) \ No newline at end of file + dump_string_table(memory, args.offset, args.start)