def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) start = read_multi(f, length=2) end = read_multi(f, length=2) f.close() n = (end - start) // 6 assert end == start + (6 * n) self.entrances = [] for i in range(n): e = Entrance(0x1fbb00 + start + (i * 6)) e.set_id(i) self.entrances.append(e) for e in self.entrances: e.read_data(filename) e.set_location(self.location) f = open(filename, 'r+b') f.seek(self.longpointer) start = read_multi(f, length=2) end = read_multi(f, length=2) f.close() n = (end - start) // 7 assert end == start + (7 * n) self.longentrances = [] for i in range(n): e = LongEntrance(0x2DF480 + start + (i * 7)) e.set_id(i) self.longentrances.append(e) for e in self.longentrances: e.read_data(filename) e.set_location(self.location) self.location.uniqify_entrances()
def read_data(self, filename=None, pointer=None): if pointer is None: pointer = self.pointer if filename is None: filename = self.filename if pointer is None or filename is None: return if self.variable_size is not None: specsattrs = self.get_variable_specsattrs() else: specsattrs = self.specsattrs f = open(filename, 'r+b') f.seek(pointer) for name, size, other in specsattrs: if other in [None, "int"]: value = read_multi(f, length=size) elif other == "str": value = f.read(size) elif other == "list": if not isinstance(size, int): number, numbytes = size.split('x') number, numbytes = int(number), int(numbytes) else: number, numbytes = size, 1 value = [] for i in xrange(number): value.append(read_multi(f, numbytes)) setattr(self, name, value) f.close()
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) start = read_multi(f, length=2) end = read_multi(f, length=2) f.close() n = (end - start) / 6 assert end == start + (6*n) self.entrances = [] for i in xrange(n): e = Entrance(0x1fbb00 + start + (i*6)) e.set_id(i) self.entrances.append(e) for e in self.entrances: e.read_data(filename) e.set_location(self.location) f = open(filename, 'r+b') f.seek(self.longpointer) start = read_multi(f, length=2) end = read_multi(f, length=2) f.close() n = (end - start) / 7 assert end == start + (7*n) self.longentrances = [] for i in xrange(n): e = LongEntrance(0x2DF480 + start + (i*7)) e.set_id(i) self.longentrances.append(e) for e in self.longentrances: e.read_data(filename) e.set_location(self.location) self.location.uniqify_entrances()
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) self.itemtype = read_multi(f, length=2) if self.itemtype in objname: self.name = objname[self.itemtype] self.x = read_multi(f, length=2) self.y = read_multi(f, length=2) f.close()
def read_stats(self, filename): global all_spells f = open(filename, 'r+b') f.seek(self.pointer) self.itemtype = ord(f.read(1)) itemtype = self.itemtype & 0x0f (self.is_tool, self.is_weapon, self.is_armor, self.is_relic, self.is_consumable) = (False, False, False, False, False) self.is_shield, self.is_helm, self.is_body_armor = False, False, False if itemtype == 0x00: self.is_tool = True elif itemtype == 0x01: self.is_weapon = True elif itemtype in [2, 3, 4]: self.is_armor = True if itemtype == 3: self.is_shield = True elif itemtype == 4: self.is_helm = True elif itemtype == 2: self.is_body_armor = True elif itemtype == 0x05: self.is_relic = True elif itemtype == 0x06: self.is_consumable = True #throwable = self.itemtype & 0x10 #usable_battle = self.itemtype & 0x20 #usable_field = self.itemtype & 0x40 self.equippable = read_multi(f, length=2) self.heavy = bool(self.equippable & 0x8000) stats = map(ord, f.read(len(ITEM_STATS))) self.features = dict(zip(ITEM_STATS, stats)) self.price = read_multi(f, length=2) if all_spells is None: all_spells = sorted([SpellBlock(i, filename) for i in xrange(0xFF)], key=lambda s: s.rank()) all_spells = filter(lambda s: s.valid, all_spells) f.seek(0x2CE408 + (8*self.itemid)) self.weapon_animation = map(ord, f.read(8)) f.seek(0x12B300 + (13*self.itemid)) self.dataname = map(ord, f.read(13)) f.close()
def read_chests(self, filename): from chestrandomizer import ChestBlock f = open(filename, 'r+b') f.seek(self.chestpointer) begin = read_multi(f, length=2) end = read_multi(f, length=2) numchests = (end - begin) // 5 self.chests = [] for i in range(numchests): pointer = begin + (i * 5) + 0x2d8634 c = ChestBlock(pointer, self.locid) c.read_data(filename) c.set_id(i) self.chests.append(c)
def read_chests(self, filename): from chestrandomizer import ChestBlock f = open(filename, 'r+b') f.seek(self.chestpointer) begin = read_multi(f, length=2) end = read_multi(f, length=2) numchests = (end - begin) / 5 self.chests = [] for i in xrange(numchests): pointer = begin + (i*5) + 0x2d8634 c = ChestBlock(pointer, self.locid) c.read_data(filename) c.set_id(i) self.chests.append(c)
def read_events(self, filename): f = open(filename, 'r+b') f.seek(self.eventpointer) begin = read_multi(f, length=2) end = read_multi(f, length=2) numevents = (end - begin) / 5.0 assert numevents == round(numevents) numevents = int(numevents) self.events = [] for i in range(numevents): pointer = begin + (i * 5) + 0x40000 e = EventBlock(pointer, self.locid) e.read_data(filename) e.set_id(i) self.events.append(e)
def read_events(self, filename): f = open(filename, 'r+b') f.seek(self.eventpointer) begin = read_multi(f, length=2) end = read_multi(f, length=2) numevents = (end - begin) / 5.0 assert numevents == round(numevents) numevents = int(numevents) self.events = [] for i in xrange(numevents): pointer = begin + (i*5) + 0x40000 e = EventBlock(pointer, self.locid) e.read_data(filename) e.set_id(i) self.events.append(e)
def read_npcs(self, filename): f = open(filename, 'r+b') f.seek(self.npcpointer) begin = read_multi(f, length=2) end = read_multi(f, length=2) numnpcs = (end - begin) / 9.0 assert numnpcs == round(numnpcs) numnpcs = int(numnpcs) self.npcs = [] for i in xrange(numnpcs): pointer = begin + (i*9) + 0x41a10 e = NPCBlock(pointer, self.locid) e.read_data(filename) e.set_id(i) self.npcs.append(e)
def read_npcs(self, filename): f = open(filename, 'r+b') f.seek(self.npcpointer) begin = read_multi(f, length=2) end = read_multi(f, length=2) numnpcs = (end - begin) / 9.0 assert numnpcs == round(numnpcs) numnpcs = int(numnpcs) self.npcs = [] for i in range(numnpcs): pointer = begin + (i * 9) + 0x41a10 e = NPCBlock(pointer, self.locid) e.read_data(filename) e.set_id(i) self.npcs.append(e)
def ptrfind(filename, ptrsize, minimum, reverse=True): ptrsize, minimum = int(ptrsize), int(minimum) reverse = False if reverse == "False" else bool(reverse) f = open(filename, 'r+b') f.seek(0, 2) filesize = f.tell() location = 0 discoveries = [] while True: f.seek(location) prev = -1 values = [] while True: if f.tell() + ptrsize >= filesize: break value = read_multi(f, ptrsize, reverse=reverse) if value < prev: break if value == prev and value == 0 and len(values) == 1: break values.append(value) prev = value if len(values) >= minimum and len(set(values)) >= minimum/8: discoveries.append((location, values)) location += len(values) * ptrsize else: location += 1 if location + ptrsize >= filesize: break f.close() return discoveries
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) self.x = ord(f.read(1)) self.y = ord(f.read(1)) self.event_addr = read_multi(f, length=3) f.close()
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) value = read_multi(f, length=4) self.palette = (value & 0x1C0000) >> 18 self.bg2_scroll = (value & 0x200000) >> 21 self.membit = (value & 0x1C00000) >> 22 self.memaddr = (value & 0xFE000000) >> 25 self.event_addr = value & 0x3FFFF byte4 = ord(f.read(1)) self.x = byte4 & 0x7f self.show_on_vehicle = (byte4 & 0x80) >> 7 byte5 = ord(f.read(1)) self.y = byte5 & 0x3F self.speed = (byte5 & 0xC0) >> 6 self.graphics = ord(f.read(1)) byte7 = ord(f.read(1)) self.move_type = byte7 & 0xF self.sprite_priority = (byte7 & 0x30) >> 4 self.vehicle = (byte7 & 0xC0) >> 6 byte8 = ord(f.read(1)) self.facing = byte8 & 0x03 self.no_turn_when_speaking = (byte8 & 0x4) >> 2 self.layer_priority = (byte8 & 0x18) >> 3 self.special_anim = (byte8 & 0xe0) >> 5 f.close()
def recolor_palette(pointer, size): fout.seek(pointer) palette = [read_multi(fout, length=2) for _ in range(size)] palette = transformer(palette) fout.seek(pointer) for c in palette: write_multi(fout, c, length=2)
def read_data(self): #f = open(self.imgname, 'r+b') f = file_from_sectors(self.imgname, self.initial_sector) f.seek(self.pointer) peek = f.read(1) if not peek: raise EOFError self.size = ord(peek) if self.size == 0: raise FileEntryReadException self.num_ear = ord(f.read(1)) assert self.num_ear == 0 assert not self.size % 2 self.target_sector = read_multi(f, length=4) f.seek(4, 1) self.filesize = read_multi(f, length=4) f.seek(4, 1) self.year = ord(f.read(1)) + 1900 self.month = ord(f.read(1)) self.day = ord(f.read(1)) self.hour = ord(f.read(1)) self.minute = ord(f.read(1)) self.second = ord(f.read(1)) self.tz_offset = ord(f.read(1)) / 4.0 self.flags = ord(f.read(1)) assert not self.flags & 0xFC self.hidden = self.flags & 1 self.is_directory = self.flags & 0x2 self.interleaved_unit_size = ord(f.read(1)) self.interleaved_gap_size = ord(f.read(1)) assert not self.interleaved_unit_size or self.interleaved_gap_size self.one = read_multi(f, length=2) assert self.one == 1 f.seek(2, 1) self.name_length = ord(f.read(1)) self.name = f.read(self.name_length) if not self.name_length % 2: p = ord(f.read(1)) assert p == 0 self.pattern = f.read(14) if self.is_directory: assert self.pattern == DIRECTORY_PATTERN else: assert self.name[-2:] == ";1" #assert self.pattern == FILE_PATTERN assert f.tell() == self.pointer + self.size f.close()
def decompress_at_location(filename, address): f = open(filename, 'r+b') f.seek(address) size = read_multi(f, length=2) #print "Size is %s" % size bytestring = f.read(size) decompressed = decompress(bytestring, complicated=True) return decompressed
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) self.x = ord(f.read(1)) self.y = ord(f.read(1)) self.dest = read_multi(f, length=2) self.destx = ord(f.read(1)) self.desty = ord(f.read(1)) f.close()
def read_stats(self, filename): global all_spells, valid_spells, items, itemids f = open(filename, 'r+b') f.seek(self.pointer) for key in stat_order: self.stats[key] = ord(f.read(1)) self.stats['hp'] = read_multi(f, length=2) self.stats['mp'] = read_multi(f, length=2) self.stats['xp'] = read_multi(f, length=2) self.stats['gp'] = read_multi(f, length=2) self.stats['level'] = ord(f.read(1)) self.morph = ord(f.read(1)) self.misc1 = ord(f.read(1)) self.misc2 = ord(f.read(1)) f.seek(self.pointer + 20) self.immunities = map(ord, f.read(3)) self.absorb = ord(f.read(1)) self.null = ord(f.read(1)) self.weakness = ord(f.read(1)) f.seek(self.pointer + 27) self.statuses = map(ord, f.read(4)) self.special = ord(f.read(1)) f.seek(self.itemptr) self.items = map(ord, f.read(4)) f.seek(self.controlptr) self.controls = map(ord, f.read(4)) f.seek(self.sketchptr) self.sketches = map(ord, f.read(2)) f.seek(self.rageptr) self.rages = map(ord, f.read(2)) f.seek(self.aiptr) self.ai = read_multi(f, length=2) f.close()
def read_dialogue(fout): #load existing script & pointer table fout.seek(0xD0000) script_bin = fout.read(0x1F0FF) #TODO battle script fout.seek(0xCE600) bankidx = read_multi(fout, 2) for idx in range(0xC0C): #C0D through CFF pointers are repurposed script_ptrs[idx] = read_multi(fout, 2) + (0x10000 if idx >= bankidx else 0) for idx in range(0xC0C): #C0D through CFF pointers are repurposed start = script_ptrs[idx] end = script_ptrs.get(idx + 1, 0) if end == 0: end = script_bin.find(b'\x00', start) script[idx] = bytes_to_dialogue(script_bin[start:end])
def read_stats(self, filename): global all_spells f = open(filename, 'r+b') f.seek(self.pointer) self.itemtype = ord(f.read(1)) # throwable = self.itemtype & 0x10 # usable_battle = self.itemtype & 0x20 # usable_field = self.itemtype & 0x40 self.equippable = read_multi(f, length=2) self.heavy = bool(self.equippable & 0x8000) stats = list(f.read(len(ITEM_STATS))) self.features = dict(list(zip(ITEM_STATS, stats))) # move flags for "randomly cast" and "destroy if used" # so breakeffect can use the full range of spells if not self.is_consumable: break_flags = self.features["breakeffect"] & 0xC0 self.features["otherproperties"] |= break_flags >> 4 self.features["breakeffect"] &= ~0xC0 self.price = read_multi(f, length=2) if all_spells is None: all_spells = get_ranked_spells(filename) all_spells = [s for s in all_spells if s.valid] f.seek(0x2CE408 + (8 * self.itemid)) self.weapon_animation = list(f.read(8)) f.seek(0x12B300 + (13 * self.itemid)) self.dataname = list(f.read(13)) # unhardcoded tintinabar patch moves the tintinabar flag if self.features["fieldeffect"] & 0x80: self.features["fieldeffect"] &= ~0x80 self.features["special2"] |= 0x80 f.close()
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) self.formids = [] if self.setid <= 0xFF: num_encounters = 4 else: num_encounters = 2 for i in xrange(num_encounters): self.formids.append(read_multi(f, length=2)) f.close()
def read_data(self, filename, header=False): f = open(filename, 'r+b') f.seek(self.pointer) if header: unknown = f.read(4) self.y = f.read(2) self.x = f.read(2) self.ff = f.read(1) nowpointer = f.tell() message = [] while True: f.seek(nowpointer) byte = ord(f.read(1)) if byte & 0xF0 == 0xF0: # stored word wordlength = (byte & 0x0F) + 4 ctrlptr = read_multi(f, length=2) #if ctrlptr >= 0x8000: # print "WARNING1 %x" % ctrlptr f.seek((nowpointer & 0xff0000) | ctrlptr) word = map(ord, f.read(wordlength)) message.extend(word) nowpointer += 3 elif byte & 0xF0 == 0xE0: # repeated character wordlength = (byte & 0x0F) + 3 char = ord(f.read(1)) message.extend([char]*wordlength) nowpointer += 2 elif byte & 0xF0 == 0xB0: # control command nowpointer += 3 elif byte & 0xF0 == 0xC0: message.append(0x18) nowpointer += 1 elif byte & 0xF0 == 0xD0: message.append(byte) nowpointer += 1 elif byte == 0x00: # termination nowpointer += 1 break elif byte < 0xb0: # ordinary character message.append(byte) nowpointer += 1 else: print "WARNING2 %x" % byte nowpointer += 1 f.close() self.message = message return nowpointer
def read_mould(self, filename): mouldspecsptrs = 0x2D01A f = open(filename, 'r+b') pointer = mouldspecsptrs + (2*self.mould) f.seek(pointer) pointer = read_multi(f, length=2) | 0x20000 for i in xrange(6): f.seek(pointer + (i*4)) a, b = tuple(map(ord, f.read(2))) width = ord(f.read(1)) height = ord(f.read(1)) enemy = self.enemies[i] if enemy: enemy.update_size(width, height)
def read_mould(self, filename): mouldspecsptrs = 0x2D01A f = open(filename, 'r+b') pointer = mouldspecsptrs + (2 * self.mould) f.seek(pointer) pointer = read_multi(f, length=2) | 0x20000 for i in xrange(6): f.seek(pointer + (i * 4)) a, b = tuple(map(ord, f.read(2))) width = ord(f.read(1)) height = ord(f.read(1)) enemy = self.enemies[i] if enemy: enemy.update_size(width, height)
def read_location_names(f): #load existing script & pointer table f.seek(0xEF100) location_name_bin = f.read(0x4ff) f.seek(0x268400) for idx in range(0x49): location_name_ptrs[idx] = read_multi(f, 2) for idx in range(0x49): start = location_name_ptrs[idx] end = location_name_ptrs.get(idx + 1, location_name_bin.find(b'\0', start) + 1) location_names[idx] = bytes_to_dialogue(location_name_bin[start:end])
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) self.name_id = ord(f.read(1)) self.layers_to_animate = ord(f.read(1)) self._battlebg = ord(f.read(1)) self.unknown0 = ord(f.read(1)) self.tileproperties = ord(f.read(1)) # mult by 2 self.attacks = ord(f.read(1)) self.unknown1 = ord(f.read(1)) self.graphic_sets = map(ord, f.read(4)) self.tileformations = read_multi(f, length=2, reverse=True) self.mapdata = read_multi(f, length=4) self.unknown2 = ord(f.read(1)) self.bgshift = map(ord, f.read(4)) self.unknown3 = ord(f.read(1)) self.layer12dimensions = ord(f.read(1)) self.unknown4 = ord(f.read(1)) self.palette_index = read_multi(f, length=3) self.music = ord(f.read(1)) self.unknown5 = ord(f.read(1)) self.width = ord(f.read(1)) self.height = ord(f.read(1)) self.layerpriorities = ord(f.read(1)) assert f.tell() == self.pointer + 0x21 f.seek(0xf5600 + self.locid) self.setid = ord(f.read(1)) f.close() self.entrance_set.read_data(filename) self.backup_entrances() self.read_chests(filename) self.read_npcs(filename) self.read_events(filename)
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) self.name_id = ord(f.read(1)) self.layers_to_animate = ord(f.read(1)) self._battlebg = ord(f.read(1)) self.unknown0 = ord(f.read(1)) self.tileproperties = ord(f.read(1)) # mult by 2 self.attacks = ord(f.read(1)) self.unknown1 = ord(f.read(1)) self.graphic_sets = list(f.read(4)) self.tileformations = read_multi(f, length=2, reverse=True) self.mapdata = read_multi(f, length=4) self.unknown2 = ord(f.read(1)) self.bgshift = list(f.read(4)) self.unknown3 = ord(f.read(1)) self.layer12dimensions = ord(f.read(1)) self.unknown4 = ord(f.read(1)) self.palette_index = read_multi(f, length=3) self.music = ord(f.read(1)) self.unknown5 = ord(f.read(1)) self.width = ord(f.read(1)) self.height = ord(f.read(1)) self.layerpriorities = ord(f.read(1)) assert f.tell() == self.pointer + 0x21 f.seek(0xf5600 + self.locid) self.setid = ord(f.read(1)) f.close() self.entrance_set.read_data(filename) self.backup_entrances() self.read_chests(filename) self.read_npcs(filename) self.read_events(filename)
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) value = read_multi(f, length=4) self.palette = (value & 0x1C0000) >> 18 self.unknown = (value & 0x200000) >> 21 self.membit = (value & 0x1C00000) >> 22 self.memaddr = (value & 0xFE000000) >> 25 self.event_addr = value & 0x3FFFF self.x = ord(f.read(1)) self.y = ord(f.read(1)) self.graphics = ord(f.read(1)) self.graphics_index = ord(f.read(1)) self.facing = ord(f.read(1)) f.close()
def read_data(self, filename): global extra_miabs f = open(filename, 'r+b') f.seek(self.pointer) self.position = read_multi(f, length=2) self.memid = ord(f.read(1)) self.content_type = ord(f.read(1)) self.contents = ord(f.read(1)) f.close() self.oldid = self.memid | ((self.content_type & 1) << 8) mark_taken_id(self.effective_id) if self.monster: add_extra_miab(self.contents)
def read_data(self, filename): global extra_miabs f = open(filename, 'r+b') f.seek(self.pointer) self.position = read_multi(f, length=2) self.memid = ord(f.read(1)) self.contenttype = ord(f.read(1)) self.contents = ord(f.read(1)) f.close() self.oldid = self.memid | ((self.contenttype & 1) << 8) mark_taken_id(self.effective_id) if self.monster: add_extra_miab(self.contents)
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.pointer) self.formids = [] if self.setid <= 0xFF: num_encounters = 4 else: num_encounters = 2 for _ in range(num_encounters): self.formids.append(read_multi(f, length=2)) if any([f & 0x8000 for f in self.formids]): assert all([f & 0x8000 for f in self.formids]) self.sixteen_pack = True else: self.sixteen_pack = False f.close()
def read_data(self, filename): f = open(filename, 'r+b') f.seek(self.ptrptr) pointer = read_multi(f, length=3) # 84d1f2 -> 251f2 assert pointer & 0xFF8000 == 0x848000 pointer = (pointer & 0x7FFF) | 0x20000 self.pointer = pointer f.seek(self.pointer) num_objects = ord(f.read(1)) f.close() self.levelobjects = [] for i in xrange(num_objects): pointer = self.pointer + 1 + (6 * i) l = LevelObject(index=i, pointer=pointer) l.read_data(filename) self.levelobjects.append(l)
def manage_dialogue_patches(fout): global script_bin #don't do anything unless we need to if not dialogue_patches and not dialogue_patches_battle: return #load existing script & pointer table fout.seek(0xD0000) script_bin = fout.read(0x1F0FF) script = {} #TODO battle script fout.seek(0xCE600) bankidx = read_multi(fout, 2) for idx in range(0xC0C): #C0D through CFF pointers are repurposed script_ptrs[idx] = read_multi(fout, 2) + (0x10000 if idx > bankidx else 0) script[idx] = read_script(idx) #TODO battle pointers #print(f"original script size is ${len(script_bin):X} bytes") #apply changes to dialogue for idx, patches in dialogue_patches.items(): line = split_line(script[idx]) #print(f"patching line {idx}") #print(f" original: {script[idx]}") token_counter = {} for i, token in enumerate(line): if token.lower() not in token_counter: token_counter[token.lower()] = 1 else: token_counter[token.lower()] += 1 if (token.lower(), token_counter[token.lower()]) in patches: line[i] = patch(patches[token.lower(), token_counter[token.lower()]]) elif (token.lower(), None) in patches: line[i] = patch(patches[token.lower(), None]) #handle removing text along with following space if line[i] is None: line[i] = "" try: if line[i + 1][0] == " ": line[i + 1] = "" if len(line[i + 1]) < 2 else line[i + 1][1:] except IndexError: pass new_text = "".join(line) #print(f" new: {new_text}") script[idx] = new_text new_script = b"" new_ptrs = b"" offset = 0 first_high_index = None for idx, text in script.items(): ####TODO!!! rewrite to only re-encode dialogue if it is changed? lastlength = len(new_script) - offset if first_high_index: lastlength -= 0x10000 offset += lastlength if offset > 0xFFFF: if offset > 0x1FFFF or first_high_index is not None: print(f"script addressing overflow at index {idx}") raise IndexError offset -= 0x10000 first_high_index = idx #print(f"first high index at {first_high_index}") new_script += dialogue_to_bytes(text) new_ptrs += bytes([offset & 0xFF, (offset >> 8) & 0xFF]) #print(f"new script: ${len(new_script):X} bytes") #write to file fout.seek(0xD0000) assert len(new_script) <= 0x1F0FF fout.write(new_script) fout.seek(0xCE600) write_multi(fout, first_high_index) assert len(new_ptrs) <= 0x19FE fout.write(new_ptrs)
def manage_ancient(options_, fout, sourcefile, form_music_overrides={}): change_battle_commands = [41, 42, 43] if not options_.shuffle_commands: alrs = AutoLearnRageSub(require_gau=True) alrs.set_location(0x23b73) alrs.write(fout) enable_morph_sub = Substitution() enable_morph_sub.bytestring = bytes([0xEA] * 2) enable_morph_sub.set_location(0x25410) enable_morph_sub.write(fout) enable_mpoint_sub = Substitution() enable_mpoint_sub.bytestring = bytes([0xEA] * 2) enable_mpoint_sub.set_location(0x25E38) enable_mpoint_sub.write(fout) change_battle_commands += list(range(18, 28)) moogle_commands = [ 0x03, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x10, 0x12, 0x13, 0x16, 0x18, 0x1a, 0x1b, 0x1d ] for i in change_battle_commands: commands = random.sample(moogle_commands, 2) c = get_character(i) c.battle_commands = [0x00, commands[0], commands[1], 0x01] c.write_battle_commands(fout) for i in [32, 33]: c = get_character(i) c.battle_commands = [0x00, 0x1D, 0xFF, 0x01] c.write_battle_commands(fout) characters = get_characters() gau = [c for c in characters if c.id == 11][0] if not options_.replace_commands and gau.battle_commands[1] in [ 0x11, None ]: gau.battle_commands[1] = 0xFF gau.write_battle_commands(fout) to_dummy = [get_item(0xF6), get_item(0xF7)] dummy_names = ["Pebble", "Tissue"] for dummy_name, item in zip(dummy_names, to_dummy): name = bytes([0xFF]) + name_to_bytes(dummy_name, 12) item.dataname = name item.price = 4 item.itemtype = 6 item.write_stats(fout) blank_sub = Substitution() blank_sub.set_location(0x2D76C1) blank_sub.bytestring = bytearray([0xFF] * (0x2D76F5 - blank_sub.location)) blank_sub.bytestring[blank_sub.size // 2] = 0 blank_sub.write(fout) goddess_save_sub = Substitution() goddess_save_sub.bytestring = bytes([0xFD, 0xFD]) goddess_save_sub.set_location(0xC170A) goddess_save_sub.write(fout) goddess_save_sub.set_location(0xC1743) goddess_save_sub.write(fout) goddess_save_sub.set_location(0xC1866) goddess_save_sub.write(fout) # decrease exp needed for level up if options_.is_code_active('racecave'): maxlevel = 49 divisor = 12.0 elif options_.is_code_active('speedcave'): maxlevel = 49 divisor = 8.0 else: maxlevel = 49 divisor = 2.0 for level in range(maxlevel): ratio = (float(level) / maxlevel)**2 ratio = min(ratio, 1.0) xptr = 0x2d8220 + (level * 2) fout.seek(xptr) exp = read_multi(fout, length=2) newexp = (exp / divisor) remaining = exp - newexp newexp = int(round(newexp + (ratio * remaining))) newexp = max(newexp, 1) fout.seek(xptr) write_multi(fout, newexp, length=2) startsub = Substitution() startsub.bytestring = bytearray([ 0xD7, 0xF3, # remove Daryl 0xD5, 0xF0, # remove Terra from party 0xD5, 0xE0, # remove Terra from party 0xDC, 0x7E, # fix ending? $1F4F bit 6 0xB8, 0x43, # show magic points after battle 0x3F, 0x0E, 0x00, 0x3F, 0x0F, 0x00, ]) if options_.is_code_active('racecave'): num_starting = 9 + random.randint(0, 2) + random.randint(0, 1) elif options_.is_code_active('speedcave'): num_starting = 4 + random.randint(0, 3) + random.randint(0, 2) else: num_starting = 4 + random.randint(0, 1) + random.randint(0, 1) starting = random.sample(list(range(14)), num_starting) for c in starting: startsub.bytestring += bytearray([0xD4, 0xF0 | c]) startsub.bytestring += bytearray([0xD4, 0xE0 | c]) for c in characters: i = c.id cptr = 0x2d7ca0 + 0x15 + (i * 22) fout.flush() fout.seek(cptr) level = ord(fout.read(1)) level &= 0xF3 if i >= 14 or options_.is_code_active( "speedcave") and i not in starting: level |= 0b1000 fout.seek(cptr) fout.write(bytes([level])) fout.seek(0xa5e74) fout.write(b'\x00') # remove Terra's magitek tempcands = [ 14, 15, random.choice(list(range(18, 28))), random.choice([32, 33]) ] if options_.is_code_active('speedcave'): tempcands.append(random.choice([16, 17])) tempcands.append(random.choice([41, 42, 43])) charcands = list(range(14)) + random.sample(tempcands, 2) chargraphics = { 14: 0x11, 15: 0x10, 16: 0x14, 17: 0x14, 32: 0xE, 33: 0xE, 41: 0x15, 42: 0x15, 43: 0x15 } for c in range(14): chargraphics[c] = c for c in range(18, 28): chargraphics[c] = 0xA for n, i in enumerate(charcands): c = [x for x in characters if x.id == i][0] if i in chargraphics: g = chargraphics[i] else: g = i startsub.bytestring.extend( [0x7F, n, i, 0x37, n, g, 0x43, n, c.palette, 0x40, n, i]) c.slotid = n runaway = random.choice([ c for c in characters if hasattr(c, "slotid") and c.id == c.slotid ]).slotid if runaway in starting: byte, bit = runaway // 8, runaway % 8 mem_addr = ((0x1b + byte) << 3) | bit startsub.bytestring += bytearray([0xD7, mem_addr]) shadow_leaving_sub = Substitution() shadow_leaving_sub.set_location(0x248A6) shadow_leaving_sub.bytestring = bytearray([ 0x1C, 0xDE + (runaway // 8), 0x1E, # TRB $1ede 0x20, 0xE3, 0x47, 0xAD, 0xFB + (runaway // 8), 0x1E, # LDA $1efb 0x09, 1 << (runaway % 8), # ORA #$08 0x8D, 0xFB + (runaway // 8), 0x1E, # STA $1efb 0xAD, 0xDE + (runaway // 8), 0x1E, # LDA $1ede 0x29, 0xFF ^ (1 << (runaway % 8)), # AND #$F7 0x8D, 0xDE + (runaway // 8), 0x1E, # STA $1ede ]) while len(shadow_leaving_sub.bytestring) < 23: shadow_leaving_sub.bytestring.append(0xEA) shadow_leaving_sub.bytestring += bytearray([0xA9, 0xFE, 0x20, 0x92, 0x07]) shadow_leaving_sub.write(fout) shadow_leaving_sub.set_location(0x24861) shadow_leaving_sub.bytestring = bytearray([ 0xAE, runaway, 0x30, 0x30, 0x26, 0x20, 0x5A, 0x4B, 0xC9, random.choice([0x20, 0x10, 0x8, 0x4, 0x2, 0x1]), 0xB0, 0x1F, 0xAD, 0x1F, 0x20, 0xD0, 0x1A, 0xAD, 0x76, 0x3A, 0xC9, 0x02, 0x90, 0x13, 0xBD, 0xE4, 0x3E, 0x89, 0xC2, 0xD0, 0x0C, 0xA9, 1 << (runaway % 8), 0x2C, 0xBD + (runaway // 8), 0x3E, 0xD0, 0x05, 0x2C, 0xDE + (runaway // 8), 0x1E, ]) shadow_leaving_sub.write(fout) shadow_leaving_sub.set_location(0x10A851) shadow_leaving_sub.bytestring = bytearray([ 0x0E, 0x03, runaway, 0x6A, 0xA8, 0x0F, 0x11, 0x01, 0xFB, 0x0E, 0x03, runaway, 0x7E, 0xA8, 0x0F, 0x01, 0xFC, 0x0E, 0x03, runaway, 0x92, 0xA8, 0x0F, 0x10, 0xFF, ]) shadow_leaving_sub.write(fout) shadow_leaving_sub.bytestring = bytearray([runaway]) shadow_leaving_sub.set_location(0x10FC2F) shadow_leaving_sub.write(fout) shadow_leaving_sub.set_location(0x10FC5D) shadow_leaving_sub.write(fout) esperevents = [ "Ramuh", "Ifrit", "Shiva", "Siren", "Terrato", "Shoat", "Maduin", "Bismark", "Stray", "Palidor", "Tritoch", "Odin", "Raiden", "Bahamut", "Alexandr", "Crusader", "Ragnarok", "Kirin", "ZoneSeek", "Carbunkl", "Phantom", "Sraphim", "Golem", "Unicorn", "Fenrir", "Starlet", "Phoenix" ] esperevents = dict([(n, i) for (i, n) in enumerate(esperevents)]) espers = list(get_espers(sourcefile)) num_espers = 3 for i in range(num_espers): if options_.is_code_active("speedcave"): esperrank = 999 else: esperrank = 0 while random.randint(1, 3) == 3: esperrank += 1 candidates = [e for e in espers if e.rank <= esperrank] esper = random.choice(candidates) espers.remove(esper) event_value = esperevents[esper.name] + 0x36 startsub.bytestring += bytearray([0x86, event_value]) for i in range(27): # espers byte, bit = i // 8, i % 8 mem_addr = ((0x17 + byte) << 3) | bit startsub.bytestring += bytearray([0xD6, mem_addr]) for i in range(16): # characters if i in starting: continue byte, bit = i // 8, i % 8 mem_addr = ((0x1b + byte) << 3) | bit startsub.bytestring += bytearray([0xD6, mem_addr]) startsub.bytestring += bytearray([ 0xB2, 0x09, 0x21, 0x02, # start on airship ]) startsub.bytestring.append(0xFE) startsub.set_location(0xADD1E) startsub.write(fout) startsub0 = Substitution() startsub0.bytestring = bytearray([0xB2, 0x1E, 0xDD, 0x00, 0xFE]) startsub0.set_location(0xC9A4F) startsub0.write(fout) set_airship_sub = Substitution() set_airship_sub.bytestring = bytearray([0xB2, 0xD6, 0x02, 0x00, 0xFE]) set_airship_sub.set_location(0xAF53A) # need first branch for button press set_airship_sub.write(fout) tower_msg_sub = Substitution() tower_msg_sub.bytestring = bytearray([0xD6, 0xE6, 0xD6, 0xE7]) # reset temp chars while len(tower_msg_sub.bytestring) < 12: tower_msg_sub.bytestring.append(0xFD) tower_msg_sub.set_location(0xA03A7) tower_msg_sub.write(fout) from locationrandomizer import NPCBlock, EventBlock falcon = get_location(0xb) save_point = NPCBlock(pointer=None, locid=falcon.locid) attributes = { "graphics": 0x6f, "palette": 6, "x": 20, "y": 8, "show_on_vehicle": False, "speed": 0, "event_addr": 0x5eb3, "facing": 3, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 2, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 1, "vehicle": 0 } for key, value in attributes.items(): setattr(save_point, key, value) save_point.set_id(len(falcon.npcs)) falcon.npcs.append(save_point) save_event = EventBlock(pointer=None, locid=falcon.locid) attributes = {"event_addr": 0x29aeb, "x": 20, "y": 8} for key, value in attributes.items(): setattr(save_event, key, value) falcon.events.append(save_event) partyswitch = NPCBlock(pointer=None, locid=falcon.locid) attributes = { "graphics": 0x17, "palette": 0, "x": 16, "y": 6, "show_on_vehicle": False, "speed": 0, "event_addr": 0x047d, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0, "npcid": 2 } for key, value in attributes.items(): setattr(partyswitch, key, value) falcon.npcs.append(partyswitch) pilot = random.choice([s for s in starting if s < 12]) pilot_sub = Substitution() pilot_sub.bytestring = bytearray([0x3D, pilot, 0x45, 0x3F, pilot, 0x01]) for i in range(14): if i == pilot: continue pilot_sub.bytestring += bytearray([0x3F, i, 0x00]) pilot_sub.set_location(0xC2110) pilot_sub.write(fout) if options_.is_code_active("racecave"): randomize_tower(filename=sourcefile, ancient=True, nummaps=50) elif options_.is_code_active("speedcave"): randomize_tower(filename=sourcefile, ancient=True, nummaps=85) else: randomize_tower(filename=sourcefile, ancient=True, nummaps=300) manage_map_names(fout) unused_enemies = [u for u in get_monsters() if u.id in REPLACE_ENEMIES] def safe_boss_validator(formation): if formation.is_fanatics: return False if set(formation.present_enemies) & set(unused_enemies): return False if formation.formid in NOREPLACE_FORMATIONS: return False if not (any([m.boss_death for m in formation.present_enemies]) or formation.get_music() in [1, 2, 5]): return False if formation.get_music() == 0: return False if formation.formid in [0x1b0, 0x1b3, 0x1d9, 0x1db, 0x1d7]: return False if formation.formid in [ 0x1a4, 0x1d4, 0x1d5, 0x1d6, 0x1e4, 0x1e2, 0x1ff, 0x1bd, 0x1be ]: return False if (options_.is_code_active("racecave") and formation.formid in [0x162, 0x1c8, 0x1d3]): return False return True def challenge_battle_validator(formation): if len(formation.present_enemies) == 0: return False if set(formation.present_enemies) & set(unused_enemies): return False if formation.formid in NOREPLACE_FORMATIONS: return False if formation.battle_event: return False if formation.formid in [ 0x1a4, 0x1ff, 0x1bd, 0x1d7, 0x200, 0x201, 0x23f ]: return False if formation.get_music() == 0: if any([ f for f in formations if f.formid != formation.formid and set(f.enemy_ids) == set(formation.enemy_ids) and f.get_music() != 0 ]): return False best_drop = formation.get_best_drop() if best_drop and (best_drop.price <= 2 or best_drop.price >= 30000 or options_.is_code_active("madworld")): return True return False formations = sorted(get_formations(), key=lambda f: f.rank()) enemy_formations = [ f for f in formations if f.is_fanatics or ( f.present_enemies and not f.has_event and not f.has_boss) ] enemy_formations = [ f for f in enemy_formations if f.formid not in REPLACE_FORMATIONS + NOREPLACE_FORMATIONS ] boss_formations = [f for f in formations if safe_boss_validator(f)] used_formations = [] challenges = sorted( [f for f in formations if challenge_battle_validator(f)], key=lambda f: f.get_best_drop().rank())[-48:] challenges = sorted(random.sample(challenges, 24), key=lambda f: f.rank()) challenges = [f.formid for f in challenges] challenges = { 1: challenges[:6], 2: challenges[6:12], 3: challenges[12:18], 4: challenges[18:24] } ch_bgs = list(range(0x31)) + [0x36, 0x37] waters = [0xD, 0x1F, 0x23] snows = [0x12] ch_bgs = random.sample(ch_bgs, 10) + [random.choice(waters), snows[0]] random.shuffle(ch_bgs) for l in get_locations(): if not hasattr(l, "ancient_rank"): l.entrance_set.entrances = [] l.entrance_set.longentrances = [] l.chests = [] l.attacks = 0 l.write_data(fout) pointer = 0xB4E35 if options_.is_code_active('racecave'): candidates = [c for c in starting if c != runaway] leaders = random.sample(candidates, 3) subptr = pointer - 0xa0000 leader_sub = Substitution() # makes switching impossible and makes row change instant # could freeze the game d+pad and A on same frame tho leader_sub.set_location(0x324b7) leader_sub.bytestring = bytes([0xEA, 0xEA, 0xEA]) leader_sub.write(fout) leader_sub.set_location(0x32473) leader_sub.bytestring = bytes([0xEA, 0xEA]) leader_sub.write(fout) leader_sub.set_location(0xa02da) leader_sub.bytestring = bytes( [0xB2, subptr & 0xFF, (subptr >> 8) & 0xFF, subptr >> 16]) leader_sub.write(fout) leader_sub.set_location(pointer) leader_sub.bytestring = bytearray([]) locked = 0 for i, c in enumerate(leaders): leader_sub.bytestring += bytearray([0x3F, c, i + 1]) locked |= (1 << c) for c in range(16): if c in leaders: continue leader_sub.bytestring += bytearray([0x3F, c, 0x00]) leader_sub.bytestring += bytearray([0x3E, c]) leader_sub.bytestring += bytearray( [0x47, 0xE1, 0xB2, 0x0B, 0xC9, 0x00, 0x45]) for i, c in enumerate(leaders): leader_sub.bytestring += bytearray([0x3F, c, 0]) leader_sub.bytestring += bytearray([0x3F, c, i + 1]) leader_sub.bytestring += bytearray( [0x99, 0x03, locked & 0xFF, locked >> 8]) for i in [14, 15]: byte, bit = i // 8, i % 8 mem_addr = ((0x1b + byte) << 3) | bit leader_sub.bytestring += bytearray([0xD6, mem_addr]) leader_sub.bytestring += bytearray([0x96, 0xFE]) leader_sub.write(fout) pswitch_ptr = pointer - 0xa0000 pointer += len(leader_sub.bytestring) espersubs = {} for esper, event_value in esperevents.items(): byte, bit = event_value // 8, event_value % 8 mem_addr = ((0x17 + byte) << 3) | bit espersub = Substitution() espersub.set_location(pointer) espersub.bytestring = [ 0xF4, 0x8D, 0x86, event_value + 0x36, 0xD7, mem_addr, 0x3E, None, 0xFE ] espersubs[esper] = espersub pointer += espersub.size inn_template = [ 0x4B, None, None, 0x4B, 0x11, 0x81, 0xB6, None, None, None, None, None, None, 0xFE ] inn_template2 = [ 0x85, None, None, 0xC0, 0xBE, 0x81, 0xFF, 0x69, 0x01, 0x31, 0x84, 0xC3, 0x8F, 0x84, 0xFF, 0xF4, 0x2C, 0x73, 0x30, 0x0E, 0x01, 0x02, 0x06, 0x16, 0x31, 0x86, 0xC3, 0x9C, 0x80, 0x8D, 0xCE, 0xFF, 0xB2, 0x67, 0xCF, 0x00, 0xF0, 0xB8, 0xFA, 0x31, 0x85, 0xD5, 0x36, 0x05, 0xCE, 0xFF, 0xB2, 0x96, 0xCF, 0x00, 0xFE ] prices = { 1: (500, 0xA6E), 2: (2000, 0xA71), 3: (8000, 0xA5F), 4: (30000, 0xA64) } if options_.is_code_active("racecave"): partyswitch_template = [ 0x4B, None, None, 0x4B, 0x86, 0x83, 0xB6, None, None, None, None, None, None, 0xFE ] partyswitch_template2 = [ 0x85, None, None, 0xC0, 0xBE, 0x81, 0xFF, 0x69, 0x01, 0xB2, pswitch_ptr & 0xFF, (pswitch_ptr >> 8) & 0xFF, pswitch_ptr >> 16, 0xFE ] save_template = [ 0x4B, None, None, 0x4B, 0x24, 0x85, 0xB6, None, None, None, None, None, None, 0xFE ] save_template2 = [ 0x85, None, None, 0xC0, 0xBE, 0x81, 0xFF, 0x69, 0x01, 0xB2, 0xEB, 0x9A, 0x02, 0xFE ] enemy_template = [ 0x4B, 0x0B, 0x07, 0xB6, None, None, None, None, None, None, 0xA0, None, None, 0xB3, 0x5E, 0x70, 0x4D, None, None, 0xA1, 0x00, 0x96, 0x5C, 0xFE ] def make_challenge_event(loc, ptr): bg = ch_bgs.pop() formids = random.sample(challenges[loc.restrank], 2) formations = [get_formation(formid) for formid in formids] for formid in formids: challenges[loc.restrank].remove(formid) setcands = [f for f in get_fsets() if f.setid >= 0x100 and f.unused] fset = setcands.pop() fset.formids = formids fset.write_data(fout) timer = max( [e.stats['hp'] for f in formations for e in f.present_enemies]) reverse = False if timer >= 32768: reverse = True timer = 65535 - timer timer = max(timer, 3600) half = None while half is None or random.randint(1, 5) == 5: half = timer // 2 timer = half + random.randint(0, half) + random.randint(0, half) if reverse: timer = 65535 - timer timer = int(round(timer / 1800.0)) timer = max(2, min(timer, 36)) timer = timer * 1800 timer = [timer & 0xFF, timer >> 8] addr1 = ptr + 10 - 0xa0000 addr2 = ptr + (len(enemy_template) - 1) - 0xa0000 addr1 = [addr1 & 0xFF, (addr1 >> 8) & 0xFF, addr1 >> 16] addr2 = [addr2 & 0xFF, (addr2 >> 8) & 0xFF, addr2 >> 16] bytestring = list(enemy_template) bytestring[4:7] = addr1 bytestring[7:10] = addr2 bytestring[11:13] = timer bytestring[17] = fset.setid & 0xFF bytestring[18] = bg assert None not in bytestring sub = Substitution() sub.set_location(ptr) sub.bytestring = bytes(bytestring) sub.write(fout) return ptr + len(enemy_template) shops = get_shops(sourcefile) shopranks = {} itemshops = [s for s in shops if s.shoptype_pretty in ["items", "misc"]] othershops = [s for s in shops if s not in itemshops] othershops = othershops[random.randint(0, len(othershops) // 2):] itemshops = sorted(random.sample(itemshops, 5), key=lambda p: p.rank()) othershops = sorted(random.sample(othershops, 7), key=lambda p: p.rank()) for i in range(1, 5): if i > 1: shopranks[i] = othershops[:2] + itemshops[:1] othershops = othershops[2:] itemshops = itemshops[1:] else: shopranks[i] = othershops[:1] + itemshops[:2] othershops = othershops[1:] itemshops = itemshops[2:] assert len(shopranks[i]) == 3 random.shuffle(shopranks[i]) shopranks[random.randint(1, 4)][random.randint(0, 2)] = None levelmusic = {} dungeonmusics = [23, 24, 33, 35, 55, 71, 40, 41, 75, 77, 78] random.shuffle(dungeonmusics) for i in range(5): levelmusic[i] = dungeonmusics.pop() locations = [l for l in get_locations() if hasattr(l, "ancient_rank")] locations = sorted(locations, key=lambda l: l.ancient_rank) restlocs = [l for l in locations if hasattr(l, "restrank")] ban_musics = [0, 36, 56, 57, 58, 73, 74, 75] + list(levelmusic.values()) restmusics = [m for m in range(1, 85) if m not in ban_musics] random.shuffle(restmusics) optional_chars = [c for c in characters if hasattr(c, "slotid")] optional_chars = [ c for c in optional_chars if c.slotid == runaway or (c.id not in starting and c.id in charcands) ] if options_.is_code_active("speedcave"): while len(optional_chars) < 24: if random.choice([True, True, False]): supplement = [ c for c in optional_chars if c.id >= 14 or c.slotid == runaway ] else: supplement = list(optional_chars) supplement = sorted(set(supplement), key=lambda c: c.id) optional_chars.append(random.choice(supplement)) random.shuffle(optional_chars) ptr = pointer - 0xA0000 c0, b0, a0 = ptr & 0xFF, (ptr >> 8) & 0xFF, ptr >> 16 ptr = (pointer + 10) - 0xA0000 c1, b1, a1 = ptr & 0xFF, (ptr >> 8) & 0xFF, ptr >> 16 ptr = (pointer + 20) - 0xA0000 c2, b2, a2 = ptr & 0xFF, (ptr >> 8) & 0xFF, ptr >> 16 num_in_party_sub = Substitution() num_in_party_sub.set_location(0xAC654) num_in_party_sub.bytestring = [0xB2, c0, b0, a0] num_in_party_sub.write(fout) num_in_party_sub.set_location(pointer) num_in_party_sub.bytestring = bytes([ 0xC0, 0xAE, 0x01, c1, b1, a1, 0xB2, 0x80, 0xC6, 0x00, 0xC0, 0xAF, 0x01, c2, b2, a2, 0xB2, 0x80, 0xC6, 0x00, 0xD3, 0xA3, 0xD3, 0xA2, 0xFE ]) num_in_party_sub.write(fout) pointer += len(num_in_party_sub.bytestring) ally_addrs = {} for chosen in set(optional_chars): byte, bit = chosen.slotid // 8, chosen.slotid % 8 mem_addr = ((0x1b + byte) << 3) | bit allysub = Substitution() for party_id in range(1, 4): for npc_id in range(4, 6): allysub.set_location(pointer) allysub.bytestring = [ 0xB2, 0xC1, 0xC5, 0x00, # set caseword 0xC0, 0xA3, 0x81, None, None, None ] allysub.bytestring += [ 0xD4, 0xF0 | chosen.slotid, 0xD4, 0xE0 | chosen.slotid, 0xD7, mem_addr ] if chosen.id >= 14 or options_.is_code_active("speedcave"): allysub.bytestring += [ 0x77, chosen.slotid, 0x8b, chosen.slotid, 0x7F, 0x8c, chosen.slotid, 0x7F, 0x88, chosen.slotid, 0x00, 0x00 ] allysub.bytestring += [ 0x3E, 0x10 | npc_id, 0x3D, chosen.slotid, 0x3F, chosen.slotid, party_id, 0x47, 0x45, 0xF4, 0xD0, 0xFE ] pointer = pointer + len(allysub.bytestring) uptr = (pointer - 1) - 0xa0000 a, b, c = (uptr >> 16, (uptr >> 8) & 0xFF, uptr & 0xFF) allysub.bytestring[7:10] = [c, b, a] allysub.write(fout) event_addr = (allysub.location - 0xa0000) & 0x3FFFF ally_addrs[chosen.id, party_id, npc_id] = event_addr npc_palettes = get_npc_palettes() for g in npc_palettes: npc_palettes[g] = [v for v in npc_palettes[g] if 0 <= v <= 5] for g in range(14, 63): if g not in npc_palettes or not npc_palettes[g]: npc_palettes[g] = list(range(6)) def make_paysub(template, template2, loc, ptr): sub = Substitution() sub.set_location(ptr) price, message = prices[loc.restrank] message |= 0x8000 sub.bytestring = list(template) ptr += len(template) price = [price & 0xFF, price >> 8] message = [message & 0xFF, message >> 8] p = (ptr - 0xA0000) & 0x3FFFF p2 = p - 1 ptrbytes = [p & 0xFF, (p >> 8) & 0xFF, p >> 16] ptrbytes2 = [p2 & 0xFF, (p2 >> 8) & 0xFF, p2 >> 16] mapid = [loc.locid & 0xFF, loc.locid >> 8] mapid[1] |= 0x23 sub.bytestring[1:3] = message sub.bytestring[7:10] = ptrbytes sub.bytestring[10:13] = ptrbytes2 assert None not in sub.bytestring assert len(sub.bytestring) == 14 sub.bytestring += template2 ptr += len(template2) sub.bytestring[15:17] = price assert None not in sub.bytestring sub.bytestring = bytes(sub.bytestring) sub.write(fout) return sub random.shuffle(restlocs) for l in restlocs: assert l.ancient_rank == 0 l.music = restmusics.pop() l.make_warpable() innsub = make_paysub(inn_template, inn_template2, l, pointer) pointer += innsub.size savesub = make_paysub(save_template, save_template2, l, pointer) pointer += savesub.size if options_.is_code_active('racecave'): pswitch_sub = make_paysub(partyswitch_template, partyswitch_template2, l, pointer) pointer += pswitch_sub.size event_addr = (innsub.location - 0xa0000) & 0x3FFFF innkeeper = NPCBlock(pointer=None, locid=l.locid) graphics = random.randint(14, 62) palette = random.choice(npc_palettes[graphics]) attributes = { "graphics": graphics, "palette": palette, "x": 52, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(innkeeper, key, value) l.npcs.append(innkeeper) unequipper = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x1e, "palette": 3, "x": 49, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": 0x23510, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(unequipper, key, value) l.npcs.append(unequipper) event_addr = (savesub.location - 0xa0000) & 0x3FFFF pay_to_save = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x6f, "palette": 6, "x": 47, "y": 4, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 3, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 2, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(pay_to_save, key, value) l.npcs.append(pay_to_save) if l.restrank == 4: final_loc = get_location(412) if len(final_loc.npcs) < 2: final_save = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x6f, "palette": 6, "x": 82, "y": 43, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 3, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 2, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0, "npcid": 1 } for key, value in attributes.items(): setattr(final_save, key, value) final_loc.npcs.append(final_save) shop = shopranks[l.restrank].pop() if shop is not None: shopsub = Substitution() shopsub.set_location(pointer) shopsub.bytestring = bytes([0x9B, shop.shopid, 0xFE]) shopsub.write(fout) pointer += len(shopsub.bytestring) event_addr = (shopsub.location - 0xa0000) & 0x3FFFF else: event_addr = 0x178cb colsub = Substitution() colsub.set_location(0xb78ea) colsub.bytestring = bytes([0x59, 0x04, 0x5C, 0xFE]) colsub.write(fout) shopkeeper = NPCBlock(pointer=None, locid=l.locid) graphics = random.randint(14, 62) palette = random.choice(npc_palettes[graphics]) attributes = { "graphics": graphics, "palette": palette, "x": 39, "y": 11, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 1, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(shopkeeper, key, value) l.npcs.append(shopkeeper) if optional_chars: chosen = optional_chars.pop() assert chosen.palette is not None if chosen.id >= 14 and False: byte, bit = 0, 0 else: byte, bit = (chosen.slotid // 8) + 0x1b, chosen.slotid % 8 event_addr = ally_addrs[chosen.id, l.party_id, len(l.npcs)] ally = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": chargraphics[chosen.id], "palette": chosen.palette, "x": 54, "y": 18, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": byte, "membit": bit, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(ally, key, value) l.npcs.append(ally) if (len(optional_chars) == 12 or (len(optional_chars) > 0 and options_.is_code_active('speedcave'))): temp = optional_chars.pop() if chosen.id != temp.id: chosen = temp if chosen.id >= 14 and False: byte, bit = 0, 0 else: byte, bit = (chosen.slotid // 8) + 0x1b, chosen.slotid % 8 event_addr = ally_addrs[chosen.id, l.party_id, len(l.npcs)] attributes = { "graphics": chargraphics[chosen.id], "palette": chosen.palette, "x": 53, "y": 18, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": byte, "membit": bit, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } ally = NPCBlock(pointer=None, locid=l.locid) for key, value in attributes.items(): setattr(ally, key, value) l.npcs.append(ally) if l.restrank == 1: num_espers = 3 elif l.restrank in [2, 3]: num_espers = 2 elif l.restrank == 4: num_espers = 1 for i in range(num_espers): if len(espers) == 0: break if options_.is_code_active('speedcave'): candidates = espers else: esperrank = l.restrank if random.randint(1, 7) == 7: esperrank += 1 candidates = [] while not candidates: candidates = [e for e in espers if e.rank == esperrank] if not candidates or random.randint(1, 3) == 3: candidates = [e for e in espers if e.rank <= esperrank] if not candidates: esperrank += 1 esper = random.choice(candidates) espers.remove(esper) espersub = espersubs[esper.name] index = espersub.bytestring.index(None) espersub.bytestring[index] = 0x10 | len(l.npcs) espersub.write(fout) event_addr = (espersub.location - 0xa0000) & 0x3FFFF event_value = esperevents[esper.name] byte, bit = event_value // 8, event_value % 8 magicite = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x5B, "palette": 2, "x": 44 + i, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 0, "no_turn_when_speaking": True, "layer_priority": 2, "special_anim": 2, "memaddr": byte + 0x17, "membit": bit, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(magicite, key, value) l.npcs.append(magicite) event_addr = pointer - 0xa0000 pointer = make_challenge_event(l, pointer) enemy = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x3e, "palette": 2, "x": 42, "y": 6, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(enemy, key, value) l.npcs.append(enemy) if options_.is_code_active('racecave'): event_addr = (pswitch_sub.location - 0xa0000) & 0x3FFFF partyswitch = NPCBlock(pointer=None, locid=l.locid) attributes = { "graphics": 0x17, "palette": 0, "x": 55, "y": 16, "show_on_vehicle": False, "speed": 0, "event_addr": event_addr, "facing": 2, "no_turn_when_speaking": False, "layer_priority": 0, "special_anim": 0, "memaddr": 0, "membit": 0, "bg2_scroll": 0, "move_type": 0, "sprite_priority": 0, "vehicle": 0 } for key, value in attributes.items(): setattr(partyswitch, key, value) l.npcs.append(partyswitch) assert len(optional_chars) == 0 if pointer >= 0xb6965: raise Exception("Cave events out of bounds. %x" % pointer) # lower encounter rate dungeon_rates = [ 0x38, 0, 0x20, 0, 0xb0, 0, 0x00, 1, 0x1c, 0, 0x10, 0, 0x58, 0, 0x80, 0 ] + ([0] * 16) assert len(dungeon_rates) == 32 encrate_sub = Substitution() encrate_sub.set_location(0xC2BF) encrate_sub.bytestring = bytes(dungeon_rates) encrate_sub.write(fout) maxrank = max(locations, key=lambda l: l.ancient_rank).ancient_rank for l in locations: if l not in restlocs and (l.npcs or l.events): for n in l.npcs: if n == final_save: continue if n.graphics == 0x6F: n.memaddr, n.membit, n.event_addr = 0x73, 1, 0x5EB3 success = False for e in l.events: if e.x % 128 == n.x % 128 and e.y % 128 == n.y % 128: if success: raise Exception("Duplicate events found.") e.event_addr = 0x5EB3 success = True if not success: raise Exception("No corresponding event found.") for e in l.entrances: e.dest |= 0x800 rank = l.ancient_rank l.name_id = min(rank, 0xFF) if not hasattr(l, "restrank"): if hasattr(l, "secret_treasure") and l.secret_treasure: pass elif l.locid == 334 or not hasattr(l, "routerank"): l.music = 58 elif l.routerank in levelmusic: l.music = levelmusic[l.routerank] else: raise Exception l.setid = rank if rank == 0: l.attacks = 0 elif rank > 0xFF: l.setid = random.randint(0xF0, 0xFF) else: def enrank(r): mr = min(maxrank, 0xFF) r = max(0, min(r, mr)) if options_.is_code_active('racecave'): half = r // 2 quarter = half // 2 r = (half + random.randint(0, quarter) + random.randint(0, quarter)) if r <= 0: return 0 elif r >= mr: return 1.0 ratio = float(r) / mr return ratio low = enrank(rank - 3) high = enrank(rank + 2) high = int(round(high * len(enemy_formations))) low = int(round(low * len(enemy_formations))) while high - low < 4: high = min(high + 1, len(enemy_formations)) low = max(low - 1, 0) candidates = enemy_formations[low:high] chosen_enemies = random.sample(candidates, 4) chosen_enemies = sorted(chosen_enemies, key=lambda f: f.rank()) if options_.is_code_active('racecave'): bossify = False elif rank >= maxrank * 0.9: bossify = True else: if options_.is_code_active('speedcave'): thresh = 0.5 else: thresh = 0.1 bossify = rank >= random.randint(int(maxrank * thresh), int(maxrank * 0.9)) bossify = bossify and random.randint(1, 3) == 3 if bossify: formrank = chosen_enemies[0].rank() candidates = [ c for c in boss_formations if c.rank() >= formrank ] if candidates: if rank < maxrank * 0.75: candidates = candidates[:random.randint(2, 4)] chosen_boss = random.choice(candidates) chosen_enemies[3] = chosen_boss if options_.is_code_active('speedcave'): thresh, bossthresh = 2, 1 else: # allow up to three of the same formation thresh, bossthresh = 3, 2 for c in chosen_enemies: used_formations.append(c) if used_formations.count(c) >= bossthresh: if c in boss_formations: boss_formations.remove(c) if used_formations.count(c) >= thresh: if c in enemy_formations: enemy_formations.remove(c) fset = get_fset(rank) fset.formids = [f.formid for f in chosen_enemies] for formation in fset.formations: if formation.get_music() == 0: formation.set_music(6) formation.set_continuous_music() formation.write_data(fout) fset.write_data(fout) if not (hasattr(l, "secret_treasure") and l.secret_treasure): if options_.is_code_active('speedcave') or rank == 0: low = random.randint(0, 400) high = random.randint(low, low * 5) high = random.randint(low, high) else: low = rank * 2 high = low * 1.5 while random.choice([True, False, False]): high = high * 1.5 if rank < maxrank * 0.4: monster = False else: monster = None if 0 < rank < maxrank * 0.75: enemy_limit = sorted([f.rank() for f in fset.formations])[-2] enemy_limit *= 1.5 else: enemy_limit = None l.unlock_chests(int(low), int(high), monster=monster, guarantee_miab_treasure=True, enemy_limit=enemy_limit, uncapped_monsters=options_.is_code_active('bsiab')) l.write_data(fout) final_cut = Substitution() final_cut.set_location(0xA057D) final_cut.bytestring = bytearray([ 0x3F, 0x0E, 0x00, 0x3F, 0x0F, 0x00, ]) if not options_.is_code_active("racecave"): final_cut.bytestring += bytearray( [0x9D, 0x4D, 0x65, 0x33, 0xB2, 0xA9, 0x5E, 0x00]) else: for i in range(16): final_cut.bytestring += bytearray([0x3F, i, 0x00]) locked = 0 protected = random.sample(starting, 4) assignments = {0: [], 1: [], 2: [], 3: []} for i, c in enumerate(protected): if 1 <= i <= 3 and random.choice([True, False]): assignments[i].append(c) chars = list(range(16)) random.shuffle(chars) for c in chars: if c in protected: continue if c >= 14 and random.choice([True, False]): continue if random.choice([True, True, False]): i = random.randint(0, 3) if len(assignments[i]) >= 3: continue elif len(assignments[i]) == 2 and random.choice([True, False]): continue assignments[i].append(c) for key in assignments: for c in assignments[key]: locked |= (1 << c) if key > 0: final_cut.bytestring += bytearray([0x3F, c, key]) final_cut.bytestring += bytearray( [0x99, 0x03, locked & 0xFF, locked >> 8]) from chestrandomizer import get_2pack event_bosses = { 1: [0xC18A4, 0xC184B], 2: [0xC16DD, 0xC171D, 0xC1756], 3: [None, None, None] } fout.seek(0xA0F6F) fout.write(bytes([0x36])) candidates = sorted(boss_formations, key=lambda b: b.rank()) candidates = [c for c in candidates if c.inescapable] candidates = candidates[random.randint(0, len(candidates) - 16):] chosens = random.sample(candidates, 8) chosens = sorted(chosens, key=lambda b: b.rank()) for rank in sorted(event_bosses): num = len(event_bosses[rank]) rankchosens, chosens = chosens[:num], chosens[num:] assert len(rankchosens) == num random.shuffle(rankchosens) if rank == 3: bgs = random.sample([ 0x07, 0x0D, 0x17, 0x18, 0x19, 0x1C, 0x1F, 0x21, 0x22, 0x23, 0x29, 0x2C, 0x30, 0x36, 0x37 ], 3) for i, (address, chosen) in enumerate(zip(event_bosses[rank], rankchosens)): if rank == 3: chosen.set_music(5) elif rank == 2: chosen.set_music(2) else: chosen.set_music(4) form_music_overrides[chosen.formid] = chosen.get_music() chosen.set_appearing([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13]) fset = get_2pack(chosen) if address is not None: fout.seek(address) fout.write(bytes([fset.setid & 0xFF])) else: bg = bgs.pop() final_cut.bytestring += bytearray([ 0x46, i + 1, 0x4D, fset.setid & 0xFF, bg, 0xB2, 0xA9, 0x5E, 0x00 ]) assert len(chosens) == 0 final_cut.bytestring += bytearray([0xB2, 0x64, 0x13, 0x00]) final_cut.write(fout)
def get_table_objects(objtype, filename=None): pointer = objtype.specspointer number = objtype.specscount grouped = objtype.specsgrouped pointed = objtype.specspointed identifier = (objtype, pointer, number) if identifier in already_gotten: return already_gotten[identifier] if filename is None: filename = GLOBAL_OUTPUT objects = [] def add_objects(n, groupindex=0, p=None): if p is None: p = pointer accumulated_size = 0 for i in xrange(n): obj = objtype(filename, p, index=len(objects), groupindex=groupindex) objects.append(obj) p += obj.total_size accumulated_size += obj.total_size return accumulated_size def add_variable_object(p1, p2): size = p2 - p1 obj = objtype(filename, p1, index=len(objects), groupindex=0, size=size) objects.append(obj) return size if not grouped and not pointed: add_objects(number) elif grouped: counter = 0 while len(objects) < number: if objtype.specs.groupednum is None: f = open(filename, 'r+b') f.seek(pointer) value = ord(f.read(1)) f.close() pointer += 1 else: value = objtype.specs.groupednum pointer += add_objects(value, groupindex=counter) counter += 1 elif pointed and objtype.total_size > 0: size = objtype.specspointedsize counter = 0 f = open(filename, 'r+b') while counter < number: f.seek(pointer) subpointer = read_multi(f, size) + objtype.specspointedpointer f.seek(pointer + size) subpointer2 = read_multi(f, size) + objtype.specspointedpointer groupcount = (subpointer2 - subpointer) / objtype.total_size if objtype.specspointedpoint1: groupcount = 1 add_objects(groupcount, groupindex=counter, p=subpointer) pointer += size counter += 1 elif pointed and objtype.total_size == 0: size = objtype.specspointedsize counter = 0 f = open(filename, 'r+b') while counter < number: f.seek(pointer + (size*counter)) subpointer = read_multi(f, size) + objtype.specspointedpointer f.seek(pointer + (size*counter) + size) subpointer2 = read_multi(f, size) + objtype.specspointedpointer add_variable_object(subpointer, subpointer2) counter += 1 already_gotten[identifier] = objects return get_table_objects(objtype, filename=filename)
def recolor_character_palette(fout, pointer, palette=None, flesh=False, middle=True, santa=False, skintones=None, char_hues=None, trance=False): fout.seek(pointer) if palette is None: palette = [read_multi(fout, length=2) for _ in range(16)] outline, eyes, hair, skintone, outfit1, outfit2, NPC = (palette[:2], palette[2:4], palette[4:6], palette[6:8], palette[8:10], palette[10:12], palette[12:]) def components_to_color(xxx_todo_changeme): (red, green, blue) = xxx_todo_changeme return red | (green << 5) | (blue << 10) new_style_palette = None if skintones and char_hues: new_style_palette = generate_character_palette(skintones, char_hues, trance=trance) # aliens, available in palette 5 only if flesh and random.randint(1, 20) == 1: transformer = get_palette_transformer(middle=middle) new_style_palette = transformer(new_style_palette) elif trance: new_style_palette = generate_character_palette(trance=True) new_palette = new_style_palette if new_style_palette else [] if not flesh: pieces = (outline, eyes, hair, skintone, outfit1, outfit2, NPC) if not new_style_palette else [NPC] for piece in pieces: transformer = get_palette_transformer(middle=middle) piece = list(piece) piece = transformer(piece) new_palette += piece if not new_style_palette: new_palette[6:8] = skintone if options_.is_code_active('christmas'): if santa: # color kefka's palette to make him look santa-ish new_palette = palette new_palette[8] = components_to_color((0x18, 0x18, 0x16)) new_palette[9] = components_to_color((0x16, 0x15, 0x0F)) new_palette[10] = components_to_color((0x1C, 0x08, 0x03)) new_palette[11] = components_to_color((0x18, 0x02, 0x05)) else: # give them red & green outfits red = [ components_to_color((0x19, 0x00, 0x05)), components_to_color((0x1c, 0x02, 0x04)) ] green = [ components_to_color((0x07, 0x12, 0x0b)), components_to_color((0x03, 0x0d, 0x07)) ] random.shuffle(red) random.shuffle(green) outfit = [red, green] random.shuffle(outfit) new_palette[8:10] = outfit[0] new_palette[10:12] = outfit[1] else: transformer = get_palette_transformer(middle=middle) new_palette = transformer(palette) if new_style_palette: new_palette = new_style_palette[0:12] + new_palette[12:] palette = new_palette fout.seek(pointer) for p in palette: write_multi(fout, p, length=2) return palette
def manage_character_appearance(fout, preserve_graphics=False): characters = get_characters() wild = options_.is_code_active('partyparty') sabin_mode = options_.is_code_active('suplexwrecks') tina_mode = options_.is_code_active('bravenudeworld') soldier_mode = options_.is_code_active('quikdraw') moogle_mode = options_.is_code_active('kupokupo') ghost_mode = options_.is_code_active('halloween') christmas_mode = options_.is_code_active('christmas') sprite_swap_mode = options_.is_code_active('makeover') and not ( sabin_mode or tina_mode or soldier_mode or moogle_mode or ghost_mode) new_palette_mode = not options_.is_code_active('sometimeszombies') if new_palette_mode: # import recolors for incompatible base sprites recolors = [("cyan", 0x152D40, 0x16A0), ("mog", 0x15E240, 0x16A0), ("umaro", 0x162620, 0x16A0), ("dancer", 0x1731C0, 0x5C0), ("lady", 0x1748C0, 0x5C0)] for rc in recolors: filename = os.path.join("data", "sprites", "RC" + rc[0] + ".bin") try: with open_mei_fallback(filename, "rb") as f: sprite = f.read() except OSError: continue if len(sprite) >= rc[2]: sprite = sprite[:rc[2]] fout.seek(rc[1]) fout.write(sprite) if (wild or tina_mode or sabin_mode or christmas_mode): if christmas_mode: char_ids = list(range(0, 0x15)) # don't replace kefka else: char_ids = list(range(0, 0x16)) else: char_ids = list(range(0, 0x0E)) male = None female = None if tina_mode: change_to = dict(list(zip(char_ids, [0x12] * 100))) elif sabin_mode: change_to = dict(list(zip(char_ids, [0x05] * 100))) elif soldier_mode: change_to = dict(list(zip(char_ids, [0x0e] * 100))) elif ghost_mode: change_to = dict(list(zip(char_ids, [0x14] * 100))) elif moogle_mode: # all characters are moogles except Mog, Imp, and Esper Terra if wild: # make mog human mog = random.choice( list(range(0, 0x0A)) + list(range(0x0B, 0x0F)) + [0x10, 0x11, 0x13, 0x15]) #esper terra and imp neither human nor moogle esper_terra, imp = random.sample([0x0F, 0x12, 0x14], 2) else: mog = random.choice(list(range(0, 0x0A)) + list(range(0x0B, 0x0E))) esper_terra = 0x12 imp = 0x0F change_to = dict(list(zip(char_ids, [0x0A] * 100))) change_to[0x0A] = mog change_to[0x12] = esper_terra change_to[0x0F] = imp else: female = [0, 0x06, 0x08] female += [ c for c in [0x03, 0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0x14] if random.choice([True, False]) ] female = [c for c in char_ids if c in female] male = [c for c in char_ids if c not in female] if preserve_graphics: change_to = dict(list(zip(char_ids, char_ids))) elif wild: change_to = list(char_ids) random.shuffle(change_to) change_to = dict(list(zip(char_ids, change_to))) else: random.shuffle(female) random.shuffle(male) change_to = dict( list(zip(sorted(male), male)) + list(zip(sorted(female), female))) manage_character_names(fout, change_to, male) swap_to = get_sprite_swaps(char_ids, male, female, change_to) for c in characters: if c.id < 14: if sprite_swap_mode and c.id in swap_to: c.new_appearance = swap_to[c.id].name elif not preserve_graphics: c.new_appearance = NAME_ID_DICT[change_to[c.id]] else: c.new_appearance = c.original_appearance sprite_ids = list(range(0x16)) ssizes = ([0x16A0] * 0x10) + ([0x1560] * 6) spointers = dict([(c, sum(ssizes[:c]) + 0x150000) for c in sprite_ids]) ssizes = dict(list(zip(sprite_ids, ssizes))) char_portraits = {} char_portrait_palettes = {} sprites = {} riding_sprites = {} try: f = open(RIDING_SPRITE_TABLE, "r") except IOError: pass else: for line in f.readlines(): char_id, filename = line.strip().split(',', 1) try: g = open_mei_fallback( os.path.join("custom", "sprites", filename), "rb") except IOError: continue riding_sprites[int(char_id)] = g.read(0x140) g.close() f.close() for c in sprite_ids: fout.seek(0x36F1B + (2 * c)) portrait = read_multi(fout, length=2) char_portraits[c] = portrait fout.seek(0x36F00 + c) portrait_palette = fout.read(1) char_portrait_palettes[c] = portrait_palette fout.seek(spointers[c]) sprite = fout.read(ssizes[c]) if c in riding_sprites: sprite = sprite[:0x1560] + riding_sprites[c] sprites[c] = sprite if tina_mode: char_portraits[0x12] = char_portraits[0] char_portrait_palettes[0x12] = char_portrait_palettes[0] portrait_data = [] portrait_palette_data = [] fout.seek(0x2D1D00) for _ in range(19): portrait_data.append(fout.read(0x320)) fout.seek(0x2D5860) for _ in range(19): portrait_palette_data.append(fout.read(0x20)) free_portrait_ids, merchant = get_free_portrait_ids( swap_to, change_to, char_ids, char_portraits) for c in char_ids: new = change_to[c] portrait = char_portraits[new] portrait_palette = char_portrait_palettes[new] if c == 0x13 and sprite_swap_mode and not merchant: new_soldier = change_to[0xE] portrait = char_portraits[new_soldier] portrait_palette = char_portrait_palettes[new_soldier] elif (char_portraits[c] == 0 and c != 0): portrait = char_portraits[0xE] portrait_palette = char_portrait_palettes[0xE] elif sprite_swap_mode and c in swap_to: use_fallback = True fallback_portrait_id = swap_to[c].fallback_portrait_id if fallback_portrait_id < 0 or fallback_portrait_id > 18: fallback_portrait_id = 0xE portrait = fallback_portrait_id * 0x320 portrait_palette = bytes([fallback_portrait_id]) new_portrait_data = portrait_data[fallback_portrait_id] new_portrait_palette_data = portrait_palette_data[ fallback_portrait_id] if swap_to[c].has_custom_portrait(): use_fallback = False try: g = open_mei_fallback( os.path.join("custom", "sprites", swap_to[c].portrait_filename), "rb") h = open_mei_fallback( os.path.join("custom", "sprites", swap_to[c].portrait_palette_filename), "rb") except IOError: use_fallback = True print("failed to load portrait %s for %s, using fallback" % (swap_to[c].portrait_filename, swap_to[c].name)) else: new_portrait_data = g.read(0x320) new_portrait_palette_data = h.read(0x20) h.close() g.close() if not use_fallback or fallback_portrait_id in free_portrait_ids: portrait_id = free_portrait_ids[0] portrait = portrait_id * 0x320 portrait_palette = bytes([portrait_id]) free_portrait_ids.remove(free_portrait_ids[0]) fout.seek(0x2D1D00 + portrait) fout.write(new_portrait_data) fout.seek(0x2D5860 + portrait_id * 0x20) fout.write(new_portrait_palette_data) elif portrait == 0 and wild and change_to[c] != 0: portrait = char_portraits[0xE] portrait_palette = char_portrait_palettes[0xE] fout.seek(0x36F1B + (2 * c)) write_multi(fout, portrait, length=2) fout.seek(0x36F00 + c) fout.write(portrait_palette) if wild: fout.seek(spointers[c]) fout.write(sprites[0xE][:ssizes[c]]) fout.seek(spointers[c]) if sprite_swap_mode and c in swap_to: try: g = open_mei_fallback( os.path.join("custom", "sprites", swap_to[c].file), "rb") except IOError: newsprite = sprites[change_to[c]] for ch in characters: if ch.id == c: ch.new_appearance = NAME_ID_DICT[change_to[c]] else: newsprite = g.read(min(ssizes[c], swap_to[c].size)) # if it doesn't have riding sprites, it probably doesn't have a death sprite either if swap_to[c].size < 0x16A0: newsprite = newsprite[:0xAE0] + sprites[0xE][ 0xAE0:0xBA0] + newsprite[0xBA0:] g.close() else: newsprite = sprites[change_to[c]] newsprite = newsprite[:ssizes[c]] fout.write(newsprite) # celes in chains fout.seek(0x159500) chains = fout.read(192) fout.seek(0x17D660) fout.write(chains) manage_palettes(fout, change_to, char_ids)