def init_free_airship(rom: Rom) -> Rom: # Move the airship's start location to right outside of Coneria Castle. airship_start = OutputStream() airship_start.put_u32(0x918) airship_start.put_u32(0x998) return rom.apply_patch(0x65280, airship_start.get_buffer())
def update_xp_requirements(rom: Rom, value) -> Rom: level_data = rom.open_bytestream(0x1BE3B4, 396) new_table = OutputStream() next_value = level_data.get_u32() while next_value is not None: new_table.put_u32(int(next_value * value)) next_value = level_data.get_u32() rom = rom.apply_patch(0x1BE3B4, new_table.get_buffer()) return rom
def test_overlap_patch(self): patch = OutputStream() patch.put_u32(0x12345678) patch.put_u32(0x12345678) with self.assertRaises(RuntimeError): patched = self.rom.apply_patches({ 0x0: patch.get_buffer(), 0x4: patch.get_buffer() }) self.assertNotEqual(patched, patched)
def test_patches(self): patch = OutputStream() patch.put_u32(0x12345678) patched = self.rom.apply_patches({ 0x0: patch.get_buffer(), 0x10: patch.get_buffer() }) confirm = patched.open_bytestream(0x0, 0x4) self.assertEqual(confirm.get_u32(), 0x12345678) confirm = patched.open_bytestream(0x10, 0x4) self.assertEqual(confirm.get_u32(), 0x12345678)
def pack(self, rom: Rom) -> Rom: text_block = OutputStream() text_lut = OutputStream() next_addr = self.lut[0] text_block_offset = Rom.pointer_to_offset(next_addr) for index, data in enumerate(self.strings): if data is not None: text_lut.put_u32(next_addr) text_block.put_bytes(data) next_addr += len(data) else: text_lut.put_u32(self.lut[0]) patches = { self.lut_offset: text_lut.get_buffer(), text_block_offset: text_block.get_buffer() } return rom.apply_patches(patches)
def add_credits(rom: Rom) -> Rom: credits_lut = rom.get_lut(0x1D871C, 128) base_addr = credits_lut[0] new_lut = OutputStream() data_stream = OutputStream() for index, line in enumerate(CREDITS_TEXT.splitlines()[1:]): line = line.strip() if len(line) > 0: encoded = TextBlock.encode_text(line) new_lut.put_u32(base_addr + data_stream.size()) data_stream.put_bytes(encoded) else: new_lut.put_u32(0x0) # And EOF marker new_lut.put_u32(0xffffffff) # Change the duration so it doesn't take so long to scroll duration = OutputStream() duration.put_u16(60 * 60) return rom.apply_patches({ 0x016848: duration.get_buffer(), 0x1D871C: new_lut.get_buffer(), Rom.pointer_to_offset(base_addr): data_stream.get_buffer() })
def write(self, stream: OutputStream): stream.put_u8(self.contents) for data in self.unused: stream.put_u8(data) stream.put_u32(self.pointer)
def randomize_rom(rom: Rom, flags: Flags, rom_seed: str) -> Rom: rng = random.Random() rng.seed(rom_seed) print(f"Randomize ROM: {flags.text()}, seed='{rom_seed}'") patches_to_load = BASE_PATCHES if flags.encounters is not None: patches_to_load.append("data/FF1EncounterToggle.ips") if flags.default_party is not None: patches_to_load.append("data/RandomDefault.ips") patched_rom_data = rom.rom_data for patch_path in patches_to_load: patch = Patch.load(patch_path) patched_rom_data = patch.apply(patched_rom_data) rom = Rom(data=bytearray(patched_rom_data)) rom = init_free_airship(rom) rom = add_credits(rom) event_text_block = EventTextBlock(rom) event_text_block.shrink() rom = event_text_block.pack(rom) rom = update_xp_requirements(rom, flags.exp_mult) if flags.key_item_shuffle is not None: placement = KeyItemPlacement(rom, rng.randint(0, 0xffffffff)) else: placement = KeyItemPlacement(rom) rom = placement.rom if flags.magic is not None: shuffle_magic = SpellShuffle(rom, rng) rom = shuffle_magic.write(rom) if flags.treasures is not None: if flags.treasures == "shuffle": rom = treasure_shuffle(rom, rng) else: rom = random_bucketed_treasures(rom, rng, flags.wealth) if flags.debug is not None: class_stats_stream = rom.open_bytestream(0x1E1354, 96) class_stats = [] while not class_stats_stream.is_eos(): class_stats.append(JobClass(class_stats_stream)) class_out_stream = OutputStream() for job_class in class_stats: # Set the starting weapon and armor for all classes to something # very fair and balanced: Masamune + Diamond Armlet. :) job_class.weapon_id = 0x28 job_class.armor_id = 0x0e # Write the (very balanced) new data out job_class.write(class_out_stream) rom = rom.apply_patch(0x1E1354, class_out_stream.get_buffer()) if flags.shuffle_formations: formation = FormationRandomization(rom, rng) rom = rom.apply_patches(formation.patches()) if True: enemy_data_stream = rom.open_bytestream(0x1DE044, 0x1860) enemies = [] while not enemy_data_stream.is_eos(): enemies.append(EnemyStats(enemy_data_stream)) # Rebalance (Revisited) Fiend HP enemies[0x78].max_hp = enemies[0x77].max_hp * 2 enemies[0x7a].max_hp = enemies[0x79].max_hp * 2 enemies[0x7c].max_hp = enemies[0x7b].max_hp * 2 enemies[0x7e].max_hp = enemies[0x7d].max_hp * 2 # And Chaos enemies[0x7f].max_hp = enemies[0x7e].max_hp * 2 # Finally, Piscodemons can suck it enemies[0x67].atk = int(enemies[0x67].atk / 2) # We'll also lower everyone's INT just to see how that works for index in range(0x80): enemies[index].intel = int(.666 * enemies[index].intel) # print(f"{hex(index)} HP: {enemies[index].max_hp}, INT: {enemies[index].intel}") out = OutputStream() for enemy in enemies: enemy.write(out) rom = rom.apply_patch(0x1DE044, out.get_buffer()) # Add the seed + flags to the party creation screen. seed_str = TextBlock.encode_text(f"Seed:\n{rom_seed}\nFlags:\n{flags}\x00") pointer = OutputStream() pointer.put_u32(0x8227054) rom = rom.apply_patches({ 0x227054: seed_str, 0x4d8d4: pointer.get_buffer() }) return rom
def get_lut(self) -> bytearray: stream = OutputStream() for addr in self._lut: stream.put_u32(addr) return stream.get_buffer()
def parse(source: str, base_addr: int, debug=False) -> bytearray: symbol_table = {} icode = [] current_addr = base_addr for line_number, line in enumerate(source.splitlines()): tokens = TokenStream(line_number, line) token = tokens.next() if token is None: # Empty or comment only line continue if isinstance(token, RawCommandToken): parameters = [] token = tokens.expect(GRAMMAR["$$value$$"]) while token is not None: if isinstance(token, SymbolToken): if token in symbol_table: parameters.append(symbol_table[token]) else: raise SymbolNotDefinedError(token, line, line_number) else: parameters.append(token) token = tokens.expect(GRAMMAR["$$value$$"]) icode.append(parameters) current_addr += parameters[1] else: # Save the op name op_name = token if type(token) not in GRAMMAR: raise ParserSyntaxError(token, line, line_number) match = GRAMMAR[type(token)] if isinstance(match, dict): rule_set = match["rule"] else: rule_set = match parameters = [] if rule_set is not None: for rule in rule_set: if isinstance(rule, str) and rule.startswith("$$"): rule = GRAMMAR[rule] token = tokens.expect(rule) if token is None: raise ParserSyntaxError(token, line, line_number) if isinstance(op_name, SymbolToken): parameters.append(token) else: if isinstance(token, SymbolToken): if token in symbol_table: parameters.append(symbol_table[token]) else: raise SymbolNotDefinedError( token, line, line_number) else: parameters.append(token) verify_end = tokens.expect(CommentToken()) else: verify_end = tokens.expect(CommentToken()) if op_name == "def_symbol" or op_name == "def_label": name = parameters[0] value = parameters[1] if name in symbol_table: raise DuplicateSymbolError(name, line, line_number) if isinstance(value, ColonToken): value = current_addr symbol_table[name] = value else: if isinstance(op_name, list): output = simple_gen(op_name, parameters) else: method = getattr(codegen, op_name) output = method(parameters) if output is not None: icode.append(output) current_addr += output[1] # At this point, all of the intermediate code is built and the only thing left is to resolve # the left over symbols, which will all bel labels. bytecode = OutputStream() for code in icode: txt = "" for bd in code: if isinstance(bd, LabelToken): label = bd if label not in symbol_table: raise UndefinedLabel(label) bytecode.put_u32(symbol_table[label]) txt += f"{label} ({hex(symbol_table[label])}) " else: txt += f"{hex(bd)} " bytecode.put_u8(bd) if debug: print(txt) # Done! return bytecode.get_buffer()
def write(self, stream: OutputStream): chest_data = (self.id << 24) | self.qty stream.put_u32(chest_data)
def write(self, stream: OutputStream): chest_data = (self.id << 24) | (self.item_id << 8) | (self.item_type & 0xff) stream.put_u32(chest_data)