def decompile_script(rom_filename, project_path, progress_bar=None): if not os.path.isdir(project_path): raise RuntimeError("Project directory \"" + project_path + "\" is not a directory.") if not os.path.isfile(rom_filename): raise RuntimeError("Rom \"" + rom_filename + "\" is not a file.") rom = Rom() rom.from_file(rom_filename) if rom.type != "Earthbound": raise CoilSnakeError( "Cannot decompile script of a non-Earthbound rom. A {} rom was supplied." .format(rom.type)) del rom project_ccscript_path = os.path.join(project_path, "ccscript") start_time = time.time() rom_file = open(rom_filename, "rb") try: ccsw = CCScriptWriter(rom_file, project_ccscript_path, False) ccsw.loadDialogue(True) ccsw.processDialogue() ccsw.outputDialogue(True) except Exception as inst: log.exception("Error") else: log.info("Decompiled script to {} in {:.2f}s".format( project_path, time.time() - start_time)) finally: rom_file.close()
def decompile_script(rom_filename, project_path, progress_bar=None): if not os.path.isdir(project_path): raise RuntimeError("Project directory \"" + project_path + "\" is not a directory.") if not os.path.isfile(rom_filename): raise RuntimeError("Rom \"" + rom_filename + "\" is not a file.") rom = Rom() rom.from_file(rom_filename) if rom.type != "Earthbound": raise CoilSnakeError("Cannot decompile script of a non-Earthbound rom. A {} rom was supplied.".format( rom.type)) del rom project_ccscript_path = os.path.join(project_path, "ccscript") start_time = time.time() rom_file = open(rom_filename, "rb") try: ccsw = CCScriptWriter(rom_file, project_ccscript_path, False) ccsw.loadDialogue(True) ccsw.processDialogue() ccsw.outputDialogue(True) except Exception as inst: log.exception("Error") else: log.info("Decompiled script to {} in {:.2f}s".format(project_path, time.time() - start_time)) finally: rom_file.close()
def expand_rom(root, ex=False): rom = Rom() filename = tkFileDialog.askopenfilename( parent=root, initialdir=os.path.expanduser("~"), title="Select a ROM to expand", filetypes=ROM_FILETYPES) if filename: rom.from_file(filename) if (not ex and len(rom) >= 0x400000) or (ex and (len(rom) >= 0x600000)): tkMessageBox.showerror( parent=root, title="Error", message="This ROM is already expanded.") else: if ex: rom.expand(0x600000) else: rom.expand(0x400000) rom.to_file(filename) del rom tkMessageBox.showinfo( parent=root, title="Expansion Successful", message="Your ROM was expanded.")
def decompile_rom(rom_filename, project_path, progress_bar=None): modules = load_modules() rom = Rom() rom.from_file(rom_filename) project = Project() project.load(os.path.join(project_path, PROJECT_FILENAME), rom.type) compatible_modules = [(name, clazz) for name, clazz in modules if clazz.is_compatible_with_romtype(rom.type)] tick_amount = 1.0/(2*len(compatible_modules)) log.info("Decompiling ROM {}".format(rom_filename)) decompile_start_time = time.time() for module_name, module_class in compatible_modules: if not module_class.is_compatible_with_romtype(rom.type): continue log.info("Decompiling {}...".format(module_class.NAME)) start_time = time.time() with module_class() as module: module.read_from_rom(rom) if progress_bar: progress_bar.tick(tick_amount) module.write_to_project(lambda x, y: project.get_resource(module_name, x, y, 'wb')) if progress_bar: progress_bar.tick(tick_amount) log.info("Finished decompiling {} in {:.2f}s".format(module_class.NAME, time.time() - start_time)) log.debug("Saving Project") project.write(os.path.join(project_path, PROJECT_FILENAME)) log.info("Decompiled to {} in {:.2f}s".format(project_path, time.time() - decompile_start_time))
def upgrade_project(project_path, base_rom_filename, progress_bar=None): if not os.path.isdir(project_path): raise RuntimeError("Project directory \"" + project_path + "\" is not a directory.") if not os.path.isfile(base_rom_filename): raise RuntimeError("Base Rom \"" + base_rom_filename + "\" is not a file.") modules = load_modules() # Open project project_filename = os.path.join(project_path, PROJECT_FILENAME) project = Project() project.load(project_filename) check_if_project_too_new(project) if project.version == FORMAT_VERSION: log.info("Project is already up to date.") return log.info("Upgrading project from version {} to {}".format( get_version_name(project.version), get_version_name(FORMAT_VERSION))) upgrade_start_time = time.time() rom = Rom() rom.from_file(base_rom_filename) check_if_types_match(project=project, rom=rom) compatible_modules = [(name, clazz) for name, clazz in modules if clazz.is_compatible_with_romtype(rom.type)] tick_amount = 1.0 / len(compatible_modules) for module_name, module_class in compatible_modules: log.info("Upgrading {}...".format(module_class.NAME)) start_time = time.time() with module_class() as module: module.upgrade_project( project.version, FORMAT_VERSION, rom, lambda x, y, astext=False: project.get_resource( module_name, x, y, 'rt' if astext else 'rb', 'utf-8' if astext else None), lambda x, y, astext=False: project.get_resource( module_name, x, y, 'wt' if astext else 'wb', 'utf-8' if astext else None, '\n' if astext else None), lambda x: project.delete_resource(module_name, x)) if progress_bar: progress_bar.tick(tick_amount) log.info("Finished upgrading {} in {:.2f}s".format( module_class.NAME, time.time() - start_time)) project.version = FORMAT_VERSION project.write(project_filename) log.info("Upgraded {} in {:.2f}s".format(project_path, time.time() - upgrade_start_time))
class TestEbModule(BaseTestCase): """ A test class for the EbModule module """ def setup(self): self.rom = Rom() self.rom.from_file(os.path.join(TEST_DATA_DIR, "roms", "EB_fake_24mbit.smc")) @nottest def test_decomp(self, decomp): onett_map = array.array('B') with Rom() as eb_rom: eb_rom.from_file(os.path.join(TEST_DATA_DIR, "roms", "real_EarthBound.smc")) onett_map.fromlist(decomp(eb_rom, 0x2021a8)) assert_equal(len(onett_map), 18496) assert_equal(crc32(onett_map), 739047015) @nottest def test_comp(self, comp, decomp): a = array.array('B') with open(os.path.join(TEST_DATA_DIR, "binaries", "compressible.bin"), 'rb') as f: a.fromstring(f.read()) assert_equal(len(a), 18496) uncompressed_data = a.tolist() compressed_data = comp(uncompressed_data) assert_equal(len(compressed_data), 58) with Rom() as fake_eb_rom: fake_eb_rom.from_file(os.path.join(TEST_DATA_DIR, "roms", "EB_fake_32mbit.smc")) fake_eb_rom[0x300000:0x300000 + len(compressed_data)] = compressed_data reuncompressed_data = decomp(fake_eb_rom, 0x300000) assert_equal(len(reuncompressed_data), len(uncompressed_data)) assert_equal(reuncompressed_data, uncompressed_data) @nottest def _test_python_comp(self): self.test_comp(EbModule._comp, EbModule.decomp) @nottest def _test_python_decomp(self): self.test_decomp(EbModule._decomp) def test_native_comp(self): self.test_comp(native_comp.comp, native_comp.decomp) def test_native_decomp(self): self.test_decomp(native_comp.decomp) def test_default_comp(self): self.test_comp(EbModule.comp, EbModule.decomp) def test_default_decomp(self): self.test_decomp(EbModule.decomp)
def expand(romfile, ex=False): rom = Rom() rom.from_file(romfile) if (not ex and len(rom) >= 0x400000) or (ex and (len(rom) >= 0x600000)): return False else: if ex: rom.expand(0x600000) else: rom.expand(0x400000) rom.to_file(romfile) del rom return True
def upgrade_project(project_path, base_rom_filename, progress_bar=None): if not os.path.isdir(project_path): raise RuntimeError("Project directory \"" + project_path + "\" is not a directory.") if not os.path.isfile(base_rom_filename): raise RuntimeError("Base Rom \"" + base_rom_filename + "\" is not a file.") modules = load_modules() # Open project project_filename = os.path.join(project_path, PROJECT_FILENAME) project = Project() project.load(project_filename) check_if_project_too_new(project) if project.version == FORMAT_VERSION: log.info("Project is already up to date.") return log.info("Upgrading project from version {} to {}".format( get_version_name(project.version), get_version_name(FORMAT_VERSION))) upgrade_start_time = time.time() rom = Rom() rom.from_file(base_rom_filename) check_if_types_match(project=project, rom=rom) compatible_modules = [(name, clazz) for name, clazz in modules if clazz.is_compatible_with_romtype(rom.type)] tick_amount = 1.0/len(compatible_modules) for module_name, module_class in compatible_modules: log.info("Upgrading {}...".format(module_class.NAME)) start_time = time.time() with module_class() as module: module.upgrade_project(project.version, FORMAT_VERSION, rom, lambda x, y, astext=False : project.get_resource(module_name, x, y, 'rt' if astext else 'rb', 'utf-8' if astext else None), lambda x, y, astext=False: project.get_resource(module_name, x, y, 'wt' if astext else 'wb', 'utf-8' if astext else None, '\n' if astext else None), lambda x: project.delete_resource(module_name, x)) if progress_bar: progress_bar.tick(tick_amount) log.info("Finished upgrading {} in {:.2f}s".format(module_class.NAME, time.time() - start_time)) project.version = FORMAT_VERSION project.write(project_filename) log.info("Upgraded {} in {:.2f}s".format(project_path, time.time() - upgrade_start_time))
def do_compile(self, project_entry, base_rom_entry, rom_entry): base_rom = base_rom_entry.get() rom = rom_entry.get() project = project_entry.get() if base_rom and rom and project: self.save_default_tab() base_rom_rom = Rom() base_rom_rom.from_file(base_rom) if base_rom_rom.type == "Earthbound" and len(base_rom_rom) == 0x300000: confirm = tkinter.messagebox.askquestion("Expand Your Base ROM?", "You are attempting to compile using a base ROM which is " "unexpanded. It is likely that this will not succeed, as CoilSnake " "needs the extra space in an expanded ROM to store additional data." "\n\n" "Would you like to expand this base ROM before proceeding? This " "will permanently overwrite your base ROM.", icon='warning') if confirm == "yes": base_rom_rom.expand(0x400000) base_rom_rom.to_file(base_rom) del base_rom_rom # Update the GUI self.console.clear() self.disable_all_components() self.progress_bar.clear() log.info("Starting compilation...") thread = Thread(target=self._do_compile_help, args=(project, base_rom, rom)) thread.start()
def upgrade_project(project_path, base_rom_filename, progress_bar=None): modules = load_modules() # Open project project_filename = os.path.join(project_path, PROJECT_FILENAME) project = Project() project.load(project_filename) check_if_project_too_new(project) if project.version == FORMAT_VERSION: log.info("Project is already up to date.") return log.info( "Upgrading project from version {} to {}".format( get_version_name(project.version), get_version_name(FORMAT_VERSION) ) ) upgrade_start_time = time.time() rom = Rom() rom.from_file(base_rom_filename) check_if_types_match(project=project, rom=rom) compatible_modules = [(name, clazz) for name, clazz in modules if clazz.is_compatible_with_romtype(rom.type)] tick_amount = 1.0 / len(compatible_modules) for module_name, module_class in compatible_modules: log.info("Upgrading {}...".format(module_class.NAME)) start_time = time.time() with module_class() as module: module.upgrade_project( project.version, FORMAT_VERSION, rom, lambda x, y: project.get_resource(module_name, x, y, "rb"), lambda x, y: project.get_resource(module_name, x, y, "wb"), lambda x: project.delete_resource(module_name, x), ) if progress_bar: progress_bar.tick(tick_amount) log.info("Finished upgrading {} in {:.2f}s".format(module_class.NAME, time.time() - start_time)) project.version = FORMAT_VERSION project.write(project_filename) log.info("Upgraded {} in {:.2f}s".format(project_path, time.time() - upgrade_start_time))
def main(): parser = argparse.ArgumentParser() parser.add_argument('input', metavar='INPUT', type=argparse.FileType('rb'), help="an unexpanded EarthBound ROM") parser.add_argument('output', metavar='OUTPUT', type=argparse.FileType('wb'), help="the expanded EarthBound ROM") parser.add_argument('-ex', action="store_true", default=False, help="expand again to 48 megabits") args = parser.parse_args() r = Rom.Rom() r.load(args.input) if args.ex: r.expand(0x600000) else: r.expand(0x400000) r.save(args.output)
def test_write_to_rom(self): with Rom() as rom: rom.from_file(os.path.join(TEST_DATA_DIR, 'roms', 'real_EarthBound.smc')) self.module.read_from_rom(rom) def resource_open(a, b): return self.temporary_wo_file self.module.write_to_project(resource_open) self.temporary_wo_file = open(self.temporary_wo_file_name) self.module.read_from_project(resource_open) with Rom() as rom: rom.from_file(os.path.join(TEST_DATA_DIR, 'roms', 'real_EarthBound.smc')) self.module.write_to_rom(rom) self.test_read_from_rom_using_rom(rom)
def strip_header(romfile): if romfile: with Rom() as rom: rom.from_file(romfile) rom.to_file(romfile) return True else: return False
def test_decomp(self, decomp): onett_map = array.array('B') with Rom() as eb_rom: eb_rom.from_file(os.path.join(TEST_DATA_DIR, "roms", "real_EarthBound.smc")) onett_map.fromlist(decomp(eb_rom, 0x2021a8)) assert_equal(len(onett_map), 18496) assert_equal(crc32(onett_map), 739047015)
def do_compile(self, project_entry, base_rom_entry, rom_entry): base_rom = base_rom_entry.get() rom = rom_entry.get() project = project_entry.get() if base_rom and rom and project: self.save_default_tab() base_rom_rom = Rom() base_rom_rom.from_file(base_rom) if base_rom_rom.type == "Earthbound" and len(base_rom_rom) == 0x300000: confirm = tkMessageBox.askquestion("Expand Your Base ROM?", "You are attempting to compile using a base ROM which is " "unexpanded. It is likely that this will not succeed, as CoilSnake " "needs the extra space in an expanded ROM to store additional data." "\n\n" "Would you like to expand this base ROM before proceeding? This " "will permanently overwrite your base ROM.", icon='warning') if confirm == "yes": base_rom_rom.expand(0x400000) base_rom_rom.to_file(base_rom) del base_rom_rom # Update the GUI self.console.clear() self.disable_all_components() self.progress_bar.clear() log.info("Starting compilation...") thread = Thread(target=self._do_compile_help, args=(project, base_rom, rom)) thread.start()
def decompile_rom(rom_filename, project_path, progress_bar=None): if not os.path.isfile(rom_filename): raise RuntimeError("Rom \"" + rom_filename + "\" is not a file.") modules = load_modules() rom = Rom() rom.from_file(rom_filename) project = Project() project.load(os.path.join(project_path, PROJECT_FILENAME), rom.type) compatible_modules = [(name, clazz) for name, clazz in modules if clazz.is_compatible_with_romtype(rom.type)] tick_amount = 1.0 / (2 * len(compatible_modules)) log.info("Decompiling ROM {}".format(rom_filename)) decompile_start_time = time.time() for module_name, module_class in compatible_modules: if not module_class.is_compatible_with_romtype(rom.type): continue log.info("Decompiling {}...".format(module_class.NAME)) start_time = time.time() with module_class() as module: module.read_from_rom(rom) if progress_bar: progress_bar.tick(tick_amount) module.write_to_project( lambda x, y, astext=False: project.get_resource( module_name, x, y, 'wt' if astext else 'wb', 'utf-8' if astext else None, '\n' if astext else None)) if progress_bar: progress_bar.tick(tick_amount) log.info("Finished decompiling {} in {:.2f}s".format( module_class.NAME, time.time() - start_time)) log.debug("Saving Project") project.write(os.path.join(project_path, PROJECT_FILENAME)) log.info("Decompiled to {} in {:.2f}s".format( project_path, time.time() - decompile_start_time))
class TestEbRom(BaseTestCase): def setup(self): self.reference_rom = Rom() self.reference_rom.from_file(join(TEST_DATA_DIR, "roms", "real_EarthBound.smc")) def teardown(self): del self.reference_rom def test_fixing_rom_variants(self): for f in listdir(join(TEST_DATA_DIR, "roms", "variants")): if is_rom_filename(f): variant = EbRom() variant.from_file(join(TEST_DATA_DIR, "roms", "variants", f)) assert_equal(self.reference_rom.data, variant.data) assert_equal(self.reference_rom.size, variant.size) assert_equal(EbRom.REFERENCE_MD5, hashlib.md5(variant.data.tostring()).hexdigest())
def strip_header_from_rom(root): filename = tkFileDialog.askopenfilename( parent=root, initialdir=os.path.expanduser("~"), title="Select a ROM from which to remove a header", filetypes=ROM_FILETYPES) if filename: with Rom() as rom: rom.from_file(filename) rom.to_file(filename) tkMessageBox.showinfo(parent=root, title="Header Removal Successful", message="Your ROM's header was removed.")
def add_header_to_rom(root): filename = tkFileDialog.askopenfilename( parent=root, initialdir=os.path.expanduser("~"), title="Select a ROM to which to add a header", filetypes=ROM_FILETYPES) if filename: with Rom() as rom: rom.from_file(filename) rom.add_header() rom.to_file(filename) tkMessageBox.showinfo(parent=root, title="Header Addition Successful", message="Your ROM was given a header.")
def test_comp(self, comp, decomp): a = array.array('B') with open(os.path.join(TEST_DATA_DIR, "binaries", "compressible.bin"), 'rb') as f: a.fromstring(f.read()) assert_equal(len(a), 18496) uncompressed_data = a.tolist() compressed_data = comp(uncompressed_data) assert_equal(len(compressed_data), 58) with Rom() as fake_eb_rom: fake_eb_rom.from_file(os.path.join(TEST_DATA_DIR, "roms", "EB_fake_32mbit.smc")) fake_eb_rom[0x300000:0x300000 + len(compressed_data)] = compressed_data reuncompressed_data = decomp(fake_eb_rom, 0x300000) assert_equal(len(reuncompressed_data), len(uncompressed_data)) assert_equal(reuncompressed_data, uncompressed_data)
def compile_project(project_path, base_rom_filename, output_rom_filename, ccscript_offset=None, progress_bar=None): if not os.path.isdir(project_path): raise RuntimeError("Project directory \"" + project_path + "\" is not a directory.") if not os.path.isfile(base_rom_filename): raise RuntimeError("Base Rom \"" + base_rom_filename + "\" is not a file.") modules = load_modules() project_filename = os.path.join(project_path, PROJECT_FILENAME) project = Project() project.load(project_filename) check_if_project_too_old(project) check_if_project_too_new(project) if base_rom_filename != output_rom_filename: copyfile(base_rom_filename, output_rom_filename) # Compile scripts using CCScript script_filenames = [ os.path.join(project_path, "ccscript", x) for x in os.listdir(os.path.join(project_path, "ccscript")) if x.lower().endswith('.ccs') ] if script_filenames: log.info("Compiling CCScript") if not ccscript_offset: ccscript_offset = "F10000" elif type(ccscript_offset) == int: ccscript_offset = "{:x}".format(ccscript_offset) ccc_args = [ "-n", "--libs", ccscript_library_path(), "--summary", os.path.join(project_path, "ccscript", "summary.txt"), "-s", ccscript_offset, "-o", output_rom_filename ] + script_filenames ccc_returncode, ccc_log = ccc(ccc_args) if ccc_returncode == 0: log.info("Finished compiling CCScript") else: raise CCScriptCompilationError( "CCScript compilation failed with output:\n" + ccc_log) rom = Rom() rom.from_file(output_rom_filename) check_if_types_match(project=project, rom=rom) compatible_modules = [(name, clazz) for name, clazz in modules if clazz.is_compatible_with_romtype(rom.type)] tick_amount = 1.0 / (2 * len(compatible_modules)) log.info("Compiling Project {}".format(project_path)) compile_start_time = time.time() for module_name, module_class in modules: if module_class.is_compatible_with_romtype(rom.type): for free_range in module_class.FREE_RANGES: rom.deallocate(free_range) for module_name, module_class in modules: if not module_class.is_compatible_with_romtype(rom.type): continue log.info("Compiling {}...".format(module_class.NAME)) start_time = time.time() with module_class() as module: module.read_from_project( lambda x, y, astext=False: project.get_resource( module_name, x, y, 'rt' if astext else 'rb', 'utf-8' if astext else None)) if progress_bar: progress_bar.tick(tick_amount) module.write_to_rom(rom) if progress_bar: progress_bar.tick(tick_amount) log.info("Finished compiling {} in {:.2f}s".format( module_class.NAME, time.time() - start_time)) log.debug("Saving ROM") rom.to_file(output_rom_filename) log.info("Compiled to {} in {:.2f}s, finished at {}".format( output_rom_filename, time.time() - compile_start_time, datetime.now().strftime('%I:%M:%S %p')))
class TestRom(TestAllocatableBlock): def setup(self): self.block = Rom() def test_detect_rom_type(self): self.block.from_file(os.path.join(TEST_DATA_DIR, "roms", "EB_fake_noheader.smc")) assert_equal(self.block.type, "Earthbound") self.block.from_file(os.path.join(TEST_DATA_DIR, "roms", "EB_fake_header.smc")) assert_equal(self.block.type, "Earthbound") self.block.from_file(os.path.join(TEST_DATA_DIR, "binaries", "empty.bin")) assert_equal(self.block.type, ROM_TYPE_NAME_UNKNOWN) self.block.from_file(os.path.join(TEST_DATA_DIR, "binaries", "1kb_null.bin")) assert_equal(self.block.type, ROM_TYPE_NAME_UNKNOWN) self.block.from_file(os.path.join(TEST_DATA_DIR, "roms", "EB_fake_header.smc")) assert_equal(self.block.type, "Earthbound") self.block.from_file(os.path.join(TEST_DATA_DIR, "roms", "real_EarthBound.smc")) assert_equal(self.block.type, "Earthbound") @raises(NotImplementedError) def test_add_header_unknown(self): self.block.from_list([0]) self.block.add_header() def test_add_header_eb(self): self.block.from_file(os.path.join(TEST_DATA_DIR, "roms", "real_EarthBound.smc")) assert_equal(self.block.size, 0x300000) self.block.add_header() assert_equal(self.block.size, 0x300200) assert_equal(len(self.block.data), 0x300200) assert_equal(self.block[0:0x200].to_list(), [0] * 0x200) @raises(NotImplementedError) def test_expand_unknown(self): self.block.from_list([0]) self.block.expand(0x123456) def test_expand_eb(self): self.block.from_file(os.path.join(TEST_DATA_DIR, "roms", "real_EarthBound.smc")) assert_raises(InvalidArgumentError, self.block.expand, 0x400200) assert_raises(InvalidArgumentError, self.block.expand, 0x300000) self.block.expand(0x400000) assert_equal(self.block.size, 0x400000) assert_equal(len(self.block.data), 0x400000) assert_list_equal(self.block[0x300000:0x400000].to_list(), [0] * 0x100000) self.block.expand(0x600000) assert_equal(self.block.size, 0x600000) assert_equal(len(self.block.data), 0x600000) assert_equal(self.block[0xffd5], 0x25) assert_equal(self.block[0xffd7], 0x0d) self.block.from_file(os.path.join(TEST_DATA_DIR, "roms", "real_EarthBound.smc")) self.block.expand(0x600000) assert_equal(self.block.size, 0x600000) assert_equal(len(self.block.data), 0x600000) assert_equal(self.block[0xffd5], 0x25) assert_equal(self.block[0xffd7], 0x0d)
def setup(self): self.block = Rom()
def patch_rom(clean_rom_filename, patched_rom_filename, patch_filename, headered, progress_bar=None): if clean_rom_filename != patched_rom_filename: copyfile(clean_rom_filename, patched_rom_filename) log.info("Patching ROM {} with patch {}".format(patched_rom_filename, patch_filename)) patching_start_time = time.time() if patch_filename.endswith(".ips"): output_rom = Rom() output_rom.from_file(clean_rom_filename) patch = IpsPatch() elif patch_filename.endswith(".ebp"): output_rom = EbRom() output_rom.from_file(clean_rom_filename) patch = EbpPatch() else: raise CoilSnakeError("Unknown patch format.") # Load the patch and expand the ROM as needed add_header = headered and not isinstance(patch, EbpPatch) extra = int(add_header)*0x200 # 0x200 if a header will be added, 0 otherwise patch.load(patch_filename) if isinstance(patch, EbpPatch): log.info("Patch: {title} by {author}".format(**patch.metadata)) if patch.last_offset_used > len(output_rom) + extra: if patch.last_offset_used < 0x400000 + extra: output_rom.expand(0x400000) elif patch.last_offset_used < 0x600000 + extra: output_rom.expand(0x600000) else: output_rom.expand(patch.last_offset_used) # If the user specified the patch was made for a headered ROM, add a header # to the ROM if add_header: output_rom.add_header() # Apply the patch and write out the patched ROM patch.apply(output_rom) if add_header: # Remove the header that was added, so that we're always dealing with # unheadered ROMs in the end output_rom.data = output_rom.data[0x200:] output_rom.size -= 0x200 output_rom.to_file(patched_rom_filename) log.info("Patched to {} in {:.2f}s".format(patched_rom_filename, time.time() - patching_start_time))
def setup(self): self.reference_rom = Rom() self.reference_rom.from_file(join(TEST_DATA_DIR, "roms", "real_EarthBound.smc"))
def test_read_from_rom(self): with Rom() as rom: rom.from_file(os.path.join(TEST_DATA_DIR, 'roms', 'real_EarthBound.smc')) self.test_read_from_rom_using_rom(rom)
def patch_rom(clean_rom_filename, patched_rom_filename, patch_filename, headered, progress_bar=None): if not os.path.isfile(clean_rom_filename): raise RuntimeError("Clean Rom \"" + clean_rom_filename + "\" is not a file.") if not os.path.isfile(patch_filename): raise RuntimeError("Patch \"" + patch_filename + "\" is not a file.") if clean_rom_filename != patched_rom_filename: copyfile(clean_rom_filename, patched_rom_filename) log.info("Patching ROM {} with patch {}".format(patched_rom_filename, patch_filename)) patching_start_time = time.time() if patch_filename.endswith(".ips"): output_rom = Rom() output_rom.from_file(clean_rom_filename) patch = IpsPatch() elif patch_filename.endswith(".ebp"): output_rom = EbRom() output_rom.from_file(clean_rom_filename) patch = EbpPatch() else: raise CoilSnakeError("Unknown patch format.") # Load the patch and expand the ROM as needed add_header = headered and not isinstance(patch, EbpPatch) extra = int( add_header) * 0x200 # 0x200 if a header will be added, 0 otherwise patch.load(patch_filename) if isinstance(patch, EbpPatch): log.info("Patch: {title} by {author}".format(**patch.metadata)) if patch.last_offset_used > len(output_rom) + extra: if patch.last_offset_used < 0x400000 + extra: output_rom.expand(0x400000) elif patch.last_offset_used < 0x600000 + extra: output_rom.expand(0x600000) else: output_rom.expand(patch.last_offset_used) # If the user specified the patch was made for a headered ROM, add a header # to the ROM if add_header: output_rom.add_header() # Apply the patch and write out the patched ROM patch.apply(output_rom) if add_header: # Remove the header that was added, so that we're always dealing with # unheadered ROMs in the end output_rom.data = output_rom.data[0x200:] output_rom.size -= 0x200 output_rom.to_file(patched_rom_filename) log.info("Patched to {} in {:.2f}s".format( patched_rom_filename, time.time() - patching_start_time))
def setup(self): self.rom = Rom() self.rom.from_file(os.path.join(TEST_DATA_DIR, "roms", "EB_fake_24mbit.smc"))
def compile_project(project_path, base_rom_filename, output_rom_filename, ccscript_offset=None, progress_bar=None): modules = load_modules() project_filename = os.path.join(project_path, PROJECT_FILENAME) project = Project() project.load(project_filename) check_if_project_too_old(project) check_if_project_too_new(project) if base_rom_filename != output_rom_filename: copyfile(base_rom_filename, output_rom_filename) # Compile scripts using CCScript script_filenames = [os.path.join(project_path, "ccscript", x) for x in os.listdir(os.path.join(project_path, "ccscript")) if x.lower().endswith('.ccs')] if script_filenames: log.info("Compiling CCScript") if not ccscript_offset: ccscript_offset = "F10000" elif type(ccscript_offset) == int: ccscript_offset = "{:x}".format(ccscript_offset) ccc_args = ["-n", "--libs", ccscript_library_path(), "--summary", os.path.join(project_path, "ccscript", "summary.txt"), "-s", ccscript_offset, "-o", output_rom_filename] + script_filenames ccc_returncode, ccc_log = ccc(ccc_args) if ccc_returncode == 0: log.info("Finished compiling CCScript") else: raise CCScriptCompilationError("CCScript compilation failed with output:\n" + ccc_log) rom = Rom() rom.from_file(output_rom_filename) check_if_types_match(project=project, rom=rom) compatible_modules = [(name, clazz) for name, clazz in modules if clazz.is_compatible_with_romtype(rom.type)] tick_amount = 1.0/(2*len(compatible_modules)) log.info("Compiling Project {}".format(project_path)) compile_start_time = time.time() for module_name, module_class in modules: if module_class.is_compatible_with_romtype(rom.type): for free_range in module_class.FREE_RANGES: rom.deallocate(free_range) for module_name, module_class in modules: if not module_class.is_compatible_with_romtype(rom.type): continue log.info("Compiling {}...".format(module_class.NAME)) start_time = time.time() with module_class() as module: module.read_from_project(lambda x, y: project.get_resource(module_name, x, y, 'rb')) if progress_bar: progress_bar.tick(tick_amount) module.write_to_rom(rom) if progress_bar: progress_bar.tick(tick_amount) log.info("Finished compiling {} in {:.2f}s".format(module_class.NAME, time.time() - start_time)) log.debug("Saving ROM") rom.to_file(output_rom_filename) log.info("Compiled to {} in {:.2f}s, finished at {}".format( output_rom_filename, time.time() - compile_start_time, datetime.now().strftime('%I:%M:%S %p')))
def create(self, clean_rom, hacked_rom, patch_path): """Creates an IPS patch from the source and target ROMs.""" with Rom() as cr, Rom() as hr: cr.from_file(clean_rom) hr.from_file(hacked_rom) if cr.__len__() > hr.__len__(): if hr.__len__() <= 0x400000 and hr.__len__() > 0x300000: raise CoilSnakeError( "Clean ROM greater in size than hacked ROM. Please use a 3 Megabyte or 4 Megabyte clean ROM." ) if hr.__len__() <= 0x300000: raise CoilSnakeError( "Clean ROM greater in size than hacked ROM. Please use a 3 Megabyte clean ROM." ) # Expand clean ROM as necessary. if cr.__len__() < hr.__len__(): if hr.__len__() == 0x400000: cr.expand(0x400000) elif hr.__len__() == 0x600000: cr.expand(0x600000) else: cr.expand(patch.last_offset_used) # Create the records. i = None records = {} index = 0 # Get the first byte of each ROM so that the loop works correctly. s = cr.__getitem__(index).to_bytes(1, byteorder='big') t = hr.__getitem__(index).to_bytes(1, byteorder='big') index += 1 while index <= cr.__len__() and index <= hr.__len__(): if t == s and i is not None: i = None elif t != s: if i is not None: # Check that the record's size can fit in 2 bytes. if index - 1 - i == 0xFFFF: i = None continue records[i] += t else: i = index - 1 # Check that the offset isn't EOF. If it is, go back one # byte to work around this IPS limitation. if i.to_bytes(3, byteorder='big') != b"EOF": records[i] = t else: i -= 1 records[i] = hr.to_list()[i] if index < cr.__len__() and index < hr.__len__(): s = cr.__getitem__(index).to_bytes(1, byteorder='big') t = hr.__getitem__(index).to_bytes(1, byteorder='big') index += 1 # Write the patch. with open(patch_path, "wb") as pfile: pfile.seek(0) pfile.write(b"PATCH") for r in sorted(records): pfile.write(r.to_bytes(3, byteorder='big')) pfile.write(len(records[r]).to_bytes(2, byteorder='big')) pfile.write(records[r]) pfile.write(b"EOF") pfile.close()