From 0d67d01f40ef34850cf2aa9872ab8ec7bc64365e Mon Sep 17 00:00:00 2001 From: tinysec Date: Thu, 23 Jul 2026 00:39:44 +0800 Subject: [PATCH] feat: add type archive snapshot transactions --- .../BNTypeArchiveNewSnapshotTransaction.cs | 4 +- Handle/BNTypeArchive.cs | 2 +- Handle/BNTypeArchiveTransactions.cs | 113 ++++++++++++++++++ Misc/CoreUtils.cs | 6 +- 4 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 Handle/BNTypeArchiveTransactions.cs diff --git a/Function/BNTypeArchiveNewSnapshotTransaction.cs b/Function/BNTypeArchiveNewSnapshotTransaction.cs index 4cab990f..4e375c45 100644 --- a/Function/BNTypeArchiveNewSnapshotTransaction.cs +++ b/Function/BNTypeArchiveNewSnapshotTransaction.cs @@ -28,11 +28,11 @@ internal static extern IntPtr BNTypeArchiveNewSnapshotTransaction( IntPtr context , // const char** parents - string[] parents , + IntPtr parents, // uint64_t parentCount ulong parentCount ); } -} \ No newline at end of file +} diff --git a/Handle/BNTypeArchive.cs b/Handle/BNTypeArchive.cs index e55d1028..e85d6f23 100644 --- a/Handle/BNTypeArchive.cs +++ b/Handle/BNTypeArchive.cs @@ -10,7 +10,7 @@ namespace BinaryNinja /// that can be shared across multiple binary views and kept in sync via snapshots. /// Each archive is backed by a file on disk and exposes a versioned snapshot graph. /// - public sealed class TypeArchive : AbstractSafeHandle + public sealed partial class TypeArchive : AbstractSafeHandle { // Live notification registrations, keyed by the user's TypeArchiveNotification instance. Each // entry roots its native callback delegates and struct for the registration lifetime. diff --git a/Handle/BNTypeArchiveTransactions.cs b/Handle/BNTypeArchiveTransactions.cs new file mode 100644 index 00000000..aea35c5e --- /dev/null +++ b/Handle/BNTypeArchiveTransactions.cs @@ -0,0 +1,113 @@ +using System; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; + +namespace BinaryNinja +{ + /// + /// Performs archive mutations in a new snapshot transaction. + /// + /// Identifier reserved for the new snapshot. + public delegate void TypeArchiveSnapshotTransaction(string snapshotId); + + public sealed partial class TypeArchive + { + [UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + private delegate bool NativeSnapshotTransaction( + IntPtr context, + [MarshalAs(UnmanagedType.LPUTF8Str)] string snapshotId); + + private sealed class SnapshotTransactionContext + { + private readonly TypeArchiveSnapshotTransaction transaction; + private ExceptionDispatchInfo? exception; + + internal SnapshotTransactionContext(TypeArchiveSnapshotTransaction transaction) + { + this.transaction = transaction; + } + + internal bool Invoke(IntPtr context, string snapshotId) + { + try + { + this.transaction(snapshotId); + return true; + } + catch (Exception caught) + { + this.exception = ExceptionDispatchInfo.Capture(caught); + return false; + } + } + + internal void ThrowIfFailed() + { + if (null != this.exception) + { + this.exception.Throw(); + } + } + } + + /// + /// Runs archive mutations atomically and creates a new snapshot when they succeed. + /// + /// Mutations to perform using the reserved snapshot identifier. + /// Parent snapshot identifiers for the new snapshot. + /// The committed snapshot identifier. + public string NewSnapshotTransaction( + TypeArchiveSnapshotTransaction transaction, + string[] parents) + { + if (null == transaction) + { + throw new ArgumentNullException(nameof(transaction)); + } + + if (null == parents) + { + throw new ArgumentNullException(nameof(parents)); + } + + for (int i = 0; i < parents.Length; i++) + { + if (null == parents[i]) + { + throw new ArgumentException("Snapshot parents cannot contain null entries.", nameof(parents)); + } + } + + SnapshotTransactionContext transactionContext = + new SnapshotTransactionContext(transaction); + NativeSnapshotTransaction nativeTransaction = transactionContext.Invoke; + IntPtr result; + + using (ScopedAllocator allocator = new ScopedAllocator()) + { + try + { + result = NativeMethods.BNTypeArchiveNewSnapshotTransaction( + this.handle, + Marshal.GetFunctionPointerForDelegate(nativeTransaction), + IntPtr.Zero, + allocator.AllocUtf8StringArray(parents), + (ulong)parents.Length); + } + finally + { + GC.KeepAlive(nativeTransaction); + } + } + + transactionContext.ThrowIfFailed(); + if (IntPtr.Zero == result) + { + throw new InvalidOperationException("BNTypeArchiveNewSnapshotTransaction failed."); + } + + return UnsafeUtils.TakeUtf8String(result); + } + } +} diff --git a/Misc/CoreUtils.cs b/Misc/CoreUtils.cs index 9cb9e4f6..77183700 100644 --- a/Misc/CoreUtils.cs +++ b/Misc/CoreUtils.cs @@ -1449,14 +1449,10 @@ public static unsafe string[] ParseTypeParserOptionsText(string optionsText) // TODO: BNPerformCustomRequest / BNPerformDownloadRequest — on DownloadInstance, // requires callback context parameters for progress/completion notifications. - // TODO: BNTypeArchiveMergeSnapshots / BNTypeArchiveNewSnapshotTransaction — - // callback-based transaction functions requiring managed delegate infrastructure. + // TODO: BNTypeArchiveMergeSnapshots requires conflict-map and progress callback marshalling. // NOTE: BNReadSnapshotDataWithProgress — implemented on Snapshot class as ReadDataWithProgress(). - // TODO: BNResolveStructureMemberOrBaseMember — on Structure, requires callback - // context (void* callbackContext, void** resolveFunc) for member resolution. - // TODO: BNComponentsNotEqual — skip, handled by AbstractSafeHandle operators (== and !=). // TODO: BNRepositoryFreePluginDirectoryList — internal free function, not a wrapper target.