This commit is contained in:
skidfire
2026-09-06 19:59:27 -04:00
parent 55d8e11a5d
commit fe3468c65d
247 changed files with 338183 additions and 0 deletions
+736
View File
@@ -0,0 +1,736 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using Vanadium.Classes.WebSocket;
using Vanadium.Auth;
using Vanadium.Hubs;
using Vanadium.Utils.NotiController;
using System.Text.RegularExpressions;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
namespace Vanadium.Controllers
{
[ApiController]
[Route("/")]
public class AccountController : ControllerBase
{
public class VerifyDiscordConfirmRequest { public string? pin { get; set; } }
[HttpPost("beta/verifydiscord/confirm")] // lowk
public async Task<IActionResult> BetaVerifyDiscordConfirm([FromBody] VerifyDiscordConfirmRequest req)
{
if (string.IsNullOrWhiteSpace(req.pin) || !Program.PendingInGameCodes.TryRemove(req.pin.Trim(), out var data))
return BadRequest(new { success = false, error = "Invalid or expired PIN." });
var player = PlayerDB.Players.FindById(data.AccountId);
if (player == null)
return BadRequest(new { success = false, error = "Account not found." });
player.Player.DiscordUserId = data.DiscordUserId.ToString();
player.Player.DiscordLinked = true;
PlayerDB.Players.Update(player);
return Ok(new { success = true });
}
/* Fixed reputation + cheering due to progression overhaul in a way */
[HttpPost("api/PlayerCheer/v1/create")]
public async Task<IActionResult> CreatePlayerCheer([FromForm] long PlayerIdTo, [FromForm] int CheerCategory, [FromForm] long RoomId, [FromForm] bool Anonymous)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (id.Value == PlayerIdTo)
return Ok(new { Message = "You can not cheer yourself silly!", success = false });
var target = PlayerDB.Players.FindById(PlayerIdTo);
if (target == null)
return NotFound("");
if (Anonymous)
await NotificationsController.SendPlayerCheerAnonymous(PlayerIdTo, id.Value, CheerCategory);
else
await NotificationsController.SendPlayerCheer(PlayerIdTo, id.Value, CheerCategory);
return Ok(new { success = true });
}
// disabled SetSelectedCheer due to it breaking rr account birthdays!?!? idk but changing it doesnt let you spawn inventions or request to join someone
/*[HttpPost("api/PlayerCheer/v1/SetSelectedCheer")]
public async Task<IActionResult> SetSelectedCheer([FromForm(Name = "CheerCategory")] long cheerCategoryValue)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null || player.Player == null)
return NotFound("");
var requestedCheer = (CheerCategory)cheerCategoryValue;
bool isDeveloper = player.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false;
if (requestedCheer == CheerCategory.RecRoomDeveloper && !isDeveloper && player.PlayerId != 216 && player.PlayerId != 936)
return StatusCode(403);
player.Player.Reputation ??= new Reputation();
player.Player.Reputation.SelectedCheer = requestedCheer;
player.Player.Reputation.IsCheerful = true;
PlayerDB.Players.Update(player);
var rep = player.Player.Reputation;
var reupation = new // game doesnt wanna respect IsCheerful true so fuck it
{
AccountId = player.PlayerId,
Notoriety = rep.Noteriety,
IsCheerful = true,
CheerGeneral = rep.CheerGeneral,
CheerHelpful = rep.CheerHelpful,
CheerGreatHost = rep.CheerGreatHost,
CheerSportsman = rep.CheerSportsman,
CheerCreative = rep.CheerCreative,
CheerCredit = rep.CheerCredit,
SelectedCheer = (int)rep.SelectedCheer
};
var json = JsonConvert.SerializeObject(new WebsocketEvents.Response { Id = NotiEventTypes.ReputationUpdate, Msg = reupation });
await Task.WhenAll(NotificationsController.SendToAll(json), NotificationsController.SendToPlayer(id.Value, json));
await NotificationsController.RefreshAccount(id.Value);
return Ok(new { success = true, selectedCheer = (int)rep.SelectedCheer });
}*/
/* End of rep/cheer */
[HttpGet("accounts/{playerId}/receives/GameplayInvites")]
public IActionResult PlayerRecievesGameplayInvites(long playerId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
return Ok(true);
}
[HttpGet("acc/account/bulk")]
public async Task<IActionResult> GetAccountsBulk([FromQuery] List<long> id)
{
return BuildAccountsBulkResponse(id);
}
[Route("acc/account/bulk/email")]
[Route("acc/account/bulk/phone")]
public IActionResult GetiOSBulk() => Ok(ServerConfig.Bracket);
[HttpPost("acc/account/bulk")]
[Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> PostAccountsBulk([FromForm] List<long> id)
{
return BuildAccountsBulkResponse(id);
}
private IActionResult BuildAccountsBulkResponse(List<long> id)
{
if (id == null || id.Count == 0)
return Ok(new List<PlayerDTO>());
if (id[0] == 0)
return Ok(new List<PlayerDTO>
{
new PlayerDTO
{
accountId = 0,
createdAt = DateTime.UtcNow,
displayName = "@",
displayEmoji = null,
isJunior = false,
platforms = 0,
profileImage = "DefaultPFP.png",
username = "@",
personalPronouns = 0,
identityFlags = 0
}
});
return Ok(PlayerDB.GetAccountsBulk(id));
}
[HttpPut("acc/account/me/profileimage")]
public async Task<IActionResult> PostAccountMeProfileImage([FromForm] string imageName)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (string.IsNullOrWhiteSpace(imageName))
return BadRequest("come on bro");
if (PlayerDB.IsBanned(id.Value))
{
APIController.SendNonHileWebhook(id.Value, "not a hile, but a banned user tried to change their profile image");
PlayerDB.UpdatePlayerHeartbeat(id.Value, null, online: false);
return StatusCode(403, "recapi activate");
}
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
player.Player.ProfileImage = imageName;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(id.Value);
var updated = PlayerDB.GetAccountMe(id.Value);
return Ok(new
{
success = true,
});
}
[HttpGet("api/banappeal/generateCode")]
public IActionResult MakeBanAppealCode()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
return Ok(new { value = "appeal at discord.gg/rec-room" });
}
[HttpPut("acc/account/me/bannerimage")]
public async Task<IActionResult> PostAccountMeBannerImage([FromForm] string imageName)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (string.IsNullOrWhiteSpace(imageName))
return BadRequest("hahaha you are bad at this");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("Player not found.");
player.Player.BannerImage = imageName;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(id.Value);
var updated = PlayerDB.GetAccountMe(id.Value);
return Ok(new
{
success = true
});
}
[HttpPut("acc/account/me/bio")]
public async Task<IActionResult> UpdateAccountMeBio([FromForm] string bio)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
bio = bio ?? "";
if (!ServerConfig.AllowSwears && !string.IsNullOrWhiteSpace(bio))
{
var swearPath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "swears.txt");
if (System.IO.File.Exists(swearPath))
{
var swearWords = System.IO.File.ReadAllLines(swearPath);
foreach (var word in swearWords.Select(w => w.Trim()).Where(w => !string.IsNullOrEmpty(w)))
{
if (Regex.IsMatch(bio, $@"(?<![a-zA-Z]){Regex.Escape(word)}(?![a-zA-Z])", RegexOptions.IgnoreCase))
return Ok(new { error = "Bio contains prohibited text.", success = false, value = (object?)null });
}
}
}
var player = PlayerDB.Players.FindById(id.Value);
if (player == null || player.Player == null)
return NotFound("");
player.Player.Bio = bio;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(id.Value);
return Ok(new { success = true });
}
[HttpGet("acc/account/{playerId}/bio")]
public async Task<IActionResult> GetAccountBio(long playerId)
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null || player.Player == null)
return NotFound("");
return Ok(new
{
accountId = playerId,
bio = player.Player.Bio ?? null
});
}
[HttpPut("/acc/account/me/username")]
public async Task<IActionResult> UpdateUsername([FromForm] string username)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
if (player.Player.NameLocked)
{
return Ok(new
{
error = "Your name is locked and is unable to be changed.",
success = false,
value = (object?)null
});
}
if (string.IsNullOrWhiteSpace(username))
{
return Ok(new
{
error = "Name contains prohibited text.",
success = false,
value = (object?)null
});
}
username = username.Trim();
username = Regex.Replace(username, @"[\u200B-\u200D\uFEFF]", "");
if (!Regex.IsMatch(username, @"^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$"))
{
return Ok(new
{
error = "Name contains prohibited text.",
success = false,
value = (object?)null
});
}
if (!ServerConfig.AllowSwears)
{
var swearPath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "swears.txt");
if (System.IO.File.Exists(swearPath))
{
var swearWords = System.IO.File.ReadAllLines(swearPath);
foreach (var word in swearWords.Select(w => w.Trim()).Where(w => !string.IsNullOrEmpty(w)))
{
string pattern = $@"\b{Regex.Escape(word)}\b";
if (Regex.IsMatch(username, pattern, RegexOptions.IgnoreCase))
{
return Ok(new
{
error = "Name contains prohibited text.",
success = false,
value = (object?)null
});
}
}
}
}
var taken = PlayerDB.Players.FindAll()
.Any(p => p.PlayerId != account.Value &&
p.Player != null &&
string.Equals(p.Player.Username, username, StringComparison.OrdinalIgnoreCase));
if (taken)
{
return Ok(new
{
errorCode = "Accounts.UsernameAlreadyInUse",
error = "This username is already in use.",
success = false,
value = (object?)null
});
}
if (player.Player.LastUsernameChangeAt.HasValue &&
(DateTime.UtcNow - player.Player.LastUsernameChangeAt.Value).TotalHours >= 2)
{
player.Player.AvailableUsernameChanges = 3;
player.Player.LastUsernameChangeAt = null;
}
if (player.Player.AvailableUsernameChanges <= 0)
{
APIController.SendNonHileWebhook(account.Value, "User tried to change their username but has no available changes left. current username: " + player.Player.Username + ", attempted new username: " + username);
return Ok(new
{
error = "Please wait a while before changing your username!",
success = false,
value = (object?)null
});
}
player.Player.Username = username;
player.Player.DisplayName = username;
player.Player.AvailableUsernameChanges -= 1;
player.Player.LastUsernameChangeAt ??= DateTime.UtcNow;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
return Ok(new
{
success = true,
});
}
[HttpPut("/acc/account/me/displayname")]
public async Task<IActionResult> UpdateDisplayName([FromForm] string displayName)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
if (player.Player.NameLocked)
return Ok(new { error = "Your name is locked and is unable to be changed.", success = false, value = (object?)null });
if (string.IsNullOrWhiteSpace(displayName))
return Ok(new { error = "Name contains prohibited text.", success = false, value = (object?)null });
displayName = displayName.Trim();
displayName = Regex.Replace(displayName, @"[\u200B-\u200D\uFEFF]", "");
if (!ServerConfig.AllowSwears)
{
var swearPath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "swears.txt");
if (System.IO.File.Exists(swearPath))
{
var swearWords = System.IO.File.ReadAllLines(swearPath);
foreach (var word in swearWords.Select(w => w.Trim()).Where(w => !string.IsNullOrEmpty(w)))
{
if (Regex.IsMatch(displayName, $@"\b{Regex.Escape(word)}\b", RegexOptions.IgnoreCase))
return Ok(new { error = "Name contains prohibited text.", success = false, value = (object?)null });
}
}
}
player.Player.DisplayName = displayName;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
return Ok(new { error = (object?)null, success = true });
}
[HttpPut("/acc/account/me/personalpronouns")]
public async Task<IActionResult> UpdatePersonalPronouns([FromForm] int pronounFlags)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
player.Player.PronounFlags = pronounFlags;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
var updated = PlayerDB.GetAccountMe(account.Value);
return Ok(new
{
success = true
});
}
[HttpPut("/acc/account/me/identityflags")]
public async Task<IActionResult> UpdateIdentityFlags([FromForm] int identityFlags)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
player.Player.IdentityFlags = identityFlags;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
var updated = PlayerDB.GetAccountMe(account.Value);
return Ok(new
{
success = true
});
}
[HttpPut("/acc/account/me/emoji")]
public async Task<IActionResult> UpdateDisplayEmoji([FromForm] string? displayEmoji)
{
var account = AuthStuff.GetPlayerId(Request);
if (account == null)
return StatusCode(403);
var player = PlayerDB.Players.FindById(account.Value);
if (player == null || player.Player == null)
return StatusCode(404);
player.Player.DisplayEmoji = string.IsNullOrWhiteSpace(displayEmoji) ? null : displayEmoji;
PlayerDB.Players.Update(player);
await NotificationsController.RefreshAccount(account.Value);
return Ok(new
{
success = true
});
}
[HttpGet("acc/account/me")]
public async Task<IActionResult> GetAccountMe()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
var account = PlayerDB.GetAccountMe(id.Value);
return account != null ? Ok(account) : NotFound();
}
[HttpPost("acc/account/me/email")]
public async Task<IActionResult> AccAccountMeEmail()
{
return Ok(new
{
email = "vanadium@van.net",
success = true
});
}
private static long EnsurePlayerClub(FullPlayer target)
{
long clubId = target.Player.PlayerExtra.PlayerClubId;
if (clubId > 0 && ClubsDB.GetClub(clubId) != null)
return clubId;
var club = ClubsDB.CreatePlayerClub(target.PlayerId);
target.Player.PlayerExtra.PlayerClubId = club.ClubId;
PlayerDB.Players.Update(target);
return club.ClubId;
}
[HttpPost("subscription/{PlayerId}")]
public async Task<IActionResult> Subscribe(ulong? PlayerId)
{
var user = AuthStuff.GetPlayerId(Request);
if (user == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(PlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound("");
long clubId = EnsurePlayerClub(target);
var club = ClubsDB.JoinClub(clubId, user.Value);
var repUpdate = WebsocketEvents.CreateReputationUpdateResponse(target.PlayerId);
await NotificationsController.SendToAll(JsonConvert.SerializeObject(repUpdate));
if (club != null)
{
var joined = club.Members.FirstOrDefault(m => m.AccountId == user.Value);
if (joined != null)
{
await NotificationsController.BroadcastClubMembershipUpdate(joined.MemberId, user.Value, clubId, joined.MemberType, joined.JoinedAt);
await NotificationsController.BroadcastCreatorClubSubscriptionUpdate(target.PlayerId, clubId, joined.MemberType);
}
}
return Ok(new
{
error = (object?)null,
success = true,
value = 0
});
}
[HttpGet("acc/parentalcontrols/me")]
public IActionResult ParentalControlShit()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
return Ok(new { accountId = player, disallowInAppPurchases = false });
}
[HttpGet("acc/accountprivacysettings/{playerId}")]
public async Task<IActionResult> PrivacySettingsAcc(long playerId)
{
var user = AuthStuff.GetPlayerId(Request);
if (user == null)
return Unauthorized("");
return Ok(new { accountId = playerId, isRecentHistoryVisible = true });
}
[HttpGet("/config/categories")]
public async Task<IActionResult> ConfigCategories(long PlayerId)
{
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "categories.json");
if (!System.IO.File.Exists(path)) return Ok(ServerConfig.Bracket);
return PhysicalFile(path, "application/json");
}
[HttpGet("/preferences")]
public async Task<IActionResult> Preferences(long PlayerId)
{
return Ok(new { mutedCategories = Array.Empty<string>() });
}
[HttpDelete("subscription/{PlayerId}")]
public async Task<IActionResult> Unsubscribe(long PlayerId)
{
var user = AuthStuff.GetPlayerId(Request);
if (user == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(PlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound();
long clubId = EnsurePlayerClub(target);
var clubBefore = ClubsDB.GetClub(clubId);
var leaving = clubBefore?.Members.FirstOrDefault(m => m.AccountId == user.Value);
ClubsDB.LeaveClub(clubId, user.Value);
var repUpdate = WebsocketEvents.CreateReputationUpdateResponse(target.PlayerId);
await NotificationsController.SendToAll(JsonConvert.SerializeObject(repUpdate));
if (leaving != null)
{
await NotificationsController.BroadcastClubMembershipUpdate(leaving.MemberId, user.Value, clubId, 0, DateTime.UtcNow);
await NotificationsController.BroadcastCreatorClubSubscriptionUpdate(target.PlayerId, clubId, 0);
}
return Ok(new
{
error = (object?)null,
success = true,
value = 1
});
}
[HttpGet("subscription/subscriberCount/{targetPlayerId}")]
public async Task<IActionResult> GetSubscriberCount(long targetPlayerId)
{
var target = PlayerDB.Players.FindById(targetPlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound("");
long clubId = EnsurePlayerClub(target);
int count = ClubsDB.GetSubscriberCount(clubId);
return Content(count.ToString(), "text/plain");
}
[Route("/acc/account/me/haspassword")]
public async Task<IActionResult> HasPassword()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return NotFound("");
return Ok(!string.IsNullOrEmpty(player.Password));
}
[HttpPost("auth/account/me/changepassword")]
public async Task<IActionResult> ChangePassword([FromForm] string newPassword)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var player = PlayerDB.Players.FindById(id.Value);
if (player == null)
return Ok(new { success = false, error = "Something went wrong trying to change your password." });
if (string.IsNullOrWhiteSpace(newPassword))
return Ok(new { success = false, error = "Password cannot be empty." });
var (valid, theerror) = PlayerDB.PasswordManager.ValidatePassword(newPassword);
if (!valid)
return Ok(new { success = false, error = theerror });
var result = PlayerDB.PasswordManager.ChangePassword(id.Value, newPassword);
if (!result)
return Ok(new { success = false, error = "Something went wrong trying to change your password." });
return Ok(new { success = true });
}
[HttpGet("subscription/details/{targetPlayerId}")]
public async Task<IActionResult> GetSubscriptionDetails(long targetPlayerId)
{
var target = PlayerDB.Players.FindById(targetPlayerId);
if (target == null || target.Player?.PlayerExtra == null)
return NotFound("");
long clubId = EnsurePlayerClub(target);
int count = ClubsDB.GetSubscriberCount(clubId);
return Ok(new
{
accountId = targetPlayerId,
clubId = clubId,
subscriberCount = count
});
}
}
}
+777
View File
@@ -0,0 +1,777 @@
using Microsoft.AspNetCore.Mvc;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using Vanadium.Auth;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Components.Forms;
namespace Vanadium.Controllers
{
[ApiController]
[Route("/auth")]
public class AuthController : ControllerBase
{
private static readonly HttpClient _webhookClient = new HttpClient();
private const string WebhookUrl = "https://discord.com/api/webhooks/1515954214885654538/-OjeNp_0GYNuO-H7ztAU37_TtxxSfqI_cVuv9p-Lk-VzVw_OkD9Ptomfe3eSgZwxryMv";
private static async Task SendCreatedAccountWebhook(long accountId, DateTime createdAt, string platformId, Platforms platform)
{
var payload = new
{
username = "Vanadium",
embeds = new[]
{
new
{
title = "Vanadium New Account",
color = 3066993,
fields = new[]
{
new { name = "Account ID", value = accountId.ToString(), inline = true },
new { name = "Platform", value = platform.ToString(), inline = true },
new { name = "Platform ID", value = platformId, inline = true },
new { name = "Created At", value = createdAt.ToString("u"), inline = false }
}
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try { await _webhookClient.PostAsync(WebhookUrl, content); } catch { }
}
private static async Task SendSteamBlockedWebhook(Platforms platform, string platformId)
{
var payload = new
{
username = "Vanadium",
embeds = new[]
{
new
{
title = "Vanadium New Account Failed (steam id blocked)",
color = 15158332,
fields = new[]
{
new { name = "Platform", value = platform.ToString(), inline = true },
new { name = "Platform ID", value = platformId, inline = true },
new { name = "Created At", value = DateTime.UtcNow.ToString("u"), inline = false }
}
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try { await _webhookClient.PostAsync(WebhookUrl, content); } catch { }
}
private static async Task SendOculusBlockedWebhook(Platforms platform, string platformId)
{
var payload = new
{
username = "Vanadium",
embeds = new[]
{
new
{
title = "Vanadium Login Failed (oculus nonce rejected)",
color = 15158332,
fields = new[]
{
new { name = "Platform", value = platform.ToString(), inline = true },
new { name = "Platform ID", value = platformId, inline = true },
new { name = "Timestamp", value = DateTime.UtcNow.ToString("u"), inline = false },
new { name = "Info", value = "Meta did not confirm the nonce was issued to this user id", inline = false }
}
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try { await _webhookClient.PostAsync(WebhookUrl, content); } catch { }
}
private static async Task SendTokenLoginWebhook(long accountId, string grantType, object requestData)
{
var player = PlayerDB.Players.FindById(accountId);
bool isDeveloper = player?.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false;
bool isRootAccount = accountId == 1;
if (!isDeveloper && !isRootAccount)
return;
var requestJson = JsonSerializer.Serialize(requestData, new JsonSerializerOptions { WriteIndented = true });
var payload = new
{
username = "Vanadium",
embeds = new[]
{
new
{
title = $"Player {accountId} logged in",
color = 5814783,
fields = new[]
{
new { name = "Grant Type", value = grantType, inline = true },
new { name = "Account ID", value = accountId.ToString(), inline = true },
new { name = "Is Developer", value = isDeveloper.ToString(), inline = true },
new { name = "Request Body", value = $"```json\n{requestJson}\n```", inline = false }
},
timestamp = DateTime.UtcNow.ToString("o")
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try { await _webhookClient.PostAsync(WebhookUrl, content); } catch { }
}
private static async Task SendSpoofAttemptWebhook(long claimedAccountId, Platforms platform, string platformId)
{
var payload = new
{
username = "Vanadium",
embeds = new[]
{
new
{
title = "Unfamiliar login hash",
color = 15158332,
fields = new[]
{
new { name = "Claimed Account ID", value = claimedAccountId.ToString(), inline = true },
new { name = "Platform", value = platform.ToString(), inline = true },
new { name = "Platform ID", value = platformId, inline = true },
new { name = "Timestamp", value = DateTime.UtcNow.ToString("u"), inline = false },
new { name = "Info", value = "Someone either tried to spoof or logged in after playing another revival", inline = false }
}
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try { await _webhookClient.PostAsync(WebhookUrl, content); } catch { }
}
private static async Task SendSkidWebhook(Platforms platform, ulong platformId)
{
var payload = new
{
username = "Vanadium",
embeds = new[]
{
new
{
title = "Blacklisted Platform Id Tryna Skid - cached login api GET",
color = 15158332,
fields = new[]
{
new { name = "Platform", value = platform.ToString(), inline = true },
new { name = "Platform ID", value = platformId.ToString(), inline = true },
new { name = "Timestamp", value = DateTime.UtcNow.ToString("u"), inline = false }
}
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try { await _webhookClient.PostAsync(WebhookUrl, content); } catch { }
}
internal async Task SendE12354Webhook(
string? request,
string? ip,
string? headersPretty)
{
var embed = new Dictionary<string, object?>
{
["title"] = $"Gotchu bru",
["color"] = 3066993,
["fields"] = new object[]
{
new { name = "Headers", value = headersPretty ?? Request.Headers.ToString(), inline = false },
new { name = "IP", value = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown", inline = false },
new { name = "Request Body", value = request ?? "No request provided", inline = true }
},
};
var payload = new
{
embeds = new[] { embed }
};
try
{
var content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(payload),
Encoding.UTF8, "application/json");
await APIController._webhookClient.PostAsync(
"https://discord.com/api/webhooks/1544973415109234738/jlsJiqhHxQ145xDjhl5c68D614mKgzJCIdGTuPwvsFmdicxR26ujube-bR2hgbQrNjKq",
content);
}
catch { }
}
[HttpGet("oculus/nonce")]
public IActionResult QuestSupportFor2025()
{
var random = new Random();
const string randomstuffatp = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var randomString = new string(Enumerable.Repeat(randomstuffatp, 64).Select(s => s[random.Next(s.Length)]).ToArray());
return Ok($"\"{randomString}\"");
}
[HttpGet("cachedlogin/forplatformids")]
public IActionResult GetPlayersForPlatformIds([FromQuery] List<string> id)
{
if (id == null || id.Count == 0)
return Ok(Array.Empty<object>());
var results = PlayerDB.Players.FindAll()
.Where(p => p.PlatformIds != null &&
p.PlatformIds.Any(pid => id.Contains(pid.PlatformId, StringComparer.OrdinalIgnoreCase)))
.ToList();
var dtos = results.Select(p => PlayerDB.GetAccountsBulk(new List<long> { p.PlayerId }).FirstOrDefault())
.Where(p => p != null)
.ToList();
return Ok(dtos);
}
[HttpGet("eac/challenge")]
public async Task<IActionResult> C_hallenge()
{
return Ok("\"AQAAAHsg7mW5FQEE9HVl9EKMWXrqDzQxUCdgV/IPuQfbRgTx+cGnQqhhAgv1RvpihEC77gQ29JdoGFn2806Q+QPEj7nYg9C8pynbaiSVO8rKLJPvROsHuSXVJpQMv3TD8KyK3Y+n5bb86vAb5kRdZGD//uC8HY+D9jJLlEfTUlU=\"");
}
[HttpGet("cachedlogin/forplatformid/{platform}/{platformId}")]
[HttpPost("cachedlogin/forplatformid/{platform}/{platformId}")]
public async Task<IActionResult> GetCachedLogins(Platforms platform, string platformId)
{
/*if (ServerConfig.BlacklistedPlatformIds.Contains(platformId))
{
Console.WriteLine($"[AUTH] AutoAcc skid detected platform={platform} platformId={platformId} in blacklisted platform ids list");
_ = SendSkidWebhook(platform, platformId);
return Ok("");
}*/
/*if (platform == Platforms.Steam && !ServerConfig.IsSteamIdWhitelisted(platformId))
{
return Ok(new
{
error = "Accounts.UserIsBlacklisted",
message_message = "Blacklisted from using Vanadium.",
success = false
});
}*/
if (PlayerDB.GetLogins(platform, platformId, out var accounts) && accounts.Count > 0)
return Ok(accounts);
if (ServerConfig.AutoAccount)
{
/*if (PlayerDB.IsSteamPlayerInSupportedGame(platformId).Result == false)
{
Console.WriteLine($"[AUTH] AutoAcc player not playing Rec Room or Spacewar platform={platform} platformId={platformId} not in supported game");
_ = SendSteamBlockedWebhook(platform, platformId);
return Ok("");
}
var cachedLogin = new List<CachedLogins>
{
new CachedLogins
{
accountId = 0,
lastLoginTime = DateTime.UtcNow,
platform = platform,
platformId = null
}
};
return Ok(cachedLogin);*/
Console.WriteLine($"[AUTH] AutoAcc no accounts for platform={platform} platformId={platformId} so we creating one");
var newPlayer = PlayerDB.CreateAccount(platform, platformId, false);
_ = SendCreatedAccountWebhook(newPlayer.PlayerId, newPlayer.Player.CreatedAt, platformId, platform);
Console.WriteLine($"[AUTH] AutoAcc created account with id={newPlayer.PlayerId}");
var cachedLogin = new List<CachedLogins>
{
new CachedLogins
{
accountId = newPlayer.PlayerId,
lastLoginTime = DateTime.UtcNow,
platform = platform,
platformId = platformId.ToString()
}
};
return Ok(cachedLogin);
}
return Unauthorized("");
}
[HttpGet("/auth/role/developer/{playerId}")]
public async Task<IActionResult> IsDeveloper(long playerId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(playerId);
if (target == null)
return NotFound("");
return Ok(target.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false);
}
[HttpGet("/auth/role/moderator/{playerId}")]
public async Task<IActionResult> IsModerator(long playerId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(playerId);
if (target == null)
return NotFound("");
return Ok(target.PlayerRoles?.Contains(PlayerRoles.Moderator) ?? false);
}
[HttpGet("/auth/role/keepsake/{playerId}")]
public async Task<IActionResult> HasScreenSharing(long playerId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(playerId);
if (target == null)
return Ok(false);
return Ok(target.PlayerRoles?.Contains(PlayerRoles.Keepsake) ?? false);
}
[HttpGet("/auth/role/developer")]
public async Task<IActionResult> IsDeveloper2025([FromQuery] long id)
{
var requesterId = AuthStuff.GetPlayerId(Request);
if (requesterId == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(id);
if (target == null)
return NotFound("");
return Ok(target.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false);
}
[HttpGet("/auth/role/moderator")]
public async Task<IActionResult> IsModerator2025([FromQuery] long id)
{
var requesterId = AuthStuff.GetPlayerId(Request);
if (requesterId == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(id);
if (target == null)
return NotFound("");
return Ok(target.PlayerRoles?.Contains(PlayerRoles.Moderator) ?? false);
}
[HttpGet("/auth/role/keepsake")]
public async Task<IActionResult> HasScreenSharing2025([FromQuery] long id)
{
var requesterId = AuthStuff.GetPlayerId(Request);
if (requesterId == null)
return Unauthorized("");
var target = PlayerDB.Players.FindById(id);
if (target == null)
return Ok(false);
return Ok(target.PlayerRoles?.Contains(PlayerRoles.Keepsake) ?? false);
}
[HttpPost("auth/cachedlogin/forplatformids")]
public async Task<IActionResult> PostCachedLoginForPlatformIds([FromForm] List<ulong> id)
{
var accounts = new List<CachedLogins>();
foreach (var platformId in id)
{
if (PlayerDB.GetLogins(Platforms.Steam, platformId.ToString(), out var foundAccounts))
accounts.AddRange(foundAccounts);
}
return Ok(accounts.OrderByDescending(a => a.lastLoginTime).ToList());
}
private static void PreWarmDorm(long accountId)
{
_ = Task.Run(() =>
{
try
{
var player = PlayerDB.Players.FindById(accountId);
if (player?.Player == null)
return;
long dormRoomId = player.Player.PlayerExtra.DormRoomId;
if (dormRoomId <= 0)
{
var dormRoom = RoomDB.GetPlayerDormRoom(accountId);
if (dormRoom == null)
{
dormRoomId = RoomDB.CloneDormRoom(accountId);
player.Player.PlayerExtra.DormRoomId = dormRoomId;
PlayerDB.UpdateDormRoomId(accountId, dormRoomId);
}
else
{
dormRoomId = dormRoom.RoomId;
PlayerDB.UpdateDormRoomId(accountId, dormRoomId);
}
}
Sessions.CreateDorm(accountId, player.Player.Username, dormRoomId);
}
catch
{
}
});
}
private static async Task SendModernVersionWebhook(long accountId, double version)
{
var payload = new
{
username = "Vanadium",
embeds = new[]
{
new
{
title = "User logged in on modern version!",
color = 0,
fields = new[]
{
new { name = "Info", value = $"`{accountId}` logged in on version `{version.ToString(System.Globalization.CultureInfo.InvariantCulture)}`", inline = false }
}
}
}
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try { await _webhookClient.PostAsync(ServerConfig.AntiCheatWebhook, content); } catch { }
}
private static readonly string TokenScope = "offline_access rn"; // i don't think i need this
private async Task<IActionResult?> Check2025ShitAsync(long playerId, string? ver)
{
if (!Build2025Validator.Is2025Build(ver)) return null;
var (allowed, errorKey, errorDesc) = await Build2025Validator.ValidateAsync(playerId);
if (allowed) return null;
return Unauthorized(new { error = errorKey, error_description = errorDesc });
}
private IActionResult TokenResponse(long accountId, string grantType, object requestData, DeviceClasses? deviceClass = null, double? ver = null)
{
// The patch gate. Every grant type funnels through here, so this is the one place
// it has to be checked - and the refusal is shaped like the other login denials
// above so the client shows the message instead of a generic failure.
string? patchDenial = Bluetint.DenyLogin(accountId, Request);
if (patchDenial != null)
{
return Ok(new
{
error = "invalid_grant",
error_description = patchDenial,
access_token = (string?)null,
key = (string?)null,
refresh_token = (string?)null
});
}
var refreshToken = AuthStuff.GenRefreshToken(accountId);
PlayerDB.StoreLoginDeviceInfo(accountId, deviceClass, (int?)ver);
if (grantType != "refresh_token")
PreWarmDorm(accountId);
_ = SendTokenLoginWebhook(accountId, grantType, requestData);
if (ver.HasValue && ver.Value > 20230616)
_ = SendModernVersionWebhook(accountId, ver.Value);
string xrnsigKey = RNSIGHandler.IssuePlayerKey(accountId);
return Ok(new
{
access_token = AuthStuff.Encode(accountId, xrnsigKey, ver?.ToString()),
error = "",
error_description = "",
refresh_token = refreshToken,
scope = TokenScope,
key = xrnsigKey
});
}
[HttpPost("connect/token")]
public async Task<IActionResult> ConnectToken([FromForm] string grant_type, [FromForm] long? account_id, [FromForm] string? client_id, [FromForm] string? client_secret, [FromForm] string? username, [FromForm] string? password, [FromForm] Platforms? platform, [FromForm] string? platform_id, [FromForm] string? device_id, [FromForm] DeviceClasses? device_class, [FromForm] DateTime? time, [FromForm] string? ver, [FromForm] string? build_key, [FromForm] string? asid, [FromForm] string? eac_challenge, [FromForm] string? eac_response, [FromForm] string? platform_auth, [FromForm] string? refresh_token)
{
double validVer;
if (!double.TryParse(ver, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out validVer))
{
validVer = 20250724;
}
var requestData = new
{
grant_type,
account_id,
client_id,
platform = platform?.ToString(),
platform_id,
device_id,
device_class = device_class?.ToString(),
time,
ver = validVer,
build_key,
asid,
eac_challenge,
eac_response
};
if (time == null || build_key == null)
{
SendE12354Webhook(JsonSerializer.Serialize(requestData), HttpContext.Connection.RemoteIpAddress?.ToString(), ImageController.GetHeadersPretty(Request));
return StatusCode(500);
}
// DevsOnlyToken uh this meaning uh only people that is devvings role can play :P
if (ServerConfig.DevsOnlyToken)
{
if (account_id == null || !(PlayerDB.Players.FindById(account_id.Value)?.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false))
return Ok(new { error = "invalid_grant", error_description = "", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
// dont let skids on or something blah blah
// READ THIS !!!!! BITCH
// after talking to "No Turning Back" (also known as: e12354, larpboi69, or sonimcrine) if we turn on pc source gets leaked (no we aint going open source)
// if (platform == Platforms.Steam && ServerConfig.LetPCPlay == false) // if (platform != Platforms.Oculus && ServerConfig.LetPCPlay == false)
if (platform != Platforms.Oculus) // if (platform != Platforms.Oculus && ServerConfig.LetPCPlay == false)
{
if (account_id == null || !(PlayerDB.Players.FindById(account_id.Value)?.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false))
{
if (!(ServerConfig.PCMaximumIdForPlay > 0 && account_id.HasValue && account_id.Value > ServerConfig.PCMaximumIdForPlay))
goto now_login;
return Ok(new { error = "invalid_grant", error_description = "", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
}
// end of checks thgbfs
now_login:
switch (grant_type)
{
case "cached_login":
{
if (platform == null || string.IsNullOrWhiteSpace(platform_id))
{
return BadRequest(new { error = "missing platform info" });
}
if (platform == Platforms.Steam)
{
if (await PlayerDB.ValidateSteamUserTicketAsync(platform_auth, ulong.Parse(platform_id)) == false)
{
_ = SendSteamBlockedWebhook(platform.Value, platform_id);
return Ok(new { error = "invalid_grant", error_description = "platform verification failed", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
}
if (platform == Platforms.Oculus)
{
if (await PlayerDB.ValidateOculusUserNonceAsync(platform_auth, platform_id) == false)
{
_ = SendOculusBlockedWebhook(platform.Value, platform_id);
return Ok(new { error = "invalid_grant", error_description = "platform verification failed", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
}
PlayerDB.GetLogins(platform.Value, platform_id, out var cachedLogins);
var platformAccountIds = cachedLogins.Select(c => c.accountId).ToHashSet();
if (platformAccountIds.Count == 0)
{
if (!ServerConfig.AutoAccount)
{
return Ok(new { error = "invalid_grant", error_description = "an error occured", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
var newPlayer = PlayerDB.CreateAccount(platform.Value, platform_id, false);
/*var deny0 = await Check2025ShitAsync(newPlayer.PlayerId, ver);
if (deny0 != null) return deny0;*/
return TokenResponse(newPlayer.PlayerId, grant_type, requestData, device_class, validVer);
}
var platformAccounts = PlayerDB.Players.FindAll()
.Where(p => platformAccountIds.Contains(p.PlayerId))
.ToList();
if (platformAccounts.Count == 0)
{
return Ok(new { error = "invalid_grant", error_description = "an error occured", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
FullPlayer targetPlayer;
if (account_id != null)
{
targetPlayer = platformAccounts.FirstOrDefault(p => p.PlayerId == account_id.Value);
if (targetPlayer == null)
{
Console.WriteLine($"[AUTH] SPOOF ATTEMPT: account_id={account_id} not owned by platform={platform} platform_id={platform_id}");
_ = SendSpoofAttemptWebhook(account_id.Value, platform.Value, platform_id);
return Ok(new { error = "invalid_grant", error_description = "an error occured", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
}
else
{
targetPlayer = platformAccounts.OrderByDescending(p => p.Player.LastLoginAt).First();
}
Console.WriteLine($"[AUTH] cached_login accepted for account_id={targetPlayer.PlayerId}");
/*var deny1 = await Check2025ShitAsync(targetPlayer.PlayerId, ver);
if (deny1 != null) return deny1;*/
return TokenResponse(targetPlayer.PlayerId, grant_type, requestData, device_class, validVer);
}
case "create_account":
{
if (!ServerConfig.CA_Auth)
return Ok("hello goodbye stop pleasseeeee im tired of skids");
if (platform == null || string.IsNullOrWhiteSpace(platform_id))
return Ok(new { error = "invalid_grant", error_description = "invalid platform", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
if (platform == Platforms.Oculus)
{
if (await PlayerDB.ValidateOculusUserNonceAsync(platform_auth, platform_id) == false)
{
Console.WriteLine($"[AUTH] create_account rejected: oculus nonce not valid for platformId={platform_id}");
_ = SendOculusBlockedWebhook(platform.Value, platform_id);
return Ok(new { error = "invalid_grant", error_description = "platform verification failed", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
}
PlayerDB.GetLogins(platform.Value, platform_id, out var existingLogins);
var existingAccountIds = existingLogins.Select(c => c.accountId).ToList();
var existingAccount = PlayerDB.Players.FindAll()
.FirstOrDefault(p => existingAccountIds.Contains(p.PlayerId));
if (existingAccount != null)
{
/*var deny2 = await Check2025ShitAsync(existingAccount.PlayerId, ver);
if (deny2 != null) return deny2;*/
return TokenResponse(existingAccount.PlayerId, grant_type, requestData, device_class, validVer);
}
if (platform == Platforms.Steam)
{
if (await PlayerDB.ValidateSteamUserTicketAsync(platform_auth, ulong.Parse(platform_id)) == false)
{
Console.WriteLine($"[AUTH] AutoAcc player ticket not valid for Rec Room or Spacewar platform={platform} platformId={platform_id}");
_ = SendSteamBlockedWebhook(platform.Value, platform_id);
return Ok(new { error = "invalid_grant", error_description = "invalid platform", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
}
}
var newPlayer = PlayerDB.CreateAccount(platform.Value, platform_id, false);
_ = SendCreatedAccountWebhook(newPlayer.PlayerId, newPlayer.Player.CreatedAt, platform_id, platform.Value);
/*var deny3 = await Check2025ShitAsync(newPlayer.PlayerId, ver);
if (deny3 != null) return deny3;*/
return TokenResponse(newPlayer.PlayerId, grant_type, requestData, device_class, validVer);
}
case "refresh_token":
{
if (string.IsNullOrWhiteSpace(refresh_token))
return BadRequest(new { error = "invalid_request", error_description = "refresh_token is required." });
var player = PlayerDB.Players.FindOne(p => p.RefreshToken == refresh_token);
if (player == null || player.RefreshTokenExpires < DateTime.UtcNow)
{
Console.WriteLine($"[AUTH] refresh_token REJECTED for token='{refresh_token}'");
return Unauthorized(new { error = "invalid_grant", error_description = "Refresh token is invalid or expired" });
}
player.RefreshToken = null;
player.RefreshTokenExpires = DateTime.MinValue;
PlayerDB.Players.Update(player);
Console.WriteLine($"[AUTH] refresh_token ACCEPTED for playerId={player.PlayerId}");
return TokenResponse(player.PlayerId, grant_type, requestData);
}
case "password":
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
return Ok(new { error = "invalid_grant", error_description = "invalid_username_or_password", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
var player = PlayerDB.Players.FindAll()
.FirstOrDefault(p => string.Equals(p.Player?.Username, username, StringComparison.OrdinalIgnoreCase));
if (player == null || string.IsNullOrEmpty(player.Password))
return Ok(new { error = "invalid_grant", error_description = "invalid_username_or_password", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
if (!PlayerDB.PasswordManager.VerifyPassword(password, player.Password))
return Ok(new { error = "invalid_grant", error_description = "invalid_username_or_password", access_token = (string?)null, key = (string?)null, refresh_token = (string?)null });
if (platform != null && !string.IsNullOrWhiteSpace(platform_id))
{
player.PlatformIds ??= new List<mPlatformID>();
bool alreadyLinked = player.PlatformIds.Any(p =>
p.Platform == platform.Value &&
string.Equals(p.PlatformId, platform_id, StringComparison.OrdinalIgnoreCase));
if (!alreadyLinked)
{
player.PlatformIds.Add(new mPlatformID { Platform = platform.Value, PlatformId = platform_id });
PlayerDB.Players.Update(player);
Console.WriteLine($"[AUTH] password: linked platform={platform} platformId={platform_id} to playerId={player.PlayerId}");
}
}
Console.WriteLine($"[AUTH] password grant accepted for playerId={player.PlayerId}");
/*var denyPw = await Check2025ShitAsync(player.PlayerId, ver);
if (denyPw != null) return denyPw;*/
return TokenResponse(player.PlayerId, grant_type, requestData, device_class, validVer);
}
default:
Console.WriteLine($"[AUTH] unsupported grant_type='{grant_type}'");
return BadRequest(new { error = "unsupported_grant_type" });
}
}
}
}
+436
View File
@@ -0,0 +1,436 @@
using Microsoft.AspNetCore.Mvc;
using Vanadium.Auth;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using static Vanadium.Classes.DBs.DBClasses.InventionDBClasses;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
namespace Vanadium.Controllers
{
[ApiController]
public class InventionController : ControllerBase
{
[HttpGet("api/inventions/v1/room")]
public IActionResult RoomsInventions([FromQuery] long? id)
=> Ok(ServerConfig.Bracket);
[HttpGet("/api/inventions/v1/tagfilters")]
public IActionResult inventiontagfilters()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
string path = Path.Join(Program.dataDir, "APIS", "jsons", "InventionTagFilters.json");
if (System.IO.File.Exists(path))
{
return Content(System.IO.File.ReadAllText(path), "application/json");
}
else
{
var json = new
{
PinnedFilters = new[] { "art", "gadget", "sound", "dormskin", "rro" },
PopularFilters = new[] { "art", "gadget", "sound", "dormskin" },
TrendingFilters = (string[]?)null
};
return Ok(json);
}
}
[HttpGet("/api/inventions/v2/mine")]
public IActionResult Mine()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
return Ok(InventionDB.GetInventionsByPlayer((long)id));
}
[HttpPost("/api/inventions/v7/save")]
[HttpPost("/api/inventions/v6/save")]
public IActionResult SaveInventionV6([FromBody] SaveInventionRequest request)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
if (request == null || string.IsNullOrWhiteSpace(request.name)) return BadRequest();
var result = InventionDB.SaveInvention((long)id, request);
if (result == null) return BadRequest();
var (invention, version) = result.Value;
return Ok(new { Status = 0, Invention = InventionDB.MapInventionFull(invention), InventionVersion = InventionDB.MapVersion(version) });
}
[HttpPost("/api/inventions/v9/save")]
public IActionResult SaveInventionV9([FromBody] SaveInventionRequest request)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
if (request == null || string.IsNullOrWhiteSpace(request.name)) return BadRequest();
var result = InventionDB.SaveInvention((long)id, request);
if (result == null) return BadRequest();
var (invention, version) = result.Value;
return Ok(new
{
value = new
{
Status = 0,
Invention = InventionDB.MapInventionFull(invention),
InventionVersion = InventionDB.MapVersion(version),
TagsResponse = new { Result = 0, Tags = new List<object>() }
},
success = true
});
}
[HttpGet("/api/inventions/v{ver}/publish")]
public IActionResult PublishInventionScary([FromQuery] long inventionId, [FromQuery] int permissionLevel, [FromQuery] int accessibility, [FromQuery] int price)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var result = InventionDB.PublishInvention(inventionId, (long)id.Value, permissionLevel, accessibility, price);
if (result == null) return BadRequest();
return Ok(InventionDB.MapInventionFull(result));
}
[HttpGet("/api/inventions/v1/unpublish")]
public IActionResult UnpublishInvention([FromQuery] long inventionId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var result = InventionDB.UnpublishInvention(inventionId, (long)id.Value);
if (result == null) return BadRequest();
return Ok(InventionDB.MapInventionFull(result));
}
[HttpPatch("/api/inventions/v4/addversion")]
public IActionResult AddVersion([FromBody] AddVersionRequest request)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
if (request == null) return Ok(new { Status = -1 });
var invention = InventionDB.GetInvention(request.inventionId);
if (invention == null || invention.CreatorPlayerId != (long)id) return Ok(new { Status = -1 });
if (string.IsNullOrEmpty(request.inventionDataFilename)) return Ok(new { Status = -1 });
var result = InventionDB.AddVersion(invention, request);
if (result == null) return Ok(new { Status = -1 });
var (inv, version) = result.Value;
return Ok(new { Status = 0, Invention = InventionDB.MapInventionToData(inv), InventionVersion = InventionDB.MapVersion(version) });
}
[HttpGet("/api/inventions/v1/versions")]
public IActionResult GetVersions([FromQuery] long inventionId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
if (InventionDB.GetInvention(inventionId) == null) return NotFound("");
return Ok(InventionDB.GetVersionsForInvention(inventionId));
}
[HttpGet("/api/inventions/v2/batch")]
public IActionResult GetInventionsBatchGet([FromQuery(Name = "id")] List<long>? queryIds)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var ids = queryIds ?? new List<long>();
if (ids.Count == 0) return Ok(new List<object>());
return Ok(InventionDB.GetInventionsBatch(ids).Select(inv => InventionDB.MapInventionFull(inv)).ToList());
}
[HttpPost("/api/inventions/v2/batch")]
public IActionResult GetInventionsBatchPost([FromBody] InventionBatchRequest? body, [FromQuery(Name = "id")] List<long>? queryIds)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var ids = (body != null && body.InventionIds.Count > 0) ? body.InventionIds
: (queryIds != null && queryIds.Count > 0) ? queryIds
: new List<long>();
if (ids.Count == 0) return Ok(new List<object>());
return Ok(InventionDB.GetInventionsBatch(ids).Select(inv => InventionDB.MapInventionFull(inv)).ToList());
}
[HttpGet("/api/inventions/v1")]
[HttpGet("/api/inventions/v2/{inventionId:long}")]
public IActionResult GetInvention([FromQuery] long inventionId, [FromRoute] long? inventionId2)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var inv = InventionDB.GetInvention(inventionId > 0 ? inventionId : inventionId2 ?? 0);
if (inv == null) return NotFound("");
return Ok(InventionDB.MapInventionFull(inv));
}
[HttpGet("/api/inventions/v2/{inventionId:long}/versions")]
public IActionResult GetVersionsV2([FromRoute] long inventionId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
if (InventionDB.GetInvention(inventionId) == null) return NotFound("");
return Ok(InventionDB.GetVersionsForInvention(inventionId));
}
[HttpGet("/api/inventions/v2/{inventionId:long}/permission/me")]
public IActionResult GetNetPermissionForMe([FromRoute] long inventionId)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null) return Unauthorized("");
var invention = InventionDB.GetInvention(inventionId);
if (invention == null) return NotFound("");
bool isOwner = invention.CreatorPlayerId == playerId || playerId == 2;
return Ok(new { Permission = isOwner ? InventionPermissions.Unlimited : InventionPermissions.Unassigned, IsOwner = isOwner });
}
[HttpGet("/api/inventions/v2/creator/{accountId:long}")]
public IActionResult GetInventionsByCreator([FromRoute] long accountId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
return Ok(InventionDB.GetInventionsByCreator(accountId));
}
[HttpGet("/api/inventions/v2/room/{roomId:long}")]
public IActionResult GetRoomInventions([FromRoute] long roomId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
return Ok(InventionDB.GetInventionsByRoom(roomId));
}
[HttpGet("/api/inventions/v1/details")]
public IActionResult GetInventionDetails([FromQuery] long inventionId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var invention = InventionDB.GetInvention(inventionId);
if (invention == null) return NotFound("");
return Ok(new DetailsResponse
{
Tags = (invention.Tags ?? new List<InventionTag>())
.Select(t => new TagData { Tag = t.Tag, Type = t.Type }).ToList()
});
}
[HttpGet("/api/inventions/v1/fulllineageowner")]
public IActionResult GetFullLineageOwner([FromQuery] long id)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null) return Unauthorized("");
bool isDeveloper = PlayerDB.Players.FindById((long)playerId)?.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false;
var invention = InventionDB.GetInvention(id);
if (invention == null)
{
if (isDeveloper) return Ok(true);
return Ok(new { success = false, error_id = "Inventions.FulllineageDoesNotExist", error = "*Invention Shit*\n This invention does not exist!" });
}
return Ok(invention.CreatorPlayerId == playerId || playerId == 2);
}
[HttpGet("/api/inventions/v1/version")]
public IActionResult GetInventionVersion([FromQuery] long inventionId, [FromQuery] int version)
{
var invention = InventionDB.GetInvention(inventionId);
if (invention == null) return NotFound("");
var invVersion = InventionDB.GetVersion(inventionId, version)
?? invention.CurrentVersion
?? InventionDB.GetLatestVersion(inventionId);
if (invVersion == null) return NotFound("");
return Ok(new
{
InventionAccessibility = (int)invention.Accessibility,
InventionPrice = invention.Price,
InventionGeneralPermission = invention.GeneralPermission,
InventionCreatorPermission = invention.CreatorPermission,
invVersion.BlobName,
BlobHash = invVersion.BlobHash ?? string.Empty,
HasBetaContent = false,
HasDevContent = false,
HasImageFileReference = !string.IsNullOrWhiteSpace(invention.ImageName),
UgcAccessibility = 0,
ReferencedUnityAssetIds = Array.Empty<object>(),
ReferencedInventions = invention.ReferencedInventions ?? new List<long>(),
invVersion.InventionId,
invVersion.ReplicationId,
invVersion.VersionNumber,
invVersion.InstantiationCost,
invVersion.LightsCost,
invVersion.ChipsCost,
invVersion.CloudVariablesCost,
invVersion.AICost,
invVersion.inkCost,
CreatedAt = invVersion.CreatedAt.ToString("O"),
invention.IsPublished,
invention.AllowTrial,
invention.Name,
invention.Description,
invention.ImageName,
Tags = invention.Tags ?? new List<InventionTag>()
});
}
[HttpGet("/api/inventions/v1/featured")]
public async Task<IActionResult> FeaturedInventions()
{
return Ok(InventionDB.GetTopTodayInventions(true));
}
[HttpGet("/api/inventions/v1/toptoday")]
public async Task<IActionResult> TopTodayInventions()
{
return Ok(InventionDB.GetTopTodayInventions(false));
}
[HttpGet("/api/inventions/v1/update")]
public IActionResult UpdateInvention(
[FromQuery] long inventionId,
[FromQuery] string? name,
[FromQuery] string? description,
[FromQuery] string? imageName,
[FromQuery] int? price,
[FromQuery] bool? isPublished,
[FromQuery] bool? allowTrial,
[FromQuery] bool? hideFromPlayer,
[FromQuery] int? generalPermission,
[FromQuery] int? creatorPermission)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null) return Unauthorized("");
var invention = InventionDB.UpdateInvention(inventionId, (long)playerId,
name, description, imageName, price, isPublished, allowTrial,
hideFromPlayer, generalPermission, creatorPermission);
if (invention == null) return NotFound("");
return Ok(InventionDB.MapInventionToData(invention));
}
[HttpPost("/api/inventions/v1/report")]
public async Task<IActionResult> ReportaInvention([FromBody] ReportInvention request)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null) return Unauthorized("");
if (request == null) return BadRequest();
var invention = InventionDB.GetInvention(request.InventionId);
var player = PlayerDB.Players.FindById((long)playerId);
var username = player?.Player?.Username ?? "Unknown";
var categoryName = Enum.IsDefined(typeof(InventionReportCategory), request.ReportCategory)
? Enum.GetName(typeof(InventionReportCategory), request.ReportCategory)!
: request.ReportCategory.ToString();
var inventionName = invention?.Name ?? $"Unknown ({request.InventionId})";
var imageUrl = !string.IsNullOrWhiteSpace(invention?.ImageName)
? $"https://reloxa.xyz/imageserver/{invention.ImageName}"
: null;
var lines = new List<string>
{
"# **🔵 New Invention Report!**",
$"Reported Invention: {inventionName} `{request.InventionId}`"
};
if (!string.IsNullOrWhiteSpace(request.Details))
lines.Add($"Reason: {request.Details}");
lines.Add($"Invention Enum Category: \"{categoryName}\" `{(int)request.ReportCategory}`");
lines.Add($"Coming From: @{username} `{playerId}`");
if (imageUrl != null)
lines.Add(imageUrl);
var payload = new { content = string.Join("\n", lines) };
var json = System.Text.Json.JsonSerializer.Serialize(payload);
using var http = new HttpClient();
await http.PostAsync(ServerConfig.InventionsReportWebhook,
new StringContent(json, System.Text.Encoding.UTF8, "application/json"));
return Ok(new { success = true });
}
[HttpGet("/api/inventions/v1/delete")]
public IActionResult DeleteInvention([FromQuery] long inventionId)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null) return Unauthorized("");
bool deleted = InventionDB.DeleteInvention(inventionId, (long)playerId);
return Ok(new { success = deleted });
}
[HttpPost("/api/inventions/v1/settags")]
public IActionResult SetInventionTags([FromBody] SetTagsRequest request)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
if (request == null || request.InventionId <= 0) return BadRequest("Invalid request");
var response = InventionDB.SetTags(request.InventionId, request);
if (response.Result != 0) return BadRequest("Failed to set tags");
return Ok(response);
}
[HttpGet("/api/inventions/v1/personaldetails/{inventionId}")]
public IActionResult GetInventionPersonalDetails([FromRoute] long inventionId)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null) return Unauthorized("");
var player = PlayerDB.Players.FindById((long)playerId);
bool isCheering = player?.Player?.PlayerExtra?.PersonalDetails?.inventionsCheered?.Contains(inventionId) ?? false;
return Ok(new PersonalDetailsResponse { IsCheering = isCheering });
}
[HttpPost("/api/inventions/v{ver}/cheer")]
public IActionResult CheerInvention([FromBody] CheerInventionRequest request)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null) return Unauthorized("");
var result = InventionDB.CheerInvention(request.InventionId, (long)playerId, request.Cheer);
if (result == null) return NotFound("");
return Ok(new { success = true, error = (object?)null, error_id = (object?)null, value = InventionDB.GetInvention(request.InventionId) });
}
[HttpGet("/api/inventions/v1/fromcreators")]
public IActionResult FromCreator()
=> Ok(ServerConfig.Bracket);
[HttpGet("/api/inventions/v2/search")]
public async Task<IActionResult> SearchInventions(
[FromQuery] string query = "",
[FromQuery(Name = "value")] string value = "",
[FromQuery] int skip = 0,
[FromQuery] int take = 999)
{
var searchText = string.IsNullOrWhiteSpace(query) ? value : query;
if (string.IsNullOrWhiteSpace(searchText))
return Ok(InventionDB.GetTopTodayInventions(false));
var searchResult = InventionDB.Search(searchText, skip: skip, take: take);
return Ok(new
{
Results = searchResult.Results.Select(inv => InventionDB.MapInventionFull(inv)).ToList(),
Total = searchResult.Total
});
}
}
}
+2049
View File
@@ -0,0 +1,2049 @@
using Microsoft.AspNetCore.Mvc;
using Vanadium.Auth;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
using static Vanadium.Classes.DBs.DBClasses.RoomDBClasses;
using Vanadium.Classes.WebSocket;
using Vanadium.Utils.NotiController;
using Vanadium.Controllers;
using System.Collections.Concurrent;
namespace Vanadium.Controllers
{
[ApiController]
[Route("/match")]
public partial class MatchController : ControllerBase
{
private static readonly ConcurrentDictionary<long, (long RoomId, DateTime MatchmadeAt)> _authorizedRoomSessions = new();
private static readonly ConcurrentDictionary<(long, long), (int Count, DateTime Window)> _inviteRateLimit = new();
private static readonly string PhotonChatAppId = "9c035c47-8725-43d4-8c53-ab45ed381511";
private static readonly Random _photonRng = new Random();
public static long EnsurePlayerDorm(FullPlayer player)
{
long dormRoomId = player.Player.PlayerExtra.DormRoomId;
if (dormRoomId > 0 && RoomDB.DoesPlayerDormExist(player.PlayerId))
return dormRoomId;
var dormRoom = RoomDB.GetPlayerDormRoom(player.PlayerId);
if (dormRoom != null)
{
dormRoomId = dormRoom.RoomId;
}
else
{
dormRoomId = RoomDB.CloneDormRoom(player.PlayerId);
}
PlayerDB.UpdateDormRoomId(player.PlayerId, dormRoomId);
player.Player.PlayerExtra.DormRoomId = dormRoomId;
return dormRoomId;
}
private static void AuthorizeRoomSession(long playerId, long roomId)
{
_authorizedRoomSessions[playerId] = (roomId, DateTime.UtcNow);
}
private static void ClearRoomSession(long playerId)
{
_authorizedRoomSessions.TryRemove(playerId, out _);
}
private static bool IsRoomSessionAuthorized(long playerId, long roomId)
{
if (!_authorizedRoomSessions.TryGetValue(playerId, out var session))
return false;
return session.RoomId == roomId && (DateTime.UtcNow - session.MatchmadeAt).TotalHours < 12;
}
private static void KickPlayer(long playerId)
{
}
private static bool IsBannedFromRoom(Room? room, long playerId)
{
return room?.Roles?.Any(r => r.AccountId == playerId && r.Role == Role.Banned) ?? false;
}
[HttpGet("player")]
public async Task<IActionResult> GetPlayerHeartbeatBulk([FromQuery] List<long> id)
{
var playerId = AuthStuff.GetPlayerId(Request);
if (playerId == null)
return Unauthorized("");
return Ok(PlayerDB.GetPlayerHeartbeatsBulk(id));
}
[HttpPost("roominstance/{roomInstanceId}/reportjoinresult")]
public async Task<IActionResult> ReportJoinResultForRoomInstanceId(ulong roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
long resolvedAccountId = player.PlayerId;
long newLevel = player.Player.Level;
long newXP = player.Player.XP;
_ = NotiController.SendEvent(resolvedAccountId, "PlayerProgressionXPUpdate", new
{
PlayerId = resolvedAccountId,
Level = newLevel,
XP = newXP
});
_ = NotiController.SendEvent(resolvedAccountId, "PlayerProgressionLevelUpdate", new
{
PlayerId = resolvedAccountId,
Level = newLevel,
XP = newXP
});
return Ok();
}
[HttpPut("roominstance/{roomInstanceId}/markprivate")]
public async Task<IActionResult> MarkRoomInstancePrivate(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
Sessions.MarkInstancePrivate(roomInstanceId);
Sessions.FakeInstanceFull(roomInstanceId);
var playersInInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.ToList();
foreach (var fp in playersInInstance)
{
if (fp.Player?.PlayerExtra?.Heartbeat?.roomInstance == null) continue;
fp.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate = true;
PlayerDB.Players.Update(fp);
}
var outsiderIds = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.isOnline == true
&& p.Player.PlayerExtra.Heartbeat.roomInstance?.roomInstanceId != roomInstanceId)
.Select(p => p.PlayerId)
.ToList();
foreach (var fp in playersInInstance)
{
if (fp.Player?.PlayerExtra?.Heartbeat == null) continue;
var hbJson = Newtonsoft.Json.JsonConvert.SerializeObject(new WebsocketEvents.Response
{
Id = NotificationsController.EventTypes.PresenceUpdate,
Msg = fp.Player.PlayerExtra.Heartbeat
});
await NotificationsController.SendToPlayer(fp.PlayerId, hbJson);
await NotificationsController.SendToPlayers(outsiderIds, hbJson);
}
return Ok(new StorefrontsDBClasses.SuccessResult());
}
private static HashSet<long> RecRoomPlusCheckWhitelisgt()
{
var path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "RRPlus.txt");
if (!System.IO.File.Exists(path))
return new HashSet<long>();
var ids = new HashSet<long>();
foreach (var line in System.IO.File.ReadAllLines(path))
{
if (long.TryParse(line.Trim(), out var id))
ids.Add(id);
}
return ids;
}
[HttpGet("rooms/requiring/developer")]
public async Task<IActionResult> RoomsRequiringDeveloper()
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
return Ok(ServerConfig.Bracket);
}
[HttpGet("rooms/requiring/rrplus")]
public async Task<IActionResult> RoomsRequiringRRPlus()
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
return Ok(ServerConfig.Bracket);
}
[HttpPut("player/statusvisibility")]
public IActionResult setStatusVisibilityShit()
{
return Ok(new { success = true });
}
[HttpPut("player/photonregionpings")]
public IActionResult setPhotonRegionShit()
{
return Ok(new { success = true });
}
[HttpGet("room/{roomId}/instances")] // ripped from other code only for reference? i only needed the response data
public async Task<IActionResult> GetRoomInstances(long roomId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var room = RoomDB.GetRoom(roomId);
if (room == null) return NotFound("");
var currentPlayer = PlayerDB.GetCurrentPlayer((long)id);
bool isDeveloper = currentPlayer?.PlayerRoles?.Any(r =>
r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool hasRoomModRole = room.CreatorAccountId == (long)id || (room.Roles?.Any(r =>
r.AccountId == (long)id &&
(int)r.Role >= (int)Role.Moderator) ?? false);
if (!isDeveloper && !hasRoomModRole)
return StatusCode(403, "Permission Denied");
var allPlayers = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance != null
&& p.Player.PlayerExtra.Heartbeat.isOnline)
.ToList();
var instanceGroups = allPlayers
.Where(p => p.Player.PlayerExtra.Heartbeat.roomInstance.roomId == roomId)
.GroupBy(p => p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId)
.ToList();
if (!instanceGroups.Any())
return Ok(Array.Empty<object>());
bool canViewAll = isDeveloper || room.CreatorAccountId == (long)id || (room.Roles?.Any(r =>
r.AccountId == (long)id &&
(int)r.Role >= (int)Role.Moderator) ?? false);
var result = instanceGroups
.Where(g =>
{
var sample = g.First();
var instance = sample.Player.PlayerExtra.Heartbeat.roomInstance;
return canViewAll || instance.isPrivate != true;
})
.OrderByDescending(g => g.Count())
.Select(g =>
{
var sample = g.First().Player.PlayerExtra.Heartbeat.roomInstance;
return new Dictionary<string, object>
{
["RoomInstanceId"] = sample.roomInstanceId,
["RoomId"] = roomId,
["SubRoomId"] = sample.subRoomId,
["IsFull"] = Sessions.IsInstanceActuallyFull(sample.roomInstanceId),
["MaxCapacity"] = sample.maxCapacity,
["IsPrivate"] = sample.isPrivate,
["PlayerIds"] = g.Select(p => p.PlayerId).ToList()
};
})
.ToList();
return Ok(result);
}
[HttpPost("player/exclusivelogin")]
public async Task<IActionResult> PlayerExclusiveLogin()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
await NotificationsController.SendPlayerOnline(id.Value, new MatchDBClasses.PlayerOnlineDTO() { SenderIsMyFavoriteFriend = true });
return Ok();
}
[HttpPost("player/login")]
public async Task<IActionResult> PlayerLogin()
{
var id = AuthStuff.GetPlayerId(Request);
if (id != null)
{
ClearRoomSession(id.Value);
PlayerDB.UpdateLastLoginAt(id.Value);
PlayerDB.UpdatePlayerHeartbeat(id.Value, null, online: true);
var someRequiredSetings = new[]
{
("Recroom.AccountCreation.HasStarted", "True"),
("Recroom.AccountCreation.HasChosenUsername", "False"),
("Recroom.AccountCreation.HasCreatedPassword", "True"),
("Recroom.AccountCreation.HasFinished", "False"),
("TUTORIAL_COMPLETE_MASK", "77")
};
var existingSettings = PlayerDB.GetPlayerSettings(id.Value);
foreach (var (key, value) in someRequiredSetings)
{
if (!existingSettings.Any(s => s.Key == key))
PlayerDB.SetPlayerSetting(key, value, id.Value);
}
}
return Ok();
}
[HttpPost("player/logout")]
public async Task<IActionResult> PlayerLogout()
{
var id = AuthStuff.GetPlayerId(Request);
if (id != null)
{
ClearRoomSession(id.Value);
PlayerDB.UpdatePlayerHeartbeat(id.Value, null, online: false);
}
return Ok();
}
[HttpPost("player/heartbeat")]
public async Task<IActionResult> PostPlayerHeartbeat()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
var playerLock = PlayerDB.GetPlayerLock(id.Value);
await playerLock.WaitAsync();
FullPlayer player;
Heartbeat hb;
try
{
player = PlayerDB.Players.FindById(id.Value);
if (player?.Player?.PlayerExtra == null)
return Unauthorized("");
hb = player.Player.PlayerExtra.Heartbeat ?? new Heartbeat();
if (hb.roomInstance != null)
{
long claimedRoomId = hb.roomInstance.roomId;
if (!IsRoomSessionAuthorized(id.Value, claimedRoomId))
{
}
}
hb.playerId = id.Value;
hb.isOnline = true;
hb.errorCode = 0;
hb.photonAuthToken = null;
hb.appVersion = player.Player.PlayerExtra.Heartbeat?.appVersion ?? hb.appVersion;
if (Request.HasFormContentType)
{
var form = await Request.ReadFormAsync();
if (form.TryGetValue("DeviceClass", out var dc) && int.TryParse(dc, out var dcVal))
hb.deviceClass = (DeviceClasses)dcVal;
if (form.TryGetValue("StatusVisibility", out var sv) && int.TryParse(sv, out var svVal))
hb.statusVisibility = (StatusVisibility)svVal;
if (form.TryGetValue("VRMovementMode", out var vrm) && int.TryParse(vrm, out var vrmVal))
hb.vrMovementMode = vrmVal;
}
player.Player.PlayerExtra.Heartbeat = hb;
PlayerDB.Players.Update(player);
await Services.ToxModService.EnsureCurrent(player);
}
finally
{
playerLock.Release();
}
/*var (xpAwarded, leveledUp, newLevel, newXP) = PlayerDB.AwardHeartbeatXP(id.Value);
if (xpAwarded)
{
var progressionPayload = Newtonsoft.Json.JsonConvert.SerializeObject(
WebsocketEvents.CreateLevelUpdateResponse(id.Value, newLevel, newXP));
await NotificationsController.SendToPlayer(id.Value, progressionPayload);
}*/
return Ok(hb);
}
[HttpPost("matchmake/none")]
public async Task<IActionResult> MatchmakeNone()
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
ClearRoomSession(id.Value);
var player = PlayerDB.GetCurrentPlayer(id.Value);
if (player != null)
EnsurePlayerDorm(player);
var heartbeat = PlayerDB.GetPlayerHeartbeat(id.Value);
if (heartbeat != null)
await NotificationsController.RefreshHeartbeat(id.Value, heartbeat);
return Ok(heartbeat);
}
[HttpPost("goto/room/{roomName}")]
public async Task<IActionResult> MatchmakeByRoomName(string roomName)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
if (roomName.Equals("DormRoom", StringComparison.OrdinalIgnoreCase))
{
long dormRoomId = EnsurePlayerDorm(player);
AuthorizeRoomSession(player.PlayerId, dormRoomId);
RoomDB.IncrementRoomVisitCount(dormRoomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, dormRoomId);
var dormHeartbeat = Sessions.CreateDorm(player.PlayerId, player.Player.Username, dormRoomId);
if (PlayerDB.IsBanned(player.PlayerId))
dormHeartbeat.roomInstance.maxCapacity = 1;
await NotificationsController.RefreshHeartbeat(player.PlayerId, dormHeartbeat);
return Ok(dormHeartbeat);
}
var room = RoomDB.GetRoomByName(roomName);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
long roomId = room.RoomId;
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
var (joinMode, autoFollow, _, additionalPlayerIds) = await ParseMatchmakeForm(Request);
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
null,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/dorm")]
public async Task<IActionResult> MatchmakeDorm()
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
long dormRoomId = EnsurePlayerDorm(player);
AuthorizeRoomSession(player.PlayerId, dormRoomId);
RoomDB.IncrementRoomVisitCount(dormRoomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, dormRoomId);
var heartbeat = Sessions.CreateDorm(player.PlayerId, player.Player.Username, dormRoomId);
if (PlayerDB.IsBanned(player.PlayerId))
heartbeat.roomInstance.maxCapacity = 1;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/event/{eventId}")]
public async Task<IActionResult> MatchmakeEvent(long eventId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var ev = Vanadium.Classes.DBs.EventDB.GetPlayerEventById(eventId);
if (ev == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchGame,
isOnline = true,
playerId = player.PlayerId
});
long roomId = ev.RoomId;
long? subRoomId = ev.SubRoomId;
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
var (joinMode, autoFollow, _, additionalPlayerIds) = await ParseMatchmakeForm(Request);
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
if (heartbeat?.roomInstance != null)
heartbeat.roomInstance.eventId = eventId;
var fp = PlayerDB.Players.FindById(player.PlayerId);
if (fp?.Player?.PlayerExtra != null)
{
fp.Player.PlayerExtra.Heartbeat = heartbeat;
PlayerDB.Players.Update(fp);
}
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId ?? 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/club/{clubId}")]
public async Task<IActionResult> MatchmakeToClub(long clubId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
return StatusCode(403);
var targetClub = Classes.DBs.ClubsDB.GetClub(clubId);
if (targetClub == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchClub,
isOnline = true,
playerId = player.PlayerId
});
long roomId = targetClub.ClubhouseRoomId ?? 0;
if (roomId <= 0)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.ClubHasNoClubhouse,
isOnline = true,
playerId = player.PlayerId
});
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.ClubHasNoClubhouse,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromClub,
isOnline = true,
playerId = player.PlayerId
});
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomId == roomId)
.Select(p => p.Player!.PlayerExtra!.Heartbeat!.roomInstance!)
.GroupBy(ri => ri.roomInstanceId)
.Select(g => g.First())
.OrderByDescending(ri => Sessions.CountPlayersInInstance(ri.roomInstanceId))
.FirstOrDefault();
if (targetInstance != null)
{
if (currentInstanceHb?.roomInstance?.roomInstanceId == targetInstance.roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
if (targetInstance.isFull || Sessions.CountPlayersInInstance(targetInstance.roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
if (heartbeat == null)
return StatusCode(500);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var createdHeartbeat = Sessions.CreateRoom(player.PlayerId, roomId, joinMode: 0, additionalPlayersAutoFollow: false, roomNameOverride: targetClub.Name + " Clubhouse");
if (createdHeartbeat == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.ClubHasNoClubhouse,
isOnline = true,
playerId = player.PlayerId
});
createdHeartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, createdHeartbeat);
return Ok(createdHeartbeat);
}
private static async Task ShovePlayersToInstance(List<long> playerIds, long roomId, long subRoomId, Heartbeat leaderHeartbeat)
{
foreach (var pid in playerIds)
{
var fp = PlayerDB.Players.FindById(pid);
if (fp == null) continue;
var hb = Sessions.CreateRoom(pid, roomId, subRoomId, joinMode: 0, additionalPlayersAutoFollow: false);
if (leaderHeartbeat.roomInstance != null && hb.roomInstance != null)
{
hb.roomInstance.photonRoomId = leaderHeartbeat.roomInstance.photonRoomId;
hb.roomInstance.photonRegion = leaderHeartbeat.roomInstance.photonRegion;
hb.roomInstance.roomInstanceId = leaderHeartbeat.roomInstance.roomInstanceId;
}
fp.Player.PlayerExtra ??= new PlayerExtra();
fp.Player.PlayerExtra.Heartbeat = hb;
PlayerDB.Players.Update(fp);
await NotificationsController.RefreshHeartbeat(pid, hb);
}
}
private static async Task<(int joinMode, bool autoFollow, bool bypassMovement, List<long> additionalPlayerIds)> ParseMatchmakeForm(HttpRequest request)
{
int joinMode = 0;
bool autoFollow = false;
bool bypassMovement = false;
var additionalPlayerIds = new List<long>();
if (request.HasFormContentType)
{
var form = await request.ReadFormAsync();
if (form.TryGetValue("JoinMode", out var joinModeStr))
int.TryParse(joinModeStr, out joinMode);
if (form.TryGetValue("AdditionalPlayersAutoFollow", out var autoFollowStr))
autoFollow = autoFollowStr.ToString().Equals("True", StringComparison.OrdinalIgnoreCase);
if (form.TryGetValue("BypassMovementModeRestriction", out var bypassStr))
bypassMovement = bypassStr.ToString().Equals("True", StringComparison.OrdinalIgnoreCase);
var additionalKey = form.Keys.FirstOrDefault(k => k.Equals("AdditionalPlayerIds", StringComparison.OrdinalIgnoreCase));
if (additionalKey != null)
{
foreach (var val in form[additionalKey])
{
if (long.TryParse(val?.Trim(), out var pid))
additionalPlayerIds.Add(pid);
}
}
}
return (joinMode, autoFollow, bypassMovement, additionalPlayerIds);
}
[HttpPost("player/notifydisconnect")]
public async Task<IActionResult> NotifyDisconnect()
{
return Ok();
}
[HttpPost("matchmake/instance/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeInstance(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchGame,
isOnline = true,
playerId = player.PlayerId
});
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
var (_, autoFollowInstance, _, additionalPlayerIdsInstance) = await ParseMatchmakeForm(Request);
if (additionalPlayerIdsInstance.Count > 0 && autoFollowInstance)
await ShovePlayersToInstance(additionalPlayerIdsInstance, targetInstance.roomId, targetInstance.subRoomId, heartbeat);
else if (additionalPlayerIdsInstance.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIdsInstance, targetInstance.roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/instance/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeInstanceV2(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchGame", error = "Instance not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchGame, isOnline = true, playerId = player.PlayerId } });
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new { success = false, error_id = "Matchmaking.InsufficientSpace", error = "Instance is full.", value = new Heartbeat { errorCode = MatchmakingErrorCode.InsufficientSpace, isOnline = true, playerId = player.PlayerId } });
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new { success = false, error_id = "Matchmaking.AlreadyInTargetInstance", error = "Already in this instance.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AlreadyInTargetInstance, isOnline = true, playerId = player.PlayerId } });
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
var (_, autoFollowInstance, _, additionalPlayerIdsInstance) = await ParseMatchmakeForm(Request);
if (additionalPlayerIdsInstance.Count > 0 && autoFollowInstance)
await ShovePlayersToInstance(additionalPlayerIdsInstance, targetInstance.roomId, targetInstance.subRoomId, heartbeat);
else if (additionalPlayerIdsInstance.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIdsInstance, targetInstance.roomId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
[HttpPost("matchmake/room/{roomId}")]
public async Task<IActionResult> MatchmakeRoomRoomId(long roomId, [FromQuery] long? subRoomId = null)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
var bannedHeartbeat = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
};
return Ok(bannedHeartbeat);
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (AuthStuff.GetAndEnsureAppVersion(Request) != "20250724" && room.Tags.Any(x => x.Tag.Equals("2025", StringComparison.OrdinalIgnoreCase)))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.UpdateRequired,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
}
var currentHb = player.Player?.PlayerExtra?.Heartbeat;
if (bypassMovement && joinMode == 0 && currentHb?.roomInstance != null && currentHb.roomInstance.roomId == roomId && !currentHb.roomInstance.isPrivate)
{
long currentInstanceId = currentHb.roomInstance.roomInstanceId;
var otherInstance = PlayerDB.Players
.FindAll()
.Where(p =>
p.PlayerId != player.PlayerId &&
p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomId == roomId &&
!p.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate &&
p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId != currentInstanceId &&
Sessions.CountPlayersInInstance(p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId) < p.Player.PlayerExtra.Heartbeat.roomInstance.maxCapacity)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (otherInstance != null)
{
var hb = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, otherInstance);
hb.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, hb);
return Ok(hb);
}
joinMode = 1;
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId ?? 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/room/{roomId}")]
public async Task<IActionResult> MatchmakeRoomRoomIdV2(long roomId, [FromQuery] long? subRoomId = null)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchRoom", error = "Room not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchRoom, isOnline = true, playerId = player.PlayerId } });
if (AuthStuff.GetAndEnsureAppVersion(Request) != "20250724" && room.Tags.Any(x => x.Tag.Equals("2025", StringComparison.OrdinalIgnoreCase)))
return Ok(new { success = false, error_id = "Matchmaking.UpdateRequired", error = "Update required.", value = new Heartbeat { errorCode = MatchmakingErrorCode.UpdateRequired, isOnline = true, playerId = player.PlayerId } });
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new { success = false, error_id = "Matchmaking.BannedFromRoom", error = "You are banned from this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.BannedFromRoom, isOnline = true, playerId = player.PlayerId } });
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new { success = false, error_id = "Matchmaking.AccountDoesNotExist", error = "Room has no owner.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AccountDoesNotExist, isOnline = true, playerId = player.PlayerId } });
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.DeveloperOnly", error = "This room is developer only.", value = new Heartbeat { errorCode = MatchmakingErrorCode.DeveloperOnly, isOnline = true, playerId = player.PlayerId } });
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.RoomIsPrivate", error = "This room is private.", value = new Heartbeat { errorCode = MatchmakingErrorCode.RoomIsPrivate, isOnline = true, playerId = player.PlayerId } });
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new { success = false, error_id = "Matchmaking.JuniorNotAllowed", error = "Juniors are not allowed in this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.JuniorNotAllowed, isOnline = true, playerId = player.PlayerId } });
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new { success = false, error_id = "Matchmaking.LevelTooLow", error = "Your level is too low.", value = new Heartbeat { errorCode = MatchmakingErrorCode.LevelTooLow, isOnline = true, playerId = player.PlayerId } });
}
}
var currentHb = player.Player?.PlayerExtra?.Heartbeat;
if (bypassMovement && joinMode == 0 && currentHb?.roomInstance != null && currentHb.roomInstance.roomId == roomId && !currentHb.roomInstance.isPrivate)
{
long currentInstanceId = currentHb.roomInstance.roomInstanceId;
var otherInstance = PlayerDB.Players
.FindAll()
.Where(p =>
p.PlayerId != player.PlayerId &&
p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomId == roomId &&
!p.Player.PlayerExtra.Heartbeat.roomInstance.isPrivate &&
p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId != currentInstanceId &&
Sessions.CountPlayersInInstance(p.Player.PlayerExtra.Heartbeat.roomInstance.roomInstanceId) < p.Player.PlayerExtra.Heartbeat.roomInstance.maxCapacity)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (otherInstance != null)
{
var hb = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, otherInstance);
hb.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, hb);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = hb });
}
joinMode = 1;
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId ?? 0, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
[Route("player/avoidjuniors")]
public IActionResult avoidjuniours()
{
return Ok(false);
}
[HttpPost("matchmake/room/{roomId}/{subRoomId}")]
public async Task<IActionResult> MatchmakeRoomWithSubRoom(long roomId, long subRoomId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
var bannedHeartbeat = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
};
return Ok(bannedHeartbeat);
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/room/{roomId}/{subRoomId}")]
public async Task<IActionResult> MatchmakeRoomWithSubRoomV2(long roomId, long subRoomId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var (joinMode, autoFollow, bypassMovement, additionalPlayerIds) = await ParseMatchmakeForm(Request);
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchRoom", error = "Room not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchRoom, isOnline = true, playerId = player.PlayerId } });
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new { success = false, error_id = "Matchmaking.BannedFromRoom", error = "You are banned from this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.BannedFromRoom, isOnline = true, playerId = player.PlayerId } });
if (room != null)
{
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new { success = false, error_id = "Matchmaking.AccountDoesNotExist", error = "Room has no owner.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AccountDoesNotExist, isOnline = true, playerId = player.PlayerId } });
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.DeveloperOnly", error = "This room is developer only.", value = new Heartbeat { errorCode = MatchmakingErrorCode.DeveloperOnly, isOnline = true, playerId = player.PlayerId } });
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new { success = false, error_id = "Matchmaking.RoomIsPrivate", error = "This room is private.", value = new Heartbeat { errorCode = MatchmakingErrorCode.RoomIsPrivate, isOnline = true, playerId = player.PlayerId } });
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new { success = false, error_id = "Matchmaking.JuniorNotAllowed", error = "Juniors are not allowed in this room.", value = new Heartbeat { errorCode = MatchmakingErrorCode.JuniorNotAllowed, isOnline = true, playerId = player.PlayerId } });
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new { success = false, error_id = "Matchmaking.LevelTooLow", error = "Your level is too low.", value = new Heartbeat { errorCode = MatchmakingErrorCode.LevelTooLow, isOnline = true, playerId = player.PlayerId } });
}
}
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = Sessions.CreateRoom(
player.PlayerId,
roomId,
subRoomId,
joinMode: joinMode,
additionalPlayersAutoFollow: autoFollow);
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
if (additionalPlayerIds.Count > 0 && autoFollow)
await ShovePlayersToInstance(additionalPlayerIds, roomId, subRoomId, heartbeat);
else if (additionalPlayerIds.Count > 0)
await NotificationsController.SendPartyActivitySwitch(player.PlayerId, additionalPlayerIds, roomId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
/* 20230616 & 20250724.01 photons */
private static readonly string[] _fallbackRealtime = new[]
{
"616d6ba8-edb1-47f8-a0de-31c8ce426685",
"a7c732cd-5cea-4839-b0b3-ba8e9af14b06",
"90d94f73-9797-409d-84ab-ed3992177612"
};
private static readonly string[] _fallbackVoice = new[]
{
"b3a7f09a-b167-4ba1-87bb-2603da5a39be",
"7e71f40c-d1fa-4e48-8851-981402e630e5",
"3e010413-c8c8-48df-95cf-bf7f2bf430e6"
};
[HttpGet("player/connection-info")]
public async Task<IActionResult> GetPlayerConnectionInfo([FromQuery] long? roomInstanceId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null)
return Unauthorized("");
if (roomInstanceId == null || roomInstanceId <= 0)
{
return Ok(new
{
success = true,
error = (string?)null,
value = new
{
PhotonRealtimeAppId = _fallbackRealtime[_photonRng.Next(_fallbackRealtime.Length)],
PhotonChatAppId,
PhotonVoiceAppId = _fallbackVoice[_photonRng.Next(_fallbackVoice.Length)],
experiments = new { shouldUseGameServerNetworking = false }
}
});
}
var dbEntry = InstanceDB.Get(roomInstanceId.Value);
if (dbEntry == null)
{
var liveInstance = PlayerDB.Players.FindAll()
.Select(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance)
.FirstOrDefault(ri => ri?.roomInstanceId == roomInstanceId.Value);
if (liveInstance != null)
{
dbEntry = InstanceDB.Upsert(liveInstance, liveInstance.isPrivate);
}
}
if (dbEntry == null)
{
return Ok(new
{
success = true,
error = (string?)null,
value = new
{
PhotonRealtimeAppId = _fallbackRealtime[_photonRng.Next(_fallbackRealtime.Length)],
PhotonChatAppId,
PhotonVoiceAppId = _fallbackVoice[_photonRng.Next(_fallbackVoice.Length)],
experiments = new { shouldUseGameServerNetworking = false }
}
});
}
if (string.IsNullOrEmpty(dbEntry.PhotonRealtimeAppId) || string.IsNullOrEmpty(dbEntry.PhotonVoiceAppId))
{
var (rt, vo) = InstanceDB.AssignPhotonIds(roomInstanceId.Value);
dbEntry.PhotonRealtimeAppId = rt;
dbEntry.PhotonVoiceAppId = vo;
}
var liveInst = PlayerDB.Players.FindAll()
.Select(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance)
.FirstOrDefault(ri => ri?.roomInstanceId == roomInstanceId.Value);
return Ok(new
{
success = true,
error = (string?)null,
value = new
{
PhotonAuthToken = "",
PhotonRealtimeAppId = dbEntry.PhotonRealtimeAppId,
PhotonChatAppId,
PhotonVoiceAppId = dbEntry.PhotonVoiceAppId,
PhotonRegion = liveInst?.photonRegion ?? dbEntry.PhotonRegion ?? "us",
PhotonRoomId = liveInst?.photonRoomId ?? dbEntry.PhotonRoomId,
VoiceConnectionInfo = (string?)null,
VoiceServerId = (string?)null,
experiments = new { shouldUseGameServerNetworking = false }
}
});
}
[HttpPost("invite")]
public async Task<IActionResult> Invite([FromForm] long playerId, [FromForm] long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (playerId <= 0 || roomInstanceId <= 0)
return BadRequest();
var targetPlayer = PlayerDB.Players.FindById(playerId);
if (targetPlayer == null)
return NotFound("");
var rel = FriendsDB.GetRelationships(player.PlayerId).FirstOrDefault(r => r.OtherPlayerID == playerId);
if (rel?.RelationshipType != FriendsDBClasses.RelationshipType.Friend)
return BadRequest();
var heartbeat = PlayerDB.GetPlayerHeartbeat(player.PlayerId);
if (heartbeat?.roomInstance == null || heartbeat.roomInstance.roomInstanceId != roomInstanceId)
return BadRequest();
var key = (player.PlayerId, playerId);
var now = DateTime.UtcNow;
_inviteRateLimit.AddOrUpdate(key, _ => (1, now), (_, e) =>
(now - e.Window).TotalSeconds >= 15 ? (1, now) : (e.Count + 1, e.Window));
if (_inviteRateLimit.TryGetValue(key, out var rate) && rate.Count > 3 && (now - rate.Window).TotalSeconds < 15)
return BadRequest();
var invite = new MatchDBClasses.InviteDTO
{
InviteId = roomInstanceId,
Name = heartbeat.roomInstance.Name ?? "Unknown Room",
InviteMode = 0
};
await NotificationsController.SendInvite(playerId, player.PlayerId, heartbeat.roomInstance.roomId, invite);
return Ok();
}
//[HttpPost("matchmake/instance/{roomInstanceId}")] // added back because some bitch deleted it
[HttpPost("matchmake/invite/{roomInstanceId}")] // ez
//[HttpPost("matchmake/v2/instance/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeToInviteInstance(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchGame,
isOnline = true,
playerId = player.PlayerId
});
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
[HttpPost("matchmake/v2/invite/{roomInstanceId}")]
public async Task<IActionResult> MatchmakeToInviteInstanceV2(long roomInstanceId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new { success = false, error_id = "Matchmaking.Banned", error = "You are banned.", value = new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
}});
}
var targetInstance = PlayerDB.Players.FindAll()
.Where(p => p.Player?.PlayerExtra?.Heartbeat?.roomInstance?.roomInstanceId == roomInstanceId)
.Select(p => p.Player.PlayerExtra.Heartbeat.roomInstance)
.FirstOrDefault();
if (targetInstance == null)
return Ok(new { success = false, error_id = "Matchmaking.NoSuchGame", error = "Instance not found.", value = new Heartbeat { errorCode = MatchmakingErrorCode.NoSuchGame, isOnline = true, playerId = player.PlayerId } });
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new { success = false, error_id = "Matchmaking.InsufficientSpace", error = "Instance is full.", value = new Heartbeat { errorCode = MatchmakingErrorCode.InsufficientSpace, isOnline = true, playerId = player.PlayerId } });
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new { success = false, error_id = "Matchmaking.AlreadyInTargetInstance", error = "Already in this instance.", value = new Heartbeat { errorCode = MatchmakingErrorCode.AlreadyInTargetInstance, isOnline = true, playerId = player.PlayerId } });
AuthorizeRoomSession(player.PlayerId, targetInstance.roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(new { success = true, error_id = (string?)null, error = (string?)null, value = heartbeat });
}
[HttpPost("matchmake/player/{targetPlayerId}")]
public async Task<IActionResult> MatchmakeToPlayer(long targetPlayerId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
if (PlayerDB.IsBanned(player.PlayerId))
{
var previousHeartbeat = player.Player?.PlayerExtra?.Heartbeat;
return Ok(new Heartbeat
{
appVersion = AuthStuff.GetAndEnsureAppVersion(Request),
deviceClass = previousHeartbeat?.deviceClass ?? DeviceClasses.Unknown,
errorCode = MatchmakingErrorCode.Banned,
isOnline = previousHeartbeat?.isOnline ?? true,
playerId = player.PlayerId,
roomInstance = null,
statusVisibility = previousHeartbeat?.statusVisibility ?? StatusVisibility.Online,
vrMovementMode = previousHeartbeat?.vrMovementMode ?? 0,
photonAuthToken = null,
photonRealtimeAppId = null,
photonVoiceAppId = null,
photonChatAppId = null,
photonRegion = null,
photonRoomId = null,
voiceConnectionInfo = null,
voiceServerId = null,
experiments = null
});
}
var targetPlayer = PlayerDB.Players.FindById(targetPlayerId);
if (targetPlayer == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
var targetHb = targetPlayer.Player?.PlayerExtra?.Heartbeat;
if (targetHb == null || !targetHb.isOnline || targetHb.roomInstance == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.PlayerNotOnline,
isOnline = true,
playerId = player.PlayerId
});
var targetInstance = targetHb.roomInstance;
long roomId = targetInstance.roomId;
long roomInstanceId = targetInstance.roomInstanceId;
var room = RoomDB.GetRoom(roomId);
if (room == null)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.NoSuchRoom,
isOnline = true,
playerId = player.PlayerId
});
if (IsBannedFromRoom(room, player.PlayerId))
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.BannedFromRoom,
isOnline = true,
playerId = player.PlayerId
});
bool isDeveloper = player.PlayerRoles?.Any(r => r == PlayerDBClasses.PlayerRoles.Developer) ?? false;
bool isCreator = room.CreatorAccountId == player.PlayerId;
bool hasAccess = room.Roles?.Any(r =>
r.AccountId == player.PlayerId && (int)r.Role >= (int)Role.CoOwner) ?? false;
bool isOwnerOrCoOwner = isCreator || hasAccess;
bool hasOwner = room.CreatorAccountId > 0 ||
(room.Roles?.Any(r => (int)r.Role >= (int)Role.CoOwner) ?? false);
if (!hasOwner)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AccountDoesNotExist,
isOnline = true,
playerId = player.PlayerId
});
if (room.Accessibility == RoomAccessibility.Dev_only || room.Accessibility == RoomAccessibility.Dev_Unlisted)
{
if (!isDeveloper && !isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.DeveloperOnly,
isOnline = true,
playerId = player.PlayerId
});
}
else if (room.Accessibility == RoomAccessibility.Private)
{
if (!isCreator && !hasAccess)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.RoomIsPrivate,
isOnline = true,
playerId = player.PlayerId
});
}
if (!isOwnerOrCoOwner)
{
if (player.Player?.IsJunior == true && room.SupportsJuniors == false)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.JuniorNotAllowed,
isOnline = true,
playerId = player.PlayerId
});
if (room.MinLevel > 0 && (player.Player?.Level ?? 1) < room.MinLevel)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.LevelTooLow,
isOnline = true,
playerId = player.PlayerId
});
}
if (targetInstance.isFull || Sessions.CountPlayersInInstance(roomInstanceId) >= targetInstance.maxCapacity)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.InsufficientSpace,
isOnline = true,
playerId = player.PlayerId
});
var currentInstanceHb = player.Player?.PlayerExtra?.Heartbeat;
if (currentInstanceHb?.roomInstance?.roomInstanceId == roomInstanceId)
return Ok(new Heartbeat
{
errorCode = MatchmakingErrorCode.AlreadyInTargetInstance,
isOnline = true,
playerId = player.PlayerId
});
RoomDB.IncrementRoomVisitCount(roomId);
PlayerDB.UpdateRoomLastVisitedAt(player.PlayerId, roomId);
AuthorizeRoomSession(player.PlayerId, roomId);
var heartbeat = PlayerDB.UpdatePlayerHeartbeat(player.PlayerId, targetInstance);
heartbeat.playerId = player.PlayerId;
await NotificationsController.RefreshHeartbeat(player.PlayerId, heartbeat);
return Ok(heartbeat);
}
[HttpDelete("invite/{inviteId}")]
public async Task<IActionResult> DeclineInvite(long inviteId)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
var messages = PlayerDB.GetMessages(player.PlayerId);
var invite = messages.FirstOrDefault(m => m.Id == inviteId);
var invitedata = new MatchDBClasses.InviteDTO
{
InviteId = inviteId,
Name = "Unknown Room",
InviteMode = 0
};
if (invite != null)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
Id = "2",
Msg = new
{
FromPlayerId = player.PlayerId,
Id = PlayerDB.GenerateRandomMessageId(),
SentTime = DateTime.UtcNow,
Type = MessageType.GameInviteDeclined,
Data = invitedata,
RoomId = invite.RoomId,
PlayerEventId = (long?)null
}
});
await NotificationsController.SendToPlayer(invite.FromPlayerId, json);
PlayerDB.DeleteMessage(player.PlayerId, inviteId);
}
return Ok();
}
}
}
+343
View File
@@ -0,0 +1,343 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration.UserSecrets;
using Vanadium.Auth;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using Vanadium.Classes.Rooms;
using Vanadium.Utils;
using System.Text;
using System.Text.Json;
using static Vanadium.Classes.DBs.DBClasses.EventDBClasses;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
namespace Vanadium.Controllers
{
[Route("/")]
public class ModernController : ControllerBase
{
public class OutfitBulkRequest
{
public List<long>? AccountIds { get; set; }
public string? UnityAssetTarget { get; set; }
public string? UnityAssetVersion { get; set; }
}
// [16:38:04] [96.2.32.187] GET: https://reloxa.xyz/crm/me/config/v2 404
// [16:38:05] [96.2.32.187] POST: https://reloxa.xyz/statsigUserProperties 404
// [16:38:06] [96.2.32.187] POST: https://reloxa.xyz/api/avatar/v1/lockeditems/bulk 404
[HttpGet("crm/me/config/v2")]
public IActionResult crmconfig()
{
return Ok(ServerConfig.Bracket);
}
[HttpPost("statsigUserProperties")]
public IActionResult statsig()
{
return Ok(new { Success = true, value = new { splitTests = (object?)null, shouldSampleGameserver = false } });
}
[HttpPost("api/avatar/v1/lockeditems/bulk")]
public IActionResult lockeditems()
{
return Ok(ServerConfig.Bracket);
}
[HttpGet("api/versioncheck/islandedversions")]
public async Task<IActionResult> GetIslandedVersions()
{
return Ok(ServerConfig.Bracket);
}
[Route("api/PlayerReporting/v1/referee")] // ok so theres options, get, post, and patch used by game for some reason
public async Task<IActionResult> PlayerReportingReferee()
{
return Ok(false);
}
[HttpPost("purchase/v1/cleanuppending")] // should be in storefront controller, but this controller is specifically for modern 2025/2026.
public async Task<IActionResult> cleanuppendingstorefront()
{
return Ok(new { success = true, error = (string?)null, error_id = (string?)null, value = (string?)null });
}
[HttpGet("crm/me/config/v3")]
public async Task<IActionResult> ConfigCRM()
{
return Ok(ServerConfig.Bracket);
}
[HttpGet("gameai/user/access")]
[HttpGet("makerai/user/access")]
[Route("identifyampltiude")]
public async Task<IActionResult> identifyamplitudeIdkWhatthisis() // idk what this is bro
{
return Ok(new { success = true, value = (object?)null, error = (object?)null, error_id = (object?)null });
}
[Route("api/players/v1/playerPhotoTaggingSetting")]
public async Task<IActionResult> PlayerPhotoTaggingSettings()
{
return Ok(new { success = true, gay = "i love femboy thighs"});
}
[HttpGet("/sections/bulk")]
public IActionResult GetSectionsBulkShit([FromQuery] List<string> id)
{
return Ok();
}
[HttpGet("outfits/me")]
public async Task<IActionResult> GetMyAvatarV2(long? playerId = null)
{
if (playerId.HasValue)
{
var player = PlayerDB.Players.FindById(playerId.Value);
if (player == null)
return NotFound();
return Ok(player.Player?.PlayerExtra.Avatar);
}
var currentPlayer = AuthStuff.GetCurrentPlayer(Request);
if (currentPlayer == null)
return Unauthorized("");
Avatar? avatar = currentPlayer.Player?.PlayerExtra?.Avatar;
return Ok(new
{
LegacyData = new
{
SelectionsV1 = avatar?.OutfitSelections,
SelectionsV2 = avatar?.OutfitSelectionsV2,
FaceFeatures = avatar?.FaceFeatures,
SkinColor = avatar?.SkinColor,
HairColor = avatar?.HairColor
},
Selections = avatar?.CustomAvatarItems?.Select(i => new
{
i.BodyPart,
i.CustomAvatarItemId,
BakedUnityAssetFileName = (string?)null,
AdditionalConfiguration = (object?)null
}).ToArray() ?? Array.Empty<object>(),
DataVersion = avatar?.DataVersion ?? 10,
CustomizationSettings = avatar?.CustomizationSettings,
ThumbnailFileName = (string?)null,
Name = (string?)null,
Accessibility = 0,
Slot = 0
});
}
[HttpPut("outfits/me")] // 2024 - will (might actually) break avatar if on 2025 tho
public async Task<IActionResult> PutOutfitMe([FromBody] SavedOutfitRequest request)
{
var player = AuthStuff.GetCurrentPlayer(Request);
if (player == null)
return Unauthorized("");
var version = AuthStuff.GetAndEnsureAppVersion(Request);
if (version == "20250724") return BadRequest("");
if (request == null)
return BadRequest("");
PlayerDB.SaveOutfit(player.PlayerId, request);
return Ok(new { success = true });
}
[HttpGet("api/referee/files")]
public async Task<IActionResult> RefereeFiles()
{
return Ok(ServerConfig.Bracket);
}
[HttpGet("sampling")]
public async Task<IActionResult> Sampling()
{
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "EventTypes.json");
if (!System.IO.File.Exists(path)) return Ok(Array.Empty<object>());
return PhysicalFile(path, "application/json");
}
[HttpGet("api/consumables/v1/all")]
public async Task<IActionResult> AllConsumables()
{
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "2025Consumables", "ModernUnlockableConsumables.json");
if (!System.IO.File.Exists(path)) return Ok(Array.Empty<object>());
return PhysicalFile(path, "application/json");
}
[HttpGet("cdn/config/Prod/AvatarEffectConfig_v1.json")]
public async Task<IActionResult> AvatarEffectConfigV1()
{
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "AvatarEffectConfigV1.json");
if (!System.IO.File.Exists(path)) return Ok(ServerConfig.Bracket);
return PhysicalFile(path, "application/json");
}
[HttpGet("aimoderation/roomie/enabledForPlayer")]
public async Task<IActionResult> IsRoomieEnabled()
{
return Ok(true);
}
[HttpPost("realtime-session/create")]
public async Task<IActionResult> CreateSessionForRoomieOrMakerAI()
{
var currentPlayer = AuthStuff.GetCurrentPlayer(Request);
if (currentPlayer == null)
return Unauthorized("");
/*return Ok(new
{
success = true,
error = "",
error_id = "",
value = new
{
ClientSecret = "",
SessionId = "sess_"
}
});*/
// dmcaed
await Task.Delay(Timeout.Infinite);
return null;
// parent of c5d30f8 (Update Controllers/ModernController.cs)
}
[HttpGet("roomieai/user/facts")]
public async Task<IActionResult> RoomieUserFacts()
{
var currentPlayer = AuthStuff.GetCurrentPlayer(Request);
if (currentPlayer == null)
return Unauthorized("");
return Ok(); // dmcaed
}
[HttpGet("roomieai/user/access")]
public async Task<IActionResult> GetUserRoomieAccess()
{
return Ok(new
{
value = new
{
MaxEnergyFromSubscriptions = 8000,
EnergyLeft = 4793,
NextSubscriptionEnergyRechargeAt = (string?)null,
OutputAudioEnabled = true
},
success = true,
error_id = (string?)null,
error = (string?)null
});
}
[HttpGet("gameai/room/{roomId}/spendsummary")]
public async Task<IActionResult> GetUserRoomSpendSummary([FromRoute] long roomId)
{
return Ok(new
{
value = (object?)null,
success = false,
error_id = "AI.ManagementAccessDenied",
error = "You don't have permission to manage AI in this room."
});
}
[HttpGet("makerai/user/balances")]
public async Task<IActionResult> GetMakerAiSpend()
{
return Ok(new
{
UsageDollars = 0,
UsersMaxUsageDollars = 0,
RRPlusUsageDollars = 0.0,
UsersMaxRRPlusUsageDollars = 20,
TimeBalanceStatus = "Empty",
TimeExpiresAt = "0001-01-01T00:00:00",
UsageBalanceStatus = "Good",
UsagePercent = 0,
RRPlusUsageBalanceStatus = "Good",
RRPlusUsagePercent = 0
});
}
// voice shit does not rlly matter
[HttpGet("voice/config")]
[HttpGet("api/config/v1/azurespeech")]
public async Task<IActionResult> VoiceCrappingsFor2023and2025()
{
return Ok(new { voiceConnectionId = ":3c", success = true, regions = ServerConfig.Bracket });
}
[HttpPost("outfits/bulk")]
public async Task<IActionResult> GetAvatarBulkV2([FromBody] OutfitBulkRequest? request)
{
var currentPlayer = AuthStuff.GetCurrentPlayer(Request);
if (currentPlayer == null)
return Unauthorized("");
var accountIds = request?.AccountIds;
if (accountIds == null || accountIds.Count == 0)
return Ok(new { OutfitsByAccountId = new Dictionary<string, object>() });
var avatars = new Dictionary<string, object>();
foreach (var accountId in accountIds)
{
var player = PlayerDB.Players.FindById(accountId);
if (player == null)
continue;
Avatar? avatar = player.Player?.PlayerExtra?.Avatar;
if (avatar == null)
continue;
avatars[accountId.ToString()] = new {
LegacyData = new
{
SelectionsV1 = avatar.OutfitSelections,
SelectionsV2 = avatar.OutfitSelectionsV2,
avatar.FaceFeatures,
avatar.SkinColor,
avatar.HairColor
},
Selections = Array.Empty<object>(),
DataVersion = 9,
CustomizationSettings = (string?)null,
ThumbnailFileName = (string?)null,
Name = (string?)null,
Accessibility = 0,
Slot = 0
};
}
return Ok(new { OutfitsByAccountId = avatars });
}
[HttpGet("purchasecampaign/allcurrent/v2")]
public async Task<IActionResult> GetPurchaseCampaign()
{
string path = Path.Join(Program.dataDir, "APIS", "PurchaseCampaign.json");
return System.IO.File.Exists(path) ? Content(System.IO.File.ReadAllText(path), "application/json") : NotFound();
}
[HttpGet("match/tachyon")]
public async Task<IActionResult> MatchTachyon([FromQuery] long accountId)
{
var player = PlayerDB.Players.FindById(accountId);
if (player == null)
return Ok(0);
return Ok(player.Player?.PlayerExtra?.Heartbeat.roomInstance.roomInstanceId ?? 0);
}
}
}
@@ -0,0 +1,507 @@
using Microsoft.AspNetCore.Mvc;
using Vanadium.Classes;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
using Vanadium.Utils.NotiController;
using System.Text.Json;
namespace Vanadium.Controllers
{
[ApiController]
[Route("api/playerdb")]
public class PlayerDBManagerController : ControllerBase // DISABLED SO LOGAN AND HIS SKID TEAM DONT TRY TO NUKE THE SERVER
{
/*
[HttpGet("players")]
public async Task<IActionResult> GetAllPlayers([FromQuery] string? search = null, [FromQuery] int skip = 0, [FromQuery] int take = 50)
{
try
{
var allPlayers = PlayerDB.Players.FindAll().ToList();
if (!string.IsNullOrWhiteSpace(search))
{
string searchLower = search.ToLower();
allPlayers = allPlayers.Where(p =>
p.Player?.Username?.ToLower().Contains(searchLower) == true ||
p.Player?.DisplayName?.ToLower().Contains(searchLower) == true ||
p.PlayerId.ToString().Contains(searchLower)
).ToList();
}
var total = allPlayers.Count;
var results = allPlayers.Skip(skip).Take(take).Select(p => new
{
p.PlayerId,
Username = p.Player?.Username,
DisplayName = p.Player?.DisplayName,
CreatedAt = p.Player?.CreatedAt,
LastLoginAt = p.Player?.LastLoginAt,
IsJunior = p.Player?.IsJunior,
Level = p.Player?.Level
}).ToList();
return Ok(new { Results = results, Total = total });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpGet("player/{playerId}")]
public async Task<IActionResult> GetPlayer(long playerId)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
return Ok(MapPlayerToDTO(player));
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpPut("player/{playerId}")]
public async Task<IActionResult> UpdatePlayer(long playerId, [FromBody] UpdatePlayerRequest request)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
if (player.Player == null)
player.Player = new Player();
if (!string.IsNullOrWhiteSpace(request.Username))
player.Player.Username = request.Username;
if (!string.IsNullOrWhiteSpace(request.DisplayName))
player.Player.DisplayName = request.DisplayName;
if (!string.IsNullOrWhiteSpace(request.Bio))
player.Player.Bio = request.Bio;
if (!string.IsNullOrWhiteSpace(request.ProfileImage))
player.Player.ProfileImage = request.ProfileImage;
if (request.IsJunior.HasValue)
player.Player.IsJunior = request.IsJunior.Value;
if (request.AvailableUsernameChanges.HasValue)
player.Player.AvailableUsernameChanges = request.AvailableUsernameChanges.Value;
if (request.Level.HasValue)
player.Player.Level = request.Level.Value;
if (request.XP.HasValue)
player.Player.XP = request.XP.Value;
if (request.CreatedAt.HasValue)
player.Player.CreatedAt = request.CreatedAt.Value;
if (request.LastLoginAt.HasValue)
player.Player.LastLoginAt = request.LastLoginAt.Value;
if (request.Birthday.HasValue)
player.Player.Birthday = request.Birthday.Value;
if (!string.IsNullOrWhiteSpace(request.Email))
player.Player.Email = request.Email;
if (!string.IsNullOrWhiteSpace(request.Password))
player.Password = request.Password;
PlayerDB.Players.Update(player);
var updatedAccount = PlayerDB.GetAccountMe(playerId);
if (updatedAccount != null)
{
await NotiController.SendAccountUpdate(playerId, updatedAccount);
await NotiController.SendEvent(playerId, NotiController.EventTypes.SelfAccountUpdate, updatedAccount);
}
return Ok(new { Message = "Player updated successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpPut("player/{playerId}/reputation")]
public async Task<IActionResult> UpdatePlayerReputation(long playerId, [FromBody] UpdateReputationRequest request)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
if (player.Player?.Reputation == null)
player.Player.Reputation = new Reputation();
var rep = player.Player.Reputation;
rep.AccountId = playerId;
if (request.CheerGeneral.HasValue)
rep.CheerGeneral = request.CheerGeneral.Value;
if (request.CheerHelpful.HasValue)
rep.CheerHelpful = request.CheerHelpful.Value;
if (request.CheerCreative.HasValue)
rep.CheerCreative = request.CheerCreative.Value;
if (request.CheerGreatHost.HasValue)
rep.CheerGreatHost = request.CheerGreatHost.Value;
if (request.CheerSportsman.HasValue)
rep.CheerSportsman = request.CheerSportsman.Value;
if (request.CheerCredit.HasValue)
rep.CheerCredit = request.CheerCredit.Value;
if (request.Notoriety.HasValue)
rep.Noteriety = request.Notoriety.Value;
if (request.SubscriberCount.HasValue)
rep.SubscriberCount = request.SubscriberCount.Value;
if (request.IsCheerful.HasValue)
rep.IsCheerful = request.IsCheerful.Value;
PlayerDB.Players.Update(player);
await NotiController.SendReputationUpdate(playerId, player.Player.Reputation);
var friends = PlayerDB.Players.FindAll()
.Where(p => p.Player?.Relationships != null &&
p.Player.Relationships.Any(r => r.PlayerId == playerId && r.RelationshipType == 5))
.Select(p => p.PlayerId)
.ToList();
foreach (var fid in friends)
await NotiController.SendReputationUpdate(fid, new { AccountId = playerId, Reputation = player.Player.Reputation });
return Ok(new { Message = "Reputation updated successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpGet("player/{playerId}/settings")]
public async Task<IActionResult> GetPlayerSettings(long playerId)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
var settings = player.Player?.PlayerExtra?.Settings ?? new List<Setting>();
return Ok(settings);
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpPost("player/{playerId}/settings")]
public async Task<IActionResult> AddPlayerSetting(long playerId, [FromBody] SettingRequest request)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
if (string.IsNullOrWhiteSpace(request.Key) || string.IsNullOrWhiteSpace(request.Value))
return BadRequest(new { Error = "Key and Value are required" });
PlayerDB.SetPlayerSetting(request.Key, request.Value, playerId);
return Ok(new { Message = "Setting added/updated successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpDelete("player/{playerId}/settings/{settingKey}")]
public async Task<IActionResult> DeletePlayerSetting(long playerId, string settingKey)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
if (player.Player?.PlayerExtra?.Settings != null)
{
var setting = player.Player.PlayerExtra.Settings.FirstOrDefault(s => s.Key == settingKey);
if (setting != null)
{
player.Player.PlayerExtra.Settings.Remove(setting);
PlayerDB.Players.Update(player);
}
}
return Ok(new { Message = "Setting deleted successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpPost("player/{playerId}/platforms")]
public async Task<IActionResult> AddPlayerPlatform(long playerId, [FromBody] AddPlatformRequest request)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
if (player.PlatformIds == null)
player.PlatformIds = new List<mPlatformID>();
var existingPlatform = player.PlatformIds.FirstOrDefault(p => p.Platform == request.Platform);
if (existingPlatform != null)
{
existingPlatform.PlatformId = request.PlatformId;
}
else
{
player.PlatformIds.Add(new mPlatformID
{
Platform = request.Platform,
PlatformId = request.PlatformId
});
}
PlayerDB.Players.Update(player);
return Ok(new { Message = "Platform added successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpDelete("player/{playerId}/platforms/{platform}")]
public async Task<IActionResult> RemovePlayerPlatform(long playerId, int platform)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
if (player.PlatformIds != null)
{
var platformToRemove = player.PlatformIds.FirstOrDefault(p => (int)p.Platform == platform);
if (platformToRemove != null)
{
player.PlatformIds.Remove(platformToRemove);
PlayerDB.Players.Update(player);
}
}
return Ok(new { Message = "Platform removed successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpPut("player/{playerId}/roles")]
public async Task<IActionResult> UpdatePlayerRoles(long playerId, [FromBody] UpdateRolesRequest request)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
player.PlayerRoles = new List<PlayerRoles>();
if (request.IsScreenshare)
player.PlayerRoles.Add(PlayerRoles.Screenshare);
if (request.IsModerator)
player.PlayerRoles.Add(PlayerRoles.Moderator);
if (request.IsDeveloper)
player.PlayerRoles.Add(PlayerRoles.Developer);
PlayerDB.Players.Update(player);
var updatedAccount = PlayerDB.GetAccountMe(playerId);
if (updatedAccount != null)
await NotiController.SendAccountUpdate(playerId, updatedAccount);
return Ok(new { Message = "Roles updated successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpPost("player/{playerId}/authtoken")]
public async Task<IActionResult> GenerateNewAuthToken(long playerId)
{
try
{
var player = PlayerDB.Players.FindById(playerId);
if (player == null)
return NotFound(new { Error = "Player not found" });
player.AuthToken = Guid.NewGuid().ToString();
PlayerDB.Players.Update(player);
return Ok(new { AuthToken = player.AuthToken, Message = "New auth token generated" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpDelete("player/{playerId}")]
public async Task<IActionResult> DeletePlayer(long playerId)
{
try
{
var success = PlayerDB.Players.Delete(playerId);
if (!success)
return NotFound(new { Error = "Player not found" });
return Ok(new { Message = "Player deleted successfully" });
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
[HttpGet("test")]
public async Task<IActionResult> test()
{
try
{
string filePath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "communityboard.json");
string jsonData = System.IO.File.ReadAllText(filePath);
var boardData = JsonSerializer.Deserialize<object>(jsonData);
var cbPayload = new {
Id = "CommunityBoardUpdate",
Msg = boardData
};
NotificationsController.SendToAll(JsonSerializer.Serialize(cbPayload));
return Ok("Community board reloaded successfully.");
}
catch (Exception ex)
{
return BadRequest(new { Error = ex.Message });
}
}
private object MapPlayerToDTO(FullPlayer player)
{
var p = player.Player ?? new Player();
var rep = p.Reputation ?? new Reputation();
return new
{
player.PlayerId,
player.AuthToken,
player.Password,
player.PlayerRoles,
player.PlatformIds,
Player = new
{
p.Username,
p.DisplayName,
p.Bio,
p.AvailableUsernameChanges,
p.IsJunior,
p.Level,
p.XP,
p.ProfileImage,
p.BannerImage,
p.Email,
p.CreatedAt,
p.LastLoginAt,
p.Birthday,
Reputation = new
{
rep.CheerGeneral,
rep.CheerHelpful,
rep.CheerCreative,
rep.CheerGreatHost,
rep.CheerSportsman,
rep.CheerCredit,
rep.Noteriety,
rep.SubscriberCount,
rep.IsCheerful
},
Settings = p.PlayerExtra?.Settings ?? new List<Setting>()
}
};
}
public class UpdatePlayerRequest
{
public string? Username { get; set; }
public string? DisplayName { get; set; }
public string? Bio { get; set; }
public string? ProfileImage { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
public bool? IsJunior { get; set; }
public int? AvailableUsernameChanges { get; set; }
public int? Level { get; set; }
public int? XP { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? LastLoginAt { get; set; }
public DateTime? Birthday { get; set; }
}
public class UpdateReputationRequest
{
public int? CheerGeneral { get; set; }
public int? CheerHelpful { get; set; }
public int? CheerCreative { get; set; }
public int? CheerGreatHost { get; set; }
public int? CheerSportsman { get; set; }
public int? CheerCredit { get; set; }
public double? Notoriety { get; set; }
public int? SubscriberCount { get; set; }
public bool? IsCheerful { get; set; }
}
public class SettingRequest
{
public required string Key { get; set; }
public required string Value { get; set; }
}
public class AddPlatformRequest
{
public Platforms Platform { get; set; }
public ulong PlatformId { get; set; }
}
public class UpdateRolesRequest
{
public bool IsScreenshare { get; set; }
public bool IsModerator { get; set; }
public bool IsDeveloper { get; set; }
}
*/
}
}
@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Mvc;
using Vanadium.Auth;
using Vanadium.Classes.DBs;
using Vanadium.Classes.DBs.DBClasses;
namespace Vanadium.Controllers
{
[ApiController]
[Route("/")]
public partial class ProgressionController : ControllerBase
{
private bool CanEdit(RoomDBClasses.Room room, long playerId)
=> room.CreatorAccountId == playerId ||
(room.Roles?.Any(r => r.AccountId == playerId &&
(r.Role == RoomDBClasses.Role.CoOwner ||
r.Role == RoomDBClasses.Role.Creator ||
r.Role == RoomDBClasses.Role.TemporaryCoOwner)) ?? false);
[HttpGet("api/players/v1/progression/xpEarnedToday")]
public IActionResult xpEarnedToday()
{
return Ok((int)67);
}
[HttpGet("roomserver/rooms/{roomId}/experience")]
public async Task<IActionResult> GetRoomExperienceTho(long roomId)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var room = RoomDB.GetRoom(roomId);
if (room == null) return NotFound();
return Ok(new { DailyLimit = room.ExperienceDailyLimit > 0 ? room.ExperienceDailyLimit : 1000000, Enabled = room.ExperienceEnabled, RoomExperienceId = roomId });
}
[HttpPost("/roomserver/rooms/{roomId}/experience")]
public async Task<IActionResult> SetRoomExperience(long roomId, [FromForm] bool enabled, [FromForm] int? dailyLimit)
{
var id = AuthStuff.GetPlayerId(Request);
if (id == null) return Unauthorized("");
var room = RoomDB.GetRoom(roomId);
if (room == null) return NotFound();
if (!CanEdit(room, (long)id)) return Ok(new { success = false });
room.ExperienceEnabled = enabled;
if (dailyLimit.HasValue)
room.ExperienceDailyLimit = dailyLimit.Value;
RoomDB.Rooms.Update(room);
return Ok(new { success = true, error = "", value = new { DailyLimit = room.ExperienceDailyLimit, Enabled = room.ExperienceEnabled, RoomExperienceId = roomId } });
}
}
}