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 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 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)
Exemple #4
0
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 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)