@@ -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