FFXIVLauncher-Netmaui

Форк
0
673 строки · 26.1 Кб
1
using Microsoft.Win32;
2
using Microsoft.Win32.SafeHandles;
3
using Newtonsoft.Json;
4
using Steamworks;
5
using System;
6
using System.Collections.Generic;
7
using System.ComponentModel;
8
using System.Diagnostics;
9
using System.Linq;
10
using System.Reflection;
11
using System.Runtime.InteropServices;
12
using System.Runtime.InteropServices.ComTypes;
13
using System.Text;
14
using System.Threading.Tasks;
15
using XIVLauncher.Common.PlatformAbstractions;
16

17
namespace LibDalamud.Common.Dalamud
18
{
19
    public class WindowsDalamudRunner : IDalamudRunner
20
    {
21
        public Process? Run(FileInfo runner, bool fakeLogin, bool noPlugins, bool noThirdPlugins, FileInfo gameExe, string gameArgs, IDictionary<string, string> environment, DalamudLoadMethod loadMethod, DalamudStartInfo startInfo)
22
        {
23
            var inheritableCurrentProcess = GetInheritableCurrentProcessHandle();
24

25
            var launchArguments = new List<string>
26
        {
27
            "launch",
28
            $"--mode={(loadMethod == DalamudLoadMethod.EntryPoint ? "entrypoint" : "inject")}",
29
            $"--handle-owner={(long)inheritableCurrentProcess.Handle}",
30
            $"--game=\"{gameExe.FullName}\"",
31
            $"--dalamud-working-directory=\"{startInfo.WorkingDirectory}\"",
32
            $"--dalamud-configuration-path=\"{startInfo.ConfigurationPath}\"",
33
            $"--dalamud-plugin-directory=\"{startInfo.PluginDirectory}\"",
34
            $"--dalamud-dev-plugin-directory=\"{startInfo.DefaultPluginDirectory}\"",
35
            $"--dalamud-asset-directory=\"{startInfo.AssetDirectory}\"",
36
            $"--dalamud-client-language={(int)startInfo.Language}",
37
            $"--dalamud-delay-initialize={startInfo.DelayInitializeMs}",
38
            $"--dalamud-tspack-b64={Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(startInfo.TroubleshootingPackData))}",
39
        };
40

41
            if (loadMethod == DalamudLoadMethod.ACLonly)
42
                launchArguments.Add("--without-dalamud");
43

44
            if (fakeLogin)
45
                launchArguments.Add("--fake-arguments");
46

47
            if (noPlugins)
48
                launchArguments.Add("--no-plugin");
49

50
            if (noThirdPlugins)
51
                launchArguments.Add("--no-3rd-plugin");
52

53
            launchArguments.Add("--");
54
            launchArguments.Add(gameArgs);
55

56
            var psi = new ProcessStartInfo(runner.FullName)
57
            {
58
                Arguments = string.Join(" ", launchArguments),
59
                RedirectStandardOutput = true,
60
                UseShellExecute = false,
61
                CreateNoWindow = true
62
            };
63

64
            foreach (var keyValuePair in environment)
65
            {
66
                if (psi.EnvironmentVariables.ContainsKey(keyValuePair.Key))
67
                    psi.EnvironmentVariables[keyValuePair.Key] = keyValuePair.Value;
68
                else
69
                    psi.EnvironmentVariables.Add(keyValuePair.Key, keyValuePair.Value);
70
            }
71

72
            try
73
            {
74
                var dalamudProcess = Process.Start(psi);
75
                var output = dalamudProcess.StandardOutput.ReadLine();
76

77
                if (output == null)
78
                    throw new DalamudRunnerException("An internal Dalamud error has occured");
79

80
                try
81
                {
82
                    var dalamudConsoleOutput = JsonConvert.DeserializeObject<DalamudConsoleOutput>(output);
83
                    Process gameProcess;
84

85
                    if (dalamudConsoleOutput.Handle == 0)
86
                    {
87
                        Console.WriteLine($"Dalamud returned NULL process handle, attempting to recover by creating a new one from pid {dalamudConsoleOutput.Pid}...");
88
                        gameProcess = Process.GetProcessById(dalamudConsoleOutput.Pid);
89
                    }
90
                    else
91
                    {
92
                        gameProcess = new ExistingProcess((IntPtr)dalamudConsoleOutput.Handle);
93
                    }
94

95
                    try
96
                    {
97
                        Console.WriteLine($"Got game process handle {gameProcess.Handle} with pid {gameProcess.Id}");
98
                    }
99
                    catch (InvalidOperationException ex)
100
                    {
101
                        Console.WriteLine(ex.Message, $"Dalamud returned invalid process handle {gameProcess.Handle}, attempting to recover by creating a new one from pid {dalamudConsoleOutput.Pid}...");
102
                        gameProcess = Process.GetProcessById(dalamudConsoleOutput.Pid);
103
                        Console.WriteLine($"Recovered with process handle {gameProcess.Handle}");
104
                    }
105

106
                    if (gameProcess.Id != dalamudConsoleOutput.Pid)
107
                        Console.WriteLine($"Internal Process ID {gameProcess.Id} does not match Dalamud provided one {dalamudConsoleOutput.Pid}");
108

109
                    return gameProcess;
110
                }
111
                catch (JsonReaderException ex)
112
                {
113
                    Console.WriteLine(ex.Message, $"Couldn't parse Dalamud output: {output}");
114
                    return null;
115
                }
116
            }
117
            catch (Exception ex)
118
            {
119
                throw new DalamudRunnerException("Error trying to start Dalamud.", ex);
120
            }
121
        }
122

123
        /// <summary>
124
        /// DUPLICATE_* values for DuplicateHandle's dwDesiredAccess.
125
        /// </summary>
126
        [Flags]
127
        private enum DuplicateOptions : uint
128
        {
129
            /// <summary>
130
            /// Closes the source handle. This occurs regardless of any error status returned.
131
            /// </summary>
132
            CloseSource = 0x00000001,
133

134
            /// <summary>
135
            /// Ignores the dwDesiredAccess parameter. The duplicate handle has the same access as the source handle.
136
            /// </summary>
137
            SameAccess = 0x00000002,
138
        }
139

140
        /// <summary>
141
        /// Duplicates an object handle.
142
        /// </summary>
143
        /// <param name="hSourceProcessHandle">
144
        /// A handle to the process with the handle to be duplicated.
145
        ///
146
        /// The handle must have the PROCESS_DUP_HANDLE access right.
147
        /// </param>
148
        /// <param name="hSourceHandle">
149
        /// The handle to be duplicated. This is an open object handle that is valid in the context of the source process.
150
        /// For a list of objects whose handles can be duplicated, see the following Remarks section.
151
        /// </param>
152
        /// <param name="hTargetProcessHandle">
153
        /// A handle to the process that is to receive the duplicated handle.
154
        ///
155
        /// The handle must have the PROCESS_DUP_HANDLE access right.
156
        /// </param>
157
        /// <param name="lpTargetHandle">
158
        /// A pointer to a variable that receives the duplicate handle. This handle value is valid in the context of the target process.
159
        ///
160
        /// If hSourceHandle is a pseudo handle returned by GetCurrentProcess or GetCurrentThread, DuplicateHandle converts it to a real handle to a process or thread, respectively.
161
        ///
162
        /// If lpTargetHandle is NULL, the function duplicates the handle, but does not return the duplicate handle value to the caller. This behavior exists only for backward compatibility with previous versions of this function. You should not use this feature, as you will lose system resources until the target process terminates.
163
        ///
164
        /// This parameter is ignored if hTargetProcessHandle is NULL.
165
        /// </param>
166
        /// <param name="dwDesiredAccess">
167
        /// The access requested for the new handle. For the flags that can be specified for each object type, see the following Remarks section.
168
        ///
169
        /// This parameter is ignored if the dwOptions parameter specifies the DUPLICATE_SAME_ACCESS flag. Otherwise, the flags that can be specified depend on the type of object whose handle is to be duplicated.
170
        ///
171
        /// This parameter is ignored if hTargetProcessHandle is NULL.
172
        /// </param>
173
        /// <param name="bInheritHandle">
174
        /// A variable that indicates whether the handle is inheritable. If TRUE, the duplicate handle can be inherited by new processes created by the target process. If FALSE, the new handle cannot be inherited.
175
        ///
176
        /// This parameter is ignored if hTargetProcessHandle is NULL.
177
        /// </param>
178
        /// <param name="dwOptions">
179
        /// Optional actions.
180
        /// </param>
181
        /// <returns>
182
        /// If the function succeeds, the return value is nonzero.
183
        ///
184
        /// If the function fails, the return value is zero. To get extended error information, call GetLastError.
185
        /// </returns>
186
        /// <remarks>
187
        /// See https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle.
188
        /// </remarks>
189
        [DllImport("kernel32.dll", SetLastError = true)]
190
        [return: MarshalAs(UnmanagedType.Bool)]
191
        private static extern bool DuplicateHandle(
192
            IntPtr hSourceProcessHandle,
193
            IntPtr hSourceHandle,
194
            IntPtr hTargetProcessHandle,
195
            out IntPtr lpTargetHandle,
196
            uint dwDesiredAccess,
197
            [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle,
198
            DuplicateOptions dwOptions);
199

200
        private static Process GetInheritableCurrentProcessHandle()
201
        {
202
            if (!DuplicateHandle(Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, Process.GetCurrentProcess().Handle, out var inheritableCurrentProcessHandle, 0, true, DuplicateOptions.SameAccess))
203
            {
204
                Console.WriteLine("Failed to call DuplicateHandle: Win32 error code {0}", Marshal.GetLastWin32Error());
205
                return null;
206
            }
207

208
            return new ExistingProcess(inheritableCurrentProcessHandle);
209
        }
210
    }
211
    public class ExistingProcess : Process
212
    {
213
        public ExistingProcess(IntPtr handle)
214
        {
215
            SetHandle(handle);
216
        }
217

218
        private void SetHandle(IntPtr handle)
219
        {
220
            var baseType = GetType().BaseType;
221
            if (baseType == null)
222
                return;
223

224
            var setProcessHandleMethod = baseType.GetMethod("SetProcessHandle",
225
                BindingFlags.NonPublic | BindingFlags.Instance);
226
            setProcessHandleMethod?.Invoke(this, new object[] { new SafeProcessHandle(handle, true) });
227
        }
228
    }
229
    public class WindowsDalamudCompatibilityCheck : IDalamudCompatibilityCheck
230
    {
231
        public void EnsureCompatibility()
232
        {
233
            if (!CheckVcRedists())
234
                throw new IDalamudCompatibilityCheck.NoRedistsException();
235

236
            EnsureArchitecture();
237
        }
238

239
        private static void EnsureArchitecture()
240
        {
241
            var arch = RuntimeInformation.ProcessArchitecture;
242

243
            switch (arch)
244
            {
245
                case Architecture.X86:
246
                    throw new IDalamudCompatibilityCheck.ArchitectureNotSupportedException("Dalamud is not supported on x86 architecture.");
247

248
                case Architecture.X64:
249
                    break;
250

251
                case Architecture.Arm:
252
                    throw new IDalamudCompatibilityCheck.ArchitectureNotSupportedException("Dalamud is not supported on ARM32.");
253

254
                case Architecture.Arm64:
255
                    throw new IDalamudCompatibilityCheck.ArchitectureNotSupportedException("x64 emulation was not detected. Please make sure to run XIVLauncher with x64 emulation.");
256
            }
257
        }
258

259
        [DllImport("kernel32", SetLastError = true)]
260
        private static extern IntPtr LoadLibrary(string lpFileName);
261

262
        private static bool CheckLibrary(string fileName)
263
        {
264
            if (LoadLibrary(fileName) != IntPtr.Zero)
265
            {
266
                Console.WriteLine("Found " + fileName);
267
                return true;
268
            }
269
            else
270
            {
271
                Console.WriteLine("Could not find " + fileName);
272
            }
273
            return false;
274
        }
275

276
        private static bool CheckVcRedists()
277
        {
278
            // snipped from https://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
279
            // and https://github.com/bitbeans/RedistributableChecker
280

281
            var vc2022Paths = new List<string>
282
        {
283
            @"SOFTWARE\Microsoft\DevDiv\VC\Servicing\14.0\RuntimeMinimum",
284
            @"SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64",
285
            @"SOFTWARE\Classes\Installer\Dependencies\Microsoft.VS.VC_RuntimeMinimumVSU_amd64,v14",
286
            @"SOFTWARE\Classes\Installer\Dependencies\VC,redist.x64,amd64,14.31,bundle",
287
            @"SOFTWARE\Classes\Installer\Dependencies\VC,redist.x64,amd64,14.30,bundle",
288
            @"SOFTWARE\Classes\Installer\Dependencies\VC,redist.x64,amd64,14.29,bundle",
289
            @"SOFTWARE\Classes\Installer\Dependencies\VC,redist.x64,amd64,14.28,bundle",
290
            // technically, this was introduced in VCrun2017 with 14.16
291
            // but we shouldn't go that far
292
            // here's a legacy vcrun2017 check
293
            @"Installer\Dependencies\,,amd64,14.0,bundle",
294
            // here's one for vcrun2015
295
            @"SOFTWARE\Classes\Installer\Dependencies\{d992c12e-cab2-426f-bde3-fb8c53950b0d}"
296
        };
297

298
            var dllPaths = new List<string>
299
        {
300
            "ucrtbase_clr0400",
301
            "vcruntime140_clr0400",
302
            "vcruntime140"
303
        };
304

305
            var passedRegistry = false;
306
            var passedDllChecks = true;
307

308
            foreach (var path in vc2022Paths)
309
            {
310
                Console.WriteLine("Checking Registry key: " + path);
311
                var vcregcheck = Registry.LocalMachine.OpenSubKey(path, false);
312
                if (vcregcheck == null) continue;
313

314
                var vcVersioncheck = vcregcheck.GetValue("Version") ?? "";
315

316
                if (((string)vcVersioncheck).StartsWith("14", StringComparison.Ordinal))
317
                {
318
                    passedRegistry = true;
319
                    Console.WriteLine("Passed Registry Check with: " + path);
320
                    break;
321
                }
322
            }
323

324
            foreach (var path in dllPaths)
325
            {
326
                Console.WriteLine("Checking for DLL: " + path);
327
                passedDllChecks = passedDllChecks && CheckLibrary(path);
328
            }
329

330
            // Display our findings
331
            if (!passedRegistry)
332
            {
333
                Console.WriteLine("Failed all registry checks to find any Visual C++ 2015-2022 Runtimes.");
334
            }
335

336
            if (!passedDllChecks)
337
            {
338
                Console.WriteLine("Missing DLL files required by Dalamud.");
339
            }
340

341
            return (passedRegistry && passedDllChecks);
342
        }
343
    }
344
    public class WindowsRestartManager : IDisposable
345
    {
346
        public delegate void RmWriteStatusCallback(uint percentageCompleted);
347

348
        private const int RM_SESSION_KEY_LEN = 16; // sizeof GUID
349
        private const int CCH_RM_SESSION_KEY = RM_SESSION_KEY_LEN * 2;
350
        private const int CCH_RM_MAX_APP_NAME = 255;
351
        private const int CCH_RM_MAX_SVC_NAME = 63;
352
        private const int RM_INVALID_TS_SESSION = -1;
353
        private const int RM_INVALID_PROCESS = -1;
354
        private const int ERROR_MORE_DATA = 234;
355

356
        [StructLayout(LayoutKind.Sequential)]
357
        public struct RmUniqueProcess
358
        {
359
            public int dwProcessId; // PID
360
            public FILETIME ProcessStartTime; // Process creation time
361
        }
362

363
        public enum RmAppType
364
        {
365
            /// <summary>
366
            /// Application type cannot be classified in known categories
367
            /// </summary>
368
            RmUnknownApp = 0,
369

370
            /// <summary>
371
            /// Application is a windows application that displays a top-level window
372
            /// </summary>
373
            RmMainWindow = 1,
374

375
            /// <summary>
376
            /// Application is a windows app but does not display a top-level window
377
            /// </summary>
378
            RmOtherWindow = 2,
379

380
            /// <summary>
381
            /// Application is an NT service
382
            /// </summary>
383
            RmService = 3,
384

385
            /// <summary>
386
            /// Application is Explorer
387
            /// </summary>
388
            RmExplorer = 4,
389

390
            /// <summary>
391
            /// Application is Console application
392
            /// </summary>
393
            RmConsole = 5,
394

395
            /// <summary>
396
            /// Application is critical system process where a reboot is required to restart
397
            /// </summary>
398
            RmCritical = 1000,
399
        }
400

401
        [Flags]
402
        public enum RmRebootReason
403
        {
404
            /// <summary>
405
            /// A system restart is not required.
406
            /// </summary>
407
            RmRebootReasonNone = 0x0,
408

409
            /// <summary>
410
            /// The current user does not have sufficient privileges to shut down one or more processes.
411
            /// </summary>
412
            RmRebootReasonPermissionDenied = 0x1,
413

414
            /// <summary>
415
            /// One or more processes are running in another Terminal Services session.
416
            /// </summary>
417
            RmRebootReasonSessionMismatch = 0x2,
418

419
            /// <summary>
420
            /// A system restart is needed because one or more processes to be shut down are critical processes.
421
            /// </summary>
422
            RmRebootReasonCriticalProcess = 0x4,
423

424
            /// <summary>
425
            /// A system restart is needed because one or more services to be shut down are critical services.
426
            /// </summary>
427
            RmRebootReasonCriticalService = 0x8,
428

429
            /// <summary>
430
            /// A system restart is needed because the current process must be shut down.
431
            /// </summary>
432
            RmRebootReasonDetectedSelf = 0x10,
433
        }
434

435
        [Flags]
436
        private enum RmShutdownType
437
        {
438
            RmForceShutdown = 0x1, // Force app shutdown
439
            RmShutdownOnlyRegistered = 0x10 // Only shutdown apps if all apps registered for restart
440
        }
441

442
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
443
        public struct RmProcessInfo
444
        {
445
            public RmUniqueProcess UniqueProcess;
446

447
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
448
            public string AppName;
449

450
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
451
            public string ServiceShortName;
452

453
            public RmAppType ApplicationType;
454
            public int AppStatus;
455
            public int TSSessionId;
456

457
            [MarshalAs(UnmanagedType.Bool)]
458
            public bool bRestartable;
459

460
            public Process Process
461
            {
462
                get
463
                {
464
                    try
465
                    {
466
                        Process process = Process.GetProcessById(UniqueProcess.dwProcessId);
467
                        long fileTime = process.StartTime.ToFileTime();
468

469
                        if ((uint)UniqueProcess.ProcessStartTime.dwLowDateTime != (uint)(fileTime & uint.MaxValue))
470
                            return null;
471

472
                        if ((uint)UniqueProcess.ProcessStartTime.dwHighDateTime != (uint)(fileTime >> 32))
473
                            return null;
474

475
                        return process;
476
                    }
477
                    catch (Exception)
478
                    {
479
                        return null;
480
                    }
481
                }
482
            }
483
        }
484

485
        [DllImport("rstrtmgr", CharSet = CharSet.Unicode)]
486
        private static extern int RmStartSession(out int dwSessionHandle, int sessionFlags, StringBuilder strSessionKey);
487

488
        [DllImport("rstrtmgr")]
489
        private static extern int RmEndSession(int dwSessionHandle);
490

491
        [DllImport("rstrtmgr")]
492
        private static extern int RmShutdown(int dwSessionHandle, RmShutdownType lAtionFlags, RmWriteStatusCallback fnStatus);
493

494
        [DllImport("rstrtmgr")]
495
        private static extern int RmRestart(int dwSessionHandle, int dwRestartFlags, RmWriteStatusCallback fnStatus);
496

497
        [DllImport("rstrtmgr")]
498
        private static extern int RmGetList(int dwSessionHandle, out int nProcInfoNeeded, ref int nProcInfo, [In, Out] RmProcessInfo[] rgAffectedApps, out RmRebootReason dwRebootReasons);
499

500
        [DllImport("rstrtmgr", CharSet = CharSet.Unicode)]
501
        private static extern int RmRegisterResources(int dwSessionHandle,
502
                                                      int nFiles, string[] rgsFileNames,
503
                                                      int nApplications, RmUniqueProcess[] rgApplications,
504
                                                      int nServices, string[] rgsServiceNames);
505

506
        private readonly int sessionHandle;
507
        private readonly string sessionKey;
508

509
        public WindowsRestartManager()
510
        {
511
            var sessKey = new StringBuilder(CCH_RM_SESSION_KEY + 1);
512
            ThrowOnFailure(RmStartSession(out sessionHandle, 0, sessKey));
513
            sessionKey = sessKey.ToString();
514
        }
515

516
        public void Register(IEnumerable<FileInfo> files = null, IEnumerable<Process> processes = null, IEnumerable<string> serviceNames = null)
517
        {
518
            string[] filesArray = files?.Select(f => f.FullName).ToArray() ?? Array.Empty<string>();
519
            RmUniqueProcess[] processesArray = processes?.Select(f => new RmUniqueProcess
520
            {
521
                dwProcessId = f.Id,
522
                ProcessStartTime = new FILETIME
523
                {
524
                    dwLowDateTime = (int)(f.StartTime.ToFileTime() & uint.MaxValue),
525
                    dwHighDateTime = (int)(f.StartTime.ToFileTime() >> 32),
526
                }
527
            }).ToArray() ?? Array.Empty<RmUniqueProcess>();
528
            string[] servicesArray = serviceNames?.ToArray() ?? Array.Empty<string>();
529
            ThrowOnFailure(RmRegisterResources(sessionHandle,
530
                filesArray.Length, filesArray,
531
                processesArray.Length, processesArray,
532
                servicesArray.Length, servicesArray));
533
        }
534

535
        public void Shutdown(bool forceShutdown = true, bool shutdownOnlyRegistered = false, RmWriteStatusCallback cb = null)
536
        {
537
            ThrowOnFailure(RmShutdown(sessionHandle, (forceShutdown ? RmShutdownType.RmForceShutdown : 0) | (shutdownOnlyRegistered ? RmShutdownType.RmShutdownOnlyRegistered : 0), cb));
538
        }
539

540
        public void Restart(RmWriteStatusCallback cb = null)
541
        {
542
            ThrowOnFailure(RmRestart(sessionHandle, 0, cb));
543
        }
544

545
        public List<RmProcessInfo> GetInterferingProcesses(out RmRebootReason rebootReason)
546
        {
547
            var count = 0;
548
            var infos = new RmProcessInfo[count];
549
            var err = 0;
550

551
            for (var i = 0; i < 16; i++)
552
            {
553
                err = RmGetList(sessionHandle, out int needed, ref count, infos, out rebootReason);
554

555
                switch (err)
556
                {
557
                    case 0:
558
                        return infos.Take(count).ToList();
559

560
                    case ERROR_MORE_DATA:
561
                        infos = new RmProcessInfo[count = needed];
562
                        break;
563

564
                    default:
565
                        ThrowOnFailure(err);
566
                        break;
567
                }
568
            }
569

570
            ThrowOnFailure(err);
571

572
            // should not reach
573
            throw new InvalidOperationException();
574
        }
575

576
        private void ReleaseUnmanagedResources()
577
        {
578
            ThrowOnFailure(RmEndSession(sessionHandle));
579
        }
580

581
        public void Dispose()
582
        {
583
            ReleaseUnmanagedResources();
584
            GC.SuppressFinalize(this);
585
        }
586

587
        ~WindowsRestartManager()
588
        {
589
            ReleaseUnmanagedResources();
590
        }
591

592
        private void ThrowOnFailure(int err)
593
        {
594
            if (err != 0)
595
                throw new Win32Exception(err);
596
        }
597
    }
598
    public class WindowsSteam : ISteam
599
    {
600
        public WindowsSteam()
601
        {
602
            SteamUtils.OnGamepadTextInputDismissed += b => OnGamepadTextInputDismissed?.Invoke(b);
603
        }
604

605
        public void Initialize(uint appId)
606
        {
607
            // workaround because SetEnvironmentVariable doesn't actually touch the process environment on unix
608
            if (Environment.OSVersion.Platform == PlatformID.Unix)
609
            {
610
                [System.Runtime.InteropServices.DllImport("c")]
611
                static extern int setenv(string name, string value, int overwrite);
612

613
                setenv("SteamAppId", appId.ToString(), 1);
614
            }
615

616
            SteamClient.Init(appId);
617
        }
618

619
        public bool IsValid => SteamClient.IsValid;
620

621
        public bool BLoggedOn => SteamClient.IsLoggedOn;
622

623
        public bool BOverlayNeedsPresent => SteamUtils.DoesOverlayNeedPresent;
624

625
        public void Shutdown()
626
        {
627
            SteamClient.Shutdown();
628
        }
629

630
        public async Task<byte[]?> GetAuthSessionTicketAsync()
631
        {
632
            var ticket = await SteamUser.GetAuthSessionTicketAsync().ConfigureAwait(true);
633
            return ticket?.Data;
634
        }
635

636
        public bool IsAppInstalled(uint appId)
637
        {
638
            return SteamApps.IsAppInstalled(appId);
639
        }
640

641
        public string GetAppInstallDir(uint appId)
642
        {
643
            return SteamApps.AppInstallDir(appId);
644
        }
645

646
        public bool ShowGamepadTextInput(bool password, bool multiline, string description, int maxChars, string existingText = "")
647
        {
648
            return SteamUtils.ShowGamepadTextInput(password ? GamepadTextInputMode.Password : GamepadTextInputMode.Normal, multiline ? GamepadTextInputLineMode.MultipleLines : GamepadTextInputLineMode.SingleLine, description, maxChars, existingText);
649
        }
650

651
        public string GetEnteredGamepadText()
652
        {
653
            return SteamUtils.GetEnteredGamepadText();
654
        }
655

656
        public bool ShowFloatingGamepadTextInput(ISteam.EFloatingGamepadTextInputMode mode, int x, int y, int width, int height)
657
        {
658
            // Facepunch.Steamworks doesn't have this...
659
            return false;
660
        }
661

662
        public bool IsRunningOnSteamDeck() => false;
663

664
        public uint GetServerRealTime() => (uint)((DateTimeOffset)SteamUtils.SteamServerTime).ToUnixTimeSeconds();
665

666
        public void ActivateGameOverlayToWebPage(string url, bool modal = false)
667
        {
668
            SteamFriends.OpenWebOverlay(url, modal);
669
        }
670

671
        public event Action<bool> OnGamepadTextInputDismissed;
672
    }
673
}
674

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.