experimental/replaymenu.py

Download file

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