Editor/HumanoidMocap/Inference/LegacyTorchCheckpoint.cs
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;

namespace HumanoidMocap.Inference;

public sealed partial class TorchCheckpoint
{
    /// <summary>Repackages a legacy PyTorch tensor dictionary into the ZIP format
    /// using inert metadata and storage bytes only. No pickle callable executes.
    /// The source is preserved and an existing destination is never overwritten.</summary>
    public static void ConvertLegacy(string source,string destination,CancellationToken cancellation=default)
    {
        using var input=File.OpenRead(source);
        var header=new byte[checked((int)Math.Min(input.Length,MaximumMetadataBytes))];input.ReadExactly(header);
        // torch.serialization MAGIC_NUMBER in its protocol-2 LONG1 envelope.
        var magic=Convert.FromHexString("80028A0A6CFC9C46F9206AA850192E");
        if(header.Length<magic.Length||!header.AsSpan(0,magic.Length).SequenceEqual(magic))
            throw new InvalidDataException("Not a supported legacy PyTorch checkpoint.");
        var position=magic.Length;
        object? Read(){var reader=new DataReader(header,position);var value=reader.Read(requireEnd:false);position=reader.Position;return value;}
        if(Read() is not int protocol||protocol!=1001)throw new InvalidDataException("Unsupported legacy checkpoint protocol.");
        if(Read() is not Dictionary<object,object?> system||!system.TryGetValue("little_endian",out var endian)||!Equals(endian,true))
            throw new InvalidDataException("Only little-endian legacy checkpoints are supported.");
        var metadataStart=position;
        if(Read() is not Dictionary<object,object?> state)throw new InvalidDataException("Expected a flat tensor dictionary.");
        var metadataLength=position-metadataStart;
        if(state.Values.Any(v=>v is not TensorRef))throw new InvalidDataException("Legacy import supports tensor dictionaries only.");
        var storages=new Dictionary<string,StorageRef>(StringComparer.Ordinal);
        foreach(var tensor in state.Values.Cast<TensorRef>())
        {
            var storage=tensor.Storage;
            if(storage.Key.Length==0||storage.Key.Any(c=>!char.IsAsciiDigit(c))||storage.Count<0||storage.Count>MaximumTensorElements)
                throw new InvalidDataException("Invalid legacy storage.");
            _=ElementSize(storage.Dtype);
            if(storages.TryGetValue(storage.Key,out var existing)&&existing!=storage)throw new InvalidDataException("Conflicting legacy storage metadata.");
            storages[storage.Key]=storage;
        }
        if(Read() is not List<object?> order||order.Count!=storages.Count||order.Any(k=>k is not string s||!storages.ContainsKey(s))
            ||order.Distinct().Count()!=order.Count)throw new InvalidDataException("Legacy storage index does not match tensor data.");
        input.Position=position;
        var locations=new List<(StorageRef Storage,long Offset,long Bytes)>();
        using var binary=new BinaryReader(input,Encoding.UTF8,leaveOpen:true);
        foreach(var key in order.Cast<string>())
        {
            cancellation.ThrowIfCancellationRequested();var storage=storages[key];
            if(binary.ReadInt64()!=storage.Count)throw new InvalidDataException("Legacy storage length differs from metadata.");
            var bytes=checked(storage.Count*ElementSize(storage.Dtype));
            if(bytes>input.Length-input.Position)throw new EndOfStreamException("Truncated legacy tensor data.");
            locations.Add((storage,input.Position,bytes));input.Position+=bytes;
        }
        if(input.Position!=input.Length)throw new InvalidDataException("Trailing legacy checkpoint data.");
        var temporary=Path.GetFullPath(destination)+"."+Guid.NewGuid().ToString("N")+".tmp";
        try
        {
            using(var archive=ZipFile.Open(temporary,ZipArchiveMode.Create))
            {
                using(var entry=archive.CreateEntry("legacy/data.pkl",CompressionLevel.NoCompression).Open())entry.Write(header,metadataStart,metadataLength);
                using(var entry=archive.CreateEntry("legacy/byteorder").Open())entry.Write(Encoding.ASCII.GetBytes("little"));
                var buffer=new byte[65536];
                foreach(var location in locations)
                {
                    input.Position=location.Offset;
                    using var entry=archive.CreateEntry("legacy/data/"+location.Storage.Key,CompressionLevel.NoCompression).Open();
                    for(long remaining=location.Bytes;remaining>0;)
                    {
                        cancellation.ThrowIfCancellationRequested();var count=(int)Math.Min(buffer.Length,remaining);
                        input.ReadExactly(buffer.AsSpan(0,count));entry.Write(buffer,0,count);remaining-=count;
                    }
                }
            }
            using(var validated=new TorchCheckpoint(temporary)){}
            cancellation.ThrowIfCancellationRequested();File.Move(temporary,destination);
        }
        finally{if(File.Exists(temporary))File.Delete(temporary);}
    }
}