diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef5b7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +/.idea +/CannedNet.API/Migrations +CannedNet.sln.DotSettings.user diff --git a/CannedNet.API/CannedNet.API.csproj b/CannedNet.API/CannedNet.API.csproj new file mode 100644 index 0000000..6afdecd --- /dev/null +++ b/CannedNet.API/CannedNet.API.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + CannedNet + + + + + + + + + + diff --git a/CannedNet.API/Data/AppDbContext.cs b/CannedNet.API/Data/AppDbContext.cs new file mode 100644 index 0000000..0a0cddc --- /dev/null +++ b/CannedNet.API/Data/AppDbContext.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; + +namespace CannedNet.Data; + +public class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Accounts { get; set; } + public DbSet CachedLogins { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // accounts + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.AccountId); + entity.Property(e => e.AccountId).ValueGeneratedNever(); + entity.Property(e => e.Username).IsRequired(); + entity.Property(e => e.DisplayName).IsRequired(); + entity.ToTable("accounts"); + }); + + // cachedlogins + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Id).ValueGeneratedOnAdd(); + entity.HasIndex(e => new { e.Platform, e.PlatformID }).IsUnique(); + entity.ToTable("cached_logins"); + }); + } +} + diff --git a/CannedNet.API/Enums/JuniorState.cs b/CannedNet.API/Enums/JuniorState.cs new file mode 100644 index 0000000..e5edf99 --- /dev/null +++ b/CannedNet.API/Enums/JuniorState.cs @@ -0,0 +1,8 @@ +namespace CannedNet; + +public enum JuniorState +{ + BasedOnBirthday = 0, + ModerationOverride = 1, + PendingOverride = 2 +} \ No newline at end of file diff --git a/CannedNet.API/Enums/PlatformType.cs b/CannedNet.API/Enums/PlatformType.cs new file mode 100644 index 0000000..d035b31 --- /dev/null +++ b/CannedNet.API/Enums/PlatformType.cs @@ -0,0 +1,15 @@ +namespace CannedNet; + +public enum PlatformType +{ + All = -1, + Steam = 0, + Oculus = 1, + PlayStation = 2, + Xbox = 3, + RecNet = 4, + IOS = 5, + GooglePlay = 6, + Standalone = 7, + Pico = 8 +} \ No newline at end of file diff --git a/CannedNet.API/JwtTokenService.cs b/CannedNet.API/JwtTokenService.cs new file mode 100644 index 0000000..39708a6 --- /dev/null +++ b/CannedNet.API/JwtTokenService.cs @@ -0,0 +1,110 @@ +using System.Security.Cryptography; +using System.Security.Claims; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; + +namespace CannedNet; + +public class JwtTokenService +{ + private static readonly RSA _rsa = RSA.Create(2048); + private readonly RsaSecurityKey _securityKey; + private readonly SigningCredentials _signingCredentials; + private const string KeyId = "7C2F041398671515B0862CB23FAF95B03"; + + public JwtTokenService() + { + _securityKey = new RsaSecurityKey(_rsa) { KeyId = KeyId }; + _signingCredentials = new SigningCredentials(_securityKey, SecurityAlgorithms.RsaSha256); + } + + public string GenerateToken(string accountId, string platformId, string platform = "Steam") + { + var now = DateTime.UtcNow; + var exp = now.AddHours(1); + + // theres gotta be some other better way to do this, but im fucking stupid so :shrug: + // if it works it works ig idk, someone can clean this up later or i will later + + var claims = new List + { + new Claim("iss", "https://rr.lapis.codes"), + new Claim("nbf", ((DateTimeOffset)now).ToUnixTimeSeconds().ToString()), + new Claim("iat", ((DateTimeOffset)now).ToUnixTimeSeconds().ToString()), + new Claim("exp", ((DateTimeOffset)exp).ToUnixTimeSeconds().ToString()), + new Claim("aud", "https://rr.lapis.codes/resources"), + new Claim("amr", "cached_login"), + new Claim("client_id", "recroom"), + new Claim("sub", accountId), + new Claim("auth_time", ((DateTimeOffset)now).ToUnixTimeSeconds().ToString()), + new Claim("idp", "local"), + new Claim("platform", platform), + new Claim("platform_id", platformId), + new Claim("rn.ver", "20210129"), + new Claim("rn.plat", "0"), + new Claim("role", "gameClient"), + new Claim("jti", Guid.NewGuid().ToString("N").Substring(0, 32).ToUpper()) + }; + + claims.Add(new Claim("scope", "profile")); + claims.Add(new Claim("scope", "rn")); + claims.Add(new Claim("scope", "rn.accounts")); + claims.Add(new Claim("scope", "rn.accounts.gc")); + claims.Add(new Claim("scope", "rn.api")); + claims.Add(new Claim("scope", "rn.chat")); + claims.Add(new Claim("scope", "rn.clubs")); + claims.Add(new Claim("scope", "rn.commerce")); + claims.Add(new Claim("scope", "rn.match.read")); + claims.Add(new Claim("scope", "rn.match.write")); + claims.Add(new Claim("scope", "rn.notify")); + claims.Add(new Claim("scope", "rn.rooms")); + claims.Add(new Claim("scope", "rn.storage")); + claims.Add(new Claim("scope", "offline_access")); + + var identity = new ClaimsIdentity(claims, "jwt"); + + var handler = new JwtSecurityTokenHandler(); + var token = handler.CreateJwtSecurityToken( + issuer: "https://auth.epicquest.live", + audience: "https://auth.epicquest.live/resources", + subject: identity, + notBefore: now, + issuedAt: now, + expires: exp, + signingCredentials: _signingCredentials); + + return handler.WriteToken(token); + } + + public string? ValidateAndGetAccountId(string token) + { + try + { + var handler = new JwtSecurityTokenHandler(); + handler.InboundClaimTypeMap.Clear(); + + var parameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = _securityKey, + ValidateIssuer = true, + ValidIssuer = "https://auth.epicquest.live", + ValidateAudience = true, + ValidAudience = "https://auth.epicquest.live/resources", + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero + }; + + var principal = handler.ValidateToken(token, parameters, out SecurityToken validatedToken); + + var accountIdClaim = principal.Claims.FirstOrDefault(c => c.Type == "sub"); + + return accountIdClaim?.Value; + } + catch (Exception ex) + { + return null; + } + } +} + diff --git a/CannedNet.API/Models/Account.cs b/CannedNet.API/Models/Account.cs new file mode 100644 index 0000000..3d37962 --- /dev/null +++ b/CannedNet.API/Models/Account.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace CannedNet; + +public class Account +{ + [JsonPropertyName("accountId")] public int? AccountId { get; set; } + [JsonPropertyName("profileImage")] public string? ProfileImage { get; set; } + [JsonPropertyName("isJunior")] public bool IsJunior { get; set; } + [JsonPropertyName("platforms")] public int? Platforms { get; set; } + [JsonPropertyName("personalPronouns")] public int? PersonalPronouns { get; set; } + [JsonPropertyName("identityFlags")] public int? IdentityFlags { get; set; } + [JsonPropertyName("username")] public string? Username { get; set; } + [JsonPropertyName("displayName")] public string? DisplayName { get; set; } + [JsonPropertyName("createdAt")] public DateTime CreatedAt { get; set; } +} \ No newline at end of file diff --git a/CannedNet.API/Models/CachedAccount.cs b/CannedNet.API/Models/CachedAccount.cs new file mode 100644 index 0000000..6a539f4 --- /dev/null +++ b/CannedNet.API/Models/CachedAccount.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace CannedNet; + +public class CachedLogin +{ + [JsonIgnore] + public int Id { get; set; } + + [JsonPropertyName("accountId")] + public int AccountId { get; set; } + + [JsonPropertyName("platform")] + public PlatformType Platform { get; set; } + + [JsonPropertyName("platformId")] + public string PlatformID { get; set; } + + [JsonPropertyName("lastLoginTime")] + public DateTime LastLoginTime { get; set; } + + [JsonPropertyName("requirePassword")] + public bool RequirePassword { get; set; } = false; +} \ No newline at end of file diff --git a/CannedNet.API/Models/RecNetResult.cs b/CannedNet.API/Models/RecNetResult.cs new file mode 100644 index 0000000..aa737e5 --- /dev/null +++ b/CannedNet.API/Models/RecNetResult.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace CannedNet; + +public class RecNetResultWithValue : RecNetResult +{ + [JsonPropertyName("value")] public required T? Value { get; set; } +} +public class RecNetResult +{ + public static RecNetResult Ok() => new RecNetResult { Success = true }; + public static RecNetResult Err(string error) => new RecNetResult { Success = false, Error = error }; + public static RecNetResultWithValue Ok(T value) => new RecNetResultWithValue { Success = true, Value = value }; + public static RecNetResultWithValue Err(string error) => new RecNetResultWithValue { Success = false, Error = error, Value = default }; + + [JsonPropertyName("success")] public bool Success { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] public string? Error { get; set; } +} \ No newline at end of file diff --git a/CannedNet.API/Models/SelfAccount.cs b/CannedNet.API/Models/SelfAccount.cs new file mode 100644 index 0000000..d18386f --- /dev/null +++ b/CannedNet.API/Models/SelfAccount.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace CannedNet; + +public class SelfAccount : Account +{ + [JsonPropertyName("email")] public string? Email { get; set; } + [JsonPropertyName("phone")] public string? Phone { get; set; } + [JsonPropertyName("juniorState")] public JuniorState? JuniorState { get; set; } + [JsonPropertyName("birthday")] public DateTime? Birthday { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentAccountId")] public string? ParentAccountId { get; set; } + [JsonPropertyName("availableUsernameChanges")] public int? AvailableUsernameChanges { get; set; } +} \ No newline at end of file diff --git a/CannedNet.API/Program.cs b/CannedNet.API/Program.cs new file mode 100644 index 0000000..93a344e --- /dev/null +++ b/CannedNet.API/Program.cs @@ -0,0 +1,337 @@ +using CannedNet; +using CannedNet.Data; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// TODO: organize this into multiple files +// TODO: cdn stuff soon so i can not just have 0 images for this lmao +// TODO: setup signalr and get websockets running, currently should focus on notifications as logging in is being blocked by that + +// needed so it won't try to auto capitalize random stuff that may be case-sensitive +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.PropertyNamingPolicy = null; +}); + +// postgresql db, will probably have it configurable later on but for now just hardcode it in +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") + ?? "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres"; + +builder.Services.AddDbContext(options => + options.UseNpgsql(connectionString)); + +builder.Services.AddScoped(); + +var app = builder.Build(); + +// apply db migrations on startup so we don't have to worry about it later bc i hate doing db things manually lol +using (var scope = app.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); +} + +var jwtService = app.Services.GetRequiredService(); + +// request logging +app.Use(async (context, next) => +{ + var request = context.Request; + Console.WriteLine($"\n{request.Method} {request.Path}{request.QueryString}"); + + foreach (var header in request.Headers) + Console.WriteLine($" {header.Key}: {string.Join(", ", header.Value)}"); + + if (request.ContentLength > 0) + { + request.EnableBuffering(); + using (var reader = new StreamReader(request.Body, leaveOpen: true)) + { + var body = await reader.ReadToEndAsync(); + Console.WriteLine($" Body: {body}"); + } + request.Body.Position = 0; + } + + await next(context); +}); + +app.MapGet("/", () => +{ + var json = File.ReadAllText("endpoints.json"); + return Results.Content(json, "application/json"); +}); + +app.MapGet("/api/config/v1/amplitude", () => +{ + var config = new + { + AmplitudeKey = "fd846cd67066d19eec62fbdd00be9258", + StatSigKey = "client-mnew4Ggjuu3Fbh86Ed3tRgWUxapp0D7PPB0i3MXUEao", + RudderStackKey = "37qVlzZyXqdmZckXJ61vQeEJp8Y", + UseRudderStack = true + }; + + return Results.Ok(config); +}); + +app.MapGet("/api/config/v2", () => +{ + var json = File.ReadAllText("configv2.json"); + return Results.Content(json, "application/json"); +}); + +app.MapGet("/api/versioncheck/v4", () => +{ + return Results.Content("{\"VersionStatus\":0}","application/json"); +}); + +app.MapGet("/api/gameconfigs/v1/all", () => +{ + var json = File.ReadAllText("gameconfigs.json"); + return Results.Content(json, "application/json"); +}); + +app.MapGet("/photon", () => +{ + var json = File.ReadAllText("photonsettings.json"); + return Results.Content(json, "application/json"); +}); + +app.MapGet("/eac/challenge", () => +{ + var file = File.ReadAllText("eacchallenge.txt"); + return Results.Content(file,"text/plain"); +}); + +app.MapGet("/api/playerReputation/v1/{id}", (string id) => +{ + // TODO: make rep actually work + return Results.Content($"{{\"AccountId\":{id},\"Noteriety\":0,\"CheerGeneral\":0,\"CheerHelpful\":0,\"CheerCreative\":0,\"CheerGreatHost\":0,\"CheerSportsman\":0,\"CheerCredit\":20,\"SelectedCheer\":null}}","application/json"); +}); + +app.MapGet("/api/players/v1/progression/{id}", (string id) => +{ + // TODO: make progression actually work + return Results.Content($"{{\"PlayerId\":{id},\"Level\":1,\"XP\":0}}","application/json"); +}); + +app.MapGet("/api/PlayerReporting/v1/moderationBlockDetails", () => +{ + // TODO: make this actually work + return Results.Content($"{{\"ReportCategory\":0,\"Duration\":0,\"GameSessionId\":0,\"IsHostKick\":false,\"Message\":\"\",\"PlayerIdReporter\":null,\"IsBan\":false}}","application/json"); +}); + +app.MapPost("/player/login", () => +{ + return Results.Ok(); +}); + +app.MapGet("/cachedlogin/forplatformid/{platform}/{id}", async (string platform, string id, AppDbContext db) => +{ + var platformType = int.Parse(platform); + var logins = await db.CachedLogins + .Where(c => c.Platform == (PlatformType)platformType && c.PlatformID == id) + .ToListAsync(); + + return Results.Json(logins.Any() ? logins : new List()); +}); + +app.MapGet("/account/me", (HttpRequest request, AppDbContext db) => +{ + var authHeader = request.Headers.Authorization.ToString(); + + if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) + return Results.Unauthorized(); + + var token = authHeader.Substring("Bearer ".Length); + var accountId = jwtService.ValidateAndGetAccountId(token); + + if (string.IsNullOrEmpty(accountId) || !int.TryParse(accountId, out var id)) + return Results.Unauthorized(); + + var selfAccount = new SelfAccount + { + AccountId = id, + ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg", + IsJunior = false, + Platforms = 0, + PersonalPronouns = 0, + IdentityFlags = 0, + Username = $"Player{id}", + DisplayName = $"Player{id}", + CreatedAt = DateTime.UtcNow, + Email = null, + Phone = null, + JuniorState = null, + Birthday = null, + ParentAccountId = null, + AvailableUsernameChanges = 1 + }; + + return Results.Ok(selfAccount); +}); + +app.MapGet("/account/bulk", (HttpRequest request) => +{ + var ids = request.Query["id"]; + var accounts = new List(); + + foreach (var id in ids) + { + if (int.TryParse(id, out var accountId)) + { + var account = new Account + { + AccountId = accountId, + ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg", + IsJunior = false, + Platforms = 0, + PersonalPronouns = 0, + IdentityFlags = 0, + Username = $"Player{accountId}", + DisplayName = $"Player{accountId}", + CreatedAt = DateTime.UtcNow + }; + accounts.Add(account); + } + } + + return Results.Json(accounts); +}); + +app.MapPost("/account/create", async (HttpRequest httpRequest, AppDbContext db) => +{ + int platform = 0; + string platformId = ""; + + if (httpRequest.ContentLength.HasValue && httpRequest.ContentLength > 0) + { + try + { + var contentType = httpRequest.ContentType ?? ""; + httpRequest.EnableBuffering(); + + using (var reader = new StreamReader(httpRequest.Body, leaveOpen: true)) + { + var body = await reader.ReadToEndAsync(); + + if (!string.IsNullOrWhiteSpace(body)) + { + if (contentType.Contains("application/x-www-form-urlencoded")) + { + var pairs = body.Split('&'); + foreach (var pair in pairs) + { + var keyValue = pair.Split('='); + if (keyValue.Length == 2) + { + var key = Uri.UnescapeDataString(keyValue[0]); + var value = Uri.UnescapeDataString(keyValue[1]); + + if (key == "platform" && int.TryParse(value, out var parsedPlatform)) + platform = parsedPlatform; + else if (key == "platformId") + platformId = value; + } + } + } + } + } + httpRequest.Body.Position = 0; + } + catch { } + } + + var accountId = new Random().Next(10000, 99999); + var account = new Account + { + AccountId = accountId, + ProfileImage = "hdqeamlcmatc6qzoi2ybgf0ddijjcf.jpg", + IsJunior = false, + Platforms = 0, + PersonalPronouns = 0, + IdentityFlags = 0, + Username = $"Player{accountId}", + DisplayName = $"Player{accountId}", + CreatedAt = DateTime.UtcNow + }; + + db.Accounts.Add(account); + + if (!string.IsNullOrEmpty(platformId)) + { + var loginRecord = new CachedLogin + { + AccountId = accountId, + Platform = (PlatformType)platform, + PlatformID = platformId, + LastLoginTime = DateTime.UtcNow, + RequirePassword = false + }; + + db.CachedLogins.Add(loginRecord); + } + + await db.SaveChangesAsync(); + + return Results.Ok(RecNetResultWithValue.Ok(account)); +}); + +app.MapPost("/connect/token", async (HttpRequest httpRequest) => +{ + string accountId = ""; + string platformId = ""; + string platform = ""; + + if (httpRequest.ContentLength.HasValue && httpRequest.ContentLength > 0) + { + try + { + httpRequest.EnableBuffering(); + using (var reader = new StreamReader(httpRequest.Body, leaveOpen: true)) + { + var body = await reader.ReadToEndAsync(); + if (!string.IsNullOrWhiteSpace(body)) + { + var pairs = body.Split('&'); + foreach (var pair in pairs) + { + var keyValue = pair.Split('='); + if (keyValue.Length == 2) + { + var key = Uri.UnescapeDataString(keyValue[0]); + var value = Uri.UnescapeDataString(keyValue[1]); + + if (key == "account_id") + accountId = value; + else if (key == "platform_id") + platformId = value; + } + } + } + } + httpRequest.Body.Position = 0; + } + catch { } + } + + var accessToken = jwtService.GenerateToken(accountId, platformId, platform); + + var response = new + { + access_token = accessToken, + expires_in = 3600, + token_type = "Bearer", + refresh_token = Guid.NewGuid().ToString("N").ToUpper() + "-1", + scope = "offline_access profile rn rn.accounts rn.accounts.gc rn.api rn.chat rn.clubs rn.commerce rn.match.read rn.match.write rn.notify rn.rooms rn.storage", + key = "8oQ+e+WQaOBPbEcakhqs3dwZZdOmmyDUmJSD9u4AHMY=" + }; + + return Results.Json(response); +}); + +app.Run(); + + diff --git a/CannedNet.API/appsettings.Production.json b/CannedNet.API/appsettings.Production.json new file mode 100644 index 0000000..dee1d72 --- /dev/null +++ b/CannedNet.API/appsettings.Production.json @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=cannednet;Username=postgres;Password=postgres" + }, + "Logging": { + "LogLevel": { + "Default": "Information" + } + } +} + diff --git a/CannedNet.API/appsettings.json b/CannedNet.API/appsettings.json new file mode 100644 index 0000000..c499586 --- /dev/null +++ b/CannedNet.API/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Data Source=game.db;Cache=Shared" + }, + "AllowedHosts": "*" +} + diff --git a/CannedNet.API/configv2.json b/CannedNet.API/configv2.json new file mode 100644 index 0000000..1149ae6 --- /dev/null +++ b/CannedNet.API/configv2.json @@ -0,0 +1,373 @@ +{ + "LevelProgressionMaps": [ + { + "Level": 0, + "RequiredXp": 0, + "GiftRarity": -1 + }, + { + "Level": 1, + "RequiredXp": 10, + "GiftRarity": 10 + }, + { + "Level": 2, + "RequiredXp": 10, + "GiftRarity": 10 + }, + { + "Level": 3, + "RequiredXp": 10, + "GiftRarity": 10 + }, + { + "Level": 4, + "RequiredXp": 20, + "GiftRarity": 10 + }, + { + "Level": 5, + "RequiredXp": 20, + "GiftRarity": 10 + }, + { + "Level": 6, + "RequiredXp": 20, + "GiftRarity": 10 + }, + { + "Level": 7, + "RequiredXp": 20, + "GiftRarity": 10 + }, + { + "Level": 8, + "RequiredXp": 20, + "GiftRarity": 10 + }, + { + "Level": 9, + "RequiredXp": 20, + "GiftRarity": 10 + }, + { + "Level": 10, + "RequiredXp": 20, + "GiftRarity": 10 + }, + { + "Level": 11, + "RequiredXp": 45, + "GiftRarity": 10 + }, + { + "Level": 12, + "RequiredXp": 45, + "GiftRarity": 10 + }, + { + "Level": 13, + "RequiredXp": 45, + "GiftRarity": 10 + }, + { + "Level": 14, + "RequiredXp": 45, + "GiftRarity": 10 + }, + { + "Level": 15, + "RequiredXp": 45, + "GiftRarity": 20 + }, + { + "Level": 16, + "RequiredXp": 45, + "GiftRarity": 20 + }, + { + "Level": 17, + "RequiredXp": 45, + "GiftRarity": 20 + }, + { + "Level": 18, + "RequiredXp": 45, + "GiftRarity": 20 + }, + { + "Level": 19, + "RequiredXp": 45, + "GiftRarity": 20 + }, + { + "Level": 20, + "RequiredXp": 45, + "GiftRarity": 20 + }, + { + "Level": 21, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 22, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 23, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 24, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 25, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 26, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 27, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 28, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 29, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 30, + "RequiredXp": 115, + "GiftRarity": 20 + }, + { + "Level": 31, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 32, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 33, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 34, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 35, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 36, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 37, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 38, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 39, + "RequiredXp": 360, + "GiftRarity": 20 + }, + { + "Level": 40, + "RequiredXp": 360, + "GiftRarity": 30 + }, + { + "Level": 41, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 42, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 43, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 44, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 45, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 46, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 47, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 48, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 49, + "RequiredXp": 1080, + "GiftRarity": 30 + }, + { + "Level": 50, + "RequiredXp": 1080, + "GiftRarity": 50 + } + ], + "DailyObjectives": [ + [ + { + "type": 1030, + "score": 1 + }, + { + "type": 1033, + "score": 10 + }, + { + "type": 2000, + "score": 1 + } + ], + [ + { + "type": 601, + "score": 2 + }, + { + "type": 31, + "score": 1 + }, + { + "type": 6, + "score": 1 + } + ], + [ + { + "type": 301, + "score": 2 + }, + { + "type": 300, + "score": 2 + }, + { + "type": 6, + "score": 1 + } + ], + [ + { + "type": 700, + "score": 2 + }, + { + "type": 701, + "score": 20 + }, + { + "type": 31, + "score": 1 + } + ], + [ + { + "type": 1010, + "score": 1 + }, + { + "type": 1013, + "score": 10 + }, + { + "type": 6, + "score": 1 + } + ], + [ + { + "type": 500, + "score": 2 + }, + { + "type": 502, + "score": 20 + }, + { + "type": 2000, + "score": 1 + } + ], + [ + { + "type": 1020, + "score": 1 + }, + { + "type": 1023, + "score": 10 + }, + { + "type": 31, + "score": 1 + } + ] + ], + "ServerMaintenance": { + "StartsInMinutes": 0 + }, + "AutoMicMutingConfig": { + "MicSpamVolumeThreshold": 0.75, + "MicVolumeSampleInterval": 0.25, + "MicVolumeSampleRollingWindowLength": 10, + "MicSpamSamplePercentageForWarning": 0.8, + "MicSpamSamplePercentageForWarningToEnd": 0.2, + "MicSpamSamplePercentageForForceMute": 0.8, + "MicSpamSamplePercentageForForceMuteToEnd": 0.2, + "MicSpamWarningStateVolumeMultiplier": 0.25 + }, + "ShareBaseUrl": "http://localhost:5000/{0}" +} \ No newline at end of file diff --git a/CannedNet.API/eacchallenge.txt b/CannedNet.API/eacchallenge.txt new file mode 100644 index 0000000..e570df3 --- /dev/null +++ b/CannedNet.API/eacchallenge.txt @@ -0,0 +1 @@ +"hi :3" \ No newline at end of file diff --git a/CannedNet.API/endpoints.json b/CannedNet.API/endpoints.json new file mode 100644 index 0000000..e5829b4 --- /dev/null +++ b/CannedNet.API/endpoints.json @@ -0,0 +1,26 @@ +{ + "Auth": "https://rr.lapis.codes", + "API": "https://rr.lapis.codes", + "WWW": "https://www.lapis.codes", + "Notifications": "https://rr.lapis.codes", + "Images": "https://rr.lapis.codes", + "CDN": "https://rr.lapis.codes", + "Commerce": "https://rr.lapis.codes", + "Matchmaking": "https://rr.lapis.codes", + "Storage": "https://rr.lapis.codes", + "Chat": "https://rr.lapis.codes", + "Leaderboard": "https://rr.lapis.codes", + "Accounts": "https://rr.lapis.codes", + "Link": "https://rr.lapis.codes", + "RoomComments": "https://rr.lapis.codes", + "Clubs": "https://rr.lapis.codes", + "Rooms": "https://rr.lapis.codes", + "PlatformNotifications": "https://rr.lapis.codes", + "Moderation": "https://rr.lapis.codes", + "DataCollection": "https://rr.lapis.codes", + "BugReporting": "https://rr.lapis.codes", + "Discovery": "https://rr.lapis.codes", + "PlayerSettings": "https://rr.lapis.codes", + "Rendering": "https://rr.lapis.codes", + "Launcher": "https://rr.lapis.codes" +} \ No newline at end of file diff --git a/CannedNet.API/endpointsreference.json b/CannedNet.API/endpointsreference.json new file mode 100644 index 0000000..97f6e85 --- /dev/null +++ b/CannedNet.API/endpointsreference.json @@ -0,0 +1,26 @@ +{ + "Auth": "https://auth.epicquest.live", + "API": "https://api.epicquest.live", + "WWW": "https://www.epicquest.live", + "Notifications": "https://notify.epicquest.live", + "Images": "https://img.epicquest.live", + "CDN": "https://cdn.epicquest.live", + "Commerce": "https://commerce.epicquest.live", + "Matchmaking": "https://match.epicquest.live", + "Storage": "https://storage.epicquest.live", + "Chat": "https://chat.epicquest.live", + "Leaderboard": "https://leaderboard.epicquest.live", + "Accounts": "https://accounts.epicquest.live", + "Link": "https://link.epicquest.live", + "RoomComments": "https://roomcomments.epicquest.live", + "Clubs": "https://clubs.epicquest.live", + "Rooms": "https://rooms.epicquest.live", + "PlatformNotifications": "https://api.epicquest.live", + "Moderation": "https://api.epicquest.live", + "DataCollection": "https://api.epicquest.live", + "BugReporting": "https://api.epicquest.live", + "Discovery": "https://api.epicquest.live", + "PlayerSettings": "https://api.epicquest.live", + "Rendering": "https://api.epicquest.live", + "Launcher": "https://launcher.epicquest.live" +} \ No newline at end of file diff --git a/CannedNet.API/gameconfigs.json b/CannedNet.API/gameconfigs.json new file mode 100644 index 0000000..bceb648 --- /dev/null +++ b/CannedNet.API/gameconfigs.json @@ -0,0 +1,170 @@ +[ + { + "Key": "Debug.Watchdog.AdditionalTraceFlagsForLoading", + "Value": "None" + }, + { + "Key": "Debug.Watchdog.CorruptRoomDetectionProbability", + "Value": "0" + }, + { + "Key": "Door.Creative.Query", + "Value": "#horror" + }, + { + "Key": "Door.Creative.Title", + "Value": "HORROR" + }, + { + "Key": "Door.Featured.Query", + "Value": "" + }, + { + "Key": "Door.Featured.Title", + "Value": "Door" + }, + { + "Key": "Door.Quests.Query", + "Value": "#quest #recroomoriginal" + }, + { + "Key": "Door.Quests.Title", + "Value": "QUESTS" + }, + { + "Key": "Door.Shooters.Query", + "Value": "#shooters" + }, + { + "Key": "Door.Shooters.Title", + "Value": "SHOOTERS" + }, + { + "Key": "Door.Sports.Query", + "Value": "#recroomoriginal #sports | #pvp #recroomoriginal " + }, + { + "Key": "Door.Sports.Title", + "Value": "SPORTS & PVP" + }, + { + "Key": "Door.StuntRunner.Query", + "Value": "#stuntrunner #community" + }, + { + "Key": "Door.StuntRunner.Title", + "Value": "COMMUNITY" + }, + { + "Key": "EnableRoomResetLogs", + "Value": "1" + }, + { + "Key": "FentReactor.Enabled", + "Value": "True" + }, + { + "Key": "Friends.PostGamePromptUnderFriendCount", + "Value": "10" + }, + { + "Key": "Friends.SuggestFriendCodeOnFriendsScreenCount", + "Value": "25" + }, + { + "Key": "Growth.ShouldPromptForCreatorEmail", + "Value": "true" + }, + { + "Key": "loadingNetworkTimeout", + "Value": "180" + }, + { + "Key": "Marquee", + "Value": "CannedNet" + }, + { + "Key": "RAC:CurrentChallenge", + "Value": "{\"Challenge\":\"AQJCIIXfWaPue2PO75i5nM9nVCPTCbhjnQDUU94I+v//SrxkmVSoLfdEoY5Dp/BwBB+wgIh3kbNL5SPZk2KRQeDDVn+LC/EQoajdcxE59qSDYLM8G1bXued1vRv/Wlk0p5M+Ov2LrnNxS6r8uL2o7V3prYZzEOTrdculBU8rbq4=\",\"LastUpdated\":\"2026-01-15T01:18:34.167596Z\"}" + }, + { + "Key": "Rendering.DisableSrpBatcher", + "Value": "true" + }, + { + "Key": "Rewards.SelectionTimeout", + "Value": "1800" + }, + { + "Key": "Rewards.UseRewardSelection", + "Value": "false" + }, + { + "Key": "RoomDetails.PhotoRollEnabled", + "Value": "true" + }, + { + "Key": "RoomScaleMotionCheatDetector.MaxAllowedAverageHmdSpeed", + "Value": "1024" + }, + { + "Key": "RoomScaleMotionCheatDetector.MeasurementPeriodInSeconds", + "Value": "5" + }, + { + "Key": "RoomScaleMotionCheatDetector.MinHmdSpeedAboveTriggerCount", + "Value": "1024" + }, + { + "Key": "RoomScaleMotionCheatDetector.ShouldBootToDormWhenDetected.Oculus", + "Value": "false" + }, + { + "Key": "RoomScaleMotionCheatDetector.ShouldBootToDormWhenDetected.Steam", + "Value": "false" + }, + { + "Key": "RoomScaleMotionCheatDetector.TriggerHmdSpeed", + "Value": "1024" + }, + { + "Key": "runningNetworkTimeout", + "Value": "30" + }, + { + "Key": "Screens.ForceVerification", + "Value": "1" + }, + { + "Key": "splitTestHardOverrides", + "Value": "GottaGoFastNUX_2020_09_04=Off;PlayMenuRRUI_2021_01_11=Off;RoomRecommendations_2020_05_06=On;" + }, + { + "Key": "splitTestSegmentProbabilities", + "Value": "" + }, + { + "Key": "splitTestSoftOverrides", + "Value": "FentReactorDebugGuiEnabled_2025_11_20=DeveloperOnly;" + }, + { + "Key": "SynchronizedField.RemoveDefaultEntries", + "Value": "true" + }, + { + "Key": "UGC.ScreenSharing.HorizontalResolution", + "Value": "360" + }, + { + "Key": "UGC.ScreenSharing.ImageCompressionQuality", + "Value": "50" + }, + { + "Key": "UGC.ScreenSharing.RefreshHz", + "Value": "15" + }, + { + "Key": "VR.ForceVerification", + "Value": "1" + } +] \ No newline at end of file diff --git a/CannedNet.API/photonsettings.json b/CannedNet.API/photonsettings.json new file mode 100644 index 0000000..3e6d13d --- /dev/null +++ b/CannedNet.API/photonsettings.json @@ -0,0 +1,13 @@ +{ + "RealtimeAppId": "038c1828-7979-4735-8d30-50c3276c30a7", + "VoiceAppId": "f6a30374-c1ce-4ea5-8919-45bc29203bc9", + "ChatAppId": "f6a30374-c1ce-4ea5-8919-45bc29203bc9", + "HostingOption": 1, + "Protocol": 0, + "ServerAddress": "127.0.0.1", + "ServerPort": -1, + "VoiceServerPort": -1, + "ChatServerPort": -1, + "NetworkLogging": 0, + "PunLogging": 0 +} \ No newline at end of file diff --git a/CannedNet.slnx b/CannedNet.slnx new file mode 100644 index 0000000..e907e66 --- /dev/null +++ b/CannedNet.slnx @@ -0,0 +1,4 @@ + + + +