commit eb27d44cbf5737eb1463fed23211babb1d481e01 Author: splootybean Date: Mon May 25 17:21:51 2026 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed6d1d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,402 @@ +# ---> VisualStudio +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a4e9dc9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +MIT No Attribution + +Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..98961ce --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Radium.DataExporter.Client + +Awesome client for the Radium Data Exporter \ No newline at end of file diff --git a/Radium.DataExporter.Client.slnx b/Radium.DataExporter.Client.slnx new file mode 100644 index 0000000..638c41a --- /dev/null +++ b/Radium.DataExporter.Client.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/Radium.DataExporter.Client/Constants.cs b/Radium.DataExporter.Client/Constants.cs new file mode 100644 index 0000000..13c85dc --- /dev/null +++ b/Radium.DataExporter.Client/Constants.cs @@ -0,0 +1,14 @@ +using System.Reflection; + +namespace Radium.DataExporter.Client; + +public class Constants +{ + // so we can reference the version of the application easily + public static string Version + => $"{Assembly.GetExecutingAssembly().GetName().Version!}"; + + // the url that moving-out is hosted at + public static Uri ProdUri + => new("https://moving-out.radie.app/"); +} \ No newline at end of file diff --git a/Radium.DataExporter.Client/Networking/RadiumHttpClient.cs b/Radium.DataExporter.Client/Networking/RadiumHttpClient.cs new file mode 100644 index 0000000..f05ce72 --- /dev/null +++ b/Radium.DataExporter.Client/Networking/RadiumHttpClient.cs @@ -0,0 +1,107 @@ +using Radium.DataExporter.Client.Platforms; +using Radium.DataExporter.Models.Accounts; +using Radium.DataExporter.Models.Authentication; +using Radium.DataExporter.Models.Common.Enums; +using Radium.DataExporter.Models.Common.Requests; +using Radium.DataExporter.Models.Common.Results; +using Radium.DataExporter.Models.JsonContext; +using System.Net.Http.Json; +using System.Text.Json; +using TlsSpoofing; + +namespace Radium.DataExporter.Client.Networking; + +public class RadiumHttpClient +{ + private readonly HttpClient _httpClient; + + public RadiumHttpClient() + { + _httpClient = SpoofedHttpClient.Create(new TlsFingerprint + { + CipherSuites = TlsFingerprint.ParseHex("0xc02b, 0xc023, 0xc009, 0xc02f, 0xc027, 0xc013, 0x009c, 0x003c, 0x002f, 0x00ff"), + SupportedGroups = TlsFingerprint.ParseHex("0x0017, 0x0018"), + SignatureAlgorithms = TlsFingerprint.ParseHex("0x0201, 0x0301, 0x0401, 0x0501, 0x0601, 0x0202, 0x0302, 0x0402, 0x0502, 0x0602, 0x0203, 0x0303, 0x0403, 0x0503, 0x0603"), + AlpnProtocols = [], + MinVersion = TlsVersion.Tls12, + MaxVersion = TlsVersion.Tls12 + }); + + _httpClient.DefaultRequestHeaders.Add("User-Agent", $"Radium.DataExporter.Client/{Constants.Version}"); + } + + public async Task> GetUsableCachedLoginsAsync(PlatformType platformType, string platformId) + { + var request = await _httpClient.GetAsync($"https://auth.rec.net/cachedLogin/forPlatformId/{platformType}/{platformId}"); + + if (!request.IsSuccessStatusCode) + return []; + + var responseStream = await request.Content.ReadAsStreamAsync(); + + var cachedLogins = await JsonSerializer.DeserializeAsync(responseStream, RecNetJsonContext.Default.ListCachedLogin); + + if (cachedLogins == null) + return []; + + return [.. cachedLogins + .Where(x => !x.RequirePassword) + .OrderBy(x => x.LastLoginTime)]; + } + + public async Task> GetAccountsBulkAsync(long[] accountIds) + { + var formData = accountIds + .Select(accountId => new KeyValuePair("id", $"{accountId}")); + var content = new FormUrlEncodedContent(formData); + + var request = await _httpClient.PostAsync($"https://accounts.rec.net/account/bulk", content); + + if (!request.IsSuccessStatusCode) + return []; + + var responseStream = await request.Content.ReadAsStreamAsync(); + + var accounts = await JsonSerializer.DeserializeAsync(responseStream, RecNetJsonContext.Default.ListAccount); + + if (accounts == null) + return []; + + return accounts; + } + + public async Task CreateJobSessionAsync(string code, long accountId, string platformId, string platformAuth) + { + var json = JsonContent.Create(new CompleteAuthLinkRequest + { + Code = code, + AccountId = accountId, + SteamId = platformId, + SteamTicket = platformAuth + }, + RecNetJsonContext.Default.CompleteAuthLinkRequest); + + var response = await _httpClient.PostAsync($"{Constants.ProdUri}api/auth-link/{PlatformManager.Instance.Platform}", json); + + var responseStream = await response.Content.ReadAsStreamAsync(); + + if (response.IsSuccessStatusCode) + { + var result = await JsonSerializer.DeserializeAsync( + responseStream, + RecNetJsonContext.Default.CompleteAuthLinkResult); + + return new CreateJobSessionResponse + { + Result = result + }; + } + + var error = await JsonSerializer.DeserializeAsync(responseStream, RecNetJsonContext.Default.RecNetError); + + return new CreateJobSessionResponse + { + Error = error + }; + } +} \ No newline at end of file diff --git a/Radium.DataExporter.Client/Networking/lib/TlsSpoof.dll b/Radium.DataExporter.Client/Networking/lib/TlsSpoof.dll new file mode 100644 index 0000000..4c54e70 Binary files /dev/null and b/Radium.DataExporter.Client/Networking/lib/TlsSpoof.dll differ diff --git a/Radium.DataExporter.Client/Networking/lib/TlsSpoofing.dll b/Radium.DataExporter.Client/Networking/lib/TlsSpoofing.dll new file mode 100644 index 0000000..f469ae7 Binary files /dev/null and b/Radium.DataExporter.Client/Networking/lib/TlsSpoofing.dll differ diff --git a/Radium.DataExporter.Client/Platforms/PlatformManager.cs b/Radium.DataExporter.Client/Platforms/PlatformManager.cs new file mode 100644 index 0000000..4083e0b --- /dev/null +++ b/Radium.DataExporter.Client/Platforms/PlatformManager.cs @@ -0,0 +1,17 @@ +using Radium.DataExporter.Models.Common.Enums; + +namespace Radium.DataExporter.Client.Platforms; + +public abstract class PlatformManager +{ + private static PlatformManager? _instance; + public static PlatformManager Instance => + _instance ?? throw new InvalidOperationException("PlatformManager not yet initialized?!"); + + public PlatformManager() => _instance = this; + + public abstract PlatformType Platform { get; } + public string PlatformId { get; protected set; } = string.Empty; + public abstract string GetPlatformAuthenticationParamaters(); + public abstract void Initialize(); +} \ No newline at end of file diff --git a/Radium.DataExporter.Client/Platforms/SteamPlatformManager.cs b/Radium.DataExporter.Client/Platforms/SteamPlatformManager.cs new file mode 100644 index 0000000..b89b109 --- /dev/null +++ b/Radium.DataExporter.Client/Platforms/SteamPlatformManager.cs @@ -0,0 +1,32 @@ +using Radium.DataExporter.Models.Common.Enums; +using Steamworks; + +namespace Radium.DataExporter.Client.Platforms; + +public class SteamPlatformManager : PlatformManager +{ + public override PlatformType Platform => PlatformType.Steam; + + public override void Initialize() + { + if (!SteamAPI.Init()) + throw new Exception($"Failed to initialize {Platform} platform, is {Platform} open and running?"); + + PlatformId = $"{SteamUser.GetSteamID()}"; + } + + public override string GetPlatformAuthenticationParamaters() + { + var ticketBuffer = new byte[1024]; + var identity = new SteamNetworkingIdentity(); + identity.SetSteamID(SteamUser.GetSteamID()); + + var authTicket = SteamUser.GetAuthSessionTicket(ticketBuffer, ticketBuffer.Length, out var ticketSize, ref identity); + if (authTicket == HAuthTicket.Invalid) + throw new Exception("Could not get Steam Ticket"); + + var ticketHex = Convert.ToHexString(ticketBuffer, 0, (int)ticketSize); + + return ticketHex; + } +} \ No newline at end of file diff --git a/Radium.DataExporter.Client/Program.cs b/Radium.DataExporter.Client/Program.cs new file mode 100644 index 0000000..498a60a --- /dev/null +++ b/Radium.DataExporter.Client/Program.cs @@ -0,0 +1,167 @@ +// This program wasn't to have comments but it's late and I'M BORED so here goes !!! + +using Radium.DataExporter.Client.Networking; +using Radium.DataExporter.Client.Platforms; +using Radium.DataExporter.Models.Accounts; +using Serilog; +using Serilog.Events; +using Spectre.Console; +using System.Text.Json; + +namespace Radium.DataExporter.Client; + +internal class Program +{ + private static async Task Main(string[] args) + { + // Let's store the title so we can add cool stuff appended to it later! + var title = $"Radium Data Export Client - v{Constants.Version} "; + + // ...and set it + Console.Title = title; + + // Setup Serilog + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Console() + + .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log", "log-.txt"), + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 5) + + .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log", "error-.txt"), + rollingInterval: RollingInterval.Day, + restrictedToMinimumLevel: LogEventLevel.Error) + + .Enrich.FromLogContext() + .CreateLogger(); + + ILogger logger = Log.Logger; + + // Show cool Radium logo + PrintLogo(); + + Console.WriteLine(); + logger.Information($"Radium Data Export Client - v{Constants.Version} made with half love & half hatred by @splootybean"); + + Thread.Sleep(3000); + + // Create instance of RadiumHttpClient so we can make requests later on + var httpClient = new RadiumHttpClient(); + + // along with a platform manager! I wish this wasn't hardcoded but honestly I doubt you'd be on anyyyyy other platform :p + var platformManager = new SteamPlatformManager(); +#if DEBUG + logger.Debug($"Initalizing PlatformManager for {platformManager.Platform}"); +#endif + // initialize our platform manager + platformManager.Initialize(); + +#if DEBUG + logger.Debug($"GetUsableCachedLoginsAsync for {platformManager.PlatformId}"); +#endif + // get all the cached logins for this platform so we can display them in the account selector + var cachedLogins = await httpClient.GetUsableCachedLoginsAsync( + platformManager.Platform, + platformManager.PlatformId)!; + + // we treat this as it being null (or maybe you really just dont play rec room!) + if (cachedLogins.Count <= 0) + { + logger.Information("No Rec Room accounts!"); + return; + } + + logger.Information($"Found {cachedLogins.Count} usable cached logins"); + + // get accounts + var accounts = await httpClient.GetAccountsBulkAsync([.. cachedLogins + .Select(x => x.AccountId)]); +#if DEBUG + foreach (var account in accounts) + logger.Debug(account.Username); +#endif + Console.Clear(); + + // this is the cool appending thing i was talking about + Console.Title = title + "(Account Selection)"; + + // prompt to select an account! + var selection = AnsiConsole.Prompt(new SelectionPrompt() + .Title("[blue]Select an account to export data for[/]") + .PageSize(12) + .UseConverter(x => Markup.Escape($"[{x.AccountId}] ({x.Username}) {x.DisplayName}")) + .AddChoices(accounts)); + + logger.Information($"Account selected: {selection.Username}"); + + // JOB CREATION + + logger.Information($"Okay {selection.Username}! You are now going to generate an authentication code to link your RecNet account to your Radium account."); + logger.Information($"Go to {Constants.ProdUri} to generate an authentication code, and type it in the box below. When you're done, press ENTER."); + + Console.Title = title + "(Account Linking)"; + + + // get our moving-out code + var code = AnsiConsole.Prompt( + new TextPrompt("Please enter your 6-digit code:") + .Validate(code => + { + return code.Length == 6 && code.All(char.IsDigit) + ? ValidationResult.Success() + : ValidationResult.Error("[red]Code must be 6-digits, and numeric![/]"); + })); + + Console.Clear(); + + logger.Information("Creating session..."); + + // get auth params to send to login + var authParams = platformManager.GetPlatformAuthenticationParamaters(); + logger.Information($"Get {platformManager.Platform} AuthenticationParamaters OK!"); + + // create a job! + var response = await httpClient.CreateJobSessionAsync(code, selection.AccountId, platformManager.PlatformId, authParams); + + // check if it failed or not... + if (!response.IsSuccess) + { + Console.Title = title + $"({response.Error?.Title})"; + // lets print the full error if we're in debug, else we can show the bare minimum +#if DEBUG + logger.Fatal(JsonSerializer.Serialize(response.Error, Models.JsonContext.RecNetJsonContext.Default.RecNetError)); +#else + logger.Fatal($"Could not get job from Radeon! {response.Error?.Detail} ({response.Error?.Title})"); +#endif + Console.ReadKey(); + // funny rec room number + Environment.Exit(87); + } + + var job = response.Result!; + + Console.Title = title; + + Console.Clear(); + + logger.Information($"{(job.ResumedExistingJob ? "Resuming" : "Started")} job for {selection.Username} (ID: {job.JobId})!"); + + logger.Information("You may now close the application and go back to the website."); + + Thread.Sleep(10000); + } + + public static void PrintLogo() + { + Console.WriteLine(" _______ ______ _______ ______ __ __ __ __ "); + Console.WriteLine("/ \\ / \\ / \\ / |/ | / |/ \\ / |"); + Console.WriteLine("$$$$$$$ |/$$$$$$ |$$$$$$$ |$$$$$$/ $$ | $$ |$$ \\ /$$ |"); + Console.WriteLine("$$ |__$$ |$$ |__$$ |$$ | $$ | $$ | $$ | $$ |$$$ \\ /$$$ |"); + Console.WriteLine("$$ $$< $$ $$ |$$ | $$ | $$ | $$ | $$ |$$$$ /$$$$ |"); + Console.WriteLine("$$$$$$$ |$$$$$$$$ |$$ | $$ | $$ | $$ | $$ |$$ $$ $$/$$ |"); + Console.WriteLine("$$ | $$ |$$ | $$ |$$ |__$$ | _$$ |_ $$ \\__$$ |$$ |$$$/ $$ |"); + Console.WriteLine("$$ | $$ |$$ | $$ |$$ $$/ / $$ |$$ $$/ $$ | $/ $$ |"); + Console.WriteLine("$$/ $$/ $$/ $$/ $$$$$$$/ $$$$$$/ $$$$$$/ $$/ $$/ "); + } +} \ No newline at end of file diff --git a/Radium.DataExporter.Client/Radium.DataExporter.Client.csproj b/Radium.DataExporter.Client/Radium.DataExporter.Client.csproj new file mode 100644 index 0000000..e8a73b8 --- /dev/null +++ b/Radium.DataExporter.Client/Radium.DataExporter.Client.csproj @@ -0,0 +1,53 @@ + + + + Exe + net10.0 + 1.0.2.4 + enable + enable + true + true + https://moving-out.radie.app/ + A tool for exporting your data from Rec Room to Radium + Radium Data Export Client + icon.ico + + + + + + + + + + + + + + + + + + + + + + + + + True + \ + + + + + Networking\lib\TlsSpoof.dll + + + + + + + + diff --git a/Radium.DataExporter.Client/icon.ico b/Radium.DataExporter.Client/icon.ico new file mode 100644 index 0000000..edf1e31 Binary files /dev/null and b/Radium.DataExporter.Client/icon.ico differ diff --git a/Radium.DataExporter.Client/lib/steam_api64.dll b/Radium.DataExporter.Client/lib/steam_api64.dll new file mode 100644 index 0000000..9ad453c Binary files /dev/null and b/Radium.DataExporter.Client/lib/steam_api64.dll differ diff --git a/Radium.DataExporter.Models/Accounts/Account.cs b/Radium.DataExporter.Models/Accounts/Account.cs new file mode 100644 index 0000000..b3cb83b --- /dev/null +++ b/Radium.DataExporter.Models/Accounts/Account.cs @@ -0,0 +1,31 @@ +using Radium.DataExporter.Models.Accounts.Enums; +using Radium.DataExporter.Models.Common.Enums; +using System.Text.Json.Serialization; + +namespace Radium.DataExporter.Models.Accounts; + +public class Account +{ + [JsonPropertyName(name: "accountId")] public long AccountId { get; set; } + + [JsonPropertyName(name: "username")] public string Username { get; set; } = string.Empty; + [JsonPropertyName(name: "displayName")] public string DisplayName { get; set; } = string.Empty; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName(name: "displayEmoji")] public string? DisplayEmoji { get; set; } + + [JsonPropertyName(name: "profileImage")] public string ProfileImage { get; set; } = "DefaultProfileImage"; + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName(name: "bannerImage")] public string? BannerImage { get; set; } + + [JsonPropertyName(name: "isJunior")] public bool? IsJunior { get; set; } + + [JsonPropertyName(name: "platforms")] public PlatformMask Platforms { get; set; } + + [JsonPropertyName(name: "personalPronouns")] public PersonalPronouns PersonalPronouns { get; set; } + [JsonPropertyName(name: "identityFlags")] public IdentityFlags IdentityFlags { get; set; } + + [JsonPropertyName(name: "createdAt")] public DateTime CreatedAt { get; set; } + + [JsonPropertyName(name: "isMetaPlatformBlocked")] public bool IsMetaPlatformBlocked { get; set; } +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Accounts/Enums/IdentityFlags.cs b/Radium.DataExporter.Models/Accounts/Enums/IdentityFlags.cs new file mode 100644 index 0000000..17cfd56 --- /dev/null +++ b/Radium.DataExporter.Models/Accounts/Enums/IdentityFlags.cs @@ -0,0 +1,17 @@ +namespace Radium.DataExporter.Models.Accounts.Enums; + +[Flags] +public enum IdentityFlags +{ + None = 0, + LGBTQIA = 1, + Transgender = 2, + Bisexual = 4, + Lesbian = 8, + Pansexual = 16, + Asexual = 32, + Intersex = 64, + Genderqueer = 128, + Nonbinary = 256, + Aromantic = 512 +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Accounts/Enums/PersonalPronouns.cs b/Radium.DataExporter.Models/Accounts/Enums/PersonalPronouns.cs new file mode 100644 index 0000000..b1248f5 --- /dev/null +++ b/Radium.DataExporter.Models/Accounts/Enums/PersonalPronouns.cs @@ -0,0 +1,13 @@ +namespace Radium.DataExporter.Models.Accounts.Enums; + +[Flags] +public enum PersonalPronouns +{ + None = 0, + SheHer = 1, + HeHim = 2, + TheyThem = 4, + ZeHir = 8, + ZeZir = 16, + XeXem = 32 +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Authentication/CachedLogin.cs b/Radium.DataExporter.Models/Authentication/CachedLogin.cs new file mode 100644 index 0000000..c0219b3 --- /dev/null +++ b/Radium.DataExporter.Models/Authentication/CachedLogin.cs @@ -0,0 +1,16 @@ +using Radium.DataExporter.Models.Common.Enums; +using System.Text.Json.Serialization; + +namespace Radium.DataExporter.Models.Authentication; + +public class CachedLogin +{ + [JsonPropertyName(name: "platform")] public PlatformType Platform { get; set; } + [JsonPropertyName(name: "platformId")] public string PlatformId { get; set; } = string.Empty; + [JsonPropertyName(name: "accountId")] public long AccountId { get; set; } + [JsonPropertyName(name: "lastLoginTime")] public DateTime LastLoginTime { get; set; } = DateTime.UtcNow; + + [JsonPropertyName(name: "requirePassword")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public bool RequirePassword { get; set; } = false; +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Common/Enums/PlatformMask.cs b/Radium.DataExporter.Models/Common/Enums/PlatformMask.cs new file mode 100644 index 0000000..f5e1f00 --- /dev/null +++ b/Radium.DataExporter.Models/Common/Enums/PlatformMask.cs @@ -0,0 +1,18 @@ +namespace Radium.DataExporter.Models.Common.Enums; + +[Flags] +public enum PlatformMask +{ + None, + Steam, + Oculus, + PlayStation = 4, + Xbox = 8, + RecNet = 16, + IOS = 32, + GooglePlay = 64, + Standalone = 128, + Pico = 256, + Switch = 512, + All = -1 +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Common/Enums/PlatformType.cs b/Radium.DataExporter.Models/Common/Enums/PlatformType.cs new file mode 100644 index 0000000..ffe9ebc --- /dev/null +++ b/Radium.DataExporter.Models/Common/Enums/PlatformType.cs @@ -0,0 +1,16 @@ +namespace Radium.DataExporter.Models.Common.Enums; + +public enum PlatformType +{ + All = -1, + Steam, + Oculus, + PlayStation, + Xbox, + RecNet, + IOS, + GooglePlay, + Standalone, + Pico, + Switch +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Common/Requests/CompleteAuthLinkRequest.cs b/Radium.DataExporter.Models/Common/Requests/CompleteAuthLinkRequest.cs new file mode 100644 index 0000000..9fe991b --- /dev/null +++ b/Radium.DataExporter.Models/Common/Requests/CompleteAuthLinkRequest.cs @@ -0,0 +1,9 @@ +namespace Radium.DataExporter.Models.Common.Requests; + +public class CompleteAuthLinkRequest +{ + public required string Code { get; set; } + public required long AccountId { get; set; } + public required string SteamId { get; set; } + public required string SteamTicket { get; set; } +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Common/Results/CompleteAuthLinkResult.cs b/Radium.DataExporter.Models/Common/Results/CompleteAuthLinkResult.cs new file mode 100644 index 0000000..bd93b6b --- /dev/null +++ b/Radium.DataExporter.Models/Common/Results/CompleteAuthLinkResult.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Radium.DataExporter.Models.Common.Results; + +public class CompleteAuthLinkResult +{ + [JsonPropertyName(name: "jobId")] public Guid JobId { get; set; } + [JsonPropertyName(name: "recNetUserId")] public long RecNetUserId { get; set; } + [JsonPropertyName(name: "resumedExistingJob")] public bool ResumedExistingJob { get; set; } +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Common/Results/CreateJobSessionResponse.cs b/Radium.DataExporter.Models/Common/Results/CreateJobSessionResponse.cs new file mode 100644 index 0000000..31d07f7 --- /dev/null +++ b/Radium.DataExporter.Models/Common/Results/CreateJobSessionResponse.cs @@ -0,0 +1,9 @@ +namespace Radium.DataExporter.Models.Common.Results; + +public class CreateJobSessionResponse +{ + public CompleteAuthLinkResult? Result { get; init; } + public RecNetError? Error { get; init; } + + public bool IsSuccess => Result != null; +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Common/Results/RecNetError.cs b/Radium.DataExporter.Models/Common/Results/RecNetError.cs new file mode 100644 index 0000000..10718ab --- /dev/null +++ b/Radium.DataExporter.Models/Common/Results/RecNetError.cs @@ -0,0 +1,22 @@ +using System.Net; +using System.Text.Json.Serialization; + +namespace Radium.DataExporter.Models.Common.Results; + +public class RecNetError +{ + [JsonPropertyName("type")] + public Uri? Type { get; init; } + + [JsonPropertyName("title")] + public string Title { get; init; } = string.Empty; + + [JsonPropertyName("status")] + public HttpStatusCode Status { get; init; } + + [JsonPropertyName("detail")] + public string Detail { get; init; } = string.Empty; + + [JsonPropertyName("traceId")] + public string TraceId { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/JsonContext/RecNetJsonContext.cs b/Radium.DataExporter.Models/JsonContext/RecNetJsonContext.cs new file mode 100644 index 0000000..c058847 --- /dev/null +++ b/Radium.DataExporter.Models/JsonContext/RecNetJsonContext.cs @@ -0,0 +1,18 @@ +using Radium.DataExporter.Models.Accounts; +using Radium.DataExporter.Models.Authentication; +using Radium.DataExporter.Models.Common.Requests; +using Radium.DataExporter.Models.Common.Results; +using System.Text.Json.Serialization; + +namespace Radium.DataExporter.Models.JsonContext; + +[JsonSerializable(typeof(CompleteAuthLinkRequest))] +[JsonSerializable(typeof(CompleteAuthLinkResult))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(CachedLogin))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(Account))] +[JsonSerializable(typeof(RecNetError))] +public partial class RecNetJsonContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Radium.DataExporter.Models/Radium.DataExporter.Models.csproj b/Radium.DataExporter.Models/Radium.DataExporter.Models.csproj new file mode 100644 index 0000000..c7a94a8 --- /dev/null +++ b/Radium.DataExporter.Models/Radium.DataExporter.Models.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + \ No newline at end of file