Initial upload

This commit is contained in:
myawired
2026-05-03 14:53:43 -04:00
commit 7a46a17131
819 changed files with 607914 additions and 0 deletions
+353
View File
@@ -0,0 +1,353 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Reflection;
using System.Security.Principal;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.VisualBasic;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using Rec_rewild_live_rewrite.Controllers;
namespace Rec_rewild_live_rewrite.Commands
{
public class CommandConsole
{
private readonly Dictionary<string, Action> _commands = new();
public CommandConsole()
{
_commands = CommandSetup.RegisterAllCommands(this);
Console.WriteLine("Type !help to see available commands.");
string? input;
while (true)
{
Console.Write("> ");
input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
continue;
if (_commands.TryGetValue(input.Trim(), out var command))
{
try
{
command.Invoke();
}
catch (Exception ex)
{
Console.WriteLine($"[Error] {ex.Message}");
}
}
else
{
Console.WriteLine($"Unknown command: {input}");
}
}
}
[ConsoleCommand("!help", "Shows this help message.")]
private void Help()
{
Console.WriteLine("\nAvailable Commands:");
foreach (var kvp in _commands)
{
var method = GetType().GetMethod(kvp.Value.Method.Name,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var attr = method?.GetCustomAttribute<ConsoleCommandAttribute>();
if (attr != null)
Console.WriteLine($" {attr.CommandName,-15} {attr.Description}");
}
Console.WriteLine();
}
[ConsoleCommand("!send_msg_in_chat", "e e e e e e.")]
private void E()
{
Console.WriteLine("Enter chat thread ID: ");
var idStr = Console.ReadLine();
if (!long.TryParse(idStr, out var id))
{
Console.WriteLine("Invalid chat thread ID format.");
return;
}
var thread = ChatDB.GetThread(id);
Console.WriteLine("Enter id who you gonna send as: ");
var senderIdStr = Console.ReadLine();
if (!int.TryParse(senderIdStr, out var senderId))
{
Console.WriteLine("Invalid sender player ID format.");
return;
}
Console.WriteLine("Enter message contents: ");
var contents = Console.ReadLine() ?? "";
if (!string.IsNullOrEmpty(contents))
{
contents = JsonConvert.SerializeObject(new
{
Type = 0,
Version = 1,
Data = contents
});
}
var msg = ChatDB.AddMessage(id, senderId, contents);
thread = ChatDB.GetThread(id);
var payload = new
{
Id = "ChatMessageReceived",
Msg = new
{
chatMessageId = msg.ChatMessageId,
chatThreadId = msg.ChatThreadId,
senderPlayerId = msg.SenderPlayerId,
timeSent = msg.TimeSent,
contents = msg.Contents,
moderationState = msg.ModerationState
}
};
Notifications.SendToPlayers(thread.PlayerIds, JsonConvert.SerializeObject(payload));
}
[ConsoleCommand("!create_test_accounts", "Creates test accounts in the database.")]
private void CreateTestAccounts()
{
try
{
Console.WriteLine("Creating test accounts...");
PlayerDB.CreateAccount(0, 1, "deviceid_test1", 1, out var account1);
PlayerDB.CreateAccount(0, 76561199535310323, "deviceid_test2", 1228278497194147860, out var account2);
PlayerDB.CreateAccount(0, 76561199244473837, "deviceid_test3", 1200818172715073596, out var account3);
Console.WriteLine("✅ Test accounts created successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex}");
}
}
[ConsoleCommand("!delete_player", "Deletes a player account by ID.")]
private void DeletePlayer()
{
Console.Write("Enter player ID to delete: ");
var idStr = Console.ReadLine();
if (ulong.TryParse(idStr, out var id))
{
if (PlayerDB.DeletePlayerFull(id))
Console.WriteLine($"✅ Deleted player {id}");
else
Console.WriteLine($"⚠️ Player {id} not found.");
}
else
{
Console.WriteLine("Invalid ID format.");
}
}
[ConsoleCommand("!set_room_image", "Sets a room's image by ID or name.")]
private void SetRoomImage()
{
Console.Write("Enter room ID or name: ");
string input = Console.ReadLine();
ulong roomId = 0;
bool validRoom = false;
if (ulong.TryParse(input, out var id))
{
if (RoomDB.DoesRoomExist(id))
{
roomId = id;
validRoom = true;
}
}
else
{
var room = RoomDB.GetRoomByName(input);
if (room != null)
{
roomId = room.RoomId;
validRoom = true;
}
Console.WriteLine("Current image name: " + room.ImageName);
}
if (!validRoom)
{
Console.WriteLine("Room not found by ID or name.");
return;
}
Console.Write("Enter new image name: ");
string imageName = Console.ReadLine();
RoomDB.SetRoomImageName(roomId, imageName);
Console.WriteLine($"Room image updated successfully for room ID {roomId}.");
}
[ConsoleCommand("!set_player_influencer", "Sets a player's influencer status and creator code.")]
private void SetPlayerInfluencer()
{
Console.Write("Enter player ID: ");
string input = Console.ReadLine();
if (!ulong.TryParse(input, out var playerId))
{
Console.WriteLine("Invalid player ID.");
return;
}
Console.Write("Set as influencer? (yes/no): ");
string influencerInput = Console.ReadLine()?.Trim().ToLowerInvariant();
bool isInfluencer = influencerInput == "yes" || influencerInput == "y";
Console.Write("Enter creator code (or leave blank to skip): ");
string creatorCode = Console.ReadLine()?.Trim();
// Set influencer
PlayerDB.SetIsInfluencer(playerId, isInfluencer);
// Set creator code if provided
if (!string.IsNullOrWhiteSpace(creatorCode))
{
PlayerDB.SetCreatorCode(playerId, creatorCode);
}
Console.WriteLine($"Player {playerId} updated: Influencer={isInfluencer}, CreatorCode='{creatorCode}'");
}
/*[ConsoleCommand("!send_message", "tset")]
private async Task Test()
{
Console.WriteLine("How u gonna send it as? \n Type 1 for [ALL] or 2 for [TOPLAYER]");
var input = Console.ReadLine();
// 1 = ALL, 2 = specific target player
bool sendToAll = input == "1";
// Create the message entry once (stored in DB)
var newMsg = PlayerDB.AddMessage(
toPlayerId: sendToAll ? 0 : playerIdTo, // 0 = no specific target for DB
fromPlayerId: fromid,
type: WebsocketEvents.MessageType.CoachMessage,
data: messagestr,
roomId: null,
playerEventId: null
);
if (newMsg == null)
{
Console.WriteLine("Failed to create message entry (AddMessage returned null).");
return;
}
try
{
// Convert message to websocket event
var response = JsonConvert.SerializeObject(
WebsocketEvents.CreateMessageResponse(newMsg)
);
if (sendToAll)
{
Console.WriteLine("Sending message to ALL players...");
await Notifications.SendToAll(response);
}
else
{
Console.WriteLine($"Sending message to player {playerIdTo}...");
await Notifications.SendToPlayer(playerIdTo, response);
}
Console.WriteLine("Message sent successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
}
*/
[ConsoleCommand("!get_player_ip", "Gets a player's IP.")]
private void GetPlayerIP()
{
Console.WriteLine("What is the player ID?");
string? playerIdInput = Console.ReadLine();
if (!ulong.TryParse(playerIdInput, out ulong playerId))
{
Console.WriteLine("Invalid player ID.");
return;
}
var decryptedIps = PlayerDB.GetDecryptedIpAddresses(playerId);
if (decryptedIps.Count == 0)
{
Console.WriteLine("No decrypted IP addresses found.");
return;
}
else
{
Console.WriteLine("Decrypted IP addresses:");
foreach (var ip in decryptedIps)
{
Console.WriteLine($"- {ip}");
}
}
}
[ConsoleCommand("!import_player_from_recnet", "Imports a player from RecNet.")]
public void ImportPlayerFromRecNet()
{
Console.WriteLine("What is the player ID?");
string? playerIdInput = Console.ReadLine();
if (!ulong.TryParse(playerIdInput, out ulong playerId))
{
Console.WriteLine("Invalid player ID.");
return;
}
using var http = new HttpClient();
var userJson = http.GetStringAsync("https://accounts.rec.net/account/bulk?id=1999").Result;
var user = JsonDocument.Parse(userJson).RootElement;
string username = user.GetProperty("username").GetString();
string displayName = user.GetProperty("displayName").GetString();
string profileImage = user.GetProperty("profileImage").GetString();
string bannerImage = user.GetProperty("bannerImage").ToString();
int personalPronouns = user.GetProperty("personalPronouns").GetInt32();
int identityFlags = user.GetProperty("identityFlags").GetInt32();
Console.WriteLine($"Downloaded the player '@{username}'!, What platform id do you want to assign '@{username}'?");
string? platformIdInput = Console.ReadLine();
if (!ulong.TryParse(platformIdInput, out ulong platformId))
{
Console.WriteLine("Invalid player ID.");
return;
}
PlayerDB.CreateAccount(Platforms.Steam, platformId, "test", 0, out var newaccount);
bool success = PlayerDB.SetPlayerUsername(username, newaccount.accountId);
PlayerDB.SetDisplayName(displayName, newaccount.accountId);
PlayerDB.SetPlayerImageName(profileImage, newaccount.accountId);
PlayerDB.SetPlayerBanner(bannerImage, newaccount.accountId);
PlayerDB.SetPlayerPronounFlags(personalPronouns, newaccount.accountId);
PlayerDB.SetPlayerIdentityFlags(identityFlags, newaccount.accountId);
}
[ConsoleCommand("!exit", "Shuts down the server.")]
private void Exit()
{
Environment.Exit(0);
}
}
}
+39
View File
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Rec_rewild_live_rewrite.Commands
{
public static class CommandSetup
{
public static Dictionary<string, Action> RegisterAllCommands(object instance)
{
var commands = new Dictionary<string, Action>();
var methods = instance.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
var attr = method.GetCustomAttribute<ConsoleCommandAttribute>();
if (attr != null)
{
commands[attr.CommandName] = () => method.Invoke(instance, null);
}
}
return commands;
}
}
[AttributeUsage(AttributeTargets.Method)]
public class ConsoleCommandAttribute : Attribute
{
public string CommandName { get; }
public string Description { get; }
public ConsoleCommandAttribute(string name, string desc)
{
CommandName = name;
Description = desc;
}
}
}
+156
View File
@@ -0,0 +1,156 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using Rec_rewild_live_rewrite.api.server;
namespace Rec_rewild_live_rewrite.Controllers
{
[ApiController]
[Route("Ai")]
public class AIController : ControllerBase
{
[HttpGet("roomieai/user/access")]
public IActionResult GetRoomieAccess()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
success = true,
error_id = (string?)null,
error = (string?)null,
value = new
{
MaxEnergyFromSubscriptions = int.MaxValue,
EnergyLeft = int.MaxValue,
NextSubscriptionEnergyRechargeAt = (string?)null,
OutputAudioEnabled = true,
}
});
}
[HttpGet("makerai/user/balances")]
public IActionResult GetMakerAIBalance()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
UsageDollars = 0,
UsersMaxUsageDollars = 0,
RRPlusUsageDollars = 0.0,
UsersMaxRRPlusUsageDollars = 0,
TimeBalanceStatus = "Empty",
TimeExpiresAt = "0001-01-01T00:00:00",
UsageBalanceStatus = "Good",
UsagePercent = 0,
RRPlusUsageBalanceStatus = "Good",
RRPlusUsagePercent = 0
});
}
[HttpGet("gameai/user/access")]
public IActionResult GetGameAIAccess([FromQuery] ulong? roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
success = false,
error_id = "AI.RoomDoesNotSupportGameAI",
error = "This room does not support Rec Room Game AI"
});
}
[HttpGet("gameai/room/{roomId}/spendsummary")]
public IActionResult GetGameAISpendSummaryForRoom(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
success = false,
error_id = "AI.RoomDoesNotSupportGameAI",
error = "This room does not support Rec Room Game AI",
value = (object?)null
});
}
[HttpPost("realtime-session/create")] // me when rr leak their open ai key
public async Task<IActionResult> CreateAISession([FromBody] JsonObject request)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
_ = Task.Run(() => Webhook.Send(
account.Id,
"roomie",
"Roomie Info",
$"@{account.Username} ({account.Id}) has pulled out their {request["AIType"]}",
5898927,
null
));
await Task.Delay(10000);
return Ok(new
{
success = false,
error = "no roomie for you!",
error_id = "",
value = (string?)null
});
/*return Ok(new
{
success = true,
error = "",
error_id = "",
value = new
{
ClientSecret = "sk-OsMMq65tXdfOIlTUYtocSL7NCsmA7CerN77OkEv29dODg1EA",
SessionId = $"sess_{Utils.GetRandomString(20)}" // sess_DGuWxBuKIgOZMO0fHkAHE
}
});*/
}
[HttpGet("roomieai/user/facts")]
public async Task<IActionResult> GetUserFactsForRoomie() // Todo
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
/*
Response from https://ai.rec.net
{
"UserContext" : "Here's a concise user profile:\n\nThis individual is a gay man who's generally easygoing and enjoys a relaxed pace of life. He's currently based in [Location - *if provided, otherwise omit*] and is focused on [Work/School - *if provided, otherwise omit*]. In his free time, he's passionate about [Interests - *if provided, otherwise omit*]. He prefers to communicate in a straightforward and approachable manner.",
"UserFacts" : [
{
"CreatedAt" : "2026-03-07T22:56:56",
"Emotion" : "neutral",
"Id" : "63cdd375-57ef-431e-be87-709cb14b487e",
"Object" : "gay",
"Predicate" : "identifies as"
}
]
}
*/
return Ok(new
{
UserContext = "",
UserFacts = Array.Empty<object>()
});
}
}
}
File diff suppressed because it is too large Load Diff
+580
View File
@@ -0,0 +1,580 @@
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.server.Classes;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using static Rec_rewild_live_rewrite.Controllers.AccountsController;
namespace Rec_rewild_live_rewrite.Controllers
{
[Route("Accounts")]
public class AccountsController : ControllerBase
{
[HttpGet("account/bulk")]
public ContentResult AccountBulk([FromQuery] List<ulong> id)
{
return new ContentResult()
{
Content = PlayerDB.GetAccountsBulk(id),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("account/me")]
public IActionResult AccountMe()
{
string authtoken = HttpContext.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(authtoken))
{
authtoken = HttpContext.Request.Cookies["Authorization"];
}
if (string.IsNullOrEmpty(authtoken))
{
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
}
string playerdata = ClientSecurity.DecodeToken(authtoken);
if (playerdata == null)
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
if (playerdata.Contains("error_token"))
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
player_info? account = JsonConvert.DeserializeObject<player_info>(playerdata);
var me = PlayerDB.GetAccountMeSafe(account.Id);
me.email = "[email protected]";
return Ok(me);
}
[HttpGet("account/search")]
public IActionResult AccountSearch(string name)
{
List<account_data> accounts = PlayerDB.Search(name);
return Ok(accounts);
}
[HttpGet("account/{id}/bio")]
public IActionResult GetBioForId(ulong id)
{
player_data? FullAccount = PlayerDB.GetFullAccount(id);
string bio = FullAccount?.player_Extra?.Bio ?? "";
return Ok(new
{
accountId = id,
bio = bio
});
}
[HttpGet("accountprivacysettings/{id}")]
public ContentResult accountprivacysettings_for_userid(ulong id)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
player_data? FullAccount = PlayerDB.GetFullAccount(id);
bool IsRecentRoomHistoryVisible = FullAccount?.player_Extra?.IsRecentRoomHistoryVisible ?? false;
var AccountPrivacySettings = JsonConvert.SerializeObject(new
{
accountId = id,
isRecentHistoryVisible = IsRecentRoomHistoryVisible
}); // Btw, the room history API can return [] if IsRecentRoomHistoryVisible is false like the Rec Room API does. - e12354
return new ContentResult()
{
Content = AccountPrivacySettings,
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("parentalcontrol/me")]
public ContentResult ParentalControlMe()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
accountId = account.Id,
disallowInAppPurchases = false,
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("account/me/username")]
public async Task<ContentResult> ChangeUsername([FromForm] string username)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (PlayerDB.HasActiveModerationBlock(account.Id))
{
return Utils.Error("You cannot change your name at this time.");
}
if (username.Length < 3)
{
return Utils.Error("Your @ Name must have atleast 3 characters!");
}
if (username.Length > 50)
{
return Utils.Error("Your @ Name must not exceed 50 characters!");
}
bool CheckSwears = !string.IsNullOrEmpty(username) && Sanitize.ContainsSwears(username);
if (CheckSwears)
{
return Utils.Error("Your @ Name contains swear words!");
}
if (username.Contains(" "))
{
return Utils.Error("Your @ Name must not contain spaces!");
}
if (Regex.IsMatch(username, @"[^\u0000-\u007F]"))
{
return Utils.Error("Name contains Unicode characters!");
}
bool usercheck = await Task.Run(() => PlayerDB.SetPlayerUsername(username, account.Id));
if (!usercheck)
{
return Utils.Error("This username is already in use!");
}
await Task.Run(() => PlayerDB.SetDisplayName(username, account.Id));
try
{
var selfUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("SelfAccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var globalUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("AccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var notifySelfTask = Notifications.SendToPlayer(account.Id, selfUpdate);
var notifyAllTask = Notifications.SendToAll(globalUpdate);
await Task.WhenAll(notifySelfTask, notifyAllTask);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
_ = Task.Run(() =>
{
try
{
Webhook.Send(
account.Id,
"prod",
"Server info",
$"({account.Id}) @{account.Username} has changed their @ name to @{username}",
5898927
);
}
catch (Exception ex)
{
Console.WriteLine($"[Webhook] Failed to send username change webhook: {ex.Message}");
}
});
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("account/me/bio")]
public async Task<ContentResult> ChangeBio([FromForm] string bio)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (bio.Length > 255)
{
return Utils.Error("Your bio must not exceed 255 characters!");
}
bool CheckSwears = !string.IsNullOrEmpty(bio) && Sanitize.ContainsSwears(bio);
if (CheckSwears)
{
return Utils.Error("Your bio contains swear words!");
}
string shortBio = bio.Length > 60 ? bio.Substring(0, 60) + "..." : bio;
await Task.Run(() => PlayerDB.SetPlayerBio(bio, account.Id));
_ = Task.Run(() =>
{
try
{
Webhook.Send(
account.Id,
"prod",
"Server info",
$"({account.Id}) @{account.Username} has changed their bio! " + $"Preview: \"{shortBio}\"",
5898927
);
}
catch (Exception ex)
{
Console.WriteLine($"[Webhook] Failed to send bio change webhook: {ex.Message}");
}
});
try
{
var selfUpdate = JsonConvert.SerializeObject(
WebsocketEvents.create_player_updated_account2_Response("SelfAccountUpdate", PlayerDB.GetAccountMeSafe(account.Id))
);
var globalUpdate = JsonConvert.SerializeObject(
WebsocketEvents.create_player_updated_account2_Response("AccountUpdate", PlayerDB.GetAccountMeSafe(account.Id))
);
var notifySelfTask = Notifications.SendToPlayer(account.Id, selfUpdate);
var notifyAllTask = Notifications.SendToAll(globalUpdate);
await Task.WhenAll(notifySelfTask, notifyAllTask);
}
catch (Exception ex)
{
Console.WriteLine($"[Notifications] Failed to send bio update: {ex.Message}");
}
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("account/me/displayname")]
public async Task<ContentResult> ChangeDisplayName()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string NewDisplayName = HttpContext.Request.Form["displayName"].ToString();
if (PlayerDB.HasActiveModerationBlock(account.Id))
{
return Utils.Error("You cannot change your name at this time.");
}
if (NewDisplayName.Length > 15)
{
return Utils.Error("Your Display Name must not exceed 15 characters!");
}
bool CheckSwears = !string.IsNullOrEmpty(NewDisplayName) && Sanitize.ContainsSwears(NewDisplayName);
if (CheckSwears)
{
return Utils.Error("Your Display Name contains swear words!");
}
if (Regex.IsMatch(NewDisplayName, @"[^\u0000-\u007F]"))
{
return Utils.Error("Your Display Name contains Unicode characters!");
}
await Task.Run(() => PlayerDB.SetDisplayName(NewDisplayName, account.Id));
try
{
var selfUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("SelfAccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var globalUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("AccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var notifySelfTask = Notifications.SendToPlayer(account.Id, selfUpdate);
var notifyAllTask = Notifications.SendToAll(globalUpdate);
await Task.WhenAll(notifySelfTask, notifyAllTask);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
_ = Task.Run(() =>
{
try
{
Webhook.Send(
account.Id,
"prod",
"Server info",
$"({account.Id}) @{account.Username} has changed their Display Name to \"{NewDisplayName}\"",
5898927
);
}
catch (Exception ex)
{
Console.WriteLine($"[Webhook] Failed to send Display Name change webhook: {ex.Message}");
}
});
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("emojiConfig/whitelistedEmojis")]
public ContentResult GetWhitelistedEmojis()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = JsonConvert.SerializeObject(Emojis.emojis),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("account/me/emoji")]
public async Task<ContentResult> ChangeEmoji()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string displayEmoji = HttpContext.Request.Form["displayEmoji"].ToString();
displayEmoji = Uri.UnescapeDataString(displayEmoji ?? "");
var whitelistedEmojis = Emojis.emojis;
if (!whitelistedEmojis.Contains(displayEmoji))
{
return Utils.Error("Emoji not allowed!");
}
await Task.Run(() => PlayerDB.SetPlayerEmoji(displayEmoji, account.Id));
try
{
var selfUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("SelfAccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var globalUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("AccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var notifySelfTask = Notifications.SendToPlayer(account.Id, selfUpdate);
var notifyAllTask = Notifications.SendToAll(globalUpdate);
await Task.WhenAll(notifySelfTask, notifyAllTask);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
_ = Task.Run(() =>
{
try
{
Webhook.Send(
account.Id,
"prod",
"Server info",
$"({account.Id}) @{account.Username} has changed their Display Emoji to \"{displayEmoji}\"",
5898927
);
}
catch (Exception ex)
{
Console.WriteLine($"[Webhook] Failed to send Display Emoji change webhook: {ex.Message}");
}
});
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("account/me/personalpronouns")]
public async Task<ContentResult> ChangePersonalPronouns([FromForm] int pronounFlags)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
await Task.Run(() => PlayerDB.SetPlayerPronounFlags(pronounFlags, account.Id));
try
{
var selfUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("SelfAccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var globalUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_player_updated_account2_Response("AccountUpdate", PlayerDB.GetAccountMeSafe(account.Id)));
var notifySelfTask = Notifications.SendToPlayer(account.Id, selfUpdate);
var notifyAllTask = Notifications.SendToAll(globalUpdate);
await Task.WhenAll(notifySelfTask, notifyAllTask);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("account/me/identityflags")]
public async Task<ContentResult> ChangeIdentityFlags([FromForm] int identityFlags)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
await Task.Run(() => PlayerDB.SetPlayerIdentityFlags(identityFlags, account.Id));
await Notifications.RefreshAccount(account.Id);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("account/me/profileimage")]
public async Task<IActionResult> ChangeProfileImage([FromForm] string imageName)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (string.IsNullOrEmpty(imageName))
{
return Ok(new
{
success = false,
error = "Image name cannot be empty!",
value = (string?)null
});
}
await Task.Run(() => PlayerDB.SetPlayerImageName(imageName, account.Id));
await Notifications.RefreshAccount(account.Id);
return Ok(new
{
success = true,
error = "",
value = (string?)null
});
}
[HttpPut("account/me/bannerimage")]
public async Task<IActionResult> ChangeBannerImage([FromForm] string imageName)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (string.IsNullOrEmpty(imageName))
{
return Ok(new
{
success = false,
error = "Image name cannot be empty!",
value = (string?)null
});
}
await Task.Run(() => PlayerDB.SetPlayerBanner(imageName, account.Id));
await Notifications.RefreshAccount(account.Id);
return Ok(new
{
success = true,
error = "",
value = (string?)null
});
}
[HttpPost("account/me/phone")]
public IActionResult AddPhoneNumber([FromForm] string phone)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string phoneNumber = phone.Substring(2);
Console.WriteLine($"[Phone] Player {account.Id} is trying to add phone number: {phoneNumber}. Full phone: {phone}");
return Ok(new
{
success = false,
error = "This ain't implemented yet.",
value = (string?)null
});
}
[HttpGet("namegen/options")]
public IActionResult NameGenOptions()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
nouns = Utils.Nouns,
adjectives = Utils.Adjectives,
});
}
}
}
+497
View File
@@ -0,0 +1,497 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using RewildLiveSource.Controllers;
using static Rec_rewild_live_rewrite.api.server.Moderation_Detail;
namespace Rec_rewild_live_rewrite.Controllers
{
public class AdminController : Controller
{
private const string AuthCookieName = "Authorization";
[HttpGet("/admin/v{version}/login")]
public IActionResult Login(int version)
{
var filePath = Path.Combine(
Environment.CurrentDirectory,
"Web",
"Admin",
$"LoginV{version}.html"
);
if (!System.IO.File.Exists(filePath))
return NotFound();
return PhysicalFile(filePath, "text/html");
}
[HttpPost("/api/web/admin/login")]
public ContentResult AdminLoginAPI([FromForm] string username, [FromForm] string password)
{
try
{
var loginResult = PlayerDB.GetAccountPassword(username, password, isAdminPanel: true);
if (!loginResult.success)
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = loginResult.success,
error = loginResult.error
}),
ContentType = "text/html"
};
}
string token = ClientSecurity.RefreshToken_Web(loginResult.accid);
Response.Cookies.Append(AuthCookieName, token, new Microsoft.AspNetCore.Http.CookieOptions
{
Expires = DateTime.Now.AddHours(24),
HttpOnly = true,
Secure = true,
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict
});
Response.Redirect($"/admin/home/v4");
return new ContentResult
{
Content = JsonConvert.SerializeObject(loginResult),
ContentType = "text/html"
};
}
catch (Exception ex)
{
Console.WriteLine($"[AdminLoginAPI] Exception: {ex}");
return new ContentResult
{
Content = "Invalid username or password. Please try again.",
ContentType = "text/html"
};
}
}
[HttpGet("/admin/home/v{version}")]
public IActionResult AdminHomeVVersion(int version)
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
{
return Redirect("/admin/v4/login");
}
var filePath = Path.Combine(
Environment.CurrentDirectory,
"Web",
"Admin",
"Home",
$"HomeV{version}.html"
);
if (!System.IO.File.Exists(filePath))
return NotFound();
return PhysicalFile(filePath, "text/html");
}
[Route("/admin/api/dashboard_data")]
public ContentResult DashboardData()
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
int OnlinePlayers = NotificationsController.WebSockets.Count;
string AdminMOTD = "Go wild like Rec Rewild!";
long bytesUsed = GC.GetTotalMemory(false);
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
total_players = PlayerDB.GetPlayerCount(),
online_players = OnlinePlayers,
banned_players = 0,
total_rooms = RoomDB.GetAllRoomsCount(),
total_inventions = 0,
mem_usage = FormatBytes(bytesUsed),
total_clubs = 0,
MOTD = AdminMOTD,
diskspace = 0,
quick_link_sidebar = new List<Object>
{
new
{
Title = "Reports",
Button_Title = "View Reports",
Button_url = "/admin/reports/all",
Button_icon = "",
}
},
quick_link = new List<Object>
{
new
{
Title = "I just don't know!",
disc = "So I'll put the index page.",
Button_Title = "Go",
Button_url = "/",
Button_Color = "#ff0",
},
new
{
Title = "Reports",
disc = "vew players Reports",
Button_Title = "View Reports",
Button_url = "/admin/reports/all",
Button_Color = "#aaa",
},
new
{
Title = "rewild net home page!",
disc = "go to rewild net home page",
Button_Title = "Go",
Button_url = "/rewild_net/home",
Button_Color = "#e9a",
}
},
}),
ContentType = "application/json"
};
}
[HttpGet("/admin/api/players")]
public ContentResult AdminGetPlayersAPI()
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
var query = HttpContext.Request.Query;
string? id = query["id"];
string? displayName = query["displayName"];
string? minLevel = query["minLevel"];
string? jrState = query["jrState"];
string? platformId = query["platformId"];
string? deviceId = query["deviceId"].FirstOrDefault();
bool onlyBannedPlayers = bool.TryParse(query["banned"], out var b) && b;
return new ContentResult
{
Content = JsonConvert.SerializeObject(PlayerDB.Find_all_player_filtered(id, displayName, minLevel, jrState, platformId, deviceId, onlyBannedPlayers)),
ContentType = "application/json"
};
}
[HttpGet("/admin/api/rooms")]
public IActionResult AdminGetRoomsAPI()
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
return Ok(RoomDB.GetAllRooms());
}
[HttpPost("/admin/api/create_player")]
public ContentResult AdminCreatePlayerAPI()
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
account_data NewAccount = null;
// ADD oculus support later when we get that ingame
string platformid = Request.Form["platform_id"];
Platforms platform = Platforms.All;
if (platformid.StartsWith("765611"))
{
platform = Platforms.Steam;
}
else
{
platform = Platforms.Oculus;
}
var acc = PlayerDB.CreateAccount(Platforms.Steam, ulong.Parse(platformid), Request.Form["device_id"], ulong.Parse(Request.Form["discord_id"]), out NewAccount);
return new ContentResult
{
Content = NewAccount.accountId.ToString(),
ContentType = "application/json"
};
}
[HttpPost("/admin/api/ban_player")]
public ContentResult AdminAPIBanPlayer()
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
string playerIdStr = Request.Form["playerId"];
string durationStr = Request.Form["duration"];
string categoryStr = Request.Form["category"];
string message = Request.Form["message"];
if (!ulong.TryParse(playerIdStr, out ulong playerId))
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "Invalid Player Id"
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (playerId == 1)
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "Cannot ban this player"
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (PlayerDB.CheckDevFlag(playerId))
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "Cannot ban this player"
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (!int.TryParse(durationStr, out int duration) || duration < 0)
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "Invalid duration"
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (!Enum.TryParse<Moderation_Detail.ReportCategory>(categoryStr, ignoreCase: true, out var category))
{
category = Moderation_Detail.ReportCategory.Moderator;
}
string result = PlayerDB.Set_moderation_block(
playerId,
category,
duration,
gameSessionId: -1,
isHostKick: false,
message: message,
playerIdReporter: 0,
isBan: true,
bannedByPlayerId: account.Id
);
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = ""
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("/admin/api/kick_player")]
public ContentResult KickPlayer()
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
string playerid = HttpContext.Request.Form["playerId"].ToString();
if (!ulong.TryParse(playerid, out ulong playerId))
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = false,
message = "Invalid player ID"
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (playerId == account.Id)
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = false,
message = "You can't kick yourself!"
}),
ContentType = "application/json",
StatusCode = 200
};
}
/*
if (player_db_file.CheckDevFlag(playerId))
{
return new ContentResult()
{
Content = "{\"success\":false,\"message\":\"You can't kick developers silly!\"}",
ContentType = "application/json",
StatusCode = 200
};
}*/
PlayerDB.Set_moderation_block(
playerId,
ReportCategory.Moderator,
duration: 1,
gameSessionId: -1,
isHostKick: true,
message: "You have been kicked.",
playerIdReporter: null,
isBan: false,
bannedByPlayerId: account.Id
);
var acc = PlayerDB.GetFullAccount(playerId);
Webhook.Send(
account.Id,
"modacc",
"Reporting info - Player",
$"({account.Id}) @{account.Username} [DEV] kicked player ({playerId}) @{acc.player.Username} {acc.player.DisplayName}",
5898927
);
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true,
message = "Kicked player!"
}),
ContentType = "application/json"
};
}
[HttpGet("/admin/api/account/bulk")]
public ContentResult AccountBulk([FromQuery] List<ulong> id)
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = PlayerDB.GetAccountsBulkAdmin(id),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("/admin/api/send_annoucement")]
public ContentResult AdminAPISendAnnoucement([FromForm] string message, [FromForm] ulong playerId)
{
var (account, error) = AuthHelper.ValidateTokenDev(HttpContext);
if (error != null)
return error;
var response = WebsocketEvents.createResponse_msg(message, playerId);
Notifications.SendToAll(JsonConvert.SerializeObject(response));
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true,
message = "sent!"
}),
ContentType = "application/json"
};
}
[HttpPost("/admin/api/{roomId}/{subroomId}/set_room_datablob")]
public ContentResult set_room_datablob([FromForm] string dataBlob, ulong roomId, ulong subroomId)
{
/*var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;*/
try
{
var e = RoomDB.SetRoomSubroomRoomFile(roomId, subroomId, dataBlob, "", 1, currentplatform: Platforms.All);
}
catch (Exception ex)
{
Console.WriteLine(ex);
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
message = "unknown error!"
}),
ContentType = "application/json"
};
}
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true,
message = "success!"
}),
ContentType = "application/json"
};
}
string FormatBytes(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len /= 1024;
}
return $"{len:0.0} {sizes[order]}";
}
}
}
+423
View File
@@ -0,0 +1,423 @@
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.server.Classes;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using Newtonsoft.Json.Linq;
using Discord.Net;
using System.Security.Principal;
namespace Rec_rewild_live_rewrite.Controllers
{
public class AuthController : ControllerBase
{
[HttpGet("/Auth/eac/challenge")]
public ContentResult EACChallenge()
{
return new ContentResult
{
Content = JsonConvert.SerializeObject("test"),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("/Auth/cachedlogin/forplatformid/{platform}/{platform_id}")]
public ContentResult CachedLogins(int platform, ulong platform_id)
{
Platforms platformEnum;
if (Enum.IsDefined(typeof(Platforms), platform))
{
platformEnum = (Platforms)platform;
}
else
{
platformEnum = Platforms.All;
}
List<AccountLogin>? accounts = null;
PlayerDB.GetLogins(platformEnum, platform_id, out accounts);
if (accounts != null && accounts.Count > 0)
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(accounts),
ContentType = "application/json",
StatusCode = 200
};
}
Webhook.Send(1,
"mod",
"Reporing Info - No Cached Logins",
$"A cached login was attempted for platform {platformEnum} with platform ID {platform_id}, but no matching accounts were found.",
5898927
);
return new ContentResult()
{
Content = "[]",
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("/Auth/connect/token")]
public async Task<ContentResult> ConnectToken(int platform, ulong platform_id)
{
HttpContext.Request.EnableBuffering();
using (var reader = new StreamReader(HttpContext.Request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true))
{
var body = await reader.ReadToEndAsync();
Console.WriteLine("[RAW BODY]");
Console.WriteLine(body);
HttpContext.Request.Body.Position = 0;
}
string token = null;
string? temp2 = HttpContext.Request.Headers["Authorization"];
string? Ip = HttpContext.Request.Headers["CF-Connecting-IP"].FirstOrDefault();
Guid myuuid = Guid.NewGuid();
string guid = myuuid.ToString();
string? grant_type = "";
try
{
grant_type = HttpContext.Request.Form["grant_type"];
}
catch
{
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 400
};
}
ulong accid = 0;
string? device_id = "";
string? username = "";
string? password = "";
string? platform_auth = HttpContext.Request.Form["platform_auth"];
string? user_agent = "";
string? ver = "";
try
{
accid = ulong.Parse(HttpContext.Request.Form["account_id"]);
device_id = HttpContext.Request.Form["device_id"];
user_agent = HttpContext.Request.Headers["User-Agent"];
platform = int.Parse(HttpContext.Request.Form["platform"]);
platform_id = ulong.Parse(HttpContext.Request.Form["platform_id"]);
username = HttpContext.Request.Form["username"];
password = HttpContext.Request.Form["password"];
platform_auth = HttpContext.Request.Form["platform_auth"];
ver = HttpContext.Request.Form["ver"];
// Rec Room Studio uses a different user agent
if (user_agent != "BestHTTP")
{
return new ContentResult()
{
Content = "If you see this, you're a stupid skid and you should 1. stop using http debugger 2. go outside for once 3. if you are from eq then stop. eq allows proxys but we dont",
ContentType = "What a fucking loser ",
StatusCode = 200
};
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.WriteLine($"{grant_type} : {accid}");
if (grant_type == "password") // Rewrite this codeeeeeeeeeeeeeeeeeee
{
try
{
string? Username = HttpContext.Request.Form["username"];
string? Pass = HttpContext.Request.Form["password"];
//var loginResult = PlayerDB.GetAccountPassword(Username, Pass);
//string Token = ClientSecurity.RefreshToken_Web(loginResult.Value.accountId);
//if (Token == null)
goto token_err;
/*return new ContentResult()
{
Content = "{\"access_token\":\"" + Token + "\",\"error_description\":\"\",\"error\":\"\",\"refresh_token\":\"" + guid + "\",\"key\":\"\"}",
ContentType = "application/json",
StatusCode = 200
};*/
}
catch
{
return new ContentResult()
{
Content = "{\r\n \"access_token\" : null,\r\n \"error\" : \"invalid_grant\",\r\n \"error_description\" : \"invalid_username_or_password\",\r\n \"key\" : null,\r\n \"refresh_token\" : null\r\n}\r\n",
ContentType = "application/json",
StatusCode = 200
};
}
}
if (grant_type == "cached_login")
{
ulong steamidticket = 0;
Console.WriteLine("account cached");
if (!string.IsNullOrEmpty(platform_auth))
{
string unescaped = System.Net.WebUtility.UrlDecode(platform_auth);
Console.WriteLine("[PLATFORM AUTH (Decoded)]");
Console.WriteLine(unescaped);
var platformAuthJson = JsonConvert.DeserializeObject<Dictionary<string, string>>(unescaped);
if (platformAuthJson != null && platformAuthJson.TryGetValue("Ticket", out string ticket))
{
Console.WriteLine("[Parsed Ticket]");
Console.WriteLine(ticket);
using (var client = new HttpClient())
{
var url = $"https://api.steampowered.com/ISteamUserAuth/AuthenticateUserTicket/v1?key={ConfigRewild.SteamAPIKey}&appid=471710&ticket={ticket}";
var response = await client.GetAsync(url);
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine("[Steam Auth Response]");
Console.WriteLine(result);
var steamAuthResponse = JsonConvert.DeserializeObject<SteamAuthResponse>(result);
if (steamAuthResponse != null && steamAuthResponse.response != null && steamAuthResponse.response.paramsData != null)
{
if (ulong.TryParse(steamAuthResponse.response.paramsData.steamid, out ulong steamIdFromTicket))
{
steamidticket = steamIdFromTicket;
Console.WriteLine("[Parsed SteamID from Ticket]");
Console.WriteLine(steamidticket);
}
else
{
return new ContentResult()
{
Content = "{\"access_token\":\"" + "\",\"error_description\":\"Cannot verify steam ticket\",\"error\":\"steam_ticket\",\"refresh_token\":\"" + "\",\"key\":\"\"}",
ContentType = "application/json",
StatusCode = 200
};
}
}
else
{
return new ContentResult()
{
Content = "{\"access_token\":\"" + "\",\"error_description\":\"Cannot verify steam ticket\",\"error\":\"steam_ticket\",\"refresh_token\":\"" + "\",\"key\":\"\"}",
ContentType = "application/json",
StatusCode = 200
};
}
}
}
}
var accountData = PlayerDB.GetFullAccount(accid);
Platforms platformparsed = Enum.IsDefined(typeof(Platforms), platform) ? (Platforms)platform : Platforms.All;
string request_url = $"{Request.Scheme}://{Request.Host}";
token = ClientSecurity.RefreshToken(temp2, accid, platform, platform_id, steamidticket, request_url, platformparsed);
if (token == null)
goto token_err;
if (string.IsNullOrEmpty(token))
{
goto token_err;
}
string playerData = ClientSecurity.DecodeToken(token).ToString();
player_info account = JsonConvert.DeserializeObject<player_info>(playerData);
Webhook.Send(account.Id,
"prod",
"Matchmaking info",
$"({account.Id}) @{account.Username} {account.DisplayName} is logging in to **{ver}** on the platform **{platformparsed.ToString()}**",
5898927
);
PlayerDB.SetPlayerDeviceId(device_id, account.Id);
PlayerDB.SetLastLoginTime(account.Id);
PlayerDB.AddIpAddress(account.Id, Ip);
PlayerDB.ResetCheerCreditIfNewDay(account.Id);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
access_token = token,
expires_in = 999999999,
token_type = "Bearer",
refresh_token = guid,
scope = "offline_access rn.accounts rn.accounts.gc rn.api rn.auth rn.auth.gc rn.bugreporting rn.cards rn.chat rn.clubs rn.cms rn.commerce rn.data rn.data.gc rn.datacollection rn.datacollection.gc rn.discovery rn.gamelogs.gc rn.leaderboard rn.link rn.lists rn.match.read rn.match.write rn.moderation rn.notify rn.platformnotifications rn.playersettings rn.roomcomments rn.rooms rn.storage rn.strings rn.studio.gc",
key = ""
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (grant_type == "refresh_token")
{
// Console.WriteLine("refresh_token");
// token = ClientSecurity.RefreshToken(temp2, accid, platform, platform_id, device_id);
// if (token == null)
// goto token_err;
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
if (grant_type == "create_account")
{
return new ContentResult()
{
Content = "The dssssssssssssssssssssssdfxe",
ContentType = "application/json",
StatusCode = 200
};
}
token_err:
return new ContentResult()
{
/*Content = JsonConvert.SerializeObject(new
{
error = "invalid_grant"
}),*/
Content = "",
ContentType = "application/json",
StatusCode = 401
};
}
[HttpPost("/Auth/cachedlogin/forplatformids")]
public IActionResult GetCachedLoginForPlatformIds()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpDelete("/Auth/cachedlogin/current")]
public ContentResult DeleteCurrentCachedLogin()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "This ain't implemented yet.",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[Route("/Auth/role/developer/{id}")]
public IActionResult AuthRoleDeveloper(ulong id)
{
return new ContentResult()
{
Content = PlayerDB.CheckDevFlag(id).ToString().ToLower(),
ContentType = "application/json",
StatusCode = 200
};
}
[Route("/Auth/role/moderator/{id}")]
public ContentResult AuthRoleModerator(ulong id)
{
return new ContentResult()
{
Content = PlayerDB.CheckModFlag(id).ToString().ToLower(),
ContentType = "application/json",
StatusCode = 200
};
}
[Route("account/me/remoteauth")]
public ContentResult RemoteAuth([FromForm] string code)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "This ain't implemented yet.",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("/Auth/account/me/changepassword")]
public ContentResult ChangePassword([FromForm] string newPassword)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (string.IsNullOrEmpty(newPassword))
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "Password cannot be empty!",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (newPassword.Length > 64)
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "Password must be fewer than 64 characters.",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
PlayerDB.SetPlayerPassword(newPassword, account.Id, log: false);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
}
}
+127
View File
@@ -0,0 +1,127 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
namespace Rec_rewild_live_rewrite.Controllers
{
public class BotController : Controller
{
[HttpPost("/api/bot/player/level_up/{discorduserid}")]
public async Task<ContentResult> LevelUp(ulong discorduserid)
{
string botkey = Request.Headers["Bot-Key"].ToString();
var newLevel = Request.Form["new_level"].ToString();
if (botkey != ConfigRewild.Rewild_bot_key)
{
return new ContentResult // The people tryna find this api wont get in
{
Content = "",
ContentType = "application/json",
StatusCode = 404
};
}
var account = PlayerDB.GetPlayerByDiscordId(discorduserid);
await PlayerDB.SetPlayerLevel(int.Parse(newLevel), account.player.Id);
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true,
message = $"Leveled up user {account?.player.Username} to level {newLevel}"
}),
ContentType = "application/json",
StatusCode = 200
};
}
[Route("/api/bot/create")]
public ContentResult CreateAccViaBot()
{
string key = HttpContext.Request.Headers["Key"].ToString();
string steamid = HttpContext.Request.Form["steam_id"].ToString();
string discordUserId = HttpContext.Request.Form["discord_user_id"].ToString();
ulong steamid2 = ulong.Parse(steamid);
if (key == ConfigRewild.Rewild_bot_key)
{
var blockedIds = new HashSet<string>
{
"1176571557414445137",
"1193883666946998383",
"1344328341951615106"
};
if (PlayerDB.GetAccountCountByPlatformId(steamid2) >= 1)
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
value = "You already have a account!"
}),
ContentType = "application/json",
StatusCode = 200
};
}
if (blockedIds.Contains(discordUserId))
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
value = "You require manual verification. Contact @e12354 on Discord"
}),
ContentType = "application/json",
StatusCode = 200
};
}
var blockedSteamIds = new HashSet<string>
{
"76561199485696854"
};
if (blockedSteamIds.Contains(steamid))
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = false,
value = "You require manual verification. Contact @e12354 on Discord"
}),
ContentType = "application/json",
StatusCode = 200
};
}
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
eeee = discordUserId, // todo
}),
ContentType = "application/json",
StatusCode = 200
};
}
else
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 404
};
}
}
}
}
+198
View File
@@ -0,0 +1,198 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.server;
namespace Rec_rewild_live_rewrite.Controllers
{
[ApiController]
public class CDNController : Controller
{
[HttpGet("/cdn/config/LoadingScreenTipData")]
public IActionResult LoadingScreenTipData()
{
string filePath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "LoadingScreenTipData.json");
if (!System.IO.File.Exists(filePath))
return Content("[]", "application/json");
return PhysicalFile(filePath, "application/json");
}
[HttpGet("/iam/me/channels/{type}")]
public IActionResult GetIamChannel(string type)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string basePath = Path.Combine(Environment.CurrentDirectory, "Data", "APIS");
switch (type.ToLower())
{
case "announcements":
string announcementsPath = Path.Combine(basePath, "IamAnnouncements.json");
if (System.IO.File.Exists(announcementsPath))
return PhysicalFile(announcementsPath, "application/json");
break;
case "justintimetutorials":
return Ok(ConfigRewild.Bracket);
}
return Ok(ConfigRewild.Bracket);
}
[HttpGet("/sections/pagesource/{type}")]
public IActionResult GetWatchPages(string type)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string path = Path.Combine(
Environment.CurrentDirectory,
"Data",
"APIS",
"WatchPages",
$"{type}.json"
);
if (!System.IO.File.Exists(path))
return Ok(ConfigRewild.Bracket);
return PhysicalFile(path, "application/json");
}
[Route("/cdn/room/{*room_path}")]
public async Task<IActionResult> CDNRoom(string room_path)
{
// 1. SANITIZE: Prevent path traversal
string fileName = Path.GetFileName(room_path);
string localPath = Path.Combine(Environment.CurrentDirectory, "Data", "CDN", "RoomBlobs", room_path);
string directory = Path.GetDirectoryName(localPath)!;
try
{
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
// 2. DOWNLOAD IF MISSING
if (!System.IO.File.Exists(localPath))
{
using var client = new HttpClient();
var response = await client.GetAsync($"https://cdn.rec.net/room/{room_path}");
if (!response.IsSuccessStatusCode) return NotFound();
await using var remoteStream = await response.Content.ReadAsStreamAsync();
await using var fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None);
await remoteStream.CopyToAsync(fileStream);
}
// 3. GATHER FILE METADATA & DYNAMIC DATA
var fileInfo = new FileInfo(localPath);
var now = DateTimeOffset.UtcNow;
// Compute actual MD5 hash dynamically
string md5Base64;
using (var md5 = System.Security.Cryptography.MD5.Create())
{
using var readStream = System.IO.File.OpenRead(localPath);
byte[] hash = await md5.ComputeHashAsync(readStream);
md5Base64 = Convert.ToBase64String(hash);
}
// Generate fake Azure/Cloudflare IDs
string requestId = Guid.NewGuid().ToString();
// Mimic Azure Ref format: YYYYMMDDTHHMMSSZ-[randomhex]
string azureRef = $"{now:yyyyMMddTHHmmssZ}-{Guid.NewGuid().ToString("N").Substring(0, 32)}";
// Generate ETag based on the file's LastWriteTime to act as a unique version identifier
string eTag = $"\"0x{fileInfo.LastWriteTimeUtc.Ticks:X}\"";
// 4. APPLY DYNAMIC HEADERS
// Note: Content-Length, Content-Type, and Accept-Ranges are handled automatically by the File() return method.
Response.Headers["Date"] = now.ToString("R"); // RFC 1123 format (e.g., Thu, 12 Mar 2026 16:06:27 GMT)
Response.Headers["Cache-Control"] = "public, max-age=31536000";
Response.Headers["Last-Modified"] = fileInfo.LastWriteTimeUtc.ToString("R");
Response.Headers["ETag"] = eTag;
// Mock Azure Blob Storage Headers
Response.Headers["x-ms-request-id"] = requestId;
Response.Headers["x-ms-version"] = "2021-04-10";
Response.Headers["x-ms-version-id"] = fileInfo.LastWriteTimeUtc.ToString("o"); // ISO 8601 format
Response.Headers["x-ms-is-current-version"] = "true";
Response.Headers["x-ms-creation-time"] = fileInfo.CreationTimeUtc.ToString("R");
Response.Headers["x-ms-blob-content-md5"] = md5Base64;
Response.Headers["x-ms-lease-status"] = "unlocked";
Response.Headers["x-ms-lease-state"] = "available";
Response.Headers["x-ms-blob-type"] = "BlockBlob";
Response.Headers["x-ms-server-encrypted"] = "true";
Response.Headers["x-ms-last-access-time"] = now.ToString("R");
// CORS and CDN Mock Headers
Response.Headers["Access-Control-Expose-Headers"] = "x-ms-request-id,Server,x-ms-version,x-ms-version-id,x-ms-is-current-version,Content-Type,Cache-Control,ETag,Last-Modified,x-ms-creation-time,x-ms-blob-content-md5,x-ms-lease-status,x-ms-lease-state,x-ms-blob-type,x-ms-server-encrypted,Accept-Ranges,x-ms-last-access-time,Content-Length,Date,Transfer-Encoding";
Response.Headers["Access-Control-Allow-Origin"] = "*";
Response.Headers["x-azure-ref"] = azureRef;
Response.Headers["x-fd-int-roxy-purgeid"] = "0";
Response.Headers["X-Cache-Info"] = "L2_T2";
Response.Headers["X-Cache"] = "TCP_REMOTE_HIT"; // Tells the client it successfully hit the CDN cache
// 5. RETURN AS STREAM
var fs = new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read);
// enableRangeProcessing allows standard 206 Partial Content responses (crucial for large room files)
return File(fs, "application/octet-stream", enableRangeProcessing: true);
}
catch (Exception ex)
{
Console.WriteLine($"CDN Error: {ex.Message}");
return NotFound();
}
}
[Route("/cdn/data/{*room_path}")]
public async Task<IActionResult> CDNData(string room_path)
{
try
{
string localPath = Path.Combine(Environment.CurrentDirectory, "Data", "CDN", "DataBlobs", room_path);
string directory = Path.GetDirectoryName(localPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
if (System.IO.File.Exists(localPath))
{
return File(await System.IO.File.ReadAllBytesAsync(localPath), "application/octet-stream");
}
else
{
using (HttpClient client = new HttpClient())
{
try
{
byte[] data = await client.GetByteArrayAsync($"https://cdn.rec.net/data/{room_path}", CancellationToken.None);
await System.IO.File.WriteAllBytesAsync(localPath, data);
return File(data, "application/octet-stream");
}
catch (HttpRequestException ex)
{
Console.WriteLine(ex);
return NotFound("");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return NotFound();
}
}
}
}
+759
View File
@@ -0,0 +1,759 @@
using System.Linq;
using Discord.Net;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.server.Classes;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using RewildLiveSource.api.admin;
using static Rec_rewild_live_rewrite.api.server.Classes.db_class.ChatDBClasses;
namespace Rec_rewild_live_rewrite.Controllers
{
[Route("chat")]
public class ChatController : ControllerBase
{
// https://chat.rec.net/thread?maxCount=50&mode=0 return
/*
[
{
"latestMessage": {
"chatMessageId": 1735292428,
"chatThreadId": 1,
"senderPlayerId": -5,
"timeSent": "2025-08-03T18:39:03.4521439Z",
"contents": "{\"Type\":0,\"Version\":1,\"Data\":\"Player <@1> started a chat\"}",
"moderationState": 0
},
"chatThreadId": 1,
"playerIds": [
1,
2
],
"lastReadMessageId": -1,
"chatThreadName": null,
"chatThreadType": 0,
"snoozedUntil": null,
"isFavorited": false
}
]
*/
[HttpGet("thread")]
public ContentResult GetThreads([FromQuery] int maxCount = 50, int mode = 0)
{
string authtoken = HttpContext.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(authtoken))
{
authtoken = HttpContext.Request.Cookies["Authorization"];
}
if (string.IsNullOrEmpty(authtoken))
{
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
}
string playerdata = ClientSecurity.DecodeToken(authtoken);
if (playerdata == null)
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
if (playerdata.Contains("error_token"))
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
player_info? account = JsonConvert.DeserializeObject<player_info>(playerdata);
// Load from DB
var threads = ChatDB.GetThreadsForPlayer((ulong)account.Id, 50);
var response = threads.Select(t => new
{
latestMessage = t.Messages.Count > 0 ? new
{
chatMessageId = t.Messages[0].ChatMessageId,
chatThreadId = t.ChatThreadId,
senderPlayerId = t.Messages[0].SenderPlayerId,
timeSent = t.Messages[0].TimeSent,
contents = t.Messages[0].Contents,
moderationState = t.Messages[0].ModerationState
} : null,
chatThreadId = t.ChatThreadId,
playerIds = t.PlayerIds,
lastReadMessageId = t.LastReadMessageId,
chatThreadName = t.ChatThreadName,
chatThreadType = 0, // RRO uses 0 for user threads
snoozedUntil = t.SnoozedUntil,
isFavorited = t.IsFavorited
});
return new ContentResult()
{
Content = JsonConvert.SerializeObject(response),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("thread/chatPrivacySetting")]
public IActionResult GetChatPrivacySetting()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new ChatPrivacySettings
{
playerId = account.Id,
directMessagePrivacySetting = ChatPrivacy.Friends,
groupChatPrivacySetting = ChatPrivacy.Friends
});
}
[HttpGet("thread/{id}")]
public ContentResult GetThreadById(long id, [FromQuery] int maxCount = 50, [FromQuery] int mode = 0)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
// Load thread
var thread = ChatDB.GetThread(id);
if (thread == null)
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
if (!thread.PlayerIds.Contains(account.Id))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
var latestMessage = thread.Messages.Count > 0
? thread.Messages.Last()
: null;
var response = new
{
latestMessage = latestMessage != null ? new
{
chatMessageId = latestMessage.ChatMessageId,
chatThreadId = latestMessage.ChatThreadId,
senderPlayerId = latestMessage.SenderPlayerId,
timeSent = latestMessage.TimeSent,
contents = latestMessage.Contents,
moderationState = latestMessage.ModerationState
} : null,
chatThreadId = thread.ChatThreadId,
playerIds = thread.PlayerIds,
lastReadMessageId = thread.LastReadMessageId,
chatThreadName = thread.ChatThreadName,
chatThreadType = 0,
snoozedUntil = thread.SnoozedUntil,
isFavorited = thread.IsFavorited,
messages = thread.Messages
.OrderByDescending(m => m.ChatMessageId)
.Take(maxCount)
.OrderBy(m => m.ChatMessageId)
.Select(m => new
{
chatMessageId = m.ChatMessageId,
chatThreadId = m.ChatThreadId,
senderPlayerId = m.SenderPlayerId,
timeSent = m.TimeSent,
contents = m.Contents,
moderationState = m.ModerationState
})
};
return new ContentResult
{
Content = JsonConvert.SerializeObject(response),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("thread/{id}")]
public async Task<ContentResult> SendMessageToThread(long id)
{
string authtoken = HttpContext.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(authtoken))
{
authtoken = HttpContext.Request.Cookies["Authorization"];
}
if (string.IsNullOrEmpty(authtoken))
{
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
}
string playerdata = ClientSecurity.DecodeToken(authtoken);
if (playerdata == null)
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
if (playerdata.Contains("error_token"))
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
player_info? account = JsonConvert.DeserializeObject<player_info>(playerdata);
// Extract "messageContents" from form or query
string? contents = HttpContext.Request.Query["messageContents"];
if (string.IsNullOrWhiteSpace(contents))
contents = HttpContext.Request.Form["messageContents"];
var actualcontents = System.Net.WebUtility.UrlDecode(contents);
var json = JObject.Parse(contents);
string actualMessage = json["Data"]?.ToString() ?? "";
if (string.IsNullOrWhiteSpace(contents))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 400
};
}
// Load thread
var thread = ChatDB.GetThread(id);
if (thread == null)
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 400
};
}
if (!thread.PlayerIds.Contains(account.Id))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
if (ConfigRewild.AllowSwearsInChatThreads == false)
{
var messageJson = JsonConvert.DeserializeObject<MessageJson>(contents);
if (messageJson.Type == MessageContentType.Text)
{
messageJson.Data = Sanitize.SanitizeMessage(messageJson.Data);
}
contents = JsonConvert.SerializeObject(messageJson);
}
// Add message to DB
var msg = ChatDB.AddMessage(id, (int)account.Id, contents);
if (msg == null)
{
return new ContentResult
{
Content = JsonConvert.SerializeObject(new { chatResult = 5, chatThread = (object)null }),
ContentType = "application/json",
StatusCode = 200
};
}
// Reload updated thread
thread = ChatDB.GetThread(id);
// Sort messages newest → oldest (same as RRO)
var messages = thread.Messages
.OrderByDescending(m => m.ChatMessageId)
.Select(m => new
{
chatMessageId = m.ChatMessageId,
chatThreadId = m.ChatThreadId,
senderPlayerId = m.SenderPlayerId,
timeSent = m.TimeSent,
contents = m.Contents,
moderationState = m.ModerationState
})
.ToList();
var response = new
{
chatResult = 0,
chatThread = new
{
messages = messages,
chatThreadId = thread.ChatThreadId,
playerIds = thread.PlayerIds,
lastReadMessageId = thread.LastReadMessageId,
chatThreadName = thread.ChatThreadName,
chatThreadType = 0,
snoozedUntil = thread.SnoozedUntil,
isFavorited = thread.IsFavorited
}
};
var payload = new
{
Id = "ChatMessageReceived",
Msg = new
{
chatMessageId = msg.ChatMessageId,
chatThreadId = msg.ChatThreadId,
senderPlayerId = msg.SenderPlayerId,
timeSent = msg.TimeSent,
contents = msg.Contents,
moderationState = msg.ModerationState
}
};
if (PlayerDB.CheckDevFlag(account.Id) && thread.PlayerIds.Contains(1))
{
_ = Task.Run(async () =>
{
try
{
await Task.Delay(250); // small delay so ordering looks natural
// Remove <=> prefix
string messageContent = actualMessage.Substring(3);
string? result =
DevChatCommands.ParseCommand(messageContent, "ChatThread", account.Id);
if (string.IsNullOrWhiteSpace(result))
return;
string botContent = JsonConvert.SerializeObject(new
{
Type = 0,
Version = 2,
Data = "<=>" + result
});
var botMsg = ChatDB.AddMessage(id, 1, botContent);
if (botMsg == null)
return;
var wsEvent = new
{
Id = "ChatMessageReceived",
Msg = new
{
chatMessageId = botMsg.ChatMessageId,
chatThreadId = botMsg.ChatThreadId,
senderPlayerId = botMsg.SenderPlayerId,
timeSent = botMsg.TimeSent,
contents = botMsg.Contents,
moderationState = botMsg.ModerationState
}
};
Notifications.SendToPlayers(
thread.PlayerIds,
JsonConvert.SerializeObject(wsEvent)
);
}
catch (Exception ex)
{
Console.WriteLine($"DevChat error: {ex}");
}
});
}
await Notifications.SendToPlayers(thread.PlayerIds, JsonConvert.SerializeObject(payload));
return new ContentResult
{
Content = JsonConvert.SerializeObject(response),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("thread/{id}/message")]
public ContentResult GetThreadMessages(
long id,
[FromQuery] int messageCount = 50,
[FromQuery] int mode = 0,
[FromQuery] int referenceMessageId = 0)
{
string authtoken = HttpContext.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(authtoken))
{
authtoken = HttpContext.Request.Cookies["Authorization"];
}
if (string.IsNullOrEmpty(authtoken))
{
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
}
string playerdata = ClientSecurity.DecodeToken(authtoken);
if (playerdata == null)
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
if (playerdata.Contains("error_token"))
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 401
};
player_info? account = JsonConvert.DeserializeObject<player_info>(playerdata);
var thread = ChatDB.GetThread(id);
if (thread == null)
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 404
};
}
if (!thread.PlayerIds.Contains(account.Id))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
IEnumerable<ChatDBClasses.ChatMessage> messages = thread.Messages;
// Apply messageCount limit and mode
switch ((ChatDBClasses.QueryMode)mode)
{
case ChatDBClasses.QueryMode.Latest:
messages = messages
.OrderByDescending(m => m.ChatMessageId)
.Take(messageCount)
.OrderBy(m => m.ChatMessageId);
break;
case ChatDBClasses.QueryMode.NewerThan:
messages = messages
.Where(m => m.ChatMessageId > referenceMessageId)
.OrderBy(m => m.ChatMessageId)
.Take(messageCount);
break;
case ChatDBClasses.QueryMode.OlderThan:
messages = messages
.Where(m => m.ChatMessageId < referenceMessageId)
.OrderByDescending(m => m.ChatMessageId)
.Take(messageCount)
.OrderBy(m => m.ChatMessageId);
break;
}
/*var response = new[]
{
new
{
chatThreadId = thread.ChatThreadId,
playerIds = thread.PlayerIds,
lastReadMessageId = thread.LastReadMessageId,
chatThreadName = thread.ChatThreadName,
snoozedUntil = thread.SnoozedUntil,
isFavorited = thread.IsFavorited,
messages = messages.Select(m => new
{
chatMessageId = m.ChatMessageId,
chatThreadId = m.ChatThreadId,
senderPlayerId = m.SenderPlayerId,
timeSent = m.TimeSent,
contents = m.Contents,
moderationState = m.ModerationState
}).ToList()
}
};*/
var response = messages.Select(m => new
{
chatMessageId = m.ChatMessageId,
chatThreadId = m.ChatThreadId,
senderPlayerId = m.SenderPlayerId,
timeSent = m.TimeSent,
contents = m.Contents,
moderationState = m.ModerationState
}).ToList();
return new ContentResult
{
Content = JsonConvert.SerializeObject(response),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("thread/withmembers")]
public ContentResult CreateThread()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string idsStr = Request.Form["ids"].ToString();
var memberIds = idsStr.Split(',')
.Select(x => ulong.Parse(x.Trim()))
.ToList();
List<RelationshipDetail> friends = FriendsDB.GetRelationships(account.Id);
// check if the account is friends with the memberIds if not then remove them from the memberIds
bool check = memberIds.Where(id => id != account.Id).All(id => friends.Any(f => f.PlayerID == id));
if (!check)
{
return new ContentResult
{
Content = "5",
ContentType = "application/json",
StatusCode = 200
};
}
memberIds.Add(account.Id);
string messageCountStr = Request.Form["messageCount"].ToString();
int messageCount = int.TryParse(messageCountStr, out var mc) ? mc : 0;
var thread = ChatDB.CreateThread(memberIds, account.Id, null);
var response = new
{
chatResult = ChatDBClasses.ChatResults.Success,
chatThread = new
{
messages = thread.Messages.FirstOrDefault(),
chatThreadId = thread.ChatThreadId,
playerIds = thread.PlayerIds,
lastReadMessageId = thread.LastReadMessageId,
chatThreadName = thread.ChatThreadName,
chatThreadType = 0,
snoozedUntil = thread.SnoozedUntil,
isFavorited = thread.IsFavorited
}
};
var payload = new
{
Id = "ChatMessageReceived",
Msg = new
{
chatMessageId = thread.Messages.FirstOrDefault().ChatMessageId,
chatThreadId = thread.Messages.FirstOrDefault().ChatThreadId,
senderPlayerId = thread.Messages.FirstOrDefault().SenderPlayerId,
timeSent = thread.Messages.FirstOrDefault().TimeSent,
contents = thread.Messages.FirstOrDefault().Contents,
moderationState = thread.Messages.FirstOrDefault().ModerationState
}
};
Notifications.SendToPlayers(memberIds, JsonConvert.SerializeObject(payload));
// todo: idk what to return back
return new ContentResult()
{
/*
Content = JsonConvert.SerializeObject(new
{
ChatMessageId = thread.Messages.FirstOrDefault().ChatMessageId,
ChatThreadId = thread.ChatThreadId,
Contents = thread.Messages.FirstOrDefault().Contents,
ModerationState = thread.Messages.FirstOrDefault().ModerationState,
SenderPlayerId = thread.Messages.FirstOrDefault().SenderPlayerId,
TimeSent = thread.Messages.FirstOrDefault().TimeSent
}),
*/
Content = JsonConvert.SerializeObject(response),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("thread/{threadid}/rename")]
public ContentResult RenameThread(long threadid)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string newName = Request.Form["name"].ToString();
newName = Uri.UnescapeDataString(newName);
var thread = ChatDB.GetThread(threadid);
if (thread == null)
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
if (!thread.PlayerIds.Contains(account.Id))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
var updatedthread = ChatDB.RenameThread(threadid, account.Id, newName);
return new ContentResult
{
Content = ChatResults.Success.ToString(),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("thread/{threadid}/leave")]
public ContentResult LeaveThread(long threadid)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var thread = ChatDB.GetThread(threadid);
if (thread == null)
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
if (!thread.PlayerIds.Contains(account.Id))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
var updatedthread = ChatDB.LeaveThread(threadid, account.Id);
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("thread/{threadid}/message/{messageid}/read")]
public ContentResult LeaveThread(long threadid, long messageid)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var thread = ChatDB.GetThread(threadid);
if (thread == null)
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
if (!thread.PlayerIds.Contains(account.Id))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
// /chat/thread/2/message/20/read
}
}
+204
View File
@@ -0,0 +1,204 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.server.Classes;
namespace Rec_rewild_live_rewrite.Controllers
{
[Route("clubs")]
public class ClubsController : ControllerBase
{
// subscription/mine/member
// club/categoryTags // the tags for clubs // todo: get club tags from rec.net
// club/home/me // return 404 if you don't have a club home set, i think this return just "1" room id
// announcements/v2/mine/unread
// announcements/v2/subscription/mine/unread
// /clubs/club/mine/created
[HttpGet("club/mine/created")]
public ContentResult MyClubs()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = "[]",
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("announcements/v2/mine/unread")]
public ContentResult UnreadAnnoucements()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = "[]",
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("announcements/v2/subscription/mine/unread")]
public ContentResult UnreadAnnoucements2()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return new ContentResult()
{
Content = "[]",
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("subscription/subscriberCount/{PlayerId}")]
public ContentResult SubscriberCount(ulong PlayerId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var acc = PlayerDB.GetFullAccount(PlayerId);
if (acc == null)
{
return new ContentResult()
{
Content = "0",
ContentType = "application/json",
StatusCode = 200
};
}
return new ContentResult()
{
Content = acc?.player_Extra?.sub_count.ToString(),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("subscription/{PlayerId}")]
public async Task<ContentResult> SubscribeToPlayer(ulong PlayerId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var roomId = Request.Form["roomId"].ToString(); // idk
await Task.Run(() => PlayerDB.AddSubscription(account.Id, PlayerId));
try
{
var selfUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_sub("CreatorClubSubscriptionUpdate", PlayerId, 10));
var notifySelfTask = Notifications.SendToPlayer(account.Id, selfUpdate);
await Task.WhenAll(notifySelfTask);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
value = PlayerId,
success = true,
error_id = (string?)null,
error = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpDelete("subscription/{PlayerId}")]
public async Task<ContentResult> UnsubscribeToPlayer(ulong PlayerId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
await Task.Run(() => PlayerDB.RemoveSubscription(account.Id, PlayerId));
try
{
var selfUpdate = JsonConvert.SerializeObject(WebsocketEvents.create_sub("CreatorClubSubscriptionUpdate", PlayerId, 0));
var notifySelfTask = Notifications.SendToPlayer(account.Id, selfUpdate);
await Task.WhenAll(notifySelfTask);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send notification: {ex.Message}");
}
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error_id = (string?)null,
error = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("subscription/my/subscriptions")]
public ContentResult MySubscriptions()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var subs = PlayerDB.GetSubscribedToIds(account.Id);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(subs),
ContentType = "application/json",
StatusCode = 200
};
}
// https://clubs.rec.net/subscription/details/1503269750 return
/*
{
"clubId": 2065378865047881941,
"accountId": 1503269750,
"subscriberCount": 0
}
*/
// https://clubs.rec.net/club/2065378865047881941 return
/*
{
"clubId": 2065378865047881941,
"name": "33c7nhjv7txs1dsitn4v92pb5",
"category": "CreatorAutoTag",
"mainImageName": "DefaultClubImage2k.jpg",
"description": null,
"creatorAccountId": 1503269750,
"state": 0,
"memberCount": 0,
"isRRO": false,
"allowJuniors": true,
"minLevel": 0,
"visibility": 1,
"joinability": 0,
"clubhouseRoomId": null,
"clubType": 1,
"createdAt": "2025-08-03T13:18:44.3295131Z",
"clubChatEnabled": false
}
*/
}
}
+61
View File
@@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Mvc;
using Rec_rewild_live_rewrite.api.server;
namespace Rec_rewild_live_rewrite.Controllers
{
[Route("Commerce")]
public class CommerceController : Controller
{
[HttpGet("purchasecampaign/allcurrent/v2")]
public IActionResult GetAllCurrentPurchaseCampaignsV2()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpPost("purchase/v1/cleanuppending")]
public IActionResult CleanUpPendingPurchases()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpGet("api/catalog/v1/all")]
public async Task<IActionResult> GetAllCatalogItemsV1(bool onlyAvailableSkus = true)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "AllCatalog.json");
string content = await System.IO.File.ReadAllTextAsync(path);
return Ok(content);
}
[HttpGet("reminder/currentTokenBundles/v2")]
public IActionResult GetCurrentTokenBundlesV2()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpGet("purchaseRestriction/isplayerrestricted")]
public IActionResult IsPlayerRestricted()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Content("false", "application/json");
}
}
}
+31
View File
@@ -0,0 +1,31 @@
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.server;
namespace Rec_rewild_live_rewrite.Controllers
{
[Route("/data/")]
public class DataController : ControllerBase
{
[HttpPost("event")]
public IActionResult CollectEvent()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok();
}
[HttpPost("heartbeat")]
public IActionResult CollectHeartbeat()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok();
}
}
}
+102
View File
@@ -0,0 +1,102 @@
using Microsoft.AspNetCore.Mvc;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
using System.Net.Http.Headers;
using System.Text.Json;
namespace Rec_rewild_live_rewrite.Controllers
{
[ApiController]
[Route("api/auth")]
public class DiscordController : Controller
{
private const string ClientId = "1449223976751464501";
private const string ClientSecret = "cwy1iuG6Aik0NtNf4hjchSaLlCcVqgmg";
private const string RedirectUri = "https://recrewild.oldrec.net/api/auth/discord/callback";
[HttpGet("discord")]
public IActionResult DiscordLogin()
{
var url =
"https://discord.com/api/oauth2/authorize" +
$"?client_id={ClientId}" +
$"&redirect_uri={Uri.EscapeDataString(RedirectUri)}" +
"&response_type=code" +
"&scope=identify email";
return Redirect(url);
}
[HttpGet("discord/callback")]
public async Task<IActionResult> DiscordCallback([FromQuery] string code)
{
if (string.IsNullOrEmpty(code))
return BadRequest("Missing OAuth code.");
using var http = new HttpClient();
// Exchange code for access token
var tokenResponse = await http.PostAsync(
"https://discord.com/api/oauth2/token",
new FormUrlEncodedContent(new Dictionary<string, string>
{
["client_id"] = ClientId,
["client_secret"] = ClientSecret,
["grant_type"] = "authorization_code",
["code"] = code,
["redirect_uri"] = RedirectUri
})
);
if (!tokenResponse.IsSuccessStatusCode)
return Unauthorized("OAuth token exchange failed.");
var tokenJson = await tokenResponse.Content.ReadAsStringAsync();
var token = JsonDocument.Parse(tokenJson)
.RootElement.GetProperty("access_token").GetString();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var userJson = await http.GetStringAsync("https://discord.com/api/users/@me");
var user = JsonDocument.Parse(userJson).RootElement;
string discordId = user.GetProperty("id").GetString();
string username = user.GetProperty("username").GetString();
// Email may be null
string? email = null;
if (user.TryGetProperty("email", out var emailProp))
email = emailProp.GetString();
Console.WriteLine($"Discord Login: {username} ({discordId}) | Email: {email ?? "N/A"}");
// 🔐 Lookup account
var accountIds = PlayerDB.GetAccountIdsByDiscordId(ulong.Parse(discordId));
if (accountIds == null || !accountIds.Any())
{
return Unauthorized("Discord account not linked.");
}
var account = PlayerDB.GetFullAccount(accountIds.First());
if (account == null)
{
return Unauthorized("Account not found.");
}
// ✅ Issue JWT
string jwtToken = ClientSecurity.RefreshToken_Web(account.playerid);
Response.Cookies.Append("Authorization", jwtToken, new Microsoft.AspNetCore.Http.CookieOptions
{
Expires = DateTime.Now.AddHours(24),
HttpOnly = true,
Secure = true,
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict
});
return Redirect("/admin/home/v4");
}
}
}
+141
View File
@@ -0,0 +1,141 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.server.Classes;
namespace Rec_rewild_live_rewrite.Controllers
{
[ApiController]
public class EconController : ControllerBase
{
[HttpGet("/econ/customAvatarItems/v1/owned")]
public IActionResult OwnedCustomAvatarItems()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "customitems.json");
var stream = System.IO.File.OpenRead(path);
return new FileStreamResult(stream, "application/json");
}
[HttpGet("/econ/roomInventory/room/{roomId}")]
public IActionResult GetRoomInventoryForRoom(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpGet("/econ/roomInventory/room/{roomId}/player")]
public IActionResult GetRoomInventoryForPlayer(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpGet("/econ/roomInventoryItemTags/room/{roomId}")]
public IActionResult GetRoomInventoryItemTagsForRoom(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpGet("/econ/roomEconConfig/{roomId}")]
public IActionResult GetRoomEconConfig(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
RoomId = roomId,
EnableSortingTabs = false
});
}
[HttpGet("/econ/roomOffer/room/{roomId}")]
public IActionResult GetRoomOffer(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpGet("/econ/roomOffer/room/{roomId}/purchaseCounts")]
public IActionResult GetRoomOfferPurchaseCounts(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
value = Array.Empty<object>(),
success = true
});
}
[HttpGet("/econ/roomGiftDropShops/room/{roomId}")]
public IActionResult GetRoomGiftDropShops(ulong roomId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(ConfigRewild.Bracket);
}
[HttpPost("/econ/roomInventory/v2/award")]
public IActionResult AwardRoomInventory([FromBody] List<AwardRoomInventoryRequest> request)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
Result = 0,
PlayerRoomInventoryItem = new
{
AccountId = account.Id,
RoomInventoryItemId = request.FirstOrDefault().RoomInventoryItemOriginId,
RoomId = request.FirstOrDefault().RoomId,
RoomInventoryItemOriginId = request.FirstOrDefault().RoomInventoryItemOriginId,
Count = 0,
ConcurrencyCode = request.FirstOrDefault().ConcurrencyCodes.NewConcurrencyCode,
FirstReceivedAt = DateTime.UtcNow,
ModifiedAt = DateTime.UtcNow,
QuantityAwarded = request.FirstOrDefault().Quantity
}
});
}
public class AwardRoomInventoryRequest
{
public ConcurrencyCodes ConcurrencyCodes { get; set; }
public int Quantity { get; set; }
public ulong RoomId { get; set; }
public string RoomInventoryItemOriginId { get; set; }
}
public class ConcurrencyCodes
{
public string? CurrentConcurrencyCode { get; set; }
public string? NewConcurrencyCode { get; set; }
}
}
}
+165
View File
@@ -0,0 +1,165 @@
using Microsoft.AspNetCore.Mvc;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace Rec_rewild_live_rewrite.Controllers
{
public class ImagesController : Controller
{
[Route("/img/{*img_path}")]
public async Task<IActionResult> ImgServer(string img_path, int width = 0, int height = 0, string sig = "p1")
{
img_path = Uri.UnescapeDataString(img_path ?? "").TrimStart('/');
HttpContext.Response.Headers.Append("content-signature", "key-id=KEY:RSA:p1.rec.net; data=IWwe/pZ5vWWqNSkSM/54isgDxlZkdrP0sUrppKCbNktO2yCOTjq746xWiiLsueGuVcAGQqkjeRTimxolHckS/YXSYkEJxtiCXbLlsRia2DyAqtWVkGWsfczzFhp/56U66FVzolTspPCvjScOVlGO7dDIK7sJ+ndcRauWjsQsC6g3e7rUc6uwY099a6gy7sw6xr5BFZQSz8wg+fqyHYD/Sc4nQQVOTFZNNASqbJYhpNhEMXRnafCMuLl8a3mkGwvy3t4q2D/7SM48xrGZjEV47qNx1A91KCe28XVToFh4BzwEUU8nZ0d+KwV79MGarLo1cY8igc8FcoThKcovI4ClOg==");
HttpContext.Response.Headers.Append("Content-Disposition", $"inline; filename=\"{Path.GetFileName(img_path)}\"");
HttpContext.Response.Headers.Append("Access-Control-Allow-Origin", "*");
HttpContext.Response.Headers.Append("Access-Control-Allow-Headers", "*");
Response.Headers["Cache-Control"] = "public, max-age=14400";
HttpContext.Response.Headers.Append("Access-Control-Allow-Headers", "Content-Type, Authorization, Cache-Control");
HttpContext.Response.Headers.Append("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
var etag = $"\"{img_path.GetHashCode()}\"";
HttpContext.Response.Headers.Append("ETag", etag);
//return File(await System.IO.File.ReadAllBytesAsync(Path.Combine(Environment.CurrentDirectory, "Data", "Images", "imageee.jpg")), "image/png");
bool cropSquare = HttpContext.Request.Query.ContainsKey("cropsquare") &&
(HttpContext.Request.Query["cropsquare"].ToString().ToLower() == "true" ||
HttpContext.Request.Query["cropsquare"].ToString() == "1");
string[] paths = new[]
{
Path.Combine(Environment.CurrentDirectory, "Data", "Images", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "rro_images", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "rro_images", "base_rros", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "player_images", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "polaroid_images", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "DefaultImgs", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "rec_net", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "custom_shirt_imgs", img_path),
Path.Combine(Environment.CurrentDirectory, "Data", "Images", "LoadingScreenTips", img_path)
//Path.Combine(Environment.CurrentDirectory, "Data", "cdn_rooms", img_path)
};
foreach (var path in paths)
{
if (System.IO.File.Exists(path))
{
try
{
var imageBytes = await System.IO.File.ReadAllBytesAsync(path);
imageBytes = await ProcessImageAsync(imageBytes, cropSquare, width, height);
return File(imageBytes, "image/png");
}
catch
{
var fallbackBytes = await System.IO.File.ReadAllBytesAsync(path);
return File(fallbackBytes, "image/png");
}
}
}
string recNetLocalPath = Path.Combine(Environment.CurrentDirectory, "Data", "Images", "rec_net", img_path);
if (!System.IO.File.Exists(recNetLocalPath))
{
try
{
using HttpClient client = new();
byte[] data = await client.GetByteArrayAsync($"https://img.rec.net/{img_path}");
Directory.CreateDirectory(Path.GetDirectoryName(recNetLocalPath)!);
await System.IO.File.WriteAllBytesAsync(recNetLocalPath, data);
try
{
var processed = await ProcessImageAsync(data, cropSquare, width, height);
return File(processed, GetMimeType(img_path));
}
catch
{
return File(data, GetMimeType(img_path));
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($"[rec_net fetch failed] {ex.Message}");
}
}
else
{
var data = await System.IO.File.ReadAllBytesAsync(recNetLocalPath);
try
{
var processed = await ProcessImageAsync(data, cropSquare, width, height);
return File(processed, GetMimeType(img_path));
}
catch
{
return File(data, GetMimeType(img_path));
}
}
var notFoundImagePath = Path.Combine(Environment.CurrentDirectory, "Data", "Images", "rro_image", "DefaultRoomImage.jpg");
if (System.IO.File.Exists(notFoundImagePath))
{
var notFoundImageBytes = await System.IO.File.ReadAllBytesAsync(notFoundImagePath);
return File(notFoundImageBytes, "image/png");
}
return NotFound();
}
private static string GetMimeType(string filePath)
{
var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider();
if (!provider.TryGetContentType(filePath, out var contentType))
{
contentType = "image/png";
}
return contentType;
}
private static async Task<byte[]> ProcessImageAsync(byte[] imageBytes, bool cropSquare, int width, int height)
{
using var ms = new MemoryStream(imageBytes);
using var image = await Image.LoadAsync<Rgba32>(ms);
if (cropSquare)
{
int size = Math.Min(image.Width, image.Height);
int x = (image.Width - size) / 2;
int y = (image.Height - size) / 2;
image.Mutate(ctx => ctx.Crop(new Rectangle(x, y, size, size)));
int targetSize = width > 0 ? width : height;
if (targetSize > 0)
{
image.Mutate(ctx => ctx.Resize(targetSize, targetSize));
}
}
else if (width > 0 || height > 0)
{
int resizeWidth = width;
int resizeHeight = height;
if (width > 0 && height == 0)
{
resizeHeight = (int)((double)image.Height / image.Width * width);
}
else if (height > 0 && width == 0)
{
resizeWidth = (int)((double)image.Width / image.Height * height);
}
image.Mutate(ctx => ctx.Resize(resizeWidth, resizeHeight));
}
using var output = new MemoryStream();
await image.SaveAsync(output, new PngEncoder());
return output.ToArray();
}
}
}
+121
View File
@@ -0,0 +1,121 @@
using LiteDB;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server.Classes;
namespace Rec_rewild_live_rewrite.Controllers
{
[ApiController]
[Route("link")]
public class LinkController : ControllerBase
{
[HttpPost("actionlink")]
public ContentResult CreateActionLink()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
// data={"ExtraJson":"{"RoomId":9,"SubRoomId":10}","Platform":0,"Type":2}&validHours=2160&codeType=2&extraDataId=9
var data = Request.Form["data"].ToString();
var validHours = int.TryParse(Request.Form["validHours"], out var vh) ? vh : 24;
var codeType = int.TryParse(Request.Form["codeType"], out var ct) ? (LinkType)ct : LinkType.Unknown;
var extraDataId = int.TryParse(Request.Form["extraDataId"], out var edid) ? edid : 0;
var link = new ActionLink
{
creatorPlayerId = account.Id,
data = data,
CodeType = codeType,
ValidHours = vh,
ExtraDataId = edid,
isValid = true,
};
var actionlink = LinkDB.Insert(link);
string url = $"{Request.Scheme}://{Request.Host}";
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = url + "/link?code=" + actionlink.LinkId
}),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("actionlink/{link}")]
public ContentResult GetActionLink(string link)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var found = LinkDB.Get(link);
if (found == null)
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 404,
};
}
return new ContentResult
{
Content = JsonConvert.SerializeObject(found),
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("actionlink/{link}/consume")]
public ContentResult ConsumeActionLink(string link)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string formlink = Request.Form["id"].ToString(); // should be the same as link param
string codeTypestring = Request.Form["codeType"].ToString(); // enum
string newPlayer = Request.Form["newPlayer"].ToString(); // Can be False or True
string newInstall = Request.Form["newInstall"].ToString(); // Can be False or True
LinkType CodeType;
if (int.TryParse(codeTypestring, out int numericValue))
{
if (Enum.IsDefined(typeof(LinkType), numericValue))
{
CodeType = (LinkType)numericValue;
}
else
{
}
}
// Welp. This is very crappy. Redo this code when the game actually wants it.
var player = PlayerDB.GetInfluencerByCreatorCode(link);
if (player != null)
{
PlayerDB.SupportInfluencer(account.Id, player.player.Id);
}
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
success = true,
error = "",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
}
}
+466
View File
@@ -0,0 +1,466 @@
using Microsoft.AspNetCore.Mvc;
using Rec_rewild_live_rewrite.api.dbs;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using static Rec_rewild_live_rewrite.api.dbs.HeartbeatDB;
using Rec_rewild_live_rewrite.api.server;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using Rec_rewild_live_rewrite.api;
using static System.Runtime.InteropServices.JavaScript.JSType;
using static Rec_rewild_live_rewrite.api.server.Classes.RRPlus;
using Discord.Net;
using System.Net.Http.Headers;
namespace Rec_rewild_live_rewrite.Controllers
{
[Route("Matchmaking")]
public class MatchmakingController : ControllerBase
{
public RoomInstance DefaultSessionDorm = new RoomInstance
{
isFull = false,
isInProgress = false,
isPrivate = true,
location = "76d98498-60a1-430c-ab76-b54a29b7a163",
maxCapacity = 4,
name = "^" + "DormRoom",
roomId = 1,
roomInstanceId = 1,
roomInstanceType = 0,
voiceAuthId = null,
voiceServerId = null,
subRoomId = 1,
matchmakingPolicy = 0,
};
[HttpPost("player/login")]
[HttpPost("player/exclusivelogin")]
public async Task<ContentResult> PlayerLogin()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var relationships = FriendsDB.GetRelationships(account.Id);
List<ulong> mutualFriendIds = relationships.Where(r => r.RelationshipType == RelationshipType.Friend).Select(r => r.PlayerID).Distinct().ToList();
var message = new MessageData
{
FromPlayerId = (ulong)account.Id,
Id = (ulong)new Random().Next(),
SentTime = DateTime.UtcNow,
Type = WebsocketEvents.MessageType.FriendStatusOnline,
Data = "Online",
RoomId = 1
};
foreach (var friendId in mutualFriendIds)
{
if (SettingsDB.GetPlayerSettingValue("ReceiveFriendOnlineStatus", friendId) == "False")
{
mutualFriendIds.Remove(friendId);
}
}
var ws_message = JsonConvert.SerializeObject(WebsocketEvents.CreateMessageResponse(message));
await Notifications.SendToPlayers(mutualFriendIds, ws_message);
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
[Route("player/logout")]
public ContentResult PlayerLogout()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
HeartbeatDB.UpdatePlayerHeartbeat(account.Id, null, online: false);
Webhook.Send(account.Id,
"prod",
"Matchmaking info",
$"({account.Id}) @{account.Username} {account.DisplayName} is logging out of **2023**",
5898927
);
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPut("player/statusvisibility")]
public IActionResult StatusVisibility([FromForm] int statusVisibility)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
HeartbeatDB.UpdateStatusVisibility(account.Id, statusVisibility);
return Ok("");
}
[HttpPost("player/heartbeat")]
public IActionResult PlayerHeartbeat()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Content(System.Text.Json.JsonSerializer.Serialize(HeartbeatDB.GetPlayerHeartbeat(account.Id)), "application/json");
}
[HttpPost("matchmake/none")]
public ContentResult Matchmaking_none()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
HeartbeatDB.UpdatePlayerHeartbeat(account.Id, DefaultSessionDorm);
var CurrentHeartbeat = JsonConvert.SerializeObject(HeartbeatDB.GetPlayerHeartbeat(account.Id));
return new ContentResult()
{
Content = CurrentHeartbeat,
ContentType = "application/json",
StatusCode = 200
};
}
[HttpPost("matchmake/dorm")]
public IActionResult MatchmakeToDorm([FromForm] string correlationId)
{
string auth = HttpContext.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(auth))
return new ContentResult { Content = "", ContentType = "application/json", StatusCode = 401 };
string playerdata = ClientSecurity.DecodeToken(auth);
if (playerdata == null || playerdata.Contains("error_token"))
return new ContentResult { Content = "", ContentType = "application/json", StatusCode = 401 };
var account = JsonConvert.DeserializeObject<player_info>(playerdata);
string token = auth.Replace("Bearer ", "", StringComparison.OrdinalIgnoreCase);
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
int platformVal = int.Parse(jwt.Payload["platform"].ToString());
Platforms platform = Enum.IsDefined(typeof(Platforms), platformVal)
? (Platforms)platformVal
: Platforms.All;
var doesPlayerDormExist = RoomDB.DoesPlayerDormExist(account.Id);
if (doesPlayerDormExist == false)
{
PlayerDB.CreateDormForPlayerId(account.Id);
}
try
{
var session = Sessions.CreateDorm(account.Id, account.Username);
if (session == null)
{
var hb = HeartbeatDB.GetPlayerHeartbeat(account.Id);
return Ok(hb);
}
HeartbeatDB.UpdatePlayerHeartbeat(account.Id, session, true, platform, DeviceClasses.Unknown);
RoomDB.IncreaseRoomVisit((ulong)session.roomId);
Webhook.Send(
account.Id,
"prod",
"Matchmaking Info",
$"({account.Id}) @{account.Username} is going to their Dorm Room",
5898927,
"",
(ulong)session.roomId
);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return Content(System.Text.Json.JsonSerializer.Serialize(new FullRoomInstance
{
roomInstance = HeartbeatDB.GetPlayerHeartbeat(account.Id)?.roomInstance,
correlationId = correlationId
}), "application/json");
}
public class MatchmakeV2Request
{
public int? JoinMode { get; set; }
public string CorrelationId { get; set; }
}
[HttpPost("matchmake/v2/room/{RoomId}")]
public IActionResult MatchmakeRoom(ulong RoomId, [FromBody] MatchmakeV2Request request)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (PlayerDB.HasActiveModerationBlock(account.Id))
{
var heartbeat = HeartbeatDB.GetPlayerHeartbeat(account.Id);
return Ok(heartbeat);
}
var room = RoomDB.GetRoom(RoomId);
string roomName = room.Name ?? "";
ulong roomId = room.RoomId;
string roomIdentifier = $"^{roomName}";
string tmp = "";
bool isPrivateRoom = request.JoinMode == 2;
RoomInstance session = isPrivateRoom
? Sessions.CreateRoom(roomName, "", true)
: Sessions.CreateRoom(roomName);
if (isPrivateRoom)
{
tmp = "[PRIVATE INSTANCE]";
}
RoomDB.IncreaseRoomVisit(roomId);
PlayerDB.SetRecentRooms(account.Id, roomId);
if (session == null)
{
var heartbeat = JsonConvert.SerializeObject(HeartbeatDB.GetPlayerHeartbeat(account.Id));
return Ok(heartbeat);
}
HeartbeatDB.UpdatePlayerHeartbeat(account.Id, session);
var CurrentHeartbeat = JsonConvert.SerializeObject(HeartbeatDB.GetPlayerHeartbeat(account.Id));
Webhook.Send(
account.Id,
"prod",
"Matchmaking Info",
$"({account.Id}) @{account.Username} is going to ^{roomName} {tmp}",
5898927,
"",
roomId
);
return Content(System.Text.Json.JsonSerializer.Serialize(new FullRoomInstance
{
roomInstance = HeartbeatDB.GetPlayerHeartbeat(account.Id)?.roomInstance,
correlationId = request.CorrelationId
}), "application/json");
}
[HttpPost("matchmake/v2/room/{RoomId}/{SubRoomId}")]
public IActionResult MatchmakeRoom(ulong RoomId, ulong SubRoomId, [FromBody] MatchmakeV2Request request)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (PlayerDB.HasActiveModerationBlock(account.Id))
{
var heartbeat = HeartbeatDB.GetPlayerHeartbeat(account.Id);
return new ContentResult
{
Content = JsonConvert.SerializeObject(new
{
heartbeat.playerId,
heartbeat.appVersion,
heartbeat.deviceClass,
errorCode = MatchmakingErrorCode.Banned,
heartbeat.isOnline,
heartbeat.lastOnline,
heartbeat.platform,
roomInstance = (string?)null,
heartbeat.statusVisibility,
heartbeat.vrMovementMode
}),
ContentType = "application/json",
StatusCode = 200
};
}
var room = RoomDB.GetRoom(RoomId);
string roomName = room.Name ?? "";
ulong roomId = room.RoomId;
string roomIdentifier = $"^{roomName}";
string subroomname = RoomDB.get_subroom_from_id(RoomId, SubRoomId);
string tmp = "";
bool isPrivateRoom = request.JoinMode == 2;
RoomInstance session = isPrivateRoom
? Sessions.CreateRoom(roomName, subroomname, true)
: Sessions.CreateRoom(roomName, subroomname);
if (isPrivateRoom)
{
tmp = "[PRIVATE INSTANCE]";
}
RoomDB.IncreaseRoomVisit(roomId);
PlayerDB.SetRecentRooms(account.Id, roomId);
if (session == null)
{
var heartbeat = JsonConvert.SerializeObject(HeartbeatDB.GetPlayerHeartbeat(account.Id));
return new ContentResult
{
Content = JsonConvert.SerializeObject(heartbeat),
ContentType = "application/json",
StatusCode = 200
};
}
HeartbeatDB.UpdatePlayerHeartbeat(account.Id, session);
var CurrentHeartbeat = JsonConvert.SerializeObject(HeartbeatDB.GetPlayerHeartbeat(account.Id));
Webhook.Send(
account.Id,
"prod",
"Matchmaking Info",
$"({account.Id}) @{account.Username} is going to ^{roomName}.{subroomname} {tmp}",
5898927,
"",
roomId
);
return Content(System.Text.Json.JsonSerializer.Serialize(new FullRoomInstance
{
roomInstance = HeartbeatDB.GetPlayerHeartbeat(account.Id)?.roomInstance,
correlationId = request.CorrelationId
}), "application/json");
}
[HttpGet("rooms/requiring/{requirement}")]// ["RecCenter", "test123"]
public ContentResult RoomsRequiring(string requirement)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
requirement = requirement?.ToLowerInvariant();
string jsonResponse = requirement switch
{
"developer" => "[]",
"rrplus" => "[]",
_ => "[]"
};
return new ContentResult
{
Content = jsonResponse,
ContentType = "application/json",
StatusCode = 200
};
}
[HttpGet("player")]
public IActionResult GetPlayerHeartbeatById([FromQuery] List<ulong> id)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (id == null || id.Count == 0)
{
return Ok(ConfigRewild.Bracket);
}
var heartbeats = new List<object>();
foreach (var playerId in id)
{
var heartbeat = HeartbeatDB.GetPlayerHeartbeat(playerId);
if (heartbeat != null)
heartbeats.Add(heartbeat);
}
return Ok(heartbeats);
}
[HttpGet("player/qos")]
public async Task<IActionResult> GetPlayerQos()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "QOS.json");
string content = await System.IO.File.ReadAllTextAsync(path);
return Ok(content);
}
[HttpGet("player/connection-info")]
public IActionResult GetPlayerConnectionInfo([FromQuery] ulong roomInstanceId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var heartbeat = HeartbeatDB.GetPlayerHeartbeat(account.Id);
return Ok(new
{
success = true,
value = new
{
photonAuthToken = ClientSecurity.Createphotonauthtoken(account.Id),// "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxNTAzMjY5NzUwIiwicm4ucGxhdGlkIjoiNzY1NjExOTk2MTM4Mzg5MjgiLCJybi5wbGF0IjoiMCIsInJuLmRldmljZWNsYXNzIjoiMiIsInJuLmVudiI6InByb2QiLCJleHAiOjE3NTQzMTYzNTMsImF1ZCI6ImM4YWFhODYwLTFjMGMtNDMzOC1iYWYyLWE1NTNjMDFiMmE1MyJ9.qY_O0tNeZeH2QKuPUIQPhRPnOpp2uhZn3b8eo-ybV3w",
photonRealtimeAppId = ConfigRewild.PhotonRealtimeId,
photonVoiceAppId = ConfigRewild.PhotonVoiceId,
photonChatAppId = ConfigRewild.PhotonChatId,
photonRegion = "us", // qos us-east1
photonRoomId = heartbeat.roomInstance.photonRoomIdYeee,// "abcdefgh-ijkl-mnop-qrst-upwxyz123456",
voiceConnectionInfo = (string?)null,
voiceServerId = (string?)null,
experiments = new
{
networkTransformSyncInterval = 10.0,
shouldUseUnreliableOnChange = false,
shouldAvoidDiscontinuityRPCs = true,
shouldAvoidRedundantDiscontinuity = false,
r2RuntimeStaticBaking = true,
r2AutoEmbodiment = true,
r2RuntimeStaticBakingMinShapeThreshold = 1,
r2UseCheapReplicas = true,
shouldUseGameServerNetworking = false // true make it connect to 127.0.0.1:7777 qos, false it connects to photon servers ns.exitgames.com
}
}
});
}
[HttpPut("player/gameserverregionpings")]
public IActionResult UpdatePlayerGameServerRegionPings()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok("");
}
[HttpPost("roominstance/{roomInstanceId}/reportjoinresult")]
public IActionResult ReportJoinResultForRoomInstanceId(ulong roomInstanceId)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok();
}
}
}
+248
View File
@@ -0,0 +1,248 @@
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Concurrent;
using Newtonsoft.Json;
namespace RewildLiveSource.Controllers
{
[ApiController]
[Route("Notifications")]
public class NotificationsController : Controller
{
public static ConcurrentDictionary<ulong, WebSocket> WebSockets { get; } = new();
// ===========================================================
// Negotiation endpoint
// ===========================================================
[HttpPost("hub/v1/negotiate")]
public ContentResult Negotiate()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var response = new
{
negotiateVersion = 0,
connectionId = account.Id.ToString(),
availableTransports = new[]
{
new {
transport = "WebSockets",
transferFormats = new[] { "Text", "Binary" }
}
}
};
return JsonResult(response);
}
// ===========================================================
// Notification preferences/config
// ===========================================================
[HttpGet("preferences")]
public IActionResult GetPreferences()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return Ok(new
{
mutedCategories = Array.Empty<object>()
});
}
[HttpGet("crm/me/config/v3")]
public ContentResult GetCRMConfig()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return JsonResult(Array.Empty<object>());
}
[HttpGet("accounts/{id}/receives/GameplayInvites")]
public ContentResult GetGameplayInvites(ulong id)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
return JsonResult(true);
}
[HttpGet("config/categories")]
public IActionResult GetCategories()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string path = Path.Combine(Environment.CurrentDirectory, "Data", "APIS", "NotiConfigCategories.json");
if (!System.IO.File.Exists(path))
{
Console.WriteLine("Warning: Notification config json file not found, returning []");
return Ok(new
{
results = Array.Empty<object>(),
totalResults = 0
});
}
return PhysicalFile(path, "application/json");
}
// ===========================================================
// WebSocket handler
// ===========================================================
[HttpGet("hub/v1")]
public async Task HandleHub([FromQuery] string id)
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
{
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
if (!ulong.TryParse(id, out var playerId))
{
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
using var socket = await HttpContext.WebSockets.AcceptWebSocketAsync();
Console.WriteLine($"[WebSocket] Player {playerId} connected.");
await HandleConnectionAsync(playerId, socket);
}
private static async Task HandleConnectionAsync(ulong playerId, WebSocket socket)
{
using var pingCts = new CancellationTokenSource();
try
{
await CloseExistingConnection(playerId);
WebSockets[playerId] = socket;
await SendHandshakeAsync(socket);
// Start ping loop
_ = Task.Run(() => PingLoopAsync(socket, pingCts.Token));
var buffer = new byte[4096];
while (socket.State == WebSocketState.Open)
{
var result = await socket.ReceiveAsync(buffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
break;
var message = Encoding.UTF8.GetString(buffer, 0, result.Count).TrimEnd('\x1e');
Console.WriteLine($"Player ({playerId}) sent {message}");
await HandleClientMessageAsync(socket, message);
}
}
catch (Exception ex)
{
Console.WriteLine($"[Error:{playerId}] {ex.Message}");
}
finally
{
pingCts.Cancel();
WebSockets.TryRemove(playerId, out _);
if (socket.State is WebSocketState.Open or WebSocketState.CloseReceived)
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection", CancellationToken.None);
}
Console.WriteLine($"[WebSocket] Player {playerId} disconnected.");
}
}
private static async Task HandleClientMessageAsync(WebSocket socket, string message)
{
try
{
using var doc = JsonDocument.Parse(message);
var root = doc.RootElement;
if (root.TryGetProperty("type", out var typeProp) && typeProp.GetInt32() == 1)
{
string target = root.GetProperty("target").GetString() ?? string.Empty;
string invocationId = root.GetProperty("invocationId").GetString() ?? string.Empty;
if (target == "SubscribeToPlayers")
{
var response = new
{
type = 3,
invocationId,
result = (object?)null
};
await SendJsonAsync(socket, response);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[ParseError] {ex.Message}");
}
}
private static async Task SendHandshakeAsync(WebSocket socket)
{
var handshake = new { protocol = "json", version = 1 };
await SendJsonAsync(socket, handshake);
}
private static async Task PingLoopAsync(WebSocket socket, CancellationToken token)
{
try
{
while (!token.IsCancellationRequested && socket.State == WebSocketState.Open)
{
var ping = new { type = 6 };
await SendJsonAsync(socket, ping);
await Task.Delay(10000, token);
}
}
catch (TaskCanceledException) { }
catch (Exception ex)
{
Console.WriteLine($"[PingError] {ex.Message}");
}
}
private static async Task CloseExistingConnection(ulong playerId)
{
if (WebSockets.TryRemove(playerId, out var oldSocket) && oldSocket.State == WebSocketState.Open)
{
await oldSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Replaced by new connection", CancellationToken.None);
Console.WriteLine($"[WebSocket] Closed existing connection for {playerId}");
}
}
private static async Task SendJsonAsync(WebSocket socket, object obj)
{
var json = JsonConvert.SerializeObject(obj) + "\x1e";
var bytes = Encoding.UTF8.GetBytes(json);
await socket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
}
private static ContentResult JsonResult(object data) => new()
{
Content = JsonConvert.SerializeObject(data),
ContentType = "application/json",
StatusCode = 200
};
}
}
+10
View File
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Rec_rewild_live_rewrite.Controllers
{
public class RewildDllController : Controller
{
}
}
+164
View File
@@ -0,0 +1,164 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using Rec_rewild_live_rewrite.api.server;
using Rec_rewild_live_rewrite.api.dbs;
using Newtonsoft.Json.Linq;
namespace Rec_rewild_live_rewrite.Controllers
{
public class RewildNetController : Controller
{
[Route("/rewild_net/online_players")]
public ContentResult online_players()
{
return new ContentResult
{
Content = System.IO.File.ReadAllText("Web\\Rewild_Net\\OnlinePlayers.html"),
ContentType = "text/html"
};
}
[Route("/rewild_net/home")]
public ContentResult RewildNetHome()
{
try
{
string authtoken = HttpContext.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(authtoken))
{
authtoken = HttpContext.Request.Cookies["Authorization"];
}
if (string.IsNullOrEmpty(authtoken))
{
Redirect("/rewild_net/login");
}
string playerdata = ClientSecurity.DecodeToken(authtoken);
if (playerdata == null)
Redirect("/rewild_net/login");
if (playerdata.Contains("error_token"))
Redirect("/rewild_net/login");
player_info? account = JsonConvert.DeserializeObject<player_info>(playerdata);
return new ContentResult
{
Content = System.IO.File.ReadAllText("Web\\Rewild_Net\\Home.html"),
ContentType = "text/html"
};
}
catch
{
return new ContentResult
{
Content = System.IO.File.ReadAllText("Web\\Rewild_Net\\Login.html"),
ContentType = "text/html"
};
}
}
[Route("/rewild_net/login")]
public ContentResult login()
{
return new ContentResult
{
Content = System.IO.File.ReadAllText("Web\\Rewild_Net\\Login.html"),
ContentType = "text/html"
};
}
[Route("/rewild_net/logout")]
public ContentResult logout()
{
Response.Cookies.Append("Authorization", "", new Microsoft.AspNetCore.Http.CookieOptions
{
Expires = DateTime.Now,
HttpOnly = true,
Secure = true,
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict
});
return new ContentResult
{
Content = System.IO.File.ReadAllText("Web\\Rewild_Net\\Login.html"),
ContentType = "text/html"
};
}
/*[HttpPost("/api/login")]
public ContentResult Login()
{
try
{
string? username = Request.Form["username"];
string? password = Request.Form["password"];
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
return new ContentResult
{
Content = "Your username or password is empty, please enter a username and password",
ContentType = "text/html"
};
}
var loginResult = PlayerDB.GetAccountPassword(username, password);
if (loginResult == null)
{
return new ContentResult
{
Content = "Invalid password or username, please use your @ name to login.",
ContentType = "text/html"
};
}
account_login<account_data> temp = PlayerDB.GetAccountPassword(username, password);
string token = ClientSecurity.RefreshToken_Web(temp.Value.accountId);
Response.Cookies.Append("Authorization", token, new Microsoft.AspNetCore.Http.CookieOptions
{
Expires = DateTime.Now.AddDays(1),
HttpOnly = true,
Secure = true,
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict
});
Response.Redirect($"/rewild_net/home");
return new ContentResult
{
Content = JsonConvert.SerializeObject(temp),
ContentType = "text/html"
};
}
catch
{
return new ContentResult
{
Content = "Invalid username or password. Please try again.",
ContentType = "text/html"
};
}
}*/
[HttpGet("/api/images/v3/feed/global")]
public ContentResult GetGlobalFeedImages()
{
var images = ImageMetadataDB.GetGlobalImages();
return new ContentResult
{
Content = JsonConvert.SerializeObject(images),
ContentType = "application/json",
StatusCode = 200
};
}
}
}
File diff suppressed because it is too large Load Diff
+386
View File
@@ -0,0 +1,386 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.dbs;
using Rec_rewild_live_rewrite.api.server;
using System.ComponentModel.DataAnnotations;
using System.Text;
using static Rec_rewild_live_rewrite.api.dbs.HeartbeatDB;
using static Rec_rewild_live_rewrite.api.server.Classes.db_class.LeaderboardDBClasses;
using static Rec_rewild_live_rewrite.api.server.Images;
namespace Rec_rewild_live_rewrite.Controllers
{
[Route("server")]
public class PlayerSettingsController : ControllerBase
{
[HttpGet("PlayerSettings/playersettings")]
public IActionResult GetPlayerSettings()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var settings = SettingsDB.LoadSettings(account.Id);
return Ok(JsonConvert.SerializeObject(settings));
}
[HttpPut("PlayerSettings/playersettings")]
public IActionResult UpdatePlayerSetting([FromForm, Required] string key, [FromForm, Required] string value)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (string.IsNullOrWhiteSpace(key))
return BadRequest("");
SettingsDB.SetPlayerSetting(key, value ?? "", account.Id);
return Ok(new
{
success = true,
errorId = (string?)null,
error = (string?)null
});
}
[HttpDelete("PlayerSettings/playersettings")]
public IActionResult UpdatePlayerSetting([FromForm, Required] string key)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
if (string.IsNullOrWhiteSpace(key))
return BadRequest("");
SettingsDB.DeletePlayerKey(key ?? "", account.Id);
return Ok(new
{
success = true,
errorId = (string?)null,
error = (string?)null
});
}
[HttpPost("Storage/upload")]
public async Task<ContentResult> server_storage_upload([FromForm] FileUploadModel model)
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
var ua = Request.Headers["User-Agent"].ToString();
if (ua.Contains("PostmanRuntime"))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
if (PlayerDB.HasActiveModerationBlock(account.Id))
{
return new ContentResult
{
Content = "",
ContentType = "application/json",
StatusCode = 403
};
}
try
{
if (model?.File == null || model.File.Length <= 0)
{
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 400
};
}
byte[] array = Array.Empty<byte>();
MemoryStream stream = new MemoryStream();
await model.File.CopyToAsync(stream);
array = stream.ToArray();
FileType_data data_type = model.FileType;
try
{
var fileResult = Images.SaveFileUnifiedv2(array, data_type);
if (fileResult != null && fileResult.saved)
{
if (fileResult.FileType == FileType_data.Image)
{
_ = Task.Run(() => Webhook.Send(
account.Id, "prod", "Server info",
$"({account.Id}) @{account.Username} has uploaded an polaroid image",
5898927, $"https://recrewild.oldrec.net/img/{fileResult.rawfilename}"
));
}
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
filename = fileResult.FileName,
Hash = "e", // Not needed for game, game expect it so hardcode
OwnershipProof = "e" // Not needed for game, game expect it so hardcode
}, Formatting.Indented),
ContentType = "application/json",
StatusCode = 200
};
}
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
success = false,
error = "Failed to upload!",
value = (string?)null
}),
ContentType = "application/json",
StatusCode = 200
};
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
filename = (string?)null,
Hash = (string?)null,
OwnershipProof = (string?)null,
}, Formatting.Indented),
ContentType = "application/json",
StatusCode = 200
};
}
}
catch (Exception e)
{
Console.WriteLine(e);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
filename = (string?)null,
Hash = (string?)null,
OwnershipProof = (string?)null,
}, Formatting.Indented),
ContentType = "application/json",
StatusCode = 200
};
}
}
[HttpPost("Leaderboard/leaderboard/GetNearbyScores")]
public async Task<ContentResult> GetNearbyScores()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string body = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var req = JsonConvert.DeserializeObject<GetNearbyScoresRequest>(body);
if (req == null)
throw new Exception("Invalid");
var rows = LeaderboardDB.GetNearbyScores(
req.PlayerId,
req.StatChannel,
req.RoomId,
req.WindowSize
);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new { Rows = rows }),
ContentType = "application/json",
StatusCode = 200
};
}
catch
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new { Rows = new List<Entry>() }),
ContentType = "application/json",
StatusCode = 200
};
}
}
[HttpPost("Leaderboard/leaderboard/GetPlayerRank")]
public async Task<ContentResult> GetPlayerRank()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string body = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var req = JsonConvert.DeserializeObject<GetNearbyScoresRequest>(body);
if (req == null)
throw new Exception("Invalid");
var rank = LeaderboardDB.GetPlayerRank(
req.PlayerId,
req.StatChannel,
req.RoomId
);
if (rank == null)
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
PlayerId = account.Id,
Score = (string?)null,
Rank = (string?)null,
}),
ContentType = "application/json",
StatusCode = 200
};
}
return new ContentResult()
{
Content = JsonConvert.SerializeObject(rank),
ContentType = "application/json",
StatusCode = 200
};
}
catch (Exception ex)
{
Console.WriteLine(ex);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new
{
PlayerId = account.Id,
Score = (string?)null,
Rank = (string?)null,
}),
ContentType = "application/json",
StatusCode = 200
};
}
}
[HttpPost("Leaderboard/leaderboard/CheckAndSetStat")]
public async Task<ContentResult> CheckAndSetStat()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string body = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var req = JsonConvert.DeserializeObject<CheckAndSetStatRequest>(body);
if (req == null)
throw new Exception("Invalid");
var room = RoomDB.GetRoom(req.RoomId);
var result = LeaderboardDB.SetStat(
account.Id,
req.StatChannel,
req.RoomId,
req.StatValue
);
Webhook.Send(
account.Id,
"prod",
"Leaderboard Info",
$"Player @{account.Username} ({account.Id}) has updated their score to {req.StatValue} in {(room != null ? room.Name : "Unknown Room")}",
5898927
);
// Rec Room returns simple enum int back. We'll return Success = 2.
return new ContentResult()
{
Content = ((int)result).ToString(),
ContentType = "application/json",
StatusCode = 200
};
}
catch (Exception ex)
{
Console.WriteLine(ex);
return new ContentResult()
{
Content = "",
ContentType = "application/json",
StatusCode = 400
};
}
}
[HttpPost("Leaderboard/leaderboard/GetRanks")]
public async Task<ContentResult> GetRanks()
{
var (account, error) = AuthHelper.ValidateToken(HttpContext);
if (error != null)
return error;
string body = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var req = JsonConvert.DeserializeObject<GetRanksRequest>(body);
if (req == null)
throw new Exception("Invalid");
var rows = LeaderboardDB.GetRanks(
req.StatChannel,
req.RoomId,
req.RankStart,
req.RankEnd
);
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new { Rows = rows }),
ContentType = "application/json",
StatusCode = 200
};
}
catch (Exception ex)
{
return new ContentResult()
{
Content = JsonConvert.SerializeObject(new { Rows = new List<Entry>() }),
ContentType = "application/json",
StatusCode = 200
};
}
}
}
}
+96
View File
@@ -0,0 +1,96 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Rec_rewild_live_rewrite.api.server.Classes.db_class;
using Rec_rewild_live_rewrite.api.server;
using System.ComponentModel.DataAnnotations;
namespace Rec_rewild_live_rewrite.Controllers
{
public class WebController : ControllerBase
{
[Route("/")]
public IActionResult Index()
{
var filePath = Path.Combine(Environment.CurrentDirectory, "Web", "Index.html");
if (!System.IO.File.Exists(filePath))
return NotFound();
return PhysicalFile(filePath, "text/html");
}
[Route("/robots.txt")]
public IActionResult Robots()
{
var content = string.Join(Environment.NewLine, new[]
{
"User-agent: *",
"Disallow: /admin/v4/login",
"Disallow: /admin/v4/home",
"Disallow: /admin/v5/login",
"Disallow: /admin/v5/home"
});
return Content(content, "text/plain");
}
[HttpGet("/favicon.ico")]
public IActionResult Favicon()
{
var filePath = Path.Combine(Environment.CurrentDirectory, "Web", "favicon.png");
if (!System.IO.File.Exists(filePath))
return NotFound();
return PhysicalFile(filePath, "image/x-icon");
}
[HttpGet("/ns")]
public IActionResult ns([FromQuery(Name = "v"), Required] string Version)
{
if (Version == "20250731")
{
return Ok(new
{
Accounts = "https://recrewildtest.oldrec.net/Accounts",
API = "https://recrewildtest.oldrec.net",
AI = "https://recrewildtest.oldrec.net/Ai",
Auth = "https://recrewildtest.oldrec.net/Auth",
BugReporting = "https://recrewildtest.oldrec.net",
Cards = "https://recrewildtest.oldrec.net",
CDN = "https://cdn.rec.net",//"https://recrewildtest.oldrec.net/cdn",
Chat = "https://recrewildtest.oldrec.net/chat",
Clubs = "https://recrewildtest.oldrec.net/clubs",
CMS = "https://recrewildtest.oldrec.net/cms",
Commerce = "https://recrewildtest.oldrec.net/Commerce",
Data = "https://recrewildtest.oldrec.net",
DataCollection = "https://recrewildtest.oldrec.net",
Discovery = "https://recrewildtest.oldrec.net",
Econ = "https://recrewildtest.oldrec.net",
GameLogs = "https://recrewildtest.oldrec.net",
Geo = "https://recrewildtest.oldrec.net",
Images = "https://recrewildtest.oldrec.net/img/",
Leaderboard = "https://recrewildtest.oldrec.net/server/Leaderboard",
Link = "https://recrewildtest.oldrec.net/link",
Lists = "https://recrewildtest.oldrec.net/lists",
Matchmaking = "https://recrewildtest.oldrec.net/Matchmaking",
Moderation = "https://recrewildtest.oldrec.net",
Notifications = "https://recrewildtest.oldrec.net/Notifications",
PlatformNotifications = "https://recrewildtest.oldrec.net/Notifications",
PlayerSettings = "https://recrewildtest.oldrec.net/server/PlayerSettings",
RoomComments = "https://recrewildtest.oldrec.net/RoomComments",
RoomieIntegrations = "https://recrewildtest.oldrec.net/Roomie",
Rooms = "https://recrewildtest.oldrec.net/Room_server",
Storage = "https://recrewildtest.oldrec.net/server/Storage",
Strings = "https://recrewildtest.oldrec.net/Strings",
StringsCDN = "https://recrewildtest.oldrec.net/StringsCDN",
Studio = "https://recrewildtest.oldrec.net/Studio",
Thorn = "https://recrewildtest.oldrec.net/Thorn",
Videos = "https://recrewildtest.oldrec.net/videodir",
WWW = "https://recrewildtest.oldrec.net"
});
}
return BadRequest("");
}
}
}
+20644
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+647
View File
@@ -0,0 +1,647 @@
[
{
"skuId": 178,
"name": "500 Tokens",
"description": "",
"imageName": "0j3qprcooizhw1fw37vbcxj5b.png",
"price": 99,
"oculusSkuId": "TK0007",
"appleProductId": "iTK007",
"googlePlaySkuId": "tk0007",
"picoSkuId": "TK007",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
14248
],
"message": "500 Tokens"
}
},
{
"skuId": 184,
"name": "Special Offer 9",
"description": "",
"imageName": "6lp54v2yy3te4xd4ilbl9gq1f.png",
"price": 99,
"oculusSkuId": "CO1009",
"xboxProductId": "43474e39-5143-3035-c04c-5a47484c5b00",
"xboxStoreId": "9NGCCQ5LZGHL",
"appleProductId": "CO0009",
"googlePlaySkuId": "co0009",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 9"
}
},
{
"skuId": 182,
"name": "Techwear Samurai Bundle",
"description": "Techwear Samurai Bundle",
"imageName": "dcsac8vyi7dewkwwh9uobi422.png",
"price": 99,
"oculusSkuId": "TK0007",
"appleProductId": "iTK007",
"googlePlaySkuId": "tk0007",
"picoSkuId": "TK007",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Techwear Samurai Bundle"
}
},
{
"skuId": 183,
"name": "Maker AI Timed Access One Day",
"description": "Grants one day of Maker AI access, timer starts after purchase completion",
"imageName": "0b8qpn5ghc8wtwqnnb7fpmcwz.png",
"price": 199,
"oculusSkuId": "MD0001",
"xboxProductId": "4a464e39-5835-3053-c046-475a53528300",
"xboxStoreId": "9NFJ5XSFGZSR",
"appleProductId": "MD0001",
"googlePlaySkuId": "md0001",
"picoSkuId": "MD0001",
"isSingleUse": false,
"shouldAppearInTokenStore": true,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "One day of Maker AI access granted!"
}
},
{
"skuId": 185,
"name": "Special Offer 10",
"description": "",
"imageName": "d8sqdpzxny5sgoebk3wfl976c.png",
"price": 199,
"oculusSkuId": "CO0010",
"xboxProductId": "48544e39-4647-304e-c033-325642524d00",
"xboxStoreId": "9NTHGFN32VBR",
"appleProductId": "CO0010",
"googlePlaySkuId": "co0010",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 10"
}
},
{
"skuId": 179,
"name": "1,000 Tokens",
"description": "",
"imageName": "535sxig13awyltiiu1tpc28em.png",
"price": 199,
"oculusSkuId": "TK0008",
"appleProductId": "iTK008",
"googlePlaySkuId": "tk0008",
"picoSkuId": "TK008",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
14249
],
"message": "1,000 Tokens"
}
},
{
"skuId": 23,
"name": "Special Offer 1",
"description": "",
"imageName": "33q0sd57vofyqyd8559zud6s5.png",
"price": 299,
"oculusSkuId": "CO0001",
"xboxProductId": "57434e39-5137-3051-c04e-3631384e3500",
"xboxStoreId": "9NCW7QQN618N",
"appleProductId": "CO0001",
"googlePlaySkuId": "co0001",
"picoSkuId": "CO001",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 1"
}
},
{
"skuId": 177,
"name": "1,500 Tokens",
"description": "",
"imageName": "bsvmr0bvc8koxe19a731sy2jt.png",
"price": 299,
"oculusSkuId": "TK0005",
"xboxProductId": "56334e39-5348-3050-c035-533647373700",
"xboxStoreId": "9N3VHSP5S6G7",
"appleProductId": "iTK005",
"googlePlaySkuId": "tk0005",
"picoSkuId": "TK005",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
4463
],
"message": "1,500 Tokens"
}
},
{
"skuId": 186,
"name": "Special Offer 11",
"description": "",
"imageName": "5msy6rgvumgv64hi25bpe9zef.png",
"price": 399,
"oculusSkuId": "CO0011",
"xboxProductId": "504c4e39-5051-304a-c058-44525054a000",
"xboxStoreId": "9NLPQPJXDRPT",
"appleProductId": "CO0011",
"googlePlaySkuId": "co0011",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 11"
}
},
{
"skuId": 180,
"name": "2,000 Tokens",
"description": "",
"imageName": "89brexfrkyxx7a1fqtb7vimtp.png",
"price": 399,
"oculusSkuId": "TK0009",
"appleProductId": "iTK009",
"googlePlaySkuId": "tk0009",
"picoSkuId": "TK009",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
14250
],
"message": "2,000 Tokens"
}
},
{
"skuId": 181,
"name": "Techwear Samurai Bundle",
"description": "Techwear Samurai Bundle",
"imageName": "dmrxioogofer7d7djcvvt6l6o.png",
"price": 499,
"oculusSkuId": "TK0001",
"psnProductLabel": "TK00010000000000",
"psnEntitlementLabel": "TK0001",
"xboxProductId": "4e444e39-5651-3046-c044-33484b374700",
"xboxStoreId": "9NDNQVFD3HK7",
"appleProductId": "iTK001",
"googlePlaySkuId": "tk0001",
"picoSkuId": "TK001",
"nintendoSkuId": "TK0001",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Techwear Samurai Bundle"
}
},
{
"skuId": 2,
"name": "2,500 Tokens",
"description": "",
"imageName": "c8nb2o470fwyyiccr4fuiv4k9.png",
"price": 499,
"oculusSkuId": "TK0001",
"psnProductLabel": "TK00010000000000",
"psnEntitlementLabel": "TK0001",
"xboxProductId": "4e444e39-5651-3046-c044-33484b374700",
"xboxStoreId": "9NDNQVFD3HK7",
"appleProductId": "iTK001",
"googlePlaySkuId": "tk0001",
"picoSkuId": "TK001",
"nintendoSkuId": "TK0001",
"isSingleUse": false,
"shouldAppearInTokenStore": true,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
2632
],
"message": "2,500 Tokens"
}
},
{
"skuId": 24,
"name": "Special Offer 2",
"description": "",
"imageName": "0snwnkn7l94klxot4hkha93ot.png",
"price": 499,
"oculusSkuId": "CO0002",
"xboxProductId": "32574e39-3250-304d-c048-445242363500",
"xboxStoreId": "9NW2P2MHDRB6",
"appleProductId": "CO0002",
"googlePlaySkuId": "co0002",
"picoSkuId": "CO002",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 2"
}
},
{
"skuId": 10,
"name": "Starter Pack",
"description": "5,500 Tokens\r\nPizza, Pop, & a Potion\r\nExclusive White Hoodie",
"imageName": "7jeywaubvdew56wlq2bazbeof.png",
"price": 499,
"oculusSkuId": "SP0001",
"psnProductLabel": "SP00010000000000",
"psnEntitlementLabel": "SP0001",
"xboxProductId": "57395039-5332-304b-c032-4239434b2400",
"xboxStoreId": "9P9W2SK2B9CK",
"appleProductId": "iSP001",
"googlePlaySkuId": "sp0001",
"isSingleUse": true,
"shouldAppearInTokenStore": true,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
2636,
2011,
2025,
2010,
2548
],
"message": "Starter Pack"
}
},
{
"skuId": 187,
"name": "Special Offer 12",
"description": "",
"imageName": "a5owgnqsbvpw0wm6im1rdtsp3.png",
"price": 599,
"oculusSkuId": "CO0012",
"xboxProductId": "4d474e39-3046-3047-c031-544742311700",
"xboxStoreId": "9NGMF0G1TGB1",
"appleProductId": "CO0012",
"googlePlaySkuId": "co0012",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 12"
}
},
{
"skuId": 188,
"name": "Special Offer 13",
"description": "",
"imageName": "dn4gs1ie40kfhoruy7jmkfey4.png",
"price": 699,
"oculusSkuId": "CO0013",
"xboxProductId": "364e4e39-4334-3044-c04a-385257352600",
"xboxStoreId": "9NN64CDJ8RW5",
"appleProductId": "CO0013",
"googlePlaySkuId": "co0013",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 13"
}
},
{
"skuId": 189,
"name": "Special Offer 14",
"description": "",
"imageName": "3fbwxtc1flb1ngd71l2vudklx.png",
"price": 799,
"oculusSkuId": "CO0014",
"xboxProductId": "43314e39-3235-3039-c050-573158562100",
"xboxStoreId": "9N1C529PW1XV",
"appleProductId": "CO0014",
"googlePlaySkuId": "co0014",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 14"
}
},
{
"skuId": 11,
"name": "Rec Room Plus Membership",
"description": "Rec Room Plus Membership",
"imageName": "304tyaxvb6d8s3dcef8s3y4pb.png",
"price": 799,
"oculusSkuId": "PM0001",
"psnProductLabel": "PM00010000000000",
"psnEntitlementLabel": "PM0001",
"xboxProductId": "54465039-534b-3044-c04a-564e32517600",
"xboxStoreId": "9PFTKSDJVN2Q",
"appleProductId": "iCC001",
"googlePlaySkuId": "pm0001",
"nintendoSkuId": "a26f976e026d86e1",
"isSingleUse": false,
"shouldAppearInTokenStore": true,
"dataSchemaVersion": 2,
"data": {
"giftDropIds": [
14258
],
"message": "Rec Room Plus Membership",
"subscriptionPurchase": {
"type": 0,
"level": 0,
"period": 0,
"isAutoRenewing": true
}
}
},
{
"skuId": 190,
"name": "Special Offer 15",
"description": "",
"imageName": "du9cs8uv5buib1ajgwbdcibtp.png",
"price": 899,
"oculusSkuId": "CO0015",
"xboxProductId": "36534e39-5435-3047-c031-585a57435d00",
"xboxStoreId": "9NS65TG1XZWC",
"appleProductId": "CO0015",
"googlePlaySkuId": "co0015",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 15"
}
},
{
"skuId": 25,
"name": "Special Offer 3",
"description": "",
"imageName": "7yr1khevvss841xrwahj6hfn9.png",
"price": 999,
"oculusSkuId": "CO0003",
"xboxProductId": "36344e39-4747-3051-c044-4d3634350000",
"xboxStoreId": "9N46GGQDM645",
"appleProductId": "CO0003",
"googlePlaySkuId": "co0003",
"picoSkuId": "CO003",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 3"
}
},
{
"skuId": 5,
"name": "5,500 Tokens",
"description": "",
"imageName": "0k5tpvs9p5tp50dfn4tq1j72l.png",
"price": 999,
"oculusSkuId": "TK0002",
"psnProductLabel": "TK00020000000000",
"psnEntitlementLabel": "TK0002",
"xboxProductId": "44544d39-5153-3030-c044-4a5734576200",
"xboxStoreId": "9MTDSQ0DJW4W",
"appleProductId": "iTK002",
"googlePlaySkuId": "tk0002",
"picoSkuId": "TK002",
"nintendoSkuId": "TK0002",
"isSingleUse": false,
"shouldAppearInTokenStore": true,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
2633
],
"message": "5,500 Tokens"
}
},
{
"skuId": 26,
"name": "Special Offer 4",
"description": "",
"imageName": "ev9ynntwhgonosztxuk6r75vq.png",
"price": 1499,
"oculusSkuId": "CO0004",
"xboxProductId": "52465039-4342-3056-c04e-465847538200",
"xboxStoreId": "9PFRBCVNFXGS",
"appleProductId": "CO0004",
"googlePlaySkuId": "co0004",
"picoSkuId": "CO004",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 4"
}
},
{
"skuId": 27,
"name": "Special Offer 5",
"description": "",
"imageName": "3exoho63d3xtnx6z8eiudm3yl.png",
"price": 1999,
"oculusSkuId": "CO0005",
"xboxProductId": "5a514e39-3544-3043-c035-5434324a2700",
"xboxStoreId": "9NQZD5C5T42J",
"appleProductId": "CO0005",
"googlePlaySkuId": "co0005",
"picoSkuId": "CO005",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 5"
}
},
{
"skuId": 8,
"name": "12,000 Tokens",
"description": "",
"imageName": "2cxy6mghew6po1qyl2hjjp1dz.png",
"price": 1999,
"oculusSkuId": "TK0003",
"psnProductLabel": "TK00030000000000",
"psnEntitlementLabel": "TK0003",
"xboxProductId": "38434e39-5447-3032-c054-503747433400",
"xboxStoreId": "9NC8GT2TP7GC",
"appleProductId": "iTK003",
"googlePlaySkuId": "tk0003",
"picoSkuId": "TK003",
"nintendoSkuId": "TK0003",
"isSingleUse": false,
"shouldAppearInTokenStore": true,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
2634
],
"message": "12,000 Tokens"
}
},
{
"skuId": 191,
"name": "Special Offer 16",
"description": "",
"imageName": "de614ysh9h08jgt6rm7xtl8g1.png",
"price": 2499,
"oculusSkuId": "CO0016",
"xboxProductId": "47314e39-3557-3043-c052-345233573000",
"xboxStoreId": "9N1GW5CR4R3W",
"appleProductId": "CO0016",
"googlePlaySkuId": "co0016",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 16"
}
},
{
"skuId": 192,
"name": "Special Offer 17",
"description": "",
"imageName": "e2pwk2sqieip5vcbd5bmf5qek.png",
"price": 2999,
"oculusSkuId": "CO0017",
"xboxProductId": "4e465039-464e-3047-c04d-374351334300",
"xboxStoreId": "9PFNNFGM7CQ3",
"appleProductId": "CO0017",
"googlePlaySkuId": "co0017",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 17"
}
},
{
"skuId": 9,
"name": "35,000 Tokens",
"description": "",
"imageName": "8okk17qjqs25tavj04fzqzski.png",
"price": 4999,
"oculusSkuId": "TK0004",
"psnProductLabel": "TK00040000000000",
"psnEntitlementLabel": "TK0004",
"xboxProductId": "36524e39-314d-3034-c039-314b5230f800",
"xboxStoreId": "9NR6M1491KR0",
"appleProductId": "iTK004",
"googlePlaySkuId": "tk0004",
"isSingleUse": false,
"shouldAppearInTokenStore": true,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [
2635
],
"message": "35,000 Tokens!"
}
},
{
"skuId": 28,
"name": "Special Offer 6",
"description": "",
"imageName": "9xtn0r4ol3cszw6n3nmdtjqpc.png",
"price": 4999,
"oculusSkuId": "CO0006",
"xboxProductId": "4a395039-4437-3054-c042-4d3847432c00",
"xboxStoreId": "9P9J7DTBM8GC",
"appleProductId": "CO0006",
"googlePlaySkuId": "co0006",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 6"
}
},
{
"skuId": 29,
"name": "Special Offer 7",
"description": "",
"imageName": "brrnvugan0vtdz5ha58xsydsk.png",
"price": 6999,
"oculusSkuId": "CO0007",
"xboxProductId": "42534e39-4230-3048-c047-46504d373700",
"xboxStoreId": "9NSB0BHGFPM7",
"appleProductId": "CO0007",
"googlePlaySkuId": "co0007",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 7"
}
},
{
"skuId": 193,
"name": "Special Offer 18",
"description": "",
"imageName": "ct4fpwoznnerrdjwuii3vmlxk.png",
"price": 7499,
"oculusSkuId": "CO0018",
"xboxProductId": "5a485039-4e56-304e-c039-5a3735324e00",
"xboxStoreId": "9PHZVNN9Z752",
"appleProductId": "CO0018",
"googlePlaySkuId": "co0018",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "Special Offer 18"
}
},
{
"skuId": 30,
"name": "99.99 Special Offer",
"description": "",
"imageName": "5gv9upz5ndauzgwgn8x4lohg6.png",
"price": 9999,
"oculusSkuId": "CO0008",
"xboxProductId": "4b4a4e39-4d51-304e-c054-5a444d569d00",
"xboxStoreId": "9NJKQMNTZDMV",
"appleProductId": "CO0008",
"googlePlaySkuId": "co0008",
"isSingleUse": false,
"shouldAppearInTokenStore": false,
"dataSchemaVersion": 1,
"data": {
"giftDropIds": [],
"message": "$99.99 Special Offer"
}
}
]
@@ -0,0 +1,93 @@
[
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 0,
"Version": 1,
"Filename": "0fhh07gjhiqi0ja1atgq312ft.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 0,
"Version": 3,
"Filename": "230c3ntlf0nyilv9946su5nlw.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 1,
"Version": 1,
"Filename": "8aas7b5ivtaggn9swhe3wkgdj.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 1,
"Version": 3,
"Filename": "6bh43zi38orrhl5yewk27u7pm.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 2,
"Version": 1,
"Filename": "f0qpzl5paupz5dwyzrnjid45c.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 2,
"Version": 3,
"Filename": "84p1vgcilddenfzw8wr90x1px.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 3,
"Version": 1,
"Filename": "1wxwtj4v0yirzp7toz17m6rk3.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 4,
"Version": 1,
"Filename": "e7o555n0k1a46miu3ekg0fp94.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 4,
"Version": 3,
"Filename": "2zxjoilc5wzlh55qhd9cma37h.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 5,
"Version": 1,
"Filename": "a61spwls2bfljmwtxwyfetrv8.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 5,
"Version": 3,
"Filename": "dscjthja3jkxu2ea66rr24pjz.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 6,
"Version": 1,
"Filename": "ay24njnu4qjoiy4348gn5ktpi.assetbundle",
"Hash": null
},
{
"UnityAssetId": "0088d207-017b-45a1-9ee6-1375ed29b0d3",
"Target": 6,
"Version": 3,
"Filename": "4s4p8evbj52pwro2dojuhuzya.assetbundle",
"Hash": null
}
]
@@ -0,0 +1,100 @@
[
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 0,
"Version": 3,
"Filename": "8iaf2yemao2d1zurblo875cn2.assetbundle",
"Hash": "z4TZGz320EVf2z1KwjvSkQhl0BnwXCYIuRtcE64AtLE="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 0,
"Version": 5,
"Filename": "7da5ci70r66t72y5wqw4cq8zy.assetbundle",
"Hash": "NiCsnMjYTYg48f00ce92oOvmVw9Fw82TJ3ONrtENS1g="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 1,
"Version": 3,
"Filename": "6c446jpe6g2sghc1auvp67tns.assetbundle",
"Hash": "IaUN2PmcqOzRl9z9HHXW2lNMRKrKhtE4GbcU4jymKSI="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 1,
"Version": 5,
"Filename": "1fskwy4lw10tqrfs23as4yfqv.assetbundle",
"Hash": "OVhqtp2YFZ3alizZRa5hvlNnYk5ml7+8K9+Wlu+Y36M="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 2,
"Version": 3,
"Filename": "2j9k6sxfpwopqgg0fhzvluov6.assetbundle",
"Hash": "Kt5iFaqcIixqQL9CC1gGlYtApEiRKMTpc6PyZUmvAJw="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 2,
"Version": 5,
"Filename": "5qvp5xm4e5ffv3y1em5kdsu07.assetbundle",
"Hash": "RQZhHpHSivPMOuG7l94iqwNDdJzgAfKg3d1u3cy56h8="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 4,
"Version": 3,
"Filename": "esjch9ko25wn6vuv51n5jzh04.assetbundle",
"Hash": "ZK7JFlB9beNoUjX/fryq51WUDsMQycsAduWDOuSMCks="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 4,
"Version": 5,
"Filename": "0b8uk02qqrc4rd90jpwfhxv1x.assetbundle",
"Hash": "WRmYmcvZKCmzhu65XyyO0ccDuNPFA6DDeIqRNHnPnO8="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 5,
"Version": 3,
"Filename": "00mpwzn0r8jyb70m5r7766y3b.assetbundle",
"Hash": "h9q0r1SRByZKp1tZrfVUxxZx6UrV38oR9SN7+azPXpU="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 5,
"Version": 5,
"Filename": "1oz78fzt1k20m0e7xvfontz0r.assetbundle",
"Hash": "It89U7rYRemsoyO0O4oEPwJT1pMHK1avrWjQUq3iRhM="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 6,
"Version": 3,
"Filename": "7ji4xrn56i81v4ojybbc4jiwc.assetbundle",
"Hash": "UpIQjfqCR8Erbgn12QMVbLsGyoeJcN6loIFKJRK2koU="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 6,
"Version": 5,
"Filename": "8521jvpmidui3k4890stjquam.assetbundle",
"Hash": "ki0mqJItNo2aHjaRJK9Voq5ETarVBMeGaok3IfXLNXk="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 8,
"Version": 3,
"Filename": "48m0rxc4fq8ys0nq00yk4p7bb.assetbundle",
"Hash": "6sLLdAQ+cddwB7ng1lkH9324cac2Z89mKs5erzJsFKg="
},
{
"UnityAssetId": "22555d14-2918-43f3-94d4-da0c893d96c4",
"Target": 8,
"Version": 5,
"Filename": "0uo7vp1zicbwu42en1ixwhfha.assetbundle",
"Hash": "cmpAZxvfRiH0GUOEA55f/8Ro+bzl/g9KaoQhExqlecM="
}
]
@@ -0,0 +1,51 @@
[
{
"UnityAssetId": "3496729e-b59b-413e-bfa8-1b5103985209",
"Target": 0,
"Version": 3,
"Filename": "ddw6632oo187a91jl9g4pyqgr.assetbundle",
"Hash": null
},
{
"UnityAssetId": "3496729e-b59b-413e-bfa8-1b5103985209",
"Target": 1,
"Version": 3,
"Filename": "bhp4yugetg5fj74i1nh2zzpqn.assetbundle",
"Hash": null
},
{
"UnityAssetId": "3496729e-b59b-413e-bfa8-1b5103985209",
"Target": 2,
"Version": 3,
"Filename": "drere5uht3ppfsfgq7fnwppkf.assetbundle",
"Hash": null
},
{
"UnityAssetId": "3496729e-b59b-413e-bfa8-1b5103985209",
"Target": 4,
"Version": 3,
"Filename": "4lukky7i6z22em4ynpl3h2z67.assetbundle",
"Hash": null
},
{
"UnityAssetId": "3496729e-b59b-413e-bfa8-1b5103985209",
"Target": 5,
"Version": 3,
"Filename": "7p1zm6dvdx4wle1aiqpz6ev96.assetbundle",
"Hash": null
},
{
"UnityAssetId": "3496729e-b59b-413e-bfa8-1b5103985209",
"Target": 6,
"Version": 3,
"Filename": "dj05k8hsh8o50tidfzcax96uf.assetbundle",
"Hash": null
},
{
"UnityAssetId": "3496729e-b59b-413e-bfa8-1b5103985209",
"Target": 8,
"Version": 3,
"Filename": "alt7794ipj98ordway76x3twi.assetbundle",
"Hash": null
}
]
@@ -0,0 +1,97 @@
[
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 0,
"Version": 3,
"Filename": "68mk9vmtjmm79erhueap1d7ld.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 0,
"Version": 5,
"Filename": "73s0z24llofp0dd5wvc359591.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 1,
"Version": 3,
"Filename": "5o3xg00wzh1w6n903naebr9se.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 1,
"Version": 5,
"Filename": "ay1pkdo7aezihezi26xajpmqq.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 2,
"Version": 3,
"Filename": "4txs7lbk86ft5f9bmswduznta.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 2,
"Version": 5,
"Filename": "4fgf3ssmh2nxenzw2ahgdyokw.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 4,
"Version": 3,
"Filename": "4n0sxfmlsl1afw94fhx2ufxqq.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 4,
"Version": 5,
"Filename": "9ulhx3kih1w49u9f87zskp0m4.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 5,
"Version": 3,
"Filename": "arw0nvoelp7l6oqsytxo5804i.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 5,
"Version": 5,
"Filename": "40gkhgs01y1oq0pycyt219gpe.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 6,
"Version": 3,
"Filename": "agdfle8geuy2a57kbtlbhga1c.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 6,
"Version": 5,
"Filename": "7rsykg5obikm9io6784u2c5js.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 8,
"Version": 3,
"Filename": "f3fwtarza2w7fea4gvny6n9yg.assetbundle"
},
{
"UnityAssetId": "72c99424-f6ee-483e-9b23-7ba187bf9f47",
"Target": 8,
"Version": 5,
"Filename": "5dgm17yv1m832z97j5559s88s.assetbundle"
}
]
@@ -0,0 +1,101 @@
[
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 8,
"Version": 3,
"Filename": "4544ltdekeqf13439p1237023.assetbundle",
"Hash": "/4xJBBGdpY2nxpKLiuuHDsx+jj777mrYzbQ/q0/0Dv4="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 4,
"Version": 3,
"Filename": "8pehmoeqxuq6zhmrm6mdgg7b0.assetbundle",
"Hash": "SOXE64Tp9WCx+dABn3egXtVnH3ReMR2QzIqRedj+FnA="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 5,
"Version": 3,
"Filename": "bkkqqkdz0e9lyz1ir9lnst815.assetbundle",
"Hash": "7o6wdRwfG3kL5Xrv2EUJGR7tZewVTR4myQkVGvVve68="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 2,
"Version": 3,
"Filename": "2dsuc7x2j6t60t5p8k4oq3ezd.assetbundle",
"Hash": "nXEYUUly8BTY99UTn8SmOvRhhe9aSbqGCXdMOi0yNJ4="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 6,
"Version": 3,
"Filename": "cuyyx5gkl377ovt6msj8nfm8i.assetbundle",
"Hash": "5rvgDpDgAMYZeuSV8tUy3rLxV7M4215tJUqbR6uH64w="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 0,
"Version": 3,
"Filename": "9xwhl5j3qe4bxnkmep4ubjqhl.assetbundle",
"Hash": "xRn+y0GH1JCI9lpTfRwsEKbS4PTiHe3Vhk+N+GMScy8="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 1,
"Version": 3,
"Filename": "3ipbviv6jx9r4w0qbjp09cblm.assetbundle",
"Hash": "Mh+apbdRXP4p0UWSqMlSLOK/kNR65OY1viP1vh0Kw5Y="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 1,
"Version": 5,
"Filename": "3ju5hcirzseyj50brwskmf6cs.assetbundle",
"Hash": "r6ACHu59r/IDQcsyyxXnX6t9zBC9qrnU9maC0HWdS0Y="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 4,
"Version": 5,
"Filename": "8jtw7s0v0fsdvfzk667z6thcf.assetbundle",
"Hash": "h8OVxzt8eeC5wmQ9/kp5igK6K5tCvSS8MnY9gVVvFKg="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 2,
"Version": 5,
"Filename": "1g26flljj84u6w8mtnhizn2tb.assetbundle",
"Hash": "4OCK0CBey5zHsB4LApMUqOLwlzAtO/JdUz+7VciojeM="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 0,
"Version": 5,
"Filename": "8bcnemi6gp9ktae83dpndsw3y.assetbundle",
"Hash": "2JvqJUpT85hdnEkZ6B+sYFa61tQafHGnCcJdSJhgnpk="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 8,
"Version": 5,
"Filename": "7g0enln1sqedj101qaiqmqbaf.assetbundle",
"Hash": "K9Zrq54oTpQOTVgRrTihr1ALht5M9kWOXwY8PZMvbEk="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 5,
"Version": 5,
"Filename": "cv9xdkhege6yj6t3tafwf9qu2.assetbundle",
"Hash": "nkECxWpElF7kzrC7xCr2kol1SI4rE/odelWEgudb5DQ="
},
{
"UnityAssetId": "8f26710f-e9f4-4bdd-9e74-b46cc292293e",
"Target": 6,
"Version": 5,
"Filename": "85acvejyio6nxy9qz4kis3kzx.assetbundle",
"Hash": "0qa2ZYxly2hMVagpERoRqttKaWuaJJQnO19mQK+g5Y0="
}
]
+107
View File
@@ -0,0 +1,107 @@
[
{
"Id": 1558,
"Difficulty": 20,
"EN_US": "If you could have any super power what would it be?"
},
{
"Id": 1559,
"Difficulty": 20,
"EN_US": "What is an animal you wish you could transform into at will?"
},
{
"Id": 1560,
"Difficulty": 20,
"EN_US": "Describe yourself in three words."
},
{
"Id": 1561,
"Difficulty": 20,
"EN_US": "If you had a billion dollars what is the first thing you'd buy?"
},
{
"Id": 1562,
"Difficulty": 20,
"EN_US": "What would you bring with you if you were going to the International Space Station?"
},
{
"Id": 1563,
"Difficulty": 20,
"EN_US": "Would you rather travel forward in time or backward in time?"
},
{
"Id": 1564,
"Difficulty": 20,
"EN_US": "What type of game would you make if you could make whatever you wanted?"
},
{
"Id": 1565,
"Difficulty": 20,
"EN_US": "What is a fictional universe you wish you could live in?"
},
{
"Id": 1566,
"Difficulty": 20,
"EN_US": "What is your favorite ice cream flavor?"
},
{
"Id": 1567,
"Difficulty": 20,
"EN_US": "What is your favorite type of weather?"
},
{
"Id": 1568,
"Difficulty": 20,
"EN_US": "Would you rather live in an ice palace or a desert palace?"
},
{
"Id": 1569,
"Difficulty": 20,
"EN_US": "What is the last movie you watched?"
},
{
"Id": 1570,
"Difficulty": 20,
"EN_US": "What is the last song you listened to?"
},
{
"Id": 1571,
"Difficulty": 20,
"EN_US": "You can have dinner with any famous person. Who would you pick?"
},
{
"Id": 1572,
"Difficulty": 20,
"EN_US": "You can have dinner with any fictional character. Who would you pick?"
},
{
"Id": 1573,
"Difficulty": 20,
"EN_US": "Pineapple on pizza - excellent or ew?"
},
{
"Id": 1574,
"Difficulty": 20,
"EN_US": "Do you like waking up early or staying up late?"
},
{
"Id": 1575,
"Difficulty": 20,
"EN_US": "Who is a fictional character you relate to?"
},
{
"Id": 1576,
"Difficulty": 20,
"EN_US": "Name a video game world you would not want to live in."
},
{
"Id": 1577,
"Difficulty": 20,
"EN_US": "What is a skill you wish you were an expert at?"
},
{
"Id": 1578,
"Difficulty": 20,
"EN_US": "Would you rather be able to fly or have a billion dollars?"
}
]
@@ -0,0 +1,22 @@
{
"ListId": 624765592684307326,
"CreatorAccountId": 1,
"Name": "Discovery.PageSource.PlayExplore",
"Description": null,
"ImageName": "DefaultRoomImage.jpg",
"Type": 7,
"ItemIds": [
"Rooms_New_PlayHighlight_TabsTest_Explore",
"RoomCategories_MoodPlaylists_FeelingLucky",
"Rooms_RecentlyUpdated_TabsTest_Explore",
"Rooms_Battle_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"Rooms_Quests_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"Rooms_Roleplay_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"Rooms_Horror_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"Rooms_Hangout_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"Rooms_Casual_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"Rooms_Explore_AlgoEndpoint_PlayHighlight_TabsTest_Explore"
],
"Accessibility": 1,
"CreatedAt": "2025-04-23T18:27:03.2643786Z"
}
@@ -0,0 +1,16 @@
{
"ListId": 5321092632685904804,
"CreatorAccountId": 1,
"Name": "Discovery.PageSource.PlayLibrary",
"Description": null,
"ImageName": "DefaultRoomImage.jpg",
"Type": 7,
"ItemIds": [
"Rooms_ContinuePlaying_PlayLibrary",
"Rooms_SavedForLater_PlayHighlight",
"Rooms_Favorites_PlayLibrary",
"Rooms_MyRooms_Play"
],
"Accessibility": 1,
"CreatedAt": "2025-04-23T18:25:31.5308539Z"
}
@@ -0,0 +1,18 @@
{
"ListId": 8578579969342570774,
"CreatorAccountId": 1,
"Name": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
"Description": "I'm Feeling Lucky",
"ImageName": "DefaultRoomImage.jpg",
"Type": 7,
"ItemIds": [
"action_algorithmiclist_roomcategory",
"hangout_roomcategory_card",
"horror_roomcategory_card",
"pvp_roomcategory_card",
"quests_roomcategory_card",
"roleplay_roomcategory_card"
],
"Accessibility": 1,
"CreatedAt": "2024-05-22T05:37:43.7726633Z"
}
File diff suppressed because one or more lines are too long
+98
View File
@@ -0,0 +1,98 @@
[
{
"MessageId": "t-78799801",
"Channel": 1,
"SenderMessageId": "ResearchLab1",
"IsBroadcast": false,
"AccountId": 620320568,
"Platform": -1,
"PlatformTypeMask": -513,
"MinAppVersion": null,
"Priority": 0,
"SentAt": "2025-05-27T14:11:10.9865487Z",
"NotAfter": "2025-06-26T14:11:10.9792846Z",
"IsRead": true,
"ContentType": 1,
"Content": "{\"TrackingCategory\":\"Update\",\"Layout\":0,\"TextSize\":1,\"TitleTextPlacement\":2,\"Header\":null,\"Title\":\"Invitation to Research Lab: Earn Exclusive Rewards!\",\"SimpleTitle\":null,\"Body\":\"Want to get a sneak peek at new features and earn exclusive rewards that you won\\u0027t find anywhere else? Join Research Lab, where you can earn exclusive avatar items and consumables just by sharing your feedback on Rec Room! Select GO on PC or mobile to sign up, or type recroom.com/survey/JoinRL in any browser.\",\"SimpleBody\":null,\"ImageNames\":[\"555qdqq4cidrlo85gzajs0fk7.png\"],\"Buttons\":[{\"LinkType\":9,\"LinkName\":\"Close\",\"LinkParameter\":null,\"LinkButtonLabel\":null},{\"LinkType\":0,\"LinkName\":\"GO\",\"LinkParameter\":\"https://recroom.qualtrics.com/jfe/form/SV_0NzdeNvOAV8WGJo?SOURCE=Rengage_new_player\",\"LinkButtonLabel\":\"GO\"}]}"
},
{
"MessageId": "b-30201",
"Channel": 1,
"SenderMessageId": "ApiAnnouncement-10852",
"IsBroadcast": true,
"AccountId": 0,
"Platform": -1,
"PlatformTypeMask": -1,
"MinAppVersion": null,
"Priority": 0,
"SentAt": "2025-05-16T17:08:47.7119133Z",
"NotAfter": "2025-06-15T17:08:47.6305245Z",
"IsRead": true,
"ContentType": 1,
"Content": "{\"TrackingCategory\":\"Update\",\"Layout\":0,\"TextSize\":1,\"TitleTextPlacement\":2,\"Header\":null,\"Title\":\"Free Surv\\u00E9 for a short survey!\",\"SimpleTitle\":null,\"Body\":\"Take a 3-minute survey and get a Blue Surv\\u00E9 Bubbly! Select GO on PC or mobile or just type recroom.com/survey/feedback in any browser.\",\"SimpleBody\":null,\"ImageNames\":[\"e6a618705768414a8b27cec5f82911ce\"],\"Buttons\":[{\"LinkType\":9,\"LinkName\":\"Close\",\"LinkParameter\":null,\"LinkButtonLabel\":null},{\"LinkType\":0,\"LinkName\":\"GO\",\"LinkParameter\":\"https://recroom.qualtrics.com/jfe/form/SV_6KAA9gus1nOfWYe?SOURCE=CLICKED\",\"LinkButtonLabel\":\"GO\"}]}"
},
{
"MessageId": "b-30200",
"Channel": 1,
"SenderMessageId": "ApiAnnouncement-10851",
"IsBroadcast": true,
"AccountId": 0,
"Platform": -1,
"PlatformTypeMask": -1,
"MinAppVersion": null,
"Priority": 0,
"SentAt": "2025-05-13T21:32:47.9015949Z",
"NotAfter": "2025-06-12T21:32:47.591975Z",
"IsRead": false,
"ContentType": 1,
"Content": "{\"TrackingCategory\":\"Update\",\"Layout\":0,\"TextSize\":1,\"TitleTextPlacement\":2,\"Header\":null,\"Title\":\"New Build Contest!\",\"SimpleTitle\":null,\"Body\":\"Build contests are back! Are you ready to create? This time we\\u2019re diving into solarpunk! \\r\\nWhat does a world built on sustainability, beauty, and cooperation look like to you? Hit GO to begin your creative adventures!\",\"SimpleBody\":null,\"ImageNames\":[\"d129b1d5ac18498d9166b032ce1eacbf\"],\"Buttons\":[{\"LinkType\":9,\"LinkName\":\"Close\",\"LinkParameter\":null,\"LinkButtonLabel\":null},{\"LinkType\":3,\"LinkName\":\"GO\",\"LinkParameter\":\"TheRoomiesAfterparty\",\"LinkButtonLabel\":\"GO\"}]}"
},
{
"MessageId": "b-30199",
"Channel": 1,
"SenderMessageId": "ApiAnnouncement-10850",
"IsBroadcast": true,
"AccountId": 0,
"Platform": -1,
"PlatformTypeMask": -1,
"MinAppVersion": null,
"Priority": 0,
"SentAt": "2025-05-02T21:56:59.3322174Z",
"NotAfter": "2025-06-01T21:56:59.1089814Z",
"IsRead": false,
"ContentType": 1,
"Content": "{\"TrackingCategory\":\"Update\",\"Layout\":0,\"TextSize\":1,\"TitleTextPlacement\":2,\"Header\":null,\"Title\":\"Take survey, get 2 exclusive bubblies!\",\"SimpleTitle\":null,\"Body\":\"TWO exclusive Orange Surv\\u00E9 Bubblies for your thoughts! Select GO on PC or mobile or just type recroom.com/survey/yourthoughts in any browser.\",\"SimpleBody\":null,\"ImageNames\":[\"e4e54c1c328945eaad17d537b7a415b8\"],\"Buttons\":[{\"LinkType\":9,\"LinkName\":\"Close\",\"LinkParameter\":null,\"LinkButtonLabel\":null},{\"LinkType\":0,\"LinkName\":\"GO\",\"LinkParameter\":\"https://recroom.qualtrics.com/jfe/form/SV_5tXP9Sq8qqKa9zo?SOURCE=CLICKED\",\"LinkButtonLabel\":\"GO\"}]}"
},
{
"MessageId": "b-30198",
"Channel": 1,
"SenderMessageId": "ApiAnnouncement-10849",
"IsBroadcast": true,
"AccountId": 0,
"Platform": -1,
"PlatformTypeMask": -1,
"MinAppVersion": null,
"Priority": 0,
"SentAt": "2025-05-02T19:38:06.9824925Z",
"NotAfter": "2025-06-01T19:34:00.7244167Z",
"IsRead": false,
"ContentType": 1,
"Content": "{\"TrackingCategory\":\"Store\",\"Layout\":0,\"TextSize\":1,\"TitleTextPlacement\":2,\"Header\":null,\"Title\":\"It\\u2019s your final chance!\",\"SimpleTitle\":null,\"Body\":\"Hit GO to check out the Sci-fi Mega Bundle and see what\\u2019s new in the Sci-fi center as you journey your way into light years of fun. Act fast, they won\\u2019t be floating around for long!\",\"SimpleBody\":null,\"ImageNames\":[\"89131858c24648a4b6f932ae7dbc67dd\"],\"Buttons\":[{\"LinkType\":9,\"LinkName\":\"Close\",\"LinkParameter\":null,\"LinkButtonLabel\":null},{\"LinkType\":4,\"LinkName\":\"GO\",\"LinkParameter\":\"purchasereminder 547\",\"LinkButtonLabel\":\"GO\"}]}"
},
{
"MessageId": "b-30197",
"Channel": 1,
"SenderMessageId": "ApiAnnouncement-10848",
"IsBroadcast": true,
"AccountId": 0,
"Platform": -1,
"PlatformTypeMask": -1,
"MinAppVersion": null,
"Priority": 0,
"SentAt": "2025-04-29T23:01:48.5791311Z",
"NotAfter": "2025-05-29T23:01:48.2797205Z",
"IsRead": false,
"ContentType": 1,
"Content": "{\"TrackingCategory\":\"Update\",\"Layout\":0,\"TextSize\":1,\"TitleTextPlacement\":2,\"Header\":null,\"Title\":\"Build Contest is back!\",\"SimpleTitle\":null,\"Body\":\"Calling all creators! It\\u0027s time to get ready! Warm up those Maker Pens and gather your team, because our Build Contest is making its grand return on May 12th!\\r\\nTeams can have up to 6 members, so start planning your dream crew now. Need teammates? Head over to the creator forums to connect with other creators and build your perfect squad.\",\"SimpleBody\":null,\"ImageNames\":[\"c92b4b0e4b5d465881abc4221e321428\"],\"Buttons\":[{\"LinkType\":9,\"LinkName\":\"Close\",\"LinkParameter\":null,\"LinkButtonLabel\":null}]}"
}
]
+37
View File
@@ -0,0 +1,37 @@
[
{
"ImageName": "RRWD.jpg",
"Message": "Have fun!",
"PlatformMask": -1,
"RoomNames": [],
"Title": "Welcome to Rewild Live 2025 Test Server"
},
{
"ImageName": "9e99537b55ca494484c1637fb77d23f9",
"Message": "Pair up, earn paint, and bring Recassos paintings to life. Start your masterpiece today!",
"PlatformMask": -1,
"RoomNames": [],
"Title": "Paint Together. Progress Together. "
},
{
"ImageName": "LS2025.jpg",
"Message": "2025 build gone wild",
"PlatformMask": -1,
"RoomNames": [],
"Title": "naaa"
},
{
"ImageName": "image.png",
"Message": "AAAA FUCK HELP I BURNING AAAA HELP IT HURTS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"PlatformMask": -1,
"RoomNames": [],
"Title": "car"
},
{
"ImageName": "insertimghere.jpg",
"Message": "To die, use the <sprite name=\"MenuIcon_Sprites_4(HR)_XboxDPadSouth\"> button",
"PlatformMask": -1,
"RoomNames": [],
"Title": "test icons"
}
]
+9
View File
@@ -0,0 +1,9 @@
[
{
"ImageName": "9e99537b55ca494484c1637fb77d23f9",
"Message": "Pair up, earn paint, and bring Recassos paintings to life. Start your masterpiece today!",
"PlatformMask": -1,
"RoomNames": [],
"Title": "Paint Together. Progress Together. "
}
]
+203
View File
@@ -0,0 +1,203 @@
{
"results" : [
{
"activeExperiments" : [],
"addsBadge" : true,
"categoryId" : 0,
"importance" : 0,
"isMuteable" : false,
"isRateLimitedByCategory" : false,
"isRateLimitedGlobally" : false,
"isStacking" : false,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Admin",
"rateLimitMaxNotifications" : 0,
"rateLimitWindowDuration" : null,
"rateLimitWindowDurationSeconds" : null,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : false
},
{
"activeExperiments" : [],
"addsBadge" : true,
"categoryId" : 1,
"importance" : 0,
"isMuteable" : true,
"isRateLimitedByCategory" : false,
"isRateLimitedGlobally" : false,
"isStacking" : true,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Events",
"rateLimitMaxNotifications" : 0,
"rateLimitWindowDuration" : null,
"rateLimitWindowDurationSeconds" : null,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : false
},
{
"activeExperiments" : [],
"addsBadge" : true,
"categoryId" : 2,
"importance" : 0,
"isMuteable" : true,
"isRateLimitedByCategory" : true,
"isRateLimitedGlobally" : false,
"isStacking" : true,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Friends",
"rateLimitMaxNotifications" : 2,
"rateLimitWindowDuration" : "03:00:00",
"rateLimitWindowDurationSeconds" : 10800.0,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : true
},
{
"activeExperiments" : [ "lifecycle_q4_reduce_chat_message_notifications" ],
"addsBadge" : true,
"categoryId" : 3,
"importance" : 0,
"isMuteable" : true,
"isRateLimitedByCategory" : true,
"isRateLimitedGlobally" : false,
"isStacking" : true,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Chat",
"rateLimitMaxNotifications" : 10,
"rateLimitWindowDuration" : "01:00:00",
"rateLimitWindowDurationSeconds" : 3600.0,
"shouldSendToOnlinePlayers" : false,
"usesActiveHours" : false
},
{
"activeExperiments" : [],
"addsBadge" : false,
"categoryId" : 4,
"importance" : 0,
"isMuteable" : true,
"isRateLimitedByCategory" : false,
"isRateLimitedGlobally" : false,
"isStacking" : true,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Room Notifications",
"rateLimitMaxNotifications" : 0,
"rateLimitWindowDuration" : null,
"rateLimitWindowDurationSeconds" : null,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : false
},
{
"activeExperiments" : [],
"addsBadge" : false,
"categoryId" : 5,
"importance" : 0,
"isMuteable" : true,
"isRateLimitedByCategory" : false,
"isRateLimitedGlobally" : true,
"isStacking" : true,
"maxQueueDuration" : "10:00:00",
"maxQueueDurationSeconds" : 36000.0,
"name" : "Creator",
"rateLimitMaxNotifications" : 0,
"rateLimitWindowDuration" : null,
"rateLimitWindowDurationSeconds" : null,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : true
},
{
"activeExperiments" : [],
"addsBadge" : false,
"categoryId" : 6,
"importance" : 2,
"isMuteable" : false,
"isRateLimitedByCategory" : true,
"isRateLimitedGlobally" : false,
"isStacking" : false,
"maxQueueDuration" : "2.00:00:00",
"maxQueueDurationSeconds" : 172800.0,
"name" : "Reengagement",
"rateLimitMaxNotifications" : 1,
"rateLimitWindowDuration" : "1.00:00:00",
"rateLimitWindowDurationSeconds" : 86400.0,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : true
},
{
"activeExperiments" : [],
"addsBadge" : false,
"categoryId" : 7,
"importance" : 0,
"isMuteable" : true,
"isRateLimitedByCategory" : true,
"isRateLimitedGlobally" : false,
"isStacking" : true,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Favorite Friend Online",
"rateLimitMaxNotifications" : 1,
"rateLimitWindowDuration" : "08:00:00",
"rateLimitWindowDurationSeconds" : 28800.0,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : true
},
{
"activeExperiments" : [ "growth_q1_notification_onlinestatus_gameinvite" ],
"addsBadge" : true,
"categoryId" : 8,
"importance" : 2,
"isMuteable" : false,
"isRateLimitedByCategory" : true,
"isRateLimitedGlobally" : false,
"isStacking" : false,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Gameplay Invites",
"rateLimitMaxNotifications" : 2,
"rateLimitWindowDuration" : "03:00:00",
"rateLimitWindowDurationSeconds" : 10800.0,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : false
},
{
"activeExperiments" : [],
"addsBadge" : true,
"categoryId" : 9,
"importance" : 0,
"isMuteable" : true,
"isRateLimitedByCategory" : true,
"isRateLimitedGlobally" : false,
"isStacking" : true,
"maxQueueDuration" : "00:00:00",
"maxQueueDurationSeconds" : 0.0,
"name" : "Feed",
"rateLimitMaxNotifications" : 2,
"rateLimitWindowDuration" : "03:00:00",
"rateLimitWindowDurationSeconds" : 10800.0,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : true
},
{
"activeExperiments" : [],
"addsBadge" : false,
"categoryId" : 999,
"importance" : 0,
"isMuteable" : false,
"isRateLimitedByCategory" : true,
"isRateLimitedGlobally" : false,
"isStacking" : false,
"maxQueueDuration" : "00:10:00",
"maxQueueDurationSeconds" : 600.0,
"name" : "Queue Tester 1",
"rateLimitMaxNotifications" : 2,
"rateLimitWindowDuration" : "00:02:00",
"rateLimitWindowDurationSeconds" : 120.0,
"shouldSendToOnlinePlayers" : true,
"usesActiveHours" : false
}
],
"totalResults" : 11
}
+26
View File
@@ -0,0 +1,26 @@
[
{
"id": "us-west1",
"address": "34.169.254.144:50000"
},
{
"id": "europe-west1",
"address": "35.205.141.119:50000"
},
{
"id": "asia-northeast1",
"address": "35.200.67.228:50000"
},
{
"id": "us-east1",
"address": "34.73.244.122:50000"
},
{
"id": "us-central1",
"address": "34.69.179.51:50000"
},
{
"id": "northamerica-northeast1",
"address": "34.152.4.100:50000"
}
]
+8
View File
@@ -0,0 +1,8 @@
{
"NewUntil": null,
"NextUpdate": "9999-12-31T18:00:00Z",
"StoreItems": [],
"StorefrontName": null,
"StorefrontType": 3,
"SubscriberDiscountPercent": 10
}
+162
View File
@@ -0,0 +1,162 @@
[
{
"id": "Accounts_OnlineFriends_WatchHome",
"sectionType": 1,
"sectionSubType": "Accounts_OnlineFriends",
"source": "OnlineFriends",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Online Friends\",\"itemCount\":3,\"cardStyle\":\"Horizontal\"}"
},
{
"id": "Rooms_ForYou_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_ForYou",
"source": "CarouselEndpoint",
"sourceMetadata": "foryou",
"displayMetadata": "{\"DisplayTitle\":\"Recommended For You\",\"supportsDedupe\":\"true\",\"numRows\":\"2\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"#F3DDCE\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_MostPopular",
"sectionType": 0,
"sectionSubType": "Rooms_MostPopular",
"source": "Hot",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Trending\",\"supportsDedupe\":\"true\",\"numRows\":\"1\"}"
},
{
"id": "Rooms_FeaturedThisWeek",
"sectionType": 0,
"sectionSubType": "Rooms_Featured",
"source": "Featured",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Featured This Week\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"all=highlight_scale_150\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_New_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_New",
"source": "Hot",
"sourceMetadata": "new",
"displayMetadata": "{\"DisplayTitle\":\"New\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\",\"unsupportedPlatforms\":[\"Switch\"]}"
},
{
"id": "Rooms_RRO_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_RRO",
"source": "Hot",
"sourceMetadata": "rro",
"displayMetadata": "{\"DisplayTitle\":\"Rec Room Originals\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_WeLove",
"sectionType": 0,
"sectionSubType": "Rooms_WeLove",
"source": "CarouselEndpoint",
"sourceMetadata": "inkshowcase",
"displayMetadata": "{\"DisplayTitle\":\"Rooms We Love\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_ContinuePlaying_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_ContinuePlaying",
"source": "Recent",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Continue Playing\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_RecentlyUpdated",
"sectionType": 0,
"sectionSubType": "Rooms_RecentlyUpdated",
"source": "CarouselEndpoint",
"sourceMetadata": "recentlyupdated",
"displayMetadata": "{\"DisplayTitle\":\"Recently Updated\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Favorites",
"sectionType": 0,
"sectionSubType": "Rooms_PlayerFavorites",
"source": "MyFavorites",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Favorites\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_TopEarning_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_TopEarning",
"source": "PlaylistById",
"sourceMetadata": "65380152",
"displayMetadata": "{\"DisplayTitle\":\"Top Earning\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_CreatorsYouFollow",
"sectionType": 0,
"sectionSubType": "Rooms_PlayerSubscriptions",
"source": "MySubscriptions",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"From Creators You Follow\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_R2Econ",
"sectionType": 0,
"sectionSubType": "Rooms_StateOfTheArt",
"source": "PlaylistById",
"sourceMetadata": "1876634643426420466",
"displayMetadata": "{\"DisplayTitle\":\"State of the Art\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\",\"unsupportedPlatforms\":[\"Switch\"]}"
},
{
"id": "Rooms_Roleplay_AlgoEndpoint_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Roleplay",
"source": "CarouselEndpoint",
"sourceMetadata": "roleplay_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Roleplay\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Casual_AlgoEndpoint_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Casual",
"source": "CarouselEndpoint",
"sourceMetadata": "casual_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Casual\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Horror_AlgoEndpoint_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Horror",
"source": "CarouselEndpoint",
"sourceMetadata": "horror_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Horror\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Battle_AlgoEndpoint_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Battle",
"source": "CarouselEndpoint",
"sourceMetadata": "battle_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Battle\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Explore_AlgoEndpoint_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Exploration",
"source": "CarouselEndpoint",
"sourceMetadata": "explore_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Explore\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Quests_AlgoEndpoint_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Quests",
"source": "CarouselEndpoint",
"sourceMetadata": "quests_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Quests\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Hangout_AlgoEndpoint_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Hangout",
"source": "CarouselEndpoint",
"sourceMetadata": "hangout_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Hangout\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
}
]
+42
View File
@@ -0,0 +1,42 @@
[
{
"displayMetadata" : "{\"DisplayTitle\":\"Compete\",\"imageName\": \"roomCatSelect_pvp.jpg\"}",
"id" : "pvp_search_roomcategory",
"sectionSubType" : "pvp_search",
"sectionType" : 11,
"source" : "ServerSearch",
"sourceMetadata" : "PVP"
},
{
"displayMetadata" : "{\"DisplayTitle\":\"Fun with Friends\",\"imageName\": \"roomCatSelect_roomsWeLove.jpg\"}",
"id" : "rooms_friendshiprates",
"sectionSubType" : "roomcategories_friendshiprates",
"sectionType" : 11,
"source" : "PlaylistById",
"sourceMetadata" : "7732248245192312892"
},
{
"displayMetadata" : "{\"DisplayTitle\":\"Quest\",\"imageName\": \"roomCatSelect_quest.jpg\"}",
"id" : "quest_search_roomcategory",
"sectionSubType" : "quest",
"sectionType" : 11,
"source" : "ServerSearch",
"sourceMetadata" : "Quest"
},
{
"displayMetadata" : "{\"DisplayTitle\":\"Horror\",\"imageName\": \"roomCatSelect_horror.jpg\"}",
"id" : "horror_search_roomcategory",
"sectionSubType" : "horror",
"sectionType" : 11,
"source" : "ServerSearch",
"sourceMetadata" : "Horror"
},
{
"displayMetadata" : "{\"DisplayTitle\":\"Art\",\"imageName\": \"roomCatSelect_art.jpg\"}",
"id" : "art_search_roomcategory",
"sectionSubType" : "art",
"sectionType" : 11,
"source" : "ServerSearch",
"sourceMetadata" : "Art"
}
]
+74
View File
@@ -0,0 +1,74 @@
[
{
"id": "Rooms_RoomBanner_SketchyShowdown",
"sectionType": 6,
"sectionSubType": "RoomBanner",
"source": "2477031627165896495",
"sourceMetadata": "2477031627165896495",
"displayMetadata": null
},
{
"id": "Rooms_ForYou_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_ForYou",
"source": "CarouselEndpoint",
"sourceMetadata": "foryou",
"displayMetadata": "{\"DisplayTitle\":\"Recommended For You\",\"supportsDedupe\":\"true\",\"numRows\":\"2\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"#F3DDCE\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Friendship_New_Users",
"sectionType": 0,
"sectionSubType": "Rooms_Friendship",
"source": "PlaylistById",
"sourceMetadata": "7732248245192312892",
"displayMetadata": "{\"DisplayTitle\":\"Fun with Friends\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\", \"accountAgeMinDays\":0, \"accountAgeMaxDays\":3}"
},
{
"id": "Rooms_StaffPicks_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_Featured",
"source": "CarouselEndpoint",
"sourceMetadata": "staffpicks",
"displayMetadata": "{\"DisplayTitle\":\"Featured This Week\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"all=highlight_scale_150\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_RRO_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_RRO",
"source": "Hot",
"sourceMetadata": "rro",
"displayMetadata": "{\"DisplayTitle\":\"Rec Room Originals\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_MostPopular",
"sectionType": 0,
"sectionSubType": "Rooms_MostPopular",
"source": "Hot",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Trending\",\"supportsDedupe\":\"true\",\"numRows\":\"1\"}"
},
{
"id": "Events_CreatorWorkshops_New_Users",
"sectionType": 5,
"sectionSubType": "Creator_Workshop_New_Users",
"source": "ClubEventsById",
"sourceMetadata": "69",
"displayMetadata": "{\"DisplayTitle\":\"Creative Workshops\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\",\"excludeJuniors\":\"true\",\"unsupportedPlatforms\":[\"Switch\"],\"accountAgeMaxDays\":3}"
},
{
"id": "Discovery_FeaturedCreator",
"sectionType": 13,
"sectionSubType": "featured_creator",
"source": "PlayerCreatedRooms",
"sourceMetadata": "730029",
"displayMetadata": "{\"DisplayTitle\":\"Featured Creator\", \"descriptionText\":\"Making moments you can play again and again!\",\"itemCount\":\"4\", \"unsupportedPlatforms\":[\"Switch\"]}"
},
{
"id": "Rooms_WeLove",
"sectionType": 0,
"sectionSubType": "Rooms_WeLove",
"source": "CarouselEndpoint",
"sourceMetadata": "inkshowcase",
"displayMetadata": "{\"DisplayTitle\":\"Rooms We Love\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
}
]
+26
View File
@@ -0,0 +1,26 @@
[
{
"id": "Highlights",
"sectionType": 13,
"sectionSubType": "highlights",
"source": "Highlights",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Highlights\"}"
},
{
"id": "PlayTabs_Explore_SearchButton",
"sectionType": 13,
"sectionSubType": "playtabs_explore",
"source": "CuratedList",
"sourceMetadata": "Discovery.PageSource.PlayExplore",
"displayMetadata": "{\"DisplayTitle\":\"Explore\", \"SearchType\":1}"
},
{
"id": "PlayTabs_Library",
"sectionType": 13,
"sectionSubType": "playtabs_library",
"source": "CuratedList",
"sourceMetadata": "Discovery.PageSource.PlayLibrary",
"displayMetadata": "{\"DisplayTitle\":\"Library\", \"unsupportedPlatforms\":[]}"
}
]
+106
View File
@@ -0,0 +1,106 @@
[
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_New",
"sectionType": 4,
"sectionSubType": "Items_NewlyReleased",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "newitems",
"displayMetadata": "{\r\n\t\"DisplayTitle\":\"New\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Headwear",
"sectionType": 4,
"sectionSubType": "Items_Headwear",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "headwearitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Headwear\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 7,\r\n \"categoryUriNames\": \"HatsItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Tops",
"sectionType": 4,
"sectionSubType": "Items_Tops",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "topsitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Tops\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 8,\r\n \"categoryUriNames\": \"TorsoItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Bottoms",
"sectionType": 4,
"sectionSubType": "Items_Bottoms",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "bottomsitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Bottoms\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 17,\r\n \"categoryUriNames\": \"BottomsItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Footwear",
"sectionType": 4,
"sectionSubType": "Items_Footwear",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "footwearitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Footwear\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 18,\r\n \"categoryUriNames\": \"ShoesItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Waist",
"sectionType": 4,
"sectionSubType": "Items_Waist",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "waistitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Waist\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 23,\r\n \"categoryUriNames\": \"WaistItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Hands",
"sectionType": 4,
"sectionSubType": "Items_Hands",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "handsitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Hands\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 10,\r\n \"categoryUriNames\": \"GlovesItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Shoulder",
"sectionType": 4,
"sectionSubType": "Items_Shoulder",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "shoulderitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Shoulders & Back\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 22,\r\n \"categoryUriNames\": \"ShoulderItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Skins",
"sectionType": 4,
"sectionSubType": "Items_Skins",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "skinsitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Skins\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 5,\r\n \"categoryUriNames\": \"SkinsItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Accessories",
"sectionType": 4,
"sectionSubType": "Items_Accessories",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "accessoriesitems",
"displayMetadata": "{\r\n\t\"DisplayTitle\":\"Accessories\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Hairstyles",
"sectionType": 4,
"sectionSubType": "Items_Hairstyles",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "hairitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Hairstyles\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 24,\r\n \"categoryUriNames\": \"HeadHairItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_FacialHair",
"sectionType": 4,
"sectionSubType": "Items_FacialHair",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "facialhairitems",
"displayMetadata": "{\r\n \"DisplayTitle\":\"Facial Hair\",\r\n \"categoryDepth\":2,\r\n \"categoryCarouselType\": 25,\r\n \"categoryUriNames\": \"FacialHairItems\",\r\n \"itemCount\": 5\r\n}"
},
{
"id": "CustomAvatarItemCarousel_AlgorithmicList_Trending",
"sectionType": 8,
"sectionSubType": "CustomAvatarItems_Trending",
"source": "AlgorithmicList",
"sourceMetadata": "Trending",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Trending Custom Shirts\",\r\n \"itemCount\": 5\r\n}"
}
]
@@ -0,0 +1,74 @@
[
{
"id": "StoreItemCarousel_Storefront_PopUpShop_18",
"sectionType": 4,
"sectionSubType": "Storefront_PopUpShop_18",
"source": "Storefront",
"sourceMetadata": "PopUpShop_18",
"displayMetadata": null
},
{
"id": "StoreItemCarousel_Storefront_PopUpShop_14",
"sectionType": 4,
"sectionSubType": "Storefront_PopUpShop_14",
"source": "Storefront",
"sourceMetadata": "PopUpShop_14",
"displayMetadata": "{\r\n \"itemCount\": 5, \r\n \"numRows\":\"2\"\r\n}"
},
{
"id": "StoreItemCarousel_Store_FoodItems",
"sectionType": 4,
"sectionSubType": "Store_FoodItems",
"source": "Store",
"sourceMetadata": "FoodItems",
"displayMetadata": "{\r\n \"itemCount\": 5, \r\n \"numRows\":\"2\"\r\n}"
},
{
"id": "StoreItemCarousel_Storefront_PopUpShop_12",
"sectionType": 4,
"sectionSubType": "Storefront_PopUpShop_12",
"source": "Storefront",
"sourceMetadata": "PopUpShop_12",
"displayMetadata": null
},
{
"id": "StoreItemCarousel_Storefront_PopUpShop_16",
"sectionType": 4,
"sectionSubType": "Storefront_PopUpShop_16",
"source": "Storefront",
"sourceMetadata": "PopUpShop_16",
"displayMetadata": null
},
{
"id": "StoreItemCarousel_Storefront_PopUpShop_17",
"sectionType": 4,
"sectionSubType": "Storefront_PopUpShop_17",
"source": "Storefront",
"sourceMetadata": "PopUpShop_17",
"displayMetadata": null
},
{
"id": "StoreItemCarousel_Store_Potions",
"sectionType": 4,
"sectionSubType": "Store_Potions",
"source": "UnifiedCuratedList",
"sourceMetadata": "StoreItemList_Potions",
"displayMetadata": "{\r\n \"itemCount\": 5,\r\n \"DisplayTitle\": \"Potions\"\r\n}"
},
{
"id": "StoreItemCarousel_Store_RandomBoxesItems",
"sectionType": 4,
"sectionSubType": "Store_RandomBoxesItems",
"source": "Store",
"sourceMetadata": "RandomBoxesItems",
"displayMetadata": "{\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_Store_KOIconsItems",
"sectionType": 4,
"sectionSubType": "Store_KOIconsItems",
"source": "Store",
"sourceMetadata": "KOIconsItems",
"displayMetadata": "{\r\n \"itemCount\": 5\r\n}"
}
]
+106
View File
@@ -0,0 +1,106 @@
[
{
"id": "StoreItemCarousel_UnifiedCuratedList_InternalMedieval",
"sectionType": 4,
"sectionSubType": "MedeivalItems",
"source": "UnifiedCuratedList",
"sourceMetadata": "Internal_Medieval_Items",
"displayMetadata": "{\r\n\"DisplayTitle\":\"FairyTale Fashions\"\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_UGCMedievalCarousel",
"sectionType": 4,
"sectionSubType": "Generic_MedievalCarousel",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "summerpartycarousel",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Medieval Masterpieces from the Community\"\r\n}"
},
{
"id": "StoreItemCarousel_Storefront_PopUpShop_5",
"sectionType": 4,
"sectionSubType": "Storefront_PopUpShop5",
"source": "Storefront",
"sourceMetadata": "PopUpShop_5",
"displayMetadata": "{\r\n \"itemCount\": 5\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_Trending2Row",
"sectionType": 4,
"sectionSubType": "StoreTrending",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "trendingitems",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Trending\",\r\n\"numRows\":\"2\"\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_NewItemsFromFeaturedCreators",
"sectionType": 4,
"sectionSubType": "StoreNewItemsFromFeaturedCreators",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "newitemsfromcreators",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Featured Creators\",\"numRows\":\"2\"\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_ItemsForYou2Row",
"sectionType": 4,
"sectionSubType": "Items_ForYou",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "itemsforyou",
"displayMetadata": "{\r\n\t\"DisplayTitle\":\"Recommended For You\",\"numRows\":\"2\"\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_BestSellers",
"sectionType": 4,
"sectionSubType": "BestSellingItems",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "bestsellingitems",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Best Sellers\",\"numRows\":\"2\"\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedCuratedList_RoomieItems",
"sectionType": 4,
"sectionSubType": "RoomieItems",
"source": "UnifiedCuratedList",
"sourceMetadata": "StoreItems_Roomie_Collection",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Stuff for Roomie\"\r\n}"
},
{
"id": "StoreItemCarousel_Storefront_PopUpShop_17",
"sectionType": 4,
"sectionSubType": "Storefront_PopUpShop_17",
"source": "Storefront",
"sourceMetadata": "PopUpShop_17",
"displayMetadata": null
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_WishlistCarousel",
"sectionType": 4,
"sectionSubType": "StoreWishlistCarousel",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "wishlistcarousel",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Most Wishlisted\"\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_NewSetsForDisplay",
"sectionType": 4,
"sectionSubType": "NewSetsFromCreators",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "featuredsetscarousel",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Featured Sets\"\r\n}"
},
{
"id": "CustomAvatarItemCarousel_CuratedList_StoreItemList_WeeklyMashup_FantasyBackpack",
"sectionType": 8,
"sectionSubType": "CuratedList_StoreItemList_WeeklyMashup_FantasyBackpack",
"source": "CuratedList",
"sourceMetadata": "CustomAvatarItemCarousel_CuratedList_StoreItemList_WeeklyMashup_FantasyBackpack",
"displayMetadata": "{\r\n\t\"DisplayTitle\":\"Fantasy Backpack\"\r\n}"
},
{
"id": "StoreItemCarousel_UnifiedAlgorithmicList_MostGiftedItems",
"sectionType": 4,
"sectionSubType": "StoreMostGiftedItems",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "mostgifteditems",
"displayMetadata": "{\r\n\"DisplayTitle\":\"Good for Gifts\"\r\n}"
}
]
+58
View File
@@ -0,0 +1,58 @@
[
{
"id": "Rooms_ForYou_WatchHome",
"sectionType": 0,
"sectionSubType": "Rooms_ForYou",
"source": "CarouselEndpoint",
"sourceMetadata": "foryou",
"displayMetadata": "{\"DisplayTitle\":\"Recommended For You\",\"supportsDedupe\":\"false\",\"numRows\":\"2\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"#F3DDCE\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Accounts_OnlineFriends_WatchHome",
"sectionType": 1,
"sectionSubType": "Accounts_OnlineFriends",
"source": "OnlineFriends",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Online Friends\",\"itemCount\":3,\"cardStyle\":\"Horizontal\"}"
},
{
"id": "Rooms_GameAINewWaysToPlayNoRotation_WatchHome",
"sectionType": 0,
"sectionSubType": "Rooms_GameAI",
"source": "PlaylistById",
"sourceMetadata": "640498224290317839",
"displayMetadata": "{\"DisplayTitle\":\"New Ways to Play\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_ContinuePlaying_WatchHome",
"sectionType": 0,
"sectionSubType": "Rooms_ContinuePlaying",
"source": "Recent",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Continue Playing\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_MostPopular",
"sectionType": 0,
"sectionSubType": "Rooms_MostPopular",
"source": "Hot",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Trending\",\"supportsDedupe\":\"true\",\"numRows\":\"1\"}"
},
{
"id": "Rooms_RRO_WatchHome",
"sectionType": 0,
"sectionSubType": "Rooms_RRO",
"source": "Hot",
"sourceMetadata": "rro",
"displayMetadata": "{\"DisplayTitle\":\"Rec Room Originals\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "StoreItemCarousel_AlgorithmicList_ItemsForYou_WatchHome",
"sectionType": 4,
"sectionSubType": "Items_ForYou",
"source": "UnifiedAlgorithmicList",
"sourceMetadata": "itemsforyou",
"displayMetadata": "{\"DisplayTitle\":\"Items You May Like\",\"horizontalScrollEnabled\":\"false\"}"
}
]
+114
View File
@@ -0,0 +1,114 @@
[
{
"id": "Rooms_New_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_New",
"source": "Hot",
"sourceMetadata": "new",
"displayMetadata": "{\"DisplayTitle\":\"New\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\",\"unsupportedPlatforms\":[\"Switch\"]}"
},
{
"id": "RoomCategories_MoodPlaylists_FeelingLucky",
"sectionType": 12,
"sectionSubType": "RoomCategories",
"source": "CuratedList",
"sourceMetadata": "RoomCategories.MoodPlaylists.AlgoEndpoint.FeelingLucky",
"displayMetadata": "{\"DisplayTitle\":\"I'm Feeling Lucky\",\"unsupportedPlatforms\":[\"Switch\",\"Pico\",\"Oculus\"], \"unsupportedInteractionCategories\":[\"VR\"]}"
},
{
"id": "Rooms_RecentlyUpdated_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_RecentlyUpdated",
"source": "CarouselEndpoint",
"sourceMetadata": "recentlyupdated",
"displayMetadata": "{\"DisplayTitle\":\"Recently Updated\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Battle_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_Battle",
"source": "CarouselEndpoint",
"sourceMetadata": "battle_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Battle\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Quests_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_Quests",
"source": "CarouselEndpoint",
"sourceMetadata": "quests_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Quests\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Roleplay_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_Roleplay",
"source": "CarouselEndpoint",
"sourceMetadata": "roleplay_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Roleplay\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Horror_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_Horror",
"source": "CarouselEndpoint",
"sourceMetadata": "horror_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Horror\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Hangout_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_Hangout",
"source": "CarouselEndpoint",
"sourceMetadata": "hangout_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Hangout\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Casual_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_Casual",
"source": "CarouselEndpoint",
"sourceMetadata": "casual_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Casual\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_Explore_AlgoEndpoint_PlayHighlight_TabsTest_Explore",
"sectionType": 0,
"sectionSubType": "Rooms_Exploration",
"source": "CarouselEndpoint",
"sourceMetadata": "explore_algoendpoint",
"displayMetadata": "{\"DisplayTitle\":\"Explore\",\"supportsDedupe\":\"true\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_ContinuePlaying_PlayLibrary",
"sectionType": 0,
"sectionSubType": "Rooms_ContinuePlaying",
"source": "Recent",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Continue Playing\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_SavedForLater_PlayHighlight",
"sectionType": 0,
"sectionSubType": "Rooms_SavedForLater",
"source": "MyPlaylistByName",
"sourceMetadata": "__SavedForLater_Rooms",
"displayMetadata": "{\"DisplayTitle\":\"Saved for Later\",\"supportsDedupe\":\"false\",\"numRows\":\"1\",\"sizePerPlatform\":\"\",\"backgroundColor\":\"\",\"horizontalScrollEnabled\":\"false\", \"minItemsToShowSection\": 1}"
},
{
"id": "Rooms_Favorites_PlayLibrary",
"sectionType": 0,
"sectionSubType": "Rooms_PlayerFavorites",
"source": "MyFavorites",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"Favorites\",\"supportsDedupe\":\"false\",\"horizontalScrollEnabled\":\"false\"}"
},
{
"id": "Rooms_MyRooms_Play",
"sectionType": 0,
"sectionSubType": "myrooms",
"source": "MyCreatedRooms",
"sourceMetadata": null,
"displayMetadata": "{\"DisplayTitle\":\"My Rooms\"}"
}
]
+25
View File
@@ -0,0 +1,25 @@
{
"FeaturedPlayer": {
"Id": 2,
"TitleOverride": null,
"UrlOverride": null
},
"FeaturedRoomGroup": {
"FeaturedRoomGroupId": 1,
"Name": null,
"StartAt": "2020-01-01T00:00:00Z",
"EndAt": "9999-12-31T00:00:00Z",
"Rooms": []
},
"CurrentAnnouncement": {
"Message": "a 2025 server",
"MoreInfoUrl": null
},
"InstagramImages": [
{
"ImageName": "67heheheaaa.jpg",
"ImageUrl": null
}
],
"Videos": []
}
+4
View File
@@ -0,0 +1,4 @@
{
"Results": [],
"TotalResults": 0
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
{
"BenefitLists": {
"0": [
0,
1,
2,
3,
4,
5,
6,
7
],
"1": [
0,
1,
2
],
"2": [
0,
1,
2
],
"3": [
8,
9
],
"4": [
10,
11,
12
],
"5": [
13,
14,
7
]
},
"BenefitLookup": {
"0": {
"CustomSpriteName": "Campus_Club_Card_Token_Icon",
"DetailedText": "You get a {{Tokens}} token box every month. Due to platform restrictions, you must log in each week in order to claim this reward. If you want to claim this reward on another platform you can, but you must login at least once a month on the platform you became a member on.",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
"3": "You get a {{Tokens}} token box every month. You must log in at least once during each 30 day period in order to claim this reward."
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "{{Tokens}} tokens per month! ($11 value)",
"TitleText": "{{Tokens}} bonus tokens per month ($11 USD in monthly value)"
},
"1": {
"CustomSpriteName": "icon_inventory_item",
"DetailedText": "You get a box containing a random 4-star item every week. If you have all 4-star items you will get a 800 token box instead. The week resets at 12 AM UTC on Sundays. Due to platform restrictions, you must login each week in order to claim this reward. If you want to claim this reward on another platform you can, but you must login at least once a month on the platform you became a member on.",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "Weekly 4-star box!",
"TitleText": "Free weekly 4-star box"
},
"10": {
"CustomSpriteName": "UI_Menu_Token_Image_50",
"DetailedText": "/month. Cancel anytime",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "Join RR+ for Endless Rewards!",
"TitleText": "Join RR+ for Endless Rewards!"
},
"11": {
"CustomSpriteName": "icon_currency_coins",
"DetailedText": null,
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "2X token rewards",
"TitleText": "2X token rewards"
},
"12": {
"CustomSpriteName": "icon_RecToken",
"DetailedText": null,
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "{{Tokens}} monthly tokens",
"TitleText": "Monthly Tokens"
},
"13": {
"CustomSpriteName": "icon_currency_dollars",
"DetailedText": "Unlock the ability to turn earned tokens into real-world money",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": true,
"RequiresPublishingEnabled": false,
"ShortText": "Unlock the ability to turn earned tokens into real-world money",
"TitleText": "Unlock the ability to turn earned tokens into real-world money"
},
"14": {
"CustomSpriteName": "Campus_Club_Card_Token_Icon",
"DetailedText": "Get {{Tokens}} every month and a {{RRODiscount}}% discount on eligible items",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "Get {{Tokens}} every month and a {{RRODiscount}}% discount on eligible items",
"TitleText": "Monthly Benefits"
},
"2": {
"CustomSpriteName": "PriceTag",
"DetailedText": "You get {{RRODiscount}}% off all purchases in Rec Room Original token stores (e.g. the watch, merch booth, mirror, cafe, paintball, etc). This does not include Rec Room Original stores that dont use tokens (e.g. Laser Tag, Lost Skulls, and Crescendo)\r",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "{{RRODiscount}}% RRO store discount!",
"TitleText": "{{RRODiscount}}% discount in Rec Room stores that accept tokens "
},
"3": {
"CustomSpriteName": "icon_OwnedCostumePieces",
"DetailedText": "You get early access to items for a limited time and can claim them for free by logging in while they are in early access.",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "Free, early access to items for a limited time!",
"TitleText": "Free, early access to items for a limited time"
},
"4": {
"CustomSpriteName": "Shirt",
"DetailedText": "You get {{MemberOutfitSlots}} outfit slots on top of the regular {{NonMemberOutfitSlots}}. Access them via the Saved Outfits button in the Profile section of your watch or on your Dorm room mirror.",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "{{MemberOutfitSlots}} saved outfit slots!",
"TitleText": "More saved outfit slots"
},
"5": {
"CustomSpriteName": null,
"DetailedText": "You can sell inventions and custom clothing in the watch store and keys, currencies, and consumables in your rooms. You will be paid 70% of all tokens earned through these sales. Those tokens will be held for a week in an escrow account to mitigate fraud, then theyll be delivered to you the same day as your other RR+ bonus tokens.\r",
"EnabledForPlatforms": 511,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": true,
"RequiresPublishingEnabled": false,
"ShortText": "Earn tokens for your creations!",
"TitleText": "Earn tokens for your creations"
},
"6": {
"CustomSpriteName": "icon_RecToken",
"DetailedText": "Whenever you are offered tokens from a post-activity or first activity of the day reward, you will be offered twice the normal amount.",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "Double token rewards!",
"TitleText": "Double token rewards"
},
"7": {
"CustomSpriteName": "icon_Shirt",
"DetailedText": "You can create your own custom shirts with the clothing customizer! You can access the clothing customizer in your backpack, and publish shirts which can be purchased and worn.",
"EnabledForPlatforms": 511,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": true,
"ShortText": "Create your own custom shirts",
"TitleText": "Create your own custom shirts"
},
"8": {
"CustomSpriteName": "Campus_Club_Card_Token_Icon",
"DetailedText": "",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "Plus {{Tokens}} tokens NOW & monthly!",
"TitleText": "Plus {{Tokens}} tokens NOW & monthly!"
},
"9": {
"CustomSpriteName": "icon_Heart",
"DetailedText": "Keep saving {{RRODiscount}}% on every RRO order!",
"EnabledForPlatforms": -1,
"PlatformSpecificDetailedTexts": {
},
"RequiresMonetizationEnabled": false,
"RequiresPublishingEnabled": false,
"ShortText": "And much more!",
"TitleText": "And much more!"
}
},
"HighlightText": "The best deal in Rec Room!",
"MoreDetailsText": "And <u>much more!</u>",
"NumberReplacements": {
"{{MemberOutfitSlots}}": 200,
"{{NonMemberOutfitSlots}}": 16,
"{{RRODiscount}}": 10,
"{{Tokens}}": 6500
},
"StringConfigs": {
"RRPlusMessageBody": "Thanks for becoming a RR+ member. You now have access to all the member benefits!",
"RRPlusMessageTitle": "Welcome to the Club!"
},
"Version": 3
}
+245
View File
@@ -0,0 +1,245 @@
{
"Version": 0,
"SkuConfigs": [
{
"SkuId": 183,
"Name": "{{MakerAIDayPassName}}",
"Description": "{{MakerAIName}} springs to life for {{MakerAIHours}} hours* of creative collaboration!\n\nJust equip your {{MakerPenName}} in any Rooms 2.0 room you own (including Dorms with Maker AI), and your holographic helper can...\n• <b>Create images and patterns</b> on canvases\n• <b>Make objects come to life</b> through AI-generated circuits\n• <b>Generate objects</b> from the {{MakerPenName}} inventory \n• <u><link=\"ID\">Learn more about upcoming features</link> </u>",
"ThumbnailImageName": "img/makerAI_thumb_01.png",
"DetailsImageName": "img/makerAI_storeDetailsPromo_01.png",
"ShowSkuDetails": true,
"Footer": {
"Text": "* {{SubjectToDayPassUsageLimits}}",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
},
"DialogConfigs": {
"PurchaseSuccess": {
"Title": "Day Pass Purchased",
"Text": "Up to 24 hours of {{MakerAIName}} access has been added to your account. The clock starts now, so go equip your Maker Pen in one of your R2 rooms to get started!",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Explore my R2 Rooms",
"Type": "Primary",
"OnClick": "GoToCreate"
}
],
"Footer": {
"Text": "{{LearnMoreAboutUsageLimits}}",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"UpsellAnnouncement": {
"Title": "Make Rooms Magical",
"Text": "Be among the first to try Maker AI in R2 rooms! Our new Day Pass extends beyond RR+ subscribers and dorms, empowering everyone to unleash their imagination.",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Unlock This Deal",
"Type": "Primary",
"OnClick": "GoToDayPass"
}
],
"Cooldown": 10080
},
"UsageLimitReached": {
"Title": "Energy Drained",
"Text": "{{MakerAIName}} hit our day pass usage limits and settled down for a power nap. Ready to wake it up with a fresh Day Pass?",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Wake Up Maker AI",
"Type": "Primary",
"OnClick": "GoToDayPass"
},
{
"Text": "Continue without Maker AI",
"Type": "Secondary",
"OnClick": "DismissDialog"
}
],
"Footer": {
"Text": "{{LearnMoreAboutUsageLimits}}",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"TimeLimitReached": {
"Title": "Time's Up!",
"Text": "{{MakerAIName}} hit the day pass time limit and settled down for a power nap. Ready to wake it up with a fresh Day Pass?",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Wake Up Maker AI",
"Type": "Primary",
"OnClick": "GoToDayPass"
},
{
"Text": "Continue without Maker AI",
"Type": "Secondary",
"OnClick": "DismissDialog"
}
],
"Footer": {
"Text": "{{LearnMoreAboutUsageLimits}}",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"UsageLimitWarning": {
"Title": "Energy Running Low",
"Text": "Heads up, Maker AI is nearing day pass usage limits and will need a power nap soon. Grab another day pass to keep the magic flowing uninterrupted!",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Add More Energy",
"Type": "Primary",
"OnClick": "GoToDayPass"
},
{
"Text": "Ignore",
"Type": "Secondary",
"OnClick": "DismissDialog"
}
],
"Footer": {
"Text": "{{LearnMoreAboutUsageLimits}}",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"TimeLimitWarning": {
"Title": "Time Running Low",
"Text": "Heads up, {{MakerAIName}} has about <b>1 hour left</b> before it needs a recharge. Grab another day pass to keep the magic flowing uninterrupted!",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Add More Time",
"Type": "Primary",
"OnClick": "GoToDayPass"
},
{
"Text": "Ignore",
"Type": "Secondary",
"OnClick": "DismissDialog"
}
],
"Footer": {
"Text": "{{LearnMoreAboutUsageLimits}}",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"ComingSoonToMakerAI": {
"Title": "Coming Soon to {{MakerAIName}}",
"Text": "There are some things Maker AI doesn't know how to do yet, and we're working on it!\n\nThis includes...\n•understanding circuits you've edited manually\n•generating objects out of basic shapes\n•placing objects relative to other objects\n\nSo, <b>\"put the lamp next to the couch\"</b> or <b>\"fix this bug in my circuit\"</b> will be confusing to Maker AI (for now).",
"SpriteName": "icon_MakerAI",
"Buttons": []
},
"ChatError": {
"Title": "Ready to create?",
"Text": "Your building bestie is taking a power nap. Wake it up with a fresh {{MakerAIDayPassName}}!",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Wake up Maker AI",
"Type": "Primary",
"OnClick": "GoToDayPass"
}
]
},
"PurchaseConfirmation": {
"Title": "Up to {{MakerAIHours}} Hours of Power!",
"Text": "Once a day pass is purchased, its timer keeps running even when you're not playing. The pass can't be paused or refunded even if you hit usage limits early. \n\n<size=75%><i>{{PlatformConfirmationMessage}}</i></size>",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Accept & Continue to Payment",
"Type": "Primary",
"OnClick": "PositiveEvent"
}
],
"Footer": {
"Text": "By pressing \"Accept & Continue to Payment\", I expressly agree that access to {{MakerAIName}} will begin immediately, and I acknowledge that I will lose my right to cancel or get a refund once access starts— even if it expires early due to <u>usage limits</u>.",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"DayPassActiveBanner": {
"Title": "Day Pass Active",
"Text": "You currently have {{MakerAITimeLeftDynamic}} of {{MakerAIName}} access. If you buy another pass now, you'll get an additional {{MakerAIHours}} hours.",
"SpriteName": "icon_hourglass",
"Buttons": []
},
"MakerAIUsageLimits": {
"Title": "{{MakerAIName}} Usage Limits and Conditions",
"Text": "<b>Why does Maker AI have usage limits?</b>\nMaker AI is a powerful tool that allows everyone to create, but every action has a cost. Usage limits ensure we can continue to offer Maker AI to everyone.\n\n<b>What are the usage limits?</b>\nWe limit day passes to many hundreds of simple requests or many dozens of our most expensive requests (e.g., images and circuits). Dont worry—this should only affect less than 15% of users. \n\n<b>What happens if I hit these limits?</b>\n•If it looks like you are getting close, we will provide a warning that you're approaching the limit.\n• If you exceed the limit, your day pass will end early. You will be able to buy another pass and continue working.\n\nWe appreciate your understanding as we work to make this tool available to all.\n\n<u><link=\"{{LearnMoreAboutUsageLimitsUrl}}\">Learn More</link></u>",
"SpriteName": "icon_MakerAI",
"Buttons": []
},
"FreeTrialConfirmation": {
"Title": "Try {{MakerAIName}} for free",
"Text": "Get up to {{MakerAIFreeTrialDurationDynamic}} of {{MakerAIName}} in an R2 room you own, free of charge.\n\nOnce your free trial starts, its timer keeps running even when you're not playing. It can't be paused or reactivated even if you hit usage limits early.",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Accept & Start Free Trial",
"Type": "Primary",
"OnClick": "PositiveEvent"
}
],
"Footer": {
"Text": "By pressing \"Accept & Start Free Trial\", I expressly agree that access to {{MakerAIName}} will begin immediately, and I acknowledge that I will lose my right to cancel once access starts—even if it expires early due to <u>usage limits</u>.",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"FreeTrialSuccess": {
"Title": "Free Trial Activated",
"Text": "Up to {{MakerAIFreeTrialDurationDynamic}} of {{MakerAIName}} access has been added to your account, free of charge. The clock starts now, so go equip your {{MakerPenName}} in one of your R2 rooms to get started.",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Explore my R2 Rooms",
"Type": "Primary",
"OnClick": "GoToCreate"
}
],
"Footer": {
"Text": "{{LearnMoreAboutUsageLimits}}",
"ExternalUrl": "{{LearnMoreAboutUsageLimitsUrl}}"
}
},
"ActivateFreeTrialError": {
"Title": "Something Went Wrong",
"Text": "A problem occurred while attempting to activate your free trial, try again in a few minutes. If the problem persists, you can contact us at <u>https://recroom.zendesk.com</u>.",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Ok",
"Type": "Primary",
"OnClick": "DismissDialog"
}
]
},
"FreeTrialAnnouncement": {
"Title": "Creation for Everyone",
"Text": "Get up to {{MakerAIFreeTrialDurationDynamic}} of {{MakerAIName}} in an R2 room you own, free of charge. No strings attached—just pure creative power to generate images, circuits, and more!",
"SpriteName": "icon_MakerAI",
"Buttons": [
{
"Text": "Unlock This Deal",
"Type": "Primary",
"OnClick": "GoToDayPass"
}
],
"Cooldown": 10080
}
}
}
],
"StringReplacements": {
"{{MakerAIName}}": "Maker AI",
"{{MakerAIDayPassName}}": "Maker AI Day Pass",
"{{DayPassNamePlural}}": "Day Passes",
"{{MakerAIHours}}": "24",
"{{SubjectToDayPassUsageLimits}}": "Subject to <u>usage limits</u>. Currently unavailable on Playstation and Nintendo Switch.",
"{{LearnMoreAboutUsageLimits}}": "<u><link=\"MakerAIUsageLimits\">Learn more about day pass usage limits</link></u>",
"{{LearnMoreAboutUsageLimitsUrl}}": "https://rec.net/creator/p/makerai-daypass",
"{{MakerPenName}}": "Maker Pen"
}
}
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 KiB

Some files were not shown because too many files have changed in this diff Show More