Skip to content

Commit 3b47520

Browse files
committed
Attempt to clean up & improve SpritePatcher
1 parent cabe24b commit 3b47520

2 files changed

Lines changed: 122 additions & 104 deletions

File tree

RandomizerCore/ROM.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ public void UpdateSprite(CharacterSprite charSprite, bool sanitize, bool changeI
579579
if (charSprite.Patch != null) {
580580
if (sanitize)
581581
{
582-
SpritePatcher.PatchSpriteSanitized(rawdata, charSprite.Patch, true, changeItems);
582+
SpritePatcher.PatchSpriteSanitized(charSprite.DisplayName, rawdata, charSprite.Patch, true, changeItems);
583583
}
584584
else
585585
{

RandomizerCore/SpritePatcher.cs

Lines changed: 121 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ internal class AddressRange
1212
public int Start { get; set; }
1313
public int Length { get; set; }
1414

15+
public int End => Start + Length; // exclusive
16+
1517
public AddressRange(int start, int length)
1618
{
1719
Start = start;
@@ -29,12 +31,16 @@ public static AddressRange[] ChrRanges(int start, int end, int[] pages)
2931
return new AddressRange(romAddrStart, length);
3032
}).ToArray();
3133
}
34+
35+
public bool Contains(int addr) => Start <= addr && addr < End;
36+
37+
public bool Overlaps(AddressRange other) => Start < other.End && other.Start < End;
3238
}
3339

3440
/// <summary>
3541
/// Applies sanitized IPS sprite patches
3642
/// </summary>
37-
internal class SpritePatcher
43+
public class SpritePatcher
3844
{
3945
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
4046

@@ -48,10 +54,8 @@ .. AddressRange.ChrRanges(0x08a0, 0x09c0, [0,0x2,0x4,0x6,0x8,0xA,0xC,0xE,0x10,0x
4854
.. AddressRange.ChrRanges(0x1800, 0x1840, [0,0x2,0x4,0x6,0x8,0xA,0xC,0xE,0x10,0x12,0x14,0x16,0x18]), // Heart, Magic container
4955
]);
5056

51-
public static void PatchSpriteSanitized(byte[] romData, byte[] ipsData, bool expandRom, bool changeItems)
57+
public static void PatchSpriteSanitized(string filename, byte[] romData, byte[] ipsData, bool expandRom, bool changeItems, bool changeGameOver = true, bool changeDialog = true, bool changeEmptySpace = true)
5258
{
53-
Debug.Assert(PatchSig.SequenceEqual(new ArraySegment<byte>(ipsData, 0, PatchSig.Count)));
54-
5559
int ipsOffs = PatchSig.Count;
5660
while (!EofSig.SequenceEqual(new ArraySegment<byte>(ipsData, ipsOffs, EofSig.Count)))
5761
{
@@ -72,220 +76,234 @@ public static void PatchSpriteSanitized(byte[] romData, byte[] ipsData, bool exp
7276
fillValue = ipsData[ipsOffs++];
7377
}
7478

79+
// destination ROM base addr before rom expansion
7580
int srcOffs = tgtOffs;
7681
if (expandRom && tgtOffs + size > ROM.VanillaChrRomOffs)
7782
{
7883
if (tgtOffs < ROM.VanillaChrRomOffs)
7984
{
8085
int segSize = ROM.VanillaChrRomOffs - tgtOffs;
81-
82-
for (int i = 0; i < segSize; i++)
83-
{
84-
int addr = srcOffs + i;
85-
byte newValue = fillValue is not null ? (byte)fillValue : ipsData[ipsOffs + i];
86-
if (IsSanitizedSpriteAddress(addr, changeItems))
87-
{
88-
romData[tgtOffs + i] = newValue;
89-
}
90-
else
91-
{
92-
byte oldValue = romData[tgtOffs + i];
93-
logger.Error($"Moderating IPS patch at address: 0x{addr.ToString("x5")} from 0x{oldValue.ToString("x2")} to 0x{newValue.ToString("x2")}");
94-
}
95-
}
86+
PatchIfSanitized(ipsOffs, srcOffs, tgtOffs, segSize, fillValue);
9687
if (fillValue is null)
9788
{
9889
ipsOffs += segSize;
9990
}
10091

92+
size -= segSize;
93+
94+
// just continue unless the patch crossed PRG-CHR boundry
95+
if (size == 0) { continue; }
96+
10197
tgtOffs += segSize;
10298
srcOffs += segSize;
103-
size -= segSize;
10499
}
105100

106101
tgtOffs += ROM.ChrRomOffset - ROM.VanillaChrRomOffs;
107102
}
108103

109-
for (int i = 0; i < size; i++)
104+
PatchIfSanitized(ipsOffs, srcOffs, tgtOffs, size, fillValue);
105+
if (fillValue is null)
110106
{
111-
int addr = srcOffs + i;
112-
byte newValue = fillValue is not null ? (byte)fillValue : ipsData[ipsOffs + i];
113-
if (IsSanitizedSpriteAddress(addr, changeItems))
107+
ipsOffs += size;
108+
}
109+
}
110+
111+
void PatchIfSanitized(int ipsOffs, int srcOffs, int tgtOffs, int size, byte? fillValue)
112+
{
113+
if (IsSanitizedSpriteAddress(srcOffs, size, changeItems, changeGameOver, changeDialog, changeEmptySpace))
114+
{
115+
if (fillValue != null)
114116
{
115-
romData[tgtOffs + i] = newValue;
117+
Array.Fill(romData, (byte)fillValue, tgtOffs, size);
116118
}
117119
else
118120
{
119-
byte oldValue = romData[tgtOffs + i];
120-
logger.Error($"Moderating IPS patch at address: 0x{addr.ToString("x5")} from 0x{oldValue.ToString("x2")} to 0x{newValue.ToString("x2")}");
121+
Array.Copy(ipsData, ipsOffs, romData, tgtOffs, size);
121122
}
122123
}
123-
if (fillValue is null)
124+
else
124125
{
125-
ipsOffs += size;
126+
for (int i = 0; i < size; i++)
127+
{
128+
int addr = srcOffs + i;
129+
byte newValue = fillValue != null ? (byte)fillValue : ipsData[ipsOffs + i];
130+
if (IsSanitizedSpriteAddress(addr, 1, changeItems, changeGameOver, changeDialog, changeEmptySpace))
131+
{
132+
romData[tgtOffs + i] = newValue;
133+
}
134+
else
135+
{
136+
if (addr < ROM.VanillaChrRomOffs) // don't warn about changeItems=false moderation
137+
{
138+
byte oldValue = romData[tgtOffs + i];
139+
logger.Warn($"Moderating IPS patch \"{filename}\" write at address: 0x{addr:x5} from 0x{oldValue:x2} to 0x{newValue:x2}");
140+
}
141+
}
142+
}
126143
}
127144
}
128145
}
129146

130-
private static bool IsSanitizedSpriteAddress(int addr, bool changeItems, bool changeGameOver=true)
147+
private static bool IsSanitizedSpriteAddress(int addr, int length, bool changeItems, bool changeGameOver, bool changeDialog, bool changeEmptySpace)
131148
{
132149
// NOTE: this isn't written in stone, if anything
133150
// more should be allowed, let us know.
134151

135-
if (ROM.VanillaChrRomOffs <= addr) {
152+
var requestedRange = new AddressRange(addr, length);
153+
154+
// item sprite logic
155+
if (addr >= ROM.VanillaChrRomOffs)
156+
{
136157
if (!changeItems)
137158
{
138-
if (ChrItemAddresses.Contains(addr))
139-
{
159+
if (ChrItemAddresses.Any(requestedRange.Contains))
160+
{
140161
return false;
141162
}
142163
}
143164
return true;
144165
}
145166

146167
// Link's palettes
147-
if (ROM.LinkOutlinePaletteAddr.Contains(addr)) { return true; }
148-
if (ROM.LinkFacePaletteAddr.Contains(addr)) { return true; }
149-
if (ROM.LinkTunicPaletteAddr.Contains(addr)) { return true; }
150-
if (ROM.LinkShieldPaletteAddr == addr) { return true; }
168+
if (ROM.LinkOutlinePaletteAddr.Contains(addr) && requestedRange.Length == 1) { return true; }
169+
if (ROM.LinkFacePaletteAddr.Contains(addr) && requestedRange.Length == 1) { return true; }
170+
if (ROM.LinkTunicPaletteAddr.Contains(addr) && requestedRange.Length == 1) { return true; }
171+
if (ROM.LinkShieldPaletteAddr == addr && requestedRange.Length == 1) { return true; }
151172
// Zelda's palettes
152-
if (ROM.ZeldaOutlinePaletteAddr.Contains(addr)) { return true; }
153-
if (ROM.ZeldaFacePaletteAddr.Contains(addr)) { return true; }
154-
if (ROM.ZeldaDressPaletteAddr.Contains(addr)) { return true; }
173+
if (ROM.ZeldaOutlinePaletteAddr.Contains(addr) && requestedRange.Length == 1) { return true; }
174+
if (ROM.ZeldaFacePaletteAddr.Contains(addr) && requestedRange.Length == 1) { return true; }
175+
if (ROM.ZeldaDressPaletteAddr.Contains(addr) && requestedRange.Length == 1) { return true; }
155176
// iNES header
156-
if (addr < 0x10) { return false; }
177+
if (requestedRange.Start < 0x10) { return false; }
157178
// Allow custom game over screens. Since this contains PPU pointers,
158179
// it can probably crash if the patch is bad. We allow everything here
159180
// until the next section. It's unlikely it will lead to *hard-to-trace* crashes.
160181
// (We're keeping the final FF ending byte at 0xE4.)
161-
if (0x10 <= addr && addr < 0xE4) { return changeGameOver; }
182+
if (requestedRange.Start >= 0x10 && requestedRange.End <= 0xE4) { return changeGameOver; }
162183

163184
// Beam sword projectile
164185
// LDA #$32 ; 0x18fa A9 32
165186
// STA ,y ; 0x18fc 99 01 02 -- writes to RAM 0x239
166-
if (addr == 0x18FB) { return true; }
187+
if (addr == 0x18FB && requestedRange.Length == 1) { return true; }
167188
// Level up pane
168-
if (0x1bba <= addr && addr < 0x1c2a) { return true; }
189+
if (requestedRange.Start >= 0x1bba && requestedRange.End <= 0x1c2a) { return true; }
169190

170191
// Table_for_Links_Palettes_Probably
171-
if (0x2a00 <= addr && addr < 0x2a18) { return true; }
192+
if (requestedRange.Start >= 0x2a00 && requestedRange.End <= 0x2a18) { return true; }
172193

173194
// bank1_Pointer_table_for_Background_Areas_Data
174195
// This is the sideview map pointer table for the background maps in the west
175196
// We deny all map command changes and pointers to room changes
176-
if (0x4010 <= addr && addr < 0x401e) { return false; }
197+
if (requestedRange.Start >= 0x4010 && requestedRange.End <= 0x401e) { return false; }
177198
// Palettes_for_Overworld
178-
if (0x401e <= addr && addr < 0x40fe) { return true; }
199+
if (requestedRange.Start >= 0x401e && requestedRange.End <= 0x40fe) { return true; }
179200

180201
// bank1_Area_Pointers_West_Hyrule
181-
if (0x4533 <= addr && addr < 0x45b1) { return false; }
202+
if (requestedRange.Start >= 0x4533 && requestedRange.End <= 0x45b1) { return false; }
182203
// bank1_Area_Data__West_Hyrule_Random_Battle___Desert__South_West_Hyrule_
183-
if (0x478f <= addr && addr < 0x479f) { return false; }
204+
if (requestedRange.Start >= 0x478f && requestedRange.End <= 0x479f) { return false; }
184205
// bank1_Background_Areas_Data
185-
if (0x4c4c <= addr && addr < 0x506c) { return false; }
206+
if (requestedRange.Start >= 0x4c4c && requestedRange.End <= 0x506c) { return false; }
186207

187208
// bank1_Area_Pointers_Death_Mountain
188-
if (0x6010 <= addr && addr < 0x610c) { return false; }
209+
if (requestedRange.Start >= 0x6010 && requestedRange.End <= 0x610c) { return false; }
189210
// Area_Data_Death_Mountain_And_Maze
190-
if (0x627c <= addr && addr < 0x665c) { return false; }
191-
// Blank data
192-
if (0x6943 <= addr && addr < 0x7f80) { return true; }
211+
if (requestedRange.Start >= 0x627c && requestedRange.End <= 0x665c) { return false; }
212+
// Blank data in bank 1
213+
if (requestedRange.Start >= 0x6943 && requestedRange.End <= 0x7f80) { return changeEmptySpace; }
193214

194215
// bank2_Pointer_table_for_background_level_data
195-
if (0x8010 <= addr && addr < 0x801e) { return false; }
216+
if (requestedRange.Start >= 0x8010 && requestedRange.End <= 0x801e) { return false; }
196217
// Palettes_for_Overworld
197-
if (0x801e <= addr && addr < 0x80fe) { return true; }
218+
if (requestedRange.Start >= 0x801e && requestedRange.End <= 0x80fe) { return true; }
198219
// bank2_Background_Areas_Data
199-
if (0x8c72 <= addr && addr < 0x8ce8) { return false; }
220+
if (requestedRange.Start >= 0x8c72 && requestedRange.End <= 0x8ce8) { return false; }
200221

201222
// bank2_Area_Pointers_Maze_Island
202-
if (0xA010 <= addr && addr < 0xA10c) { return false; }
223+
if (requestedRange.Start >= 0xA010 && requestedRange.End <= 0xA10c) { return false; }
203224
// bank2: Area_Data_Death_Mountain_And_Maze
204-
if (0xA27c <= addr && addr < 0xA65c) { return false; }
205-
// Blank data
206-
if (0xA943 <= addr && addr < 0xBf80) { return true; }
225+
if (requestedRange.Start >= 0xA27c && requestedRange.End <= 0xA65c) { return false; }
226+
// Blank data in bank 2
227+
if (requestedRange.Start >= 0xA943 && requestedRange.End <= 0xBf80) { return changeEmptySpace; }
207228

208229
// Palettes for towns
209-
if (0xC01e <= addr && addr < 0xC0fe) { return true; }
230+
if (requestedRange.Start >= 0xC01e && requestedRange.End <= 0xC0fe) { return true; }
210231
// Area objects tile mappings
211-
if (0xC3da <= addr && addr < 0xC51c) { return true; }
232+
if (requestedRange.Start >= 0xC3da && requestedRange.End <= 0xC51c) { return false; }
212233
// bank3_Area_Pointers__Towns
213-
if (0xC533 <= addr && addr < 0xC5b1) { return false; }
234+
if (requestedRange.Start >= 0xC533 && requestedRange.End <= 0xC5b1) { return false; }
214235
// bank3_Area_Data_Towns1
215-
if (0xC9d0 <= addr && addr < 0xCb9e) { return false; }
216-
// bank3_SmallObjectsConstructionRoutines_Locked_Door_glitched_tiles_0E
217-
if (0xCb9e <= addr && addr < 0xCba5) { return false; }
218-
// bank3_Objects_Construction_Routines_Bushes_1_high_X_wide_Y_Position_A__1x
219-
if (0xCb9e <= addr && addr < 0xCba5) { return false; }
236+
if (requestedRange.Start >= 0xC9d0 && requestedRange.End <= 0xCb9e) { return false; }
237+
// bank3_SmallObjectsConstructionRoutines
238+
if (requestedRange.Start >= 0xCb9e && requestedRange.End <= 0xCba5) { return false; }
220239
// bank3_Object_Construction_Routine
221-
if (0xCba5 <= addr && addr < 0xCbc1) { return false; }
222-
// Blank data
223-
if (0xCbc1 <= addr && addr < 0xD100) { return true; }
240+
if (requestedRange.Start >= 0xCba5 && requestedRange.End <= 0xCbc1) { return false; }
241+
// Blank data in bank 3
242+
if (requestedRange.Start >= 0xCbc1 && requestedRange.End <= 0xD100) { return changeEmptySpace; }
224243

225244
// bank3_Pointer_table_for_Objects_Construction_Routines
226-
if (0xDbaf <= addr && addr < 0xDbdb) { return false; }
245+
if (requestedRange.Start >= 0xDbaf && requestedRange.End <= 0xDbdb) { return false; }
227246
// bank3_Table_for_Small_Objects_Construction_Routines
228-
if (0xDbed <= addr && addr < 0xDc21) { return false; }
247+
if (requestedRange.Start >= 0xDbed && requestedRange.End <= 0xDc21) { return false; }
229248
// bank3_Area_Data_Towns3
230-
if (0xDcd2 <= addr && addr < 0xEfce) { return false; }
249+
if (requestedRange.Start >= 0xDcd2 && requestedRange.End <= 0xEfce) { return false; }
231250

232251
// bank3_Dialogs_Pointer_Table_Towns_in_West_Hyrule
233-
if (0xEfce <= addr && addr < 0xF092) { return true; }
252+
if (requestedRange.Start >= 0xEfce && requestedRange.End <= 0xF092) { return changeDialog; }
234253

235-
// Blank data
236-
if (0xF813 <= addr && addr < 0xFf80) { return true; }
254+
// Blank data in bank 3
255+
if (requestedRange.Start >= 0xF813 && requestedRange.End <= 0xFf80) { return changeEmptySpace; }
237256

238257
// bank4_Default_Palettes_for_Palaces_Type_A_B_
239-
if (0x1001e <= addr && addr < 0x100fe) { return true; }
258+
if (requestedRange.Start >= 0x1001e && requestedRange.End <= 0x100fe) { return true; }
240259
// bank4_Area_Pointers_Palaces_Type_A
241-
if (0x10533 <= addr && addr < 0x1072b) { return false; }
260+
if (requestedRange.Start >= 0x10533 && requestedRange.End <= 0x1072b) { return false; }
242261
// bank4_Area_Data
243-
if (0x10c83 <= addr && addr < 0x10f26) { return false; }
262+
if (requestedRange.Start >= 0x10c83 && requestedRange.End <= 0x10f26) { return false; }
244263

245-
// Blank data
246-
if (0x12775 <= addr && addr < 0x12910) { return true; }
264+
// Blank data in bank 4
265+
if (requestedRange.Start >= 0x12775 && requestedRange.End <= 0x12910) { return changeEmptySpace; }
247266

248267
// Palettes for Great Palace
249-
if (0x1401e <= addr && addr < 0x140fe) { return true; }
268+
if (requestedRange.Start >= 0x1401e && requestedRange.End <= 0x140fe) { return true; }
250269
// bank5_Area_Data_Great_Palace2
251-
if (0x14533 <= addr && addr < 0x145b1) { return false; }
270+
if (requestedRange.Start >= 0x14533 && requestedRange.End <= 0x145b1) { return false; }
252271
// bank5_Area_Pointers_Great_Palace
253-
if (0x14827 <= addr && addr < 0x148b0) { return false; }
272+
if (requestedRange.Start >= 0x14827 && requestedRange.End <= 0x148b0) { return false; }
254273
// bank5_Area_Data_Great_Palace3
255-
if (0x149e8 <= addr && addr < 0x14b41) { return false; }
274+
if (requestedRange.Start >= 0x149e8 && requestedRange.End <= 0x14b41) { return false; }
256275
// bank5_Ending_Text_Zelda_
257-
if (0x14df1 <= addr && addr < 0x14e1a) { return true; }
258-
276+
if (requestedRange.Start >= 0x14df1 && requestedRange.End <= 0x14e1a) { return true; }
259277
// bank5_End_Credits
260-
if (0x1528d <= addr && addr < 0x153bd) { return true; }
278+
if (requestedRange.Start >= 0x1528d && requestedRange.End <= 0x153bd) { return true; }
261279

262280
// bank5_table_intro_screen_text (Sprite credits stored at 0x16abb)
263-
if (0x16942 <= addr && addr < 0x16af5) { return true; }
264-
265-
// Blank data
266-
if (0x17db1 <= addr && addr < 0x17f70) { return true; }
281+
if (requestedRange.Start >= 0x16942 && requestedRange.End <= 0x16af5) { return true; }
282+
283+
// Blank data in bank 5
284+
if (requestedRange.Start >= 0x17db1 && requestedRange.End <= 0x17f70) { return changeEmptySpace; }
267285

268286
// bank7_Table_for_Overworld_Palettes
269-
if (0x1c468 <= addr && addr < 0x1c48c) { return true; }
287+
if (requestedRange.Start >= 0x1c468 && requestedRange.End <= 0x1c48c) { return true; }
270288

271289
// bank7_Table_for_some_Palettes
272-
if (0x1d0cd <= addr && addr < 0x1d0e1) { return true; }
290+
if (requestedRange.Start >= 0x1d0cd && requestedRange.End <= 0x1d0e1) { return true; }
273291
// bank7_Tables_for_some_PPU_Command_Data
274292
// Addr before Magic? icon?
275-
if (0x1d0e1 <= addr && addr < 0x1d0e3) { return true; }
293+
if (requestedRange.Start >= 0x1d0e1 && requestedRange.End <= 0x1d0e3) { return true; }
276294
// Not allowing change of length of the MAGIC- text
277-
if (0x1d0e3 == addr) { return false; }
295+
if (requestedRange.Start == 0x1d0e3) { return false; }
278296
// text: MAGIC-
279-
if (0x1d0e4 <= addr && addr < 0x1d0ea) { return true; }
297+
if (requestedRange.Start >= 0x1d0e4 && requestedRange.End <= 0x1d0ea) { return true; }
280298
// Addr before Life? icon?
281-
if (0x1d0ea <= addr && addr < 0x1d0ec) { return true; }
299+
if (requestedRange.Start >= 0x1d0ea && requestedRange.End <= 0x1d0ec) { return true; }
282300
// Not allowing change of length of the text
283-
if (0x1d0ec == addr) { return false; }
301+
if (requestedRange.Start == 0x1d0ec) { return false; }
284302
// text: LIFE-
285-
if (0x1d0ed <= addr && addr < 0x1d0f2) { return true; }
303+
if (requestedRange.Start >= 0x1d0ed && requestedRange.End <= 0x1d0f2) { return true; }
286304

287305
// bank7_Continue_Save_Screen_Tile_Mappings
288-
if (0x1fddb <= addr && addr < 0x1fe86) { return true; }
306+
if (requestedRange.Start >= 0x1fddb && requestedRange.End <= 0x1fe86) { return true; }
289307

290308
return false;
291309
}

0 commit comments

Comments
 (0)