Editor/Core/Analysis/Inference/HandModelAssets.cs
using System.IO.Compression;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace HumanoidRigger;
/// <summary>Pinned, hash-checked model/runtime cache. Downloads never contain user model data.</summary>
public static class HandModelAssets
{
public const string ModelHash="db0898ae717b76b075d9bf563af315b29562e11f8df5027a1ef07b02bef6d81c";
public const string RuntimeHash="dec964ab1ee36cc9b0ae247d13b376627992fc57dec0454354017ab8fd84f1ea";
public static string CacheDirectory=>Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"sbox-humanoid-rigger","inference","hand-v1");
public static async Task<NativeHandModel> Load(string folder)
{
Directory.CreateDirectory(folder);
string model=Path.Combine(folder,"hand.onnx"),runtime=Path.Combine(folder,"onnxruntime.dll");
using var http=new HttpClient{Timeout=TimeSpan.FromSeconds(90)};
await Download(http,"https://media.githubusercontent.com/media/opencv/opencv_zoo/25f423d0e04c31a17254620e58febd7386da523b/models/handpose_estimation_mediapipe/handpose_estimation_mediapipe_2023feb.onnx",model,ModelHash);
if(!Valid(runtime,RuntimeHash))
{
string archive=Path.Combine(folder,"onnxruntime-1.23.2.zip");
await Download(http,"https://github.com/microsoft/onnxruntime/releases/download/v1.23.2/onnxruntime-win-x64-1.23.2.zip",archive,"0b38df9af21834e41e73d602d90db5cb06dbd1ca618948b8f1d66d607ac9f3cd");
using var zip=ZipFile.OpenRead(archive);
foreach(var name in new[]{"lib/onnxruntime.dll","LICENSE","ThirdPartyNotices.txt"})
{
var entry=zip.GetEntry("onnxruntime-win-x64-1.23.2/"+name)??throw new InvalidDataException("Missing runtime asset.");
string destination=Path.Combine(folder,Path.GetFileName(name)),temporary=destination+"."+Guid.NewGuid().ToString("N")+".tmp";
try{entry.ExtractToFile(temporary);if(name.EndsWith(".dll")&&!Valid(temporary,RuntimeHash))throw new InvalidDataException("Runtime checksum mismatch.");File.Move(temporary,destination,true);}
finally{if(File.Exists(temporary))File.Delete(temporary);}
}
}
if(!Valid(runtime,RuntimeHash)||!Valid(model,ModelHash))throw new InvalidDataException("Hand inference asset checksum mismatch.");
return new NativeHandModel(runtime,model);
}
public static bool Valid(string path,string hash)
{
if(!File.Exists(path))return false;
using var stream=File.OpenRead(path);return Convert.ToHexString(SHA256.HashData(stream)).Equals(hash,StringComparison.OrdinalIgnoreCase);
}
static async Task Download(HttpClient http,string url,string path,string hash)
{
if(Valid(path,hash))return;
string temporary=path+"."+Guid.NewGuid().ToString("N")+".tmp";
try
{
using var response=await http.GetAsync(url,HttpCompletionOption.ResponseHeadersRead);response.EnsureSuccessStatusCode();
using(var destination=File.Create(temporary))await response.Content.CopyToAsync(destination);
if(!Valid(temporary,hash))throw new InvalidDataException("Hand inference download checksum mismatch.");
File.Move(temporary,path,true);
}
finally{if(File.Exists(temporary))File.Delete(temporary);}
}
}