mirror of
				https://github.com/MarioSpore/Grinch-AP.git
				synced 2025-10-21 20:21:32 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			212 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import copy
 | |
| import struct
 | |
| 
 | |
| 
 | |
| class PointerTable:
 | |
|     END_OF_DATA = (0xff, )
 | |
| 
 | |
|     """
 | |
|         Class to manage a list of pointers to data objects
 | |
|         Can rewrite the rom to modify the data objects and still keep the pointers intact.
 | |
|     """
 | |
|     def __init__(self, rom, info):
 | |
|         assert "count" in info
 | |
|         assert "pointers_bank" in info
 | |
|         assert "pointers_addr" in info
 | |
|         assert ("banks_bank" in info and "banks_addr" in info) or ("data_bank" in info)
 | |
|         self.__info = info
 | |
| 
 | |
|         self.__data = []
 | |
|         self.__alt_data = {}
 | |
|         self.__banks = []
 | |
|         self.__storage = []
 | |
| 
 | |
|         count = info["count"]
 | |
|         addr = info["pointers_addr"]
 | |
|         pointers_bank = rom.banks[info["pointers_bank"]]
 | |
|         if "data_addr" in info:
 | |
|             pointers_raw = []
 | |
|             for n in range(count):
 | |
|                 pointers_raw.append(info["data_addr"] + 0x4000 + pointers_bank[addr + n] * info["data_size"])
 | |
|         else:
 | |
|             pointers_raw = struct.unpack("<" + "H" * count, pointers_bank[addr:addr+count*2])
 | |
|         if "data_bank" in info:
 | |
|             banks = [info["data_bank"]] * count
 | |
|         else:
 | |
|             addr = info["banks_addr"]
 | |
|             banks = rom.banks[info["banks_bank"]][addr:addr+count]
 | |
| 
 | |
|         if "alt_pointers" in self.__info:
 | |
|             for key, (bank, addr) in self.__info["alt_pointers"].items():
 | |
|                 pointer = struct.unpack("<H", rom.banks[bank][addr:addr+2])[0]
 | |
|                 assert 0x4000 <= pointer < 0x8000
 | |
|                 self.__alt_data[key] = self._readData(rom, self.__info["data_bank"], pointer & 0x3FFF)
 | |
| 
 | |
|         for n in range(count):
 | |
|             bank = banks[n] & 0x3f
 | |
|             pointer = pointers_raw[n]
 | |
|             if 0x4000 <= pointer < 0x8000:
 | |
|                 pointer &= 0x3fff
 | |
|                 self.__data.append(self._readData(rom, bank, pointer))
 | |
|             else:
 | |
|                 self.__data.append(pointer)
 | |
|             self.__banks.append(bank)
 | |
| 
 | |
|         while self.__mergeStorage():
 | |
|             pass
 | |
|         self.__storage.sort(key=lambda n: n["start"])
 | |
|         if "claim_storage_gaps" in info and info["claim_storage_gaps"]:
 | |
|             self.__storage = [{"bank": self.__storage[0]["bank"], "start": self.__storage[0]["start"], "end": self.__storage[-1]["end"]}]
 | |
|         if "expand_to_end_of_bank" in info and info["expand_to_end_of_bank"]:
 | |
|             for st in self.__storage:
 | |
|                 if info["expand_to_end_of_bank"] == True or st["bank"] in info["expand_to_end_of_bank"]:
 | |
|                     expand = True
 | |
|                     for st2 in self.__storage:
 | |
|                         if st["bank"] == st2["bank"] and st["end"] < st2["end"]:
 | |
|                             expand = False
 | |
|                     if expand:
 | |
|                         st["end"] = 0x4000
 | |
|         self.storage = self.__storage
 | |
| 
 | |
|         # for s in sorted(self.__storage, key=lambda s: (s["bank"], s["start"])):
 | |
|         #     print(self.__class__.__name__, s)
 | |
| 
 | |
|     def __contains__(self, item):
 | |
|         if isinstance(item, str):
 | |
|             return item in self.__alt_data
 | |
|         return 0 <= item < len(self.__data)
 | |
| 
 | |
|     def __setitem__(self, item, value):
 | |
|         if isinstance(item, str):
 | |
|             self.__alt_data[item] = value
 | |
|         else:
 | |
|             self.__data[item] = value
 | |
| 
 | |
|     def __getitem__(self, item):
 | |
|         if isinstance(item, str):
 | |
|             return self.__alt_data[item]
 | |
|         return self.__data[item]
 | |
| 
 | |
|     def __len__(self):
 | |
|         return len(self.__data)
 | |
| 
 | |
|     def store(self, rom):
 | |
|         storage = copy.deepcopy(self.__storage)
 | |
| 
 | |
|         pointers_bank = self.__info["pointers_bank"]
 | |
|         pointers_addr = self.__info["pointers_addr"]
 | |
| 
 | |
|         done = {}
 | |
|         for st in storage:
 | |
|             done[st["bank"]] = {}
 | |
|         for key, (ptr_bank, ptr_addr) in self.__info.get("alt_pointers", {}).items():
 | |
|             s = bytes(self.__alt_data[key])
 | |
|             bank = self.__info["data_bank"]
 | |
|             my_storage = None
 | |
|             for st in storage:
 | |
|                 if st["end"] - st["start"] >= len(s) and st["bank"] == bank:
 | |
|                     my_storage = st
 | |
|                     break
 | |
|             assert my_storage is not None, "Not enough room in storage... %s" % (storage)
 | |
| 
 | |
|             pointer = my_storage["start"]
 | |
|             my_storage["start"] = pointer + len(s)
 | |
|             rom.banks[bank][pointer:pointer + len(s)] = s
 | |
| 
 | |
|             rom.banks[ptr_bank][ptr_addr] = pointer & 0xFF
 | |
|             rom.banks[ptr_bank][ptr_addr + 1] = (pointer >> 8) | 0x40
 | |
| 
 | |
|         data = list(enumerate(self.__data))        
 | |
|         data.sort(key=lambda t: type(t[1]) == int or -len(t[1]))
 | |
| 
 | |
|         for n, s in data:
 | |
|             if isinstance(s, int):
 | |
|                 pointer = s
 | |
|             else:
 | |
|                 s = bytes(s)
 | |
|                 bank = self.__banks[n]
 | |
|                 if s in done[bank]:
 | |
|                     pointer = done[bank][s]
 | |
|                     assert rom.banks[bank][pointer:pointer+len(s)] == s
 | |
|                 else:
 | |
|                     my_storage = None
 | |
|                     for st in storage:
 | |
|                         if st["end"] - st["start"] >= len(s) and st["bank"] == bank:
 | |
|                             my_storage = st
 | |
|                             break
 | |
|                     assert my_storage is not None, "Not enough room in storage... %d/%d %s id:%x(%d) bank:%d" % (n, len(self.__data), storage, n, n, bank)
 | |
| 
 | |
|                     pointer = my_storage["start"]
 | |
|                     my_storage["start"] = pointer + len(s)
 | |
|                     rom.banks[bank][pointer:pointer+len(s)] = s
 | |
| 
 | |
|                     if "data_size" not in self.__info:
 | |
|                         # aggressive de-duplication.
 | |
|                         for skip in range(len(s)):
 | |
|                             done[bank][s[skip:]] = pointer + skip
 | |
|                     done[bank][s] = pointer
 | |
| 
 | |
|             if "data_addr" in self.__info:
 | |
|                 offset = pointer - self.__info["data_addr"]
 | |
|                 if "data_size" in self.__info:
 | |
|                     assert offset % self.__info["data_size"] == 0
 | |
|                     offset //= self.__info["data_size"]
 | |
|                 rom.banks[pointers_bank][pointers_addr + n] = offset
 | |
|             else:
 | |
|                 rom.banks[pointers_bank][pointers_addr+n*2] = pointer & 0xff
 | |
|                 rom.banks[pointers_bank][pointers_addr+n*2+1] = ((pointer >> 8) & 0xff) | 0x40
 | |
| 
 | |
|         space_left = sum(map(lambda n: n["end"] - n["start"], storage))
 | |
|         # print(self.__class__.__name__, "Space left:", space_left)
 | |
|         return storage
 | |
| 
 | |
|     def _readData(self, rom, bank_nr, pointer):
 | |
|         bank = rom.banks[bank_nr]
 | |
|         start = pointer
 | |
|         if "data_size" in self.__info:
 | |
|             pointer += self.__info["data_size"]
 | |
|         else:
 | |
|             while bank[pointer] not in self.END_OF_DATA:
 | |
|                 pointer += 1
 | |
|             pointer += 1
 | |
|         self._addStorage(bank_nr, start, pointer)
 | |
|         return bank[start:pointer]
 | |
| 
 | |
|     def _addStorage(self, bank, start, end):
 | |
|         for n, data in enumerate(self.__storage):
 | |
|             if data["bank"] == bank:
 | |
|                 if data["start"] == end:
 | |
|                     data["start"] = start
 | |
|                     return
 | |
|                 if data["end"] == start:
 | |
|                     data["end"] = end
 | |
|                     return
 | |
|                 if data["start"] <= start and data["end"] >= end:
 | |
|                     return
 | |
|         self.__storage.append({"bank": bank, "start": start, "end": end})
 | |
| 
 | |
|     def __mergeStorage(self):
 | |
|         for n in range(len(self.__storage)):
 | |
|             n_end = self.__storage[n]["end"]
 | |
|             n_start = self.__storage[n]["start"]
 | |
|             for m in range(len(self.__storage)):
 | |
|                 if m == n or self.__storage[n]["bank"] != self.__storage[m]["bank"]:
 | |
|                     continue
 | |
|                 m_end = self.__storage[m]["end"]
 | |
|                 m_start = self.__storage[m]["start"]
 | |
|                 if m_start - 1 <= n_end <= m_end:
 | |
|                     self.__storage[n]["start"] = min(self.__storage[n]["start"], self.__storage[m]["start"])
 | |
|                     self.__storage[n]["end"] = self.__storage[m]["end"]
 | |
|                     self.__storage.pop(m)
 | |
|                     return True
 | |
|         return False
 | |
| 
 | |
|     def addStorage(self, extra_storage):
 | |
|         for data in extra_storage:
 | |
|             self._addStorage(data["bank"], data["start"], data["end"])
 | |
|         while self.__mergeStorage():
 | |
|             pass
 | |
|         self.__storage.sort(key=lambda n: n["start"])
 | |
| 
 | |
|     def adjustDataStart(self, new_start):
 | |
|         self.__info["data_addr"] = new_start | 
