-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpatch.py
More file actions
1547 lines (1432 loc) · 62.3 KB
/
Copy pathpatch.py
File metadata and controls
1547 lines (1432 loc) · 62.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# Model-output: Claude Fable 5
# Model-output: GPT-5.6-Sol
"""Patch the exact Process Lasso build supplied on 2026-08-17 for Per-Monitor V2 HiDPI."""
from __future__ import annotations
import argparse
import hashlib
import shutil
import struct
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path
EXPECTED_SHA256 = "3bfdbad16ddf47e2e9d303294c9f6de90eb90f6856bc0126e27bc1bd30e4e884"
EXPECTED_IMAGE_BASE = 0x140000000
EXPECTED_NEW_SECTION_RVA = 0x280000
EXPECTED_CERTIFICATE_OFFSET = 0x274600
MANIFEST_DATA_ENTRY_OFFSET = 0x235970
LEGACY_HIDPI_CODE_SIZE = 0x4CD
HIDPI_CODE_SIZE = 0xB00
HIDPI_CODE_BASE_VA = EXPECTED_IMAGE_BASE + EXPECTED_NEW_SECTION_RVA
USER32_DLL_OFFSET = 0x03F0
GET_DPI_FOR_WINDOW_NAME_OFFSET = 0x0406
SYSTEM_PARAMETERS_INFO_FOR_DPI_NAME_OFFSET = 0x04B2
GET_DPI_FOR_SYSTEM_NAME_OFFSET = 0x04D0
HIDPI_SYMBOL_OFFSETS = {
"graph_height": 0x0050,
"main_margins": 0x0090,
"graph_threshold": 0x0100,
"graph_gap": 0x0160,
"load1_width": 0x01C0,
"load1_x": 0x0200,
"load2_x": 0x0220,
"card_rect": 0x02E0,
"card_text": 0x0380,
"system_parameters_info": 0x0420,
"fixed_load_reservation_1": 0x04E0,
"fixed_load_reservation_2": 0x0580,
"load2_gap": 0x0620,
"processor_gap": 0x0680,
"upper_bar_height": 0x0760,
"lower_bar_height": 0x07B0,
"main_system_parameters_info": 0x0820,
"upper_search_icon_square": 0x08C0,
"lower_search_icon_square": 0x08E0,
"upper_search_margin": 0x0930,
"lower_search_margin": 0x0970,
"process_icon_size": 0x0A00,
}
PATCH_SITES = {
"graph_height": (0x0509DD, bytes.fromhex("3bc10f42c1")),
"main_margins": (0x050A36, bytes.fromhex("448b4c24444532f6448b4424404183c1058b5424484183c00583c2fb44898fc4090000448be8448987c00900008997c8090000")),
"graph_threshold": (0x050AD4, bytes.fromhex("8d419c413bc5")),
"graph_gap": (0x050ADC, bytes.fromhex("412bcd83c1fb898fc8090000")),
"load1_width": (0x050B2E, bytes.fromhex("41b93c000000")),
"load1_x": (0x050B4E, bytes.fromhex("488d8f900700008d50c4")),
"load2_x": (0x050C35, bytes.fromhex("8d50c4488b8710080000")),
"upper_bar_height": (0x050DA8, bytes.fromhex("418d412089876c0a0000")),
"lower_bar_height": (0x0518BC, bytes.fromhex("418d412044898f140a0000")),
"system_parameters_info": (0x05BF46, bytes.fromhex("ff157c3d1700")),
"card_rect": (0x05D59C, bytes.fromhex("8b4c246083c1058b54246483c205448b45c04183c0164403c1458d680f448b4d90440faf4dc44183c1164403ca")),
"card_text": (0x05D61C, bytes.fromhex("8b5c246483c30d8b74246083c611")),
"fixed_load_reservation_1": (0x050A72, bytes.fromhex("8d429c83f83c7e0b83c2bf8997c8090000eb0341b601")),
"fixed_load_reservation_2": (0x050AB2, bytes.fromhex("8d429c83f83c7e0b8d4abf898fc8090000eb0341b401")),
"load2_gap": (0x050C1E, bytes.fromhex("8b87d00a000083e805")),
"processor_gap": (0x050CF3, bytes.fromhex("412bc083c20589442420")),
"main_system_parameters_info": (0x04C855, bytes.fromhex("ff156d341800")),
"upper_search_icon_square": (0x051200, bytes.fromhex("412bc1782d")),
"lower_search_icon_square": (0x0519E7, bytes.fromhex("412bc1412bc8")),
"upper_search_margin": (0x04E177, bytes.fromhex("41b900001000")),
"lower_search_margin": (0x04E0EA, bytes.fromhex("41b900001000")),
"process_icon_size": (0x018836, bytes.fromhex("ba10000000")),
}
# In-place instruction replacements that do not trampoline into `.hidpi`. Each maps a
# build-locked original slice to assembly that must reassemble to exactly the same length,
# leaving the surrounding code untouched. Unlike `PATCH_SITES`, these do not jump away: they
# either reuse a register that a nearby trampoline leaves live, or NOP a now-dead hardcoded
# store whose field a trampoline has already rewritten with a DPI-scaled value.
_NOP3 = "\n".join("nop" for _ in range(3))
_NOP10 = "\n".join("nop" for _ in range(10))
INPLACE_PATCH_SITES = {
# Fixed load-display #2 width: reuse the DPI-scaled width already in r10d (from load1_width).
"load2_width": (0x050C4D, bytes.fromhex("41b93c000000"), "mov r9d, r10d\n" + _NOP3),
# Outer tab-strip `cy` argument to SetWindowPos: use the scaled 32 its trampoline left in r10d.
"upper_bar_cy": (0x050DCE, bytes.fromhex("c744242820000000"), "mov dword ptr [rsp + 0x28], r10d\n" + _NOP3),
"lower_bar_cy": (0x0518E3, bytes.fromhex("c744242820000000"), "mov dword ptr [rsp + 0x28], r10d\n" + _NOP3),
# Overlaid child-cell heights (originally 22 px). The bar-height trampolines pre-write
# DPI-scaled values into these fields, so the original constant stores must be neutralized
# or they would clobber the scaled values back to 22.
"upper_child_bac": (0x050E50, bytes.fromhex("c787ac0b000016000000"), _NOP10),
"upper_child_acc": (0x050E8E, bytes.fromhex("c787cc0a000016000000"), _NOP10),
"lower_child_b4c": (0x05192D, bytes.fromhex("c7874c0b000016000000"), _NOP10),
"lower_child_b7c": (0x051BB5, bytes.fromhex("c7877c0b000016000000"), _NOP10),
"lower_child_b9c": (0x051DC5, bytes.fromhex("c7879c0b000016000000"), _NOP10),
"lower_child_a5c": (0x051FF4, bytes.fromhex("c7875c0a000016000000"), _NOP10),
# Upper search-icon Static width: use the square side its trampoline leaves live in eax
# instead of the fixed 16 px, so the stretched HICON keeps a 1:1 aspect ratio.
"upper_search_icon_width": (0x051224, bytes.fromhex("c744242010000000"), "mov dword ptr [rsp + 0x20], eax\n" + _NOP3 + "\nnop"),
}
UNWIND_PARENT_GRAPH_HEIGHT = (0x050870, 0x0509F0, 0x20295C)
UNWIND_PARENT_MAIN_LAYOUT = (0x0509F0, 0x0521BE, 0x202978)
UNWIND_PARENT_GRAPH_RENDERER = (0x05BE30, 0x05E8EF, 0x20348C)
UNWIND_PARENT_MAIN_INIT = (0x04C750, 0x04D62A, 0x202718)
UNWIND_PARENT_MAIN_CREATE = (0x04DCB0, 0x050868, 0x202818)
UNWIND_PARENT_ICON_CACHE_INIT = (0x018770, 0x018C01, 0x1FD968)
HIDPI_CHAINED_RANGES = (
("graph_height_prefix", 0x0050, 0x0055, 0x00, UNWIND_PARENT_GRAPH_HEIGHT),
("graph_height_body", 0x0055, 0x007B, 0x30, UNWIND_PARENT_GRAPH_HEIGHT),
("graph_height_suffix", 0x007B, 0x0084, 0x00, UNWIND_PARENT_GRAPH_HEIGHT),
("main_margins_body", 0x0090, 0x00BF, 0x30, UNWIND_PARENT_MAIN_LAYOUT),
("main_margins_suffix", 0x00BF, 0x00F9, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("graph_threshold_body", 0x0100, 0x0142, 0x40, UNWIND_PARENT_MAIN_LAYOUT),
("graph_threshold_suffix", 0x0142, 0x0153, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("graph_gap_body", 0x0160, 0x01A2, 0x40, UNWIND_PARENT_MAIN_LAYOUT),
("graph_gap_suffix", 0x01A2, 0x01B7, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("load1_width_body", 0x01C0, 0x01F1, 0x30, UNWIND_PARENT_MAIN_LAYOUT),
("load1_width_suffix", 0x01F1, 0x01FA, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("load1_x", 0x0200, 0x0211, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("load2_x_body", 0x0220, 0x0261, 0x40, UNWIND_PARENT_MAIN_LAYOUT),
("load2_x_suffix", 0x0261, 0x0277, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("card_rect_body", 0x02E0, 0x0345, 0x40, UNWIND_PARENT_GRAPH_RENDERER),
("card_rect_suffix", 0x0345, 0x0379, 0x00, UNWIND_PARENT_GRAPH_RENDERER),
("card_text_body", 0x0380, 0x03CA, 0x40, UNWIND_PARENT_GRAPH_RENDERER),
("card_text_suffix", 0x03CA, 0x03E1, 0x00, UNWIND_PARENT_GRAPH_RENDERER),
("system_parameters_body", 0x0420, 0x04A8, 0x60, UNWIND_PARENT_GRAPH_RENDERER),
("system_parameters_tail", 0x04A8, 0x04B1, 0x00, UNWIND_PARENT_GRAPH_RENDERER),
("fixed_load_reservation_1_body", 0x04E0, 0x054E, 0x40, UNWIND_PARENT_MAIN_LAYOUT),
("fixed_load_reservation_1_suffix", 0x054E, 0x0566, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("fixed_load_reservation_2_body", 0x0580, 0x05F6, 0x40, UNWIND_PARENT_MAIN_LAYOUT),
("fixed_load_reservation_2_suffix", 0x05F6, 0x0610, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("load2_gap_body", 0x0620, 0x065C, 0x30, UNWIND_PARENT_MAIN_LAYOUT),
("load2_gap_suffix", 0x065C, 0x066A, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("processor_gap_body", 0x0680, 0x06DC, 0x40, UNWIND_PARENT_MAIN_LAYOUT),
("processor_gap_suffix", 0x06DC, 0x06EC, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("upper_bar_height_body", 0x0760, 0x078C, 0x30, UNWIND_PARENT_MAIN_LAYOUT),
("upper_bar_height_suffix", 0x078C, 0x07AD, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("lower_bar_height_body", 0x07B0, 0x07E6, 0x30, UNWIND_PARENT_MAIN_LAYOUT),
("lower_bar_height_suffix", 0x07E6, 0x0816, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("main_system_parameters_body", 0x0820, 0x08A8, 0x60, UNWIND_PARENT_MAIN_INIT),
("main_system_parameters_tail", 0x08A8, 0x08B1, 0x00, UNWIND_PARENT_MAIN_INIT),
("upper_search_icon_square", 0x08C0, 0x08DF, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("lower_search_icon_square", 0x08E0, 0x08FD, 0x00, UNWIND_PARENT_MAIN_LAYOUT),
("upper_search_margin_body", 0x0930, 0x094C, 0x30, UNWIND_PARENT_MAIN_CREATE),
("upper_search_margin_tail", 0x094C, 0x0955, 0x00, UNWIND_PARENT_MAIN_CREATE),
("lower_search_margin_body", 0x0970, 0x098C, 0x30, UNWIND_PARENT_MAIN_CREATE),
("lower_search_margin_tail", 0x098C, 0x0995, 0x00, UNWIND_PARENT_MAIN_CREATE),
("process_icon_size_body", 0x0A00, 0x0A28, 0x30, UNWIND_PARENT_ICON_CACHE_INIT),
("process_icon_size_tail", 0x0A28, 0x0A31, 0x00, UNWIND_PARENT_ICON_CACHE_INIT),
)
@dataclass(frozen=True)
class AssemblyBlock:
"""One injected x86-64 routine assembled at a fixed offset.
Attributes:
name: Stable symbolic name used by tests and diagnostics.
offset: Byte offset from the beginning of the `.hidpi` section.
end_offset: Exclusive byte offset expected after assembly.
source: Intel-syntax x86-64 assembly. Lines may contain `#` comments.
"""
name: str
offset: int
end_offset: int
source: str
HIDPI_ASSEMBLY_BLOCKS = (
AssemblyBlock(
"get_dpi",
0x0000,
0x0043,
r"""
push rbx
sub rsp, 0x20
mov rbx, rcx
lea rcx, [rip + 0x3e1] # L"user32.dll"
call qword ptr [rip - 0xb08f5] # IAT GetModuleHandleW @ 0x1401cf720
test rax, rax
je fallback
mov rcx, rax
lea rdx, [rip + 0x3e2] # "GetDpiForWindow"
call qword ptr [rip - 0xb08f2] # IAT GetProcAddress @ 0x1401cf738
test rax, rax
je fallback
mov rcx, rbx
call rax # GetDpiForWindow(hwnd)
test eax, eax
jne done
fallback:
mov eax, 0x60 # USER_DEFAULT_SCREEN_DPI
done:
add rsp, 0x20
pop rbx
ret
""",
),
AssemblyBlock(
"graph_height",
0x0050,
0x0084,
r"""
cmp eax, ecx
cmovb eax, ecx
sub rsp, 0x30
mov dword ptr [rsp + 0x20], eax
mov rcx, qword ptr [r13 + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, dword ptr [rsp + 0x20]
mov r8d, 0x60
call qword ptr [rip - 0xb09f3] # IAT MulDiv @ 0x1401cf688
add rsp, 0x30
jmp 0x1400509e2 # original continuation
""",
),
AssemblyBlock(
"main_margins",
0x0090,
0x00F9,
r"""
sub rsp, 0x30
mov dword ptr [rsp + 0x20], eax
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x5
mov r8d, 0x60
call qword ptr [rip - 0xb0a2f] # MulDiv(5, dpi, 96)
mov r10d, eax
mov r11d, dword ptr [rsp + 0x20]
add rsp, 0x30
mov r9d, dword ptr [rsp + 0x44]
xor r14b, r14b
mov r8d, dword ptr [rsp + 0x40]
add r9d, r10d
mov edx, dword ptr [rsp + 0x48]
add r8d, r10d
sub edx, r10d
mov dword ptr [rdi + 0x9c4], r9d
mov r13d, r11d
mov dword ptr [rdi + 0x9c0], r8d
mov dword ptr [rdi + 0x9c8], edx
jmp 0x140050a69 # original continuation
""",
),
AssemblyBlock(
"graph_threshold",
0x0100,
0x0153,
r"""
sub rsp, 0x40
mov dword ptr [rsp + 0x20], ecx
mov dword ptr [rsp + 0x24], r8d
mov dword ptr [rsp + 0x28], r9d
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x64
mov r8d, 0x60
call qword ptr [rip - 0xb0aa9] # MulDiv(100, dpi, 96)
mov r10d, eax
mov ecx, dword ptr [rsp + 0x20]
mov r8d, dword ptr [rsp + 0x24]
mov r9d, dword ptr [rsp + 0x28]
add rsp, 0x40
mov eax, ecx
sub eax, r10d
cmp eax, r13d
jmp 0x140050ada # original continuation
""",
),
AssemblyBlock(
"graph_gap",
0x0160,
0x01B7,
r"""
sub rsp, 0x40
mov dword ptr [rsp + 0x20], ecx
mov dword ptr [rsp + 0x24], r8d
mov dword ptr [rsp + 0x28], r9d
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x5
mov r8d, 0x60
call qword ptr [rip - 0xb0b09] # MulDiv(5, dpi, 96)
mov r10d, eax
mov ecx, dword ptr [rsp + 0x20]
mov r8d, dword ptr [rsp + 0x24]
mov r9d, dword ptr [rsp + 0x28]
add rsp, 0x40
sub ecx, r13d
sub ecx, r10d
mov dword ptr [rdi + 0x9c8], ecx
jmp 0x140050ae8 # original continuation
""",
),
AssemblyBlock(
"load1_width",
0x01C0,
0x01FA,
r"""
sub rsp, 0x30
mov dword ptr [rsp + 0x20], ecx
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x3c
mov r8d, 0x60
call qword ptr [rip - 0xb0b5f] # MulDiv(60, dpi, 96)
mov r10d, eax
mov r9d, eax
mov ecx, dword ptr [rsp + 0x20]
add rsp, 0x30
jmp 0x140050b34 # original continuation
""",
),
AssemblyBlock(
"load1_x",
0x0200,
0x0211,
r"""
lea rcx, [rdi + 0x790] # Bitsum_LoadDisplay #1
mov edx, eax
sub edx, r10d
jmp 0x140050b58 # original continuation
""",
),
AssemblyBlock(
"load2_x",
0x0220,
0x0277,
r"""
sub rsp, 0x40
mov dword ptr [rsp + 0x20], eax
mov dword ptr [rsp + 0x24], ecx
mov dword ptr [rsp + 0x28], r8d
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x3c
mov r8d, 0x60
call qword ptr [rip - 0xb0bc8] # MulDiv(60, dpi, 96)
mov r10d, eax
mov r11d, dword ptr [rsp + 0x20]
mov ecx, dword ptr [rsp + 0x24]
mov r8d, dword ptr [rsp + 0x28]
add rsp, 0x40
mov edx, r11d
sub edx, r10d
mov rax, qword ptr [rdi + 0x810]
jmp 0x140050c3f # original continuation
""",
),
AssemblyBlock(
"card_rect",
0x02E0,
0x0379,
r"""
sub rsp, 0x40
mov rcx, qword ptr [r15 + 0x880]
call 0x140280000 # get_dpi(hwnd)
mov dword ptr [rsp + 0x20], eax
mov ecx, 0x5
mov edx, dword ptr [rsp + 0x20]
mov r8d, 0x60
call qword ptr [rip - 0xb0c81] # MulDiv(5, dpi, 96)
mov dword ptr [rsp + 0x24], eax
mov ecx, 0x16
mov edx, dword ptr [rsp + 0x20]
mov r8d, 0x60
call qword ptr [rip - 0xb0c9a] # MulDiv(22, dpi, 96)
mov dword ptr [rsp + 0x28], eax
mov ecx, 0xf
mov edx, dword ptr [rsp + 0x20]
mov r8d, 0x60
call qword ptr [rip - 0xb0cb3] # MulDiv(15, dpi, 96)
mov r10d, dword ptr [rsp + 0x24]
mov r11d, dword ptr [rsp + 0x28]
add rsp, 0x40
mov ecx, dword ptr [rsp + 0x60]
add ecx, r10d
mov edx, dword ptr [rsp + 0x64]
add edx, r10d
mov r8d, dword ptr [rbp - 0x40]
add r8d, r11d
add r8d, ecx
lea r13d, [r8 + rax]
mov r9d, dword ptr [rbp - 0x70]
imul r9d, dword ptr [rbp - 0x3c]
add r9d, r11d
add r9d, edx
jmp 0x14005d5c9 # original continuation
""",
),
AssemblyBlock(
"card_text",
0x0380,
0x03E1,
r"""
sub rsp, 0x40
mov rcx, qword ptr [r15 + 0x880]
call 0x140280000 # get_dpi(hwnd)
mov dword ptr [rsp + 0x20], eax
mov ecx, 0xd
mov edx, dword ptr [rsp + 0x20]
mov r8d, 0x60
call qword ptr [rip - 0xb0d21] # MulDiv(13, dpi, 96)
mov dword ptr [rsp + 0x24], eax
mov ecx, 0x11
mov edx, dword ptr [rsp + 0x20]
mov r8d, 0x60
call qword ptr [rip - 0xb0d3a] # MulDiv(17, dpi, 96)
mov r10d, dword ptr [rsp + 0x24]
mov r11d, eax
add rsp, 0x40
mov ebx, dword ptr [rsp + 0x64]
add ebx, r10d
mov esi, dword ptr [rsp + 0x60]
add esi, r11d
jmp 0x14005d62a # original continuation
""",
),
AssemblyBlock(
"system_parameters_info",
0x0420,
0x04B1,
r"""
sub rsp, 0x60
mov dword ptr [rsp + 0x30], ecx
mov qword ptr [rsp + 0x38], rdx
mov qword ptr [rsp + 0x40], r8
mov qword ptr [rsp + 0x48], r9
mov rcx, qword ptr [r15 + 0x880]
call 0x140280000 # get_dpi(hwnd)
mov dword ptr [rsp + 0x50], eax
lea rcx, [rip - 0x5e] # L"user32.dll"
call qword ptr [rip - 0xb0d34] # IAT GetModuleHandleW
test rax, rax
je fallback
mov rcx, rax
lea rdx, [rip + 0x4f] # "SystemParametersInfoForDpi"
call qword ptr [rip - 0xb0d31] # IAT GetProcAddress
test rax, rax
je fallback
mov ecx, dword ptr [rsp + 0x30]
mov rdx, qword ptr [rsp + 0x38]
mov r8, qword ptr [rsp + 0x40]
mov r9, qword ptr [rsp + 0x48]
mov r10d, dword ptr [rsp + 0x50]
mov dword ptr [rsp + 0x20], r10d # fifth arg: dpi
call rax # SystemParametersInfoForDpi(...)
jmp done
fallback:
mov ecx, dword ptr [rsp + 0x30]
mov rdx, qword ptr [rsp + 0x38]
mov r8, qword ptr [rsp + 0x40]
mov r9, qword ptr [rsp + 0x48]
call qword ptr [rip - 0xb07e0] # IAT SystemParametersInfoW
done:
add rsp, 0x60
jmp 0x14005bf4c # original continuation
""",
),
AssemblyBlock(
"fixed_load_reservation_1",
0x04E0,
0x0566,
r"""
sub rsp, 0x40
mov dword ptr [rsp + 0x20], edx
mov dword ptr [rsp + 0x24], r8d
mov dword ptr [rsp + 0x28], r9d
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x41 # 65 px = 60 px display + 5 px gap
mov r8d, 0x60
call qword ptr [rip - 0xb0e89] # MulDiv(65, dpi, 96)
mov dword ptr [rsp + 0x2c], eax
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0xa0 # original 100 + 60 minimum-space test
mov r8d, 0x60
call qword ptr [rip - 0xb0eac] # MulDiv(160, dpi, 96)
mov r11d, eax
mov r10d, dword ptr [rsp + 0x2c]
mov edx, dword ptr [rsp + 0x20]
mov r8d, dword ptr [rsp + 0x24]
mov r9d, dword ptr [rsp + 0x28]
add rsp, 0x40
cmp edx, r11d
jle no_room
sub edx, r10d
mov dword ptr [rdi + 0x9c8], edx
jmp done
no_room:
mov r14b, 1
done:
jmp 0x140050a88 # original continuation
""",
),
AssemblyBlock(
"fixed_load_reservation_2",
0x0580,
0x0610,
r"""
sub rsp, 0x40
mov dword ptr [rsp + 0x20], ecx
mov dword ptr [rsp + 0x24], edx
mov dword ptr [rsp + 0x28], r8d
mov dword ptr [rsp + 0x2c], r9d
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x41 # 65 px = 60 px display + 5 px gap
mov r8d, 0x60
call qword ptr [rip - 0xb0f2d] # MulDiv(65, dpi, 96)
mov dword ptr [rsp + 0x30], eax
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0xa0 # original 100 + 60 minimum-space test
mov r8d, 0x60
call qword ptr [rip - 0xb0f50] # MulDiv(160, dpi, 96)
mov r11d, eax
mov r10d, dword ptr [rsp + 0x30]
mov ecx, dword ptr [rsp + 0x20]
mov edx, dword ptr [rsp + 0x24]
mov r8d, dword ptr [rsp + 0x28]
mov r9d, dword ptr [rsp + 0x2c]
add rsp, 0x40
cmp edx, r11d
jle no_room
mov ecx, edx
sub ecx, r10d
mov dword ptr [rdi + 0x9c8], ecx
jmp done
no_room:
mov r12b, 1
done:
jmp 0x140050ac8 # original continuation
""",
),
AssemblyBlock(
"load2_gap",
0x0620,
0x066A,
r"""
sub rsp, 0x30
mov dword ptr [rsp + 0x20], ecx
mov dword ptr [rsp + 0x24], r8d
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x5
mov r8d, 0x60
call qword ptr [rip - 0xb0fc4] # MulDiv(5, dpi, 96)
mov r10d, eax
mov ecx, dword ptr [rsp + 0x20]
mov r8d, dword ptr [rsp + 0x24]
add rsp, 0x30
mov eax, dword ptr [rdi + 0xad0]
sub eax, r10d
jmp 0x140050c27 # original continuation
""",
),
AssemblyBlock(
"processor_gap",
0x0680,
0x06EC,
r"""
sub rsp, 0x40
mov qword ptr [rsp + 0x20], rcx
mov dword ptr [rsp + 0x28], edx
mov dword ptr [rsp + 0x2c], r8d
mov dword ptr [rsp + 0x30], r9d
sub eax, r8d
mov dword ptr [rsp + 0x34], eax
mov rcx, qword ptr [rdi + 0x870]
call 0x140280000 # get_dpi(hwnd)
mov edx, eax
mov ecx, 0x5
mov r8d, 0x60
call qword ptr [rip - 0xb1035] # MulDiv(5, dpi, 96)
mov r10d, eax
mov rcx, qword ptr [rsp + 0x20]
mov edx, dword ptr [rsp + 0x28]
mov r8d, dword ptr [rsp + 0x2c]
mov r9d, dword ptr [rsp + 0x30]
mov r11d, dword ptr [rsp + 0x34]
add rsp, 0x40
add edx, r10d
mov eax, r11d
mov dword ptr [rsp + 0x20], r11d
jmp 0x140050cfd # original continuation
""",
),
AssemblyBlock(
"bar_metrics",
0x0700,
0x0744,
# The two MulDiv calls are RIP-relative through the IAT (like get_dpi), not an absolute
# `mov rax, imm64`: the module is DYNAMIC_BASE, so an absolute IAT address would not be
# relocated at load and would fault under ASLR. The displacements are specific to this
# block being assembled at 0x140280700; the pinned test hash guards that placement.
r"""
sub rsp, 0x38
call 0x140280000 # get_dpi(hwnd in rcx) -> eax = dpi
mov dword ptr [rsp + 0x20], eax # save dpi
mov ecx, 0x20
mov edx, eax # dpi
mov r8d, 0x60
call qword ptr [rip - 0xb1098] # MulDiv(32, dpi, 96) via IAT 0x1401cf688
mov dword ptr [rsp + 0x24], eax # save scaled 32
mov ecx, 0x16
mov edx, dword ptr [rsp + 0x20] # dpi
mov r8d, 0x60
call qword ptr [rip - 0xb10b1] # MulDiv(22, dpi, 96) via IAT 0x1401cf688 -> eax
mov edx, eax # out: scaled 22
mov eax, dword ptr [rsp + 0x24] # out: scaled 32
add rsp, 0x38
ret
""",
),
AssemblyBlock(
"upper_bar_height",
0x0760,
0x07AD,
r"""
sub rsp, 0x30
mov dword ptr [rsp + 0x20], edx # live strip width
mov dword ptr [rsp + 0x24], r9d # live strip y
mov rcx, qword ptr [rdi + 0x8d0] # upper tab-strip HWND
call 0x140280700 # bar_metrics -> eax=scaled32, edx=scaled22
mov r10d, eax # scaled 32, live to the cy replacement at 0x050dce
mov r11d, edx # scaled 22
mov edx, dword ptr [rsp + 0x20] # restore width
mov r9d, dword ptr [rsp + 0x24] # restore y
add rsp, 0x30
mov dword ptr [rdi + 0xbac], r11d # Edit +0x970 cell bottom
mov dword ptr [rdi + 0xacc], r11d # Button +0x900 cell bottom (propagated to Edit Rules/Pause)
mov eax, r9d
add eax, r10d
mov dword ptr [rdi + 0xa6c], eax # cached strip bottom = y + scaled 32 (process-list top reads this)
test edx, edx # recreate the sign flag consumed by the original js
jmp 0x140050db2 # original continuation
""",
),
AssemblyBlock(
"lower_bar_height",
0x07B0,
0x0816,
r"""
sub rsp, 0x30
mov dword ptr [rsp + 0x20], ecx # live strip width
mov dword ptr [rsp + 0x24], r8d # live strip x
mov dword ptr [rsp + 0x28], r9d # live strip y
mov rcx, qword ptr [rdi + 0x8a8] # lower tab-strip HWND
call 0x140280700 # bar_metrics -> eax=scaled32, edx=scaled22
mov r10d, eax # scaled 32, live to the cy replacement at 0x0518e3
mov r11d, edx # scaled 22
mov ecx, dword ptr [rsp + 0x20] # restore width
mov r8d, dword ptr [rsp + 0x24] # restore x
mov r9d, dword ptr [rsp + 0x28] # restore y
add rsp, 0x30
mov dword ptr [rdi + 0xb4c], r11d # Edit +0x940 cell bottom
mov dword ptr [rdi + 0xb7c], r11d # View Log +0x958 cell bottom
mov dword ptr [rdi + 0xb9c], r11d # Insights +0x968 cell bottom
mov dword ptr [rdi + 0xa5c], r11d # Buy Now +0x8c8 cell bottom
mov eax, r9d
add eax, r10d
mov dword ptr [rdi + 0xa14], r9d # replay overwritten cached-y store
test ecx, ecx # recreate the sign flag consumed by the original js
jmp 0x1400518c7 # original cached-bottom store: mov [rdi+0xa1c],eax
""",
),
AssemblyBlock(
"main_system_parameters_info",
0x0820,
0x08B1,
# DPI-scale the main-UI font: redirect SystemParametersInfoW(SPI_GETNONCLIENTMETRICS) at
# 0x04c855 to SystemParametersInfoForDpi. The main window (app+0x870) does not exist yet at
# this init point, but the hidden notification window (app+0x878) is already live and is a
# valid GetDpiForWindow input. Same call shape as the graph `system_parameters_info` block;
# reuses the same user32.dll / SystemParametersInfoForDpi data strings. All references are
# rel32 / RIP-relative (ASLR-safe); the returned API pointer comes from GetProcAddress.
r"""
sub rsp, 0x60
mov dword ptr [rsp + 0x30], ecx
mov qword ptr [rsp + 0x38], rdx
mov qword ptr [rsp + 0x40], r8
mov qword ptr [rsp + 0x48], r9
mov rcx, qword ptr [rbx + 0x878] # live hidden notification-window HWND
call 0x140280000 # get_dpi(hwnd)
mov dword ptr [rsp + 0x50], eax
lea rcx, [rip - 0x45e] # L"user32.dll" at .hidpi+0x03f0
call qword ptr [rip - 0xb1134] # IAT GetModuleHandleW, RVA 0x1cf720
test rax, rax
je fallback
mov rcx, rax
lea rdx, [rip - 0x3b1] # "SystemParametersInfoForDpi" at .hidpi+0x04b2
call qword ptr [rip - 0xb1131] # IAT GetProcAddress, RVA 0x1cf738
test rax, rax
je fallback
mov ecx, dword ptr [rsp + 0x30]
mov rdx, qword ptr [rsp + 0x38]
mov r8, qword ptr [rsp + 0x40]
mov r9, qword ptr [rsp + 0x48]
mov r10d, dword ptr [rsp + 0x50]
mov dword ptr [rsp + 0x20], r10d # fifth arg: dpi
call rax # SystemParametersInfoForDpi(...)
jmp done
fallback:
mov ecx, dword ptr [rsp + 0x30]
mov rdx, qword ptr [rsp + 0x38]
mov r8, qword ptr [rsp + 0x40]
mov r9, qword ptr [rsp + 0x48]
call qword ptr [rip - 0xb0be0] # IAT SystemParametersInfoW, RVA 0x1cfcc8
done:
add rsp, 0x60
jmp 0x14004c85b # original continuation
""",
),
AssemblyBlock(
"upper_search_icon_square",
0x08C0,
0x08DF,
# Make the upper search-glass Static square. At the hook, eax=bottom-2, r9d=top+2; the
# original `sub eax,r9d` yields the icon side (client height - 4). Recompute the right-aligned
# x = (right-1) - side using the cached right-1 (app+0xc18), leaving it in r8d (the SetWindowPos
# X register) and updating the cached x (app+0xc10). The paired in-place width patch at
# 0x051224 stores this side as cx, so cx == cy and SS_REALSIZECONTROL cannot distort the icon.
r"""
sub eax, r9d # side = (bottom-2) - (top+2)
js 0x140051232 # preserve the original invalid-height exit
mov r8d, dword ptr [rdi + 0xc18] # cached right-1
sub r8d, eax # x = (right-1) - side
mov dword ptr [rdi + 0xc10], r8d # keep cached x consistent
jmp 0x140051205 # resume at the original x/y validity tests
""",
),
AssemblyBlock(
"lower_search_icon_square",
0x08E0,
0x08FD,
# Lower search-glass Static, same square geometry. The hook replaces `sub eax,r9d; sub ecx,r8d`
# (cy and the fixed-16 cx). Compute side=cy in eax, x=(right-1)-side in r8d from cached right-1
# (app+0xc28), set cx=side in ecx, and recreate the sign flag the original js at 0x0519ed reads.
r"""
sub eax, r9d # side = (bottom-2) - (top+2)
mov r8d, dword ptr [rdi + 0xc28] # cached right-1
sub r8d, eax # x = (right-1) - side
mov dword ptr [rdi + 0xc20], r8d # keep cached x consistent
mov ecx, eax # cx = side (== cy)
test ecx, ecx # recreate the sign flag consumed by the original js
jmp 0x1400519ed # original validity tests + SetWindowPos
""",
),
AssemblyBlock(
"search_margin",
0x0900,
0x0924,
# Shared helper for the two search-Edit right margins. Input rcx = Edit HWND; returns in eax
# MulDiv(22, dpi, 96) - 6, which matches the DPI-scaled search-icon width (client_height - 4
# for a 1px-border Edit). RIP-relative MulDiv (ASLR-safe); displacement is specific to 0x0900.
r"""
sub rsp, 0x28
call 0x140280000 # get_dpi(hwnd in rcx) -> eax = dpi
mov edx, eax # dpi
mov ecx, 0x16 # 22 (original Edit cell height / margin base)
mov r8d, 0x60
call qword ptr [rip - 0xb1294] # MulDiv(22, dpi, 96) via IAT 0x1401cf688
sub eax, 6 # scaled icon width = scaled cell height - border(2) - 4
add rsp, 0x28
ret
""",
),
AssemblyBlock(
"upper_search_margin",
0x0930,
0x0955,
# Scale the upper search Edit's EM_SETMARGINS right margin to match the DPI-scaled icon.
# Replaces `mov r9d, 0x100000` (MAKELONG(0,16)); rax holds the just-created Edit HWND and must
# survive for the following `mov rcx,rax`. Build lParam = MAKELONG(0, scaled_margin) in r9d.
r"""
sub rsp, 0x30
mov qword ptr [rsp + 0x20], rax # save Edit HWND (consumed by the original mov rcx,rax)
mov rcx, rax
call 0x140280900 # search_margin(hwnd) -> eax = scaled right margin
shl eax, 16 # MAKELONG(0, margin)
mov r9d, eax # lParam
mov rax, qword ptr [rsp + 0x20] # restore Edit HWND
add rsp, 0x30
jmp 0x14004e17d # original continuation (mov r8d,2 ; ...)
""",
),
AssemblyBlock(
"lower_search_margin",
0x0970,
0x0995,
# Lower search Edit right margin, same as upper; continuation is the lower site's next insn.
r"""
sub rsp, 0x30
mov qword ptr [rsp + 0x20], rax # save Edit HWND
mov rcx, rax
call 0x140280900 # search_margin(hwnd) -> eax = scaled right margin
shl eax, 16 # MAKELONG(0, margin)
mov r9d, eax # lParam
mov rax, qword ptr [rsp + 0x20] # restore Edit HWND
add rsp, 0x30
jmp 0x14004e0f0 # original continuation (mov r8d,2 ; ...)
""",
),
AssemblyBlock(
"get_system_dpi",
0x09A0,
0x09DB,
# `get_dpi` for callers that have no window: resolves GetDpiForSystem (Windows 10 1607+)
# and falls back to 96 when it is unavailable or fails. Takes no argument, so unlike
# `get_dpi` it needs no nonvolatile register and its whole prologue is one stack alloc.
r"""
sub rsp, 0x28
lea rcx, [rip - 0x5bb] # L"user32.dll" at .hidpi+0x03f0
call qword ptr [rip - 0xb1291] # IAT GetModuleHandleW, RVA 0x1cf720
test rax, rax
je fallback
mov rcx, rax
lea rdx, [rip - 0x4f0] # "GetDpiForSystem" at .hidpi+0x04d0
call qword ptr [rip - 0xb128e] # IAT GetProcAddress, RVA 0x1cf738
test rax, rax
je fallback
call rax # GetDpiForSystem()
test eax, eax
jne done
fallback:
mov eax, 0x60 # USER_DEFAULT_SCREEN_DPI
done:
add rsp, 0x28
ret
""",
),
AssemblyBlock(
"process_icon_size",
0x0A00,
0x0A31,
# DPI-scale the process-list icons: the shared shell-icon image list is created 16x16 at
# 0x018836, so the ListViews draw every process icon at its 96-DPI size. Replace the fixed
# `cy` with MulDiv(16, dpi, 96) and return to the original `mov ecx,edx`, which mirrors it
# into `cx`. The size is fixed for the image list's lifetime and this runs during app-object
# construction, before any window exists, so the system DPI is both the only DPI available
# and the only one that stays meaningful. A 16 px floor keeps a failed MulDiv (-1) or an
# implausible sub-96 DPI from producing an image list that ImageList_Create would reject.
r"""
sub rsp, 0x30
call 0x1402809a0 # get_system_dpi() -> eax = dpi
mov edx, eax
mov ecx, 0x10 # 16 (original shell small-icon side)
mov r8d, 0x60
call qword ptr [rip - 0xb1394] # MulDiv(16, dpi, 96) via IAT 0x1401cf688
cmp eax, 0x10
jge scaled
mov eax, 0x10
scaled:
mov edx, eax # cy = scaled icon side
add rsp, 0x30
jmp 0x14001883b # original continuation (mov ecx,edx -> cx = cy)
""",
),
)
@dataclass(frozen=True)
class Section:
"""A PE section required for RVA/file-offset translation.
Attributes:
name: Section name with trailing NUL bytes removed.
virtual_size: Mapped size requested by the PE section header.
virtual_address: RVA at which the section is mapped.
raw_size: File-backed size of the section.
raw_offset: File offset of the section's first byte.
"""
name: str
virtual_size: int
virtual_address: int
raw_size: int
raw_offset: int
@dataclass(frozen=True)
class PeLayout:
"""PE header offsets and alignment values used by this patch.
Attributes:
pe_offset: File offset of the PE signature.
optional_offset: File offset of the PE32+ optional header.
section_table_offset: File offset of the section-header array.
section_count: Number of original PE sections.
section_alignment: In-memory section alignment.
file_alignment: On-disk section alignment.
image_base: Preferred image base.
size_of_headers: File-backed PE-header size.
exception_directory_offset: File offset of the exception data-directory entry.
security_directory_offset: File offset of the Authenticode data-directory entry.
sections: Parsed original section headers.
"""
pe_offset: int
optional_offset: int
section_table_offset: int
section_count: int
section_alignment: int
file_alignment: int
image_base: int
size_of_headers: int
exception_directory_offset: int
security_directory_offset: int
sections: tuple[Section, ...]
BINUTILS_TOOLS = ("as", "ld", "objcopy")
ELF_MAGIC = b"\x7fELF"
def strip_assembly_comments(source: str) -> str:
"""Remove human-readable `#` comments before assembling.
Args:
source: Intel-syntax assembly source that may contain trailing `#` comments.
Returns:
Assembly source containing only labels and instructions.
"""
lines = []
for source_line in source.splitlines():
instruction = source_line.split("#", 1)[0].strip()
if instruction:
lines.append(instruction)
return "\n".join(lines)
def run_binutils_step(command: list[str]) -> None:
"""Run one GNU binutils command, raising with its diagnostics on failure.
Args:
command: Full argument vector to execute; `command[0]` is the tool name.
Raises:
ValueError: If the tool exits non-zero, carrying its captured stderr.
"""
result = subprocess.run(command, capture_output=True, text=True)
if result.returncode != 0:
raise ValueError(f"{command[0]} failed: {result.stderr.strip()}")
def assemble_x86_64(source: str, address: int) -> bytes:
"""Assemble Intel-syntax x86-64 source at a fixed virtual address with GNU binutils.
The source is assembled with `as`, located at `address` with `ld` so absolute branch
targets resolve to correct rel32 displacements, and reduced to raw machine code with
`objcopy`. This reproduces the position-dependent encoding expected at `address`.
Args:
source: Assembly source. Local labels are allowed; `#` comments are stripped first.
address: Virtual address of the first emitted instruction, used for relative branches.
Returns:
Exact machine-code bytes of the assembled `.text` section.
"""
assert 0 <= address < 1 << 64
missing = [tool for tool in BINUTILS_TOOLS if shutil.which(tool) is None]
if missing:
raise RuntimeError(
f"Cannot assemble x86-64: missing GNU binutils tool(s) {', '.join(missing)} on PATH. "
"Install binutils (for example, add `binutils` to your Nix environment)."
)
program = ".intel_syntax noprefix\n.text\n" + strip_assembly_comments(source) + "\n"
with tempfile.TemporaryDirectory() as directory:
work = Path(directory)
source_path, object_path = work / "block.s", work / "block.o"
linked_path, binary_path = work / "block.elf", work / "block.bin"
source_path.write_text(program)
run_binutils_step(["as", "--64", "-o", str(object_path), str(source_path)])
if object_path.read_bytes()[:4] != ELF_MAGIC:
raise RuntimeError(
"This patcher requires an ELF-targeted GNU binutils: absolute branch "