Release 2026-11-04-r2 · SHA256 3bd7af7ee9ae0358ae2573c4fd3f9bfa7e1a23b61a160a601ddbcc21822d6025 · Click a line number for a direct link.
1"""Auto Replays/Crit preferences plus a per-season Icing row; all default On."""2import struct3ORIGIN=0x2DE0004LABELS=0x2DE8005VALUES=0x2DE9006COUNTS=0x2DEF007ADAPTER=0x2DEF2089def legacy_install(data,assemble,patch):10 code=assemble('replaymenu',ORIGIN);assert len(code)<0x80011 patch(ORIGIN,b'\xff'*len(code),code)12 labels=bytes(data[0x20915A:0x20915A+160])+b'\x00\x10Auto Replays \x00'13 assert len(labels)==17614 patch(LABELS,b'\xff'*len(labels),labels)15 # Keep the old relative string offsets valid by copying their whole span,16 # inserting the new offset word and adjusting existing offsets by two.17 old=bytes(data[0x2091FE:0x209742]);values=bytearray(old[:20]+b'\x00\x00'+old[20:])18 for i in range(10):struct.pack_into('>H',values,2*i,struct.unpack_from('>H',old,2*i)[0]+2)19 struct.pack_into('>H',values,20,len(values))20 values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'On',b'Off'))21 if with_refinements:22 ai=struct.unpack_from('>H',values,46)[0]23 momentum=struct.unpack_from('>H',values,50)[0]24 glass=struct.unpack_from('>H',values,24)[0] # existing On/Off order25 struct.pack_into('>4H',values,44,ai,momentum,unit_pointer,glass)26 assert VALUES+len(values)<COUNTS27 patch(VALUES,b'\xff'*len(values),values)28 for oldaddr,newaddr in [(0x2095AE,COUNTS),(0x2095C2,ADAPTER)]:29 counts=bytes(data[oldaddr:oldaddr+20])+b'\x00\x02'30 patch(newaddr,b'\xff'*len(counts),counts)31 for operand,oldaddr,newaddr in [(0xF807C,0x20915A,LABELS),(0x209638,0x2091FE,VALUES),(0x2FE27A,0x2091FE,VALUES),(0xF79AC,0x2095AE,COUNTS),(0xF79BA,0x2095C2,ADAPTER)]:32 patch(operand,struct.pack('>I',oldaddr),struct.pack('>I',newaddr))33 # Native shootout value special case compares the string-group pointer.34 patch(0xF7BBA,struct.pack('>I',0x209438),struct.pack('>I',VALUES+0x209438-0x2091FE+2))35 patch(0x2FE220,bytes.fromhex('32070c41000c'),bytes.fromhex('4ef9002de000'))36 patch(0xF777A,bytes.fromhex('0014'),bytes.fromhex('0016'))37 for addr in (0xF7758,0xF7762,0xF7C08):patch(addr,bytes.fromhex('0004'),bytes.fromhex('0005'))383940def install(data,assemble,patch,with_crit_option=True,legacy_crit_value_draw=False,with_icing=True,with_height=False,with_speed=False,with_directional=False,with_penalty_rate=False,with_pass_charge=False,with_pickup_delay=False,with_alt_goalies=False,with_strict_so=False,with_ai_shot_plan=False,with_faceoff_dots=False,with_momentum=False,with_transfers=False,with_refinements=False):41 if not with_crit_option:42 return legacy_install(data,assemble,patch)43 labels_at=0x2E1200 if with_directional else LABELS44 adapter_at=0x2DEF40 if with_directional else ADAPTER45 code=assemble('replaymenu',ORIGIN);assert len(code)<(0x400 if with_height else 0x800)46 patch(ORIGIN,b'\xff'*len(code),code)47 labels=bytes(data[0x20915A:0x20915A+160])+b'\x00\x10Auto Replays \x00'+b'\x00\x10Crit Shots \x00'48 if with_icing:labels+=b'\x00\x10Icing \x00'49 if with_height:labels+=b'\x00\x10Pass Height \x00'50 if with_speed:labels+=b'\x00\x10Pass Speed \x00'51 if with_directional:labels+=b'\x00\x10Pass Mode \x00'+b'\x00\x10Target Cone \x00'52 if with_penalty_rate:53 labels=labels[:96]+b'\x00\x10Rules \x00'+labels[112:]+b'\x00\x10Penaltys \x00'54 if with_pass_charge:labels+=b'\x00\x10Pass Charge \x00'55 if with_pickup_delay:labels+=b'\x00\x10Pickup Delay \x00'56 if with_alt_goalies:labels+=b'\x00\x10Alt Goalies \x00'57 if with_strict_so:labels+=b'\x00\x10OT SO Rules \x00'58 if with_ai_shot_plan:labels+=b'\x00\x10AI Shot Plan \x00'59 if with_faceoff_dots:labels+=b'\x00\x10Faceoff Dots \x00'60 if with_momentum:labels+=b'\x00\x10Momentum \x00'61 assert len(labels)==(400 if with_momentum else 384 if with_faceoff_dots else 368 if with_ai_shot_plan else 352 if with_strict_so else 336 if with_alt_goalies else 320 if with_pickup_delay else 304 if with_pass_charge else 288 if with_penalty_rate else 272 if with_directional else 240 if with_speed else 224 if with_height else 208 if with_icing else 192)62 if with_transfers:63 assert with_momentum and with_directional and with_icing64 labels=labels[:208]+b'\x00\x10Transfers \x00'+labels[208:]65 if with_refinements:66 assert with_transfers and len(labels)==41667 labels=labels[:22*16]+b''.join(b'\x00\x10'+v.ljust(13)+b'\x00' for v in [b'AI Shot Plan',b'Momentum',b'Shot Units',b'Fragile Glass'])68 patch(labels_at,b'\xff'*len(labels),labels)69 extra=30 if with_momentum else 28 if with_faceoff_dots else 26 if with_ai_shot_plan else 24 if with_strict_so else 22 if with_alt_goalies else 20 if with_pickup_delay else 18 if with_pass_charge else 16 if with_penalty_rate else 14 if with_directional else 10 if with_speed else 8 if with_height else 6 if with_icing else 470 old=bytes(data[0x2091FE:0x209742]);values=bytearray(old[:20]+b'\x00'*extra+old[20:])71 for i in range(10):struct.pack_into('>H',values,2*i,struct.unpack_from('>H',old,2*i)[0]+extra)72 for i in ((20,22,24) if with_icing else (20,22)):struct.pack_into('>H',values,i,len(values))73 values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'On',b'Off'))74 if with_height:75 struct.pack_into('>H',values,26,len(values))76 values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Normal',b'Low'))77 if with_speed:78 struct.pack_into('>H',values,28,len(values))79 values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Normal',b'Slow'))80 if with_directional:81 struct.pack_into('>H',values,30,0x2E1400-VALUES)82 struct.pack_into('>H',values,32,0x2E1400+44-VALUES)83 extra_values=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Assisted',b'Directional',b'Default',b'15 degrees',b'30 degrees',b'45 degrees'))84 if with_penalty_rate:85 struct.pack_into('>H',values,34,0x2E1400+len(extra_values)-VALUES)86 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'100%',b'50%',b'Off'))87 if with_pass_charge:88 struct.pack_into('>H',values,36,0x2E1400+len(extra_values)-VALUES)89 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))90 if with_pickup_delay:91 struct.pack_into('>H',values,38,0x2E1400+len(extra_values)-VALUES)92 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))93 if with_alt_goalies:94 struct.pack_into('>H',values,40,0x2E1400+len(extra_values)-VALUES)95 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))96 if with_strict_so:97 struct.pack_into('>H',values,42,0x2E1400+len(extra_values)-VALUES)98 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))99 if with_ai_shot_plan:100 struct.pack_into('>H',values,44,0x2E1400+len(extra_values)-VALUES)101 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))102 if with_faceoff_dots:103 struct.pack_into('>H',values,46,0x2E1400+len(extra_values)-VALUES)104 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))105 if with_refinements:106 # Existing groups are contiguous22B records; rewrite only text bytes.107 for group,suffix in [(6*22+3*22+2*22,b'Brief Catch Lock'),(6*22+3*22+4*22,b'New Goalie Model'),(6*22+3*22+8*22,b'Smarter Hard AI')]:108 for n,val in enumerate([b'Off',b'On']):109 text=val+b' '+suffix;assert len(text)<=20110 at=group+22*n+2;extra_values=extra_values[:at]+text.ljust(20)+extra_values[at+20:]111 if with_momentum:112 struct.pack_into('>H',values,48,0x2E1400+len(extra_values)-VALUES)113 extra_values+=b''.join(b'\x00\x16'+(v+(b' ~10 % Stats'if with_refinements else b'')).ljust(20) for v in (b'On',b'Off'))114 if with_transfers:115 transfer_pointer=0x2E1400+len(extra_values)-VALUES116 extra_values+=b''.join(b'\x00\x16'+v.ljust(20) for v in (b'Off',b'On'))117 if with_refinements:118 unit_pointer=0x2E1400+len(extra_values)-VALUES119 extra_values+=b''.join(b'\x00\x16'+v.ljust(20)for v in [b'KMH',b'MPH'])120 assert len(extra_values)<=0x400121 patch(0x2E1400,b'\xff'*len(extra_values),extra_values)122 if with_transfers:123 # Insert a real row after Icing. Only in-table relative pointers move;124 # external value groups at2E1400 retain their existing absolute targets.125 old_values=bytes(values)126 values=bytearray(values[:26]+struct.pack('>H',transfer_pointer)+values[26:])127 for offset in range(0,50,2):128 target=struct.unpack_from('>H',old_values,offset)[0]129 if target<len(old_values):target+=2130 struct.pack_into('>H',values,offset+(2 if offset>=26 else 0),target)131 extra+=2132 if with_refinements:133 ai=struct.unpack_from('>H',values,46)[0]134 momentum=struct.unpack_from('>H',values,50)[0]135 glass=struct.unpack_from('>H',values,24)[0] # existing On/Off order136 struct.pack_into('>4H',values,44,ai,momentum,unit_pointer,glass)137 assert VALUES+len(values)<COUNTS138 patch(VALUES,b'\xff'*len(values),values)139 for oldaddr,newaddr in [(0x2095AE,COUNTS),(0x2095C2,adapter_at)]:140 counts=bytes(data[oldaddr:oldaddr+20])+b'\x00\x02'*(5 if with_speed else 4 if with_height else 3 if with_icing else 2)141 if with_directional:counts+=b'\x00\x02\x00\x04'142 if with_penalty_rate:counts+=b'\x00\x03'143 if with_pass_charge:counts+=b'\x00\x02'144 if with_pickup_delay:counts+=b'\x00\x02'145 if with_alt_goalies:counts+=b'\x00\x02'146 if with_strict_so:counts+=b'\x00\x02'147 if with_ai_shot_plan:counts+=b'\x00\x02'148 if with_faceoff_dots:counts+=b'\x00\x02'149 if with_momentum:counts+=b'\x00\x02'150 if with_transfers:counts=counts[:26]+b'\x00\x02'+counts[26:]151 patch(newaddr,b'\xff'*len(counts),counts)152 for operand,oldaddr,newaddr in [(0xF807C,0x20915A,labels_at),(0x209638,0x2091FE,VALUES),(0x2FE27A,0x2091FE,VALUES),(0xF79AC,0x2095AE,COUNTS),(0xF79BA,0x2095C2,adapter_at)]:153 patch(operand,struct.pack('>I',oldaddr),struct.pack('>I',newaddr))154 patch(0xF7BBA,struct.pack('>I',0x209438),struct.pack('>I',VALUES+0x209438-0x2091FE+extra))155 patch(0x2FE220,bytes.fromhex('32070c41000c'),bytes.fromhex('4ef9002de000'))156 patch(0xF777A,bytes.fromhex('0014'),bytes.fromhex('0032' if with_momentum else '0030' if with_faceoff_dots else '002e' if with_ai_shot_plan else '002c' if with_strict_so else '002a' if with_alt_goalies else '0028' if with_pickup_delay else '0026' if with_pass_charge else '0024' if with_penalty_rate else '0022' if with_directional else '001e' if with_speed else '001c' if with_height else '001a' if with_icing else '0018'))157 # Native full value redraw previously stopped after logical row20.158 if not legacy_crit_value_draw:patch(0xF7B44,bytes.fromhex('0014'),bytes.fromhex('0030' if with_momentum else '002e' if with_faceoff_dots else '002c' if with_ai_shot_plan else '002a' if with_strict_so else '0028' if with_alt_goalies else '0026' if with_pickup_delay else '0024' if with_pass_charge else '0022' if with_penalty_rate else '0020' if with_directional else '001c' if with_speed else '001a' if with_height else '0018' if with_icing else '0016'))159 for addr in (0xF7758,0xF7762,0xF7C08):patch(addr,bytes.fromhex('0004'),bytes.fromhex('0013' if with_momentum else '0012' if with_faceoff_dots else '0011' if with_ai_shot_plan else '0010' if with_strict_so else '000f' if with_alt_goalies else '000e' if with_pickup_delay else '000d' if with_pass_charge else '000c' if with_penalty_rate else '000b' if with_directional else '0009' if with_speed else '0008' if with_height else '0007' if with_icing else '0006'))160 if with_transfers:161 for at,old,new in [(0xF777A,50,52),(0xF7B44,48,50),(0xF7758,19,20),(0xF7762,19,20),(0xF7C08,19,20)]:162 patch(at,struct.pack('>H',old),struct.pack('>H',new))163