Initialize repository

Added basic info to get in game for like...August 2016 ;-;
It's not much but it's a start
This commit is contained in:
2026-02-27 00:58:13 -08:00
parent 05c35b2a18
commit 387ec7ba89
61 changed files with 1722 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Activities
{
public class CharadesWord(string word, CharadesWordsDifficulty difficulty = CharadesWordsDifficulty.Easy)
{
[JsonPropertyName(name: "Difficulty")] public CharadesWordsDifficulty Difficulty { get; set; } = difficulty;
[JsonPropertyName(name: "EN_US")] public string EN_US { get; set; } = word;
}
}

View File

@@ -0,0 +1,8 @@
namespace RecRoomArchive.Models.API.Activities
{
public enum CharadesWordsDifficulty
{
Easy,
Hard
}
}

View File

@@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Avatar
{
/// <summary>
/// The avatar of the player
/// </summary>
public class PlayerAvatar
{
/// <summary>
/// The outfit that the player has on. This includes data like their hair model, torso, hats, glasses, torso, etc...
/// </summary>
[JsonPropertyName(name: "OutfitSelections")] public string OutfitSelections { get; set; } = string.Empty;
/// <summary>
/// The skin color guid of the player
/// </summary>
[JsonPropertyName(name: "SkinColor")] public string SkinColor { get; set; } = string.Empty;
/// <summary>
/// The hair color guid of the player
/// </summary>
[JsonPropertyName(name: "HairColor")] public string HairColor { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Config
{
public class DailyObjective
{
[JsonPropertyName(name: "type")] public ObjectiveType Type { get; set; }
[JsonPropertyName(name: "score")] public int Score { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Config
{
public class GameConfig
{
[JsonPropertyName(name: "Key")] public required string Key { get; set; }
[JsonPropertyName(name: "Value")] public required string Value { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Config
{
public class LevelProgressionMap(int level, int requiredXp)
{
[JsonPropertyName(name: "Level")] public int Level { get; set; } = level;
[JsonPropertyName(name: "RequiredXp")] public int RequiredXp { get; set; } = requiredXp;
}
}

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Config
{
public class MatchmakingParams
{
[JsonPropertyName(name: "PreferFullRoomsFrequency")] public float PreferFullRoomsFrequency { get; set; } = 1.0f;
[JsonPropertyName(name: "PreferEmptyRoomsFrequency")] public float PreferEmptyRoomsFrequency { get; set; } = 0.0f;
}
}

View File

@@ -0,0 +1,139 @@
namespace RecRoomArchive.Models.API.Config
{
public enum ObjectiveType
{
Default = -1,
FirstSessionOfDay = 1,
AddAFriend,
PartyUp,
AllOtherChallenges,
LevelUp,
CheerAPlayer,
PointedAtPlayer,
CheerARoom,
SubscribeToPlayer,
DailyObjective1,
DailyObjective2,
DailyObjective3,
AllDailyObjectives,
CompleteAnyDaily,
CompleteAnyWeekly,
OOBE_GoToLockerRoom = 20,
OOBE_GoToActivity,
OOBE_FinishActivity,
NUX_PunchcardObjective = 25,
NUX_AllPunchcardObjectives,
GoToRecCenter = 30,
FinishActivity,
VisitACustomRoom,
CreateACustomRoom,
ScoreBasketInRecCenter = 35,
UploadPhotoToRecNet,
UpdatePlayerBio,
SaveOutfitSlot,
PurchaseClothingItem,
PurchaseNonClothingItem,
DrinkWater,
ColorOnWhiteboard,
SetBasketballSkin,
ThrowBasketball,
PlaceInventionInDorm,
ChangeDormRoomSkin,
ToggleOwnedClothes,
EquipHat,
LoadOutfit,
SaveNewOutfitSlot,
SpawnCamera,
TakeSelfie,
PrintSelfie,
TakePictureOfPlayer,
PrintPictureOfPlayer,
PublishSelfieWithPlayer,
SpawnFoodWithOtherPlayers,
EmoteInRecCenter,
SendRoomChatInRecCenter,
UseFrendotron,
GoToDormRoom,
VisitSpecificRoom,
VisitPublicRRO,
VisitPublicRoomBySource,
FavoriteARoom,
TakePhotoWithFilter,
OpenYourPlayerProfile,
OpenOnlineStatusModal,
ChangeProfilePicture,
ChangePlayerDisplayName,
ChangePlayerDescriptionText,
OpenPlayerPronounsModal,
OpenOtherPlayersProfile,
VisitPlayersPortfolio,
FavoriteAFriend,
CharadesGames = 100,
CharadesWinsPerformer,
CharadesWinsGuesser,
DiscGolfWins = 200,
DiscGolfGames,
DiscGolfHolesUnderPar,
DodgeballWins = 300,
DodgeballGames,
DodgeballHits,
PaddleballGames = 400,
PaddleballWins,
PaddleballScores,
PaintballAnyModeGames = 500,
PaintballAnyModeWins,
PaintballAnyModeHits,
PaintballCTFWins = 600,
PaintballCTFGames,
PaintballCTFHits,
PaintballFlagCaptures,
PaintballTeamBattleWins = 700,
PaintballTeamBattleGames,
PaintballTeamBattleHits,
PaintballFreeForAllWins = 710,
PaintballFreeForAllGames,
PaintballFreeForAllHits,
SoccerWins = 800,
SoccerGames,
SoccerGoals,
BowlingGames = 900,
BowlingWins,
BowlingStrike,
QuestGames = 1000,
QuestWins,
QuestPlayerRevives,
QuestEnemyKills,
QuestGames_Goblin1 = 1010,
QuestWins_Goblin1,
QuestPlayerRevives_Goblin1,
QuestEnemyKills_Goblin1,
QuestGames_Goblin2 = 1020,
QuestWins_Goblin2,
QuestPlayerRevives_Goblin2,
QuestEnemyKills_Goblin2,
QuestGames_Scifi1 = 1030,
QuestWins_Scifi1,
QuestPlayerRevives_Scifi1,
QuestEnemyKills_Scifi1,
QuestGames_Pirate1 = 1040,
QuestWins_Pirate1,
QuestPlayerRevives_Pirate1,
QuestEnemyKills_Pirate1,
QuestGames_Dracula1 = 1050,
QuestWins_Dracula1,
QuestPlayerRevives_Dracula1,
QuestEnemyKills_Dracula1,
ArenaGames = 2000,
ArenaWins,
ArenaPlayerRevives,
ArenaHeroTags,
ArenaBotTags,
RecRoyaleGames = 3000,
RecRoyaleWins,
RecRoyaleTags,
StuntRunnerGames = 4000,
StuntRunnerWins,
RecRallyGames = 5000,
RecRallyWins
}
}

View File

@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Config
{
public class PhotonConfig
{
[JsonPropertyName(name: "CloudRegion")] public string CloudRegion { get; set; } = "us";
[JsonPropertyName(name: "CrcCheckEnabled")] public bool CrcCheckEnabled { get; set; } = true;
}
}

View File

@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using System.Xml.Linq;
namespace RecRoomArchive.Models.API.Config
{
public class RecRoomConfig
{
[JsonPropertyName(name: "MessageOfTheDay")] public string MessageOfTheDay { get; set; } = string.Empty;
[JsonPropertyName(name: "CdnBaseUri")] public string CdnBaseUri { get; set; } = string.Empty;
[JsonPropertyName(name: "MatchmakingParams")] public required MatchmakingParams MatchmakingParams { get; set; }
[JsonPropertyName(name: "LevelProgressionMaps")] public required List<LevelProgressionMap> LevelProgressionMaps { get; set; }
[JsonPropertyName(name: "DailyObjectives")] public required DailyObjective[][] DailyObjectives { get; set; }
[JsonPropertyName(name: "PhotonConfig")] public required PhotonConfig PhotonConfig { get; set; }
[JsonPropertyName(name: "ConfigTable")] public required List<GameConfig> ConfigTable { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace RecRoomArchive.Models.API.Messages
{
public enum GameInviteResponseDetails
{
Declined,
Accepted,
ComeToMe,
GiveMe2,
GiveMe5,
GiveMe10
}
}

View File

@@ -0,0 +1,9 @@
namespace RecRoomArchive.Models.API.Messages
{
public enum MessageType
{
GameInvite,
GameInviteResponse,
GameJoinFailed
}
}

View File

@@ -0,0 +1,21 @@
namespace RecRoomArchive.Models.API.Platform
{
/// <summary>
/// The types of platforms that Rec Room has support for...as a mask!
/// </summary>
[Flags]
public enum PlatformMask
{
None = 0,
Steam = 1,
Oculus = 2,
PlayStation = 4,
Xbox = 8,
RecNet = 16,
IOS = 32,
GooglePlay = 64,
Standalone = 128,
Pico = 256,
All = -1
}
}

View File

@@ -0,0 +1,19 @@
namespace RecRoomArchive.Models.API.Platform
{
/// <summary>
/// The types of platforms that Rec Room has support for
/// </summary>
public enum PlatformType
{
All = -1,
Steam,
Oculus,
PlayStation,
Xbox,
RecNet,
IOS,
GooglePlay,
Standalone,
Pico
}
}

View File

@@ -0,0 +1,16 @@
using RecRoomArchive.Models.API.Platform;
namespace RecRoomArchive.Models.API.PlatformLogin.Requests
{
public class BaseLoginRequest
{
public required PlatformType Platform { get; set; }
public required string PlatformId { get; set; }
public string? Name { get; set; }
public required long ClientTimestamp { get; set; }
public required string DeviceId { get; set; }
public required long BuildTimestamp { get; set; }
public required string AuthParams { get; set; }
public required string Verify { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.PlatformLogin.Responses
{
public class BaseLoginResponse
{
[JsonPropertyName(name: "Token")]
public string Token { get; set; } = string.Empty;
[JsonPropertyName(name: "PlayerId")]
public ulong PlayerId { get; set; }
}
}

View File

@@ -0,0 +1,44 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Players
{
/// <summary>
/// Extensions of data used in August 2016 Rec Room
/// </summary>
public class AugustProfile : BaseProfile
{
public AugustProfile(BaseProfile baseProfile)
{
Id = baseProfile.Id;
Username = baseProfile.Username;
DisplayName = baseProfile.DisplayName;
XP = baseProfile.XP;
Level = baseProfile.Level;
Reputation = baseProfile.Reputation;
Developer = baseProfile.Developer;
Bio = baseProfile.Bio;
RegistrationStatus = baseProfile.RegistrationStatus;
CanReceiveInvites = baseProfile.CanReceiveInvites;
ProfileImageName = baseProfile.ProfileImageName;
JuniorProfile = baseProfile.JuniorProfile;
ForceJuniorImages = baseProfile.ForceJuniorImages;
PendingJunior = baseProfile.PendingJunior;
HasBirthday = baseProfile.HasBirthday;
HasEmail = baseProfile.HasEmail;
AvoidJuniors = baseProfile.AvoidJuniors;
}
/// <summary>
/// Used in early versions of Rec Room from 2016 as the players DisplayName
/// </summary>
[JsonPropertyName(name: "Name")] public string Name => DisplayName;
/// <summary>
/// The SteamID of the local player, used in 2016
/// </summary>
[JsonPropertyName(name: "SteamID")] public ulong SteamID { get; set; }
/// <summary>
/// The..gender?? of the local player, this isn't used by Rec Room at all so I'm guessing whoever made their webmanager just added this to add it
/// </summary>
[JsonPropertyName(name: "Gender")] public string Gender { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,84 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Players
{
/// <summary>
/// The profile of the player from 2016-2019
/// </summary>
public class BaseProfile
{
/// <summary>
/// The unique id of the player
/// </summary>
[JsonPropertyName(name: "Id")] public ulong Id { get; set; }
/// <summary>
/// The username of the player. This usually doesn't show up in game until around 2017...
/// </summary>
[JsonPropertyName(name: "Username")] public string Username { get; set; } = string.Empty;
/// <summary>
/// The display name of the player, this is what's most commonly used in game and is seen on the players nametag
/// </summary>
[JsonPropertyName(name: "DisplayName")] public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// The XP of the player, this determines how much XP is required until the next level up but because this is a local server, who gaf
/// </summary>
[JsonPropertyName(name: "XP")] public int XP { get; set; } = 0;
/// <summary>
/// The level of the player, usually in a range from 1-30 or 1-50 depending on your time period
/// </summary>
[JsonPropertyName(name: "Level")] public int Level { get; set; } = 1;
/// <summary>
/// The internal reputation of the player, reputation works in mysterious ways so I'm not too sure about this one...
/// </summary>
[JsonPropertyName(name: "Reputation")] public float Reputation { get; set; } = 1.0f;
/// <summary>
/// If the player has a verified email on their Rec Room account (which they always will)
/// </summary>
[JsonPropertyName(name: "Verified")] public bool Verified => RegistrationStatus == RegistrationStatus.Registered;
/// <summary>
/// If the player is a developer of Rec Room (they always will be)
/// </summary>
[JsonPropertyName(name: "Developer")] public bool Developer { get; set; } = true;
/// <summary>
/// The bio of the player, I need to find what build this is added in so I can add it to its model...
/// </summary>
[JsonPropertyName(name: "Bio")] public string Bio { get; set; } = string.Empty;
/// <summary>
/// The registration status of the player, determined by if they have an email
/// </summary>
[JsonPropertyName(name: "RegistrationStatus")] public RegistrationStatus RegistrationStatus { get; set; } = RegistrationStatus.Registered;
/// <summary>
/// If the local player is allowed to recieve invites (junior restriction?)
/// </summary>
[JsonPropertyName(name: "CanReceiveInvites")] public bool CanReceiveInvites { get; set; } = true;
/// <summary>
/// The image name of the player
/// </summary>
[JsonPropertyName(name: "ProfileImageName")] public string ProfileImageName { get; set; } = "DefaultProfileImage";
/// <summary>
/// If the player is allowed to recieve invites (junior restriction?)
/// </summary>
[JsonPropertyName(name: "JuniorProfile")] public bool JuniorProfile { get; set; } = false;
/// <summary>
/// If the player is forced to only see alt images like a junior player
/// </summary>
[JsonPropertyName(name: "ForceJuniorImages")] public bool ForceJuniorImages { get; set; } = false;
/// <summary>
/// If the player is about to become a junior
/// </summary>
[JsonPropertyName(name: "PendingJunior")] public bool PendingJunior { get; set; } = false;
/// <summary>
/// If the player has a birthday on their account
/// </summary>
[JsonPropertyName(name: "HasBirthday")] public bool HasBirthday { get; set; } = true;
/// <summary>
/// If the player has an email on their account
/// </summary>
[JsonPropertyName(name: "HasEmail")] public bool HasEmail { get; set; } = true;
/// <summary>
/// If the player perfers to not matchmake with junior players, can be null
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull), JsonPropertyName(name: "AvoidJuniors")] public bool? AvoidJuniors { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Players
{
public class BlockDurationDTO
{
[JsonPropertyName(name: "BlockedDuration")] public int BlockedDuration { get; set; } = 0;
}
}

View File

@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Players
{
public class PhoneNumberDTO
{
[JsonPropertyName(name: "PhoneNumber")] public string PhoneNumber { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,21 @@
namespace RecRoomArchive.Models.API.Players
{
/// <summary>
/// The status of the players registration to Rec Room
/// </summary>
public enum RegistrationStatus
{
/// <summary>
/// This player has no email entered for Rec Room and may be prompted to enter one
/// </summary>
Unregistered,
/// <summary>
/// This player has a pending email from Rec Room that they have not accepted yet
/// </summary>
PendingEmailVerification,
/// <summary>
/// This player has a verified Rec Room Profile!
/// </summary>
Registered
}
}

View File

@@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.API.Setting
{
/// <summary>
/// The players local settings and preferences
/// </summary>
public class Setting
{
/// <summary>
/// The key of the setting (ex: DebugGuiEnabled)
/// </summary>
[JsonPropertyName(name: "Key")] public required string Key { get; set; }
/// <summary>
/// The value related to the key (ex: true)
/// </summary>
[JsonPropertyName(name: "Value")] public required string Value { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace RecRoomArchive.Models.Common
{
public class OkResponse
{
public static OkResponse Ok(string? message = null) => new() { Success = true, Message = message };
public static OkResponse Fail(string message) => new() { Success = false, Message = message };
[JsonPropertyName("Success")] public bool Success { get; set; }
[JsonPropertyName("Message")] public string? Message { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

4
RecRoomArchive.slnx Normal file
View File

@@ -0,0 +1,4 @@
<Solution>
<Project Path="RecRoomArchive.Models/RecRoomArchive.Models.csproj" />
<Project Path="RecRoomArchive/RecRoomArchive.csproj" />
</Solution>

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Activities;
using RecRoomArchive.Services;
namespace RecRoomArchive.Controllers.API.Activities.Charades.V1
{
[Route(template: "api/[controller]/charades/v1")]
[ApiController]
public class ActivitiesController(MessageOfTheDayService motdService) : ControllerBase
{
// TODO: Move out of MOTD service
[HttpGet(template: "words")]
public async Task<ActionResult<List<CharadesWord>>> GetCharadesWords()
{
return Ok(await motdService.GetCharadesWordsList());
}
}
}

View File

@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Avatar;
using RecRoomArchive.Services;
using System.Text.Json;
namespace RecRoomArchive.Controllers.API.Avatar.V2
{
[Route(template: "api/[controller]/v2")]
[ApiController]
public class AvatarController(FileService fileService) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<PlayerAvatar>> GetAvatar()
{
var avatarData = fileService.GetData("avatar.json");
if (string.IsNullOrWhiteSpace(avatarData))
{
var baseAvatar = new PlayerAvatar();
fileService.SetData("avatar.json", JsonSerializer.Serialize(baseAvatar));
return Ok(baseAvatar);
}
var avatar = JsonSerializer.Deserialize<PlayerAvatar>(avatarData);
return Ok(avatar);
}
[HttpGet(template: "gifts")]
public async Task<ActionResult<List<object>>> GetPendingGifts()
{
return Ok(new List<object>());
}
[HttpPost(template: "set")]
public async Task<ActionResult<PlayerAvatar>> SetAvatar(PlayerAvatar avatar)
{
fileService.SetData("avatar.json", JsonSerializer.Serialize(avatar));
return Ok();
}
}
}

View File

@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
namespace RecRoomArchive.Controllers.API.Avatar.V3
{
[Route(template: "api/[controller]/v3")]
[ApiController]
public class AvatarController() : ControllerBase
{
// TODO: Implement
[HttpGet(template: "items")]
public async Task<ActionResult<List<object>>> GetUnlockedAvatarItems()
{
return Ok(new List<object>());
}
}
}

View File

@@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Services;
namespace RecRoomArchive.Controllers.API.Config.V1
{
/// <summary>
/// Configs that control Rec Room. These configs include data like Amplitude, MessageOfTheDay, Objectives, etc...
/// </summary>
[Route(template: "api/[controller]/v1")]
[ApiController]
public class ConfigController(MessageOfTheDayService motdService) : ControllerBase
{
/// <summary>
/// Gets the legacy version of the Message of the Day, to display on the Dorm Room bulletin board, or to show on the login screen in pre-Dorm Room versions of the game
/// </summary>
/// <returns>A basic string to display in game, related to the Message of the Day</returns>
[HttpGet(template: "motd")]
public async Task<ActionResult<string>> GetMessageOfTheDay()
{
var messageOfTheDay = await motdService.GetMessageOfTheDay();
return Ok(messageOfTheDay);
}
/// <summary>
/// Returns the current daily objectives to use before config/v2 existed
/// </summary>
/// <returns>A list of objectives</returns>
[HttpGet(template: "objectives")]
public async Task<ActionResult<List<object>>> GetDailyObjectives()
{
return Ok(new List<object>());
}
}
}

View File

@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Config;
using RecRoomArchive.Services;
namespace RecRoomArchive.Controllers.API.Config.V2
{
/// <summary>
/// Configs that control Rec Room. These configs include data like Amplitude, MessageOfTheDay, Objectives, etc...
/// </summary>
[Route(template: "api/[controller]/v2")]
[ApiController]
public class ConfigController(ConfigService configService) : ControllerBase
{
/// <summary>
/// Gets the RecRoomConfig, containing lots of data on how the game should function
/// </summary>
/// <returns>RecRoomConfig</returns>
[HttpGet]
public async Task<ActionResult<RecRoomConfig>> GetRecRoomConfig()
{
var recRoomConfig = await configService.GetRecRoomConfig();
return Ok(recRoomConfig);
}
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Players;
using RecRoomArchive.Services;
using System.Text.Json;
namespace RecRoomArchive.Controllers.API.Images.V2
{
[Route("api/[controller]/v2")]
[ApiController]
public class ImagesController(FileService fileService, ImageService imageService) : ControllerBase
{
[HttpPost(template: "profile")]
public async Task<ActionResult> SetProfileImage(IFormFile image)
{
using var stream = image.OpenReadStream();
var imageName = await imageService.SaveImageAsync(stream);
var profileData = fileService.GetData("profile.json");
if (profileData == null)
return BadRequest("Profile is not populated");
var profile = JsonSerializer.Deserialize<BaseProfile>(profileData)!;
profile.ProfileImageName = imageName;
fileService.SetData("profile.json", JsonSerializer.Serialize(profile));
return Ok();
}
}
}

View File

@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
namespace RecRoomArchive.Controllers.API.Messages.V2
{
[Route(template: "api/[controller]/v2")]
[ApiController]
public class MessagesController : ControllerBase
{
[HttpGet(template: "get")]
public async Task<ActionResult<List<object>>> GetMessages()
{
return new List<object>();
}
}
}

View File

@@ -0,0 +1,46 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.PlatformLogin.Requests;
using RecRoomArchive.Models.API.PlatformLogin.Responses;
using RecRoomArchive.Services;
namespace RecRoomArchive.Controllers.API.PlatformLogin.V2
{
/// <summary>
/// Used to login to accounts on Rec Room
/// </summary>
[Route(template: "api/[controller]/v2")]
[ApiController]
public class PlatformLoginController(AppVersionService appVersionService, AccountService accountService, AuthorizationService authorizationService) : ControllerBase
{
/// <summary>
/// Checks if the appVersion provided is allowed to play (which it most certainly will be unless its a weird build or someone messed with it)
/// </summary>
/// <returns>An Ok response if the version is valid, Forbid if it is not. Forbid will yield the client displaying "Rec Room Update Required" soo maybe don't</returns>
[HttpPost]
public async Task<ActionResult> Login([FromHeader(Name = "X-Rec-Room-Version")] string appVersion, [FromForm] BaseLoginRequest loginRequest)
{
var username = loginRequest.Name ?? string.Empty;
var buildTimestamp = loginRequest.BuildTimestamp;
// We are going to store the appVersion on login now...
await appVersionService.StoreAppVersion(appVersion);
await appVersionService.StoreBuildTimestamp(buildTimestamp);
// See if a profile exists yet...
if(!accountService.AccountExists())
{
// ...if not, create it!
accountService.CreateAccount(username);
}
var accountId = accountService.GetSelfAccount()!.Id;
return Ok(new BaseLoginResponse
{
Token = authorizationService.GenerateToken(accountId),
PlayerId = accountId
});
}
}
}

View File

@@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Players;
using RecRoomArchive.Services;
using System.ComponentModel.DataAnnotations;
namespace RecRoomArchive.Controllers.API.Players
{
/// <summary>
/// Used in August 2016 RecNet
/// </summary>
[Route(template: "api/[controller]")]
[ApiController]
public class PlayersController(AccountService accountService) : ControllerBase
{
/// <summary>
/// Returns the profile tied to a SteamID (If it exists)
/// </summary>
/// <param name="steamId">The SteamID of the player we are trying to get the profile of</param>
/// <returns>The profile of the player, if it exists</returns>
[HttpGet]
public async Task<ActionResult<AugustProfile>> GetProfile([Required, FromQuery] ulong steamId)
{
var baseProfile = accountService.GetSelfAccount();
if (baseProfile == null)
return NotFound();
var profile = new AugustProfile(baseProfile)
{
SteamID = steamId
};
return profile;
}
/// <summary>
/// Creates a profile and links it to a SteamID if the player does not already exist
/// </summary>
/// <param name="steamId">The SteamID of the player that we are creating a profile for</param>
/// <param name="username">The Steam username of the player that we are creating a profile for</param>
/// <returns>A new AugustProfile</returns>
[HttpPost]
public async Task<ActionResult<AugustProfile>> CreateProfile(
[Required, FromForm(Name = "SteamID")] ulong steamId,
[Required, FromForm(Name = "Name")] string username)
{
if (!accountService.AccountExists())
{
accountService.CreateAccount(username);
}
var baseProfile = accountService.GetSelfAccount();
if (baseProfile == null)
return NotFound();
var profile = new AugustProfile(baseProfile)
{
SteamID = steamId
};
return profile;
}
/// <summary>
/// Stores data related to the player onto the server
/// </summary>
/// <param name="profileId">The Id of the Profile we are storing data for</param>
/// <param name="model">The data the client posts...it happens to be an entire profile but we can take only the data we need :)</param>
/// <returns>A successful response, likely will just return the updated profile</returns>
[HttpPut(template: "{profileId:long}")]
public async Task<ActionResult<AugustProfile>> UpdateProfile([Required] ulong profileId, [FromBody] AugustProfile model)
{
return (AugustProfile)accountService.GetSelfAccount()!;
}
}
}

View File

@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Config;
using RecRoomArchive.Models.API.Platform;
using RecRoomArchive.Models.API.Players;
using RecRoomArchive.Services;
using System.ComponentModel.DataAnnotations;
namespace RecRoomArchive.Controllers.API.Players.V1
{
/// <summary>
/// Used to get accounts / profiles in 2016-2020 although gets phased out as time goes on...
/// </summary>
[Route(template: "api/[controller]/v1")]
[ApiController]
public class PlayersController(AccountService accountService) : ControllerBase
{
/// <summary>
/// Returns the profile of this ID (If it exists)
/// </summary>
/// <param name="id">The ID of the player we are trying to get the profile of</param>
/// <returns>The profile of the player, if it exists</returns>
[HttpGet(template: "{id:long}")]
public async Task<ActionResult<BaseProfile>> GetProfile([Required] ulong id)
{
return accountService.GetSelfAccount()!;
}
[HttpPost(template: "listByPlatformId")]
public async Task<ActionResult<List<object>>> GetFromServer([FromForm] PlatformType platform, [FromForm] List<ulong> platformIds)
{
return Ok(new List<object>());
}
[HttpGet(template: "search/{query}")]
public async Task<ActionResult<List<BaseProfile>>> SearchForProfiles(string query)
{
return Ok(new List<BaseProfile>());
}
[HttpGet(template: "phoneLastFour")]
public async Task<ActionResult<PhoneNumberDTO>> GetPhoneLastFour()
{
return Ok(new PhoneNumberDTO());
}
[HttpGet(template: "blockDuration")]
public async Task<ActionResult<BlockDurationDTO>> GetBlockDuration()
{
return Ok(new BlockDurationDTO());
}
[HttpPost(template: "objectives")]
public async Task<ActionResult> CompleteObjectives(object objective)
{
return Ok();
}
}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Players;
using RecRoomArchive.Models.Common;
using RecRoomArchive.Services;
using System.Text.Json;
namespace RecRoomArchive.Controllers.API.Players.V2
{
/// <summary>
/// Used to modify accounts / profiles in 2017-2019 although gets phased out as time goes on...
/// </summary>
[Route(template: "api/[controller]/v2")]
[ApiController]
public class PlayersController(FileService fileService) : ControllerBase
{
/// <summary>
/// Sets the players displayName
/// </summary>
/// <param name="name">The displayName the player is requesting</param>
/// <returns>OkResponse</returns>
[HttpPost(template: "displayName")]
public async Task<ActionResult<OkResponse>> SetDisplayName([FromForm(Name = "Name")] string name)
{
var profileData = fileService.GetData("profile.json");
if (profileData == null)
return BadRequest("Profile is not populated");
var profile = JsonSerializer.Deserialize<BaseProfile>(profileData)!;
profile.DisplayName = name;
fileService.SetData("profile.json", JsonSerializer.Serialize(profile));
return Ok(OkResponse.Ok());
}
}
}

View File

@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
namespace RecRoomArchive.Controllers.API.Relationships.V2
{
[Route(template: "api/[controller]/v2")]
[ApiController]
public class RelationshipsController : ControllerBase
{
[HttpGet(template: "get")]
public async Task<ActionResult<List<object>>> GetRelationships()
{
return new List<object>();
}
}
}

View File

@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Models.API.Avatar;
using RecRoomArchive.Models.API.Setting;
using RecRoomArchive.Services;
using System.Text.Json;
namespace RecRoomArchive.Controllers.API.Settings.V2
{
[Route(template: "api/[controller]/v2")]
[ApiController]
public class SettingsController(FileService fileService) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<List<Setting>>> GetSettings()
{
var settingsData = fileService.GetData("settings.json");
if (string.IsNullOrWhiteSpace(settingsData))
{
return Ok(new List<Setting>());
}
var settings = JsonSerializer.Deserialize<List<Setting>>(settingsData);
return Ok(settings);
}
[HttpPost(template: "set")]
public async Task<ActionResult> SetSetting(Setting setting)
{
var settingsData = fileService.GetData("settings.json");
var settings = string.IsNullOrWhiteSpace(settingsData) ? [] : JsonSerializer.Deserialize<List<Setting>>(settingsData) ?? [];
settings.RemoveAll(x => x.Key == setting.Key);
settings.Add(setting);
fileService.SetData("settings.json", JsonSerializer.Serialize(settings));
return Ok();
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Services;
namespace RecRoomArchive.Controllers.API.VersionCheck.V1
{
/// <summary>
/// Endpoints used to check if the version the player is playing on is up to date enough to play Rec Room, due to this being a custom server, it doesn't really matter
/// </summary>
[Route(template: "api/[controller]/v1")]
[ApiController]
public class VersionCheckController(AppVersionService appVersionService) : ControllerBase
{
/// <summary>
/// Checks if the appVersion provided is allowed to play (which it most certainly will be unless its a weird build or someone messed with it)
/// </summary>
/// <returns>An Ok response if the version is valid, Forbid if it is not. Forbid will yield the client displaying "Rec Room Update Required" soo maybe don't</returns>
[HttpGet]
public async Task<ActionResult> CheckVersion([FromHeader(Name = "X-Rec-Room-Version")] string appVersion)
{
if (appVersion == null)
return Forbid();
return Ok();
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Services;
using System.Net.Mime;
namespace RecRoomArchive.Controllers.Images
{
[Route("img")]
[ApiController]
public class RootController(ImageService imageService) : ControllerBase
{
[HttpGet(template: "{imageName}")]
public async Task<IActionResult> GetImage(string imageName)
{
var image = await imageService.GetImage(imageName);
if (image == null)
return NotFound();
return File(image, MediaTypeNames.Image.Png);
}
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc;
using RecRoomArchive.Services;
namespace RecRoomArchive.Controllers
{
/// <summary>
/// Only exists for pre-RecNet versions of Rec Room to be able to fetch a Message of the Day from the server...
/// </summary>
[ApiController]
public class RootController(MessageOfTheDayService motdService) : ControllerBase
{
/// <summary>
/// Gets the legacy version of the Message of the Day, to display on the login screen in pre-Dorm Room versions of the game
/// </summary>
/// <returns>A basic string to display in game, related to the Message of the Day</returns>
[HttpGet(template: "motd")]
public async Task<ActionResult<string>> GetMessageOfTheDay()
{
var messageOfTheDay = await motdService.GetMessageOfTheDay();
return Ok(messageOfTheDay);
}
}
}

40
RecRoomArchive/Program.cs Normal file
View File

@@ -0,0 +1,40 @@
using RecRoomArchive.Services;
namespace RecRoomArchive
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
#region Services
builder.Services.AddControllers();
builder.Services.AddOpenApi();
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<AccountService>();
builder.Services.AddScoped<AppVersionService>();
builder.Services.AddScoped<AuthorizationService>();
builder.Services.AddScoped<ConfigService>();
builder.Services.AddScoped<FileService>();
builder.Services.AddScoped<ImageService>();
builder.Services.AddScoped<MessageOfTheDayService>();
#endregion
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
}
}
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5090",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7040;http://localhost:5090",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RecRoomArchive.Models\RecRoomArchive.Models.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@RecRoomArchive_HostAddress = http://localhost:5090
GET {{RecRoomArchive_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,72 @@
using RecRoomArchive.Models.API.Players;
using System.Security.Cryptography;
using System.Text.Json;
namespace RecRoomArchive.Services
{
public class AccountService
{
public bool AccountExists()
{
return File.Exists("data/profile.json");
}
public bool CreateAccount(string? username = null)
{
PopulateServerData();
if (string.IsNullOrEmpty(username))
{
username = GetRandomUsername();
}
Console.WriteLine($"Creating account for {username}");
var profile = new BaseProfile
{
Id = (ulong)RandomNumberGenerator.GetInt32(1000, 9999999),
Username = username,
DisplayName = username
};
File.WriteAllText("data/profile.json", JsonSerializer.Serialize(profile));
return File.Exists("data/profile.json");
}
public BaseProfile? GetSelfAccount()
{
return JsonSerializer.Deserialize<BaseProfile>(File.ReadAllText("data/profile.json"));
}
private static string GetRandomUsername()
{
int randomFourDigits = RandomNumberGenerator.GetInt32(1000, 9999);
return $"RRA-User_{randomFourDigits}";
}
private static void PopulateServerData()
{
string basePath = "data";
string[] directories = ["rooms", "images", "blobs"];
string[] files = [];
Directory.CreateDirectory(basePath);
foreach (var directory in directories)
{
Directory.CreateDirectory(Path.Combine(basePath, directory));
}
foreach (var file in files)
{
string fullPath = Path.Combine(basePath, file);
if (!File.Exists(fullPath))
{
File.WriteAllText(fullPath, string.Empty);
}
}
}
}
}

View File

@@ -0,0 +1,85 @@
namespace RecRoomArchive.Services
{
/// <summary>
/// Used to get the appVersion from the client to determine how to run the server
/// </summary>
public class AppVersionService
{
/// <summary>
/// AppVersion reference
/// </summary>
private static string? AppVersion { get; set; }
/// <summary>
/// The AppVersion as it would want to be seen by the game
/// </summary>
private static string? FullAppVersion { get; set; }
/// <summary>
/// The BuildTimestamp of the build, this is a long that when converted to ticks, will be the accurate time at which the game was built.
/// This only exists on some builds so I wouldn't rely on it too much
/// </summary>
private static DateTime? BuildTimestamp { get; set; }
/// <summary>
/// The BuildTimestamp as it would want to be seen by the game
/// </summary>
private static long? FullBuildTimestamp { get; set; }
/// <summary>
/// To store the AppVersion of the current build
/// </summary>
/// <param name="appVersion">The version of the game</param>
/// <returns>If the operation was a success</returns>
public async Task<bool> StoreAppVersion(string appVersion)
{
if (appVersion == null)
return false;
// To remove any _EA's or .01's or just any weird Rec Room bullshit from the build's name
var standardizedAppVersion = await ParseAppVersion(appVersion);
// idrk if storing both of these is overkill
FullAppVersion = appVersion;
AppVersion = standardizedAppVersion;
Console.WriteLine($"appVersion: {FullAppVersion}, standardizedAppVersion: {AppVersion}");
return true;
}
/// <summary>
/// To store the BuildTimestamp of the current build
/// </summary>
/// <param name="buildTimestamp">The BuildTimestamp of the game</param>
/// <returns>If the operation was a success</returns>
public async Task<bool> StoreBuildTimestamp(long buildTimestamp)
{
if (buildTimestamp == 0)
return false;
DateTime buildTimestampDateTime = new(buildTimestamp);
// same here
FullBuildTimestamp = buildTimestamp;
BuildTimestamp = buildTimestampDateTime;
Console.WriteLine($"buildTimestamp: {FullBuildTimestamp}, buildTimestampDateTime: {BuildTimestamp}");
return true;
}
/// <summary>
/// Parses the appVersion to "remove any _EA's or .01's or just any weird Rec Room bullshit from the build's name"
/// </summary>
/// <param name="appVersion">The version of the game</param>
/// <returns>The standardized string of the appVersion</returns>
public async Task<string> ParseAppVersion(string appVersion)
{
// To remove any _EA's or .01's or just any weird Rec Room bullshit from the build's name
int index = appVersion.IndexOfAny(['_', '.']);
string standardizedAppVersion = index >= 0 ? appVersion[..index] : appVersion;
return standardizedAppVersion;
}
}
}

View File

@@ -0,0 +1,34 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace RecRoomArchive.Services
{
public class AuthorizationService
{
private static readonly string Key = "hello diddy blud. you need to replace me. or not, really it doesnt matter...I forgot how long a token like this has to be so I'm just gonna run my mouth. Hi. This is RecRoomArchive. You may wonder why we have JWT tokens in a localhost server, and that's because 2020 requires it and stuff. and 2019. Especially 2021. I wonder if I will be doing 2021 or not...";
public string GenerateToken(ulong id)
{
JwtSecurityTokenHandler handler = new();
List<Claim> claims = new List<Claim>()
{
new(ClaimTypes.NameIdentifier, id.ToString()),
new(ClaimTypes.Role, "gameClient")
};
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.Add(TimeSpan.FromHours(12)),
Issuer = "https://recroomarchive.org/",
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Key)), SecurityAlgorithms.HmacSha256)
};
JwtSecurityToken token = handler.CreateJwtSecurityToken(tokenDescriptor);
return handler.WriteToken(token);
}
}
}

View File

@@ -0,0 +1,188 @@
using RecRoomArchive.Models.API.Config;
namespace RecRoomArchive.Services
{
/// <summary>
/// I don't even want to explain this one. I'm sorry
/// </summary>
public class ConfigService(MessageOfTheDayService motdService, IHttpContextAccessor httpContextAccessor)
{
private readonly MessageOfTheDayService _motdService = motdService;
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
/// <summary>
/// I'm only doing this because I think that having the full Rec Room config in a controller would be very very ugly.
/// </summary>
/// <returns>RecRoomConfig</returns>
public async Task<RecRoomConfig> GetRecRoomConfig()
{
var request = _httpContextAccessor.HttpContext?.Request;
var host = $"{request!.Scheme}://{request.Host}";
return new RecRoomConfig()
{
MessageOfTheDay = await _motdService.GetMessageOfTheDay(),
CdnBaseUri = host,
MatchmakingParams = new MatchmakingParams(),
LevelProgressionMaps =
[
new LevelProgressionMap(0, 0),
new LevelProgressionMap(1, 10),
new LevelProgressionMap(2, 10),
new LevelProgressionMap(3, 10),
new LevelProgressionMap(4, 20),
new LevelProgressionMap(5, 20),
new LevelProgressionMap(6, 20),
new LevelProgressionMap(7, 20),
new LevelProgressionMap(8, 20),
new LevelProgressionMap(9, 20),
new LevelProgressionMap(10, 20),
new LevelProgressionMap(11, 45),
new LevelProgressionMap(12, 45),
new LevelProgressionMap(13, 45),
new LevelProgressionMap(14, 45),
new LevelProgressionMap(15, 45),
new LevelProgressionMap(16, 45),
new LevelProgressionMap(17, 45),
new LevelProgressionMap(18, 45),
new LevelProgressionMap(19, 45),
new LevelProgressionMap(20, 45),
new LevelProgressionMap(21, 115),
new LevelProgressionMap(22, 115),
new LevelProgressionMap(23, 115),
new LevelProgressionMap(24, 115),
new LevelProgressionMap(25, 115),
new LevelProgressionMap(26, 115),
new LevelProgressionMap(27, 115),
new LevelProgressionMap(28, 115),
new LevelProgressionMap(29, 115),
new LevelProgressionMap(30, 115)
],
DailyObjectives =
[
[
new DailyObjective()
{
Type = ObjectiveType.QuestGames_Scifi1,
Score = 1
},
new DailyObjective()
{
Type = ObjectiveType.QuestEnemyKills_Scifi1,
Score = 10
},
new DailyObjective()
{
Type = ObjectiveType.ArenaGames,
Score = 1
}
],
[
new DailyObjective()
{
Type = ObjectiveType.PaintballCTFGames,
Score = 2
},
new DailyObjective()
{
Type = ObjectiveType.FinishActivity,
Score = 1
},
new DailyObjective()
{
Type = ObjectiveType.CheerAPlayer,
Score = 1
}
],
[
new DailyObjective()
{
Type = ObjectiveType.DodgeballGames,
Score = 2
},
new DailyObjective()
{
Type = ObjectiveType.DodgeballWins,
Score = 2
},
new DailyObjective()
{
Type = ObjectiveType.CheerAPlayer,
Score = 1
}
],
[
new DailyObjective()
{
Type = ObjectiveType.PaintballTeamBattleWins,
Score = 2
},
new DailyObjective()
{
Type = ObjectiveType.PaintballTeamBattleGames,
Score = 20
},
new DailyObjective()
{
Type = ObjectiveType.FinishActivity,
Score = 1
}
],
[
new DailyObjective()
{
Type = ObjectiveType.QuestGames_Goblin1,
Score = 1
},
new DailyObjective()
{
Type = ObjectiveType.QuestEnemyKills_Goblin1,
Score = 10
},
new DailyObjective()
{
Type = ObjectiveType.CheerAPlayer,
Score = 1
}
],
[
new DailyObjective()
{
Type = ObjectiveType.PaintballAnyModeGames,
Score = 2
},
new DailyObjective()
{
Type = ObjectiveType.PaintballAnyModeHits,
Score = 20
},
new DailyObjective()
{
Type = ObjectiveType.ArenaGames,
Score = 1
}
],
[
new DailyObjective()
{
Type = ObjectiveType.QuestGames_Goblin2,
Score = 1
},
new DailyObjective()
{
Type = ObjectiveType.QuestEnemyKills_Goblin2,
Score = 10
},
new DailyObjective()
{
Type = ObjectiveType.FinishActivity,
Score = 1
}
]
],
ConfigTable = [],
PhotonConfig = new PhotonConfig()
};
}
}
}

View File

@@ -0,0 +1,58 @@
using System.Xml.Linq;
namespace RecRoomArchive.Services
{
public class FileService
{
private static void PopulateServerData()
{
string basePath = "data";
string[] directories = ["rooms", "images", "blobs"];
string[] files = ["rooms.json", "avatar.json", "settings.json", "profile.json"];
Directory.CreateDirectory(basePath);
foreach (var directory in directories)
{
Directory.CreateDirectory(Path.Combine(basePath, directory));
}
foreach (var file in files)
{
string fullPath = Path.Combine(basePath, file);
if (!File.Exists(fullPath))
{
File.WriteAllText(fullPath, string.Empty);
}
}
}
public string? GetData(string name)
{
var path = $"data/{name}";
if (!File.Exists(path))
return null;
return File.ReadAllText(path);
}
public void SetData(string name, string data)
{
var path = $"data/{name}";
File.WriteAllText(path, data);
}
public bool FileExists(string name)
{
var path = $"data/{name}";
if (!File.Exists(path))
return false;
return true;
}
}
}

View File

@@ -0,0 +1,44 @@
namespace RecRoomArchive.Services
{
public class ImageService(FileService fileService)
{
private static readonly HttpClient httpClient = new();
private readonly FileService _fileService = fileService;
public async Task<Stream?> GetImage(string imageName)
{
var path = $"images/{imageName}";
if (!_fileService.FileExists(path))
{
var response = await httpClient.GetAsync($"https://cdn.rec.net/img/{imageName}");
if (!response.IsSuccessStatusCode)
return null;
return await response.Content.ReadAsStreamAsync();
}
return new FileStream(Path.Combine("data", path), FileMode.Open, FileAccess.Read, FileShare.Read);
}
public async Task<string> SaveImageAsync(Stream imageStream)
{
var imageName = $"{GetRandomFileName()}.jpg";
var path = ("data/images");
var fullPath = Path.Combine(path, imageName);
await using var fileStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 81920, useAsync: true);
await imageStream.CopyToAsync(fileStream);
return imageName;
}
private static string GetRandomFileName()
{
return Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace('+', '-').Replace('/', '_').TrimEnd('=');
}
}
}

View File

@@ -0,0 +1,62 @@
using RecRoomArchive.Models.API.Activities;
using System.Text.Json;
namespace RecRoomArchive.Services
{
/// <summary>
/// Service used to get the message of the day as it is used in multiple areas of the server
/// </summary>
public class MessageOfTheDayService
{
/// <summary>
/// HttpClient for making requests to Gitea
/// </summary>
private static readonly HttpClient httpClient = new HttpClient();
/// <summary>
/// MessageOfTheDay reference
/// </summary>
private static string? MessageOfTheDay { get; set; }
/// <summary>
/// Gets the message of the day from Gitea. If the URL cannot be resolved, it will fall back to "Welcome to RecRoomArchive!"
/// </summary>
/// <returns>String related to the Message of the Day</returns>
public async Task<string> GetMessageOfTheDay(string? version = null)
{
// I wouldn't want to re-request the MOTD from the server a bunch of times...
if (string.IsNullOrEmpty(MessageOfTheDay))
{
var motd = await httpClient.GetAsync($"https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/MOTD");
if (!motd.IsSuccessStatusCode)
return "Welcome to RecRoomArchive!";
MessageOfTheDay = await motd.Content.ReadAsStringAsync();
}
return MessageOfTheDay;
}
// move out of motdservice
private DateTime CharadesWordsLastFetchedAt { get; set; }
private List<CharadesWord> CachedCharadesWords { get; set; } = [];
public async Task <List<CharadesWord>> GetCharadesWordsList()
{
if (CharadesWordsLastFetchedAt - DateTime.UtcNow > TimeSpan.FromMinutes(30))
return CachedCharadesWords;
var request = await httpClient.GetAsync("https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/CharadesWords");
if (!request.IsSuccessStatusCode)
return [];
var words = await request.Content.ReadAsStringAsync();
if (words == null)
return [];
CachedCharadesWords = JsonSerializer.Deserialize<List<CharadesWord>>(words)!;
return CachedCharadesWords;
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1 @@
{"OutfitSelections":"e36bcd98-7e85-43fa-89f8-57e4ec33823a,,,,0;c6c08eb5-381a-4193-9722-80da95d62abe,,,,1;d0a9262f-5504-46a7-bb10-7507503db58e,,,,1;40528de7-38a3-4a7c-8f93-6d3bfa5573f2,dee70c38-7a99-4c2b-9181-665f1bf75aca,018a5c07-e956-457d-a540-a5e2cd68da09,,0","SkinColor":"2d398478-37c4-4c4a-a471-fbcbe3e5b1f5","HairColor":"5e51ac88-e365-418b-a303-8737fd2e6bc8"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -0,0 +1 @@
{"Id":1775080,"Username":"splootybean","DisplayName":"city boy","XP":0,"Level":1,"Reputation":1,"Verified":true,"Developer":true,"Bio":"","RegistrationStatus":2,"CanReceiveInvites":true,"ProfileImageName":"Mh6wZ4jA6k2bx-nwb__YnA.jpg","JuniorProfile":false,"ForceJuniorImages":false,"PendingJunior":false,"HasBirthday":true,"HasEmail":true}

View File

@@ -0,0 +1 @@
[{"Key":"google_analytics_clientid_pref_key","Value":"JPOTDRUNb2592478225be25d5b5950fcf20d6f2601e231d2"},{"Key":"DAILY_LOGIN_DATE","Value":"57"},{"Key":"OBJECTIVE_DATE","Value":"57"},{"Key":"OBJECTIVE_PROGRESS0","Value":"1"},{"Key":"OBJECTIVE_COMPLETED0","Value":"1"},{"Key":"OBJECTIVE_PROGRESS1","Value":"0"},{"Key":"OBJECTIVE_COMPLETED1","Value":"0"},{"Key":"OBJECTIVE_PROGRESS2","Value":"0"},{"Key":"OBJECTIVE_COMPLETED2","Value":"0"},{"Key":"Recroom.OOBE","Value":"10"},{"Key":"PlayerSessionCount","Value":"10"}]