using System.Runtime.InteropServices; using System.Text.Json; using RblxTool.Models; namespace RblxTool.Interop; public sealed class RblxEngine : IDisposable { private ulong _handle; private bool _disposed; private RblxEngine(ulong handle) => _handle = handle; public static RblxEngine Open(string? vaultPath = null, string? passphrase = null) { var handle = NativeMethods.rblx_engine_open(vaultPath, passphrase); if (handle == 0) { throw new InvalidOperationException("native engine failed to start"); } return new RblxEngine(handle); } public static string Version() => Take(NativeMethods.rblx_version()); public IReadOnlyList Accounts() { var json = Take(NativeMethods.rblx_accounts_list(_handle)); var envelope = JsonSerializer.Deserialize( json, ToolJsonContext.Default.EnvelopeListAccountSummary); return envelope?.Data ?? new List(); } public AccountSummary AddCookie(string cookie, string? label) { var json = Take(NativeMethods.rblx_account_add_cookie(_handle, cookie, label)); var envelope = JsonSerializer.Deserialize(json, ToolJsonContext.Default.EnvelopeAccountSummary); if (envelope is null || !envelope.Ok || envelope.Data is null) { throw new InvalidOperationException(envelope?.Error ?? "cookie rejected"); } return envelope.Data; } public LoginResult Login(string identifier, string password, string? label) { var json = Take(NativeMethods.rblx_account_login(_handle, identifier, password, label)); return JsonSerializer.Deserialize(json, ToolJsonContext.Default.LoginResult) ?? new LoginResult { Ok = false, Error = "login failed" }; } public AccountSummary SubmitTwoStep(string pendingId, string code) { var json = Take(NativeMethods.rblx_account_two_step(_handle, pendingId, code)); var envelope = JsonSerializer.Deserialize(json, ToolJsonContext.Default.EnvelopeAccountSummary); if (envelope is null || !envelope.Ok || envelope.Data is null) { throw new InvalidOperationException(envelope?.Error ?? "verification failed"); } return envelope.Data; } public OauthUrlResult OauthUrl(OauthRequest request) { var config = JsonSerializer.Serialize(request, ToolJsonContext.Default.OauthRequest); var json = Take(NativeMethods.rblx_account_oauth_url(_handle, config)); return JsonSerializer.Deserialize(json, ToolJsonContext.Default.OauthUrlResult) ?? new OauthUrlResult { Ok = false, Error = "oauth url failed" }; } public AccountSummary OauthExchange(OauthRequest request, string code, string verifier) { var config = JsonSerializer.Serialize(request, ToolJsonContext.Default.OauthRequest); var json = Take(NativeMethods.rblx_account_oauth_exchange(_handle, config, code, verifier)); var envelope = JsonSerializer.Deserialize(json, ToolJsonContext.Default.EnvelopeAccountSummary); if (envelope is null || !envelope.Ok || envelope.Data is null) { throw new InvalidOperationException(envelope?.Error ?? "oauth exchange failed"); } return envelope.Data; } public bool Select(string id) => NativeMethods.rblx_account_select(_handle, id) == 1; public bool Remove(string id) => NativeMethods.rblx_account_remove(_handle, id) == 1; public AccountOverview? Overview() { var json = Take(NativeMethods.rblx_account_overview(_handle)); var envelope = JsonSerializer.Deserialize(json, ToolJsonContext.Default.EnvelopeAccountOverview); return envelope?.Data; } public EngineMetrics Metrics() { var json = Take(NativeMethods.rblx_metrics(_handle)); var envelope = JsonSerializer.Deserialize(json, ToolJsonContext.Default.EnvelopeEngineMetrics); return envelope?.Data ?? new EngineMetrics(); } public ulong StartSweep(SweepRequest request) => Start("sweep", JsonSerializer.Serialize(request, ToolJsonContext.Default.SweepRequest)); public ulong StartSnipe(SnipeRequest request) => Start("snipe", JsonSerializer.Serialize(request, ToolJsonContext.Default.SnipeRequest)); public ulong StartHunt(HuntRequest request) => Start("grouphunt", JsonSerializer.Serialize(request, ToolJsonContext.Default.HuntRequest)); private ulong Start(string kind, string configJson) => NativeMethods.rblx_job_start(_handle, kind, configJson); public JobPoll Poll(ulong jobId, int maxEvents = 256) { var json = Take(NativeMethods.rblx_job_poll(_handle, jobId, maxEvents)); return JsonSerializer.Deserialize(json, ToolJsonContext.Default.JobPoll) ?? new JobPoll(); } public void Cancel(ulong jobId) => NativeMethods.rblx_job_cancel(_handle, jobId); public string Result(ulong jobId) => Take(NativeMethods.rblx_job_result(_handle, jobId)); private static string Take(nint pointer) { if (pointer == 0) { return "{}"; } try { return Marshal.PtrToStringUTF8(pointer) ?? "{}"; } finally { NativeMethods.rblx_string_free(pointer); } } public void Dispose() { if (_disposed) { return; } _disposed = true; if (_handle != 0) { NativeMethods.rblx_engine_close(_handle); _handle = 0; } GC.SuppressFinalize(this); } }