-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathSpecialK_Shared.iss
981 lines (838 loc) · 29.5 KB
/
SpecialK_Shared.iss
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
; -- SpecialK_Shared.iss --
;
; This script holds shared helper code, procedures, functions, and logic
; used by the other Special K build scripts.
;
;
;
; licensed under MIT
; https://github.com/SpecialKO/Installer/blob/main/LICENSE
; -----------
; SHARED DEFINITIONS
; -----------
#define SpecialKUninstID "{F4A43527-9457-424A-90A6-17CF02ACF677}"
#define SKIFdrvUninstID "{A459BBFA-0819-49C4-8BF7-5BDF1559ED0C}"
#define ValvePlugUninstID "{E100754B-5610-4DA5-A572-B37BE59B0562}"
//#define SpecialKModUninstID "" // Holds AppID for game-specific mods; defined in the separate mod install scripts
; -----------
; SHARED CODE
; -----------
[Code]
// -----------
// Constants
// -----------
const
IMAGE_FILE_AGGRESIVE_WS_TRIM = $0010; // 0x10 (16) - Aggressively trim working set
IMAGE_FILE_LARGE_ADDRESS_AWARE = $0020; // 0x20 (32) - App can handle >2gb addresses
// -----------
// Global variables
// -----------
var
WbemLocator : Variant;
WbemServices : Variant;
MusicPlayback : Boolean;
MusicAvailable : Boolean;
LocPLUGroupName : String;
LocINTUserName : String;
ToggleMusicButton : TNewButton;
CreditMusicButton : TNewButton;
OneDriveStopped : Boolean;
OneDrivePath : String;
SteamStopped : Boolean;
// -----------
// Imported Win32 functions
// -----------
// These are all required to fix Inno Setup missing a taskbar preview
// - Not critical, so use delayload
// - If Inno Setup ever becomes native 64-bit, the below rows needs to be changed to SetWindowLongPtrW/GetWindowLongPtrW
function GetWindow (hWnd: HWND; uCmd: Cardinal) : HWND; external 'GetWindow@user32.dll stdcall delayload';
function SetWindowLong (hWnd: HWND; nIndex: Integer; dwNewLong: Longint): Longint; external 'SetWindowLongW@user32.dll stdcall delayload';
function GetWindowLong (hWnd: HWND; nIndex: Integer) : Longint; external 'GetWindowLongW@user32.dll stdcall delayload';
//function SetWindowLongPtr (hWnd: HWND; nIndex: Integer; dwNewLong: Longint): Longint; external 'SetWindowLongPtrW@user32.dll stdcall delayload';
//function GetWindowLongPtr (hWnd: HWND; nIndex: Integer) : Longint; external 'GetWindowLongPtrW@user32.dll stdcall delayload';
// Used to play background music during installation
// - Not critical, so use delayload
function mciSendString (lpstrCommand: String; lpstrReturnString: Integer; uReturnLength: Cardinal; hWndCallback: HWND): Cardinal; external 'mciSendStringW@winmm.dll stdcall delayload';
// -----------
// Fixes Inno Setup no taskbar preview
// -----------
// From StackOverflow: https://stackoverflow.com/a/64162597/15133327
// Created by: https://stackoverflow.com/users/709507/inside-man
// Licensed under CC BY-SA 4.0, https://creativecommons.org/licenses/by-sa/4.0/
//
// Technically wrong: "You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window.
// Instead, use the SetParent function."
function FixInnoSetupTaskbarPreview: Boolean;
begin
if not WizardSilent() then
begin
// We are delay loading all of these required DLL functions, so need additional exception handling
try
Log('Fixing the no taskbar preview bug of Inno Setup.');
SetWindowLong(WizardForm.Handle, -8, GetWindowLong(GetWindow(WizardForm.Handle, 4), -8));
Result := True;
except
Log('Catastrophic error in FixInnoSetupTaskbarPreview() !');
// Surpresses exception when an issue prevents proper lookup
end;
end;
end;
// -----------
// Command line
// -----------
// Used to check for the presence of cmd line switches
function SwitchHasValue(Name: String; Value: String; DefaultValue: String): Boolean;
begin
Result := CompareText(ExpandConstant('{param:' + Name + '|' + DefaultValue + '}'), Value) = 0;
end;
// -----------
// Windows version helpers
// -----------
function IsWindows10OrLater: Boolean;
begin
Result := (GetWindowsVersion >= $0A002800);
end;
function IsWindows8OrLater: Boolean;
begin
Result := (GetWindowsVersion >= $06020000);
end;
// -----------
// WMI
// -----------
function InitializeWMI(): Boolean;
begin
if VarIsEmpty(WbemLocator) or VarIsEmpty(WbemServices) then
begin
try
if VarIsEmpty(WbemLocator) then
begin
Log('Creating an IDispatch based COM Automation object...');
WbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
end;
if not VarIsEmpty(WbemLocator) and VarIsEmpty(WbemServices) then
begin
Log('Connecting to the local root\CIMV2 WMI namespace...');
WbemServices := WbemLocator.ConnectServer('', 'root\CIMV2'); // Let's not include 'localhost'
end;
if not VarIsEmpty(WbemLocator) and not VarIsEmpty(WbemServices) then
begin
Result := True;
end;
except
Log('Catastrophic error in InitializeWMI() !');
// Surpresses exception when an issue prevents proper lookup
end;
end
else
begin
Result := True;
end;
end;
// -----------
// Music playback
// -----------
// This is called by the OnClick handler of a button
procedure ToggleButtonClick(Sender: TObject);
begin
if MusicAvailable then
begin
if MusicPlayback then
begin
mciSendString('stop soundbg', 0, 0, 0);
MusicPlayback := False;
ToggleMusicButton.Caption := 'Play Music';
end
else
begin
mciSendString('play soundbg repeat', 0, 0, 0);
MusicPlayback := True;
ToggleMusicButton.Caption := 'Stop Music';
end;
end;
end;
// This is called by the OnClick handler of a button
procedure CreditButtonClick(Sender: TObject);
var
ErrorCode: Integer;
begin
ShellExec('', 'https://opengameart.org/content/stargazer', '', '', SW_SHOW, ewNoWait, ErrorCode);
end;
function InitializeMusicPlayback(FileName: String): Boolean;
begin
if not WizardSilent() then
begin
// Some nice background tunes
Log('Preparing music components.');
try
MusicPlayback := False;
MusicAvailable := False;
ExtractTemporaryFile(FileName);
// Open the track
if (0 = mciSendString(ExpandConstant('open "{tmp}/' + FileName + '" alias soundbg'), 0, 0, 0)) then
begin
// Adjust the volume
if (0 = mciSendString('setaudio soundbg volume to 125', 0, 0, 0)) then
begin
// Create the UI elements
ToggleMusicButton := TNewButton.Create(WizardForm);
ToggleMusicButton.Parent := WizardForm;
ToggleMusicButton.Left :=
WizardForm.ClientWidth -
WizardForm.CancelButton.Left -
WizardForm.CancelButton.Width;
ToggleMusicButton.Top := WizardForm.CancelButton.Top; //WizardForm.CancelButton.Top + 50;
ToggleMusicButton.Width := WizardForm.CancelButton.Width;
ToggleMusicButton.Height := WizardForm.CancelButton.Height;
ToggleMusicButton.Caption := 'Play Music';
ToggleMusicButton.OnClick := @ToggleButtonClick;
ToggleMusicButton.Anchors := [akLeft, akBottom];
CreditMusicButton := TNewButton.Create(WizardForm);
CreditMusicButton.Parent := WizardForm;
CreditMusicButton.Left :=
WizardForm.ClientWidth -
WizardForm.NextButton.Left -
WizardForm.NextButton.Width;
CreditMusicButton.Top := WizardForm.NextButton.Top; //WizardForm.CancelButton.Top + 50;
CreditMusicButton.Width := WizardForm.NextButton.Width;
CreditMusicButton.Height := WizardForm.NextButton.Height;
CreditMusicButton.Caption := 'Music By';
CreditMusicButton.OnClick := @CreditButtonClick;
CreditMusicButton.Anchors := [akLeft, akBottom];
// If everything worked so far
MusicAvailable := True;
Result := True;
end;
end;
except
Log('Failed initializing music components: ' + AddPeriod(GetExceptionMessage));
end;
end;
end;
function DeinitializeMusicPlayback: Boolean;
begin
if not WizardSilent() and MusicAvailable then
begin
Log('Cleaning up music components.');
try
if MusicPlayback then
begin
// Stop music playback if it's currently playing
mciSendString('stop soundbg', 0, 0, 0);
MusicPlayback := False;
end;
// Close the MCI device
mciSendString('close all', 0, 0, 0);
Result := True;
except
Log('Failed deinitializing music components: ' + AddPeriod(GetExceptionMessage));
end;
end;
end;
// -----------
// Steam / Valve
// -----------
// Parses Valve Data Format (.VDF and .ACF) files
// Based on StackOverflow: https://stackoverflow.com/a/37019690/15133327
// Created by: https://stackoverflow.com/users/850848/martin-prikryl
// Licensed under CC BY-SA 3.0, https://creativecommons.org/licenses/by-sa/3.0/
function GetVDFKeyValues(FileName: String; Key: String; var Values: TArrayOfString): Boolean;
var
I: Integer;
P: Integer;
Lines: TArrayOfString;
Line: String;
LineKey: String;
LineValue: String;
Count: Integer;
begin
Result := LoadStringsFromFile(FileName, Lines);
Count := 0;
for I := 0 to GetArrayLength(Lines) - 1 do
begin
Line := Trim(Lines[I]);
if Copy(Line, 1, 1) = '"' then
begin
Delete(Line, 1, 1);
P := Pos('"', Line);
if P > 0 then
begin
LineKey := Trim(Copy(Line, 1, P - 1));
Delete(Line, 1, P);
Line := Trim(Line);
//Log(Format('Found VDF key "%s"', [LineKey]));
if (CompareText(
Copy(LineKey, 1, Length(Key)),
Key) = 0) and
(Line[1] = '"') then
begin
Delete(Line, 1, 1);
P := Pos('"', Line);
if P > 0 then
begin
LineValue := Trim(Copy(Line, 1, P - 1));
StringChange(LineValue, '\\', '\');
//Log(Format('Found VDF value: %s', [LineValue]));
Inc(Count);
SetArrayLength(Values, Count);
Values[Count - 1] := LineValue;
end;
end;
end;
end;
end;
end;
// Parses Valve Data Format (.VDF and .ACF) files
// Based on StackOverflow: https://stackoverflow.com/a/37019690/15133327
// Created by: https://stackoverflow.com/users/850848/martin-prikryl
// Licensed under CC BY-SA 3.0, https://creativecommons.org/licenses/by-sa/3.0/
function GetVDFKeyValue(FileName: String; Key: String; var Value: String): Boolean;
var
I: Integer;
P: Integer;
Lines: TArrayOfString;
Line: String;
LineKey: String;
LineValue: String;
Count: Integer;
begin
Result := LoadStringsFromFile(FileName, Lines);
Count := 0;
for I := 0 to GetArrayLength(Lines) - 1 do
begin
Line := Trim(Lines[I]);
if Copy(Line, 1, 1) = '"' then
begin
Delete(Line, 1, 1);
P := Pos('"', Line);
if P > 0 then
begin
LineKey := Trim(Copy(Line, 1, P - 1));
Delete(Line, 1, P);
Line := Trim(Line);
//Log(Format('Found VDF key "%s"', [LineKey]));
if (CompareText(
Copy(LineKey, 1, Length(Key)),
Key) = 0) and
(Line[1] = '"') then
begin
Delete(Line, 1, 1);
P := Pos('"', Line);
if P > 0 then
begin
LineValue := Trim(Copy(Line, 1, P - 1));
StringChange(LineValue, '\\', '\');
//Log(Format('Found VDF value: %s', [LineValue]));
Value := LineValue;
break;
end;
end;
end;
end;
end;
end;
// Detects and returns the install folder of a Steam game given its AppID
// This is called from [Setup] to dynamically set the install folder
// 0 = Get the root Steam install folder
function GetSteamInstallFolder(AppID: String): String;
var
I: Integer;
Libraries: TArrayOfString;
Library: String;
SteamInstallPath: String;
SteamLibVDFPath: String;
GameInstallPath: String;
GameInstallDir: String;
begin
SteamInstallPath := ExpandConstant('{reg:HKLM32\SOFTWARE\Valve\Steam,InstallPath|{commonpf32}\Steam}');
if DirExists(SteamInstallPath) then
begin
Log(Format('Found Steam folder: %s', [SteamInstallPath]));
if SameText (AppId, '0') then
begin
Log('AppID 0 was given, returning base Steam folder.');
Result := SteamInstallPath;
end
else
begin
if FileExists(SteamInstallPath + '\config\libraryfolders.vdf') then
SteamLibVDFPath := SteamInstallPath + '\config\libraryfolders.vdf' // Modern location
else
SteamLibVDFPath := SteamInstallPath + '\steamapps\libraryfolders.vdf'; // Legacy location
if GetVDFKeyValues(SteamLibVDFPath, 'path', Libraries) then
begin
for I := 0 to GetArrayLength(Libraries) - 1 do
begin
Library := Libraries[I];
GameInstallPath := Library + '\steamapps\common\';
if FileExists(Library + '\steamapps\appmanifest_' + AppID + '.acf') and
GetVDFKeyValue(Library + '\steamapps\appmanifest_' + AppID + '.acf', 'installdir', GameInstallDir) then
begin
if DirExists(GameInstallPath + GameInstallDir) then
begin
Log(Format('Found game folder: %s', [GameInstallPath + GameInstallDir]));
Result := GameInstallPath + GameInstallDir;
break;
end;
end;
end;
end;
end
end;
end;
// Checks if Steam is currently running
function IsSteamRunning(): Boolean;
var
WbemObjectSet : Variant;
begin
try
if InitializeWMI() then
begin
WbemObjectSet := WbemServices.ExecQuery('SELECT Name FROM Win32_Process WHERE (Name = "Steam.exe")');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
Result := True;
end;
end;
except
Log('Catastrophic error in IsSteamRunning()!');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// Stops the Steam client
function StopSteam: Boolean;
var
ResultCode : Integer;
begin
Log('Shutting down Steam...');
SteamStopped := ShellExec('', 'steam://exit', '', '', SW_HIDE, ewNoWait, ResultCode);
Result := SteamStopped;
end;
// Restarts the Steam client if it was stopped by us
function RestartSteam: Boolean;
var
ResultCode : Integer;
begin
if SteamStopped then
begin
Log('Restarting Steam...');
Result := ShellExec('', 'steam://', '', '', SW_HIDE, ewNoWait, ResultCode);
end;
end;
// -----------
// LAAwareness
// -----------
// This is a helper function to convert from Unicode string to Ansi string as
// byte array comparisons with data read using TFileStream otherwise break
// See https://stackoverflow.com/a/43161113/15133327 for more information
//
// From StackOverflow: https://stackoverflow.com/q/31228103/15133327
// Created by: https://stackoverflow.com/users/3992415/leduc
// Licensed under CC BY-SA 4.0, https://creativecommons.org/licenses/by-sa/4.0/
function BufferToAnsi(const Buffer: String): AnsiString;
var
W: Word;
I: Integer;
begin
SetLength(Result, Length(Buffer) * 2);
for I := 1 to Length(Buffer) do
begin
W := Ord(Buffer[I]);
Result[(I * 2)] := Chr(W shr 8); // high byte
Result[(I * 2) - 1] := Chr(Byte(W)); // low byte
end;
end;
// Sets IMAGE_FILE_LARGE_ADDRESS_AWARE flag on an executable
// IMAGE_FILE_LARGE_ADDRESS_AWARE = App can handle >2gb addresses
function MakeExecutableLAAware(FileName: String): Boolean;
var
Stream: TFileStream;
Buffer: String;
BufferH: String;
AnsiStr: AnsiString;
HeaderPos: Longint;
Error: Longint;
Flag: Integer;
begin
Log(Format('Checking LAA on %s', [FileName]));
if FileExists(FileName) then
begin
try
// Open the file for reading
Stream := TFileStream.Create(FileName, fmOpenReadWrite or fmShareDenyWrite);
SetLength(Buffer, 1);
SetLength(BufferH, 2);
// Detect if we are in an executable
Stream.Seek(0, soFromBeginning);
Stream.ReadBuffer(Buffer, 1);
AnsiStr := BufferToAnsi(Buffer);
//Log(Format('Byte 01: %2.2x (%s)', [Ord(AnsiStr[1]), AnsiStr[1]]));
if AnsiStr[1] = #$4D then // M
begin
Stream.ReadBuffer(Buffer, 1);
AnsiStr := BufferToAnsi(Buffer);
if AnsiStr[1] = #$5A then // Z
begin
// We are in an executable!
// Look up the offset for the PE header
Stream.Seek(60, soFromBeginning);
Stream.ReadBuffer(BufferH, 2);
HeaderPos := Ord(BufferH[1]);
Log(Format('PE header offset: %d (dec), %x (hex)', [HeaderPos, HeaderPos]));
// Seek to the offset we found
Stream.Seek(HeaderPos, soFromBeginning);
Stream.ReadBuffer(Buffer, 1);
AnsiStr := BufferToAnsi(Buffer);
if AnsiStr[1] = #$50 then // P(ortable)
begin
Stream.ReadBuffer(Buffer, 1);
AnsiStr := BufferToAnsi(Buffer);
if AnsiStr[1] = #$45 then // E(xecutable)
begin
// We have located the PE header!
Stream.Seek(20, soFromCurrent); // Move the cursor 20 steps forward
Stream.ReadBuffer(Buffer, 1);
AnsiStr := BufferToAnsi(Buffer);
Flag := Ord(AnsiStr[1]);
Log(Format('Got flag: %d', [Flag]));
if (Flag and IMAGE_FILE_LARGE_ADDRESS_AWARE) = IMAGE_FILE_LARGE_ADDRESS_AWARE then
begin // LAAware
Log('Executable is LAAware, nothing to do.');
Result := True;
end
else // LAUnaware
begin
Log('Executable is LAUnware, patching...');
if FileCopy(FileName, ChangeFileExt(FileName, '_LAUnaware.bak'), False) then
begin
Stream.Seek(-1, soFromCurrent); // Move the cursor back one step
Flag := (Flag or IMAGE_FILE_LARGE_ADDRESS_AWARE);
Log(Format('New flag: %d', [Flag]));
Stream.WriteBuffer(Chr(Flag), 1); // IntToStr() results in incorrect data being written, but Chr() writes the right one
Log('Executable was patched successfully!');
Result := True;
end
else
begin
Error := DLLGetLastError;
Log(Format('Copying "%s" to "%s" failed with code %d (0x%x) - %s', [
FileName, FileName + '_LAUnaware.bak', Error, Error, SysErrorMessage(Error)]));
end;
end;
end;
end;
end;
end;
except
Error := DLLGetLastError;
Log(Format('Operating on "%s" failed with code %d (0x%x) - %s', [
FileName, Error, Error, SysErrorMessage(Error)]));
finally
Stream.Free;
end;
end
else
begin
Log('The installer cannot find the file specified.');
end;
end;
// -----------
// PresentMon
// -----------
// Checks if the required permissions exists for PresentMon stats
function IsInteractiveInPLU(): Boolean;
var
WbemObjectSet : Variant;
IsMember : Boolean;
ComputerName : String;
I : Integer;
begin
try
(*
PS C:\> Get-WmiObject -Query "SELECT * FROM Win32_Group WHERE (LocalAccount = True) AND (SID = 'S-1-5-32-559')" | fl
Caption : <ComputerName>\Performance Log Users
Domain : <ComputerName>
Name : Performance Log Users
SID : S-1-5-32-559
PS C:\> Get-WMIObject -Query 'ASSOCIATORS OF {Win32_Group.Domain="<ComputerName>",Name="Performance Log Users"} WHERE assocClass=Win32_GroupUser Role=GroupComponent ResultRole=PartComponent' | fl
Caption : <ComputerName>\INTERACTIVE
Domain : <ComputerName>
Name : INTERACTIVE
SID : S-1-5-4
*)
if InitializeWMI() then
begin
Log('Attempting to retrieve PLU membership...');
// Retrieve the localized name of the PLU group
WbemObjectSet := WbemServices.ExecQuery('SELECT * FROM Win32_Group WHERE (LocalAccount = True) AND (SID = "S-1-5-32-559")');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
ComputerName := WbemObjectSet.ItemIndex(0).Domain;
LocPLUGroupName := WbemObjectSet.ItemIndex(0).Name;
//MsgBox(ComputerName, mbInformation, MB_OK);
//MsgBox(LocPLUGroupName, mbInformation, MB_OK);
if not VarIsNull(ComputerName) and not VarIsNull(LocPLUGroupName) then
begin
WbemObjectSet := Null;
// Retrieve members of the local PLU group
WbemObjectSet := WbemServices.ExecQuery('ASSOCIATORS OF {Win32_Group.Domain="' + ComputerName + '",Name="' + LocPLUGroupName + '"} WHERE assocClass=Win32_GroupUser Role=GroupComponent ResultRole=PartComponent');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
for I := 0 to WbemObjectSet.Count - 1 do
begin
// Check if one of the members is NT AUTHORITY\Interactive
if (WbemObjectSet.ItemIndex(I).SID = 'S-1-5-4') then
begin
IsMember := True;
end;
end;
end;
end;
// If Interactive was not a member, we still need to retrieve the localized username
if not IsMember then
begin
Log('Attempting to retrieve localized username for NT AUTHORITY\Interactive...');
WbemObjectSet := Null;
WbemObjectSet := WbemServices.ExecQuery('SELECT * FROM Win32_SystemAccount WHERE (LocalAccount = True) AND (SID = "S-1-5-4")');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
LocINTUserName := WbemObjectSet.ItemIndex(0).Name;
end;
end;
Result := IsMember;
end;
end;
except
Log('Catastrophic error in IsInteractiveInPLU() !');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// -----------
// Special K / SKIF / Injection Service
// -----------
// Detects and returns the install folder of Special K
// This is called from [Setup] to dynamically set the install folder
function GetSpecialKInstallFolder(SKInstallPath: String): String;
begin
if not RegQueryStringValue(HKLM64, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{#SpecialKUninstID}_is1', 'InstallLocation', SKInstallPath) then
begin
SKInstallPath := ExpandConstant('{reg:HKCU\SOFTWARE\Kaldaien\Special K,Path|{autopf64}\Special K}');
end;
if DirExists(SKInstallPath) then
begin
Log(Format('Found Special K folder: %s', [SKInstallPath]));
end
else
begin
Log(Format('Failed to locate Special K folder, using fallback: %s', [SKInstallPath]));
end;
Result := SKInstallPath;
end;
// Checks if the injector service of Special K or SKIF is running
function IsSKIForSvcRunning(): Boolean;
var
WbemObjectSet : Variant;
InstallFolder : String;
begin
InstallFolder := ExtractFileName(RemoveBackslashUnlessRoot(ExpandConstant('{app}')));
if Length(InstallFolder) = 0 then
begin
InstallFolder := 'SpecialK';
end;
try
if InitializeWMI() then
begin
WbemObjectSet := WbemServices.ExecQuery('SELECT Name FROM Win32_Process WHERE (Name = "SKIFsvc.exe" OR Name = "SKIFsvc32.exe" OR Name = "SKIFsvc64.exe" OR Name = "SKIF.exe") OR ((Name = "rundll32.exe") AND (CommandLine LIKE "%SpecialK%" OR CommandLine LIKE "%Special K%" OR CommandLine LIKE "%' + InstallFolder + '%" OR ExecutablePath LIKE "%SpecialK%" OR ExecutablePath LIKE "%Special K%" OR ExecutablePath LIKE "%' + InstallFolder + '%"))');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
Result := True;
end;
end;
except
Log('Catastrophic error in IsSKIForSvcRunning()!');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// Checks if SKIF is currently running
function IsSKIFRunning(): Boolean;
var
WbemObjectSet : Variant;
begin
try
if InitializeWMI() then
begin
WbemObjectSet := WbemServices.ExecQuery('SELECT Name FROM Win32_Process WHERE (Name = "SKIF.exe")');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
Result := True;
end;
end;
except
Log('Catastrophic error in IsSKIFRunning()!');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// Forcefully stops SKIF and the service components
function ForceStopSKIFandSvc(): Integer;
begin
try
Exec('taskkill.exe', '/F /IM SKIF.exe', '', SW_HIDE, ewNoWait, Result);
// Not safe to do as it seems to get the service stuck in an unstartable state
//Exec('taskkill.exe', '/F /IM SKIFsvc32.exe', '', SW_HIDE, ewNoWait, Result);
//Exec('taskkill.exe', '/F /IM SKIFsvc64.exe', '', SW_HIDE, ewNoWait, Result);
except
Log('Catastrophic error in ForceStopSKIFandSvc()!');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// Checks if SKIF is set up to start automatically with Windows
function IsSKIFAutoStartEnabled(): Boolean;
var
TaskService: Variant;
RootFolder: Variant;
TaskCollection: Variant;
I: Integer;
begin
try
TaskService := CreateOleObject('Schedule.Service');
TaskService.Connect();
if TaskService.Connected then
begin
RootFolder := TaskService.GetFolder('\');
TaskCollection := RootFolder.GetTasks(0);
if not VarIsNull(TaskCollection) and (TaskCollection.Count > 0) then
begin
for I := 1 to TaskCollection.Count - 1 do // Item enumeration starts at 1 apparently
begin
if not VarIsNull(TaskCollection.Item(I)) and (TaskCollection.Item(I).Name = 'SK_InjectLogon') then
begin
Result := True;
end;
end;
end;
end;
except
Log('Catastrophic error in IsSKIFAutoStartEnabled() !');
// Surpresses exception when task does not exist or another issue prevents proper lookup
end;
end;
// -----------
// OneDrive
// -----------
// Checks if OneDrive is currently running
function IsOneDriveRunning(): Boolean;
var
WbemObjectSet : Variant;
Path : String;
begin
try
if InitializeWMI() then
begin
WbemObjectSet := WbemServices.ExecQuery('SELECT ExecutablePath FROM Win32_Process WHERE (Name = "OneDrive.exe")');
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
Path := WbemObjectSet.ItemIndex(0).ExecutablePath;
if Length(Path) > 0 then
begin
OneDrivePath := Path;
Result := True;
end;
end;
end;
except
Log('Catastrophic error in IsOneDriveRunning()!');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// Stops OneDrive
function StopOneDrive(): Integer;
begin
try
Exec('taskkill.exe', '/F /IM OneDrive.exe', '', SW_HIDE, ewNoWait, Result);
OneDriveStopped := True;
except
Log('Catastrophic error in StopOneDrive()!');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// Retrieves the path of OneDrive
function GetOneDrivePath(Value: string): string;
begin
Result := OneDrivePath;
end;
// Returns TRUE if OneDrive was stopped during installation
function RestartOneDrive: Boolean;
begin
Result := OneDriveStopped;
end;
// -----------
// Kernel Driver
// -----------
// Checks if the WinRing0 kernel driver is installed
function IsKernelDriverInstalled(): Boolean;
var
WbemObjectSet : Variant;
//InstallFolder : String;
begin
//InstallFolder := ExtractFileName(RemoveBackslashUnlessRoot(ExpandConstant('{app}')));
//if Length(InstallFolder) = 0 then
//begin
// InstallFolder := 'SpecialK';
//end;
try
if InitializeWMI() then
begin
WbemObjectSet := WbemServices.ExecQuery('SELECT PathName FROM Win32_SystemDriver WHERE Name = "SK_WinRing0"'); // AND (PathName LIKE "%SpecialK%" OR PathName LIKE "%Special K%" OR PathName LIKE "%' + InstallFolder + '%")
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
Result := True;
end;
end;
except
Log('Catastrophic error in IsKernelDriverInstalled() !');
// Surpresses exception when an issue prevents proper lookup
end;
end;
// -----------
// Check if contants can be expanded successfully
// -----------
function TryExpandConstant(ConstantFolder: String): Boolean;
var
Folder: String;
begin
try
// Test if we can expand the userdocs constant and if it exists
Folder := ExpandConstant('{' + ConstantFolder + '}');
if DirExists(Folder) then
begin
Result := True;
end;
except
Log('Failed to expand constant: ' + AddPeriod(GetExceptionMessage));
end;
end;
// -----------
// Procedure to extract a ZIP archive
// -----------
// CC BY-SA 4.0: https://stackoverflow.com/a/40706549/15133327
const
SHCONTCH_NOPROGRESSBOX = 4;
SHCONTCH_RESPONDYESTOALL = 16;
procedure UnZip(ZipPath, TargetPath: string);
var
Shell: Variant;
ZipFile: Variant;
TargetFolder: Variant;
begin
Shell := CreateOleObject('Shell.Application');
ZipFile := Shell.NameSpace(ZipPath);
if VarIsClear(ZipFile) then
RaiseException(
Format('ZIP file "%s" does not exist or cannot be opened', [ZipPath]));
TargetFolder := Shell.NameSpace(TargetPath);
if VarIsClear(TargetFolder) then
RaiseException(Format('Target path "%s" does not exist', [TargetPath]));
TargetFolder.CopyHere(
ZipFile.Items, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL);
end;
[Setup]
; Required as otherwise the file cannot be compiled