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
+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("");
}
}
}