-
Notifications
You must be signed in to change notification settings - Fork 2
/
ERProcess.cs
2206 lines (1956 loc) · 101 KB
/
ERProcess.cs
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Reflection;
using System.IO;
using MiscUtils;
namespace EldenRingTool
{
public class ERProcess : IDisposable
{
public const uint PROCESS_ALL_ACCESS = 2035711;
private Process _targetProcess = null;
private IntPtr _targetProcessHandle = IntPtr.Zero;
public IntPtr erBase = IntPtr.Zero;
int erSize = 0;
protected bool disposed = false;
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(uint dwDesiredAcess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int iSize, ref int lpNumberOfBytesRead);
[DllImport("ntdll.dll")]
static extern int NtReadVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, byte[] Buffer, UInt32 NumberOfBytesToRead, ref UInt32 NumberOfBytesRead); //consider replacing ReadProcessMemory with this
[DllImport("kernel32.dll")]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int iSize, int lpNumberOfBytesWritten);
[DllImport("ntdll.dll")]
static extern int NtWriteVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, byte[] Buffer, UInt32 NumberOfBytesToWrite, ref UInt32 NumberOfBytesWritten); //faster alternative to WriteProcessMemory
[DllImport("kernel32.dll")]
private static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);
public uint RunThread(IntPtr address, uint timeout = 0xFFFFFFFF, IntPtr? param = null)
{
var thread = CreateRemoteThread(_targetProcessHandle, IntPtr.Zero, 0, address, param ?? IntPtr.Zero, 0, IntPtr.Zero);
var ret = WaitForSingleObject(thread, timeout);
CloseHandle(thread); //return value unimportant
return ret;
}
Thread freezeThread = null;
bool _running = true;
public ERProcess()
{
findAttach();
findBaseAddress();
aobScan();
freezeThread = new Thread(() => { freezeFunc(); });
freezeThread.Start();
}
public void Dispose()
{
if (!disposed)
{
_running = false;
if (freezeThread != null)
{
freezeThread.Abort();
freezeThread = null;
}
detach();
disposed = true;
}
}
~ERProcess()
{
Dispose();
}
const string erProNameMain = "eldenring";
const string erProNameAlt = "start_protected_game"; //alternate EAC bypass trick is just an exe rename
string erProName = "";
public static bool checkGameRunning()
{
var processes = Process.GetProcesses();
foreach (var process in processes)
{
if (process.ProcessName.ToLower().Equals(erProNameMain.ToLower()) && !process.HasExited)
{
return true;
}
if (process.ProcessName.ToLower().Equals(erProNameAlt.ToLower()) && !process.HasExited)
{
return true;
}
}
return false;
}
private void findAttach()
{
var processes = Process.GetProcesses();
foreach (var process in processes)
{
if (process.ProcessName.ToLower().Equals(erProNameMain.ToLower()) && !process.HasExited)
{
attach(process);
erProName = erProNameMain;
return;
}
if (process.ProcessName.ToLower().Equals(erProNameAlt.ToLower()) && !process.HasExited)
{
attach(process);
erProName = erProNameAlt;
return;
}
}
throw new Exception("Elden Ring not running");
}
private void attach(Process proc)
{
if (_targetProcessHandle == IntPtr.Zero)
{
_targetProcess = proc;
_targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, bInheritHandle: false, _targetProcess.Id);
if (_targetProcessHandle == IntPtr.Zero)
{
throw new Exception("Attach failed");
}
}
else
{
#if NO_WPF
Console.WriteLine("Already attached");
#else
System.Windows.MessageBox.Show("Already attached");
#endif
}
}
private void findBaseAddress()
{
try
{
foreach (var module in _targetProcess.Modules)
{
var processModule = module as ProcessModule;
//Utils.debugWrite(processModule.ModuleName);
var modNameLower = processModule.ModuleName.ToLower();
if (modNameLower == erProName + ".exe")
{
erBase = processModule.BaseAddress;
erSize = processModule.ModuleMemorySize;
}
}
}
catch (Exception ex) { Utils.debugWrite(ex.ToString()); }
if (erBase == IntPtr.Zero)
{//this is pretty unlikely
throw new Exception("Couldn't find ER base address");
}
}
private void detach()
{
if (!(_targetProcessHandle == IntPtr.Zero))
{
_targetProcess = null;
try
{
CloseHandle(_targetProcessHandle);
_targetProcessHandle = IntPtr.Zero;
}
catch (Exception ex)
{
Utils.debugWrite(ex.ToString());
}
}
}
//all read/write funcs just fail silently, except this one:
public bool ReadTest(IntPtr addr)
{
var array = new byte[1];
var lpNumberOfBytesRead = 1;
return ReadProcessMemory(_targetProcessHandle, addr, array, 1, ref lpNumberOfBytesRead) && lpNumberOfBytesRead == 1;
}
public int ReadInt32(IntPtr addr)
{
var bytes = ReadBytes(addr, 4);
return BitConverter.ToInt32(bytes, 0);
}
public long ReadInt64(IntPtr addr)
{
var bytes = ReadBytes(addr, 8);
return BitConverter.ToInt64(bytes, 0);
}
public byte ReadUInt8(IntPtr addr)
{
var bytes = ReadBytes(addr, 1);
return bytes[0];
}
public uint ReadUInt32(IntPtr addr)
{
var bytes = ReadBytes(addr, 4);
return BitConverter.ToUInt32(bytes, 0);
}
public ulong ReadUInt64(IntPtr addr)
{
var bytes = ReadBytes(addr, 8);
return BitConverter.ToUInt64(bytes, 0);
}
public float ReadFloat(IntPtr addr)
{
var bytes = ReadBytes(addr, 4);
return BitConverter.ToSingle(bytes, 0);
}
public double ReadDouble(IntPtr addr)
{
var bytes = ReadBytes(addr, 8);
return BitConverter.ToDouble(bytes, 0);
}
public byte[] ReadBytes(IntPtr addr, int size)
{
var array = new byte[size];
var targetProcessHandle = _targetProcessHandle;
var lpNumberOfBytesRead = 1;
ReadProcessMemory(targetProcessHandle, addr, array, size, ref lpNumberOfBytesRead);
return array;
}
public void WriteInt32(IntPtr addr, int val)
{
WriteBytes(addr, BitConverter.GetBytes(val));
}
public void WriteUInt32(IntPtr addr, uint val)
{
WriteBytes(addr, BitConverter.GetBytes(val));
}
public void WriteFloat(IntPtr addr, float val)
{
WriteBytes(addr, BitConverter.GetBytes(val));
}
public void WriteUInt8(IntPtr addr, byte val)
{
var bytes = new byte[] { val };
WriteBytes(addr, bytes);
}
public void WriteBytes(IntPtr addr, byte[] val, bool useNewWrite = true)
{
if (useNewWrite)
{
uint written = 0;
NtWriteVirtualMemory(_targetProcessHandle, addr, val, (uint)val.Length, ref written); //MUCH faster, <1ms
}
else
{
WriteProcessMemory(_targetProcessHandle, addr, val, val.Length, 0); //can take as long as 15ms!
}
}
public enum DebugOpts
{
COL_MESH_A, COL_MESH_B,
CHARACTER_MESH,
HITBOX_VIEW_A, HITBOX_VIEW_B,
DISABLE_MOST_RENDER,
DISABLE_MAP,
DISABLE_TREES,
DISABLE_ROCKS,
DISABLE_DISTANT_MAP,
DISABLE_CHARACTER,
DISABLE_GRASS,
NO_DEATH, ALL_CHR_NO_DEATH,
INSTANT_QUITOUT,
ONE_HP,
MAX_HP, RUNE_ARC,
TARGET_HP,
DISABLE_AI, NO_STAM, NO_FP, NO_GOODS, NO_ARROWS, ONE_SHOT,
NO_GRAVITY_ALTERNATE, NO_MAP_COLLISION, NO_GRAVITY,
TORRENT_NO_DEATH, TORRENT_NO_GRAV_ALT, TORRENT_NO_MAP_COLL, TORRENT_NO_GRAV,
TOP_DEBUG_MENU,
POISE_VIEW,
TARGETING_VIEW,
EVENT_DRAW, EVENT_STOP,
FREE_CAM,
DISABLE_STEAM_INPUT_ENUM, DISABLE_STEAM_ACHIVEMENTS,
}
public enum TargetInfo
{
HP, MAX_HP,
POISE, MAX_POISE, POISE_TIMER,
POISON, POISON_MAX, //resists must match memory order
ROT, ROT_MAX,
BLEED, BLEED_MAX,
BLIGHT, BLIGHT_MAX,
FROST, FROST_MAX,
SLEEP, SLEEP_MAX,
MADNESS, MADNESS_MAX,
}
const long SANE_MINIMUM = 0x700000000000;
const long SANE_MAXIMUM = 0x800000000000; //TODO: refine. much lower addresses may be valid in some cases.
//addresses/offsets - all are for 1.06 but will be replaced by AOB scanning at run time
int worldChrManOff = 0x3C310B8; //pointer to CS::WorldChrManImp
int hitboxBase = 0x3C31488; //currently no RTTI name for this.
uint hitboxOffset = 0xA0;
int groupMaskBase = 0x3A1E830;//most render
//const int groupMaskMap = 0x3A1E831;
int groupMaskTrees = 0x3A1E839;
int meshesOff = 0x3C3518C;//static addresses again
int quitoutBase = 0x3C349D8; //CS::GameMan.
int logoScreenBase = 0xA9807D;
int codeCavePtrLoc = 0x25450;
int targetHookLoc = 0x6F89A2;
int targetHookOffset = 0x6A0;
int codeCaveCodeLoc { get { return codeCavePtrLoc + 0x10; } }
List<int> menuOffsets = new List<int>();
int noGoodsConsume = 0x3C312B3;
int noAiUpdate = 0x3C312BF;
int chrDbg = 0x3C312A8;//should be close to misc debug
int newMenuSystem = 0x3C369A0;//CS::CSMenuManImp //irrelvant now with top debug gone
int fontDrawOffset = 0x25EAF20; //defaults to 0x48. need 0xC3 for in-game poise viewer.
int DbgEventManOff = 0x3C330C0; //no name. static addresses.
int EventPatchLoc1 = 0xDC8670; //32 C0 C3 (next 3 vary with patch, eg. CC BF 60 in 1.03.2, cc 7b 83 in 1.04.0)
int EventPatchLoc2 = 0xDC8650; //32 C0 C3 (next 3 vary with patch, eg. CC E3 A2 in 1.03.2, 90 49 8b in 1.04.0)
int FieldAreaOff = 0x3C34298;//CS::FieldArea
//int freeCamPatchLoc = 0x415305;
int freeCamPatchLocAlt = 0xDB8A00; //1st addr after call (a jmp)
int freeCamPlayerControlPatchLoc = 0x664EE6;
int mapOpenInCombatOff = 0x7CB4D3;
int mapStayOpenInCombatOff = 0x979AE7;
//DbgGetForceActIdx. patch changes it to use the addr from DbgSetLastActIdx
int enemyRepeatActionOff = 0x4F22456;
int zeroCaveOffset = 0x28E3E00; //zeroes at the end of the program
int warpFirstCallOffset = 0x5DDE30;
int warpSecondCallOffset = 0x65E260;
int itemSpawnStart { get { return zeroCaveOffset + 0x100; } } //warp is only 0x3E big but just go for a round number
int mapItemManOff = 0x3C32B20;
int itemSpawnCall = 0x5539E0;
int itemSpawnData { get { return itemSpawnStart + 0x30; } }
int usrInputMgrImplOff = 0x45075C8;//DLUID::DLUserInputManagerImpl<DLKR::DLMultiThreadingPolicy> //RTTI should find it
uint usrInputMgrImpSteamInputFlagOff = 0x88b; //in 1.05, the func checking the flag is at +1E7D75F
//above originally found by putting breakpoints in user32 device enum funcs, which get called by dinput8, which gets called by the steam overlay dll, which gets called by elden ring, then triggering the stutter.
int trophyImpOffset = 0x4453838; //CS::CSTrophyImp
int gameDataMan = 0;
int csFlipperOff = 0x4453E98; //lots of interesting stuff here. frame times, fps, etc.
int gameSpeedOffset = 0x2D4;
int frameTimeTargetOffset = 0xE44832; //value for 2.01
int upgradeRuneCostOff = 0x765241;
int upgradeMatCostOff = 0x8417FC;
int soundDrawPatchLoc = 0x33bfd6;
int allTargetingDebugDraw = 0x3C2D43A;
int allChrNoDeath = 0x3C312BA;
int torrentDisabledCheckOne = 0xC730EA;
int torrentDisabledCheckTwo = 0x6E7CDF;
//both of these are equivalent. not sure why different tables use different ones. perhaps one is more likely to survive future patches.
//uint worldChrManPlayerOff1 = 0xB658; //points to a pointer to CS::PlayerIns. constant not found...? likely less reliable.
uint worldChrManPlayerOff2 = 0x18468; //points directly to CS::PlayerIns, commonly found after refs to CS::WorldChrManImp
//uint worldChrManTorrentOff = 0x18378; //changed in 1.06. need AOB for this. cannot find the constant in the game however so it likely has a different way to get to torrent.
//above method works in 1.07, base ptr is 0x1e1a0: 0x1ded8 + 59*8
//uint worldChrManTorrentOffAlt = 0xb6f0; //works up to 1.06. broken in 1.07.
uint noDeathOffset = 0x19B; //was 197 in an older patch
uint mapIDinPlayerIns = 0x6C0;
uint chrSetOffset = 0x1DED8; //1.07 addr, was stable previously
uint pgDataOffset = 0x570; //patch stable so far
uint torrentIDOffset = 0x930; //also appears patch stable
int scadOffset = 0; //should be 0xfc or close to it
public bool exeSupportsDlc() { return scadOffset > 0 && scadOffset < 0x10000; } //if no plausible value is found then the exe is too old
int musicMuteLoc = 0;
int csEventFlagMan = 0;
int triggerNGPlusOffset = 0; //in GameMan
//scanning for above addresses
void aobScan()
{//see https://github.com/kh0nsu/FromAobScan
var sw = new Stopwatch();
sw.Start();
var scanner = new AOBScanner(_targetProcessHandle, erBase, erSize);
worldChrManOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "E8 ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 4C 8B A8 ?? ?? ?? ?? 4D 85 ED 0F 84 ?? ?? ?? ??", "CS::WorldChrManImp", 5 + 3, 5 + 3 + 4, startIndex: 1800000);
worldChrManPlayerOff2 = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "E8 ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 4C 8B A8 ?? ?? ?? ?? 4D 85 ED 0F 84 ?? ?? ?? ??", "CS::WorldChrManImp offset", 5 + 7 + 3, startIndex: 1800000);
hitboxBase = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B0D ???????? E8 ???????? F3 0F1057 ?? 48 8BCB 0FB693 ????0000 E8 ???????? 48 8BCB E8 ???????? 48 8B0D ????????", "hitboxBase", 1 + 2, 1 + 2 + 4, startIndex: 10900000);
hitboxOffset = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "80B9 ????0000 00 48 8BF9 BE FFFFFFFF 74 ?? 48 8B19 48 85DB 74 ??", "hitboxOffset", 2, startIndex: 5200000);
groupMaskBase = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "803D ???????? 00 0F1000 0F1145 D0 0F84 ????0000 803D ???????? 00 0F85 ????0000 803D ???????? 00 B3 01 C605 ???????? 00 74 ?? BA 01000000", "groupMaskBase", 2, 2 + 4 + 1, startIndex: 6500000);
groupMaskTrees = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "803D ???????? 00 74 1f ba 05000000", "groupMaskTrees", 2, 7, startIndex: 6500000);
meshesOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "0F B6 25 ?? ?? ?? ?? 44 0F B6 3D ?? ?? ?? ?? E8 ?? ?? ?? ?? 0F B6 F8", "meshesOff", 3, 7, startIndex: 7100000);
quitoutBase = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 05 ?? ?? ?? ?? 0F B6 40 10 C3", "CS::GameMan", 3, 7, startIndex: 6500000);
logoScreenBase = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "74 53 48 8B05 ???????? 48 85C0 75 ?? 48 8D0D ???????? E8 ???????? 4C 8BC8 4C 8D05 ???????? BA ????0000 48 8D0D ???????? E8 ????????", "logoScreenBase", startIndex: 10900000);
var thl = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B48 ?? 49 898D ????0000 49 8BCE E8 ???????? 84C0 75 ?? 49 8B5E ?? 48 8D4D ?? E8 ????????", "targetHookLoc", startIndex: 7100000);
if (thl > 0) { targetHookLoc = thl; } //should do this with all patches really, as they will fail the scan if already patched
var tho = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B48 ?? 49 898D ????0000 49 8BCE E8 ???????? 84C0 75 ?? 49 8B5E ?? 48 8D4D ?? E8 ????????", "targetHookLoc offset", 1 + 2 + 1 + 1 + 2, startIndex: 7100000);
if (tho > 0) { targetHookOffset = tho; }
noGoodsConsume = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "803D ???????? 00 75 05 45 33C0 EB 03 41 B0 01 48 8D8424 ??000000 48 898424 ??000000 41 8B06 898424 ??000000 48 8D8E ????0000 48 8D9424 ??000000 E8 ???????? 0FB6D0", "noGoodsConsume", 2, 2 + 4 + 1, startIndex: 6300000);
noAiUpdate = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "0FB63D ???????? 48 85C0 75 2E", "noAIUpdate", 3, 7, startIndex: 3800000);
chrDbg = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 05 ?? ?? ?? ?? 41 83 FF 02 ?? ?? 48 85 C0", "chrDbg", 3, 7, startIndex: 5000000);
newMenuSystem = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B05 ???????? 33DB 48 897424 ?? 48 8BF1 895C24 ?? 48 85C0 75 ??", "CSMenuManImp", 3, 7, startIndex: 7400000);
fontDrawOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 895C24 10 55 56 57 41 54 41 55 41 56 41 57 48 8D6C24 ?? 48 81EC ???????? 0F29B424 ????????", "fontDrawOffset", startIndex: 39000000);
DbgEventManOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 0D ???????? 48 85 C9 ???? 83 CF 20", "DbgEventManOff", 3, 7, startIndex: 10000000);
EventPatchLoc1 = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "E8 ???????? 84C0 74 06 E8 ???????? 90 48 8BC7", "Event patch func 1", 1, 1 + 4, startIndex: 5500000);
EventPatchLoc2 = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "E8 ???????? 84C0 74 06 E8 ???????? 90 48 8BC7", "Event patch func 2", 1 + 4 + 2 + 1 + 1 + 1, 1 + 4 + 2 + 1 + 1 + 1 + 4, startIndex: 5500000);
FieldAreaOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 0D ?? ?? ?? ?? 48 ?? ?? ?? 44 0F B6 61 ?? E8 ?? ?? ?? ?? 48 63 87 ?? ?? ?? ?? 48 ?? ?? ?? 48 85 C0", "CS::FieldArea", 3, 7, startIndex: 6400000);
freeCamPatchLocAlt = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "EB 05 E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 75 0C", "free cam patch loc, 1st called addr", readoffset32: 1 + 1 + 1 + 4 + 1, 1 + 1 + 1 + 4 + 1 + 4, startIndex: 7000000);
freeCamPlayerControlPatchLoc = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "8B 83 ?? 00 00 00 FF C8 83 F8 01", "free cam player control patch loc", startIndex: 6500000);
if (freeCamPlayerControlPatchLoc < 0)
{
freeCamPlayerControlPatchLoc = scanner.findAddr(scanner.sectionTwo, scanner.textTwoAddr, "8B 83 ?? 00 00 00 FF C8 83 F8 01", "free cam player control patch loc (2nd section)");
if (freeCamPlayerControlPatchLoc < 0)
{//ugh
freeCamPlayerControlPatchLoc = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "8B 93 c8 00 00 00 85 d2 0f ?? ?? ?? 00 00", "free cam player control patch loc 1.12", startIndex: 6500000);
}
}
mapOpenInCombatOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "E8 ???????? 84C0 74 ?? C745 ?? ???????? C745 ?? ???????? C745 ?? ???????? 48 8D05 ????????", "map open in combat", startIndex: 8000000);
mapStayOpenInCombatOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "E8 ?? ?? ?? ?? 84 C0 75 ?? 38 83 ?? ?? 00 00 75 ?? 83 ?? fe 89", "map stay open in combat", startIndex: 9800000);
enemyRepeatActionOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 41 08 0F BE 80 ?? E9 00 00", "enemyRepeatActionOff (1st sect)", justOffset: 7);
if (enemyRepeatActionOff < 0)
{
enemyRepeatActionOff = scanner.findAddr(scanner.sectionTwo, scanner.textTwoAddr, "48 8B 41 08 0F BE 80 ?? E9 00 00", "enemyRepeatActionOff (2nd sect)", justOffset: 7);
}
warpFirstCallOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 83EC 48 48 C74424 28 FEFFFFFF E8 ?? ?? ?? ?? 48", "warp call one", startIndex: 6000000);
warpSecondCallOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "488B05 ???????? 8988 ??0C0000 C3", "warp call two", startIndex: 6500000);
usrInputMgrImplOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8905 ???????? 48 8B05 ???????? E8 ???????? 4C 8B08 41 B8 ??000000 48 8D15 ????0000 48 8BC8 41 FF51 ?? 48 8B1D ????????", "usrInputMgrImplOff", 3, 7, startIndex: 1000000);
usrInputMgrImpSteamInputFlagOff = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "80B9 ????0000 00 48 8B5C24 40", "steam input flag check", 2, startIndex: 31500000);
csFlipperOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 05 ?? ?? ?? ?? F3 0F 10 88 ?? ?? ?? ?? F3 0F", "csFlipperOff", 3, 7, startIndex: 13800000);
gameSpeedOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 05 ?? ?? ?? ?? F3 0F 10 88 ?? ?? ?? ?? F3 0F", "csFlipperOff gameSpeedOffset", 7 + 4, startIndex: 13800000);
frameTimeTargetOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "8973 ?? C743 ?? ??88883C EB ?? 8973 ??", "frame time target (1/60.0f)", justOffset: 6, startIndex: 13000000);
noDeathOffset = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "4883EC20F681????000001488bd97408", "no-death offset in CSChrDataModule", 4 + 2, startIndex: 4200000);
gameDataMan = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 05 ?? ?? ?? ?? 48 85 C0 74 05 48 8B 40 58 C3 C3", "GameDataMan", 3, 7, startIndex: 2000000);
trophyImpOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48833D ???????? 00 75 31 4C 8B05 ???????? 4C 8945 10 BA 08000000 8D4A 18", "CS::CSTrophyImp", 3, 8, startIndex: 13800000);
upgradeRuneCostOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "74 28 48 8B45 ?? 48 85C0 74 07 F3 0F1048 ?? EB 08 F3 0F100D ????????", "Weapon upgrade rune cost", startIndex: 7500000);
upgradeMatCostOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "8BF8 44 8BC3 48 8D55 ?? 48 8D4D ?? E8 ????????", "Weapon upgrade material cost", startIndex: 8500000);
soundDrawPatchLoc = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "74 ?? 48 8B0D ???????? BE 01000000 897424 ?? 48 85C9 75 ?? 48 8D0D ???????? E8 ????????", "soundDrawPatchLoc", startIndex: 3200000);
allTargetingDebugDraw = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "40 3835 ???????? 0F84 ????0000 48 8D5424 ?? 48 8BCF E8 ????0000 48 8D4C24 ?? E8 ???????? 6644 85BF ????000074 ?? 48 8B05 ???????? 48 85C0 75 ?? 48 8D0D ???????? E8 ???????? 4C 8BC8 4C 8D05 ????????BA ????0000 48 8D0D ????????E8 ???????? 48 8B05 ????????48 8B80 ????????48 8D5424 ?? 48 8B88 ????????48 8B49 ?? E8 ???????? EB ?? 8B8F ????0000 E8 ???????? F3 0F1145 ?? 48 8D4C24 ?? 66 859F ????000074 ?? B2 ?? EB ??", "allTargetingDebugDraw", 3, 3 + 4, startIndex: 3200000); //yes, it's long, take off more than a little and it gets two matches
mapItemManOff = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B 0D ???????? C7 44 24 50 FFFFFFFF", "MapItemManImpl", 3, 7, startIndex: 5700000);
itemSpawnCall = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "8B 02 83 F8 0A", "ItemSpawnCall", justOffset: -0x52, startIndex: 5400000);
if (itemSpawnCall < 0)
{
itemSpawnCall = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "40 55 56 57 41 54 41 55 41 56 41 57 48 8D6C24 ?? 48 81EC ????0000 48 C745 ?? FEFFFFFF 48 899C24 ????0000 48 8B05 ???????? 48 33C4 48 8945 ?? 44 894C24 ?? 4D 8B??4C 894424 ?? 4C 8B??33FF 897C24 ?? 8B02 83F8 ?? 0F87 ????0000", "ItemSpawnCall 1.02.x", startIndex: 5400000);
}
allChrNoDeath = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "803D ???????? 00 75 ?? 48 8BCB E8 ???????? 48 8BC8 E8 ???????? 84C0 74 ?? 48 833D ???????? 00", "allChrNoDeath", 2, 2 + 4 + 1, startIndex: 4200000);
//scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "803D ???????? 00 0F85 ???????? 32C0 48 83C4 20 5B C3", "playerNoDeath", 2, 2 + 4 + 1, startIndex: 4200000);
torrentDisabledCheckOne = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B40 68 8078 36 00 0F95C0 40 B7 01 8806 48 8B5C24 30 40 0FB6C7 48 8B7424 38 48 83C4 20 5F C3", "torrentDisabledCheckOne", justOffset: 4 + 4, startIndex: 12900000);
torrentDisabledCheckTwo = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "E8 ???????? 48 8B48 ?? 8079 36 00 0F95C0 48 83C4 ?? C3", "torrentDisabledCheckTwo", justOffset: 5 + 4 + 4, startIndex: 7000000);
mapIDinPlayerIns = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "C783 ????0000 FFFFFFFF 0F280D ???????? 66 0F7F4D ?? F2 0F118B ????0000 66 0F73D9 ?? 66 0F7E8B", "mapIDinPlayerIns", readoffset32: 2, startIndex: 6300000);
chrSetOffset = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B8CFE ??????00 48 85C9 74 ?? 4C 8B01 8BD0 41 FF50 ?? 48 8B7C24 ?? 48 8B5C24 ?? 48 83C4 ??", "worldChrManChrSetOffset", 4, startIndex: 5000000);
pgDataOffset = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B81 ????0000 48 C702 FFFFFFFF 48 85C0 74 0A 48 8B80 ????0000 48 8902 48 8BC2", "PGDataOffset", 3, startIndex: 6400000);
torrentIDOffset = (uint)scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8B81 ????0000 48 C702 FFFFFFFF 48 85C0 74 0A 48 8B80 ????0000 48 8902 48 8BC2", "TorrentIDOffset", 3 + 4 + 3 + 4 + 3 + 2 + 3, startIndex: 6400000);
scadOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "80 b9 ?? ?? 00 00 00 74 08 0f b6 81 ?? ?? 00 00 c3 0f b6 81 ?? ?? 00 00 c3", "CS::PlayerGameData::GetScadutreeBlessing (scadu offset in pgdata)", readoffset32: 20, startIndex: 2000000);
musicMuteLoc = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "0f b6 48 04 0f 29 74 24 70 0f 57 f6 0f 29 7c 24 60", "Music volume read (patch 31C99090 to mute)", startIndex: 13000000);
scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "4c 8b dc 53 56 57 48 81 ec 90 00 00 00 49 c7 43 88 fe ff ff ff 48 8b d9", "bonfire menu (6 matches)", startIndex: 7500000, singleMatch: false,
callback: x => menuOffsets.Add(x));
scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "4c 8b dc 53 48 81 ec 90 00 00 00 49 c7 43 88 fe ff ff ff 48 8b 05 ?? ?? ?? ?? 48 33 c4 48 89 84 24 80 00 00 00 48 8b d9 49 c7 43 e0 00 00 00 00 49 8d 43 a8 49 89 43 90 49 8d 43 a8 49 89 43 98 48 8d 05 ?? ?? ?? ?? 49 89 43 a8 48 8d 05 ?? ?? ?? ?? 49 89 43 a8 48 8d 05 ?? ?? ?? ?? 49 89 43 b0", "three more menus", startIndex: 7500000, singleMatch: false,
callback: x => menuOffsets.Add(x));
csEventFlagMan = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 833D ???????? 00 0F84 ????0000 44 8BE6 85 C0 0F 84 ????0000", "GLOBAL_CSEventFlagMan", 1 + 2, 1 + 2 + 4 + 1, startIndex: 2000000);
triggerNGPlusOffset = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, "48 8b 05 ?? ?? ?? ?? 0f b6 80 ?? ?? 00 00 c3 ?? 48 8b 05 ?? ?? ?? ?? 8b 90", "Trigger NG+ offset in GameMan", readoffset32: 3 + 4 + 3, startIndex: 6000000);
var cave = "";
for (int i = 0; i < 0xA0; i++) { cave += "90"; }
codeCavePtrLoc = scanner.findAddr(scanner.sectionOne, scanner.textOneAddr, cave, "codeCave_0x60_nops", startIndex: 100000);
zeroCaveOffset = scanner.textOneAddr + (int)scanner.textOneSize;
scanner.Dispose();
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
//code templates and patches - should be game version independent
static readonly byte[] itemSpawnTemplate = new byte[]
{
0x48, 0x83, 0xEC, 0x48, //sub rsp,48
0x4D, 0x31, 0xC9, //xor r9,r9
0x4C, 0x8D, 0x05, 0x22, 0x00, 0x00, 0x00, //lea r8,[eldenring.exe+28E3F30] - relative address, won't change
0x49, 0x8D, 0x50, 0x20, //lea rdx,[r8+20]
0x49, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, //mov r10,MapItemMan (addr offset 0x14)
0x49, 0x8B, 0x0A, //mov rcx,[r10]
0xE8, 0, 0, 0, 0, //call itemSpawnCall. itemSpawnCall - (itemSpawnStart + 0x24) (addr offset 0x20)
0x48, 0x83, 0xC4, 0x48, //add rsp,48
0xC3, //ret
};
static readonly byte[] warpCodeTemplate = new byte[]
{
0x00, 0x00, 0x00, 0x00, // id (seems pointless)
0x48, 0x83, 0xEC, 0x48, // sub rsp,48 (func start)
0xB9, 0xAA, 0x00, 0x00, 0x00, // mov ecx,000000AA
0xBA, 0xBB, 0x00, 0x00, 0x00, // mov edx,000000BB
0x41, 0xB8, 0xCC, 0x00, 0x00, 0x00, // mov r8d,000000CC
0x41, 0xB9, 0xDD, 0x00, 0x00, 0x00, // mov r9d,000000DD
0x48, 0x8D, 0x05, 0xDB, 0xFF, 0xFF, 0xFF, // lea rax,[eldenring.exe+zeroCaveOffset] (relative addr)
0x48, 0x89, 0x44, 0x24, 0x20, // mov [rsp+20],rax
0xE8, 0, 0, 0, 0, //call to pack coords+warp (to be filled in)
0xB9, 0x00, 0x00, 0x00, 0x00, // mov ecx,00000000
0xE8, 0, 0, 0, 0, //call set warp coords (with 0 to clear) (to be filled in)
0x48, 0x83, 0xC4, 0x48, // add rsp,48
0xC3, // ret
};
readonly byte[] torrentCheckOrigBytes = { 0x0F, 0x95, 0xC0 };
readonly byte[] torrentCheckPatchBytes = { 0x30, 0xC0, 0x90 };
static readonly byte[] targetHookOrigCode = new byte[] { 0x48, 0x8B, 0x48, 0x08, 0x49, 0x89, 0x8D, 0, 0, 0, 0, }; //last four bytes are the 'target hook offset' which varies with patches. followed by 0x49, 0x8B, 0xCE, 0xE8, which stays unchanged.
static readonly byte[] targetHookReplacementCodeTemplate = new byte[] { 0xE9,
0, 0, 0, 0, //address offset
0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
//replacement code contains the offset from the following instruction (basically hook loc + 5) to the code cave.
//then it just nops to fill out the rest of the old instructions
byte[] getTargetHookReplacementCode()
{
var ret = new byte[targetHookReplacementCodeTemplate.Length];
int addrOffset = codeCaveCodeLoc - (targetHookLoc + 5); //target minus next instruction location (ie. the NOP 5 bytes in)
Array.Copy(targetHookReplacementCodeTemplate, ret, ret.Length);
Array.Copy(BitConverter.GetBytes(addrOffset), 0, ret, 1, 4);
return ret;
}
static readonly byte[] targetHookCaveCodeTemplate = new byte[] { 0x48, 0xA3,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //full 64 bit ptr address goes here
0x48, 0x8B, 0x48, 0x08, //should be identical to orig code from here to just before E9
0x49, 0x89, 0x8D, 0, 0, 0, 0, //fill in offset from scan
0xE9,
0, 0, 0, 0, //address offset
};
readonly byte[] freeCamPlayerControlPatchOrig = new byte[] { 0x8B, 0x83, 0xC8, 0, 0, 0 }; //C8 may need to change in different patches
readonly byte[] freeCamPlayerControlPatchReplacement = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
readonly byte[] freeCamPlayerControlPatchOrig112 = new byte[] { 0x8B, 0x93, 0xC8, 0, 0, 0 }; //MOV EDX,dword ptr[RBX + 0xc8]
readonly byte[] freeCamPlayerControlPatchReplacement112 = new byte[] { 0x31, 0xD2, 0x90, 0x90, 0x90, 0x90 }; //xor edx,edx, nop nop nop nop
readonly byte[] logoScreenOrig = new byte[] { 0x74, 0x53 };
readonly byte[] logoScreenPatch = new byte[] { 0x90, 0x90 };
readonly byte[] eventPatch = new byte[] { 0xB0, 0x01 };
readonly byte[] soundDrawOrigBytes = { 0x74, 0x53 }; //JZ +53
readonly byte[] soundDrawPatchBytes = { 0x90, 0x90 }; //nop nop
//readonly byte[] freeCamOrigCode = new byte[] { 0x32, 0xC0 };
//readonly byte[] freeCamPatchCode = new byte[] { 0xB0, 0x01 };
readonly byte[] freeCamPatchCodeAlt = new byte[] { 0xB0, 0x01, 0xC3 }; //return 1
readonly byte[] mapCombatCheckPatchCode = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90 }; //xor eax, eax; nop nop nop
byte fontDrawNewValue = 0xC3; //ret
const byte enemyRepeatActionPatchVal = 0xB2;
const byte enemyRepeatActionOrigVal = 0xB1;
const byte enemyRepeatActionPatchVal108 = 0xC2;
const byte enemyRepeatActionOrigVal108 = 0xC1;
readonly byte[] musicMuteOrig = { 0x0f, 0xb6, 0x48, 0x04 }; //MOVZX ECX,byte ptr[RAX + 0x4]
readonly byte[] musicMutePatch = { 0x31, 0xC9, 0x90, 0x90 }; //xor ecx,ecx; nop; nop
//patch functions, helper functions, etc.
//we have another way to get this, but can use this as a fallback.
/*IntPtr getPlayerGameDataPtr()
{
var ptr = ReadUInt64(erBase + gameDataMan);
var ptr2 = ReadUInt64((IntPtr)ptr + 8); //CS::PlayerGameData
return (IntPtr)ptr2;
}*/
byte[] getTargetHookCaveCodeTemplate()
{
var ret = new byte[targetHookCaveCodeTemplate.Length];
int addrOffset = targetHookLoc + targetHookReplacementCodeTemplate.Length - (codeCaveCodeLoc + ret.Length); //again, target (after the hook) minus next instruction location (the NOPs after the end of our injection)
Array.Copy(targetHookCaveCodeTemplate, ret, ret.Length);
Array.Copy(BitConverter.GetBytes(addrOffset), 0, ret, ret.Length - 4, 4);
Array.Copy(BitConverter.GetBytes(targetHookOffset), 0, ret, 2 + 8 + 4 + 3, 4);
return ret;
}
public void setTorrentAnywherePatch(bool on)
{
var existingBytes = ReadBytes(erBase + torrentDisabledCheckOne, 3);
if (existingBytes.SequenceEqual(torrentCheckOrigBytes) && on)
{
WriteBytes(erBase + torrentDisabledCheckOne, torrentCheckPatchBytes);
}
else if (existingBytes.SequenceEqual(torrentCheckPatchBytes) && !on)
{
WriteBytes(erBase + torrentDisabledCheckOne, torrentCheckOrigBytes);
}
existingBytes = ReadBytes(erBase + torrentDisabledCheckTwo, 3);
if (existingBytes.SequenceEqual(torrentCheckOrigBytes) && on)
{
WriteBytes(erBase + torrentDisabledCheckTwo, torrentCheckPatchBytes);
}
else if (existingBytes.SequenceEqual(torrentCheckPatchBytes) && !on)
{
WriteBytes(erBase + torrentDisabledCheckTwo, torrentCheckOrigBytes);
}
}
public void setSoundView(bool on)
{//this can be enabled in a few ways. either look over all targeting system instances and set a flag, or just patch the location that checks the flag. let's go with the patch.
//this will also attempt to draw text, which is broken in the game, so the font draw patch must be set
if (on) { setFontDraw(); }
var existingBytes = ReadBytes(erBase + soundDrawPatchLoc, 2);
if (existingBytes.SequenceEqual(soundDrawOrigBytes) && on)
{
WriteBytes(erBase + soundDrawPatchLoc, soundDrawPatchBytes);
}
else if (existingBytes.SequenceEqual(soundDrawPatchBytes) && !on)
{
WriteBytes(erBase + soundDrawPatchLoc, soundDrawOrigBytes);
}
}
public float getSetGameSpeed(float? val = null)
{
var ptr = erBase + csFlipperOff;
var ptr2 = (IntPtr)ReadUInt64(ptr) + gameSpeedOffset;
var ret = ReadFloat(ptr2);
if (val.HasValue)
{
WriteFloat(ptr2, val.Value);
}
return ret;
}
public float getSetFrameTimeTarget(float? val = null)
{//target frame time in seconds, default 1/60.0f
var ptr = erBase + frameTimeTargetOffset;
var ret = ReadFloat(ptr);
if (val.HasValue)
{
WriteFloat(ptr, val.Value);
}
return ret;
}
byte[] getWarpCodeTemplate()
{
var buf = warpCodeTemplate.ToArray();
int callOneAddr = warpFirstCallOffset - zeroCaveOffset - 0x2F;
Array.Copy(BitConverter.GetBytes(callOneAddr), 0, buf, 4 + 4 + 5 + 5 + 6 + 6 + 7 + 5 + 1, 4);
int callTwoAddr = warpSecondCallOffset - zeroCaveOffset - 0x39;
Array.Copy(BitConverter.GetBytes(callTwoAddr), 0, buf, 4 + 4 + 5 + 5 + 6 + 6 + 7 + 5 + 5 + 5 + 1, 4);
return buf;
}
public void doWarp(byte aa, byte bb, byte cc, byte dd)
{
var buf = getWarpCodeTemplate();
buf[9] = aa;
buf[14] = bb;
buf[20] = cc;
buf[26] = dd;
WriteBytes(erBase + zeroCaveOffset, buf);
RunThread(erBase + zeroCaveOffset + 4);
//WriteBytes(erBase + zeroCaveOffset, new byte[buf.Length]); //blank it out - is this really needed?
}
public void doWarp(uint mapID)
{
uint P3 = (mapID & 0xFF000000U) >> 24; //area number
uint P2 = (mapID & 0x00FF0000U) >> 16; //X grid
uint P1 = (mapID & 0x0000FF00U) >> 8; //Z grid
uint P0 = (mapID & 0x000000FFU); //map type?? this is possibly two nibbles
doWarp((byte)P3, (byte)P2, (byte)P1, (byte)P0);
}
byte[] getItemSpawnTemplate()
{
var buf = itemSpawnTemplate.ToArray();
var mapItemManAddr = (erBase + mapItemManOff).ToInt64();
Array.Copy(BitConverter.GetBytes(mapItemManAddr), 0, buf, 0x14, 8);
int callAddr = itemSpawnCall - (itemSpawnStart + 0x24);
Array.Copy(BitConverter.GetBytes(callAddr), 0, buf, 0x20, 4);
return buf;
}
public void spawnItem(uint itemID, uint qty = 1, uint ashOfWar = 0xFFFFFFFF)
{
var buf = getItemSpawnTemplate();
WriteBytes(erBase + itemSpawnStart, buf);
WriteUInt32(erBase + itemSpawnData + 0x20, 1); //struct count
WriteUInt32(erBase + itemSpawnData + 0x24, itemID);
WriteUInt32(erBase + itemSpawnData + 0x28, qty);
WriteUInt32(erBase + itemSpawnData + 0x2C, 0); //unused?
WriteUInt32(erBase + itemSpawnData + 0x30, ashOfWar);
RunThread(erBase + itemSpawnStart);
if ((itemID & 0xF0000000) == 0x40000000)
{//Goods item
var goodsID = itemID & 0xFFFFFFF;
var evt = GoodsEvents.getEvent(goodsID);
if (evt >= 0)
{
Console.WriteLine($"Enabling flag {evt} for goods item {goodsID}");
getSetEventFlag(evt, true);
}
}
}
void openMenuByAddr(int menuAddr)
{//give menu calls scratch space to write result pointer/code
RunThread(erBase + menuAddr, param: erBase + zeroCaveOffset);
}
public readonly string[] MENUS = { "Credits", "Great Rune", "Mix Physick", "Ashes of War", "Send player home", "Memorise Spell", "Level Up", "Sort Chest", "Rebirth" };
public void openMenuByName(string name)
{
for (int i = 0; i < MENUS.Length; i++)
{
if (MENUS[i] == name && i < menuOffsets.Count) { openMenuByAddr(menuOffsets[i]); break; }
}
}
public void setEnemyRepeatActionPatch(bool on)
{
var b = ReadUInt8(erBase + enemyRepeatActionOff);
if (on && b == enemyRepeatActionOrigVal)
{
WriteUInt8(erBase + enemyRepeatActionOff, enemyRepeatActionPatchVal);
}
else if (!on && b == enemyRepeatActionPatchVal)
{
WriteUInt8(erBase + enemyRepeatActionOff, enemyRepeatActionOrigVal);
}
else if (on && b == enemyRepeatActionOrigVal108)
{
WriteUInt8(erBase + enemyRepeatActionOff, enemyRepeatActionPatchVal108);
}
else if (!on && b == enemyRepeatActionPatchVal108)
{
WriteUInt8(erBase + enemyRepeatActionOff, enemyRepeatActionOrigVal108);
}
else
{
Utils.debugWrite("Unexpected value trying to apply enemy repeat action patch");
}
}
byte[] mapOpenInCombatOrig = null;
byte[] mapStayOpenInCombatOrig = null;
public void doCombatMapPatch()
{
if (ReadUInt8(erBase + mapOpenInCombatOff) == 0xE8)
{
mapOpenInCombatOrig = ReadBytes(erBase + mapOpenInCombatOff, mapCombatCheckPatchCode.Length);
WriteBytes(erBase + mapOpenInCombatOff, mapCombatCheckPatchCode);
}
if (ReadUInt8(erBase + mapStayOpenInCombatOff) == 0xE8)
{
mapStayOpenInCombatOrig = ReadBytes(erBase + mapStayOpenInCombatOff, mapCombatCheckPatchCode.Length);
WriteBytes(erBase + mapStayOpenInCombatOff, mapCombatCheckPatchCode);
}
}
public void undoCombatMapPatch()
{
if (ReadUInt8(erBase + mapOpenInCombatOff) == 0x31 && mapOpenInCombatOrig != null)
{
WriteBytes(erBase + mapOpenInCombatOff, mapOpenInCombatOrig);
}
if (ReadUInt8(erBase + mapStayOpenInCombatOff) == 0x31 && mapStayOpenInCombatOrig != null)
{
WriteBytes(erBase + mapStayOpenInCombatOff, mapStayOpenInCombatOrig);
}
}
byte[] freeCamPatchAltOrig = null;
void doFreeCamPatch(bool on = true)
{
if (on)
{
/*if (!ReadBytes(erBase + freeCamPatchLoc, 2).SequenceEqual(freeCamPatchCode))
{
WriteBytes(erBase + freeCamPatchLoc, freeCamPatchCode);
}*/
if (ReadUInt8(erBase + freeCamPatchLocAlt) == 0xEB) //jmp
{
freeCamPatchAltOrig = ReadBytes(erBase + freeCamPatchLocAlt, freeCamPatchCodeAlt.Length);
WriteBytes(erBase + freeCamPatchLocAlt, freeCamPatchCodeAlt);
}
}
else
{
if (ReadBytes(erBase + freeCamPatchLocAlt, freeCamPatchCodeAlt.Length).SequenceEqual(freeCamPatchCodeAlt))
{
if (freeCamPatchAltOrig != null)
{
WriteBytes(erBase + freeCamPatchLocAlt, freeCamPatchAltOrig);
}
}
}
}
public void doFreeCamPlayerControlPatch()
{
if (ReadBytes(erBase + freeCamPlayerControlPatchLoc, 6).SequenceEqual(freeCamPlayerControlPatchOrig))
{
WriteBytes(erBase + freeCamPlayerControlPatchLoc, freeCamPlayerControlPatchReplacement);
}
if (ReadBytes(erBase + freeCamPlayerControlPatchLoc, 6).SequenceEqual(freeCamPlayerControlPatchOrig112))
{
WriteBytes(erBase + freeCamPlayerControlPatchLoc, freeCamPlayerControlPatchReplacement112);
}
}
public void undoFreeCamPlayerControlPatch()
{
if (ReadBytes(erBase + freeCamPlayerControlPatchLoc, 6).SequenceEqual(freeCamPlayerControlPatchReplacement))
{
WriteBytes(erBase + freeCamPlayerControlPatchLoc, freeCamPlayerControlPatchOrig);
}
if (ReadBytes(erBase + freeCamPlayerControlPatchLoc, 6).SequenceEqual(freeCamPlayerControlPatchReplacement112))
{
WriteBytes(erBase + freeCamPlayerControlPatchLoc, freeCamPlayerControlPatchOrig112);
}
}
void setFontDraw()
{//needed for poise bars and some other things. no need to turn off.
int oldVal = ReadUInt8(erBase + fontDrawOffset);
if (oldVal == fontDrawNewValue) { return; }
WriteUInt8(erBase + fontDrawOffset, fontDrawNewValue);
}
void doEventPatch()
{//not sure what these do, but needed for event draw. thanks pav!
if (!ReadBytes(erBase + EventPatchLoc1, 2).SequenceEqual(eventPatch))
{
WriteBytes(erBase + EventPatchLoc1, eventPatch);
}
if (!ReadBytes(erBase + EventPatchLoc2, 2).SequenceEqual(eventPatch))
{
WriteBytes(erBase + EventPatchLoc2, eventPatch);
}
}
public void doMusicMutePatch(bool on)
{
if (on)
{
if (ReadBytes(erBase + musicMuteLoc, 4).SequenceEqual(musicMuteOrig))
{
WriteBytes(erBase + musicMuteLoc, musicMutePatch);
}
}
else
{
if (ReadBytes(erBase + musicMuteLoc, 4).SequenceEqual(musicMutePatch))
{
WriteBytes(erBase + musicMuteLoc, musicMuteOrig);
}
}
}
public bool patchLogos()
{//see https://github.com/bladecoding/DarkSouls3RemoveIntroScreens/blob/master/SoulsSkipIntroScreen/dllmain.cpp, or my fork i guess
if (logoScreenBase < 0) { return false; }
var code = ReadBytes(erBase + logoScreenBase, logoScreenOrig.Length);
if (code.SequenceEqual(logoScreenOrig))
{//original code
WriteBytes(erBase + logoScreenBase, logoScreenPatch);
Utils.debugWrite("Patched");
return true;
}
else if (code.SequenceEqual(logoScreenPatch))
{
Utils.debugWrite("Already patched");
return true;
}
else
{
Utils.debugWrite("Unexpected data for logo patch, unknown version?");
return false;
}
}
public void setFreeUpgrade(bool on)
{
WriteUInt8(erBase + upgradeRuneCostOff, (byte)(on ? 0xEB : 0x74)); //patch JE to JMP
WriteBytes(erBase + upgradeMatCostOff, on ? new byte[] { 0x31, 0xFF } : new byte[] { 0x8B, 0xF8 }); //patch mov edi,eax to xor edi,edi
}
public bool installTargetHook()
{
//generate code first
var targetHookReplacementCode = getTargetHookReplacementCode();
var targetHookCaveCode = getTargetHookCaveCodeTemplate(); //still needs to have ptr addr added in
var code = ReadBytes(erBase + targetHookLoc, targetHookOrigCode.Length).Take(7); //compare first 7 bytes only; ignores target offset
if (code.SequenceEqual(targetHookReplacementCode.Take(7)))
{
Console.WriteLine("Already hooked");
return true;
}
if (!code.SequenceEqual(targetHookOrigCode.Take(7)))
{
Console.WriteLine("Unexpected code at hook location");
return false;
}
var caveCheck1 = ReadUInt64(erBase + codeCavePtrLoc);
var caveCheck2 = ReadUInt64(erBase + codeCaveCodeLoc);
if (caveCheck1 != 0x9090909090909090 || caveCheck2 != 0x9090909090909090) //byte reversal doesn't matter
{
Console.WriteLine("Code cave not empty");
return false;
}