def write_to_rom(self, rom): for ips_desc_filename in [s for s in os.listdir(get_ips_directory(rom.type)) if s.lower().endswith(".yml")]: patch_name = ips_desc_filename[:-4] with open(os.path.join(get_ips_directory(rom.type), ips_desc_filename)) as ips_desc_file: ips_desc = yml_load(ips_desc_file) if (ips_desc["Title"] in self.patches) and (self.patches[ips_desc["Title"]].lower() == "enabled"): # First, check that we can apply this ranges = map(lambda y: tuple(map(lambda z: int(z, 0), y[1:-1].split(','))), ips_desc["Ranges"]) for range in ranges: if not rom.is_unallocated(range): raise CoilSnakeError( "Can't apply patch \"{}\" because range ({:#x},{:#x}) is not unallocated".format( ips_desc["Title"], range[0], range[1])) # Now apply the patch ips = IpsPatch() offset = 0 if "Header" in ips_desc: offset = ips_desc["Header"] ips.load(get_ips_filename(rom.type, patch_name), offset) ips.apply(rom) # Mark the used ranges as used for range in ranges: rom.mark_allocated(range)
def write_to_rom(self, rom): for ips_desc_filename in [s for s in os.listdir(get_ips_directory(rom.type)) if s.lower().endswith(".yml")]: patch_name = ips_desc_filename[:-4] with open(os.path.join(get_ips_directory(rom.type), ips_desc_filename)) as ips_desc_file: ips_desc = yml_load(ips_desc_file) if "Hidden" in ips_desc and ips_desc["Hidden"]: continue elif (ips_desc["Title"] in self.patches) and (self.patches[ips_desc["Title"]].lower() == "enabled"): # First, check that we can apply this ranges = map(lambda y: tuple(map(lambda z: int(z, 0), y[1:-1].split(','))), ips_desc["Ranges"]) for range in ranges: if not rom.is_unallocated(range): raise CoilSnakeError( "Can't apply patch \"{}\" because range ({:#x},{:#x}) is not unallocated".format( ips_desc["Title"], range[0], range[1])) # Now apply the patch ips = IpsPatch() offset = 0 if "Header" in ips_desc: offset = ips_desc["Header"] ips.load(get_ips_filename(rom.type, patch_name), offset) ips.apply(rom) # Mark the used ranges as used for range in ranges: rom.mark_allocated(range)
class EbpPatch(object): def __init__(self): self.patch = IpsPatch() self.metadata = None @property def last_offset_used(self): return self.patch.last_offset_used def load(self, filename): try: self.patch.load(filename) with open(filename, 'rb') as ebp: # Skip to the end of the IPS part of the patch ebp.seek(5 + sum([ 3 + 2 + i[1][1] + (2 if i[0] == 'RLE' else 0) for i in self.patch.instructions ]) + 3) # Load the metadata, if available try: self.metadata = json.loads(ebp.read().decode("utf-8")) except ValueError: self.metadata = None except: raise CoilSnakeError("Not a valid EBP file: " + filename) def apply(self, rom): self.patch.apply(rom) def is_applied(self, rom): return self.patch.is_applied(rom) def create(self, clean_rom, modified_rom, author="", title="", description=""): self.patch.create(clean_rom, modified_rom) self.metadata = { "patcher": "EBPatcher", # Used for compatibility with EBPatcher "author": author, "title": title, "description": description }
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))
class EbpPatch(object): def __init__(self): self.patch = IpsPatch() self.metadata = None @property def last_offset_used(self): return self.patch.last_offset_used def load(self, filename): try: self.patch.load(filename) with open(filename, 'rb') as ebp: # Skip to the end of the IPS part of the patch ebp.seek( 5 + sum([3+2+i[1][1]+(2 if i[0] == 'RLE' else 0) for i in self.patch.instructions]) + 3 ) # Load the metadata, if available try: self.metadata = json.loads(ebp.read().decode("utf-8")) except ValueError: self.metadata = None except: raise CoilSnakeError("Not a valid EBP file: " + filename) def apply(self, rom): self.patch.apply(rom) def is_applied(self, rom): return self.patch.is_applied(rom) def create(self, clean_rom, modified_rom, author="", title="", description=""): self.patch.create(clean_rom, modified_rom) self.metadata = { "patcher": "EBPatcher", # Used for compatibility with EBPatcher "author": author, "title": title, "description": description }
def create_patch(clean_rom, hacked_rom, patch_path, author, description, title, progress_bar=None): """Starts creating the patch in its own thread.""" creating_patch_start_time = time.time() # Prepare the metadata. metadata = json.dumps({ "patcher": "EBPatcher", "author": author, "title": title, "description": description }) # Try to create the patch; if it fails, display an error message. try: if patch_path.endswith(".ebp"): log.info("Creating EBP patch by " + author + " with description \"" + description + "\" called " + title + "...") patch = EbpPatch() patch.create(clean_rom, hacked_rom, patch_path, metadata) elif patch_path.endswith(".ips"): log.info("Creating IPS patch...") patch = IpsPatch() patch.create(clean_rom, hacked_rom, patch_path) else: raise CoilSnakeError("Unknown patch format.") except OSError as e: log.info("There was an error creating the patch: " + e) return # Display a success message. patch_name = "" if patch_path.rfind("/") != -1: patch_name = patch_path[patch_path.rfind("/") + 1:len(patch_path)] else: patch_name = patch_path[patch_path.rfind("\\") + 1:len(patch_path)] log.info("The patch {} was successfully created in {:.2f}s.".format( patch_name, time.time() - creating_patch_start_time))
class EbpPatch(object): def __init__(self): self.patch = IpsPatch() self.metadata = None @property def last_offset_used(self): return self.patch.last_offset_used def load(self, filename): try: self.patch.load(filename) with open(filename, 'rb') as ebp: # Skip to the end of the IPS part of the patch ebp.seek( 5 + sum([3+2+i[1][1]+(2 if i[0] == 'RLE' else 0) for i in self.patch.instructions]) + 3 ) # Load the metadata, if available try: self.metadata = json.loads(ebp.read().decode("utf-8")) except ValueError: self.metadata = None except: raise CoilSnakeError("Not a valid EBP file: " + filename) def apply(self, rom): self.patch.apply(rom) def is_applied(self, rom): return self.patch.is_applied(rom) def create(self, clean_rom, hacked_rom, patch_path, metadata): self.patch.create(clean_rom, hacked_rom, patch_path) with open(patch_path, "ab") as pfile: pfile.write(bytes(metadata, 'utf8')) pfile.close()
class EbpPatch(object): def __init__(self): self.patch = IpsPatch() self.metadata = None @property def last_offset_used(self): return self.patch.last_offset_used def load(self, filename): try: self.patch.load(filename) with open(filename, 'rb') as ebp: # Skip to the end of the IPS part of the patch ebp.seek(5 + sum([ 3 + 2 + i[1][1] + (2 if i[0] == 'RLE' else 0) for i in self.patch.instructions ]) + 3) # Load the metadata, if available try: self.metadata = json.loads(ebp.read().decode("utf-8")) except ValueError: self.metadata = None except: raise CoilSnakeError("Not a valid EBP file: " + filename) def apply(self, rom): self.patch.apply(rom) def is_applied(self, rom): return self.patch.is_applied(rom) def create(self, clean_rom, hacked_rom, patch_path, metadata): self.patch.create(clean_rom, hacked_rom, patch_path) with open(patch_path, "ab") as pfile: pfile.write(bytes(metadata, 'utf8')) pfile.close()
def _clean(self): """If this is a clean version of one of the variants of the EarthBound ROM, patch it so that it becomes a clean version of the reference ROM. """ # Truncate the ROM if it is has been expanded if len(self) > 0x300000: # Change the ExHiROM bytes in case this is an ExHiROM self[0x00ffd5] = 0x31 self[0x00ffd7] = 0x0c # Truncate the data self.size = 0x300000 self.data = self.data[:0x300000] # Ensure the ROM isn't too small elif len(self) < 0x300000: raise CoilSnakeError("Not a valid clean EarthBound ROM.") # Check if this ROM is already a reference ROM hash = self._calc_hash() if hash == self.REFERENCE_MD5: return # Try to fix the ROM with a patch if it is one of the known alternatives try: patch_filename = self.ALT_MD5[hash] except KeyError: pass # Unknown variant else: patch = IpsPatch() patch.load( os.path.join(ASSET_PATH, "rom-fixes", "Earthbound", patch_filename)) patch.apply(self) self._setup_rom_post_load() return # As a last attempt, try to set the last byte to 0x0, since LunarIPS # likes to add 0xff at the end self[-1] = 0x0 if self._calc_hash() == self.REFERENCE_MD5: return raise CoilSnakeError("Not a valid clean EarthBound ROM.")
def apply_relocation_patch(rom): ips = IpsPatch() ips.load(get_ips_filename(rom.type, "swirl_relocate"), 0x200) ips.apply(rom)
def get_patch(self, mode, rom_type): ips = IpsPatch() ips.load( get_ips_filename(rom_type, '{}_{}'.format(self.patch_name_prefix, mode)), 0) return ips
def __init__(self): self.patch = IpsPatch() self.metadata = None
def test_swirl_relocated(rom): ips = IpsPatch() ips.load(get_ips_filename(rom.type, "swirl_relocate"), 0x200) return ips.is_applied(rom)