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:
splootybean
2026-02-27 00:58:13 -08:00
parent 05c35b2a18
commit 387ec7ba89
61 changed files with 1722 additions and 0 deletions
+72
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);
}
}
}
}
}
@@ -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;
}
}
}
@@ -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);
}
}
}
+188
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()
};
}
}
}
+58
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;
}
}
}
+44
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('=');
}
}
}
@@ -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;
}
}
}