diff --git a/gaseous-server/Classes/RomMediaGroup.cs b/gaseous-server/Classes/RomMediaGroup.cs index 887b9c6..f8f2f78 100644 --- a/gaseous-server/Classes/RomMediaGroup.cs +++ b/gaseous-server/Classes/RomMediaGroup.cs @@ -58,7 +58,7 @@ namespace gaseous_server.Classes public static GameRomMediaGroupItem GetMediaGroup(long Id) { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT * FROM RomMediaGroup WHERE Id=@id;"; + string sql = "SELECT DISTINCT RomMediaGroup.*, GameState.RomId AS GameStateRomId FROM gaseous.RomMediaGroup LEFT JOIN GameState ON RomMediaGroup.Id = GameState.RomId AND GameState.IsMediaGroup = 1 WHERE RomMediaGroup.Id=@id;"; Dictionary dbDict = new Dictionary(); dbDict.Add("id", Id); @@ -78,7 +78,7 @@ namespace gaseous_server.Classes public static List GetMediaGroupsFromGameId(long GameId) { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "SELECT * FROM RomMediaGroup WHERE GameId=@gameid;"; + string sql = "SELECT DISTINCT RomMediaGroup.*, GameState.RomId AS GameStateRomId FROM gaseous.RomMediaGroup LEFT JOIN GameState ON RomMediaGroup.Id = GameState.RomId AND GameState.IsMediaGroup = 1 WHERE RomMediaGroup.GameId=@gameid;"; Dictionary dbDict = new Dictionary(); dbDict.Add("gameid", GameId); @@ -156,7 +156,7 @@ namespace gaseous_server.Classes public static void DeleteMediaGroup(long Id) { Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "DELETE FROM RomMediaGroup WHERE Id=@id;"; + string sql = "DELETE FROM RomMediaGroup WHERE Id=@id; DELETE FROM GameState WHERE RomId=@id AND IsMediaGroup=1;"; Dictionary dbDict = new Dictionary(); dbDict.Add("id", Id); db.ExecuteCMD(sql, dbDict); @@ -170,6 +170,15 @@ namespace gaseous_server.Classes internal static GameRomMediaGroupItem BuildMediaGroupFromRow(DataRow row) { + bool hasSaveStates = false; + if (row.Table.Columns.Contains("GameStateRomId")) + { + if (row["GameStateRomId"] != DBNull.Value) + { + hasSaveStates = true; + } + } + GameRomMediaGroupItem mediaGroupItem = new GameRomMediaGroupItem(); mediaGroupItem.Id = (long)row["Id"]; mediaGroupItem.Status = (GameRomMediaGroupItem.GroupBuildStatus)row["Status"]; @@ -177,6 +186,7 @@ namespace gaseous_server.Classes mediaGroupItem.GameId = (long)row["GameId"]; mediaGroupItem.RomIds = new List(); mediaGroupItem.Roms = new List(); + mediaGroupItem.HasSaveStates = hasSaveStates; // get members Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); @@ -397,6 +407,7 @@ namespace gaseous_server.Classes public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; } public List RomIds { get; set; } public List Roms { get; set; } + public bool HasSaveStates { get; set; } = false; private GroupBuildStatus _Status { get; set; } public GroupBuildStatus Status { get diff --git a/gaseous-server/Classes/Roms.cs b/gaseous-server/Classes/Roms.cs index d732cab..81ddfd4 100644 --- a/gaseous-server/Classes/Roms.cs +++ b/gaseous-server/Classes/Roms.cs @@ -15,7 +15,7 @@ namespace gaseous_server.Classes {} } - public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0) + public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "") { GameRomObject GameRoms = new GameRomObject(); @@ -25,6 +25,7 @@ namespace gaseous_server.Classes string sqlPlatform = ""; Dictionary dbDict = new Dictionary(); dbDict.Add("id", GameId); + dbDict.Add("userid", userid); string NameSearchWhere = ""; if (NameSearch.Length > 0) @@ -38,13 +39,13 @@ namespace gaseous_server.Classes if (PlatformId == -1) { // data query - sql = "SELECT Games_Roms.*, Platform.`Name` AS platformname FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; + sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; // count query sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id" + NameSearchWhere + ";"; } else { // data query - sql = "SELECT Games_Roms.*, Platform.`Name` AS platformname FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; + sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; // count query sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + ";"; @@ -131,7 +132,7 @@ namespace gaseous_server.Classes } Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - string sql = "DELETE FROM Games_Roms WHERE Id = @id"; + string sql = "DELETE FROM Games_Roms WHERE Id = @id; DELETE FROM GameState WHERE RomId = @id;"; Dictionary dbDict = new Dictionary(); dbDict.Add("id", RomId); db.ExecuteCMD(sql, dbDict); @@ -140,6 +141,15 @@ namespace gaseous_server.Classes private static GameRomItem BuildRom(DataRow romDR) { + bool hasSaveStates = false; + if (romDR.Table.Columns.Contains("SavedStateRomId")) + { + if (romDR["SavedStateRomId"] != DBNull.Value) + { + hasSaveStates = true; + } + } + GameRomItem romItem = new GameRomItem { Id = (long)romDR["id"], @@ -159,6 +169,7 @@ namespace gaseous_server.Classes Path = (string)romDR["path"], SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"], SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""), + HasSaveStates = hasSaveStates, Library = GameLibrary.GetLibrary((int)romDR["LibraryId"]) }; @@ -191,6 +202,7 @@ namespace gaseous_server.Classes public long GameId { get; set; } public string? Path { get; set; } public string? SignatureSourceGameTitle { get; set;} + public bool HasSaveStates { get; set; } = false; public GameLibrary.LibraryItem Library { get; set; } } } diff --git a/gaseous-server/Controllers/V1.0/GamesController.cs b/gaseous-server/Controllers/V1.0/GamesController.cs index 49bb3a0..228adfd 100644 --- a/gaseous-server/Controllers/V1.0/GamesController.cs +++ b/gaseous-server/Controllers/V1.0/GamesController.cs @@ -6,12 +6,14 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Authentication; using gaseous_server.Classes; using gaseous_server.Classes.Metadata; using gaseous_server.Models; using IGDB.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis.Scripting; using static gaseous_server.Classes.Metadata.AgeRatings; @@ -25,6 +27,18 @@ namespace gaseous_server.Controllers [ApiController] public class GamesController : Controller { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public GamesController( + UserManager userManager, + SignInManager signInManager + ) + { + _userManager = userManager; + _signInManager = signInManager; + } + [MapToApiVersion("1.0")] [HttpGet] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] @@ -830,13 +844,15 @@ namespace gaseous_server.Controllers [ProducesResponseType(typeof(Classes.Roms.GameRomObject), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] //[ResponseCache(CacheProfileName = "5Minute")] - public ActionResult GameRom(long GameId, int pageNumber = 0, int pageSize = 0, long PlatformId = -1, string NameSearch = "") + public async Task GameRomAsync(long GameId, int pageNumber = 0, int pageSize = 0, long PlatformId = -1, string NameSearch = "") { + var user = await _userManager.GetUserAsync(User); + try { Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); - return Ok(Classes.Roms.GetRoms(GameId, PlatformId, NameSearch, pageNumber, pageSize)); + return Ok(Classes.Roms.GetRoms(GameId, PlatformId, NameSearch, pageNumber, pageSize, user.Id)); } catch { diff --git a/gaseous-server/Controllers/V1.1/GamesController.cs b/gaseous-server/Controllers/V1.1/GamesController.cs index fdc7b13..c85a457 100644 --- a/gaseous-server/Controllers/V1.1/GamesController.cs +++ b/gaseous-server/Controllers/V1.1/GamesController.cs @@ -444,8 +444,6 @@ namespace gaseous_server.Controllers.v1_1 string orderByClause = "ORDER BY `" + orderByField + "` " + orderByOrder; Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); - //string sql = "SELECT DISTINCT view_Games.* FROM view_Games LEFT JOIN Relation_Game_Platforms ON view_Games.Id = Relation_Game_Platforms.GameId AND (Relation_Game_Platforms.PlatformsId IN (SELECT DISTINCT PlatformId FROM Games_Roms WHERE Games_Roms.GameId = view_Games.Id)) LEFT JOIN Relation_Game_Genres ON view_Games.Id = Relation_Game_Genres.GameId LEFT JOIN Relation_Game_GameModes ON view_Games.Id = Relation_Game_GameModes.GameId LEFT JOIN Relation_Game_PlayerPerspectives ON view_Games.Id = Relation_Game_PlayerPerspectives.GameId LEFT JOIN Relation_Game_Themes ON view_Games.Id = Relation_Game_Themes.GameId " + whereClause + " " + havingClause + " " + orderByClause; - string sql = "SELECT DISTINCT Game.Id, Game.`Name`, Game.NameThe, Game.PlatformId, Game.TotalRating, Game.TotalRatingCount, Game.Cover, Game.Artworks, Game.FirstReleaseDate, Game.Category, Game.ParentGame, Game.AgeRatings, Game.AgeGroupId, Game.RomCount FROM (SELECT DISTINCT Game.*, CASE WHEN Game.`Name` LIKE 'The %' THEN CONCAT(TRIM(SUBSTR(Game.`Name` FROM 4)), ', The') ELSE Game.`Name` END AS NameThe, Games_Roms.PlatformId, AgeGroup.AgeGroupId, COUNT(Games_Roms.Id) AS RomCount FROM Game LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId LEFT JOIN Games_Roms ON Game.Id = Games_Roms.GameId" + platformWhereClause + " LEFT JOIN AlternativeName ON Game.Id = AlternativeName.Game " + nameWhereClause + " GROUP BY Game.Id HAVING RomCount > 0) Game LEFT JOIN Relation_Game_Genres ON Game.Id = Relation_Game_Genres.GameId LEFT JOIN Relation_Game_GameModes ON Game.Id = Relation_Game_GameModes.GameId LEFT JOIN Relation_Game_PlayerPerspectives ON Game.Id = Relation_Game_PlayerPerspectives.GameId LEFT JOIN Relation_Game_Themes ON Game.Id = Relation_Game_Themes.GameId " + whereClause + " " + havingClause + " " + orderByClause; List RetVal = new List(); diff --git a/gaseous-server/Controllers/V1.1/StateManagerController.cs b/gaseous-server/Controllers/V1.1/StateManagerController.cs new file mode 100644 index 0000000..33bb15b --- /dev/null +++ b/gaseous-server/Controllers/V1.1/StateManagerController.cs @@ -0,0 +1,279 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using gaseous_server.Models; +using gaseous_server.Classes; +using Authentication; +using Microsoft.AspNetCore.Identity; +using System.Data; + +namespace gaseous_server.Controllers.v1_1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiVersion("1.0")] + [ApiVersion("1.1")] + [ApiController] + public class StateManagerController: ControllerBase + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public StateManagerController( + UserManager userManager, + SignInManager signInManager + ) + { + _userManager = userManager; + _signInManager = signInManager; + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpPost] + [Authorize] + [ProducesResponseType(typeof(Models.GameStateItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{RomId}")] + public async Task SaveStateAsync(long RomId, UploadStateModel uploadState, bool IsMediaGroup = false) + { + var user = await _userManager.GetUserAsync(User); + + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state); SELECT LAST_INSERT_ID();"; + Dictionary dbDict = new Dictionary + { + { "userid", user.Id }, + { "romid", RomId }, + { "ismediagroup", IsMediaGroup }, + { "statedatetime", DateTime.UtcNow }, + { "name", "" }, + { "screenshot", uploadState.ScreenshotByteArray }, + { "state", uploadState.StateByteArray } + }; + DataTable data = db.ExecuteCMD(sql, dbDict); + + return Ok(await GetStateAsync(RomId, (long)(ulong)data.Rows[0][0], IsMediaGroup)); + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpGet] + [Authorize] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{RomId}")] + public async Task GetAllStateAsync(long RomId, bool IsMediaGroup = false) + { + var user = await _userManager.GetUserAsync(User); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT Id, StateDateTime, `Name`, Screenshot FROM GameState WHERE RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid ORDER BY StateDateTime DESC;"; + Dictionary dbDict = new Dictionary + { + { "romid", RomId }, + { "userid", user.Id }, + { "ismediagroup", IsMediaGroup } + }; + DataTable data = db.ExecuteCMD(sql, dbDict); + + List gameStates = new List(); + foreach (DataRow row in data.Rows) + { + gameStates.Add(BuildGameStateItem(row)); + } + + return Ok(gameStates); + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpGet] + [Authorize] + [ProducesResponseType(typeof(Models.GameStateItem), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{RomId}/{StateId}")] + public async Task GetStateAsync(long RomId, long StateId, bool IsMediaGroup = false) + { + var user = await _userManager.GetUserAsync(User); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT Id, StateDateTime, `Name`, Screenshot FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; + Dictionary dbDict = new Dictionary + { + { "id", StateId }, + { "romid", RomId }, + { "userid", user.Id }, + { "ismediagroup", IsMediaGroup } + }; + DataTable data = db.ExecuteCMD(sql, dbDict); + + if (data.Rows.Count == 0) + { + // invalid match - return not found + return NotFound(); + } + else + { + GameStateItem stateItem = BuildGameStateItem(data.Rows[0]); + + return Ok(stateItem); + } + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpDelete] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{RomId}/{StateId}")] + public async Task DeleteStateAsync(long RomId, long StateId, bool IsMediaGroup = false) + { + var user = await _userManager.GetUserAsync(User); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "DELETE FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; + Dictionary dbDict = new Dictionary + { + { "id", StateId }, + { "romid", RomId }, + { "userid", user.Id }, + { "ismediagroup", IsMediaGroup } + }; + db.ExecuteNonQuery(sql, dbDict); + + return Ok(); + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpPut] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{RomId}/{StateId}")] + public async Task EditStateAsync(long RomId, long StateId, GameStateItemUpdateModel model, bool IsMediaGroup = false) + { + var user = await _userManager.GetUserAsync(User); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "UPDATE GameState SET `Name` = @name WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; + Dictionary dbDict = new Dictionary + { + { "id", StateId }, + { "romid", RomId }, + { "userid", user.Id }, + { "ismediagroup", IsMediaGroup }, + { "name", model.Name } + }; + db.ExecuteNonQuery(sql, dbDict); + + return Ok(); + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpGet] + [Authorize] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{RomId}/{StateId}/Screenshot/")] + [Route("{RomId}/{StateId}/Screenshot/image.png")] + public async Task GetStateScreenshotAsync(long RomId, long StateId, bool IsMediaGroup = false) + { + var user = await _userManager.GetUserAsync(User); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT Screenshot FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; + Dictionary dbDict = new Dictionary + { + { "id", StateId }, + { "romid", RomId }, + { "userid", user.Id }, + { "ismediagroup", IsMediaGroup } + }; + DataTable data = db.ExecuteCMD(sql, dbDict); + + if (data.Rows.Count == 0) + { + // invalid match - return not found + return NotFound(); + } + else + { + string filename = "image.jpg"; + byte[] bytes = (byte[])data.Rows[0][0]; + string contentType = "image/png"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(bytes, contentType); + } + } + + [MapToApiVersion("1.0")] + [MapToApiVersion("1.1")] + [HttpGet] + [Authorize] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Route("{RomId}/{StateId}/State/")] + [Route("{RomId}/{StateId}/State/savestate.state")] + public async Task GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false) + { + var user = await _userManager.GetUserAsync(User); + Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; + Dictionary dbDict = new Dictionary + { + { "id", StateId }, + { "romid", RomId }, + { "userid", user.Id }, + { "ismediagroup", IsMediaGroup } + }; + DataTable data = db.ExecuteCMD(sql, dbDict); + + if (data.Rows.Count == 0) + { + // invalid match - return not found + return NotFound(); + } + else + { + string filename = "savestate.state"; + byte[] bytes = (byte[])data.Rows[0][0]; + string contentType = "application/octet-stream"; + + var cd = new System.Net.Mime.ContentDisposition + { + FileName = filename, + Inline = true, + }; + + Response.Headers.Add("Content-Disposition", cd.ToString()); + Response.Headers.Add("Cache-Control", "public, max-age=604800"); + + return File(bytes, contentType); + } + } + + private Models.GameStateItem BuildGameStateItem(DataRow dr) + { + bool HasScreenshot = true; + if (dr["Screenshot"] == DBNull.Value) + { + HasScreenshot = false; + } + GameStateItem stateItem = new GameStateItem + { + Id = (long)dr["Id"], + Name = (string)dr["Name"], + SaveTime = DateTime.Parse(((DateTime)dr["StateDateTime"]).ToString("yyyy-MM-ddThh:mm:ss") + 'Z'), + HasScreenshot = HasScreenshot + }; + + return stateItem; + } + } +} \ No newline at end of file diff --git a/gaseous-server/Models/GameState.cs b/gaseous-server/Models/GameState.cs new file mode 100644 index 0000000..d148d63 --- /dev/null +++ b/gaseous-server/Models/GameState.cs @@ -0,0 +1,35 @@ +namespace gaseous_server.Models +{ + public class UploadStateModel + { + public string ScreenshotByteArrayBase64 { get; set; } + public string StateByteArrayBase64 { get; set; } + public byte[] ScreenshotByteArray + { + get + { + return Convert.FromBase64String(ScreenshotByteArrayBase64); + } + } + public byte[] StateByteArray + { + get + { + return Convert.FromBase64String(StateByteArrayBase64); + } + } + } + + public class GameStateItem + { + public long Id { get; set; } + public string Name { get; set; } = ""; + public DateTime SaveTime { get; set; } + public bool HasScreenshot { get; set; } + } + + public class GameStateItemUpdateModel + { + public string Name { get; set; } = ""; + } +} \ No newline at end of file diff --git a/gaseous-server/Support/Database/MySQL/gaseous-1015.sql b/gaseous-server/Support/Database/MySQL/gaseous-1015.sql new file mode 100644 index 0000000..3bf7c87 --- /dev/null +++ b/gaseous-server/Support/Database/MySQL/gaseous-1015.sql @@ -0,0 +1,11 @@ +CREATE TABLE `GameState` ( + `Id` BIGINT NOT NULL AUTO_INCREMENT, + `UserId` VARCHAR(45) NULL, + `RomId` BIGINT NULL, + `IsMediaGroup` INT NULL, + `StateDateTime` DATETIME NULL, + `Name` VARCHAR(100) NULL, + `Screenshot` LONGBLOB NULL, + `State` LONGBLOB NULL, + PRIMARY KEY (`Id`), + INDEX `idx_UserId` (`UserId` ASC) VISIBLE); diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj index efe47df..0a82578 100644 --- a/gaseous-server/gaseous-server.csproj +++ b/gaseous-server/gaseous-server.csproj @@ -59,6 +59,7 @@ + @@ -95,5 +96,6 @@ + diff --git a/gaseous-server/wwwroot/images/SaveStates.png b/gaseous-server/wwwroot/images/SaveStates.png new file mode 100644 index 0000000..66cca2d Binary files /dev/null and b/gaseous-server/wwwroot/images/SaveStates.png differ diff --git a/gaseous-server/wwwroot/index.html b/gaseous-server/wwwroot/index.html index 848c5de..4c57e73 100644 --- a/gaseous-server/wwwroot/index.html +++ b/gaseous-server/wwwroot/index.html @@ -4,6 +4,7 @@ + @@ -14,6 +15,7 @@ + @@ -43,6 +45,9 @@ + +
+ diff --git a/gaseous-server/wwwroot/pages/EmulatorJS.html b/gaseous-server/wwwroot/pages/EmulatorJS.html index 64adf78..8165dd7 100644 --- a/gaseous-server/wwwroot/pages/EmulatorJS.html +++ b/gaseous-server/wwwroot/pages/EmulatorJS.html @@ -18,18 +18,56 @@ // URL to Game rom EJS_gameUrl = decodeURIComponent(getQueryString('rompath', 'string')); + // load state if defined + if (StateUrl) { + console.log('Loading saved state from: ' + StateUrl); + EJS_loadStateURL = StateUrl; + EJS_startOnLoaded = true; + } + // Path to the data directory EJS_pathtodata = '/emulators/EmulatorJS/data/'; EJS_DEBUG_XX = false; - EJS_startOnLoaded = false; - EJS_backgroundImage = emuBackground; EJS_backgroundBlur = true; + EJS_fullscreenOnLoaded = false; + EJS_gameName = emuGameTitle; EJS_threads = false; + + EJS_onSaveState = function(e) { + var returnValue = { + "ScreenshotByteArrayBase64": btoa(Uint8ToString(e.screenshot)), + "StateByteArrayBase64": btoa(Uint8ToString(e.state)) + }; + + var url = '/api/v1.1/StateManager/' + romId + '?IsMediaGroup=' + IsMediaGroup; + + ajaxCall( + url, + 'POST', + function (result) { + console.log("Upload complete"); + console.log(result); + + displayNotification('State Saved', 'Game state has been saved.', '/api/v1.1/StateManager/' + romId + '/' + result.value.id + '/Screenshot/image.png?IsMediaGroup=' + IsMediaGroup); + }, + function (error) { + console.log("An error occurred"); + console.log(error); + }, + JSON.stringify(returnValue) + ); + + returnValue = undefined; + } + + EJS_onLoadState = function(e) { + showDialog('emulatorloadstate', { "romId": romId, "IsMediaGroup": IsMediaGroup }); + } \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/dialogs/emulatorloadstate.html b/gaseous-server/wwwroot/pages/dialogs/emulatorloadstate.html new file mode 100644 index 0000000..b0ed4e9 --- /dev/null +++ b/gaseous-server/wwwroot/pages/dialogs/emulatorloadstate.html @@ -0,0 +1,159 @@ +
+ +
+ + \ No newline at end of file diff --git a/gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html b/gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html index 70cc2ee..d61d073 100644 --- a/gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html +++ b/gaseous-server/wwwroot/pages/dialogs/mediagroupdelete.html @@ -1,4 +1,4 @@ -

Are you sure you want to delete this media group?

+

Are you sure you want to delete this media group and all associated saved states?

Warning: This cannot be undone!

diff --git a/gaseous-server/wwwroot/pages/dialogs/romdelete.html b/gaseous-server/wwwroot/pages/dialogs/romdelete.html index f3997f0..62af3b3 100644 --- a/gaseous-server/wwwroot/pages/dialogs/romdelete.html +++ b/gaseous-server/wwwroot/pages/dialogs/romdelete.html @@ -1,4 +1,4 @@ -

Are you sure you want to delete this ROM?

+

Are you sure you want to delete this ROM and all associated saved states?

Warning: This cannot be undone!

diff --git a/gaseous-server/wwwroot/pages/dialogs/romsdelete.html b/gaseous-server/wwwroot/pages/dialogs/romsdelete.html index 1f92a8d..4e2ab32 100644 --- a/gaseous-server/wwwroot/pages/dialogs/romsdelete.html +++ b/gaseous-server/wwwroot/pages/dialogs/romsdelete.html @@ -1,4 +1,4 @@ -

Are you sure you want to delete the selected ROMs?

+

Are you sure you want to delete the selected ROMs and any associated saved states?

Warning: This cannot be undone!

diff --git a/gaseous-server/wwwroot/pages/emulator.html b/gaseous-server/wwwroot/pages/emulator.html index bd38b78..60889b2 100644 --- a/gaseous-server/wwwroot/pages/emulator.html +++ b/gaseous-server/wwwroot/pages/emulator.html @@ -6,7 +6,15 @@