Skip to content

Commit 9a081f5

Browse files
committed
Plugins! / Moved Most Trademarked Content to Plugins (in the future, completely independent from vj0) / Detection/AES/Mappings API calls are done in Plugins
1 parent 84a1ee7 commit 9a081f5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+868
-452
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Text.Json;
2+
3+
using CUE4Parse.UE4.Versions;
4+
5+
using vj0.Plugins.Interfaces;
6+
using vj0.Shared.Framework.Base;
7+
8+
namespace vj0.Plugins.EpicGames.Detection;
9+
10+
public interface IEpicGamesDetection : IGameDetectionPlugin
11+
{
12+
public static BaseProfile? DetectGame(string appName, string pakPath, EGame version, EDetectedGameId gameId)
13+
{
14+
var launcherPath = GetLauncherInstalledPath();
15+
if (launcherPath is null) return null;
16+
17+
return (from install in launcherPath.InstallationList where install.AppName!.Equals(appName, StringComparison.OrdinalIgnoreCase) select Path.Combine(install.InstallLocation!, pakPath.TrimStart('\\'))
18+
into fullPath where Directory.Exists(fullPath)
19+
select new BaseProfile
20+
{
21+
Name = appName,
22+
ArchiveDirectory = fullPath,
23+
Version = version,
24+
AutoDetectedGameId = gameId
25+
}).FirstOrDefault();
26+
}
27+
28+
private static LauncherInstalled? GetLauncherInstalledPath()
29+
{
30+
return (from drive in DriveInfo.GetDrives() select Path.Combine(drive.Name, "ProgramData", "Epic", "UnrealEngineLauncher", "LauncherInstalled.dat") into path where File.Exists(path) select File.ReadAllText(path) into json select JsonSerializer.Deserialize<LauncherInstalled>(json)).FirstOrDefault();
31+
}
32+
33+
private class LauncherInstalled
34+
{
35+
public Installation[]? InstallationList { get; set; }
36+
}
37+
38+
/* ReSharper disable once ClassNeverInstantiated.Local */
39+
private class Installation
40+
{
41+
/* ReSharper disable once UnusedAutoPropertyAccessor.Local */
42+
public string? InstallLocation { get; set; }
43+
/* ReSharper disable once UnusedAutoPropertyAccessor.Local */
44+
public string? AppName { get; set; }
45+
}
46+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPublishable>false</IsPublishable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\..\..\Source\vj0.Shared\vj0.Shared.csproj" />
12+
<ProjectReference Include="..\..\vj0.Plugins\vj0.Plugins.csproj" />
13+
14+
<!-- Licensed ~~~~~~~~~~~ -->
15+
<PackageReference Include="JWT" Version="11.0.0-beta3" />
16+
<PackageReference Include="RestSharp" Version="112.1.1-alpha.0.4" />
17+
<PackageReference Include="RestSharp.Serializers.NewtonsoftJson" Version="112.1.1-alpha.0.4" />
18+
<!-- <- Licensed ~~~~~~~~~~~ -->
19+
</ItemGroup>
20+
21+
</Project>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using CUE4Parse.UE4.Versions;
2+
3+
using vj0.Plugins.EpicGames.Detection;
4+
using vj0.Shared.Framework.Base;
5+
6+
namespace vj0.Plugins.Fortnite.Detection;
7+
8+
public sealed class FortniteProfileDetectionPlugin : IEpicGamesDetection
9+
{
10+
public string Name => "Fortnite Profile Detection";
11+
12+
public async void Detect(List<BaseProfile> LoadedProfiles, Action<BaseProfile>? onDetected = null)
13+
{
14+
await IEpicGamesDetection.TryDetectGameAsync(
15+
gameId: EDetectedGameId.Fortnite,
16+
detectFunc: () =>
17+
{
18+
var latestVersion = EGame.GAME_UE5_LATEST;
19+
20+
/* ReSharper disable once ConditionIsAlwaysTrueOrFalse */
21+
if (latestVersion == EGame.GAME_UE5_6)
22+
{
23+
latestVersion = EGame.GAME_UE5_7;
24+
}
25+
26+
return IEpicGamesDetection.DetectGame("Fortnite", @"\FortniteGame\Content\Paks", latestVersion, EDetectedGameId.Fortnite);
27+
},
28+
onDetected,
29+
LoadedProfiles
30+
);
31+
}
32+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System.Text.RegularExpressions;
2+
3+
using Serilog;
4+
5+
using vj0.Plugins.Interfaces;
6+
using vj0.Plugins.Resolvers;
7+
using vj0.Shared;
8+
using vj0.Shared.Extensions;
9+
using vj0.Shared.Framework.Base;
10+
using vj0.Shared.Framework.CUEParse;
11+
12+
namespace vj0.Plugins.Fortnite.Resolvers;
13+
14+
public sealed class FortniteArchiveResolverPlugin : IArchiveResolverPlugin, IGameIdPlugin
15+
{
16+
public string Name => "Fortnite Archive Resolver";
17+
public EDetectedGameId GameId => EDetectedGameId.Fortnite;
18+
19+
public bool DoesCharacteristicsMatch(BaseProfile Profile)
20+
{
21+
return Profile.ArchiveDirectory.Contains("Fortnite", StringComparison.OrdinalIgnoreCase) && Regex.IsMatch(Profile.Name, @"^\d+(\.\d+){0,2}$");
22+
}
23+
24+
public async Task ResolveKeys(BaseProfile Profile)
25+
{
26+
var InherentlyMatches = ((IGamePlugin)this).DoesInherentlyMatch(Profile);
27+
28+
const string GEN_API_URL = $"https://fortnitecentral.genxgames.gg/api/v1/aes";
29+
var GIT_ARCHIVE_URL = $"https://raw.githubusercontent.com/Tectors/fortnite-aes-archive/refs/heads/master/api/archive/{Profile.Name}.json";
30+
31+
string API_URL;
32+
33+
if (InherentlyMatches)
34+
{
35+
API_URL = GEN_API_URL;
36+
} else {
37+
if (!StringExtensions.TryParseStringToDouble(Profile.Name, out var value))
38+
{
39+
return;
40+
}
41+
42+
var useGenAPI = value >= 18.00;
43+
API_URL = useGenAPI ? GEN_API_URL + $"?version={Profile.Name}" : GIT_ARCHIVE_URL;
44+
}
45+
46+
var aes = await SharedGlobal.API_Central.GetAesAsync(API_URL, useBaseUrl: false);
47+
48+
if (aes is null)
49+
{
50+
Log.Information("ResolveKeys Failed");
51+
52+
return;
53+
}
54+
55+
if (!string.IsNullOrWhiteSpace(aes.MainKey))
56+
{
57+
Profile.Encryption.MainKey = aes.MainKey;
58+
}
59+
60+
if (aes.DynamicKeys is not { Count: > 0 })
61+
{
62+
return;
63+
}
64+
65+
Profile.Encryption.Keys.Clear();
66+
67+
var newKeys = new List<EncryptionKey>();
68+
var dynamicGUIDs = new HashSet<string>();
69+
70+
foreach (var key in aes.DynamicKeys)
71+
{
72+
if (!EncryptionKey.IsValidKey(key.Key))
73+
{
74+
continue;
75+
}
76+
77+
newKeys.Add(key);
78+
dynamicGUIDs.Add(key.Guid);
79+
}
80+
81+
if (!InherentlyMatches)
82+
{
83+
Profile.Encryption.UnknownKeys.AddRange(newKeys);
84+
}
85+
else
86+
{
87+
Profile.Encryption.Keys.RemoveAll(k => k is null);
88+
Profile.Encryption.Keys.RemoveAll(k => dynamicGUIDs.Contains(k.Guid));
89+
Profile.Encryption.Keys.AddRange(newKeys);
90+
}
91+
}
92+
93+
/* This is only done for inherently matched Profiles */
94+
public async Task ResolveMappings(BaseProfile Profile)
95+
{
96+
var InherentlyMatches = ((IGamePlugin)this).DoesInherentlyMatch(Profile);
97+
if (InherentlyMatches) return;
98+
99+
if (!StringExtensions.TryParseStringToDouble(Profile.Name, out var value))
100+
{
101+
return;
102+
}
103+
104+
if (value >= 15.20)
105+
{
106+
var mapping = await SharedGlobal.API_Central.FetchMappingAsync(Profile.Name);
107+
108+
if (mapping is { LocalPath: not null })
109+
{
110+
Profile.MappingsContainer.Override = true;
111+
Profile.MappingsContainer.Path = mapping.LocalPath;
112+
}
113+
}
114+
}
115+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPublishable>false</IsPublishable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\..\..\Source\vj0.Shared\vj0.Shared.csproj" />
12+
<ProjectReference Include="..\..\vj0.Plugins\vj0.Plugins.csproj" />
13+
<ProjectReference Include="..\vj0.Plugins.EpicGames\vj0.Plugins.EpicGames.csproj" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using CUE4Parse.UE4.Versions;
2+
using Serilog;
3+
using vj0.Plugins.Interfaces;
4+
using vj0.Shared.Framework.Base;
5+
6+
namespace vj0.Plugins.Valorant.Detection;
7+
8+
public sealed class ValorantProfileDetectionPlugin : IGameDetectionPlugin
9+
{
10+
public string Name => "Valorant Profile Detection";
11+
12+
public async void Detect(List<BaseProfile> LoadedProfiles, Action<BaseProfile>? onDetected = null)
13+
{
14+
await IGameDetectionPlugin.TryDetectGameAsync(
15+
gameId: EDetectedGameId.Valorant,
16+
detectFunc: () =>
17+
DetectValorantGame("VALORANT", @"\ShooterGame\Content\Paks", EGame.GAME_Valorant, EDetectedGameId.Valorant),
18+
onDetected,
19+
LoadedProfiles
20+
);
21+
}
22+
23+
#pragma warning disable CA1416
24+
private static BaseProfile? DetectValorantGame(string appName, string pakPath, EGame version, EDetectedGameId gameId)
25+
{
26+
try
27+
{
28+
var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\tof_launcher");
29+
if (key is not null)
30+
{
31+
var installPath = key.GetValue("GameInstallPath") as string;
32+
if (!string.IsNullOrWhiteSpace(installPath))
33+
{
34+
var fullPath = Path.Combine(installPath, pakPath.TrimStart('\\'));
35+
if (Directory.Exists(fullPath))
36+
{
37+
return new BaseProfile
38+
{
39+
Name = appName,
40+
ArchiveDirectory = fullPath,
41+
Version = version,
42+
AutoDetectedGameId = gameId
43+
};
44+
}
45+
}
46+
}
47+
}
48+
catch (Exception ex)
49+
{
50+
Log.Warning(ex, "Failed to detect VALORANT");
51+
}
52+
53+
return null;
54+
}
55+
#pragma warning restore CA1416
56+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPublishable>false</IsPublishable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<ProjectReference Include="..\..\..\Source\vj0.Shared\vj0.Shared.csproj" />
12+
<ProjectReference Include="..\..\vj0.Plugins\vj0.Plugins.csproj" />
13+
</ItemGroup>
14+
15+
</Project>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Serilog;
2+
using vj0.Shared.Framework.Base;
3+
4+
namespace vj0.Plugins.Interfaces;
5+
6+
public interface IGameDetectionPlugin : IPlugin
7+
{
8+
void Detect(List<BaseProfile> LoadedProfiles, Action<BaseProfile>? onDetected = null);
9+
10+
public static Task TryDetectGameAsync(
11+
EDetectedGameId gameId,
12+
Func<BaseProfile?> detectFunc,
13+
Action<BaseProfile>? onDetected,
14+
List<BaseProfile> LoadedProfiles)
15+
{
16+
var detectedProfile = detectFunc();
17+
if (detectedProfile is not null)
18+
{
19+
Log.Information($"Detected {detectedProfile.Name} at {detectedProfile.ArchiveDirectory}");
20+
21+
LoadedProfiles.Add(detectedProfile);
22+
23+
onDetected?.Invoke(detectedProfile);
24+
}
25+
26+
return Task.CompletedTask;
27+
}
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using vj0.Shared.Framework.Base;
2+
3+
namespace vj0.Plugins.Interfaces;
4+
5+
public interface IGameIdPlugin : IGamePlugin
6+
{
7+
EDetectedGameId GameId => EDetectedGameId.None;
8+
9+
bool IGamePlugin.DoesInherentlyMatch(BaseProfile profile) => profile.AutoDetectedGameId == GameId;
10+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using vj0.Shared.Framework.Base;
2+
3+
namespace vj0.Plugins.Interfaces;
4+
5+
public interface IGamePlugin : IPlugin
6+
{
7+
/* Does it match at all? */
8+
public bool Match(BaseProfile profile, out string reason)
9+
{
10+
reason = string.Empty;
11+
12+
if (DoesCharacteristicsMatch(profile))
13+
{
14+
reason = "Characteristics matched";
15+
return true;
16+
}
17+
18+
if (DoesInherentlyMatch(profile))
19+
{
20+
reason = "Inherently matched";
21+
return true;
22+
}
23+
24+
return false;
25+
}
26+
27+
/* Checks if the profile isn't auto-detected but has the characteristics of one. */
28+
public bool DoesCharacteristicsMatch(BaseProfile Profile);
29+
30+
/* Checks if the profile matches inherently (e.g., auto-detected). */
31+
public bool DoesInherentlyMatch(BaseProfile Profile);
32+
}

0 commit comments

Comments
 (0)