class SpriteGroupModule(EbModule.EbModule): _name = "Sprite Groups" def __init__(self): self._grPtrTbl = EbTable(0xef133f) self._grPalTbl = EbTable(0xc30000) self._groups = None def freeRanges(self): return [(0x2f1a7f, 0x2f4a3f), (0x110000, 0x11ffff), (0x120000, 0x12ffff), (0x130000, 0x13ffff), (0x140000, 0x14ffff), (0x150000, 0x154fff)] def free(self): del(self._grPtrTbl) del(self._grPalTbl) def readFromRom(self, rom): self._grPtrTbl.readFromRom(rom) updateProgress(5) self._grPalTbl.readFromRom(rom) updateProgress(5) # Load the sprite groups self._groups = [] pct = 40.0/self._grPtrTbl.height() for i in range(self._grPtrTbl.height()): # Note: this assumes that the SPT is written contiguously numSprites = 8 # Assume that the last group only has 8 sprites if i < self._grPtrTbl.height()-1: numSprites = (self._grPtrTbl[i+1,0].val() - self._grPtrTbl[i,0].val() - 9) / 2 g = SpriteGroup(numSprites) g.readFromRom(rom, EbModule.toRegAddr(self._grPtrTbl[i,0].val())) self._groups.append(g) updateProgress(pct) def writeToProject(self, resourceOpener): # Write the palettes self._grPalTbl.writeToProject(resourceOpener) updateProgress(5) out = { } i = 0 pct = 40.0/len(self._groups) for g in self._groups: out[i] = g.dump() img = g.toImage(self._grPalTbl[g.palette(),0].val()) imgFile = resourceOpener("SpriteGroups/" + str(i).zfill(3), 'png') img.save(imgFile, 'png', transparency=0) imgFile.close() del(img) i += 1 updateProgress(pct) yaml.dump(out, resourceOpener("sprite_groups", "yml"), Dumper=yaml.CSafeDumper) updateProgress(5) def readFromProject(self, resourceOpener): self._grPalTbl.readFromProject(resourceOpener) updateProgress(5) input = yaml.load(resourceOpener("sprite_groups", "yml"), Loader=yaml.CSafeLoader) numGroups = len(input) self._groups = [] pct = 45.0/numGroups for i in range(numGroups): g = SpriteGroup(16) g.load(input[i]) img = Image.open( resourceOpener("SpriteGroups/" + str(i).zfill(3), "png")) g.fromImage(img) palData = img.getpalette() del(img) self._groups.append(g) pal = [ ] # Read the palette from the image for i in range(1, 16): pal.append((palData[i*3], palData[i*3+1], palData[i*3+2])) # Assign the palette number to the sprite for i in range(8): if pal == self._grPalTbl[i,0].val()[1:]: g.setPalette(i) break else: # Error, this image uses an invalid palette raise RuntimeException("Sprite Group #" + i + "uses an invalid palette.") updateProgress(pct) def writeToRom(self, rom): numGroups = len(self._groups) self._grPtrTbl.clear(numGroups) with DataBlock(sum(map( lambda x: x.blockSize(), self._groups))) as block: loc = 0 i = 0 # Write all the groups to the block, and sprites to rom pct = 40.0 / numGroups for g in self._groups: g.writeSpritesToFree(rom) g.writeToBlock(block, loc) self._grPtrTbl[i,0].setVal(loc) loc += g.blockSize() i += 1 updateProgress(pct) # Write the block to rom and correct the group pointers addr = EbModule.toSnesAddr(block.writeToFree(rom)) for i in range(self._grPtrTbl.height()): self._grPtrTbl[i,0].setVal( self._grPtrTbl[i,0].val() + addr) # Write the pointer table self._grPtrTbl.writeToRom(rom) updateProgress(5) # Write the palettes self._grPalTbl.writeToRom(rom) updateProgress(5)
class DoorModule(EbModule.EbModule): _name = "Doors" def __init__(self): self._ptrTbl = EbTable("DOOR_POINTER_TABLE") self._entries = [ ] def readFromRom(self, rom): self._ptrTbl.readFromRom(rom) updateProgress(5) pct = 45.0/(40*32) for i in range(self._ptrTbl.height()): loc = EbModule.toRegAddr(self._ptrTbl[i,0].val()) entry = [ ] numDoors = rom.readMulti(loc, 2) loc += 2 for j in range(numDoors): d = Door() try: d.readFromRom(rom, loc) except ValueError: # Invalid door entry. Some entries in EB are invalid. # When we encounter one, just assume we've reached the end # of this entry. break entry.append(d) loc += 5 self._entries.append(entry) i += 1 updateProgress(pct) def writeToProject(self, resourceOpener): out = dict() x = y = 0 rowOut = dict() pct = 45.0/(40*32) for entry in self._entries: if not entry: rowOut[x%32] = None else: rowOut[x%32] = map(lambda z: z.dump(), entry) if (x % 32) == 31: # Start new row out[y] = rowOut x = 0 y += 1 rowOut = dict() else: x += 1 updateProgress(pct) with resourceOpener("map_doors", "yml") as f: s = yaml.dump(out, default_flow_style=False, Dumper=yaml.CSafeDumper) s = sub("Event Flag: (\d+)", lambda i: "Event Flag: " + hex(int(i.group(0)[12:])), s) f.write(s) updateProgress(5) def readFromProject(self, resourceOpener): self._entries = [] pct = 45.0/(40*32) with resourceOpener("map_doors", "yml") as f: updateProgress(5) input = yaml.load(f, Loader=yaml.CSafeLoader) for y in input: row = input[y] for x in row: if row[x] == None: self._entries.append(None) else: entry = [] for door in row[x]: d = Door() d.load(door) entry.append(d) self._entries.append(entry) updateProgress(pct) def writeToRom(self, rom): self._ptrTbl.clear(32*40) destWriteLoc = 0xF0000 destRangeEnd = 0xF58EE # TODO Is this correct? Can we go more? destLocs = dict() emptyEntryPtr = EbModule.toSnesAddr(rom.writeToFree([0, 0])) pct = 45.0/(40*32) i=0 for entry in self._entries: if (entry == None) or (not entry): self._ptrTbl[i,0].setVal(emptyEntryPtr) else: entryLen = len(entry) writeLoc = rom.getFreeLoc(2 + entryLen*5) self._ptrTbl[i,0].setVal(EbModule.toSnesAddr(writeLoc)) rom[writeLoc] = entryLen & 0xff rom[writeLoc+1] = entryLen >> 8 writeLoc += 2 for door in entry: destWriteLoc += door.writeToRom(rom, writeLoc, destWriteLoc, destRangeEnd, destLocs) writeLoc += 5 i += 1 updateProgress(pct) self._ptrTbl.writeToRom(rom) # Mark any remaining space as free if destWriteLoc < destRangeEnd: rom.addFreeRanges([(destWriteLoc, destRangeEnd)]) updateProgress(5)
class MapModule(EbModule.EbModule): _name = "Map" _MAP_PTRS_PTR_ADDR = 0xa1db _LOCAL_TSET_ADDR = 0x175000 _MAP_HEIGHT = 320 _MAP_WIDTH = 256 def __init__(self): self._tiles = [] self._mapSecTsetPalsTbl = EbTable(0xD7A800) self._mapSecMusicTbl = EbTable(0xDCD637) self._mapSecMiscTbl = EbTable(0xD7B200) self._mapSecTownMapTbl = EbTable(0xEFA70F) self.teleport = ValuedIntTableEntry(None, None, ["Enabled", "Disabled"]) self.townmap = ValuedIntTableEntry(None, None, ["None", "Onett", "Twoson", "Threed", "Fourside", "Scaraba", "Summers", "None 2"]) self.setting = ValuedIntTableEntry(None, None, ["None", "Indoors", "Exit Mouse usable", "Lost Underworld sprites", "Magicant sprites", "Robot sprites", "Butterflies", "Indoors and Butterflies"]) self.townmap_image = ValuedIntTableEntry(None, None, ["None", "Onett", "Twoson", "Threed", "Fourside", "Scaraba", "Summers" ]) self.townmap_arrow = ValuedIntTableEntry(None, None, ["None", "Up", "Down", "Right", "Left"]) def readFromRom(self, rom): # Read map tiles map_ptrs_addr = \ EbModule.toRegAddr(rom.readMulti(self._MAP_PTRS_PTR_ADDR, 3)) map_addrs = map(lambda x: \ EbModule.toRegAddr(rom.readMulti(map_ptrs_addr+x*4,4)), \ range(8)) self._tiles = map( lambda y: rom.readList(map_addrs[y%8] + ((y>>3)<<8), self._MAP_WIDTH).tolist(), range(self._MAP_HEIGHT)) k = self._LOCAL_TSET_ADDR for i in range(self._MAP_HEIGHT>>3): for j in range(self._MAP_WIDTH): self._tiles[i<<3][j] |= (rom[k] & 3) << 8 self._tiles[(i<<3)|1][j] |= ((rom[k] >> 2) & 3) << 8 self._tiles[(i<<3)|2][j] |= ((rom[k] >> 4) & 3) << 8 self._tiles[(i<<3)|3][j] |= ((rom[k] >> 6) & 3) << 8 self._tiles[(i<<3)|4][j] |= (rom[k+0x3000] & 3) << 8 self._tiles[(i<<3)|5][j] |= ((rom[k+0x3000] >> 2) & 3) << 8 self._tiles[(i<<3)|6][j] |= ((rom[k+0x3000] >> 4) & 3) << 8 self._tiles[(i<<3)|7][j] |= ((rom[k+0x3000] >> 6) & 3) << 8 k += 1 updateProgress(25) # Read sector data self._mapSecTsetPalsTbl.readFromRom(rom) updateProgress(25.0/4) self._mapSecMusicTbl.readFromRom(rom) updateProgress(25.0/4) self._mapSecMiscTbl.readFromRom(rom) updateProgress(25.0/4) self._mapSecTownMapTbl.readFromRom(rom) updateProgress(25.0/4) def writeToRom(self, rom): map_ptrs_addr = \ EbModule.toRegAddr(rom.readMulti(self._MAP_PTRS_PTR_ADDR, 3)) map_addrs = map(lambda x: \ EbModule.toRegAddr(rom.readMulti(map_ptrs_addr+x*4,4)), \ range(8)) for i in range(self._MAP_HEIGHT): rom.write(map_addrs[i%8] + ((i>>3)<<8), map(lambda x: x & 0xff, self._tiles[i])) k = self._LOCAL_TSET_ADDR for i in range(self._MAP_HEIGHT>>3): for j in range(self._MAP_WIDTH): c = ((self._tiles[i<<3][j] >> 8) | ((self._tiles[(i<<3)|1][j] >> 8) << 2) | ((self._tiles[(i<<3)|2][j] >> 8) << 4) | ((self._tiles[(i<<3)|3][j] >> 8) << 6)) rom.write(k, c) c = ((self._tiles[(i<<3)|4][j] >> 8) | ((self._tiles[(i<<3)|5][j] >> 8) << 2) | ((self._tiles[(i<<3)|6][j] >> 8) << 4) | ((self._tiles[(i<<3)|7][j] >> 8) << 6)) rom.write(k+0x3000, c) k += 1 updateProgress(25) # Write sector data self._mapSecTsetPalsTbl.writeToRom(rom) updateProgress(25.0/4) self._mapSecMusicTbl.writeToRom(rom) updateProgress(25.0/4) self._mapSecMiscTbl.writeToRom(rom) updateProgress(25.0/4) self._mapSecTownMapTbl.writeToRom(rom) updateProgress(25.0/4) def writeToProject(self, resourceOpener): # Write map tiles with resourceOpener("map_tiles", "map") as f: for row in self._tiles: f.write(hex(row[0])[2:].zfill(3)) for tile in row[1:]: f.write(" ") f.write(hex(tile)[2:].zfill(3)) f.write("\n") updateProgress(25.0) # Write sector data out = dict() for i in range(self._mapSecTsetPalsTbl.height()): self.teleport.setVal(self._mapSecMiscTbl[i,0].val() >> 7) self.townmap.setVal((self._mapSecMiscTbl[i,0].val() >> 3) & 7) self.setting.setVal(self._mapSecMiscTbl[i,0].val() & 3) self.townmap_image.setVal(self._mapSecTownMapTbl[i,0].val() & 0xf) self.townmap_arrow.setVal(self._mapSecTownMapTbl[i,0].val() >> 4) out[i] = { "Tileset": self._mapSecTsetPalsTbl[i,0].val() >> 3, "Palette": self._mapSecTsetPalsTbl[i,0].val() & 7, "Music": self._mapSecMusicTbl[i,0].dump(), "Teleport": self.teleport.dump(), "Town Map": self.townmap.dump(), "Setting": self.setting.dump(), "Item": self._mapSecMiscTbl[i,1].dump(), "Town Map Image": self.townmap_image.dump(), "Town Map Arrow": self.townmap_arrow.dump(), "Town Map X": self._mapSecTownMapTbl[i,1].dump(), "Town Map Y": self._mapSecTownMapTbl[i,2].dump() } updateProgress(12.5) with resourceOpener("map_sectors", "yml") as f: yaml.dump(out, f, Dumper=yaml.CSafeDumper, default_flow_style=False) updateProgress(12.5) def readFromProject(self, resourceOpener): # Read map data with resourceOpener("map_tiles", "map") as f: self._tiles = map(lambda y: map(lambda x: int(x, 16), y.split(" ")), f.readlines()) updateProgress(25) # Read sector data self._mapSecTsetPalsTbl.clear(2560) self._mapSecMusicTbl.clear(2560) self._mapSecMiscTbl.clear(2560) self._mapSecTownMapTbl.clear(2560) pct = (25.0/2560) with resourceOpener("map_sectors", "yml") as f: input = yaml.load(f, Loader=yaml.CSafeLoader) for i in input: entry = input[i] self._mapSecTsetPalsTbl[i,0].setVal( (entry["Tileset"] << 3) | entry["Palette"]) self._mapSecMusicTbl[i,0].load(entry["Music"]) self._mapSecMiscTbl[i,1].load(entry["Item"]) self.teleport.load(entry["Teleport"]) self.townmap.load(entry["Town Map"]) self.setting.load(entry["Setting"]) self._mapSecMiscTbl[i,0].setVal((self.teleport.val() << 7) | (self.townmap.val() << 3) | self.setting.val()) self.townmap_image.load(entry["Town Map Image"]) self.townmap_arrow.load(entry["Town Map Arrow"]) self._mapSecTownMapTbl[i,0].setVal( (self.townmap_arrow.val() << 4) | (self.townmap_image.val() & 0xf)) self._mapSecTownMapTbl[i,1].load(entry["Town Map X"]) self._mapSecTownMapTbl[i,2].load(entry["Town Map Y"]) updateProgress(pct) def upgradeProject(self, oldVersion, newVersion, rom, resourceOpenerR, resourceOpenerW): global updateProgress def replaceField(fname, oldField, newField, valueMap): if newField == None: newField = oldField valueMap = dict((k, v) for k,v in valueMap.iteritems()) with resourceOpenerR(fname, 'yml') as f: data = yaml.load(f, Loader=yaml.CSafeLoader) for i in data: if data[i][oldField] in valueMap: data[i][newField] = valueMap[data[i][oldField]].lower() else: data[i][newField] = data[i][oldField] if newField != oldField: del data[i][oldField] with resourceOpenerW(fname, 'yml') as f: yaml.dump(data, f, Dumper=yaml.CSafeDumper, default_flow_style=False) if oldVersion == newVersion: updateProgress(100) return elif oldVersion <= 2: replaceField("map_sectors", "Town Map", None, { "scummers": "summers" }) # Need to add the Town Map Image/Arrow/X/Y fields tmp = updateProgress updateProgress = lambda x: None self.readFromRom(rom) updateProgress = tmp with resourceOpenerR("map_sectors", 'yml') as f: data = yaml.load(f, Loader=yaml.CSafeLoader) for i in data: self.townmap_image.setVal(self._mapSecTownMapTbl[i,0].val() & 0xf) self.townmap_arrow.setVal(self._mapSecTownMapTbl[i,0].val() >> 4) data[i]["Town Map Image"] = self.townmap_image.dump() data[i]["Town Map Arrow"] = self.townmap_arrow.dump() data[i]["Town Map X"] = self._mapSecTownMapTbl[i,1].dump() data[i]["Town Map Y"] = self._mapSecTownMapTbl[i,2].dump() with resourceOpenerW("map_sectors", 'yml') as f: yaml.dump(data, f, Dumper=yaml.CSafeDumper, default_flow_style=False) self.upgradeProject(3, newVersion, rom, resourceOpenerR, resourceOpenerW) else: self.upgradeProject(oldVersion+1, newVersion, rom, resourceOpenerR, resourceOpenerW)
class MapSpriteModule(EbModule.EbModule): _name = "Map Sprites" _PTR_LOC = 0x2261 def __init__(self): self._ptrTbl = EbTable("SPRITE_PLACEMENT_PTR_TABLE") self._entries = [ ] def readFromRom(self, rom): ptr = EbModule.toRegAddr(rom.readMulti(self._PTR_LOC, 3)) updateProgress(5) self._ptrTbl.readFromRom(rom, ptr) pct = 45.0/(40*32) for i in range(self._ptrTbl.height()): loc = self._ptrTbl[i,0].val() # Format: AA AA [BB BB YY XX] # AA = # of entries. BB = TPT. YY = y pos. XX = x pos. if loc != 0: loc |= 0x0F0000 entry = [ ] size = rom.readMulti(loc, 2) loc += 2 for i in range(size): entry.append(SpritePlacement( rom.readMulti(loc, 2), rom[loc+3], rom[loc+2])) loc += 4 self._entries.append(entry) else: self._entries.append(None) updateProgress(pct) def writeToProject(self, resourceOpener): out = dict() x = y = 0 rowOut = dict() pct = 45.0/(40*32) for entry in self._entries: if entry != None: rowOut[x%32] = map( lambda sp: { "NPC ID": sp.npcID, "X": sp.x, "Y": sp.y }, entry) else: rowOut[x%32] = None if (x % 32) == 31: # Start next row out[y] = rowOut x = 0 y += 1 rowOut = dict() else: x += 1 updateProgress(pct) with resourceOpener("map_sprites", "yml") as f: yaml.dump(out, f, Dumper=yaml.CSafeDumper) updateProgress(5) def readFromProject(self, resourceOpener): self._entries = [] pct = 45.0/(40*32) with resourceOpener("map_sprites", "yml") as f: input = yaml.load(f, Loader=yaml.CSafeLoader) updateProgress(5) for y in input: row = input[y] for x in row: if row[x] == None: self._entries.append(None) else: self._entries.append(map(lambda x: SpritePlacement( x["NPC ID"], x["X"], x["Y"]), row[x])) updateProgress(pct) def writeToRom(self, rom): self._ptrTbl.clear(32*40) writeLoc = 0xf61e7 writeRangeEnd = 0xf8984 i = 0 pct = 45.0/(40*32) for entry in self._entries: if (entry == None) or (not entry): self._ptrTbl[i,0].setVal(0) else: entryLen = len(entry) with DataBlock(2 + entryLen*4) as block: block[0] = entryLen & 0xff block[1] = entryLen >> 8 j = 2 for sprite in entry: block[j] = sprite.npcID & 0xff block[j+1] = sprite.npcID >> 8 block[j+2] = sprite.y block[j+3] = sprite.x j += 4 if writeLoc + len(block) > writeRangeEnd: # TODO Error, not enough space raise RuntimeError("Not enough map sprite space") else: block.writeToRom(rom, writeLoc) self._ptrTbl[i,0].setVal(writeLoc & 0xffff) writeLoc += len(block) updateProgress(pct) i += 1 loc = self._ptrTbl.writeToFree(rom) rom.writeMulti(self._PTR_LOC, EbModule.toSnesAddr(loc), 3) # Mark any remaining space as free if writeLoc < writeRangeEnd: rom.addFreeRanges([(writeLoc, writeRangeEnd)]) updateProgress(5)
class EnemyModule(EbModule.EbModule): _name = "Enemies" _ASMPTR_GFX = 0x2ee0b _REGPTR_GFX = [ 0x2ebe0, 0x2f014, 0x2f065 ] _ASMPTR_PAL = 0x2ef74 def __init__(self): self._enemyCfgTable = EbTable(0xd59589) self._bsPtrTbl = EbTable(0xce62ee) self._bsPalsTable = EbTable(0xce6514) self._enemyGroupTbl = EbTable(0xD0C60D) self._enemyGroupBgTbl = EbTable(0xCBD89A) self._bsprites = [ ] self._bsPals = [ ] self._enemyGroups = [ ] def readFromRom(self, rom): self._bsPtrTbl.readFromRom(rom, EbModule.toRegAddr(EbModule.readAsmPointer(rom, self._ASMPTR_GFX))) self._bsPalsTable.readFromRom(rom, EbModule.toRegAddr(EbModule.readAsmPointer(rom, self._ASMPTR_PAL))) pct = 45.0/(self._bsPtrTbl.height() + self._bsPalsTable.height() + 1) self._enemyCfgTable.readFromRom(rom) updateProgress(pct) # Read the palettes for i in range(self._bsPalsTable.height()): pal = EbPalettes(1,16) pal.set(0, self._bsPalsTable[i,0].val()) self._bsPals.append(pal) updateProgress(pct) # Read the sprites for i in range(self._bsPtrTbl.height()): with EbCompressedData() as bsb: bsb.readFromRom(rom, EbModule.toRegAddr(self._bsPtrTbl[i,0].val())) bs = EbBattleSprite() bs.readFromBlock(bsb, self._bsPtrTbl[i,1].val()) self._bsprites.append(bs) updateProgress(pct) # Read the group data self._enemyGroupTbl.readFromRom(rom) self._enemyGroupBgTbl.readFromRom(rom) self._enemyGroups = [ ] pct = 5.0/self._enemyGroupTbl.height() for i in range(self._enemyGroupTbl.height()): group = [ ] ptr = EbModule.toRegAddr(self._enemyGroupTbl[i,0].val()) while(rom[ptr] != 0xff): group.append((rom.readMulti(ptr+1,2), rom[ptr])) ptr += 3 self._enemyGroups.append(group) updateProgress(pct) def freeRanges(self): return [(0x0d0000, 0x0dffff), # Battle Sprites (0x0e0000, 0x0e6913), # Battle Sprites Cont'd & Btl Spr. Pals (0x10d52d, 0x10dfb3)] # Enemy Group Data def writeToRom(self, rom): pct = 40.0/(len(self._bsprites) + len(self._bsPals) + 3) # Write the main table self._enemyCfgTable.writeToRom(rom) updateProgress(pct) # Write the gfx ptr table self._bsPtrTbl.clear(len(self._bsprites)) i = 0 for bs in self._bsprites: with EbCompressedData(bs.sizeBlock()) as bsb: bs.writeToBlock(bsb) self._bsPtrTbl[i,0].setVal(EbModule.toSnesAddr( bsb.writeToFree(rom))) self._bsPtrTbl[i,1].setVal(bs.size()) i += 1 updateProgress(pct) gfxAddr = EbModule.toSnesAddr(self._bsPtrTbl.writeToFree(rom)) EbModule.writeAsmPointer(rom, self._ASMPTR_GFX, gfxAddr) updateProgress(pct) for p in self._REGPTR_GFX: rom.writeMulti(p, gfxAddr, 3) # Write the pal table self._bsPalsTable.clear(len(self._bsPals)) i = 0 for p in self._bsPals: self._bsPalsTable[i,0].setVal(p.getSubpal(0)) i += 1 updateProgress(pct) EbModule.writeAsmPointer(rom, self._ASMPTR_PAL, EbModule.toSnesAddr(self._bsPalsTable.writeToFree(rom))) updateProgress(pct) # Write the groups self._enemyGroupBgTbl.writeToRom(rom) updateProgress(5) i=0 for group in self._enemyGroups: loc = rom.getFreeLoc(len(group)*3 + 1) self._enemyGroupTbl[i,0].setVal(EbModule.toSnesAddr(loc)) i += 1 for enemyID, amount in group: rom[loc] = amount rom[loc+1] = enemyID & 0xff rom[loc+2] = enemyID >> 8 loc += 3 rom[loc] = 0xff self._enemyGroupTbl.writeToRom(rom) updateProgress(5) def writeToProject(self, resourceOpener): pct = 40.0/(self._enemyCfgTable.height() + 1) # First, write the Enemy Configuration Table self._enemyCfgTable.writeToProject(resourceOpener, [4,14]) updateProgress(pct) # Next, write the battle sprite images for i in range(self._enemyCfgTable.height()): if self._enemyCfgTable[i,4].val() > 0: self._bsprites[self._enemyCfgTable[i,4].val()-1].writeToProject( resourceOpener, i, self._bsPals[self._enemyCfgTable[i,14].val()].getSubpal(0)) updateProgress(pct) # Now write the groups out = dict() i = 0 pct = 5.0/len(self._enemyGroups) for group in self._enemyGroups: entry = dict() for j in range(1,4): field = self._enemyGroupTbl[i,j] entry[field.name] = field.dump() for j in range(2): field = self._enemyGroupBgTbl[i,j] entry[field.name] = field.dump() enemyList = dict() j = 0 for enemyID, amount in group: enemyEntry = dict() enemyEntry["Enemy"] = enemyID enemyEntry["Amount"] = amount enemyList[j] = enemyEntry j += 1 entry["Enemies"] = enemyList out[i] = entry i += 1 updateProgress(pct) with resourceOpener("enemy_groups", "yml") as f: yaml.dump(out, f, Dumper=yaml.CSafeDumper) updateProgress(5) def readFromProject(self, resourceOpener): # First, read the Enemy Configuration Table self._enemyCfgTable.readFromProject(resourceOpener) pct = 40.0/(self._enemyCfgTable.height()) # Second, read the Battle Sprites bsHashes = dict() bsNextNum = 1 palNextNum = 0 for i in range(self._enemyCfgTable.height()): bs = EbBattleSprite() pal = EbPalettes(1,16) try: bs.readFromProject(resourceOpener, i, pal) # Add the battle sprite try: #self._enemyCfgTable[i,4].set(self._bsprites.index(bs)) bsNum = bsHashes[bs._sprite._spriteHash] self._enemyCfgTable[i,4].setVal(bsNum) except KeyError: self._bsprites.append(bs) self._enemyCfgTable[i,4].setVal(bsNextNum) bsHashes[bs._sprite._spriteHash] = bsNextNum bsNextNum += 1 # Add the palette # TODO should probably use hash table here too? # then again, I don't think it's actually a bottleneck try: self._enemyCfgTable[i,14].setVal(self._bsPals.index(pal)) except ValueError: self._bsPals.append(pal) self._enemyCfgTable[i,14].setVal(palNextNum) palNextNum += 1 except IOError: # No battle sprite PNG self._enemyCfgTable[i,4].setVal(0) self._enemyCfgTable[i,14].setVal(0) updateProgress(pct) # Third, read the groups self._enemyGroupTbl.readFromProject(resourceOpener, "enemy_groups") updateProgress(2) self._enemyGroupBgTbl.readFromProject(resourceOpener, "enemy_groups") updateProgress(2) self._enemyGroups = [ ] pct = 4.0/484 with resourceOpener("enemy_groups", "yml") as f: input = yaml.load(f, Loader=yaml.CSafeLoader) updateProgress(2) for group in input: tmp1 = input[group]["Enemies"] enemyList = [ ] i = 0 for enemy in tmp1: tmp2 = tmp1[i] enemyList.append((tmp2["Enemy"], tmp2["Amount"])) i += 1 self._enemyGroups.append(enemyList) updateProgress(pct)
class BattleBgModule(EbModule.EbModule): _name = "Battle Backgrounds" _ASMPTRS_GFX = [0x2d1ba, 0x2d4dc, 0x2d8c3, 0x4a3ba] _ASMPTRS_ARR = [0x2d2c1, 0x2d537, 0x2d91f, 0x4a416] _ASMPTRS_PAL = [0x2d3bb, 0x2d61b, 0x2d7e8, 0x2d9e8, 0x4a4d0] def __init__(self): self._bbgGfxPtrTbl = EbTable("BATTLEBG_GFX_POINTERS") self._bbgArrPtrTbl = EbTable("BATTLEBG_ARR_POINTERS") self._bbgPalPtrTbl = EbTable("BATTLEBG_PALETTE_POINTERS") self._bbgScrollTbl = EbTable("BG_SCROLLING_TABLE") self._bbgDistorTbl = EbTable("BG_DISTORTION_TABLE") self._bbgTbl = EbTable("BG_DATA_TABLE") def free(self): del(self._bbgGfxPtrTbl) del(self._bbgArrPtrTbl) del(self._bbgPalPtrTbl) del(self._bbgTbl) del(self._bbgGfxArrs) del(self._bbgPals) def readFromRom(self, rom): self._bbgTbl.readFromRom(rom) pct = 50.0/(6+self._bbgTbl.height()) self._bbgGfxPtrTbl.readFromRom(rom, EbModule.toRegAddr(EbModule.readAsmPointer(rom, self._ASMPTRS_GFX[0]))) updateProgress(pct) self._bbgArrPtrTbl.readFromRom(rom, EbModule.toRegAddr(EbModule.readAsmPointer(rom, self._ASMPTRS_ARR[0]))) updateProgress(pct) self._bbgPalPtrTbl.readFromRom(rom, EbModule.toRegAddr(EbModule.readAsmPointer(rom, self._ASMPTRS_PAL[0]))) updateProgress(pct) self._bbgGfxArrs = [ None for i in range(self._bbgGfxPtrTbl.height()) ] self._bbgPals = [ None for i in range(self._bbgPalPtrTbl.height()) ] updateProgress(pct) self._bbgScrollTbl.readFromRom(rom) updateProgress(pct) self._bbgDistorTbl.readFromRom(rom) updateProgress(pct) for i in range(self._bbgTbl.height()): gfxNum = self._bbgTbl[i,0].val() colorDepth = self._bbgTbl[i,2].val() if (self._bbgGfxArrs[gfxNum] == None): # Max size used in rom: 421 (2bpp) 442 (4bpp) tg = EbTileGraphics(512, 8, colorDepth) with EbCompressedData() as tgb: tgb.readFromRom(rom, EbModule.toRegAddr( self._bbgGfxPtrTbl[gfxNum,0].val())) tg.readFromBlock(tgb) a = EbArrangement(32, 32) with EbCompressedData() as ab: ab.readFromRom(rom, EbModule.toRegAddr( self._bbgArrPtrTbl[gfxNum,0].val())) a.readFromBlock(ab) self._bbgGfxArrs[gfxNum] = (tg, a) palNum = self._bbgTbl[i,1].val() if (self._bbgPals[palNum] == None): with DataBlock(32) as pb: pb.readFromRom(rom, EbModule.toRegAddr(self._bbgPalPtrTbl[palNum,0].val())) p = EbPalettes(1, 16) p.readFromBlock(pb) self._bbgPals[palNum] = p updateProgress(pct) def writeToProject(self, resourceOpener): pct = 50.0/(3+self._bbgTbl.height()) self._bbgTbl.writeToProject(resourceOpener, hiddenColumns=[0,1]) updateProgress(pct) self._bbgScrollTbl.writeToProject(resourceOpener) updateProgress(pct) self._bbgDistorTbl.writeToProject(resourceOpener) updateProgress(pct) # Export BGs by table entry for i in range(self._bbgTbl.height()): (tg, a) = self._bbgGfxArrs[self._bbgTbl[i,0].val()] pal = self._bbgTbl[i,1].val() img = a.toImage(tg, self._bbgPals[pal]) imgFile = resourceOpener('BattleBGs/' + str(i).zfill(3), 'png') img.save(imgFile, 'png') imgFile.close() del(img) updateProgress(pct) def readFromProject(self, resourceOpener): self._bbgTbl.readFromProject(resourceOpener) pct = 50.0/(2+self._bbgTbl.height()) self._bbgScrollTbl.readFromProject(resourceOpener) updateProgress(pct) self._bbgDistorTbl.readFromProject(resourceOpener) updateProgress(pct) self._bbgGfxArrs = [] self._bbgPals = [] for i in range(self._bbgTbl.height()): img = Image.open( resourceOpener('BattleBGs/' + str(i).zfill(3), 'png')) np = EbPalettes(1, 16) colorDepth = self._bbgTbl[i,2].val() # Max size used in rom: 421 (2bpp) 442 (4bpp) ntg = EbTileGraphics(512, 8, colorDepth) na = EbArrangement(32, 32) na.readFromImage(img, np, ntg) j=0 for (tg, a) in self._bbgGfxArrs: if (tg == ntg) and (a == na): self._bbgTbl[i,0].setVal(j) break j += 1 else: self._bbgGfxArrs.append((ntg, na)) self._bbgTbl[i,0].setVal(j) j=0 for p in self._bbgPals: if (p == np): self._bbgTbl[i,1].setVal(j) break j += 1 else: self._bbgPals.append((np)) self._bbgTbl[i,1].setVal(j) updateProgress(pct) def freeRanges(self): return [(0xa0000,0xadca0), (0xb0000, 0xbd899)] def writeToRom(self, rom): self._bbgGfxPtrTbl.clear(len(self._bbgGfxArrs)) self._bbgArrPtrTbl.clear(len(self._bbgGfxArrs)) self._bbgPalPtrTbl.clear(len(self._bbgPals)) # Write gfx+arrs i = 0 pct = (50.0/3)/len(self._bbgGfxArrs) for (tg, a) in self._bbgGfxArrs: with EbCompressedData(tg.sizeBlock()) as tgb: tg.writeToBlock(tgb) self._bbgGfxPtrTbl[i,0].setVal(EbModule.toSnesAddr( tgb.writeToFree(rom))) with EbCompressedData(a.sizeBlock()) as ab: a.writeToBlock(ab) self._bbgArrPtrTbl[i,0].setVal(EbModule.toSnesAddr( ab.writeToFree(rom))) i += 1 updateProgress(pct) EbModule.writeAsmPointers(rom, self._ASMPTRS_GFX, EbModule.toSnesAddr(self._bbgGfxPtrTbl.writeToFree(rom))) EbModule.writeAsmPointers(rom, self._ASMPTRS_ARR, EbModule.toSnesAddr(self._bbgArrPtrTbl.writeToFree(rom))) # Write pals i = 0 pct = (50.0/3)/len(self._bbgPals) for p in self._bbgPals: with DataBlock(32) as pb: p.writeToBlock(pb) self._bbgPalPtrTbl[i,0].setVal(EbModule.toSnesAddr( pb.writeToFree(rom))) i += 1 updateProgress(pct) EbModule.writeAsmPointers(rom, self._ASMPTRS_PAL, EbModule.toSnesAddr(self._bbgPalPtrTbl.writeToFree(rom))) # Write the data table pct = (50.0/3)/3 self._bbgTbl.writeToRom(rom) updateProgress(pct) self._bbgScrollTbl.writeToRom(rom) updateProgress(pct) self._bbgDistorTbl.writeToRom(rom) updateProgress(pct)
class TilesetModule(EbModule.EbModule): _name = "Tilesets" def __init__(self): self._gfxPtrTbl = EbTable("MAP_DATA_TILESET_PTR_TABLE") self._arrPtrTbl = EbTable("MAP_DATA_TILE_ARRANGEMENT_PTR_TABLE") self._colPtrTbl = EbTable("MAP_DATA_TILE_COLLISION_PTR_TABLE") self._mapTsetTbl = EbTable("TILESET_TABLE") self._palPtrTbl = EbTable("MAP_PALETTE_PTR_TABLE") self._tsets = [ Tileset() for i in range(20) ] def freeRanges(self): return [(0x17c600, 0x17fbe7), (0x190000, 0x19fc17), (0x1b0000, 0x1bf2ea), (0x1c0000, 0x1cd636), (0x1d0000, 0x1dfecd), (0x1e0000, 0x1ef0e6), (0x1f0000, 0x1fc242)] def readFromRom(self, rom): self._gfxPtrTbl.readFromRom(rom) updateProgress(2) self._arrPtrTbl.readFromRom(rom) updateProgress(2) self._colPtrTbl.readFromRom(rom) updateProgress(2) self._mapTsetTbl.readFromRom(rom) updateProgress(2) self._palPtrTbl.readFromRom(rom) updateProgress(2) # Read tilesets pct = 30.0/len(self._tsets) i=0 for tset in self._tsets: # Read data tset.readMinitilesFromRom(rom, EbModule.toRegAddr(self._gfxPtrTbl[i,0].val())) tset.readArrangementsFromRom(rom, EbModule.toRegAddr(self._arrPtrTbl[i,0].val())) tset.readCollisionsFromRom(rom, EbModule.toRegAddr(self._colPtrTbl[i,0].val())) i += 1 updateProgress(pct) # Read palettes pct = 10.0/self._mapTsetTbl.height() for i in range(self._mapTsetTbl.height()): drawTset = self._mapTsetTbl[i,0].val() # Each map tset has 8 maximum palettes # We'll just assume they all use 8 and read the garbage #romLoc = self._palPtrTbl[i,0].val() #for j in xrange(8): # # Read the palette # self._tsets[drawTset].readPaletteFromRom(rom, i, j, # EbModule.toRegAddr(romLoc)) # romLoc += 0xc0 # OK, as it turns out, all palettes need to be in the 1A bank # So we actually need to conserve space and not read garbage # Estimate the number of palettes for this map tileset if i == 31: #k = 0xDAFAA7 - self._palPtrTbl[i,0].val() k = 7 else: k = self._palPtrTbl[i+1,0].val() - self._palPtrTbl[i,0].val() k /= 0xc0 # Add the palettes romLoc = EbModule.toRegAddr(self._palPtrTbl[i,0].val()) for j in range(k): # Read the palette self._tsets[drawTset].readPaletteFromRom(rom, i, j, romLoc) romLoc += 0xc0 updateProgress(pct) def writeToRom(self, rom): numTsets = len(self._tsets) self._gfxPtrTbl.clear(numTsets) self._arrPtrTbl.clear(numTsets) self._colPtrTbl.clear(numTsets) self._mapTsetTbl.clear(32) self._palPtrTbl.clear(32) # Write gfx & arrs pct = 30.0/numTsets i=0 for tset in self._tsets: self._gfxPtrTbl[i,0].setVal(EbModule.toSnesAddr( tset.writeMinitilesToFree(rom))) self._arrPtrTbl[i,0].setVal(EbModule.toSnesAddr( tset.writeArrangementsToFree(rom))) i += 1 updateProgress(pct) self._gfxPtrTbl.writeToRom(rom) updateProgress(2) self._arrPtrTbl.writeToRom(rom) updateProgress(2) # Write collissions pct = 6.0/numTsets colLocs = dict() colWriteLoc = 0x180000 colRangeEnd = 0x18f05d i=0 for tset in self._tsets: with DataBlock(len(tset.col)*2) as colTable: j=0 for c in tset.col: hash = crc32(c) try: addr = colLocs[hash] except KeyError: if (colWriteLoc + 16) > colRangeEnd: # TODO Error, not enough space for collisions print "Ran out of collision space" raise Exception addr = 0 else: colLocs[hash] = colWriteLoc addr = colWriteLoc rom.write(colWriteLoc, c) colWriteLoc += 16 colTable[j] = addr & 0xff colTable[j+1] = (addr >> 8) & 0xff j += 2 self._colPtrTbl[i,0].setVal(EbModule.toSnesAddr( colTable.writeToFree(rom))) i += 1 updateProgress(pct) self._colPtrTbl.writeToRom(rom) updateProgress(1) # Write the palettes, they need to be in the DA bank pct = 7.0/32 palWriteLoc = 0x1a0000 palRangeEnd = 0x1afaa6 # can we go more? # Write maps/drawing tilesets associations and map tset pals for i in range(32): # For each map tileset # Find the drawing tileset number for this map tileset drawTset = -1 j = 0 for tset in self._tsets: for (mt,mp,pal) in tset.pals: if mt == i: drawTset = j break if drawTset != -1: break j += 1 else: # TODO Error, this drawing tileset isn't associated drawTset = 0 self._mapTsetTbl[i,0].setVal(drawTset) # Write the palette data for this map tileset mtset_pals = [(mp,pal) for (mt,mp,pal) in self._tsets[drawTset].pals if mt == i] mtset_pals.sort() # Let's take the easy way out and just write redundant flag pals # This will waste space but oh well # First, write the flag pals for (mp,pal) in mtset_pals: if pal.flag != 0: if palWriteLoc + 0xc0 > palRangeEnd: # TODO Error, not enough space for all these palettes raise RuntimeError("Too many palettes") pal.flagPal.writeToBlock(rom, palWriteLoc) pal.flagPalPtr = palWriteLoc & 0xffff palWriteLoc += 0xc0 self._palPtrTbl[i,0].setVal(EbModule.toSnesAddr(palWriteLoc)) # Now write the regular pals for (mp,pal) in mtset_pals: if palWriteLoc + 0xc0 > palRangeEnd: # TODO Error, not enough space for all these palettes raise RuntimeException("Too many palettes") pal.writeToBlock(rom, palWriteLoc) palWriteLoc += 0xc0 updateProgress(pct) self._mapTsetTbl.writeToRom(rom) updateProgress(1) self._palPtrTbl.writeToRom(rom) updateProgress(1) # Might as well use any extra leftover space ranges = [(colWriteLoc, colRangeEnd), (palWriteLoc, palRangeEnd)] ranges = [(a,b) for (a,b) in ranges if a < b] rom.addFreeRanges(ranges) def writeToProject(self, resourceOpener): # Dump an additional YML with color0 data out = dict() for i in range(0,32): # For each map tset entry = dict() tset = None for ts in self._tsets: if ts.hasMapTileset(i): tset = ts break for (pN,p) in [(mp,p) for (mt,mp,p) in tset.pals if mt == i]: entry[pN] = p.dump() out[i] = entry with resourceOpener('map_palette_settings', 'yml') as f: s = yaml.dump(out, default_flow_style=False, Dumper=yaml.CSafeDumper) s = sub("Event Flag: (\d+)", lambda i: "Event Flag: " + hex(int(i.group(0)[12:])), s) f.write(s) updateProgress(5) # Dump the FTS files pct=45.0/len(self._tsets) i=0 for tset in self._tsets: with resourceOpener('Tilesets/' + str(i).zfill(2), 'fts') as f: tset.writeToFTS(f) i += 1 updateProgress(pct) def readFromProject(self, resourceOpener): i=0 pct = 45.0/len(self._tsets) for tset in self._tsets: with resourceOpener('Tilesets/' + str(i).zfill(2), 'fts') as f: tset.readFromFTS(f) i += 1 updateProgress(pct) with resourceOpener('map_palette_settings', 'yml') as f: input = yaml.load(f, Loader=yaml.CSafeLoader) for mtset in input: # For each map tileset # Get the draw (normal) tileset tset = None for ts in self._tsets: if ts.hasMapTileset(mtset): tset = ts break # For each map palette mtset_pals = [(mp,p) for (mt,mp,p) in tset.pals if mt == mtset] for (pN,mtset_pal) in mtset_pals: entry = input[mtset][pN] mtset_pal.flag = entry["Event Flag"] mtset_pal.flashEffect = entry["Flash Effect"] mtset_pal.spritePalNum = entry["Sprite Palette"] if mtset_pal.flag != 0: mtset_pal.flagPal = MapPalette() mtset_pal.flagPal.setFromString(entry["Event Palette"]) mtset_pal.flagPal.spritePalNum = entry["Sprite Palette"] updateProgress(5.0/32)
class TownMapIconModule(EbModule.EbModule): _name = "Town Map Icon Positions" _ASMPTR_PTR_TBL = 0x4d464 def __init__(self): self._ptrTbl = EbTable(0xE1F491) self._entries = [ ] self._entryIdField = ValuedIntTableEntry(None, None, ["Onett", "Twoson", "Threed", "Fourside", "Scaraba", "Summers"]) self._iconField = ValuedIntTableEntry(None, None, ["0", "Hamburger Shop", "Bakery", "Hotel", "Restaurant", "Hospital", "Shop", "Dept Store", "Bus Stop", "South to Twoson", "North to Onett", "South to Threed", "West to Twoson", "East to Desert", "West to Desert", "East to Toto", "Hint", "Ness", "Small Ness", "North", "South", "West", "East" ]) def freeRanges(self): return [(0x21f491, 0x21f580)] # Pointer Table and Data def readFromRom(self, rom): self._ptrTbl.readFromRom(rom, EbModule.toRegAddr(EbModule.readAsmPointer(rom, self._ASMPTR_PTR_TBL))) updateProgress(5) for i in range(self._ptrTbl.height()): loc = EbModule.toRegAddr(self._ptrTbl[i,0].val()) entry = [] while True: x = rom[loc] if x == 0xff: break y = rom[loc+1] icon = rom[loc+2] flag = rom.readMulti(loc+3, 2) entry.append((x, y, icon, flag)) loc += 5 self._entries.append(entry) i += 1 updateProgress(45) def writeToRom(self, rom): self._ptrTbl.clear(6) i = 0 for entry in self._entries: writeLoc = rom.getFreeLoc(len(entry)*5 + 1) self._ptrTbl[i,0].setVal( EbModule.toSnesAddr(writeLoc)) for (x, y, icon, flag) in entry: rom[writeLoc] = x rom[writeLoc+1] = y rom[writeLoc+2] = icon rom.writeMulti(writeLoc+3, flag, 2) writeLoc += 5 rom[writeLoc] = 0xff i += 1 updateProgress(45) EbModule.writeAsmPointer(rom, self._ASMPTR_PTR_TBL, EbModule.toSnesAddr( self._ptrTbl.writeToFree(rom))) updateProgress(5) def readFromProject(self, resourceOpener): self._entries = [None] * 6 with resourceOpener("TownMaps/icon_positions", "yml") as f: data = yaml.load(f, Loader=yaml.CSafeLoader) for name in data: entry = [] for subEntry in data[name]: self._iconField.load(subEntry["Icon"]) entry.append(( subEntry["X"], subEntry["Y"], self._iconField.val(), subEntry["Event Flag"])) self._entryIdField.load(name) self._entries[self._entryIdField.val()] = entry updateProgress(50) def writeToProject(self, resourceOpener): out = dict() i = 0 for entry in self._entries: outEntry = [] for (x, y, icon, flag) in entry: self._iconField.setVal(icon) outEntry.append({ "X": x, "Y": y, "Icon": self._iconField.dump(), "Event Flag": flag }) self._entryIdField.setVal(i) out[self._entryIdField.dump()] = outEntry i += 1 updateProgress(25) with resourceOpener("TownMaps/icon_positions", "yml") as f: s = yaml.dump(out, default_flow_style=False, Dumper=yaml.CSafeDumper) s = sub("Event Flag: (\d+)", lambda i: "Event Flag: " + hex(int(i.group(0)[12:])), s) f.write(s) updateProgress(25) def upgradeProject(self, oldVersion, newVersion, rom, resourceOpenerR, resourceOpenerW): global updateProgress if oldVersion == newVersion: updateProgress(100) return elif oldVersion <= 2: tmp = updateProgress updateProgress = lambda x: None self.readFromRom(rom) self.writeToProject(resourceOpenerW) updateProgress = tmp self.upgradeProject(3, newVersion, rom, resourceOpenerR, resourceOpenerW) else: self.upgradeProject(oldVersion+1, newVersion, rom, resourceOpenerR, resourceOpenerW)
class MapEnemyModule(EbModule.EbModule): _name = "Map Enemies" def __init__(self): self._mapGroupPtrTbl = EbTable("ENEMY_PLACEMENT_GROUPS_PTR_TABLE") self._mapEnemyTbl = EbTable("ENEMY_PLACEMENT_DATA") def freeRanges(self): return [(0x10BBAC, 0x10C6AC)] # Groups data def readFromRom(self, rom): self._mapEnemyTbl.readFromRom(rom) updateProgress(2.5) self._mapGroupPtrTbl.readFromRom(rom) updateProgress(2.5) # Read the groups pct = 45.0 / (self._mapGroupPtrTbl.height()) self._mapGroups = [] for i in range(self._mapGroupPtrTbl.height()): loc = EbModule.toRegAddr(self._mapGroupPtrTbl[i, 0].val()) flag = rom.readMulti(loc, 2) rate1 = rom[loc + 2] rate2 = rom[loc + 3] loc += 4 # Read the enemies/probabilities group1 = [] if rate1 > 0: sum = 0 while sum < 8: prob = rom[loc] enemy = rom.readMulti(loc + 1, 2) sum += prob loc += 3 group1.append((prob, enemy)) group2 = [] if rate2 > 0: sum = 0 while sum < 8: prob = rom[loc] enemy = rom.readMulti(loc + 1, 2) sum += prob loc += 3 group2.append((prob, enemy)) # Add to the list self._mapGroups.append((flag, rate1, rate2, group1, group2)) updateProgress(pct) def writeToRom(self, rom): self._mapEnemyTbl.writeToRom(rom) updateProgress(2.5) self._mapGroupPtrTbl.clear(len(self._mapGroups)) updateProgress(2.5) pct = 42.5 / len(self._mapGroups) i = 0 for (flag, rate1, rate2, subg1, subg2) in self._mapGroups: size = 4 if rate1 > 0: size += len(subg1) * 3 if rate2 > 0: size += len(subg2) * 3 loc = rom.getFreeLoc(size) self._mapGroupPtrTbl[i, 0].setVal(EbModule.toSnesAddr(loc)) rom.writeMulti(loc, flag, 2) rom[loc + 2] = rate1 rom[loc + 3] = rate2 loc += 4 for prob, egroup in subg1: rom[loc] = prob rom.writeMulti(loc + 1, egroup, 2) loc += 3 for prob, egroup in subg2: rom[loc] = prob rom.writeMulti(loc + 1, egroup, 2) loc += 3 i += 1 updateProgress(pct) self._mapGroupPtrTbl.writeToRom(rom) updateProgress(2.5) def writeToProject(self, resourceOpener): self._mapEnemyTbl.writeToProject(resourceOpener) updateProgress(2.5) # Write the groups pct = 42.5 / len(self._mapGroups) out = dict() i = 0 for (flag, rate1, rate2, group1, group2) in self._mapGroups: # Generate first enemy/prob list g1out = dict() j = 0 for prob, enemy in group1: g1out[j] = {"Enemy Group": enemy, "Probability": prob} j += 1 g2out = dict() j = 0 for prob, enemy in group2: g2out[j] = {"Enemy Group": enemy, "Probability": prob} j += 1 out[i] = { "Event Flag": flag, "Sub-Group 1 Rate": rate1, "Sub-Group 1": g1out, "Sub-Group 2 Rate": rate2, "Sub-Group 2": g2out, } i += 1 updateProgress(pct) s = yaml.dump(out, Dumper=yaml.CSafeDumper) updateProgress(2.5) s = sub("Event Flag: (\d+)", lambda i: "Event Flag: " + hex(int(i.group(0)[12:])), s) with resourceOpener("map_enemy_groups", "yml") as f: f.write(s) updateProgress(2.5) def readFromProject(self, resourceOpener): self._mapEnemyTbl.readFromProject(resourceOpener) updateProgress(5) pct = 40.0 / 203 self._mapGroups = [] with resourceOpener("map_enemy_groups", "yml") as f: input = yaml.load(f, Loader=yaml.CSafeLoader) updateProgress(5) for gid in input: group = input[gid] flag = group["Event Flag"] rate1 = group["Sub-Group 1 Rate"] rate2 = group["Sub-Group 2 Rate"] subg1 = [] if rate1 > 0: for eid in group["Sub-Group 1"]: entry = group["Sub-Group 1"][eid] subg1.append((entry["Probability"], entry["Enemy Group"])) subg2 = [] if rate2 > 0: for eid in group["Sub-Group 2"]: entry = group["Sub-Group 2"][eid] subg2.append((entry["Probability"], entry["Enemy Group"])) self._mapGroups.append((flag, rate1, rate2, subg1, subg2)) updateProgress(pct)
class MapModule(EbModule.EbModule): _name = "Map" _MAP_PTRS_PTR_ADDR = 0xa1db _LOCAL_TSET_ADDR = 0x175000 _MAP_HEIGHT = 320 _MAP_WIDTH = 256 def __init__(self): self._tiles = [] self._mapSecTsetPalsTbl = EbTable("GLOBAL_MAP_TILESETPALETTE_DATA") self._mapSecMusicTbl = EbTable("MAP_DATA_PER-SECTOR_MUSIC") self._mapSecMiscTbl = EbTable("MAP_DATA_PER-SECTOR_ATTRIBUTES_TABLE") self.teleport = ValuedIntTableEntry(None, None, ["Enabled", "Disabled"]) self.townmap = ValuedIntTableEntry(None, None, ["None", "Onett", "Twoson", "Threed", "Fourside", "Scaraba", "Scummers", "None 2"]) self.setting = ValuedIntTableEntry(None, None, ["None", "Indoors", "Exit Mouse usable", "Lost Underworld sprites", "Magicant sprites", "Robot sprites", "Butterflies", "Indoors and Butterflies"]) def readFromRom(self, rom): # Read map tiles map_ptrs_addr = \ EbModule.toRegAddr(rom.readMulti(self._MAP_PTRS_PTR_ADDR, 3)) map_addrs = map(lambda x: \ EbModule.toRegAddr(rom.readMulti(map_ptrs_addr+x*4,4)), \ range(8)) self._tiles = map( lambda y: rom.readList(map_addrs[y%8] + ((y>>3)<<8), self._MAP_WIDTH).tolist(), range(self._MAP_HEIGHT)) k = self._LOCAL_TSET_ADDR for i in range(self._MAP_HEIGHT>>3): for j in range(self._MAP_WIDTH): self._tiles[i<<3][j] |= (rom[k] & 3) << 8 self._tiles[(i<<3)|1][j] |= ((rom[k] >> 2) & 3) << 8 self._tiles[(i<<3)|2][j] |= ((rom[k] >> 4) & 3) << 8 self._tiles[(i<<3)|3][j] |= ((rom[k] >> 6) & 3) << 8 self._tiles[(i<<3)|4][j] |= (rom[k+0x3000] & 3) << 8 self._tiles[(i<<3)|5][j] |= ((rom[k+0x3000] >> 2) & 3) << 8 self._tiles[(i<<3)|6][j] |= ((rom[k+0x3000] >> 4) & 3) << 8 self._tiles[(i<<3)|7][j] |= ((rom[k+0x3000] >> 6) & 3) << 8 k += 1 updateProgress(25) # Read sector data self._mapSecTsetPalsTbl.readFromRom(rom) updateProgress(25.0/3) self._mapSecMusicTbl.readFromRom(rom) updateProgress(25.0/3) self._mapSecMiscTbl.readFromRom(rom) updateProgress(25.0/3) def writeToRom(self, rom): map_ptrs_addr = \ EbModule.toRegAddr(rom.readMulti(self._MAP_PTRS_PTR_ADDR, 3)) map_addrs = map(lambda x: \ EbModule.toRegAddr(rom.readMulti(map_ptrs_addr+x*4,4)), \ range(8)) for i in range(self._MAP_HEIGHT): rom.write(map_addrs[i%8] + ((i>>3)<<8), map(lambda x: x & 0xff, self._tiles[i])) k = self._LOCAL_TSET_ADDR for i in range(self._MAP_HEIGHT>>3): for j in range(self._MAP_WIDTH): c = ((self._tiles[i<<3][j] >> 8) | ((self._tiles[(i<<3)|1][j] >> 8) << 2) | ((self._tiles[(i<<3)|2][j] >> 8) << 4) | ((self._tiles[(i<<3)|3][j] >> 8) << 6)) rom.write(k, c) c = ((self._tiles[(i<<3)|4][j] >> 8) | ((self._tiles[(i<<3)|5][j] >> 8) << 2) | ((self._tiles[(i<<3)|6][j] >> 8) << 4) | ((self._tiles[(i<<3)|7][j] >> 8) << 6)) rom.write(k+0x3000, c) k += 1 updateProgress(25) # Write sector data self._mapSecTsetPalsTbl.writeToRom(rom) updateProgress(25.0/3) self._mapSecMusicTbl.writeToRom(rom) updateProgress(25.0/3) self._mapSecMiscTbl.writeToRom(rom) updateProgress(25.0/3) def writeToProject(self, resourceOpener): # Write map tiles with resourceOpener("map_tiles", "map") as f: for row in self._tiles: f.write(hex(row[0])[2:].zfill(3)) for tile in row[1:]: f.write(" ") f.write(hex(tile)[2:].zfill(3)) f.write("\n") updateProgress(25.0) # Write sector data out = dict() for i in range(self._mapSecTsetPalsTbl.height()): self.teleport.setVal(self._mapSecMiscTbl[i,0].val() >> 7) self.townmap.setVal((self._mapSecMiscTbl[i,0].val() >> 3) & 7) self.setting.setVal(self._mapSecMiscTbl[i,0].val() & 3) out[i] = { "Tileset": self._mapSecTsetPalsTbl[i,0].val() >> 3, "Palette": self._mapSecTsetPalsTbl[i,0].val() & 7, "Music": self._mapSecMusicTbl[i,0].dump(), "Teleport": self.teleport.dump(), "Town Map": self.townmap.dump(), "Setting": self.setting.dump(), "Item": self._mapSecMiscTbl[i,1].dump() } updateProgress(12.5) with resourceOpener("map_sectors", "yml") as f: yaml.dump(out, f, Dumper=yaml.CSafeDumper, default_flow_style=False) updateProgress(12.5) def readFromProject(self, resourceOpener): # Read map data with resourceOpener("map_tiles", "map") as f: self._tiles = map(lambda y: map(lambda x: int(x, 16), y.split(" ")), f.readlines()) updateProgress(25) # Read sector data self._mapSecTsetPalsTbl.clear(2560) self._mapSecMusicTbl.clear(2560) self._mapSecMiscTbl.clear(2560) pct = (25.0/2560) with resourceOpener("map_sectors", "yml") as f: input = yaml.load(f, Loader=yaml.CSafeLoader) for i in input: entry = input[i] self._mapSecTsetPalsTbl[i,0].setVal( (entry["Tileset"] << 3) | entry["Palette"]) self._mapSecMusicTbl[i,0].load(entry["Music"]) self._mapSecMiscTbl[i,1].load(entry["Item"]) self.teleport.load(entry["Teleport"]) self.townmap.load(entry["Town Map"]) self.setting.load(entry["Setting"]) self._mapSecMiscTbl[i,0].setVal((self.teleport.val() << 7) | (self.townmap.val() << 3) | self.setting.val()) updateProgress(pct)
class MapMusicModule(EbModule.EbModule): _ASMPTR = 0x6939 _name = "Map Music" def __init__(self): self._ptrTbl = EbTable(0xCF58EF) self._entries = [] def readFromRom(self, rom): self._ptrTbl.readFromRom(rom, EbModule.toRegAddr(rom.readMulti(self._ASMPTR, 3))) updateProgress(25) for i in range(self._ptrTbl.height()): loc = 0xf0000 | self._ptrTbl[i,0].val() entry = [ ] flag = 1 while flag != 0: flag = rom.readMulti(loc, 2) music = rom[loc+2] entry.append((flag, music)) loc += 4 self._entries.append(entry) updateProgress(25) def writeToRom(self, rom): self._ptrTbl.clear(165) writeLoc = 0xf58ef writeRangeEnd = 0xf61e5 # TODO Can re-use bank space from doors i=0 for entry in self._entries: entryLen = len(entry)*4 if writeLoc+entryLen > writeRangeEnd: raise RuntimeError("Not enough room for map music") self._ptrTbl[i,0].setVal(writeLoc & 0xffff) i += 1 for (flag, music) in entry: rom.writeMulti(writeLoc, flag, 2) rom[writeLoc+2] = music rom[writeLoc+3] = 0 writeLoc += 4 updateProgress(25) rom.writeMulti(self._ASMPTR, EbModule.toSnesAddr(self._ptrTbl.writeToFree(rom)), 3) if writeLoc < writeRangeEnd: rom.addFreeRanges([(writeLoc, writeRangeEnd)]) updateProgress(25) def writeToProject(self, resourceOpener): out = dict() i = 0 for entry in self._entries: outEntry = [] for (flag, music) in entry: outEntry.append({ "Event Flag": flag, "Music": music }) out[i] = outEntry i += 1 updateProgress(25) with resourceOpener("map_music", "yml") as f: s = yaml.dump(out, default_flow_style=False, Dumper=yaml.CSafeDumper) s = sub("Event Flag: (\d+)", lambda i: "Event Flag: " + hex(int(i.group(0)[12:])), s) f.write(s) updateProgress(25) def readFromProject(self, resourceOpener): with resourceOpener("map_music", "yml") as f: input = yaml.load(f, Loader=yaml.CSafeLoader) for i in input: entry = [] for subEntry in input[i]: entry.append((subEntry["Event Flag"], subEntry["Music"])) self._entries.append(entry) updateProgress(50)
class SpriteGroupModule(EbModule.EbModule): _name = "Sprite Groups" def __init__(self): self._grPtrTbl = EbTable(0xef133f) self._grPalTbl = EbTable(0xc30000) self._groups = None def freeRanges(self): return [(0x2f1a7f, 0x2f4a3f), (0x110000, 0x11ffff), (0x120000, 0x12ffff), (0x130000, 0x13ffff), (0x140000, 0x14ffff), (0x150000, 0x154fff)] def free(self): del(self._grPtrTbl) del(self._grPalTbl) def readFromRom(self, rom): self._grPtrTbl.readFromRom(rom) updateProgress(5) self._grPalTbl.readFromRom(rom) updateProgress(5) # Load the sprite groups self._groups = [] pct = 40.0/self._grPtrTbl.height() for i in range(self._grPtrTbl.height()): # Note: this assumes that the SPT is written contiguously numSprites = 8 # Assume that the last group only has 8 sprites if i < self._grPtrTbl.height()-1: numSprites = (self._grPtrTbl[i+1,0].val() - self._grPtrTbl[i,0].val() - 9) / 2 g = SpriteGroup(numSprites) g.readFromRom(rom, EbModule.toRegAddr(self._grPtrTbl[i,0].val())) self._groups.append(g) updateProgress(pct) def writeToProject(self, resourceOpener): # Write the palettes self._grPalTbl.writeToProject(resourceOpener) updateProgress(5) out = { } i = 0 pct = 40.0/len(self._groups) for g in self._groups: out[i] = g.dump() img = g.toImage(self._grPalTbl[g.palette(),0].val()) imgFile = resourceOpener("SpriteGroups/" + str(i).zfill(3), 'png') img.save(imgFile, 'png', transparency=0) imgFile.close() del(img) i += 1 updateProgress(pct) yaml.dump(out, resourceOpener("sprite_groups", "yml"), Dumper=yaml.CSafeDumper) updateProgress(5) def readFromProject(self, resourceOpener): self._grPalTbl.readFromProject(resourceOpener) updateProgress(5) input = yaml.load(resourceOpener("sprite_groups", "yml"), Loader=yaml.CSafeLoader) numGroups = len(input) self._groups = [] pct = 45.0/numGroups for i in range(numGroups): g = SpriteGroup(16) g.load(input[i]) try: img = Image.open( resourceOpener("SpriteGroups/" + str(i).zfill(3), "png")) except IOError: print "Could not load Sprite Group #" + str(i) raise if img.mode != 'P': raise RuntimeError("SpriteGroups/" + str(i).zfill(3) + " is not an indexed PNG.") g.fromImage(img) palData = img.getpalette() del(img) self._groups.append(g) pal = [ ] # Read the palette from the image for j in range(1, 16): pal.append((palData[j*3], palData[j*3+1], palData[j*3+2])) # Assign the palette number to the sprite for j in range(8): if pal == self._grPalTbl[j,0].val()[1:]: g.setPalette(j) break else: # Error, this image uses an invalid palette for j in range(8): print j, ":", self._grPalTbl[j,0].val()[1:] raise RuntimeError("Sprite Group #" + str(i) + " uses an invalid palette: " + str(pal)) updateProgress(pct) def writeToRom(self, rom): numGroups = len(self._groups) self._grPtrTbl.clear(numGroups) with DataBlock(sum(map( lambda x: x.blockSize(), self._groups))) as block: loc = 0 i = 0 # Write all the groups to the block, and sprites to rom pct = 40.0 / numGroups for g in self._groups: g.writeSpritesToFree(rom) g.writeToBlock(block, loc) self._grPtrTbl[i,0].setVal(loc) loc += g.blockSize() i += 1 updateProgress(pct) # Write the block to rom and correct the group pointers addr = EbModule.toSnesAddr(block.writeToFree(rom)) for i in range(self._grPtrTbl.height()): self._grPtrTbl[i,0].setVal( self._grPtrTbl[i,0].val() + addr) # Write the pointer table self._grPtrTbl.writeToRom(rom) updateProgress(5) # Write the palettes self._grPalTbl.writeToRom(rom) updateProgress(5) def upgradeProject(self, oldVersion, newVersion, rom, resourceOpenerR, resourceOpenerW): def replaceField(fname, oldField, newField, valueMap): if newField == None: newField = oldField valueMap = dict((k, v) for k,v in valueMap.iteritems()) with resourceOpenerR(fname, 'yml') as f: data = yaml.load(f, Loader=yaml.CSafeLoader) for i in data: if data[i][oldField] in valueMap: data[i][newField] = valueMap[data[i][oldField]].lower() else: data[i][newField] = data[i][oldField] if newField != oldField: del data[i][oldField] with resourceOpenerW(fname, 'yml') as f: yaml.dump(data, f, Dumper=yaml.CSafeDumper) def replaceFieldName(fname, oldField, newField): if newField == None: newField = oldField with resourceOpenerR(fname, 'yml') as f: data = yaml.load(f, Loader=yaml.CSafeLoader) for i in data: data[i][newField] = data[i][oldField] del data[i][oldField] with resourceOpenerW(fname, 'yml') as f: yaml.dump(data, f, Dumper=yaml.CSafeDumper) if oldVersion == newVersion: updateProgress(100) return elif oldVersion == 2: replaceField("sprite_groups", "Unknown A", "Size", { 0: "16x16", 1: "16x16 2", 2: "24x16", 3: "32x16", 4: "48x16", 5: "16x24", 6: "24x24", 7: "16x32", 8: "32x32", 9: "48x32", 10: "24x40", 11: "16x48", 12: "32x48", 13: "48x48", 14: "64x48", 15: "64x64", 16: "64x80" }) replaceFieldName("sprite_groups", "Unknown B", "Collision Settings") self.upgradeProject(oldVersion+1, newVersion, rom, resourceOpenerR, resourceOpenerW) elif oldVersion == 1: self.upgradeProject(oldVersion+1, newVersion, rom, resourceOpenerR, resourceOpenerW)
class MapEventModule(EbModule.EbModule): _name = "Map Events" _PTR_LOC = 0x70d _PTR_BANK_LOC = 0x704 def __init__(self): self._ptrTbl = EbTable(0xD01598) self._entries = [ ] def freeRanges(self): return [(0x101598, 0x10187f)] def readFromRom(self, rom): self._ptrTbl.readFromRom(rom, EbModule.toRegAddr(rom.readMulti(self._PTR_LOC, 3))) updateProgress(5) bank = (rom[self._PTR_BANK_LOC] - 0xc0) << 16 pct = 45.0/20 for i in range(20): addr = bank | self._ptrTbl[i,0].val() tsetEntry = [] while (rom.readMulti(addr, 2) != 0): flag = rom.readMulti(addr, 2) num = rom.readMulti(addr+2, 2) addr += 4 changes = [] for j in range(num): changes.append((rom.readMulti(addr, 2), rom.readMulti(addr+2, 2))) addr += 4 tsetEntry.append((flag, changes)) self._entries.append(tsetEntry) updateProgress(pct) def writeToProject(self, resourceOpener): out = dict() i = 0 for entry in self._entries: entryOut = [] for (flag, changes) in entry: changeOut = { "Event Flag": flag } changeOut["Changes"] = changes entryOut.append(changeOut) if entryOut == []: out[i] = None else: out[i] = entryOut i += 1 updateProgress(25) with resourceOpener("map_changes", "yml") as f: s = yaml.dump(out, Dumper=yaml.CSafeDumper) s = sub("Event Flag: (\d+)", lambda i: "Event Flag: " + hex(int(i.group(0)[12:])), s) f.write(s) updateProgress(25) def readFromProject(self, resourceOpener): with resourceOpener("map_changes", "yml") as f: input = yaml.load(f, Loader=yaml.CSafeLoader) for mtset in input: entry = [] entryIn = input[mtset] if (entryIn != None): for csetIn in entryIn: entry.append((csetIn["Event Flag"], csetIn["Changes"])) self._entries.append(entry) updateProgress(50.0/20) def writeToRom(self, rom): self._ptrTbl.clear(20) blockSize = 0 for entry in self._entries: for (flag, set) in entry: blockSize += 4 + 4*len(set) blockSize += 2 if blockSize > 0xffff: raise RuntimeError("Too many map changes") loc = rom.getFreeLoc(blockSize) rom[self._PTR_BANK_LOC] = (loc >> 16) + 0xc0 i = 0 for entry in self._entries: self._ptrTbl[i,0].setVal(loc & 0xffff) for (flag, set) in entry: rom.writeMulti(loc, flag, 2) rom.writeMulti(loc+2, len(set), 2) loc += 4 for (before, after) in set: rom.writeMulti(loc, before, 2) rom.writeMulti(loc+2, after, 2) loc += 4 rom[loc] = 0 rom[loc+1] = 0 loc += 2 i += 1 updateProgress(45.0/20) ptrTblLoc = self._ptrTbl.writeToFree(rom) rom.writeMulti(self._PTR_LOC, EbModule.toSnesAddr(ptrTblLoc), 3) updateProgress(5)