diff --git a/packages/scripts/src/core/github/commitMultipleFiles.ts b/packages/scripts/src/core/github/commitMultipleFiles.ts index c3b41765b..f8ee1ae82 100644 --- a/packages/scripts/src/core/github/commitMultipleFiles.ts +++ b/packages/scripts/src/core/github/commitMultipleFiles.ts @@ -32,7 +32,9 @@ export async function commitMultipleFiles( // we discard blob if they're the same size; it's unlikely their // contents will be different; I love playing russian roulette! - const equal = buffer.length === change.content.length || buffer.equals(change.content); + const equal = + buffer.length === change.content.length || + buffer.equals(change.content); if (!equal) { const diff = change.content.length - buffer.length; diff --git a/src/BlitzKit.CLI/BlitzKit.CLI.csproj b/src/BlitzKit.CLI/BlitzKit.CLI.csproj index 50d09a569..d50d97098 100644 --- a/src/BlitzKit.CLI/BlitzKit.CLI.csproj +++ b/src/BlitzKit.CLI/BlitzKit.CLI.csproj @@ -13,6 +13,8 @@ + + diff --git a/src/BlitzKit.CLI/Functions/Mangler.cs b/src/BlitzKit.CLI/Functions/Mangler.cs index 801dda85a..f00821514 100644 --- a/src/BlitzKit.CLI/Functions/Mangler.cs +++ b/src/BlitzKit.CLI/Functions/Mangler.cs @@ -5,6 +5,7 @@ using CUE4Parse.UE4.Assets.Exports.Engine; using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.UObject; using Google.Protobuf.Collections; using Newtonsoft.Json.Linq; @@ -14,8 +15,9 @@ public class Mangler { readonly Tanks tanks = new(); readonly BlitzProvider provider = new(); + readonly List changes = []; - public void Mangle() + public async Task Mangle() { var nationDirs = provider.RootDirectory.GetDirectory("Blitz/Content/Tanks").Directories; @@ -26,6 +28,8 @@ public void Mangle() MangleNation(nationDir.Value); } + + await BlitzKitAssets.CommitAssets("ue assets", changes); } void MangleNation(VFS nation) @@ -48,6 +52,9 @@ void MangleNation(VFS nation) { var pdaName = $"PDA_{tankDir.Name}"; var pda = provider.LoadObject($"{tankDir.Path}/{pdaName}.{pdaName}"); + + var tankId = PropertyUtil.Get(pda, "TankId").Text; + var hulls = PropertyUtil.Get(pda, "DT_Hulls"); UDataTableUtility.TryGetDataTableRow(hulls, "hull", StringComparison.Ordinal, out var hull); var hullVisualData = PropertyUtil.Get(hull, "VisualData"); @@ -55,9 +62,9 @@ void MangleNation(VFS nation) var hullCollision = PropertyUtil.Get(hullMeshSettings, "CollisionMesh"); MonoGltf hullCollisionGltf = new(hullCollision); - hullCollisionGltf.Write(); + changes.Add(new($"tanks/{tankId}/collision/hull.glb", hullCollisionGltf.Write())); - return new() { Id = "" }; + return new() { Id = tankId }; } Dictionary CrewTypes = new() diff --git a/src/BlitzKit.CLI/Models/BlitzKitAssets.cs b/src/BlitzKit.CLI/Models/BlitzKitAssets.cs new file mode 100644 index 000000000..522f32f3d --- /dev/null +++ b/src/BlitzKit.CLI/Models/BlitzKitAssets.cs @@ -0,0 +1,162 @@ +using System.Net; +using BlitzKit.CLI.Utils; +using DotNetEnv; +using Octokit; + +namespace BlitzKit.CLI.Models +{ + public static class BlitzKitAssets + { + public static int TIME_PER_BLOB = (int)Math.Pow(2, 4) * 10000; + public static int TIME_BETWEEN_BLOBS = (int)(60 * 60 * 1000 / 5000 / 0.9); + + public static async Task CommitAssets(string message, List changes) + { + Console.WriteLine($"Committing \"{message}\"... with {changes.Count} proposed changes"); + + if (changes.Count == 0) + return; + + await CommitMultipleFiles( + Env.GetString("PUBLIC_ASSET_REPO"), + Env.GetString("PUBLIC_ASSET_BRANCH"), + message, + changes + ); + } + + public static async Task CommitMultipleFiles( + string repoRaw, + string branch, + string message, + List changesRaw + ) + { + using HttpClient httpClient = new(); + + GitHubClient octokit = new(new ProductHeaderValue("MyAmazingApp")) + { + Credentials = new(Env.GetString("GH_TOKEN")), + }; + List changes = []; + + await Task.WhenAll( + changesRaw + .Select(async change => + { + var blobPath = $"{repoRaw}/{branch}/{change.Path}"; + + HttpResponseMessage response = await httpClient.GetAsync( + $"https://raw.githubusercontent.com/{blobPath}" + ); + + if (response.StatusCode == HttpStatusCode.NotFound) + { + Console.WriteLine( + $"🟢 (+{change.Content.Count.ToString("N0", Program.Culture)}B) {blobPath}" + ); + changes.Add(change); + } + else if (response.StatusCode == HttpStatusCode.OK) + { + var bytes = await response.Content.ReadAsByteArrayAsync(); + + // we discard blob if they're the same size; it's unlikely their + // contents will be different; I love playing russian roulette! + var equal = + bytes.Length == change.Content.Count || bytes.SequenceEqual([.. change.Content]); + if (!equal) + { + var diff = change.Content.Count - bytes.Length; + + Console.WriteLine( + $"🟡 ({(diff > 0 ? '+' : "")}{diff.ToString("N0", Program.Culture)}B) {blobPath}" + ); + + changes.Add(change); + } + } + else + { + throw new Exception($"Unexpected status code {response.StatusCode} for {blobPath}"); + } + }) + .ToList() + ); + + if (changes.Count == 0) + return; + + var repoRawSplit = repoRaw.Split('/'); + var owner = repoRawSplit[0]; + var repo = repoRawSplit[1]; + var latestCommitSha = (await octokit.Git.Reference.Get(owner, repo, $"heads/{branch}")) + .Object + .Sha; + var treeSha = (await octokit.Git.Commit.Get(owner, repo, latestCommitSha)).Tree.Sha; + NewTree newTree = new() { BaseTree = treeSha }; + + foreach (var change in changes) + { + while (true) + { + try + { + var blobSha = octokit + .Git.Blob.Create( + owner, + repo, + new() + { + Encoding = EncodingType.Base64, + Content = Convert.ToBase64String(change.Content), + } + ) + .Result.Sha; + + await Task.Delay(TIME_BETWEEN_BLOBS); + + newTree.Tree.Add( + new() + { + Sha = blobSha, + Path = change.Path, + Mode = "100644", + Type = TreeType.Blob, + } + ); + + break; + } + catch + { + PrettyLog.Warn($"Failed blob {change.Path}; retrying in {TIME_PER_BLOB}ms..."); + await Task.Delay(TIME_PER_BLOB); + } + } + } + + var treeData = await octokit.Git.Tree.Create(owner, repo, newTree); + var newCommit = await octokit.Git.Commit.Create( + owner, + repo, + new(message, treeData.Sha, latestCommitSha) + ); + await octokit.Git.Reference.Update(owner, repo, $"heads/{branch}", new(newCommit.Sha, true)); + } + } + + public class GithubChangeBlob + { + public required string Sha; + public required string Path; + public required string Mode; + public required string Type; + } + + public class FileChange(string path, ArraySegment content) + { + public string Path = path; + public ArraySegment Content = content; + } +} diff --git a/src/BlitzKit.CLI/Program.cs b/src/BlitzKit.CLI/Program.cs index 940572cf1..d0c86fc3c 100644 --- a/src/BlitzKit.CLI/Program.cs +++ b/src/BlitzKit.CLI/Program.cs @@ -1,12 +1,17 @@ -using BlitzKit.CLI.Functions; +using System.Globalization; +using BlitzKit.CLI.Functions; using BlitzKit.CLI.Utils; +using DotNetEnv; namespace BlitzKit.CLI { class Program { + public static CultureInfo Culture = new("en-US"); + static async Task Main(string[] args) { + Env.Load(); await AgnosticHelpers.Initialize(); switch (args[0]) @@ -21,7 +26,7 @@ static async Task Main(string[] args) { Mangler mangler = new(); - mangler.Mangle(); + await mangler.Mangle(); break; }