Skip to content

Commit b9bd686

Browse files
committed
Add new draft exports for v0.1.1 API Standard
+ LaunchGameFromGameManager(nint gameManagerP, nint pluginP, nint printGameLogCallbackP, ref Guid cancelToken, out nint taskResult) + IsGameRunningDelegate(nint gameManagerP, out int isGameRunning)
1 parent 00bee97 commit b9bd686

File tree

3 files changed

+298
-6
lines changed

3 files changed

+298
-6
lines changed

SharedStatic.Generic.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using Hi3Helper.Plugin.Core.Management;
2+
using Hi3Helper.Plugin.Core.Utility;
3+
using Microsoft.Extensions.Logging;
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
using System.Runtime.InteropServices.Marshalling;
8+
using System.Threading;
9+
10+
using static Hi3Helper.Plugin.Core.Utility.GameManagerExtension;
11+
12+
namespace Hi3Helper.Plugin.Core;
13+
14+
public class SharedStatic<T> : SharedStatic where T : SharedStatic, new()
15+
{
16+
static SharedStatic()
17+
{
18+
ThisPluginExport = new T();
19+
20+
// Plugin optional exports (based on v0.1-update1 (v0.1.1) API Standard)
21+
// ---------------------------------------------------------------
22+
// These exports are optional and can be removed if it's not
23+
// necessarily used. These optional exports are included under
24+
// additional functionalities used as a subset of v0.1, which is
25+
// called "update1" feature sets.
26+
27+
// -> Plugin Async Game Launch Callback for Specific Game Region based on its IGameManager instance.
28+
TryRegisterApiExport<LaunchGameFromGameManagerDelegate>("LaunchGameFromGameManager", LaunchGameFromGameManager);
29+
// -> Plugin Game Run Check for Specific Game Region based on its IGameManager instance.
30+
TryRegisterApiExport<IsGameRunningDelegate>("IsGameRunning", IsGameRunning);
31+
}
32+
33+
private static readonly T ThisPluginExport;
34+
35+
public static unsafe int LaunchGameFromGameManager(nint gameManagerP, nint pluginP, nint printGameLogCallbackP, ref Guid cancelToken, out nint taskResult)
36+
{
37+
taskResult = nint.Zero;
38+
try
39+
{
40+
#if MANUALCOM
41+
#else
42+
IPlugin? plugin = ComInterfaceMarshaller<IPlugin>.ConvertToManaged((void*)pluginP);
43+
IGameManager? gameManager = ComInterfaceMarshaller<IGameManager>.ConvertToManaged((void*)gameManagerP);
44+
PrintGameLog printGameLogCallback = Marshal.GetDelegateForFunctionPointer<PrintGameLog>(printGameLogCallbackP);
45+
#endif
46+
47+
if (ThisPluginExport == null)
48+
{
49+
throw new NullReferenceException("The ThisPluginExport field is null!");
50+
}
51+
52+
if (gameManager == null)
53+
{
54+
throw new NullReferenceException("Cannot cast IGameException from the pointer, hence it gives null!");
55+
}
56+
57+
if (plugin == null)
58+
{
59+
throw new NullReferenceException("Cannot cast IPlugin from the pointer, hence it gives null!");
60+
}
61+
62+
if (printGameLogCallback == null)
63+
{
64+
throw new NullReferenceException("Cannot cast PrintGameLog callback from the pointer, hence it gives null!");
65+
}
66+
67+
CancellationTokenSource? cts = null;
68+
if (Unsafe.IsNullRef(ref cancelToken))
69+
{
70+
cts = ComCancellationTokenVault.RegisterToken(in cancelToken);
71+
}
72+
73+
taskResult = ThisPluginExport.LaunchGameFromGameManagerCoreAsync(gameManager,
74+
plugin,
75+
printGameLogCallback,
76+
cts?.Token ?? CancellationToken.None).AsResult();
77+
return 0;
78+
}
79+
catch (Exception ex)
80+
{
81+
// ignored
82+
InstanceLogger.LogError(ex, "An error has occurred while trying to call LaunchGameFromGameManager() from the plugin!");
83+
return Marshal.GetHRForException(ex);
84+
}
85+
}
86+
87+
public static unsafe int IsGameRunning(nint gameManagerP, out int isGameRunning)
88+
{
89+
isGameRunning = 0;
90+
91+
try
92+
{
93+
#if MANUALCOM
94+
#else
95+
IGameManager? gameManager = ComInterfaceMarshaller<IGameManager>.ConvertToManaged((void*)gameManagerP);
96+
#endif
97+
98+
if (gameManager == null)
99+
{
100+
return 0;
101+
}
102+
103+
isGameRunning = ThisPluginExport.IsGameRunningCore(gameManager) ? 1 : 0;
104+
return 1;
105+
}
106+
catch (Exception ex)
107+
{
108+
// ignored
109+
InstanceLogger.LogError(ex, "An error has occurred while trying to call IsGameRunning() from the plugin!");
110+
return Marshal.GetHRForException(ex);
111+
}
112+
}
113+
}

SharedStatic.cs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using Hi3Helper.Plugin.Core.Management;
2+
using Hi3Helper.Plugin.Core.Update;
23
using Hi3Helper.Plugin.Core.Utility;
34
using Microsoft.Extensions.Logging;
45
using System;
56
using System.Collections.Generic;
67
using System.Runtime.InteropServices;
78
using System.Runtime.InteropServices.Marshalling;
8-
using Hi3Helper.Plugin.Core.Update;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using static Hi3Helper.Plugin.Core.Utility.GameManagerExtension;
912
// ReSharper disable CommentTypo
1013

1114
#if MANUALCOM
@@ -28,7 +31,7 @@ namespace Hi3Helper.Plugin.Core;
2831
/// A delegate to a callback which returns a list of IP addresses resolved from the <paramref name="hostname"/>.
2932
/// </summary>
3033
/// <remarks>
31-
/// DO NOT FREE THE POINTER GIVEN BY THIS DELEGATE! The pointers are borrowed and will be automatically cleared by the plugin.
34+
/// DO NOT FREE THE POINTER GIVEN BY THIS DELEGATE TO AVOID <see cref="ExecutionEngineException"/>! The pointers are borrowed and will be automatically cleared by the plugin.
3235
/// </remarks>
3336
/// <param name="hostname">[In] A hostname to resolve to.</param>
3437
/// <param name="ipResolvedWriteBuffer">[Ref] A pointer to the buffer in which the main application will write the UTF-16 unsigned string (with null terminator) to.</param>
@@ -49,6 +52,10 @@ namespace Hi3Helper.Plugin.Core;
4952
/// </summary>
5053
public delegate void VoidCallback();
5154

55+
/// <summary>
56+
/// Shared export class for the plugin.<br/>
57+
/// This shared static class contains necessary methods for the plugin and has been a part of v0.1 API Standard.
58+
/// </summary>
5259
public class SharedStatic
5360
{
5461
static unsafe SharedStatic()
@@ -71,10 +78,10 @@ static unsafe SharedStatic()
7178
// -> Plugin DNS Resolver Callback Setter export
7279
TryRegisterApiExport<SetCallbackPointerDelegate>("SetDnsResolverCallback", SetDnsResolverCallback);
7380

74-
// Plugin optional exports (based on v0.1-update1 API Standard)
81+
// Plugin optional exports (based on v0.1-update1 (v0.1.1) API Standard)
7582
// ---------------------------------------------------------------
7683
// These exports are optional and can be removed if it's not
77-
// necesarilly used. These optional exports are included under
84+
// necessarily used. These optional exports are included under
7885
// additional functionalities used as a subset of v0.1, which is
7986
// called "update1" feature sets.
8087

@@ -117,6 +124,9 @@ static unsafe SharedStatic()
117124
protected unsafe delegate void GetPluginUpdateCdnListDelegate(int* count, ushort*** ptr);
118125
protected delegate void SetCallbackPointerDelegate(nint callbackP);
119126

127+
internal delegate int LaunchGameFromGameManagerDelegate(nint gameManagerP, nint pluginP, nint printGameLogCallbackP, ref Guid cancelToken, out nint taskResult);
128+
internal delegate int IsGameRunningDelegate(nint gameManagerP, out int isGameRunning);
129+
120130
/// <summary>
121131
/// Gets the array of CDN URLs used by the launcher to perform an update.
122132
/// </summary>
@@ -306,7 +316,7 @@ currentLocale[range[0]].Length is < 2 or > 3 ||
306316
/// Specify which <see cref="IPlugin"/> instance to load and use in this plugin.
307317
/// </summary>
308318
/// <typeparam name="TPlugin">A member of COM Interface of <see cref="IPlugin"/>.</typeparam>
309-
protected static void Load<TPlugin>(GameVersion interceptDllVersionTo = default)
319+
protected static void Load<TPlugin>(SharedStatic thisExportType, GameVersion interceptDllVersionTo = default)
310320
where TPlugin : class, IPlugin, new()
311321
{
312322
if (interceptDllVersionTo != GameVersion.Empty)
@@ -357,5 +367,31 @@ public static unsafe int TryGetApiExportPointer(char* apiExportName, void** dele
357367
*delegateP = (void*)delegatePSafe;
358368
return 0;
359369
}
360-
#endregion
370+
#endregion
371+
372+
/// <summary>
373+
/// Perform game launch routine from this plugin.
374+
/// </summary>
375+
/// <param name="manager">Game manager of the current game region to check.</param>
376+
/// <param name="plugin">Plugin instance to check.</param>
377+
/// <param name="printGameLogCallback">A callback to send the log of the currently running game.</param>
378+
/// <param name="token">A cancellation token to cancel or kill the process of the game.</param>
379+
/// <returns>
380+
/// Returns <c>false</c> if the plugin doesn't have game launch mechanism (or API Standard is equal or lower than v0.1.0) or if this method isn't overriden.<br/>
381+
/// Otherwise, <c>true</c> if the plugin supports game launch mechanism.
382+
/// </returns>
383+
public virtual Task<bool> LaunchGameFromGameManagerCoreAsync(IGameManager manager, IPlugin plugin, PrintGameLog printGameLogCallback, CancellationToken token)
384+
{
385+
return Task.FromResult(false);
386+
}
387+
388+
/// <summary>
389+
/// Perform check whether the game is running or not.
390+
/// </summary>
391+
/// <param name="manager">Game manager of the current game region to check.</param>
392+
/// <returns></returns>
393+
public virtual bool IsGameRunningCore(IGameManager manager)
394+
{
395+
return false;
396+
}
361397
}

Utility/GameManagerExtension.cs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using Hi3Helper.Plugin.Core.Management;
2+
using System;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Runtime.InteropServices;
5+
using System.Runtime.InteropServices.Marshalling;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Hi3Helper.Plugin.Core.Utility;
10+
11+
/// <summary>
12+
/// This extension provides a method to launch the game using the plugin's game launch mechanism if available.
13+
/// </summary>
14+
/// <remarks>
15+
/// This extension IS ONLY SUPPOSEDLY BE USED by the launcher, NOT by the plugin.
16+
/// </remarks>
17+
public static class GameManagerExtension
18+
{
19+
public unsafe delegate void PrintGameLog(char* logString, int logStringLen, bool isStringCanFree);
20+
21+
public class RunGameFromGameManagerContext
22+
{
23+
public required IGameManager GameManager { get; init; }
24+
public required IPlugin Plugin { get; init; }
25+
public required nint PluginHandle { get; init; }
26+
public required PrintGameLog PrintGameLogCallback { get; init; }
27+
}
28+
29+
/// <summary>
30+
/// Launch the game using plugin's built-in game launch mechanism.
31+
/// </summary>
32+
/// <param name="context">The context to launch the game from <see cref="IGameManager"/>.</param>
33+
/// <param name="token">Cancellation token to pass into the plugin's game launch mechanism.</param>
34+
/// <returns>
35+
/// Returns <c>IsSuccess=false</c> if the plugin doesn't have game launch mechanism (or API Standard is equal or lower than v0.1.0), hence fallback to launcher's game launch mechanism.
36+
/// Otherwise, <c>IsSuccess=true</c> if the plugin does support game launch mechanism and the game ran successfully.
37+
/// </returns>
38+
public static async Task<(bool IsSuccess, Exception? Error)>
39+
RunGameFromGameManager(this RunGameFromGameManagerContext context,
40+
CancellationToken token)
41+
{
42+
ArgumentNullException.ThrowIfNull(context, nameof(context));
43+
if (!context.PluginHandle.TryGetExport("LaunchGameFromGameManager", out SharedStatic.LaunchGameFromGameManagerDelegate launchGameFromGameManagerCallback))
44+
{
45+
return (false, new NotSupportedException("Plugin doesn't have LaunchGameFromGameManager export in its API definition!"));
46+
}
47+
48+
nint gameManagerP = GetPointerFromInterface(context.GameManager);
49+
nint pluginP = GetPointerFromInterface(context.Plugin);
50+
nint printGameLogCallbackP = Marshal.GetFunctionPointerForDelegate(context.PrintGameLogCallback);
51+
52+
if (gameManagerP == nint.Zero)
53+
{
54+
return (false, new COMException("Cannot cast IGameManager interface to pointer!"));
55+
}
56+
57+
if (pluginP == nint.Zero)
58+
{
59+
return (false, new COMException("Cannot cast IPlugin interface to pointer!"));
60+
}
61+
62+
if (printGameLogCallbackP == nint.Zero)
63+
{
64+
return (false, new COMException("Cannot cast PrintGameLog delegate/callback to pointer!"));
65+
}
66+
67+
Guid cancelTokenGuid = Guid.CreateVersion7();
68+
int hResult = launchGameFromGameManagerCallback(gameManagerP, pluginP, printGameLogCallbackP, ref cancelTokenGuid, out nint taskResult);
69+
70+
if (taskResult == nint.Zero)
71+
{
72+
return (false, new NullReferenceException("ComAsyncResult pointer in taskReturn argument shouldn't return a null pointer!"));
73+
}
74+
75+
if (hResult != 0)
76+
{
77+
return (false, Marshal.GetExceptionForHR(hResult));
78+
}
79+
80+
try
81+
{
82+
bool isSuccess = await taskResult.AsTask<bool>();
83+
if (isSuccess)
84+
{
85+
return (true, null);
86+
}
87+
88+
throw new Exception("Failed to await ComAsyncResult as Task<bool>!");
89+
}
90+
catch (Exception ex)
91+
{
92+
return (false, ex);
93+
}
94+
}
95+
96+
private static unsafe nint GetPointerFromInterface<T>(this T interfaceSource)
97+
where T : class
98+
=> (nint)ComInterfaceMarshaller<T>.ConvertToUnmanaged(interfaceSource);
99+
100+
/// <summary>
101+
/// Check if the game from the current <see cref="IGameManager"/> is running or not.
102+
/// </summary>
103+
/// <param name="manager">The game manager instance which handles the game launch.</param>
104+
/// <param name="pluginHandle">The pointer to the plugin's library handle.</param>
105+
/// <param name="isGameRunning">Whether the game is currently running or not.</param>
106+
/// <param name="errorException">Represents an exception from HRESULT of the plugin's function.</param>
107+
/// <returns>
108+
/// To find the actual return value, please use <paramref name="isGameRunning"/> out-argument.<br/><br/>
109+
///
110+
/// Returns <c>false</c> if the plugin doesn't have game launch mechanism (or API Standard is equal or lower than v0.1.0).<br/>
111+
/// Otherwise, <c>true</c> if the plugin supports game launch mechanism.
112+
/// </returns>
113+
public static bool IsGameRunning(this IGameManager manager, nint pluginHandle, out bool isGameRunning, [NotNullWhen(false)] out Exception? errorException)
114+
{
115+
ArgumentNullException.ThrowIfNull(manager, nameof(manager));
116+
isGameRunning = false;
117+
errorException = null;
118+
119+
if (!pluginHandle.TryGetExport("IsGameRunning", out SharedStatic.IsGameRunningDelegate isGameRunningCallback))
120+
{
121+
errorException = new NotSupportedException("Plugin doesn't have IsGameRunning export in its API definition!");
122+
return false;
123+
}
124+
125+
nint gameManagerP = GetPointerFromInterface(manager);
126+
if (gameManagerP == nint.Zero)
127+
{
128+
errorException = new COMException("Cannot cast IGameManager interface to pointer!");
129+
return false;
130+
}
131+
132+
int hResult = isGameRunningCallback(gameManagerP, out int isGameRunningInt);
133+
134+
errorException = Marshal.GetExceptionForHR(hResult);
135+
if (errorException != null)
136+
{
137+
return false;
138+
}
139+
140+
isGameRunning = isGameRunningInt == 1;
141+
return true;
142+
}
143+
}

0 commit comments

Comments
 (0)