diff --git a/Cpp2IL/Cpp2IL.csproj b/Cpp2IL/Cpp2IL.csproj
index 93638976..f8af50ce 100644
--- a/Cpp2IL/Cpp2IL.csproj
+++ b/Cpp2IL/Cpp2IL.csproj
@@ -20,7 +20,7 @@
partial
true
-
+
@@ -31,6 +31,7 @@
+
diff --git a/Cpp2IL/Program.cs b/Cpp2IL/Program.cs
index 53251941..7158d131 100644
--- a/Cpp2IL/Program.cs
+++ b/Cpp2IL/Program.cs
@@ -6,6 +6,8 @@
using System.Linq;
using System.Reflection;
using System.Runtime;
+using System.Text;
+using System.Text.Json;
using CommandLine;
using Cpp2IL.Core;
using Cpp2IL.Core.Api;
@@ -35,7 +37,7 @@ private static void ResolvePathsFromCommandLine(string? gamePath, string? inputE
{
if (string.IsNullOrEmpty(gamePath))
throw new SoftException("No force options provided, and no game path was provided either. Please provide a game path or use the --force- options.");
-
+
//Somehow the above doesn't tell .net that gamePath can't be null on net472, so we do this stupid thing to avoid nullable warnings
#if NET472
gamePath = gamePath!;
@@ -55,6 +57,8 @@ private static void ResolvePathsFromCommandLine(string? gamePath, string? inputE
HandleXapk(gamePath, ref args);
else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() is ".ipa" or ".tipa")
HandleIpa(gamePath, ref args);
+ else if (File.Exists(gamePath) && Path.GetExtension(gamePath).ToLowerInvariant() == ".json")
+ HandleWebGL(gamePath, ref args);
else
{
if (!Cpp2IlPluginManager.TryProcessGamePath(gamePath, ref args))
@@ -487,6 +491,91 @@ private static void HandleIpa(string gamePath, ref Cpp2IlRuntimeArgs args)
args.Valid = true;
}
+ private static void HandleWebGL(string gamePath, ref Cpp2IlRuntimeArgs args)
+ {
+ Logger.VerboseNewline("Trying HandleWebGL as provided path is an json file");
+
+ Logger.InfoNewline($"Attempting to extract required file paths from WebGL json config {gamePath}", "WebGL");
+
+ var json = File.ReadAllText(gamePath);
+ var doc = JsonDocument.Parse(json) ?? throw new SoftException("WebGL config is null.");
+ var frameworkUrl = doc.RootElement.GetProperty("wasmFrameworkUrl").GetString() ?? throw new SoftException("wasmFrameworkUrl is null.");
+ var codeUrl = doc.RootElement.GetProperty("wasmCodeUrl").GetString() ?? throw new SoftException("wasmCodeUrl is null.");
+ var dataUrl = doc.RootElement.GetProperty("dataUrl").GetString() ?? throw new SoftException("dataUrl is null.");
+
+ var tempFileMeta = Path.GetTempFileName();
+ PathsToDeleteOnExit.Add(tempFileMeta);
+
+ var gameDir = Path.GetDirectoryName(gamePath) ?? "";
+ args.PathToAssembly = Path.Combine(gameDir, codeUrl);
+ args.WasmFrameworkJsFile = Path.Combine(gameDir, frameworkUrl);
+ args.PathToMetadata = tempFileMeta;
+
+ var dataBytes = File.ReadAllBytes(Path.Combine(gameDir, dataUrl));
+ var dataFiles = ParseUnityWebData(dataBytes);
+
+ if (dataFiles.TryGetValue("globalgamemanagers", out var globalGameManagers))
+ {
+ Logger.InfoNewline("Reading globalgamemanagers to determine unity version...", "WebGL");
+ var ggmBytes = new byte[0x40];
+ using var ggmStream = new MemoryStream(globalGameManagers);
+
+ // ReSharper disable once MustUseReturnValue
+ ggmStream.Read(ggmBytes, 0, 0x40);
+
+ args.UnityVersion = Cpp2IlApi.GetVersionFromGlobalGameManagers(ggmBytes);
+ }
+ else if (dataFiles.TryGetValue("data.unity3d", out var dataUnity3d))
+ {
+ Logger.InfoNewline("Reading data.unity3d to determine unity version...", "WebGL");
+ using var du3dStream = new MemoryStream(dataUnity3d);
+
+ args.UnityVersion = Cpp2IlApi.GetVersionFromDataUnity3D(du3dStream);
+ }
+ else
+ throw new SoftException("Could not find globalgamemanagers or unity3d inside UnityWebData.");
+
+ if (!dataFiles.TryGetValue("Il2CppData/Metadata/global-metadata.dat", out var globalMetadata))
+ throw new SoftException("Could not find global-metadata.dat inside UnityWebData.");
+ File.WriteAllBytes(tempFileMeta, globalMetadata);
+
+ Logger.InfoNewline($"Determined game's unity version to be {args.UnityVersion}", "WebGL");
+
+ args.Valid = true;
+ }
+
+ private static Dictionary ParseUnityWebData(byte[] bytes)
+ {
+ const string HEADER = "UnityWebData1.0\0";
+
+ var files = new Dictionary();
+ var offset = 0;
+
+ var header = Encoding.ASCII.GetString(bytes, offset, HEADER.Length);
+ if (header != HEADER)
+ throw new SoftException($"Invalid UnityWebData header \"{header}\".");
+ offset += HEADER.Length;
+
+ var headerEnd = BitConverter.ToUInt32(bytes, offset);
+ offset += 4;
+
+ while (offset < headerEnd)
+ {
+ var fileOffset = BitConverter.ToUInt32(bytes, offset); offset += 4;
+ var fileSize = BitConverter.ToUInt32(bytes, offset); offset += 4;
+ var filePathLength = BitConverter.ToUInt32(bytes, offset); offset += 4;
+
+ var filePath = Encoding.ASCII.GetString(bytes, offset, (int)filePathLength);
+ offset += (int)filePathLength;
+
+ var fileData = new byte[fileSize];
+ Array.Copy(bytes, fileOffset, fileData, 0, fileData.Length);
+ files[filePath] = fileData;
+ }
+
+ return files;
+ }
+
#if !NETFRAMEWORK
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "Cpp2IL.CommandLineArgs", "Cpp2IL")]
#endif
@@ -539,7 +628,7 @@ private static Cpp2IlRuntimeArgs GetRuntimeOptionsFromCommandLine(string[] comma
if (options.GamePath != null && options.GamePath.StartsWith("~"))
options.GamePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + options.GamePath[1..];
#endif
-
+
ResolvePathsFromCommandLine(options.GamePath, options.ExeName, ref result);
}
else