More changes...
This commit is contained in:
@@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
namespace RecRoomArchive.Models.API.Activities
|
namespace RecRoomArchive.Models.API.Activities
|
||||||
{
|
{
|
||||||
public class CharadesWord(string word, CharadesWordsDifficulty difficulty = CharadesWordsDifficulty.Easy)
|
public class CharadesWord
|
||||||
{
|
{
|
||||||
[JsonPropertyName(name: "Difficulty")] public CharadesWordsDifficulty Difficulty { get; set; } = difficulty;
|
[JsonPropertyName("Difficulty")]
|
||||||
[JsonPropertyName(name: "EN_US")] public string EN_US { get; set; } = word;
|
public CharadesWordsDifficulty Difficulty { get; set; } = CharadesWordsDifficulty.Easy;
|
||||||
|
|
||||||
|
[JsonPropertyName("EN_US")]
|
||||||
|
public string EN_US { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,6 @@ namespace RecRoomArchive.Models.API.Config
|
|||||||
{
|
{
|
||||||
[JsonPropertyName(name: "CloudRegion")] public string CloudRegion { get; set; } = "us";
|
[JsonPropertyName(name: "CloudRegion")] public string CloudRegion { get; set; } = "us";
|
||||||
[JsonPropertyName(name: "CrcCheckEnabled")] public bool CrcCheckEnabled { get; set; } = true;
|
[JsonPropertyName(name: "CrcCheckEnabled")] public bool CrcCheckEnabled { get; set; } = true;
|
||||||
|
[JsonPropertyName(name: "EnableServerTracingAfterDisconnect")] public bool EnableServerTracingAfterDisconnect { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
21
RecRoomArchive.Models/API/GameSessions/ActivityLevels.cs
Normal file
21
RecRoomArchive.Models/API/GameSessions/ActivityLevels.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace RecRoomArchive.Models.API.GameSessions
|
||||||
|
{
|
||||||
|
public enum ActivityLevels
|
||||||
|
{
|
||||||
|
INVALID = -1,
|
||||||
|
DORM_ROOM = 1000,
|
||||||
|
REC_CENTER = 2000,
|
||||||
|
CHARADES = 3000,
|
||||||
|
DISC_GOLF = 4000,
|
||||||
|
DODGEBALL = 5000,
|
||||||
|
THE_LOUNGE = 6000,
|
||||||
|
PADDLEBALL = 7000,
|
||||||
|
PAINTBALL = 8000,
|
||||||
|
QUEST = 9000,
|
||||||
|
SOCCER = 10000,
|
||||||
|
ART_TESTING = 11000,
|
||||||
|
PERFORMANCE_HALL = 12000,
|
||||||
|
ROOM_CALIBRATION = 13000,
|
||||||
|
PARK = 14000
|
||||||
|
}
|
||||||
|
}
|
||||||
34
RecRoomArchive.Models/API/GameSessions/GameSession.cs
Normal file
34
RecRoomArchive.Models/API/GameSessions/GameSession.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Models.API.GameSessions
|
||||||
|
{
|
||||||
|
public class GameSession
|
||||||
|
{
|
||||||
|
[JsonPropertyName(name: "GameSessionId")]
|
||||||
|
public long GameSessionId { get; set; } = 1;
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "RegionId")]
|
||||||
|
public string RegionId { get; set; } = "us";
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "RoomId")]
|
||||||
|
public string RoomId { get; set; } = "fba33a23-b4a5-4f55-a631-37028b1db7f9";
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "EventId")]
|
||||||
|
public ulong? EventId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "ActivityLevelId")]
|
||||||
|
public string ActivityLevelId { get; set; } = "76d98498-60a1-430c-ab76-b54a29b7a163";
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "Private")]
|
||||||
|
public bool Private { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "GameInProgress")]
|
||||||
|
public bool GameInProgress { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "MaxCapacity")]
|
||||||
|
public int MaxCapacity { get; set; } = 8;
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "IsFull")]
|
||||||
|
public bool IsFull { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
RecRoomArchive.Models/API/GameSessions/JoinGameErrorCode.cs
Normal file
14
RecRoomArchive.Models/API/GameSessions/JoinGameErrorCode.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace RecRoomArchive.Models.API.GameSessions
|
||||||
|
{
|
||||||
|
public enum JoinGameErrorCode
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
NoSuchGame,
|
||||||
|
PlayerNotOnline,
|
||||||
|
InsufficientSpace,
|
||||||
|
EventNotStarted,
|
||||||
|
EventAlreadyFinished,
|
||||||
|
EventCreatorNotReady,
|
||||||
|
Blocked
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Models.API.GameSessions
|
||||||
|
{
|
||||||
|
public class JoinGameSessionResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName(name: "Result")]
|
||||||
|
public JoinGameErrorCode Result { get; set; } = JoinGameErrorCode.Success;
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "GameSession")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public GameSession? GameSession { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace RecRoomArchive.Models.API.GameSessions
|
||||||
|
{
|
||||||
|
public class JoinRandomGameSessionRequest
|
||||||
|
{
|
||||||
|
public required string[] ActivityLevelIds { get; set; } = [];
|
||||||
|
public ulong[] ExpectedPlayerIds { get; set; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Models.API.GameSessions
|
||||||
|
{
|
||||||
|
public class PresenceResponseFuckYou
|
||||||
|
{
|
||||||
|
[JsonPropertyName(name: "PlayerId")]
|
||||||
|
public ulong PlayerId { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "IsOnline")]
|
||||||
|
public bool IsOnline { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "GameSession")]
|
||||||
|
public GameSession? GameSession { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
46
RecRoomArchive.Models/API/Notification/PushNotificationId.cs
Normal file
46
RecRoomArchive.Models/API/Notification/PushNotificationId.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
namespace RecRoomArchive.Models.API.Notification
|
||||||
|
{
|
||||||
|
public enum PushNotificationId
|
||||||
|
{
|
||||||
|
RelationshipChanged = 1,
|
||||||
|
MessageReceived,
|
||||||
|
MessageDeleted,
|
||||||
|
PresenceHeartbeatResponse,
|
||||||
|
RefreshLogin,
|
||||||
|
Logout,
|
||||||
|
SubscriptionUpdateProfile = 11,
|
||||||
|
SubscriptionUpdatePresence,
|
||||||
|
SubscriptionUpdateGameSession,
|
||||||
|
SubscriptionUpdateRoom = 15,
|
||||||
|
SubscriptionUpdateRoomPlaylist,
|
||||||
|
ModerationQuitGame = 20,
|
||||||
|
ModerationUpdateRequired,
|
||||||
|
ModerationKick,
|
||||||
|
ModerationKickAttemptFailed,
|
||||||
|
ModerationRoomBan,
|
||||||
|
ServerMaintenance,
|
||||||
|
GiftPackageReceived = 30,
|
||||||
|
GiftPackageReceivedImmediate,
|
||||||
|
GiftPackageRewardSelectionReceived,
|
||||||
|
ProfileJuniorStatusUpdate = 40,
|
||||||
|
RelationshipsInvalid = 50,
|
||||||
|
StorefrontBalanceAdd = 60,
|
||||||
|
StorefrontBalanceUpdate,
|
||||||
|
StorefrontBalancePurchase,
|
||||||
|
ConsumableMappingAdded = 70,
|
||||||
|
ConsumableMappingRemoved,
|
||||||
|
PlayerEventCreated = 80,
|
||||||
|
PlayerEventUpdated,
|
||||||
|
PlayerEventDeleted,
|
||||||
|
PlayerEventResponseChanged,
|
||||||
|
PlayerEventResponseDeleted,
|
||||||
|
PlayerEventStateChanged,
|
||||||
|
ChatMessageReceived = 90,
|
||||||
|
CommunityBoardUpdate = 95,
|
||||||
|
CommunityBoardAnnouncementUpdate,
|
||||||
|
InventionModerationStateChanged = 100,
|
||||||
|
FreeGiftButtonItemsAdded = 110,
|
||||||
|
LocalRoomKeyCreated = 120,
|
||||||
|
LocalRoomKeyDeleted
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,5 +12,6 @@ namespace RecRoomArchive.Models.API.PlatformLogin.Requests
|
|||||||
public required long BuildTimestamp { get; set; }
|
public required long BuildTimestamp { get; set; }
|
||||||
public required string AuthParams { get; set; }
|
public required string AuthParams { get; set; }
|
||||||
public required string Verify { get; set; }
|
public required string Verify { get; set; }
|
||||||
|
public string? PlayerId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,13 @@ namespace RecRoomArchive.Models.API.PlatformLogin.Responses
|
|||||||
{
|
{
|
||||||
public class BaseLoginResponse
|
public class BaseLoginResponse
|
||||||
{
|
{
|
||||||
|
[JsonPropertyName(name: "PlayerId")]
|
||||||
|
public ulong PlayerId { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName(name: "Token")]
|
[JsonPropertyName(name: "Token")]
|
||||||
public string Token { get; set; } = string.Empty;
|
public string Token { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName(name: "PlayerId")]
|
[JsonPropertyName(name: "Error")]
|
||||||
public ulong PlayerId { get; set; }
|
public string Error { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Models.API.VersionCheck
|
||||||
|
{
|
||||||
|
public class VersionCheckResponse(VersionStatus versionStatus = VersionStatus.ValidForPlay)
|
||||||
|
{
|
||||||
|
[JsonPropertyName(name: "VersionStatus")] public VersionStatus VersionStatus { get; set; } = versionStatus;
|
||||||
|
|
||||||
|
[JsonPropertyName(name: "ValidVersion")] public bool ValidVersion => VersionStatus == VersionStatus.ValidForPlay;
|
||||||
|
[JsonPropertyName(name: "IsValid")] public bool IsValid => VersionStatus == VersionStatus.ValidForPlay;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
RecRoomArchive.Models/API/VersionCheck/VersionStatus.cs
Normal file
8
RecRoomArchive.Models/API/VersionCheck/VersionStatus.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace RecRoomArchive.Models.API.VersionCheck
|
||||||
|
{
|
||||||
|
public enum VersionStatus
|
||||||
|
{
|
||||||
|
ValidForPlay,
|
||||||
|
UpdateRequired
|
||||||
|
}
|
||||||
|
}
|
||||||
7
RecRoomArchive.Models/Common/Constants.cs
Normal file
7
RecRoomArchive.Models/Common/Constants.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace RecRoomArchive.Models.Common
|
||||||
|
{
|
||||||
|
public class Constants
|
||||||
|
{
|
||||||
|
public static readonly string Version = "0.0.5";
|
||||||
|
}
|
||||||
|
}
|
||||||
11
RecRoomArchive.Models/RRA/ServerPreferences.cs
Normal file
11
RecRoomArchive.Models/RRA/ServerPreferences.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Models.RRA
|
||||||
|
{
|
||||||
|
public class ServerPreferences
|
||||||
|
{
|
||||||
|
[JsonPropertyName(name: "CompletedSetup")] public bool CompletedSetup { get; set; } = false;
|
||||||
|
[JsonPropertyName(name: "OverrideNamedImages")] public bool OverrideNamedImages { get; set; } = false;
|
||||||
|
[JsonPropertyName(name: "EnableWebRequests")] public bool EnableWebRequests { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Controllers.API.Equipment.V1
|
||||||
|
{
|
||||||
|
[Route(template: "api/[controller]/v1")]
|
||||||
|
[ApiController]
|
||||||
|
public class EquipmentController : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet(template: "getUnlocked")]
|
||||||
|
public async Task<ActionResult<List<object>>> GetEquipment()
|
||||||
|
{
|
||||||
|
return new List<object>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
RecRoomArchive/Controllers/API/Events/V3/EventsController.cs
Normal file
15
RecRoomArchive/Controllers/API/Events/V3/EventsController.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Controllers.API.Events.V3
|
||||||
|
{
|
||||||
|
[Route(template: "api/[controller]/v3")]
|
||||||
|
[ApiController]
|
||||||
|
public class EventsController : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet(template: "list")]
|
||||||
|
public async Task<ActionResult<List<object>>> GetActiveEventsList()
|
||||||
|
{
|
||||||
|
return new List<object>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecRoomArchive.Models.API.GameSessions;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Controllers.API.GameSessions.V2
|
||||||
|
{
|
||||||
|
[Route("api/[controller]/v2")]
|
||||||
|
[ApiController]
|
||||||
|
public class GameSessionsController : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpPost(template: "joinRandom")]
|
||||||
|
public async Task<ActionResult<JoinGameSessionResponse>> JoinRandomGameSession([FromBody] JoinRandomGameSessionRequest request)
|
||||||
|
{
|
||||||
|
Console.WriteLine(JsonSerializer.Serialize(request));
|
||||||
|
|
||||||
|
var session = new JoinGameSessionResponse()
|
||||||
|
{
|
||||||
|
GameSession = new GameSession()
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine(JsonSerializer.Serialize(session));
|
||||||
|
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecRoomArchive.Controllers.API.Notification;
|
||||||
|
using RecRoomArchive.Models.API.Notification;
|
||||||
using RecRoomArchive.Models.API.Players;
|
using RecRoomArchive.Models.API.Players;
|
||||||
using RecRoomArchive.Services;
|
using RecRoomArchive.Services;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -25,6 +27,8 @@ namespace RecRoomArchive.Controllers.API.Images.V2
|
|||||||
|
|
||||||
fileService.SetData("profile.json", JsonSerializer.Serialize(profile));
|
fileService.SetData("profile.json", JsonSerializer.Serialize(profile));
|
||||||
|
|
||||||
|
await NotificationController.Notify(profile.Id, PushNotificationId.SubscriptionUpdateProfile, profile);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecRoomArchive.Models.API.Notification;
|
||||||
|
using System.Net.WebSockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Controllers.API.Notification
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class NotificationController : ControllerBase
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<ulong, WebSocket> Clients = [];
|
||||||
|
|
||||||
|
[HttpGet("v2")]
|
||||||
|
public async Task Get()
|
||||||
|
{
|
||||||
|
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||||
|
{
|
||||||
|
HttpContext.Response.StatusCode = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||||
|
var buffer = new byte[1024 * 4];
|
||||||
|
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
var handshakeJson = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||||
|
var handshakeData = JsonSerializer.Deserialize<Dictionary<string, string>>(handshakeJson)!;
|
||||||
|
var playerId = ulong.Parse(handshakeData["PlayerId"]);
|
||||||
|
Clients[playerId] = webSocket;
|
||||||
|
var sessionResponse = JsonSerializer.Serialize(new { SessionId = playerId });
|
||||||
|
await webSocket.SendAsync(Encoding.UTF8.GetBytes(sessionResponse), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||||
|
await Echo(playerId, webSocket);
|
||||||
|
Clients.Remove(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task Echo(ulong playerId, WebSocket webSocket)
|
||||||
|
{
|
||||||
|
var buffer = new byte[1024 * 4];
|
||||||
|
var receiveResult = await webSocket.ReceiveAsync(
|
||||||
|
new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
|
||||||
|
while (!receiveResult.CloseStatus.HasValue)
|
||||||
|
{
|
||||||
|
var response = JsonSerializer.Serialize(new { SessionId = playerId }); // i think?? only for analytics tho lolololool
|
||||||
|
|
||||||
|
await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(response)),
|
||||||
|
WebSocketMessageType.Text, true, CancellationToken.None);
|
||||||
|
|
||||||
|
/*await webSocket.SendAsync(
|
||||||
|
new ArraySegment<byte>(buffer, 0, receiveResult.Count),
|
||||||
|
receiveResult.MessageType,
|
||||||
|
receiveResult.EndOfMessage,
|
||||||
|
CancellationToken.None);*/
|
||||||
|
|
||||||
|
receiveResult = await webSocket.ReceiveAsync(
|
||||||
|
new ArraySegment<byte>(buffer), CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
await webSocket.CloseAsync(
|
||||||
|
receiveResult.CloseStatus.Value,
|
||||||
|
receiveResult.CloseStatusDescription,
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
Clients.Remove(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Notify(ulong playerId, PushNotificationId id, object msg)
|
||||||
|
{
|
||||||
|
if (!Clients.TryGetValue(playerId, out var socket))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (socket.State != WebSocketState.Open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var notification = new
|
||||||
|
{
|
||||||
|
Id = (int)id,
|
||||||
|
Msg = msg
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(notification);
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
|
||||||
|
await socket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecRoomArchive.Models.API.Platform;
|
||||||
|
using RecRoomArchive.Models.API.Players;
|
||||||
|
using RecRoomArchive.Services;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Controllers.API.PlatformLogin.V1
|
||||||
|
{
|
||||||
|
[Route("api/[controller]/v1")]
|
||||||
|
[ApiController]
|
||||||
|
public class PlatformLoginController(AccountService accountService) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpPost(template: "profiles")]
|
||||||
|
public async Task<ActionResult<List<BaseProfile>>> GetProfiles([FromForm(Name = "Platform")] PlatformType platform, [FromForm(Name = "PlatformId")] ulong platformId)
|
||||||
|
{
|
||||||
|
if (!accountService.AccountExists())
|
||||||
|
{
|
||||||
|
accountService.CreateAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile = accountService.GetSelfAccount();
|
||||||
|
if (profile == null)
|
||||||
|
return BadRequest("Please relaunch RRAC and re-run setup");
|
||||||
|
|
||||||
|
List<BaseProfile> profiles = [profile];
|
||||||
|
|
||||||
|
return Ok(profiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ namespace RecRoomArchive.Controllers.API.PlatformLogin.V2
|
|||||||
accountService.CreateAccount(username);
|
accountService.CreateAccount(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
var accountId = accountService.GetSelfAccount()!.Id;
|
var accountId = accountService.GetSelfAccountId()!.Value;
|
||||||
|
|
||||||
return Ok(new BaseLoginResponse
|
return Ok(new BaseLoginResponse
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecRoomArchive.Models.API.PlatformLogin.Requests;
|
||||||
|
using RecRoomArchive.Models.API.PlatformLogin.Responses;
|
||||||
|
using RecRoomArchive.Services;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Controllers.API.PlatformLogin.V5
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to login to accounts on Rec Room
|
||||||
|
/// </summary>
|
||||||
|
[Route(template: "api/[controller]/v5")]
|
||||||
|
[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([FromForm] BaseLoginRequest loginRequest)
|
||||||
|
{
|
||||||
|
var username = loginRequest.Name ?? string.Empty;
|
||||||
|
|
||||||
|
var buildTimestamp = loginRequest.BuildTimestamp;
|
||||||
|
|
||||||
|
await appVersionService.StoreBuildTimestamp(buildTimestamp);
|
||||||
|
|
||||||
|
// See if a profile exists yet...
|
||||||
|
if (!accountService.AccountExists())
|
||||||
|
{
|
||||||
|
// ...if not, create it!
|
||||||
|
accountService.CreateAccount(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountId = accountService.GetSelfAccountId()!.Value;
|
||||||
|
|
||||||
|
return Ok(new BaseLoginResponse
|
||||||
|
{
|
||||||
|
Token = authorizationService.GenerateToken(accountId),
|
||||||
|
PlayerId = accountId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,7 +69,13 @@ namespace RecRoomArchive.Controllers.API.Players
|
|||||||
[HttpPut(template: "{profileId:long}")]
|
[HttpPut(template: "{profileId:long}")]
|
||||||
public async Task<ActionResult<AugustProfile>> UpdateProfile([Required] ulong profileId, [FromBody] AugustProfile model)
|
public async Task<ActionResult<AugustProfile>> UpdateProfile([Required] ulong profileId, [FromBody] AugustProfile model)
|
||||||
{
|
{
|
||||||
return (AugustProfile)accountService.GetSelfAccount()!;
|
var baseProfile = accountService.GetSelfAccount();
|
||||||
|
if (baseProfile == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var profile = new AugustProfile(baseProfile);
|
||||||
|
|
||||||
|
return profile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using RecRoomArchive.Models.API.Config;
|
|
||||||
using RecRoomArchive.Models.API.Platform;
|
using RecRoomArchive.Models.API.Platform;
|
||||||
using RecRoomArchive.Models.API.Players;
|
using RecRoomArchive.Models.API.Players;
|
||||||
using RecRoomArchive.Services;
|
using RecRoomArchive.Services;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecRoomArchive.Controllers.API.Notification;
|
||||||
|
using RecRoomArchive.Models.API.Notification;
|
||||||
using RecRoomArchive.Models.API.Players;
|
using RecRoomArchive.Models.API.Players;
|
||||||
using RecRoomArchive.Models.Common;
|
using RecRoomArchive.Models.Common;
|
||||||
using RecRoomArchive.Services;
|
using RecRoomArchive.Services;
|
||||||
@@ -31,6 +33,8 @@ namespace RecRoomArchive.Controllers.API.Players.V2
|
|||||||
|
|
||||||
fileService.SetData("profile.json", JsonSerializer.Serialize(profile));
|
fileService.SetData("profile.json", JsonSerializer.Serialize(profile));
|
||||||
|
|
||||||
|
await NotificationController.Notify(profile.Id, PushNotificationId.SubscriptionUpdateProfile, profile);
|
||||||
|
|
||||||
return Ok(OkResponse.Ok());
|
return Ok(OkResponse.Ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecRoomArchive.Models.API.VersionCheck;
|
||||||
|
using RecRoomArchive.Services;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Controllers.API.VersionCheck.V3
|
||||||
|
{
|
||||||
|
/// <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]/v3")]
|
||||||
|
[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>A valid VersionCheckResponse if the version is valid, an invalid VersionCheckResponse 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([FromQuery(Name = "v")] string appVersion)
|
||||||
|
{
|
||||||
|
if (appVersion == null)
|
||||||
|
return BadRequest(new VersionCheckResponse(VersionStatus.UpdateRequired));
|
||||||
|
|
||||||
|
await appVersionService.StoreAppVersion(appVersion);
|
||||||
|
|
||||||
|
return Ok(new VersionCheckResponse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
using RecRoomArchive.Models.Common;
|
||||||
using RecRoomArchive.Services;
|
using RecRoomArchive.Services;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace RecRoomArchive
|
namespace RecRoomArchive
|
||||||
{
|
{
|
||||||
@@ -8,11 +9,15 @@ namespace RecRoomArchive
|
|||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.WebHost.UseKestrel();
|
||||||
|
|
||||||
|
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
|
||||||
|
|
||||||
#region Services
|
#region Services
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
builder.Services.AddSerilog();
|
||||||
|
|
||||||
builder.Services.AddScoped<AccountService>();
|
builder.Services.AddScoped<AccountService>();
|
||||||
builder.Services.AddScoped<AppVersionService>();
|
builder.Services.AddScoped<AppVersionService>();
|
||||||
@@ -21,17 +26,31 @@ namespace RecRoomArchive
|
|||||||
builder.Services.AddScoped<FileService>();
|
builder.Services.AddScoped<FileService>();
|
||||||
builder.Services.AddScoped<ImageService>();
|
builder.Services.AddScoped<ImageService>();
|
||||||
builder.Services.AddScoped<MessageOfTheDayService>();
|
builder.Services.AddScoped<MessageOfTheDayService>();
|
||||||
|
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
Console.Title = $"RRAC {Constants.Version}";
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
app.MapOpenApi();
|
||||||
{
|
|
||||||
app.MapOpenApi();
|
app.UseSwagger();
|
||||||
}
|
app.UseSwaggerUI();
|
||||||
|
|
||||||
|
app.UseSerilogRequestLogging();
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
var webSocketOptions = new WebSocketOptions
|
||||||
|
{
|
||||||
|
KeepAliveInterval = TimeSpan.FromMinutes(2)
|
||||||
|
};
|
||||||
|
|
||||||
|
app.UseWebSockets(webSocketOptions);
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
@@ -20,4 +20,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.3" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.4" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="10.1.4" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.16.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ namespace RecRoomArchive.Services
|
|||||||
{
|
{
|
||||||
public class AccountService
|
public class AccountService
|
||||||
{
|
{
|
||||||
|
public static ulong? AccountId { get; private set; }
|
||||||
|
|
||||||
public bool AccountExists()
|
public bool AccountExists()
|
||||||
{
|
{
|
||||||
return File.Exists("data/profile.json");
|
return File.Exists("data/profile.json");
|
||||||
@@ -13,8 +15,6 @@ namespace RecRoomArchive.Services
|
|||||||
|
|
||||||
public bool CreateAccount(string? username = null)
|
public bool CreateAccount(string? username = null)
|
||||||
{
|
{
|
||||||
PopulateServerData();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(username))
|
if (string.IsNullOrEmpty(username))
|
||||||
{
|
{
|
||||||
username = GetRandomUsername();
|
username = GetRandomUsername();
|
||||||
@@ -38,35 +38,25 @@ namespace RecRoomArchive.Services
|
|||||||
return JsonSerializer.Deserialize<BaseProfile>(File.ReadAllText("data/profile.json"));
|
return JsonSerializer.Deserialize<BaseProfile>(File.ReadAllText("data/profile.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ulong? GetSelfAccountId()
|
||||||
|
{
|
||||||
|
if (AccountId.HasValue)
|
||||||
|
return AccountId;
|
||||||
|
|
||||||
|
var profile = JsonSerializer.Deserialize<BaseProfile>(File.ReadAllText("data/profile.json"));
|
||||||
|
if (profile == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
AccountId = profile.Id;
|
||||||
|
|
||||||
|
return AccountId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetRandomUsername()
|
private static string GetRandomUsername()
|
||||||
{
|
{
|
||||||
int randomFourDigits = RandomNumberGenerator.GetInt32(1000, 9999);
|
int randomFourDigits = RandomNumberGenerator.GetInt32(1000, 9999);
|
||||||
return $"RRA-User_{randomFourDigits}";
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace RecRoomArchive.Services
|
using RecRoomArchive.Models.Common;
|
||||||
|
|
||||||
|
namespace RecRoomArchive.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to get the appVersion from the client to determine how to run the server
|
/// Used to get the appVersion from the client to determine how to run the server
|
||||||
@@ -63,6 +65,8 @@
|
|||||||
FullBuildTimestamp = buildTimestamp;
|
FullBuildTimestamp = buildTimestamp;
|
||||||
BuildTimestamp = buildTimestampDateTime;
|
BuildTimestamp = buildTimestampDateTime;
|
||||||
|
|
||||||
|
Console.Title = $"RRAC {Constants.Version} - ({BuildTimestamp.Value.Year}) {BuildTimestamp.Value}";
|
||||||
|
Console.WriteLine ($"RRAC {Constants.Version} - ({BuildTimestamp.Value.Year}) {BuildTimestamp.Value}");
|
||||||
Console.WriteLine($"buildTimestamp: {FullBuildTimestamp}, buildTimestampDateTime: {BuildTimestamp}");
|
Console.WriteLine($"buildTimestamp: {FullBuildTimestamp}, buildTimestampDateTime: {BuildTimestamp}");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace RecRoomArchive.Services
|
|||||||
new(ClaimTypes.Role, "gameClient")
|
new(ClaimTypes.Role, "gameClient")
|
||||||
};
|
};
|
||||||
|
|
||||||
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor()
|
SecurityTokenDescriptor tokenDescriptor = new()
|
||||||
{
|
{
|
||||||
Subject = new ClaimsIdentity(claims),
|
Subject = new ClaimsIdentity(claims),
|
||||||
Expires = DateTime.UtcNow.Add(TimeSpan.FromHours(12)),
|
Expires = DateTime.UtcNow.Add(TimeSpan.FromHours(12)),
|
||||||
|
|||||||
@@ -17,12 +17,13 @@ namespace RecRoomArchive.Services
|
|||||||
public async Task<RecRoomConfig> GetRecRoomConfig()
|
public async Task<RecRoomConfig> GetRecRoomConfig()
|
||||||
{
|
{
|
||||||
var request = _httpContextAccessor.HttpContext?.Request;
|
var request = _httpContextAccessor.HttpContext?.Request;
|
||||||
var host = $"{request!.Scheme}://{request.Host}";
|
var cdnHost = $"{request!.Scheme}://{request.Host}";
|
||||||
|
var motd = await _motdService.GetMessageOfTheDay();
|
||||||
|
|
||||||
return new RecRoomConfig()
|
return new RecRoomConfig()
|
||||||
{
|
{
|
||||||
MessageOfTheDay = await _motdService.GetMessageOfTheDay(),
|
MessageOfTheDay = motd,
|
||||||
CdnBaseUri = host,
|
CdnBaseUri = cdnHost,
|
||||||
MatchmakingParams = new MatchmakingParams(),
|
MatchmakingParams = new MatchmakingParams(),
|
||||||
LevelProgressionMaps =
|
LevelProgressionMaps =
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Xml.Linq;
|
using RecRoomArchive.Models.RRA;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace RecRoomArchive.Services
|
namespace RecRoomArchive.Services
|
||||||
{
|
{
|
||||||
@@ -9,7 +10,7 @@ namespace RecRoomArchive.Services
|
|||||||
string basePath = "data";
|
string basePath = "data";
|
||||||
|
|
||||||
string[] directories = ["rooms", "images", "blobs"];
|
string[] directories = ["rooms", "images", "blobs"];
|
||||||
string[] files = ["rooms.json", "avatar.json", "settings.json", "profile.json"];
|
string[] files = ["rooms.json", "avatar.json", "settings.json", "profile.json", "avatarItems.json", "serverPreferences.json"];
|
||||||
|
|
||||||
Directory.CreateDirectory(basePath);
|
Directory.CreateDirectory(basePath);
|
||||||
|
|
||||||
@@ -28,6 +29,17 @@ namespace RecRoomArchive.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteServerPrefs(bool isComplete)
|
||||||
|
{
|
||||||
|
var preferences = new ServerPreferences
|
||||||
|
{
|
||||||
|
CompletedSetup = isComplete
|
||||||
|
};
|
||||||
|
|
||||||
|
SetData("serverPreferences.json", JsonSerializer.Serialize(preferences));
|
||||||
|
}
|
||||||
|
|
||||||
public string? GetData(string name)
|
public string? GetData(string name)
|
||||||
{
|
{
|
||||||
var path = $"data/{name}";
|
var path = $"data/{name}";
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace RecRoomArchive.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// HttpClient for making requests to Gitea
|
/// HttpClient for making requests to Gitea
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly HttpClient httpClient = new HttpClient();
|
private static readonly HttpClient httpClient = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MessageOfTheDay reference
|
/// MessageOfTheDay reference
|
||||||
@@ -22,16 +22,18 @@ namespace RecRoomArchive.Services
|
|||||||
/// Gets the message of the day from Gitea. If the URL cannot be resolved, it will fall back to "Welcome to RecRoomArchive!"
|
/// Gets the message of the day from Gitea. If the URL cannot be resolved, it will fall back to "Welcome to RecRoomArchive!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>String related to the Message of the Day</returns>
|
/// <returns>String related to the Message of the Day</returns>
|
||||||
public async Task<string> GetMessageOfTheDay(string? version = null)
|
public async Task<string> GetMessageOfTheDay()
|
||||||
{
|
{
|
||||||
// I wouldn't want to re-request the MOTD from the server a bunch of times...
|
// I wouldn't want to re-request the MOTD from the server a bunch of times...
|
||||||
if (string.IsNullOrEmpty(MessageOfTheDay))
|
if (string.IsNullOrEmpty(MessageOfTheDay))
|
||||||
{
|
{
|
||||||
var motd = await httpClient.GetAsync($"https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/MOTD");
|
//var motd = await httpClient.GetAsync($"https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/MOTD");
|
||||||
if (!motd.IsSuccessStatusCode)
|
//if (!motd.IsSuccessStatusCode)
|
||||||
return "Welcome to RecRoomArchive!";
|
// return "Welcome to RecRoomArchive!";
|
||||||
|
|
||||||
MessageOfTheDay = await motd.Content.ReadAsStringAsync();
|
//MessageOfTheDay = await motd.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
MessageOfTheDay = "Welcome to RecRoomArchive!";
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageOfTheDay;
|
return MessageOfTheDay;
|
||||||
@@ -46,17 +48,19 @@ namespace RecRoomArchive.Services
|
|||||||
if (CharadesWordsLastFetchedAt - DateTime.UtcNow > TimeSpan.FromMinutes(30))
|
if (CharadesWordsLastFetchedAt - DateTime.UtcNow > TimeSpan.FromMinutes(30))
|
||||||
return CachedCharadesWords;
|
return CachedCharadesWords;
|
||||||
|
|
||||||
var request = await httpClient.GetAsync("https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/CharadesWords");
|
//var request = await httpClient.GetAsync("https://git.recroomarchive.org/RecRoomArchive/RRAC/raw/branch/main/CharadesWords");
|
||||||
if (!request.IsSuccessStatusCode)
|
//if (!request.IsSuccessStatusCode)
|
||||||
return [];
|
// return [];
|
||||||
|
|
||||||
var words = await request.Content.ReadAsStringAsync();
|
//var words = await request.Content.ReadAsStringAsync();
|
||||||
if (words == null)
|
//if (words == null)
|
||||||
return [];
|
// return [];
|
||||||
|
|
||||||
CachedCharadesWords = JsonSerializer.Deserialize<List<CharadesWord>>(words)!;
|
return [];
|
||||||
|
|
||||||
return CachedCharadesWords;
|
//CachedCharadesWords = JsonSerializer.Deserialize<List<CharadesWord>>(words)!;
|
||||||
|
|
||||||
|
//return CachedCharadesWords;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user