ResourceDomain
This commit is contained in:
@ -73,8 +73,9 @@ namespace RageCoop.Client
|
||||
}
|
||||
DevToolMenu.secondaryBoneIndexItem.AltTitle = Secondary.ToString();
|
||||
}
|
||||
private static void OnTick(object sender, EventArgs e)
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
if (!Util.ShouldBeRunning) { Abort(); }
|
||||
if (ToMark == null || !ToMark.Exists()) { return; }
|
||||
Update();
|
||||
Draw(Current);
|
||||
|
@ -2,8 +2,10 @@
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
@ -19,7 +21,7 @@ namespace RageCoop.Client
|
||||
/// </summary>
|
||||
internal class Main : Script
|
||||
{
|
||||
private bool _gameLoaded = false;
|
||||
private static bool _gameLoaded = false;
|
||||
internal static Version Version = typeof(Main).Assembly.GetName().Version;
|
||||
|
||||
internal static int LocalPlayerID = 0;
|
||||
@ -34,26 +36,27 @@ namespace RageCoop.Client
|
||||
internal static Chat MainChat = null;
|
||||
internal static Stopwatch Counter = new Stopwatch();
|
||||
internal static Logger Logger = null;
|
||||
|
||||
internal static ulong Ticked = 0;
|
||||
internal static Vector3 PlayerPosition;
|
||||
internal static Scripting.Resources Resources = null;
|
||||
private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
|
||||
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
public static Worker Worker;
|
||||
|
||||
public static string ScriptPath;
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public Main()
|
||||
{
|
||||
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
|
||||
ScriptPath = Filename;
|
||||
if (!Util.ShouldBeRunning) { return; }
|
||||
try
|
||||
{
|
||||
Settings = Util.ReadSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
|
||||
// GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
|
||||
Settings = new Settings();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
@ -68,7 +71,9 @@ namespace RageCoop.Client
|
||||
LogLevel=Settings.LogLevel,
|
||||
#endif
|
||||
};
|
||||
Resources = new Scripting.Resources();
|
||||
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
|
||||
SHVDN.ScriptDomain.CurrentDomain.Tick += DomainTick;
|
||||
Resources = new Resources();
|
||||
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
|
||||
{
|
||||
Tick += (object sender, EventArgs e) =>
|
||||
@ -91,21 +96,66 @@ namespace RageCoop.Client
|
||||
#if !NON_INTERACTIVE
|
||||
#endif
|
||||
MainChat = new Chat();
|
||||
Aborted += OnAborted;
|
||||
Tick += OnTick;
|
||||
Tick += (s, e) => { Scripting.API.Events.InvokeTick(); };
|
||||
Tick += (s, e) => { API.Events.InvokeTick(); };
|
||||
KeyDown += OnKeyDown;
|
||||
KeyDown += (s, e) => { Scripting.API.Events.InvokeKeyDown(s, e); };
|
||||
KeyUp += (s, e) => { Scripting.API.Events.InvokeKeyUp(s, e); };
|
||||
KeyDown += (s, e) => { API.Events.InvokeKeyDown(s, e); };
|
||||
KeyUp += (s, e) => { API.Events.InvokeKeyUp(s, e); };
|
||||
Aborted += (object sender, EventArgs e) => Disconnected("Abort");
|
||||
|
||||
Util.NativeMemory();
|
||||
Counter.Restart();
|
||||
}
|
||||
|
||||
private static void OnAborted(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
ResourceDomain.Unload();
|
||||
SHVDN.ScriptDomain.CurrentDomain.Tick -= DomainTick;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DomainTick(object sender, EventArgs e)
|
||||
{
|
||||
while(TaskQueue.TryDequeue(out var task))
|
||||
{
|
||||
try
|
||||
{
|
||||
task.Invoke();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
try
|
||||
{
|
||||
EntityPool.DoSync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static void QueueToMainThread(Action task)
|
||||
{
|
||||
TaskQueue.Enqueue(task);
|
||||
}
|
||||
|
||||
public static Ped P;
|
||||
public static float FPS;
|
||||
private bool _lastDead;
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
private static bool _lastDead;
|
||||
private static void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
P = Game.Player.Character;
|
||||
PlayerPosition = P.ReadPosition();
|
||||
@ -135,14 +185,6 @@ namespace RageCoop.Client
|
||||
{
|
||||
Game.TimeScale = 1;
|
||||
}
|
||||
try
|
||||
{
|
||||
EntityPool.DoSync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
}
|
||||
|
||||
if (Networking.ShowNetworkInfo)
|
||||
{
|
||||
@ -153,7 +195,7 @@ namespace RageCoop.Client
|
||||
|
||||
MainChat.Tick();
|
||||
PlayerList.Tick();
|
||||
if (!Scripting.API.Config.EnableAutoRespawn)
|
||||
if (!API.Config.EnableAutoRespawn)
|
||||
{
|
||||
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
|
||||
Function.Call(Hash.IGNORE_NEXT_RESTART, true);
|
||||
@ -167,8 +209,8 @@ namespace RageCoop.Client
|
||||
{
|
||||
P.Health = 1;
|
||||
Game.Player.WantedLevel = 0;
|
||||
Main.Logger.Debug("Player died.");
|
||||
Scripting.API.Events.InvokePlayerDied();
|
||||
Logger.Debug("Player died.");
|
||||
API.Events.InvokePlayerDied();
|
||||
}
|
||||
GTA.UI.Screen.StopEffects();
|
||||
}
|
||||
@ -185,8 +227,16 @@ namespace RageCoop.Client
|
||||
_lastDead = P.IsDead;
|
||||
Ticked++;
|
||||
}
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
private static void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.U)
|
||||
{
|
||||
ResourceDomain.Unload();
|
||||
}
|
||||
if (e.KeyCode == Keys.L)
|
||||
{
|
||||
ResourceDomain.Load();
|
||||
}
|
||||
if (MainChat.Focused)
|
||||
{
|
||||
MainChat.OnKeyDown(e.KeyCode);
|
||||
|
@ -16,7 +16,7 @@ using System.Resources;
|
||||
|
||||
|
||||
// Version informationr(
|
||||
[assembly: AssemblyVersion("1.5.4.9")]
|
||||
[assembly: AssemblyFileVersion("1.5.4.9")]
|
||||
[assembly: AssemblyVersion("1.5.4.105")]
|
||||
[assembly: AssemblyFileVersion("1.5.4.105")]
|
||||
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
||||
|
||||
|
@ -55,6 +55,7 @@
|
||||
<Compile Include="Scripting\API.cs" />
|
||||
<Compile Include="Scripting\BaseScript.cs" />
|
||||
<Compile Include="Scripting\ClientScript.cs" />
|
||||
<Compile Include="Scripting\ResourceDomain.cs" />
|
||||
<Compile Include="Scripting\Resources.cs" />
|
||||
<Compile Include="Security.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
@ -296,6 +297,17 @@
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="mscoree">
|
||||
<Guid>{5477469E-83B1-11D2-8B49-00A0C9B7C9C4}</Guid>
|
||||
<VersionMajor>2</VersionMajor>
|
||||
<VersionMinor>4</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\Fody.6.6.3\build\Fody.targets" Condition="Exists('..\packages\Fody.6.6.3\build\Fody.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
|
@ -93,10 +93,9 @@ namespace RageCoop.Client.Scripting
|
||||
{
|
||||
var vehicleModel = (Model)e.Args[1];
|
||||
vehicleModel.Request(1000);
|
||||
Vehicle veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
|
||||
while (veh == null)
|
||||
Vehicle veh;
|
||||
while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
|
||||
{
|
||||
veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
veh.CanPretendOccupants = false;
|
||||
|
109
RageCoop.Client/Scripting/ResourceDomain.cs
Normal file
109
RageCoop.Client/Scripting/ResourceDomain.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SHVDN;
|
||||
using RageCoop.Core;
|
||||
using GTA.Native;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public class ResourceDomain : MarshalByRefObject, IDisposable
|
||||
{
|
||||
public static ResourceDomain Instance;
|
||||
readonly ScriptDomain RootDomain;
|
||||
ScriptDomain CurrentDomain => ScriptDomain.CurrentDomain;
|
||||
internal ResourceDomain(ScriptDomain root)
|
||||
{
|
||||
RootDomain = root;
|
||||
|
||||
// Bridge to current ScriptDomain
|
||||
root.Tick += Tick;
|
||||
root.KeyEvent += KeyEvent;
|
||||
}
|
||||
public static void Load()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
throw new Exception("Already loaded");
|
||||
}
|
||||
ScriptDomain domain = null;
|
||||
try
|
||||
{
|
||||
var dir = Path.GetFullPath(@"RageCoop\Scripts");
|
||||
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
// Copy API assemblies
|
||||
var api = typeof(ResourceDomain).Assembly;
|
||||
File.Copy(api.Location, Path.Combine(dir, Path.GetFileName(api.Location)), true);
|
||||
foreach (var a in api.GetReferencedAssemblies())
|
||||
{
|
||||
var asm = Assembly.Load(a.FullName);
|
||||
if (string.IsNullOrEmpty(asm.Location))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
File.Copy(asm.Location, Path.Combine(dir, Path.GetFileName(asm.Location)), true);
|
||||
}
|
||||
|
||||
// Copy test script
|
||||
// File.Copy(@"M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\TestScript.dll", Path.Combine(dir, Path.GetFileName("TestScript.dll")), true);
|
||||
|
||||
// Load domain in main thread
|
||||
Main.QueueToMainThread(() =>
|
||||
{
|
||||
domain = ScriptDomain.Load(Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName, dir);
|
||||
domain.AppDomain.SetData("Console",ScriptDomain.CurrentDomain.AppDomain.GetData("Console"));
|
||||
Instance = (ResourceDomain)domain.AppDomain.CreateInstanceFromAndUnwrap(typeof(ResourceDomain).Assembly.Location, typeof(ResourceDomain).FullName, false, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { ScriptDomain.CurrentDomain }, null, null);
|
||||
domain.Start();
|
||||
});
|
||||
|
||||
// Wait till next tick
|
||||
GTA.Script.Yield();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GTA.UI.Notification.Show(ex.ToString());
|
||||
Main.Logger.Error(ex);
|
||||
if (domain != null)
|
||||
{
|
||||
ScriptDomain.Unload(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void Unload()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Instance.Dispose();
|
||||
ScriptDomain.Unload(Instance.CurrentDomain);
|
||||
Instance = null;
|
||||
}
|
||||
void Tick(object sender, EventArgs args)
|
||||
{
|
||||
CurrentDomain.DoTick();
|
||||
}
|
||||
void KeyEvent(Keys keys, bool status)
|
||||
{
|
||||
CurrentDomain.DoKeyEvent(keys, status);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
RootDomain.Tick -= Tick;
|
||||
RootDomain.KeyEvent -= KeyEvent;
|
||||
}
|
||||
}
|
||||
}
|
@ -40,8 +40,10 @@ namespace RageCoop.Client
|
||||
var flag = AnimationFlags.Loop;
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
|
||||
{
|
||||
if (LoadAnim(animDict) == null) { return; }
|
||||
|
||||
MainPed.Task.ClearAll();
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim(animDict), ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,18 +145,11 @@ namespace RageCoop.Client
|
||||
|
||||
private string LoadAnim(string anim)
|
||||
{
|
||||
ulong startTime = Util.GetTickCount64();
|
||||
|
||||
while (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
|
||||
if(!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
|
||||
{
|
||||
Script.Yield();
|
||||
Function.Call(Hash.REQUEST_ANIM_DICT, anim);
|
||||
if (Util.GetTickCount64() - startTime >= 1000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return anim;
|
||||
}
|
||||
}
|
||||
|
@ -283,7 +283,10 @@ namespace RageCoop.Client
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
|
||||
{
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, LoadAnim("skydive@base"), "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
// Skip update if animation is not loaded
|
||||
var dict = LoadAnim("skydive@base");
|
||||
if (dict == null) { return; }
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -312,7 +315,9 @@ namespace RageCoop.Client
|
||||
|
||||
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person", "chute_idle_right", 3))
|
||||
{
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim("skydive@parachute@first_person"), "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
var dict = LoadAnim("skydive@parachute@first_person");
|
||||
if (dict == null) { return; }
|
||||
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -3,6 +3,7 @@ using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -10,12 +11,44 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml.Serialization;
|
||||
using mscoree;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class Util
|
||||
{
|
||||
public static bool ShouldBeRunning => Main.ScriptPath == null || Main.ScriptPath.ToLower() == Path.Combine(Directory.GetCurrentDirectory(), @"Scripts\RageCoop\RageCoop.Client.dll").ToLower();
|
||||
public static IList<AppDomain> GetAppDomains()
|
||||
{
|
||||
IList<AppDomain> _IList = new List<AppDomain>();
|
||||
IntPtr enumHandle = IntPtr.Zero;
|
||||
ICorRuntimeHost host = new CorRuntimeHost();
|
||||
try
|
||||
{
|
||||
host.EnumDomains(out enumHandle);
|
||||
object domain = null;
|
||||
while (true)
|
||||
{
|
||||
host.NextDomain(enumHandle, out domain);
|
||||
if (domain == null) break;
|
||||
AppDomain appDomain = (AppDomain)domain;
|
||||
_IList.Add(appDomain);
|
||||
}
|
||||
return _IList;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.CloseEnum(enumHandle);
|
||||
Marshal.ReleaseComObject(host);
|
||||
}
|
||||
}
|
||||
public static SizeF ResolutionMaintainRatio
|
||||
{
|
||||
get
|
||||
|
@ -25,6 +25,7 @@ namespace RageCoop.Client
|
||||
private static bool _trafficEnabled;
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
if (!Util.ShouldBeRunning) { Abort(); }
|
||||
if (Game.IsLoading || !Networking.IsOnServer)
|
||||
{
|
||||
return;
|
||||
|
@ -26,15 +26,16 @@ namespace RageCoop.Core
|
||||
{
|
||||
private static readonly HashSet<string> ToIgnore = new HashSet<string>()
|
||||
{
|
||||
"RageCoop.Client.dll",
|
||||
"RageCoop.Core.dll",
|
||||
"RageCoop.Server.dll",
|
||||
"ScriptHookVDotNet3.dll",
|
||||
"ScriptHookVDotNet.dll"
|
||||
"RageCoop.Client",
|
||||
"RageCoop.Core",
|
||||
"RageCoop.Server",
|
||||
"ScriptHookVDotNet2",
|
||||
"ScriptHookVDotNet3",
|
||||
"ScriptHookVDotNet"
|
||||
};
|
||||
public static bool CanBeIgnored(this string name)
|
||||
{
|
||||
return ToIgnore.Contains(name);
|
||||
return ToIgnore.Contains(Path.GetFileNameWithoutExtension(name));
|
||||
}
|
||||
public static void GetBytesFromObject(object obj, NetOutgoingMessage m)
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ using System.Resources;
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Version information
|
||||
[assembly: AssemblyVersion("1.5.4.10")]
|
||||
[assembly: AssemblyFileVersion("1.5.4.10")]
|
||||
[assembly: AssemblyVersion("1.5.4.40")]
|
||||
[assembly: AssemblyFileVersion("1.5.4.40")]
|
||||
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
|
||||
|
||||
|
19
TestScript/Test.cs
Normal file
19
TestScript/Test.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using GTA;
|
||||
using GTA.UI;
|
||||
|
||||
namespace TestScript
|
||||
{
|
||||
public class Test : Script
|
||||
{
|
||||
public Test()
|
||||
{
|
||||
Tick += OnTick;
|
||||
}
|
||||
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
Screen.ShowHelpTextThisFrame("bruh");
|
||||
}
|
||||
}
|
||||
}
|
17
TestScript/TestScript.csproj
Normal file
17
TestScript/TestScript.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<OutDir>..\bin\Debug</OutDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="ScriptHookVDotNet">
|
||||
<HintPath>..\libs\ScriptHookVDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ScriptHookVDotNet3">
|
||||
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user