diff --git a/BizHawk.Client.Common/BizHawk.Client.Common.csproj b/BizHawk.Client.Common/BizHawk.Client.Common.csproj index 6c0d64b248..c35c7ad9d1 100644 --- a/BizHawk.Client.Common/BizHawk.Client.Common.csproj +++ b/BizHawk.Client.Common/BizHawk.Client.Common.csproj @@ -154,6 +154,7 @@ Bk2Movie.cs + @@ -229,7 +230,6 @@ - diff --git a/BizHawk.Client.Common/CoreFileProvider.cs b/BizHawk.Client.Common/CoreFileProvider.cs index 0a56502f40..7e46e32003 100644 --- a/BizHawk.Client.Common/CoreFileProvider.cs +++ b/BizHawk.Client.Common/CoreFileProvider.cs @@ -32,6 +32,17 @@ namespace BizHawk.Client.Common return PathManager.SaveRamPath(Global.Game); } + + public string GetRetroSaveRAMDirectory() + { + return PathManager.RetroSaveRAMDirectory(Global.Game); + } + + public string GetRetroSystemPath() + { + return PathManager.RetroSystemPath(Global.Game); + } + public string GetGameBasePath() { return PathManager.GetGameBasePath(Global.Game); diff --git a/BizHawk.Client.Common/PathManager.cs b/BizHawk.Client.Common/PathManager.cs index 008b4f841c..ad5705e534 100644 --- a/BizHawk.Client.Common/PathManager.cs +++ b/BizHawk.Client.Common/PathManager.cs @@ -292,6 +292,38 @@ namespace BizHawk.Client.Common return Path.Combine(MakeAbsolutePath(pathEntry.Path, game.System), name) + ".SaveRAM"; } + public static string RetroSaveRAMDirectory(GameInfo game) + { + //hijinx here to get the core name out of the game name + var name = FilesystemSafeName(game); + name = Path.GetDirectoryName(name); + if (name == "") name = FilesystemSafeName(game); + + if (Global.MovieSession.Movie.IsActive) + { + name = Path.Combine(name, "movie-" + Path.GetFileNameWithoutExtension(Global.MovieSession.Movie.Filename)); + } + + var pathEntry = Global.Config.PathEntries[game.System, "Save RAM"] ?? + Global.Config.PathEntries[game.System, "Base"]; + + return Path.Combine(MakeAbsolutePath(pathEntry.Path, game.System), name); + } + + + public static string RetroSystemPath(GameInfo game) + { + //hijinx here to get the core name out of the game name + var name = FilesystemSafeName(game); + name = Path.GetDirectoryName(name); + if(name == "") name = FilesystemSafeName(game); + + var pathEntry = Global.Config.PathEntries[game.System, "System"] ?? + Global.Config.PathEntries[game.System, "Base"]; + + return Path.Combine(MakeAbsolutePath(pathEntry.Path, game.System), name); + } + public static string GetGameBasePath(GameInfo game) { var name = FilesystemSafeName(game); diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 61da326679..cc977ef0a4 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -255,39 +255,49 @@ namespace BizHawk.Client.Common if (AsLibretro) { - //we'll need to generate a game name for purposes of state/saveram pathing etc. - string gameName; - string codePathPart = Path.GetFileNameWithoutExtension(nextComm.LaunchLibretroCore); var retro = new LibRetroEmulator(nextComm, nextComm.LaunchLibretroCore); nextEmulator = retro; + //kind of dirty.. we need to stash this, and then we can unstash it in a moment, in case the core doesnt fail + var oldGame = Global.Game; + if (retro.Description.SupportsNoGame && string.IsNullOrEmpty(path)) { + //must be done before LoadNoGame (which triggers retro_init and the paths to be consumed by the core) + //game name == name of core + var gameName = codePathPart; + Global.Game = game = new GameInfo { Name = gameName, System = "Libretro" }; + //if we are allowed to run NoGame and we dont have a game, boot up the core that way bool ret = retro.LoadNoGame(); + + Global.Game = oldGame; + if (!ret) { DoLoadErrorCallback("LibretroNoGame failed to load. This is weird", "Libretro"); retro.Dispose(); return false; } - - //game name == name of core - gameName = codePathPart; } else { bool ret; + //must be done before LoadNoGame (which triggers retro_init and the paths to be consumed by the core) + //game name == name of core + extensionless_game_filename + var gameName = Path.Combine(codePathPart, Path.GetFileNameWithoutExtension(file.Name)); + Global.Game = game = new GameInfo { Name = gameName, System = "Libretro" }; + //if the core requires an archive file, then try passing the filename of the archive //(but do we ever need to actually load the contents of the archive file into ram?) if (retro.Description.NeedsArchives) { if (file.IsArchiveMember) throw new InvalidOperationException("Should not have bound file member for libretro block_extract core"); - retro.LoadPath(file.FullPathWithoutMember); + ret = retro.LoadPath(file.FullPathWithoutMember); } else { @@ -303,21 +313,18 @@ namespace BizHawk.Client.Common if (ret) ret = retro.LoadData(file.ReadAllBytes()); } - - if (!ret) - { - DoLoadErrorCallback("Libretro failed to load the given file. This is probably due to a core/content mismatch. Moreover, the process is now likely to be hosed. We suggest you restart the program.", "Libretro"); - retro.Dispose(); - return false; - } - } - //game name == name of core + extensionless_game_filename - gameName = Path.Combine(codePathPart, Path.GetFileNameWithoutExtension(file.Name)); + Global.Game = oldGame; + + if (!ret) + { + DoLoadErrorCallback("Libretro failed to load the given file. This is probably due to a core/content mismatch. Moreover, the process is now likely to be hosed. We suggest you restart the program.", "Libretro"); + retro.Dispose(); + return false; + } } - game = new GameInfo { Name = gameName, System = "Libretro" }; } else diff --git a/BizHawk.Client.Common/config/Config.cs b/BizHawk.Client.Common/config/Config.cs index 2e487fe448..ea8487d026 100644 --- a/BizHawk.Client.Common/config/Config.cs +++ b/BizHawk.Client.Common/config/Config.cs @@ -74,8 +74,10 @@ namespace BizHawk.Client.Common public bool AcceptBackgroundInput = false; public bool SingleInstanceMode = false; public bool AllowUD_LR = false; + public bool ForbidUD_LR = false; public bool ShowContextMenu = true; public bool EnableBackupMovies = true; + public bool MoviesOnDisk = false; public bool HotkeyConfigAutoTab = true; public bool InputConfigAutoTab = true; public bool ShowLogWindow = false; diff --git a/BizHawk.Client.Common/config/PathEntry.cs b/BizHawk.Client.Common/config/PathEntry.cs index b433af2eee..9db7d454c0 100644 --- a/BizHawk.Client.Common/config/PathEntry.cs +++ b/BizHawk.Client.Common/config/PathEntry.cs @@ -315,10 +315,11 @@ namespace BizHawk.Client.Common new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Base", Path = Path.Combine(".", "Libretro"), Ordinal = 0 }, new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Cores", Path = Path.Combine(".", "Cores"), Ordinal = 1 }, - new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 }, - new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 3 }, - new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 }, - new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 }, + new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "System", Path = Path.Combine(".", "System"), Ordinal = 2 }, + new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 3 }, + new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 4 }, + new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 5 }, + new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 6 }, }; } diff --git a/BizHawk.Client.Common/inputAdapters/InputAdapters.cs b/BizHawk.Client.Common/inputAdapters/InputAdapters.cs index 46a31fb849..4042aaa89d 100644 --- a/BizHawk.Client.Common/inputAdapters/InputAdapters.cs +++ b/BizHawk.Client.Common/inputAdapters/InputAdapters.cs @@ -105,8 +105,13 @@ namespace BizHawk.Client.Common return Source.GetFloat(name); } + HashSet Unpresses = new HashSet(); + public bool IsPressed(string button) { + bool PriorityUD_LR = !Global.Config.AllowUD_LR && !Global.Config.ForbidUD_LR; //implied by neither of the others being set (left as non-enum for back-compatibility) + + if (Global.Config.AllowUD_LR) { return Source.IsPressed(button); @@ -116,37 +121,63 @@ namespace BizHawk.Client.Common //" C " is for N64 "P1 C Up" and the like, which should not be subject to mutexing + //regarding the unpressing and UDLR logic...... don't think about it. don't question it. don't look at it. + if (button.Contains("Down") && !button.Contains(" C ")) { + if (!Source.IsPressed(button)) Unpresses.Remove(button); prefix = button.GetPrecedingString("Down"); - if (Source.IsPressed(prefix + "Up")) - return false; + string other = prefix + "Up"; + if (Source.IsPressed(other)) + { + if (Unpresses.Contains(button)) return false; + if (Global.Config.ForbidUD_LR) return false; + Unpresses.Add(other); + } + else Unpresses.Remove(button); } if (button.Contains("Up") && !button.Contains(" C ")) { + if (!Source.IsPressed(button)) Unpresses.Remove(button); prefix = button.GetPrecedingString("Up"); - if (Source.IsPressed(prefix + "Down")) - return false; + string other = prefix + "Down"; + if (Source.IsPressed(other)) + { + if (Unpresses.Contains(button)) return false; + if (Global.Config.ForbidUD_LR) return false; + Unpresses.Add(other); + } + else Unpresses.Remove(button); } if (button.Contains("Right") && !button.Contains(" C ")) { + if (!Source.IsPressed(button)) Unpresses.Remove(button); prefix = button.GetPrecedingString("Right"); - if (Source.IsPressed(prefix + "Left")) + string other = prefix + "Left"; + if (Source.IsPressed(other)) { - return false; + if (Unpresses.Contains(button)) return false; + if (Global.Config.ForbidUD_LR) return false; + Unpresses.Add(other); } + else Unpresses.Remove(button); } if (button.Contains("Left") && !button.Contains(" C ")) { + if (!Source.IsPressed(button)) Unpresses.Remove(button); prefix = button.GetPrecedingString("Left"); - if (Source.IsPressed(prefix + "Right")) + string other = prefix + "Right"; + if (Source.IsPressed(other)) { - return false; + if (Unpresses.Contains(button)) return false; + if (Global.Config.ForbidUD_LR) return false; + Unpresses.Add(other); } + else Unpresses.Remove(button); } return Source.IsPressed(button); diff --git a/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs b/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs index 2deacda209..99425ac9ed 100644 --- a/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs +++ b/BizHawk.Client.Common/movie/bk2/Bk2Movie.InputLog.cs @@ -8,7 +8,7 @@ namespace BizHawk.Client.Common { public partial class Bk2Movie { - protected List _log = new List(); + protected IStringLog _log; protected string LogKey = string.Empty; public string GetInputLog() @@ -58,7 +58,7 @@ namespace BizHawk.Client.Common // We are in record mode so replace the movie log with the one from the savestate if (!Global.MovieSession.MultiTrack.IsActive) { - if (Global.Config.EnableBackupMovies && MakeBackup && _log.Any()) + if (Global.Config.EnableBackupMovies && MakeBackup && _log.Count != 0) { SaveBackup(); MakeBackup = false; diff --git a/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs b/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs index 12bffc939e..ddbd4f4dd0 100644 --- a/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs +++ b/BizHawk.Client.Common/movie/bk2/Bk2Movie.cs @@ -27,6 +27,8 @@ namespace BizHawk.Client.Common MakeBackup = true; Header[HeaderKeys.MOVIEVERSION] = "BizHawk v2.0.0"; + + _log = StringLogUtil.MakeStringLog(); } private string _filename; diff --git a/BizHawk.Client.Common/movie/bk2/StringLogs.cs b/BizHawk.Client.Common/movie/bk2/StringLogs.cs new file mode 100644 index 0000000000..16391df2b5 --- /dev/null +++ b/BizHawk.Client.Common/movie/bk2/StringLogs.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +using BizHawk.Common; + +namespace BizHawk.Client.Common +{ + public static class StringLogUtil + { + public static bool DefaultToDisk; + public static IStringLog MakeStringLog() + { + if (DefaultToDisk) + return new DiskStringLog(); + else return new ListStringLog(); + } + } + + public interface IStringLog : IDisposable, IEnumerable + { + void RemoveAt(int index); + int Count { get; } + void Clear(); + void Add(string str); + string this[int index] { get; set; } + void Insert(int index, string val); + void InsertRange(int index, IEnumerable collection); + void AddRange(IEnumerable collection); + void RemoveRange(int index, int count); + IStringLog Clone(); + void CopyTo(string[] array); + void CopyTo(int index, string[] array, int arrayIndex, int count); + } + + class ListStringLog : List, IStringLog + { + public IStringLog Clone() + { + ListStringLog ret = new ListStringLog(); + ret.AddRange(this); + return ret; + } + + public void Dispose() { } + } + + /// + /// A dumb-ish IStringLog with storage on disk with no provision for recovering lost space, except upon Clear() + /// The purpose here is to avoid having too complicated buggy logic or a dependency on sqlite or such. + /// It should be faster than those alternatives, but wasteful of disk space. + /// It should also be easier to add new IList-like methods than dealing with a database + /// + class DiskStringLog : IStringLog + { + List Offsets = new List(); + long cursor = 0; + BinaryWriter bw; + BinaryReader br; + + FileStream stream; + public DiskStringLog() + { + var path = TempFileCleaner.GetTempFilename("movieOnDisk"); + stream = new FileStream(path, FileMode.Create, System.Security.AccessControl.FileSystemRights.FullControl, FileShare.None, 4 * 1024, FileOptions.DeleteOnClose); + bw = new BinaryWriter(stream); + br = new BinaryReader(stream); + } + + public IStringLog Clone() + { + DiskStringLog ret = new DiskStringLog(); + for (int i = 0; i < Count; i++) + ret.Add(this[i]); + return ret; + } + + public void Dispose() + { + stream.Dispose(); + } + + public int Count { get { return Offsets.Count; } } + + public void Clear() + { + stream.SetLength(0); + Offsets.Clear(); + cursor = 0; + } + + public void Add(string str) + { + Offsets.Add(stream.Position); + bw.Write(str); + bw.Flush(); + } + + public void RemoveAt(int index) + { + Offsets.RemoveAt(index); + //no garbage collection in the disk file... oh well. + } + + public string this[int index] + { + get + { + stream.Position = Offsets[index]; + return br.ReadString(); + } + set + { + stream.Position = stream.Length; + Offsets[index] = stream.Position; + bw.Write(value); + bw.Flush(); + } + } + + public void Insert(int index, string val) + { + Offsets.Insert(index, stream.Position); + bw.Write(val); + bw.Flush(); + } + + public void InsertRange(int index, IEnumerable collection) + { + foreach(var item in collection) + Insert(index++,item); + } + + public void AddRange(IEnumerable collection) + { + foreach (var item in collection) + Add(item); + } + + class Enumerator : IEnumerator + { + public DiskStringLog log; + int index; + public string Current { get { return log[index]; } } + object System.Collections.IEnumerator.Current { get { return log[index]; } } + bool System.Collections.IEnumerator.MoveNext() + { + index++; + if (index >= log.Count) + { + index = log.Count; + return false; + } + return true; + } + void System.Collections.IEnumerator.Reset() { index = 0; } + + public void Dispose() { } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator() { log = this }; + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return new Enumerator() { log = this }; + } + + public void RemoveRange(int index, int count) + { + int end = index + count - 1; + for (int i = 0; i < count; i++) + { + RemoveAt(end); + end--; + } + } + + public void CopyTo(string[] array) + { + for (int i = 0; i < Count; i++) + array[i] = this[i]; + } + + public void CopyTo(int index, string[] array, int arrayIndex, int count) + { + for (int i = 0; i < count; i++) + array[i + arrayIndex] = this[index + i]; + } + } +} diff --git a/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs b/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs index 0ddee97697..30e94dd5a9 100644 --- a/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs +++ b/BizHawk.Client.Common/movie/conversions/MovieConversionExtensions.cs @@ -145,7 +145,7 @@ namespace BizHawk.Client.Common.MovieConversionExtensions tas.BinarySavestate = savestate; tas.ClearLagLog(); - List entries = old.GetLogEntries(); + var entries = old.GetLogEntries(); tas.CopyLog(entries.Skip(frame)); tas.CopyVerificationLog(old.VerificationLog); @@ -220,7 +220,7 @@ namespace BizHawk.Client.Common.MovieConversionExtensions tas.TasStateManager.Clear(); tas.ClearLagLog(); - List entries = old.GetLogEntries(); + var entries = old.GetLogEntries(); tas.CopyVerificationLog(old.VerificationLog); tas.CopyVerificationLog(entries); diff --git a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs index b290831ff5..1350300629 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasBranch.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasBranch.cs @@ -12,7 +12,7 @@ namespace BizHawk.Client.Common { public int Frame { get; set; } public byte[] CoreData { get; set; } - public List InputLog { get; set; } + public IStringLog InputLog { get; set; } public BitmapBuffer OSDFrameBuffer { get; set; } public TasLagLog LagLog { get; set; } public TasMovieChangeLog ChangeLog { get; set; } @@ -64,8 +64,9 @@ namespace BizHawk.Client.Common bs.PutLump(ninput, delegate(TextWriter tw) { - foreach (var line in b.InputLog) - tw.WriteLine(line); + int todo = b.InputLog.Count; + for (int i = 0; i < todo; i++) + tw.WriteLine(b.InputLog[i]); }); bs.PutLump(nframebuffer, delegate(Stream s) @@ -146,7 +147,7 @@ namespace BizHawk.Client.Common bl.GetLump(ninput, true, delegate(TextReader tr) { - b.InputLog = new List(); + b.InputLog = StringLogUtil.MakeStringLog(); string line; while ((line = tr.ReadLine()) != null) b.InputLog.Add(line); diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs index 58ce372164..51f979c27f 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.IO.cs @@ -319,7 +319,7 @@ namespace BizHawk.Client.Common ChangeLog.ClearLog(); } - private static string InputLogToString(List log) + private static string InputLogToString(IStringLog log) { var sb = new StringBuilder(); foreach (var record in log) diff --git a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs index b78016fdf1..4bfdeeb103 100644 --- a/BizHawk.Client.Common/movie/tasproj/TasMovie.cs +++ b/BizHawk.Client.Common/movie/tasproj/TasMovie.cs @@ -21,7 +21,7 @@ namespace BizHawk.Client.Common public readonly TasSession Session; private readonly TasLagLog LagLog = new TasLagLog(); private readonly Dictionary InputStateCache = new Dictionary(); - public readonly List VerificationLog = new List(); // For movies that do not begin with power-on, this is the input required to get into the initial state + public readonly IStringLog VerificationLog = StringLogUtil.MakeStringLog(); // For movies that do not begin with power-on, this is the input required to get into the initial state public readonly TasBranchCollection Branches = new TasBranchCollection(); private BackgroundWorker _progressReportWorker = null; @@ -81,7 +81,7 @@ namespace BizHawk.Client.Common } public TasLagLog TasLagLog { get { return LagLog; } } - public List InputLog { get { return _log; } } + public IStringLog InputLog { get { return _log; } } public TasMovieMarkerList Markers { get; set; } public bool BindMarkersToInput { get; set; } public bool UseInputCache { get; set; } @@ -331,7 +331,7 @@ namespace BizHawk.Client.Common } } - public List GetLogEntries() + public IStringLog GetLogEntries() { return _log; } @@ -350,7 +350,7 @@ namespace BizHawk.Client.Common { TimelineBranchFrame = null; - if (Global.Config.EnableBackupMovies && MakeBackup && _log.Any()) + if (Global.Config.EnableBackupMovies && MakeBackup && _log.Count != 0) { SaveBackup(); MakeBackup = false; @@ -500,7 +500,8 @@ namespace BizHawk.Client.Common { int? divergentPoint = DivergentPoint(_log, branch.InputLog); - _log = branch.InputLog.ToList(); + if (_log != null) _log.Dispose(); + _log = branch.InputLog.Clone(); //_changes = true; // if there are branch states, they will be loaded anyway @@ -523,7 +524,7 @@ namespace BizHawk.Client.Common } // TODO: use LogGenerators rather than string comparisons - private int? DivergentPoint(List currentLog, List newLog) + private int? DivergentPoint(IStringLog currentLog, IStringLog newLog) { int max = newLog.Count; if (currentLog.Count < newLog.Count) diff --git a/BizHawk.Client.Common/rewind/Rewinder.cs b/BizHawk.Client.Common/rewind/Rewinder.cs index 438b2a44c8..03c01aa2ed 100644 --- a/BizHawk.Client.Common/rewind/Rewinder.cs +++ b/BizHawk.Client.Common/rewind/Rewinder.cs @@ -58,9 +58,22 @@ namespace BizHawk.Client.Common get { return _rewindFrequency; } } + bool IsRewindEnabledAtAll + { + get + { + if (!Global.Config.RewindEnabledLarge && !Global.Config.RewindEnabledMedium && !Global.Config.RewindEnabledSmall) + return false; + else return true; + } + } + // TOOD: this should not be parameterless?! It is only possible due to passing a static context in public void CaptureRewindState() { + if (!IsRewindEnabledAtAll) + return; + if (Global.Emulator.HasSavestates()) { if (_rewindImpossible) @@ -83,6 +96,12 @@ namespace BizHawk.Client.Common public void DoRewindSettings() { + if (_rewindThread != null) + { + _rewindThread.Dispose(); + _rewindThread = null; + } + if (Global.Emulator.HasSavestates()) { // This is the first frame. Capture the state, and put it in LastState for future deltas to be compared against. @@ -132,11 +151,6 @@ namespace BizHawk.Client.Common _rewindBuffer = new StreamBlobDatabase(Global.Config.Rewind_OnDisk, capacity, BufferManage); - if (_rewindThread != null) - { - _rewindThread.Dispose(); - } - _rewindThread = new RewindThreader(this, Global.Config.Rewind_IsThreaded); } } @@ -144,7 +158,7 @@ namespace BizHawk.Client.Common public void Rewind(int frames) { - if (Global.Emulator.HasSavestates()) + if (Global.Emulator.HasSavestates() && _rewindThread != null) { _rewindThread.Rewind(frames); } diff --git a/BizHawk.Client.Common/rewind/StreamBlobDatabase.cs b/BizHawk.Client.Common/rewind/StreamBlobDatabase.cs index 1ec2f9e157..50dbc74333 100644 --- a/BizHawk.Client.Common/rewind/StreamBlobDatabase.cs +++ b/BizHawk.Client.Common/rewind/StreamBlobDatabase.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; +using BizHawk.Common; + namespace BizHawk.Client.Common { /// @@ -23,7 +25,7 @@ namespace BizHawk.Client.Common _mCapacity = capacity; if (onDisk) { - var path = Path.Combine(Path.GetTempPath(), "bizhawk.rewindbuf-pid" + System.Diagnostics.Process.GetCurrentProcess().Id + "-" + Guid.NewGuid()); + var path = TempFileCleaner.GetTempFilename("rewindbuf"); // I checked the DeleteOnClose operation to make sure it cleans up when the process is aborted, and it seems to. // Otherwise we would have a more complex tempfile management problem here. diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index a3adff8f2b..20d4bb7b89 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -733,6 +733,12 @@ RegisterBoxControl.cs + + Form + + + GameShark.cs + Form @@ -1379,6 +1385,9 @@ RegisterBoxControl.cs + + GameShark.cs + GBAGPUView.cs diff --git a/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs b/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs index 74a2f72974..3d7e6fa2a5 100644 --- a/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs +++ b/BizHawk.Client.EmuHawk/DisplayManager/DisplayManager.cs @@ -120,6 +120,7 @@ namespace BizHawk.Client.EmuHawk /// /// these variables will track the dimensions of the last frame's (or the next frame? this is confusing) emulator native output size + /// THIS IS OLD JUNK. I should get rid of it, I think. complex results from the last filter ingestion should be saved instead. /// int currEmuWidth, currEmuHeight; @@ -800,8 +801,16 @@ namespace BizHawk.Client.EmuHawk int currNativeWidth = presentationPanel.NativeSize.Width; int currNativeHeight = presentationPanel.NativeSize.Height; + currNativeWidth += ClientExtraPadding.Horizontal; + currNativeHeight += ClientExtraPadding.Vertical; + int width,height; - if(name == "emu") { width = currEmuWidth; height = currEmuHeight; } + if(name == "emu") { + width = currEmuWidth; + height = currEmuHeight; + width += GameExtraPadding.Horizontal; + height += GameExtraPadding.Vertical; + } else if(name == "native") { width = currNativeWidth; height = currNativeHeight; } else throw new InvalidOperationException("Unknown lua surface name: " +name); diff --git a/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs b/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs index f0314abfb1..e917046e48 100644 --- a/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs +++ b/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs @@ -157,10 +157,10 @@ namespace BizHawk.Client.EmuHawk.WinFormExtensions /// /// Handles EmuHawk specific issues before showing a modal dialog /// - public static DialogResult ShowHawkDialog(this Form form) + public static DialogResult ShowHawkDialog(this Form form, IWin32Window owner = null) { GlobalWin.Sound.StopSound(); - var result = form.ShowDialog(); + var result = (owner == null ? form.ShowDialog() : form.ShowDialog(owner)); GlobalWin.Sound.StartSound(); return result; } diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index 6d16c75173..04e9f9d47e 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -408,6 +408,8 @@ this.ShowMenuContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.timerMouseIdle = new System.Windows.Forms.Timer(this.components); this.OpenAdvancedMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + + this.gameSharkConverterToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainformMenu.SuspendLayout(); this.MainStatusBar.SuspendLayout(); this.MainFormContextMenu.SuspendLayout(); @@ -1841,6 +1843,9 @@ this.customToolToolStripMenuItem, this.toolStripSeparator29, this.MultiDiskBundlerFileMenuItem, + this.toolStripSeparator29, + this.MultiDiskBundlerFileMenuItem, + this.gameSharkConverterToolStripMenuItem, this.batchRunnerToolStripMenuItem}); this.ToolsSubMenu.Name = "ToolsSubMenu"; this.ToolsSubMenu.Size = new System.Drawing.Size(44, 17); @@ -3459,6 +3464,7 @@ this.toolStripMenuItem13.Name = "toolStripMenuItem13"; this.toolStripMenuItem13.Size = new System.Drawing.Size(157, 22); this.toolStripMenuItem13.Text = "&Autofire..."; + this.toolStripMenuItem13.Click += new System.EventHandler(this.AutofireMenuItem_Click); // // toolStripMenuItem14 // @@ -3502,16 +3508,19 @@ // this.SavestateTypeDefaultContextMenuItem.Name = "SavestateTypeDefaultContextMenuItem"; this.SavestateTypeDefaultContextMenuItem.Size = new System.Drawing.Size(67, 22); + this.SavestateTypeDefaultContextMenuItem.Text = "&Default"; // // SavestateBinaryContextMenuItem // this.SavestateBinaryContextMenuItem.Name = "SavestateBinaryContextMenuItem"; this.SavestateBinaryContextMenuItem.Size = new System.Drawing.Size(67, 22); + this.SavestateBinaryContextMenuItem.Text = "&Binary"; // // SavestateTextContextMenuItem // this.SavestateTextContextMenuItem.Name = "SavestateTextContextMenuItem"; this.SavestateTextContextMenuItem.Size = new System.Drawing.Size(67, 22); + this.SavestateTextContextMenuItem.Text = "&Text"; // // CoreSelectionContextSubMenu // @@ -3606,6 +3615,14 @@ this.OpenAdvancedMenuItem.Text = "Open Ad&vanced"; this.OpenAdvancedMenuItem.Click += new System.EventHandler(this.OpenAdvancedMenuItem_Click); // + // gameSharkConverterToolStripMenuItem + // + this.gameSharkConverterToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("gameSharkConverterToolStripMenuItem.Image"))); + this.gameSharkConverterToolStripMenuItem.Name = "gameSharkConverterToolStripMenuItem"; + this.gameSharkConverterToolStripMenuItem.Size = new System.Drawing.Size(189, 22); + this.gameSharkConverterToolStripMenuItem.Text = "GameShark Converter"; + this.gameSharkConverterToolStripMenuItem.Click += new System.EventHandler(this.gameSharkConverterToolStripMenuItem_Click); + // // MainForm // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; @@ -4025,6 +4042,6 @@ private System.Windows.Forms.ToolStripMenuItem CodeDataLoggerMenuItem; private System.Windows.Forms.ToolStripMenuItem setLibretroCoreToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem OpenAdvancedMenuItem; + private System.Windows.Forms.ToolStripMenuItem gameSharkConverterToolStripMenuItem; } } - diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 7b70d50049..0ec2e6f0df 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -1246,9 +1246,10 @@ namespace BizHawk.Client.EmuHawk AutoHawkMenuItem.Visible = VersionInfo.DeveloperBuild; BasicBotMenuItem.Enabled = GlobalWin.Tools.IsAvailable(); - + string toolPath = Path.Combine(Global.Config.PathEntries["Global", "GameTools"].Path, string.Format("{0}.dll", Global.Game.Name)); - customToolToolStripMenuItem.Enabled = File.Exists(toolPath); + customToolToolStripMenuItem.Enabled = File.Exists(toolPath); + gameSharkConverterToolStripMenuItem.Enabled = GlobalWin.Tools.IsAvailable(); } private void AutoHawkMenuItem_Click(object sender, EventArgs e) diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 3e0a71bc51..dac22baccc 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -321,7 +321,7 @@ namespace BizHawk.Client.EmuHawk if (cmdRom != null) { // Commandline should always override auto-load - LoadRom(cmdRom); + LoadRom(cmdRom, new MainForm.LoadRomArgs() { OpenAdvanced = new OpenAdvanced_OpenRom() }); if (Global.Game == null) { MessageBox.Show("Failed to load " + cmdRom + " specified on commandline"); @@ -3446,7 +3446,7 @@ namespace BizHawk.Client.EmuHawk LoadRomArgs CurrentLoadRomArgs; // Still needs a good bit of refactoring - public bool LoadRom(string path, LoadRomArgs args = null) + public bool LoadRom(string path, LoadRomArgs args) { //default args if (args == null) args = new LoadRomArgs(); @@ -3977,6 +3977,11 @@ namespace BizHawk.Client.EmuHawk Global.Config.DisplayMessages ^= true; } + private void gameSharkConverterToolStripMenuItem_Click(object sender, EventArgs e) + { + GlobalWin.Tools.Load(); + } + private void HelpSubMenu_DropDownOpened(object sender, EventArgs e) { FeaturesMenuItem.Visible = VersionInfo.DeveloperBuild; @@ -3994,4 +3999,4 @@ namespace BizHawk.Client.EmuHawk } } -} \ No newline at end of file +} diff --git a/BizHawk.Client.EmuHawk/MainForm.resx b/BizHawk.Client.EmuHawk/MainForm.resx index b1102bdcb9..76ad03062f 100644 --- a/BizHawk.Client.EmuHawk/MainForm.resx +++ b/BizHawk.Client.EmuHawk/MainForm.resx @@ -121,6 +121,39 @@ 17, 17 + + + iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAY9SURBVEhLtZZpUJNXFIZRZ/SHf6BCEWrHpaAUbS0TkDAp + IEtAiMCAYisIsSASZJNNJJRYGECCEjZtyxI26RQIYcAAsoU97IsKOgQLLuA+Ou770jd+aQhpAP3hO3cy + X77cc557zzn33Ki8/8z6ZMC7d++eP3/+8OHDBw8e4PPRo0dPnz59/fq19Of/6RMAz549u3nz5j8fNDk5 + eeXKlatXr05PT9+4cQPv7969iwnSqXL6KMDjx4/h8eLFi3V1ddHR0VZW1uvWrdPU0tb+evU3Ojpubm5C + oXB0dHRoaGhgYGBwcBDP2ChhuwDg1atXcD08PJyenm68xVj1qzX6zvStcdkOBUKfmnMc4WBFU1tLS0tT + U1N9fX1tbW1FRUVxcXFQUBB2RnhQAnj79i1i+ubNm/v37589ezYnJ2fTpk2rfiBTWZnu5f17KoYiagZL + 2vpEXV2dHZ0CgQDLh2SAU6dO0el0qS8FwMuXLxHK69evT01NIb4TExO9vb1OO1xpRzL3loropd3+/O6S + tn6ot6enC4DOzqSkpNTUVHlAeHh4WVkZkk/4nAW4ffv20aNHEUECgOAgn+Pj4vbhEValiF3d1TM4TAS6 + 5z9AW5skRDIAn883s3O4PDV9/vx5wqdiiBCczMzMxsZGOcD42NjYhQsXYIOIKQXIcsBkMreH/FY/MNrQ + 0EDkWRGAEJWWlq7X068SVMsDUEIjIyNzAcrLy6uqqqqrq8mWtowsfk5dp7BZiPKFw1kAnCCQ12/Q2xF6 + hM46fqa+YS4AcgNAe3s7l8ttbm7e4+GJ5QcHB7scZDEyio+V1GJCd3c3fM4CoBzt7e2pu739krMYyVkR + ySeUAhBDR0enkpISpNfGzh5xN9/uXMHnG5pZwwojIZ8nEomQGPicASDDiYmJ+iSyb1wqRlDSib7hcwoA + 2FhTqYYWtlHHTxpTTANDwnbuC9jt5h4Qm7zTdZd7GAuGB+LTOHnFNTU1YMCtFICEtLa26ulvpIfG7GMm + YtS3dSrkAEc0MjIygZMRfyLXh5mwwyfI0d3Lj8W2dXU/wGJbOrrGpv3JLfqLk5q6b78vDjzKZAZw69at + iIgICpW2N4SJkc4tUqgihRwIW1pZx9LpBw9jMj79mXHFpbza2jNJ7CSdDd/W1DX4+/vjwM4AkBBDoy0/ + +QTQA8JDWQli8fj8AFSRSNTlGxrp5hu8P4x5WlCNMkVKdPX0fwkMn5iY9PPzIzxLAKj9vLy8kLCI3v4B + 9BDZSZ4f4O3tbUTZutPT5+8yHnKDFoTidnLb+we3MDc3l8fjzQDu3bsnFovhGk7ReBcEwN22bdu+MzC0 + c94VEc0iDhqF8qOVvZPzzx5jYrGDg8OLFy9mADii5ubmK1dqaWmvYjD85gJgGto1OuXq1WtIW0zMrWyt + bWmlZTwA4uPjN35vgDdZudzCwsKUlBTCOyQBwIxG225oRCYZkVM4aUoBcXFxmzdvVtf4UkdnPcnQmGpr + d5j562mBgDjJgYGBsN3t7nHp0iVLS8snT54Q3iEJAPXn7OICS4zs7GylgPJyflpaOoeTevLk75WVlcgB + surl5YWuBUBuLtfAgNTX189gMNAzCNeEpCHy8PBQU/sCA8d9wRygSRw6dGjFihXLly+n0WhISWNjU1kZ + r6CgAEjZXUZIArh27VpMTMzixYsXLVqkpqaGNjIXAN0tLCxMS0tLRUUF8wkTpBQdLD8/39TUFJcr4Vcm + CQD3DMpUW1sbZhAeUGeXP4gAICBsNhttatmyZcQcmdTV1YuKikJCQmJjY7Emwqm8JAAI20R5YDlSOxUV + DQ0NCwsLKpWqq6u7ZMkS6Vs5YbKJiQkaKroeiUTCpX3nzh3Cm7ykAFy/2KOLi4vUeiFpamqiM+IWioqK + MjMzI1q/UkkBEGqD+EOgqqoqdaNMa9euRcvr6+vD9Ysd4Fl2ppRqBoB5CDQqAac8NDSUQqEgmQj60qVL + ES4EwdPTE7vEjY8Jjo6O1tbW6BlS47k1A4DwLygjIwONEKcRhfGh/hrx2dHRgeaOLaLYyGQyShPviWa5 + oGYBIFQxmhK2gvVijTY2NsgzQgG/KEdkErWrUOnzSxEgE7ygy6IwwEN14+sn+ZWJAMCSGPNK8vtHTJPX + +/f/AtNpu+2UY3h4AAAAAElFTkSuQmCC + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO diff --git a/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs b/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs index 05aa3e0237..a1e6ee192e 100644 --- a/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs +++ b/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs @@ -72,7 +72,10 @@ namespace BizHawk.Client.EmuHawk //scan the current libretro core to see if it can be launched with NoGame,and other stuff try { - using (var retro = new LibRetroEmulator(new BizHawk.Emulation.Common.CoreComm(null, null), core)) + //a stub corecomm. to reinforce that this won't touch the frontend at all! + //LibRetroEmulator should be able to survive having this stub corecomm + var coreComm = new BizHawk.Emulation.Common.CoreComm(null, null); + using (var retro = new LibRetroEmulator(coreComm, core)) { btnLibretroLaunchGame.Enabled = true; if (retro.Description.SupportsNoGame) diff --git a/BizHawk.Client.EmuHawk/Program.cs b/BizHawk.Client.EmuHawk/Program.cs index 794f97fac4..96c37c97c6 100644 --- a/BizHawk.Client.EmuHawk/Program.cs +++ b/BizHawk.Client.EmuHawk/Program.cs @@ -59,13 +59,16 @@ namespace BizHawk.Client.EmuHawk } } - BizHawk.Client.Common.TempFileCleaner.Start(); + BizHawk.Common.TempFileCleaner.Start(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + + HawkFile.ArchiveHandlerFactory = new SevenZipSharpArchiveHandler(); + string iniPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "config.ini"); Global.Config = ConfigService.Load(iniPath); Global.Config.ResolveDefaults(); - HawkFile.ArchiveHandlerFactory = new SevenZipSharpArchiveHandler(); + BizHawk.Client.Common.StringLogUtil.DefaultToDisk = Global.Config.MoviesOnDisk; //super hacky! this needs to be done first. still not worth the trouble to make this system fully proper for (int i = 0; i < args.Length; i++) @@ -307,7 +310,7 @@ namespace BizHawk.Client.EmuHawk void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e) { if (e.CommandLine.Count >= 1) - (MainForm as MainForm).LoadRom(e.CommandLine[0]); + (MainForm as MainForm).LoadRom(e.CommandLine[0], new MainForm.LoadRomArgs() { OpenAdvanced = new OpenAdvanced_OpenRom() }); } protected override void OnCreateMainForm() diff --git a/BizHawk.Client.EmuHawk/ToolAttributes.cs b/BizHawk.Client.EmuHawk/ToolAttributes.cs index fdec622477..93ccf61a03 100644 --- a/BizHawk.Client.EmuHawk/ToolAttributes.cs +++ b/BizHawk.Client.EmuHawk/ToolAttributes.cs @@ -1,16 +1,19 @@ using System; -using System.Drawing; +using System.Collections.Generic; namespace BizHawk.Client.EmuHawk { [AttributeUsage(AttributeTargets.Class)] public class ToolAttributes : Attribute { - public ToolAttributes(bool released) + public ToolAttributes(bool released, string[] supportedSystems) { Released = released; + SupportedSystems = supportedSystems; } public bool Released { get; private set; } + + public IEnumerable SupportedSystems { get; private set; } } } diff --git a/BizHawk.Client.EmuHawk/config/ControllerConfig.Designer.cs b/BizHawk.Client.EmuHawk/config/ControllerConfig.Designer.cs index 3c731c42dd..d8581756c4 100644 --- a/BizHawk.Client.EmuHawk/config/ControllerConfig.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/ControllerConfig.Designer.cs @@ -35,7 +35,6 @@ this.AutofireControlsTab = new System.Windows.Forms.TabPage(); this.AnalogControlsTab = new System.Windows.Forms.TabPage(); this.checkBoxAutoTab = new System.Windows.Forms.CheckBox(); - this.checkBoxUDLR = new System.Windows.Forms.CheckBox(); this.buttonOK = new System.Windows.Forms.Button(); this.buttonCancel = new System.Windows.Forms.Button(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); @@ -48,6 +47,10 @@ this.label3 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.label38 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.rbUDLRForbid = new System.Windows.Forms.RadioButton(); + this.rbUDLRPriority = new System.Windows.Forms.RadioButton(); + this.rbUDLRAllow = new System.Windows.Forms.RadioButton(); this.btnMisc = new BizHawk.Client.EmuHawk.MenuButton(); this.tabControl1.SuspendLayout(); this.tableLayoutPanel1.SuspendLayout(); @@ -82,7 +85,7 @@ this.AutofireControlsTab.Location = new System.Drawing.Point(4, 22); this.AutofireControlsTab.Name = "AutofireControlsTab"; this.AutofireControlsTab.Padding = new System.Windows.Forms.Padding(3); - this.AutofireControlsTab.Size = new System.Drawing.Size(554, 478); + this.AutofireControlsTab.Size = new System.Drawing.Size(554, 495); this.AutofireControlsTab.TabIndex = 1; this.AutofireControlsTab.Text = "Autofire Controls"; this.AutofireControlsTab.UseVisualStyleBackColor = true; @@ -91,7 +94,7 @@ // this.AnalogControlsTab.Location = new System.Drawing.Point(4, 22); this.AnalogControlsTab.Name = "AnalogControlsTab"; - this.AnalogControlsTab.Size = new System.Drawing.Size(554, 478); + this.AnalogControlsTab.Size = new System.Drawing.Size(554, 495); this.AnalogControlsTab.TabIndex = 2; this.AnalogControlsTab.Text = "Analog Controls"; this.AnalogControlsTab.UseVisualStyleBackColor = true; @@ -100,7 +103,7 @@ // this.checkBoxAutoTab.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.checkBoxAutoTab.AutoSize = true; - this.checkBoxAutoTab.Location = new System.Drawing.Point(394, 548); + this.checkBoxAutoTab.Location = new System.Drawing.Point(371, 548); this.checkBoxAutoTab.Name = "checkBoxAutoTab"; this.checkBoxAutoTab.Size = new System.Drawing.Size(70, 17); this.checkBoxAutoTab.TabIndex = 3; @@ -108,17 +111,6 @@ this.checkBoxAutoTab.UseVisualStyleBackColor = true; this.checkBoxAutoTab.CheckedChanged += new System.EventHandler(this.CheckBoxAutoTab_CheckedChanged); // - // checkBoxUDLR - // - this.checkBoxUDLR.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.checkBoxUDLR.AutoSize = true; - this.checkBoxUDLR.Location = new System.Drawing.Point(470, 548); - this.checkBoxUDLR.Name = "checkBoxUDLR"; - this.checkBoxUDLR.Size = new System.Drawing.Size(101, 17); - this.checkBoxUDLR.TabIndex = 4; - this.checkBoxUDLR.Text = "Allow U+D/L+R"; - this.checkBoxUDLR.UseVisualStyleBackColor = true; - // // buttonOK // this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); @@ -177,26 +169,26 @@ this.loadDefaultsToolStripMenuItem, this.clearToolStripMenuItem}); this.contextMenuStrip1.Name = "contextMenuStrip1"; - this.contextMenuStrip1.Size = new System.Drawing.Size(147, 70); + this.contextMenuStrip1.Size = new System.Drawing.Size(142, 70); // // testToolStripMenuItem // this.testToolStripMenuItem.Name = "testToolStripMenuItem"; - this.testToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.testToolStripMenuItem.Size = new System.Drawing.Size(141, 22); this.testToolStripMenuItem.Text = "Save Defaults"; this.testToolStripMenuItem.Click += new System.EventHandler(this.ButtonSaveDefaults_Click); // // loadDefaultsToolStripMenuItem // this.loadDefaultsToolStripMenuItem.Name = "loadDefaultsToolStripMenuItem"; - this.loadDefaultsToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.loadDefaultsToolStripMenuItem.Size = new System.Drawing.Size(141, 22); this.loadDefaultsToolStripMenuItem.Text = "Load Defaults"; this.loadDefaultsToolStripMenuItem.Click += new System.EventHandler(this.ButtonLoadDefaults_Click); // // clearToolStripMenuItem // this.clearToolStripMenuItem.Name = "clearToolStripMenuItem"; - this.clearToolStripMenuItem.Size = new System.Drawing.Size(146, 22); + this.clearToolStripMenuItem.Size = new System.Drawing.Size(141, 22); this.clearToolStripMenuItem.Text = "Clear"; this.clearToolStripMenuItem.Click += new System.EventHandler(this.ClearBtn_Click); // @@ -214,7 +206,7 @@ // this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(206, 550); + this.label2.Location = new System.Drawing.Point(197, 550); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(168, 13); this.label2.TabIndex = 111; @@ -224,12 +216,54 @@ // this.label38.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.label38.AutoSize = true; - this.label38.Location = new System.Drawing.Point(47, 550); + this.label38.Location = new System.Drawing.Point(41, 550); this.label38.Name = "label38"; this.label38.Size = new System.Drawing.Size(153, 13); this.label38.TabIndex = 110; this.label38.Text = "* Escape clears a key mapping"; // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(442, 550); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(57, 13); + this.label1.TabIndex = 113; + this.label1.Text = "U+D/L+R:"; + // + // rbUDLRForbid + // + this.rbUDLRForbid.AutoSize = true; + this.rbUDLRForbid.Location = new System.Drawing.Point(559, 548); + this.rbUDLRForbid.Name = "rbUDLRForbid"; + this.rbUDLRForbid.Size = new System.Drawing.Size(54, 17); + this.rbUDLRForbid.TabIndex = 114; + this.rbUDLRForbid.TabStop = true; + this.rbUDLRForbid.Text = "Forbid"; + this.rbUDLRForbid.UseVisualStyleBackColor = true; + // + // rbUDLRPriority + // + this.rbUDLRPriority.AutoSize = true; + this.rbUDLRPriority.Location = new System.Drawing.Point(622, 548); + this.rbUDLRPriority.Name = "rbUDLRPriority"; + this.rbUDLRPriority.Size = new System.Drawing.Size(56, 17); + this.rbUDLRPriority.TabIndex = 115; + this.rbUDLRPriority.TabStop = true; + this.rbUDLRPriority.Text = "Priority"; + this.rbUDLRPriority.UseVisualStyleBackColor = true; + // + // rbUDLRAllow + // + this.rbUDLRAllow.AutoSize = true; + this.rbUDLRAllow.Location = new System.Drawing.Point(503, 548); + this.rbUDLRAllow.Name = "rbUDLRAllow"; + this.rbUDLRAllow.Size = new System.Drawing.Size(50, 17); + this.rbUDLRAllow.TabIndex = 116; + this.rbUDLRAllow.TabStop = true; + this.rbUDLRAllow.Text = "Allow"; + this.rbUDLRAllow.UseVisualStyleBackColor = true; + // // btnMisc // this.btnMisc.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); @@ -248,11 +282,14 @@ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.buttonCancel; this.ClientSize = new System.Drawing.Size(932, 572); + this.Controls.Add(this.rbUDLRAllow); + this.Controls.Add(this.rbUDLRPriority); + this.Controls.Add(this.rbUDLRForbid); + this.Controls.Add(this.label1); this.Controls.Add(this.label3); this.Controls.Add(this.label2); this.Controls.Add(this.label38); this.Controls.Add(this.btnMisc); - this.Controls.Add(this.checkBoxUDLR); this.Controls.Add(this.tableLayoutPanel1); this.Controls.Add(this.buttonCancel); this.Controls.Add(this.buttonOK); @@ -277,7 +314,6 @@ private System.Windows.Forms.TabPage NormalControlsTab; private System.Windows.Forms.TabPage AutofireControlsTab; private System.Windows.Forms.CheckBox checkBoxAutoTab; - private System.Windows.Forms.CheckBox checkBoxUDLR; private System.Windows.Forms.Button buttonOK; private System.Windows.Forms.Button buttonCancel; private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; @@ -292,5 +328,9 @@ private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label38; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.RadioButton rbUDLRForbid; + private System.Windows.Forms.RadioButton rbUDLRPriority; + private System.Windows.Forms.RadioButton rbUDLRAllow; } } \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs index c444df9759..cc5193c866 100644 --- a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs +++ b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs @@ -5,6 +5,7 @@ using System.Drawing; using System.Linq; using System.Windows.Forms; +using BizHawk.Common; using BizHawk.Client.Common; using BizHawk.Emulation.Common; using BizHawk.Client.EmuHawk.WinFormExtensions; @@ -84,7 +85,7 @@ namespace BizHawk.Client.EmuHawk return new AnalogBindPanel(settings, buttons) { Dock = DockStyle.Fill, AutoScroll = true }; } - private static void LoadToPanel(Control dest, string controllerName, IList controllerButtons, IDictionary> settingsblock, T defaultvalue, PanelCreator createpanel) + private static void LoadToPanel(Control dest, string controllerName, IList controllerButtons, Dictionary categoryLabels, IDictionary> settingsblock, T defaultvalue, PanelCreator createpanel) { Dictionary settings; if (!settingsblock.TryGetValue(controllerName, out settings)) @@ -109,6 +110,7 @@ namespace BizHawk.Client.EmuHawk // split the list of all settings into buckets by player number var buckets = new List[MAXPLAYERS + 1]; + var categoryBuckets = new WorkingDictionary>(); for (var i = 0; i < buckets.Length; i++) { buckets[i] = new List(); @@ -133,7 +135,14 @@ namespace BizHawk.Client.EmuHawk i = 0; } - buckets[i].Add(button); + if (button == "Pointer Pressed") + { + int zzz = 9; + } + + if (categoryLabels.ContainsKey(button)) + categoryBuckets[categoryLabels[button]].Add(button); + else buckets[i].Add(button); } if (buckets[0].Count == controllerButtons.Count) @@ -158,6 +167,13 @@ namespace BizHawk.Client.EmuHawk } } + foreach (var cat in categoryBuckets) + { + string tabname = cat.Key; + tt.TabPages.Add(tabname); + tt.TabPages[pageidx].Controls.Add(createpanel(settings, cat.Value, tt.Size)); + } + if (buckets[0].Count > 0) { string tabname = Global.Emulator.SystemId == "C64" ? "Keyboard" : "Console"; // hack @@ -174,7 +190,9 @@ namespace BizHawk.Client.EmuHawk SuspendLayout(); LoadPanels(Global.Config); - checkBoxUDLR.Checked = Global.Config.AllowUD_LR; + rbUDLRAllow.Checked = Global.Config.AllowUD_LR; + rbUDLRForbid.Checked = Global.Config.ForbidUD_LR; + rbUDLRPriority.Checked = !Global.Config.AllowUD_LR && !Global.Config.ForbidUD_LR; checkBoxAutoTab.Checked = Global.Config.InputConfigAutoTab; SetControllerPicture(def.Name); @@ -189,9 +207,9 @@ namespace BizHawk.Client.EmuHawk IDictionary> autofire, IDictionary> analog) { - LoadToPanel(NormalControlsTab, _theDefinition.Name, _theDefinition.BoolButtons, normal, string.Empty, CreateNormalPanel); - LoadToPanel(AutofireControlsTab, _theDefinition.Name, _theDefinition.BoolButtons, autofire, string.Empty, CreateNormalPanel); - LoadToPanel(AnalogControlsTab, _theDefinition.Name, _theDefinition.FloatControls, analog, new Config.AnalogBind(string.Empty, 1.0f, 0.1f), CreateAnalogPanel); + LoadToPanel(NormalControlsTab, _theDefinition.Name, _theDefinition.BoolButtons, _theDefinition.CategoryLabels, normal, string.Empty, CreateNormalPanel); + LoadToPanel(AutofireControlsTab, _theDefinition.Name, _theDefinition.BoolButtons, _theDefinition.CategoryLabels, autofire, string.Empty, CreateNormalPanel); + LoadToPanel(AnalogControlsTab, _theDefinition.Name, _theDefinition.FloatControls, _theDefinition.CategoryLabels, analog, new Config.AnalogBind(string.Empty, 1.0f, 0.1f), CreateAnalogPanel); if (AnalogControlsTab.Controls.Count == 0) { @@ -298,7 +316,8 @@ namespace BizHawk.Client.EmuHawk private void ButtonOk_Click(object sender, EventArgs e) { - Global.Config.AllowUD_LR = checkBoxUDLR.Checked; + Global.Config.AllowUD_LR = rbUDLRAllow.Checked; + Global.Config.ForbidUD_LR = rbUDLRForbid.Checked; Global.Config.InputConfigAutoTab = checkBoxAutoTab.Checked; Save(); diff --git a/BizHawk.Client.EmuHawk/images/Shark.ico b/BizHawk.Client.EmuHawk/images/Shark.ico new file mode 100644 index 0000000000..9f35543e2e Binary files /dev/null and b/BizHawk.Client.EmuHawk/images/Shark.ico differ diff --git a/BizHawk.Client.EmuHawk/images/Shark.png b/BizHawk.Client.EmuHawk/images/Shark.png new file mode 100644 index 0000000000..c3bdaf2e43 Binary files /dev/null and b/BizHawk.Client.EmuHawk/images/Shark.png differ diff --git a/BizHawk.Client.EmuHawk/tools/AutoHawk.cs b/BizHawk.Client.EmuHawk/tools/AutoHawk.cs index 632a2f1198..fa56b6a20e 100644 --- a/BizHawk.Client.EmuHawk/tools/AutoHawk.cs +++ b/BizHawk.Client.EmuHawk/tools/AutoHawk.cs @@ -11,7 +11,7 @@ using BizHawk.Emulation.Common; namespace BizHawk.Client.EmuHawk { - [ToolAttributes(released: false)] + [ToolAttributes(released: false, supportedSystems: null)] public partial class AutoHawk : Form, IToolFormAutoConfig { public AutoHawk() diff --git a/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.cs b/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.cs index 6f933ccd00..97eec314be 100644 --- a/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.cs +++ b/BizHawk.Client.EmuHawk/tools/Debugger/GenericDebugger.cs @@ -13,7 +13,6 @@ using BizHawk.Client.Common; namespace BizHawk.Client.EmuHawk { - [ToolAttributes(released: true)] public partial class GenericDebugger : Form, IToolFormAutoConfig, IControlMainform { public GenericDebugger() diff --git a/BizHawk.Client.EmuHawk/tools/GameShark.Designer.cs b/BizHawk.Client.EmuHawk/tools/GameShark.Designer.cs new file mode 100644 index 0000000000..843dbb5bba --- /dev/null +++ b/BizHawk.Client.EmuHawk/tools/GameShark.Designer.cs @@ -0,0 +1,133 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class GameShark + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GameShark)); + this.mnuGameShark = new System.Windows.Forms.MenuStrip(); + this.btnClear = new System.Windows.Forms.Button(); + this.lblCheat = new System.Windows.Forms.Label(); + this.txtCheat = new System.Windows.Forms.TextBox(); + this.btnGo = new System.Windows.Forms.Button(); + this.lblDescription = new System.Windows.Forms.Label(); + this.txtDescription = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // mnuGameShark + // + this.mnuGameShark.Location = new System.Drawing.Point(0, 0); + this.mnuGameShark.Name = "mnuGameShark"; + this.mnuGameShark.Size = new System.Drawing.Size(284, 24); + this.mnuGameShark.TabIndex = 0; + // + // btnClear + // + this.btnClear.Location = new System.Drawing.Point(141, 132); + this.btnClear.Name = "btnClear"; + this.btnClear.Size = new System.Drawing.Size(75, 23); + this.btnClear.TabIndex = 4; + this.btnClear.Text = "Clear"; + this.btnClear.UseVisualStyleBackColor = true; + this.btnClear.Click += new System.EventHandler(this.btnClear_Click); + // + // lblCheat + // + this.lblCheat.AutoSize = true; + this.lblCheat.Location = new System.Drawing.Point(147, 91); + this.lblCheat.Name = "lblCheat"; + this.lblCheat.Size = new System.Drawing.Size(63, 13); + this.lblCheat.TabIndex = 11; + this.lblCheat.Text = "Cheat Code"; + // + // txtCheat + // + this.txtCheat.Location = new System.Drawing.Point(128, 106); + this.txtCheat.Name = "txtCheat"; + this.txtCheat.Size = new System.Drawing.Size(100, 20); + this.txtCheat.TabIndex = 2; + // + // btnGo + // + this.btnGo.Location = new System.Drawing.Point(35, 131); + this.btnGo.Name = "btnGo"; + this.btnGo.Size = new System.Drawing.Size(75, 23); + this.btnGo.TabIndex = 3; + this.btnGo.Text = "Convert"; + this.btnGo.UseVisualStyleBackColor = true; + this.btnGo.Click += new System.EventHandler(this.btnGo_Click); + // + // lblDescription + // + this.lblDescription.AutoSize = true; + this.lblDescription.Location = new System.Drawing.Point(42, 90); + this.lblDescription.Name = "lblDescription"; + this.lblDescription.Size = new System.Drawing.Size(60, 13); + this.lblDescription.TabIndex = 17; + this.lblDescription.Text = "Description"; + // + // txtDescription + // + this.txtDescription.Location = new System.Drawing.Point(22, 106); + this.txtDescription.Name = "txtDescription"; + this.txtDescription.Size = new System.Drawing.Size(100, 20); + this.txtDescription.TabIndex = 1; + // + // GameShark + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(284, 261); + this.Controls.Add(this.txtDescription); + this.Controls.Add(this.lblDescription); + this.Controls.Add(this.btnClear); + this.Controls.Add(this.lblCheat); + this.Controls.Add(this.txtCheat); + this.Controls.Add(this.btnGo); + this.Controls.Add(this.mnuGameShark); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MainMenuStrip = this.mnuGameShark; + this.MaximizeBox = false; + this.Name = "GameShark"; + this.Text = "GameShark Converter"; + this.Load += new System.EventHandler(this.GameShark_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip mnuGameShark; + internal System.Windows.Forms.Button btnClear; + internal System.Windows.Forms.Label lblCheat; + internal System.Windows.Forms.TextBox txtCheat; + internal System.Windows.Forms.Button btnGo; + private System.Windows.Forms.Label lblDescription; + private System.Windows.Forms.TextBox txtDescription; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/GameShark.cs b/BizHawk.Client.EmuHawk/tools/GameShark.cs new file mode 100644 index 0000000000..3ac98d6e56 --- /dev/null +++ b/BizHawk.Client.EmuHawk/tools/GameShark.cs @@ -0,0 +1,103 @@ +using System; +using System.Windows.Forms; +using BizHawk.Emulation.Common; +using BizHawk.Client.Common; +using System.Globalization; + +namespace BizHawk.Client.EmuHawk +{ + [ToolAttributes(released: true, supportedSystems: new[] { "GB" })] + public partial class GameShark : Form, IToolForm, IToolFormAutoConfig + { + //We are using Memory Domains, so we NEED this. + [RequiredService] + private IMemoryDomains MemoryDomains { get; set; } + public GameShark() + { + InitializeComponent(); + } + + public bool UpdateBefore + { + get + { + return true; + } + } + + public bool AskSaveChanges() + { + return true; + } + + public void FastUpdate() + { + + } + + public void Restart() + { + + } + + public void UpdateValues() + { + + } + + private void btnGo_Click(object sender, EventArgs e) + { + //This line ONLY applies to GB/GBC codes. + if (txtCheat.Text.Length != 8) + { + MessageBox.Show("All GameShark and CodeBreaker cheats need to be Eight characters in Length", "Input Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + //Sample Input for GB/GBC: + //010FF6C1 + //Becomes: + //Address C1F6 + //Value 0F + string parseString = null; + string RAMAddress = null; + string RAMValue = null; + parseString = txtCheat.Text.Remove(0, 2); + //Now we need to break it down a little more. + RAMValue = parseString.Remove(2, 4); + parseString = parseString.Remove(0, 2); + //The issue is Endian... Time to get ultra clever. And Regret it. + //First Half + RAMAddress = parseString.Remove(0, 2); + RAMAddress = RAMAddress + parseString.Remove(2, 2); + //We now have our values. + //This part, is annoying... + try + { + //A Watch needs to be generated so we can make a cheat out of that. This is due to how the Cheat engine works. + //System Bus Domain, The Address to Watch, Byte size (Byte), Hex Display, Description. Not Big Endian. + var watch = Watch.GenerateWatch(MemoryDomains["System Bus"], long.Parse(RAMAddress, NumberStyles.HexNumber), Watch.WatchSize.Byte, Watch.DisplayType.Hex, txtDescription.Text, false); + //Take Watch, Add our Value we want, and it should be active when addded? + Global.CheatList.Add(new Cheat(watch, int.Parse(RAMValue, NumberStyles.HexNumber))); + //Clear old Inputs + txtCheat.Clear(); + txtDescription.Clear(); + } + catch (Exception ex) + { + MessageBox.Show("An Error occured:" + ex.GetType().ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void btnClear_Click(object sender, EventArgs e) + { + //Clear old Inputs + txtCheat.Clear(); + txtDescription.Clear(); + } + + private void GameShark_Load(object sender, EventArgs e) + { + + } + } +} diff --git a/BizHawk.Client.EmuHawk/tools/GameShark.resx b/BizHawk.Client.EmuHawk/tools/GameShark.resx new file mode 100644 index 0000000000..45b6d9a8ef --- /dev/null +++ b/BizHawk.Client.EmuHawk/tools/GameShark.resx @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + + AAABAAEAICAAAAEAGACoDAAAFgAAACgAAAAgAAAAQAAAAAEAGAAAAAAAAAAAAGAAAABgAAAAAAAAAAAA + AAD////+///+///+/////////////////////////////////////////////v/+///+//////////// + //////////////////////////////////////////////////////////////////////////////// + ///+/v77+/vq6urk5OTz8/P+/v7+/v7///////////////////////////////////////////////// + ///////////////////////////////////////+/v7k5OSJiYleXl5ERERGRkZFRUU5OTk4ODhNTU2R + kZHe3t7+/v7///////////////////////////////////////////////////////////////////// + ///6+vqSkpJra2uhoaG6urq9vb27u7u9vb3Dw8PFxcWwsLB9fX04ODhLS0u7u7v9/f3///////////// + ///////////////////////////////////////////////4+PiJiYmgoKCrq6tycnI7OzsZGRkHBwcG + BgYVFRUzMzNeXl6fn5/Ly8urq6tOTk5ERETMzMz///////////////////////////////////////// + //////////////+wsLCkpKRubm4QEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhISF3d3fKysqu + rq45OTl3d3f4+Pj////////////////////////////////////////////u7u6fn59SUlIAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXFxeGhobV1dV5eXk+Pj7m5ub///////////// + //////////////////////////+9vb1ubm4BAQEAAAAAAAAAAAAVFRVBQUFFRUUlJSUDAwMAAAAAAAAA + AAAAAAAAAAAAAAABAQE5OTnCwsKoqKgzMzORkZHq6ur////////////////////////////5+fmenp4a + GhoAAAAAAAAaGhqcnJzf39/f39/c3Nza2trIyMiJiYlKSkoHBwcAAAAAAAAAAAAAAAAAAAAUFBSioqJx + cXGBgYHh4eH////////////////////////////i4uJ9fX0CAgIBAQERERHJycnj4+Ph4eHe3t7c3Nza + 2trY2NjW1tbT09PExMRzc3MZGRkAAAACAgICAgIBAQFNTU26urqfn589PT3z8/P///////////////// + ///////S0tJdXV0SERESERFvb2/l5eXj4+Ph4eHe3t7c3Nza2trY2NjW1tbT09PQ0NDOzs7GxsZ2dnYT + ExMLCwtLS0u9vb28u7urqqqgoKBiYmL+/v7////////////////////Nzc1SUlEmJCQmJCSamprl5eXj + 4+Ph4eHe3t7c3Nza2trY2NitrKyRkJCPjo6WlZWwsLDIyMi5ubliYmK7u7u/v7+dnJwzMTHLyspoaGix + sbH///////////////////+4uLhMS0s4NTQ4NTOQjo3l5eXj4+Ph4eHe3t7c3Nza2trY2NiCgoIvLy8W + FRQmJCQ3NDNJR0V8enizs7LBwcG/v79tbW04NTNdW1nb29tCQkL09PT////////////////S0tI/Pz8Z + GBgcGhlpaGjl5eXj4+Ph4eHe3t7c3Nza2trY2NjW1tbS0tK4uLhubm4gHx85NjNHQz9LR0Srqqm/v7+E + hIQxLitHQz+dnJmhoaGNjY3////////////////t7e3Z2dnk5OTh4eHn5+fl5eXj4+Ph4eHe3t7c3Nza + 2trY2NjW1tbT09PQ0NDOzs69vb1ISEg0MS1WUUl+e3W/v7+8vLw8OztPSkNdWFHZ2dhNTU34+Pj///// + ///////7+/uenp51c3HMy8nk5OTl5eXj4+Ph4eHe3t7c3Nza2trY2NjW1tbT09PQ0NDOzs7MzMzIyMhj + Y2NAOzVlXlSrqqe9vb2kpKQqKCZgWk+hnZecnJyrq6v////////////////Hx8c2NTRsZVd0bF+FfnLa + 2dnh4eHe3t7c3Nza2trY2NjW1tbT09PQ0NDOzs7MzMzIyMjGxcV3cmdvZ1p6c2a0s7K5ubmPj48qKCV0 + bWHe3d1paWn////////////////o6Oh1dXVLRTt6cWB6cWCinZHh4eHe3t7c3Nza2trY2NjW1tbT09PQ + 0NDOzs7MzMzAv72Ri354cF96cWB4cF+CemurqaS3traIiYgpJyS6uLVra2v9/f3////////////+/v7A + wMArKih9cl+GemSGemTEwbne3t7c3Nza2trY2NjW1tbNzc13d3eSjoWclISFemVuZVNiW06JfmlbVUeJ + fmpVTkKYkIGlop2Pj45nZmR7e3vh4eH////////////////p6emGhoY4MyqPgmePgmeUiG7Rz8rc3Nza + 2trY2NjW1tbT09O9vb1FRURHQTSVjHk9OzdzcW1sZFRbWlmKgWxWVVR+c1uPgmeQhGqkno61tbXFxcX/ + ///////////////////Pz89KSkpjWUWZimmZimiUinXc3Nza2trY2NjW1tbT09PQ0NDOzs6Tk5NPTk6o + qKiPj49KSUatra1RTD+tra5EPjSZimiZimirn4XFxcW9vb3////////////////////39/e6urooJyZ/ + clOMfl+6ubfc3Nza2trY2NjW1tbT09PQ0NDOzs7MzMzJycnGxsbCwsKdnZ2+vr5eXVy2trZvb29+cFKk + kmi2qIvJycnHx8f////////////////////////r6+upqakqKCa0s7He3t7c3Nza2trY2NjW1tbT09PQ + 0NDOzs7MzMzIyMjGxsbExMTBwcG/v7+srKyxsbG0tLRHQjitmWjEuJu9vr3m5ub///////////////// + ///////7+/uTk5O7u7vh4eHe3t7c3Nza2trY2NjV1dXS0tLQ0NDOzs7MzMzIyMjGxsbExMTBwcG/v7+8 + vLy5ubm2trZ6enqAcUzUzLm6urr+/v7////////////////////p6emHh4fU1NTj4+Ph4eHe3t7c3NzZ + 2trTz8LFsH7GtInPzcjOzs7MzMzIyMjGxsbExMTBwcG/v7+9vb25ubm2tratra1NST7k49/S0tL///// + ///////////////////5+fnr6+vl5eXj4+Ph4eHd3d3JyclVU0+Tf0vFqWDHqV/HrWvLwajLy8vIyMnG + xsbExMSIiIiPj4+9vb25ubm2traurq50dHSqqqry8vL////////////////////////////////9/f37 + +/v8/Pzu7u7Q0NCbm5ssLCw4MByTfkXLrFvOrlzNtXXKwajGxsXEw8Szs7O9vb29vb25ubm2traurq6j + o6NfX1/////////////////////////////////////////////////////6+vre3t7Pz8+RkZE3Njcg + GxBfUSqagkC9oE3RtWXNvY7BvK6/v7+8vLy5ubm2traurq6kpKRubm7k5OT///////////////////// + ///////////////////////////////////z8/Pe3t7W1ta4uLh7e3tEQ0MiIiIaGRcfHRokJCNaWlq9 + vb3U1NTOzs7MzMzNzc3U1NT+/v7///////////////////////////////////////////////////// + ///////////29vbn5+fc3Nzc3Nze3t7g4ODh4eHk5OTm5ubn5+fr6+v29vb///////////////////// + ///////////////////////////////////////////////////////////////////+/v739/fx8fHw + 8PDx8fHy8vL19fX7+/v///////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAH/8AABwfgAAwA+AAAACAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs index fb54eebcbd..e4dd65f42b 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Client.cs @@ -240,7 +240,7 @@ namespace BizHawk.Client.EmuHawk )] public static void OpenRom(string path) { - GlobalWin.MainForm.LoadRom(path); + GlobalWin.MainForm.LoadRom(path, new MainForm.LoadRomArgs() { OpenAdvanced = new OpenAdvanced_OpenRom() }); } [LuaMethodAttributes( diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs index 1be505e439..6b88687cc0 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.Forms.cs @@ -190,10 +190,9 @@ namespace BizHawk.Client.EmuHawk )] public void DestroyAll() { - foreach (var form in _luaForms) + for (var i = _luaForms.Count - 1; i >= 0; i--) { - form.Close(); - _luaForms.Remove(form); + _luaForms.ElementAt(i).Close(); } } diff --git a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.cs b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.cs index d282147435..ab48b62381 100644 --- a/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.cs +++ b/BizHawk.Client.EmuHawk/tools/Lua/Libraries/EmuLuaLibrary.cs @@ -136,6 +136,7 @@ namespace BizHawk.Client.EmuHawk public void Close() { + FormsLibrary.DestroyAll(); _lua = new Lua(); GuiLibrary.Dispose(); } diff --git a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs index ee01768f49..f9dc7a0f81 100644 --- a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs +++ b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs @@ -109,7 +109,7 @@ namespace BizHawk.Client.EmuHawk DialogResult = DialogResult.OK; Close(); - GlobalWin.MainForm.LoadRom(fileInfo.FullName); + GlobalWin.MainForm.LoadRom(fileInfo.FullName, new MainForm.LoadRomArgs() { OpenAdvanced = new OpenAdvanced_OpenRom() }); } } diff --git a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs index 9379ade84a..d0ee563907 100644 --- a/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs +++ b/BizHawk.Client.EmuHawk/tools/TAStudio/BookmarksBranchesBox.cs @@ -230,7 +230,7 @@ namespace BizHawk.Client.EmuHawk { Frame = Global.Emulator.Frame, CoreData = (byte[])((Global.Emulator as IStatable).SaveStateBinary().Clone()), - InputLog = Movie.InputLog.ToList(), + InputLog = Movie.InputLog.Clone(), OSDFrameBuffer = GlobalWin.MainForm.CaptureOSD(), LagLog = Movie.TasLagLog.Clone(), ChangeLog = new TasMovieChangeLog(Movie), diff --git a/BizHawk.Client.EmuHawk/tools/ToolManager.cs b/BizHawk.Client.EmuHawk/tools/ToolManager.cs index 1e58da5eda..083a42ea32 100644 --- a/BizHawk.Client.EmuHawk/tools/ToolManager.cs +++ b/BizHawk.Client.EmuHawk/tools/ToolManager.cs @@ -43,10 +43,14 @@ namespace BizHawk.Client.EmuHawk public IToolForm Load(Type toolType, bool focus = true) { if (!typeof(IToolForm).IsAssignableFrom(toolType)) - throw new ArgumentException(String.Format("Type {0} does not implement IToolForm.", toolType.Name)); + { + throw new ArgumentException(string.Format("Type {0} does not implement IToolForm.", toolType.Name)); + } - if (!ServiceInjector.IsAvailable(Global.Emulator.ServiceProvider, toolType)) + if (!IsAvailable(toolType)) + { return null; + } var existingTool = _tools.FirstOrDefault(x => toolType.IsAssignableFrom(x.GetType())); @@ -63,6 +67,7 @@ namespace BizHawk.Client.EmuHawk existingTool.Show(); existingTool.Focus(); } + return existingTool; } } @@ -624,7 +629,37 @@ namespace BizHawk.Client.EmuHawk public bool IsAvailable(Type t) { - return ServiceInjector.IsAvailable(Global.Emulator.ServiceProvider, t); + if (!ServiceInjector.IsAvailable(Global.Emulator.ServiceProvider, t)) + { + return false; + } + + var tool = Assembly + .GetAssembly(typeof(IToolForm)) + .GetTypes() + .FirstOrDefault(type => type == t); + + if (tool == null) // This isn't a tool, must not be available + { + return false; + } + + var attr = tool.GetCustomAttributes(false) + .OfType() + .FirstOrDefault(); + + if (attr == null) // If no attributes there is no supported systems documented so assume all + { + return true; + } + + // If no supported systems mentioned assume all + if (attr.SupportedSystems != null && attr.SupportedSystems.Any()) + { + return attr.SupportedSystems.Contains(Global.Emulator.SystemId); + } + + return true; } // Eventually we want a single game genie tool, then this mess goes away diff --git a/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs b/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs index e61f3e09d2..d0137d2490 100644 --- a/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs +++ b/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs @@ -756,7 +756,7 @@ namespace BizHawk.Client.EmuHawk MemoryDomains = _memoryDomains }; we.SetWatch(_watches.Domain); - we.ShowHawkDialog(); + we.ShowHawkDialog(this); if (we.DialogResult == DialogResult.OK) { _watches.Add(we.Watches[0]); diff --git a/BizHawk.Common/BizHawk.Common.csproj b/BizHawk.Common/BizHawk.Common.csproj index 825675481d..eaeabf9830 100644 --- a/BizHawk.Common/BizHawk.Common.csproj +++ b/BizHawk.Common/BizHawk.Common.csproj @@ -76,6 +76,7 @@ + diff --git a/BizHawk.Common/InstanceDll.cs b/BizHawk.Common/InstanceDll.cs index 9fb3a393f6..e8d24f2cbc 100644 --- a/BizHawk.Common/InstanceDll.cs +++ b/BizHawk.Common/InstanceDll.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Runtime.InteropServices; @@ -9,7 +9,7 @@ namespace BizHawk.Common public InstanceDll(string dllPath) { //copy the dll to a temp directory - var path = Path.Combine(Path.GetTempPath(), "instancedll-pid" + System.Diagnostics.Process.GetCurrentProcess().Id + "-" + Guid.NewGuid()) + "-" + Path.GetFileName(dllPath); + var path = TempFileCleaner.GetTempFilename(string.Format("{0}", Path.GetFileNameWithoutExtension(dllPath)),".dll",false); using (var stream = new FileStream(path, FileMode.Create, System.Security.AccessControl.FileSystemRights.FullControl, FileShare.ReadWrite | FileShare.Delete, 4 * 1024, FileOptions.None)) using (var sdll = File.OpenRead(dllPath)) sdll.CopyTo(stream); @@ -24,10 +24,8 @@ namespace BizHawk.Common string envpath_new = Path.GetDirectoryName(path) + ";" + envpath; Environment.SetEnvironmentVariable("PATH", envpath_new, EnvironmentVariableTarget.Process); _hModule = LoadLibrary(path); //consider using LoadLibraryEx instead of shenanigans? - var newfname = Path.GetFileName(path); - newfname = "bizhawk.bizdelete-" + newfname; - var newpath = Path.Combine(Path.GetDirectoryName(path), newfname); - File.Move(path, newpath); + var newfname = TempFileCleaner.RenameTempFilenameForDelete(path); + File.Move(path, newfname); } finally { @@ -71,4 +69,4 @@ namespace BizHawk.Common IntPtr _hModule; } -} +} \ No newline at end of file diff --git a/BizHawk.Common/Sprintf.cs b/BizHawk.Common/Sprintf.cs index faa3ef4699..010e3635fe 100644 --- a/BizHawk.Common/Sprintf.cs +++ b/BizHawk.Common/Sprintf.cs @@ -442,7 +442,9 @@ namespace BizHawk.Common #region s - string case 's': // string string t = "{0" + ( fieldLength != int.MinValue ? "," + ( flagLeft2Right ? "-" : String.Empty ) + fieldLength.ToString() : String.Empty ) + ":s}"; - w = Marshal.PtrToStringAnsi(n); + if (n == IntPtr.Zero) + w = "(null)"; + else w = Marshal.PtrToStringAnsi(n); if ( fieldPrecision >= 0 ) w = w.Substring( 0, fieldPrecision ); diff --git a/BizHawk.Client.Common/TempFileCleaner.cs b/BizHawk.Common/TempFileManager.cs similarity index 61% rename from BizHawk.Client.Common/TempFileCleaner.cs rename to BizHawk.Common/TempFileManager.cs index 2a9ed19c11..04d84727c4 100644 --- a/BizHawk.Client.Common/TempFileCleaner.cs +++ b/BizHawk.Common/TempFileManager.cs @@ -1,7 +1,7 @@ -using System; +using System; using System.IO; -namespace BizHawk.Client.Common +namespace BizHawk.Common { /// /// Starts a thread which cleans any filenames in %temp% beginning with bizhawk.bizdelete. @@ -12,6 +12,23 @@ namespace BizHawk.Client.Common { //todo - manage paths other than %temp%, make not static, or allow adding multiple paths to static instance + public static string GetTempFilename(string friendlyname, string extension = null, bool delete = true) + { + string guidPart = Guid.NewGuid().ToString(); + var fname = string.Format("biz-{0}-{1}-{2}{3}", System.Diagnostics.Process.GetCurrentProcess().Id, friendlyname, guidPart, extension ?? ""); + if (delete) fname = RenameTempFilenameForDelete(fname); + return Path.Combine(Path.GetTempPath(), fname); + } + + public static string RenameTempFilenameForDelete(string path) + { + string filename = Path.GetFileName(path); + string dir = Path.GetDirectoryName(path); + if (!filename.StartsWith("biz-")) throw new InvalidOperationException(); + filename = "bizdelete-" + filename.Remove(0, 4); + return Path.Combine(dir, filename); + } + public static void Start() { lock (typeof(TempFileCleaner)) @@ -31,7 +48,7 @@ namespace BizHawk.Client.Common var di = new DirectoryInfo(Path.GetTempPath()); for (; ; ) { - var fis = di.GetFiles("bizhawk.bizdelete*"); + var fis = di.GetFiles("bizdelete-*"); foreach (var fi in fis) { try diff --git a/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs b/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs index f20ed1d22f..f4d81bf2bb 100644 --- a/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs +++ b/BizHawk.Emulation.Common/Base Implementations/ControllerDefinition.cs @@ -81,6 +81,7 @@ namespace BizHawk.Emulation.Common public string Name { get; set; } + public Dictionary CategoryLabels = new Dictionary(); public List BoolButtons { get; set; } public List FloatControls { get; private set; } public List FloatRanges { get; private set; } diff --git a/BizHawk.Emulation.Common/Interfaces/ICoreFileProvider.cs b/BizHawk.Emulation.Common/Interfaces/ICoreFileProvider.cs index 75f2c4074c..89f2bce021 100644 --- a/BizHawk.Emulation.Common/Interfaces/ICoreFileProvider.cs +++ b/BizHawk.Emulation.Common/Interfaces/ICoreFileProvider.cs @@ -18,9 +18,15 @@ namespace BizHawk.Emulation.Common string DllPath(); /// - /// produces a path that contains saveram... because libretro cores need it? not sure yet + /// produces a path that contains saveram... because libretro cores need it /// - string GetSaveRAMPath(); + /// + string GetRetroSaveRAMDirectory(); + + /// + /// produces a path for use as a libretro system path (different for each core) + /// + string GetRetroSystemPath(); string GetGameBasePath(); diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 821d875f10..74216b0188 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -867,8 +867,6 @@ - - @@ -877,6 +875,9 @@ + + + diff --git a/BizHawk.Emulation.Cores/LibRetro.cs b/BizHawk.Emulation.Cores/Libretro/LibRetro.cs similarity index 88% rename from BizHawk.Emulation.Cores/LibRetro.cs rename to BizHawk.Emulation.Cores/Libretro/LibRetro.cs index 5bdcfca37a..a6d285cc52 100644 --- a/BizHawk.Emulation.Cores/LibRetro.cs +++ b/BizHawk.Emulation.Cores/Libretro/LibRetro.cs @@ -1,603 +1,664 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Runtime.InteropServices; -using System.Reflection; - -using BizHawk.Common; - -namespace BizHawk.Emulation.Cores -{ - /// - /// libretro related shims - /// - public class LibRetro : IDisposable - { - public const int RETRO_API_VERSION = 1; - - public enum RETRO_DEVICE - { - NONE = 0, - JOYPAD = 1, - MOUSE = 2, - KEYBOARD = 3, - LIGHTGUN = 4, - ANALOG = 5, - POINTER = 6, - SENSOR_ACCELEROMETER = 7 - }; - - public enum RETRO_DEVICE_ID_JOYPAD - { - B = 0, - Y = 1, - SELECT = 2, - START = 3, - UP = 4, - DOWN = 5, - LEFT = 6, - RIGHT = 7, - A = 8, - X = 9, - L = 10, - R = 11, - L2 = 12, - R2 = 13, - L3 = 14, - R3 = 15 - }; - - public enum RETRO_LOG_LEVEL : int //exact type size is unclear - { - DEBUG = 0, - INFO, - WARN, - ERROR, - - DUMMY = Int32.MaxValue - }; - - public enum RETRO_DEVICE_ID_ANALOG - { - // LEFT / RIGHT? - X = 0, - Y = 1 - }; - - public enum RETRO_DEVICE_ID_MOUSE - { - X = 0, - Y = 1, - LEFT = 2, - RIGHT = 3 - }; - - public enum RETRO_DEVICE_ID_LIGHTGUN - { - X = 0, - Y = 1, - TRIGGER = 2, - CURSOR = 3, - TURBO = 4, - PAUSE = 5, - START = 6 - }; - - public enum RETRO_DEVICE_ID_POINTER - { - X = 0, - Y = 1, - PRESSED = 2 - }; - - public enum RETRO_DEVICE_ID_SENSOR_ACCELEROMETER - { - X = 0, - Y = 1, - Z = 2 - }; - - public enum RETRO_REGION - { - NTSC = 0, - PAL = 1 - }; - - public enum RETRO_MEMORY - { - SAVE_RAM = 0, - RTC = 1, - SYSTEM_RAM = 2, - VIDEO_RAM = 3, - }; - - public enum RETRO_KEY - { - UNKNOWN = 0, - FIRST = 0, - BACKSPACE = 8, - TAB = 9, - CLEAR = 12, - RETURN = 13, - PAUSE = 19, - ESCAPE = 27, - SPACE = 32, - EXCLAIM = 33, - QUOTEDBL = 34, - HASH = 35, - DOLLAR = 36, - AMPERSAND = 38, - QUOTE = 39, - LEFTPAREN = 40, - RIGHTPAREN = 41, - ASTERISK = 42, - PLUS = 43, - COMMA = 44, - MINUS = 45, - PERIOD = 46, - SLASH = 47, - _0 = 48, - _1 = 49, - _2 = 50, - _3 = 51, - _4 = 52, - _5 = 53, - _6 = 54, - _7 = 55, - _8 = 56, - _9 = 57, - COLON = 58, - SEMICOLON = 59, - LESS = 60, - EQUALS = 61, - GREATER = 62, - QUESTION = 63, - AT = 64, - LEFTBRACKET = 91, - BACKSLASH = 92, - RIGHTBRACKET = 93, - CARET = 94, - UNDERSCORE = 95, - BACKQUOTE = 96, - a = 97, - b = 98, - c = 99, - d = 100, - e = 101, - f = 102, - g = 103, - h = 104, - i = 105, - j = 106, - k = 107, - l = 108, - m = 109, - n = 110, - o = 111, - p = 112, - q = 113, - r = 114, - s = 115, - t = 116, - u = 117, - v = 118, - w = 119, - x = 120, - y = 121, - z = 122, - DELETE = 127, - - KP0 = 256, - KP1 = 257, - KP2 = 258, - KP3 = 259, - KP4 = 260, - KP5 = 261, - KP6 = 262, - KP7 = 263, - KP8 = 264, - KP9 = 265, - KP_PERIOD = 266, - KP_DIVIDE = 267, - KP_MULTIPLY = 268, - KP_MINUS = 269, - KP_PLUS = 270, - KP_ENTER = 271, - KP_EQUALS = 272, - - UP = 273, - DOWN = 274, - RIGHT = 275, - LEFT = 276, - INSERT = 277, - HOME = 278, - END = 279, - PAGEUP = 280, - PAGEDOWN = 281, - - F1 = 282, - F2 = 283, - F3 = 284, - F4 = 285, - F5 = 286, - F6 = 287, - F7 = 288, - F8 = 289, - F9 = 290, - F10 = 291, - F11 = 292, - F12 = 293, - F13 = 294, - F14 = 295, - F15 = 296, - - NUMLOCK = 300, - CAPSLOCK = 301, - SCROLLOCK = 302, - RSHIFT = 303, - LSHIFT = 304, - RCTRL = 305, - LCTRL = 306, - RALT = 307, - LALT = 308, - RMETA = 309, - LMETA = 310, - LSUPER = 311, - RSUPER = 312, - MODE = 313, - COMPOSE = 314, - - HELP = 315, - PRINT = 316, - SYSREQ = 317, - BREAK = 318, - MENU = 319, - POWER = 320, - EURO = 321, - UNDO = 322, - - LAST - }; - - [Flags] - public enum RETRO_MOD - { - NONE = 0, - SHIFT = 1, - CTRL = 2, - ALT = 4, - META = 8, - NUMLOCK = 16, - CAPSLOCK = 32, - SCROLLLOCK = 64 - }; - - [Flags] - public enum RETRO_SIMD - { - SSE = (1 << 0), - SSE2 = (1 << 1), - VMX = (1 << 2), - VMX128 = (1 << 3), - AVX = (1 << 4), - NEON = (1 << 5), - SSE3 = (1 << 6), - SSSE3 = (1 << 7), - MMX = (1 << 8), - MMXEXT = (1 << 9), - SSE4 = (1 << 10), - SSE42 = (1 << 11), - AVX2 = (1 << 12), - VFPU = (1 << 13), - PS = (1 << 14), - AES = (1 << 15), - VFPV3 = (1 << 16), - VFPV4 = (1 << 17), - } - - public enum RETRO_ENVIRONMENT - { - SET_ROTATION = 1, - GET_OVERSCAN = 2, - GET_CAN_DUPE = 3, - SET_MESSAGE = 6, - SHUTDOWN = 7, - SET_PERFORMANCE_LEVEL = 8, - GET_SYSTEM_DIRECTORY = 9, - SET_PIXEL_FORMAT = 10, - SET_INPUT_DESCRIPTORS = 11, - SET_KEYBOARD_CALLBACK = 12, - SET_DISK_CONTROL_INTERFACE = 13, - SET_HW_RENDER = 14, - GET_VARIABLE = 15, - SET_VARIABLES = 16, - GET_VARIABLE_UPDATE = 17, - SET_SUPPORT_NO_GAME = 18, - GET_LIBRETRO_PATH = 19, - SET_AUDIO_CALLBACK = 22, - SET_FRAME_TIME_CALLBACK = 21, - GET_RUMBLE_INTERFACE = 23, - GET_INPUT_DEVICE_CAPABILITIES = 24, - //25,26 are experimental - GET_LOG_INTERFACE = 27, - GET_PERF_INTERFACE = 28, - GET_LOCATION_INTERFACE = 29, - GET_CORE_ASSETS_DIRECTORY = 30, - GET_SAVE_DIRECTORY = 31, - SET_SYSTEM_AV_INFO = 32, - SET_PROC_ADDRESS_CALLBACK = 33, - SET_SUBSYSTEM_INFO = 34, - SET_CONTROLLER_INFO = 35, - SET_MEMORY_MAPS = 36 | EXPERIMENTAL, - SET_GEOMETRY = 37, - GET_USERNAME = 38, - GET_LANGUAGE = 39, - - EXPERIMENTAL = 0x10000 - }; - - public enum RETRO_PIXEL_FORMAT - { - XRGB1555 = 0, - XRGB8888 = 1, - RGB565 = 2 - }; - - public struct retro_message - { - public string msg; - public uint frames; - } - - public struct retro_input_descriptor - { - public uint port; - public uint device; - public uint index; - public uint id; - } - - public struct retro_system_info - { - public string library_name; - public string library_version; - public string valid_extensions; - [MarshalAs(UnmanagedType.U1)] - public bool need_fullpath; - [MarshalAs(UnmanagedType.U1)] - public bool block_extract; - } - - public struct retro_game_geometry - { - public uint base_width; - public uint base_height; - public uint max_width; - public uint max_height; - public float aspect_ratio; - } - - public struct retro_system_timing - { - public double fps; - public double sample_rate; - } - - public struct retro_system_av_info - { - public retro_game_geometry geometry; - public retro_system_timing timing; - } - - public struct retro_variable - { - public string key; - public string value; - } - - public struct retro_game_info - { - public string path; - public IntPtr data; - public uint size; - public string meta; - } - - //untested - public struct retro_perf_counter - { - public string ident; - public ulong start; - public ulong total; - public ulong call_cnt; - - [MarshalAs(UnmanagedType.U1)] - public bool registered; - }; - - //perf callbacks - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate long retro_perf_get_time_usec_t(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate long retro_perf_get_counter_t(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate ulong retro_get_cpu_features_t(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void retro_perf_log_t(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void retro_perf_register_t(ref retro_perf_counter counter); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void retro_perf_start_t(ref retro_perf_counter counter); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void retro_perf_stop_t(ref retro_perf_counter counter); - - //for GET_PERF_INTERFACE - public struct retro_perf_callback - { - public retro_perf_get_time_usec_t get_time_usec; - public retro_get_cpu_features_t get_cpu_features; - public retro_perf_get_counter_t get_perf_counter; - public retro_perf_register_t perf_register; - public retro_perf_start_t perf_start; - public retro_perf_stop_t perf_stop; - public retro_perf_log_t perf_log; - } - - #region callback prototypes - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public unsafe delegate void retro_log_printf_t(RETRO_LOG_LEVEL level, string fmt, IntPtr a0, IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, IntPtr a7, IntPtr a8, IntPtr a9, IntPtr a10, IntPtr a11, IntPtr a12, IntPtr a13, IntPtr a14, IntPtr a15); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - [return:MarshalAs(UnmanagedType.U1)] - public delegate bool retro_environment_t(RETRO_ENVIRONMENT cmd, IntPtr data); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void retro_video_refresh_t(IntPtr data, uint width, uint height, uint pitch); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void retro_audio_sample_t(short left, short right); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate uint retro_audio_sample_batch_t(IntPtr data, uint frames); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void retro_input_poll_t(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate short retro_input_state_t(uint port, uint device, uint index, uint id); - #endregion - - #region entry point prototypes - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_set_environment(retro_environment_t cb); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_set_video_refresh(retro_video_refresh_t cb); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_set_audio_sample(retro_audio_sample_t cb); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_set_audio_sample_batch(retro_audio_sample_batch_t cb); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_set_input_poll(retro_input_poll_t cb); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_set_input_state(retro_input_state_t cb); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_init(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_deinit(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate uint epretro_api_version(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_get_system_info(ref retro_system_info info); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_get_system_av_info(ref retro_system_av_info info); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_set_controller_port_device(uint port, uint device); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_reset(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_run(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate uint epretro_serialize_size(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public delegate bool epretro_serialize(IntPtr data, uint size); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public delegate bool epretro_unserialize(IntPtr data, uint size); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_cheat_reset(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_cheat_set(uint index, [MarshalAs(UnmanagedType.U1)]bool enabled, string code); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public delegate bool epretro_load_game(ref retro_game_info game); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - [return: MarshalAs(UnmanagedType.U1)] - public delegate bool epretro_load_game_special(uint game_type, ref retro_game_info info, uint num_info); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void epretro_unload_game(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate uint epretro_get_region(); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate IntPtr epretro_get_memory_data(RETRO_MEMORY id); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate uint epretro_get_memory_size(RETRO_MEMORY id); - #endregion - - #region entry points - // these are all hooked up by reflection on dll load - public epretro_set_environment retro_set_environment; - public epretro_set_video_refresh retro_set_video_refresh; - public epretro_set_audio_sample retro_set_audio_sample; - public epretro_set_audio_sample_batch retro_set_audio_sample_batch; - public epretro_set_input_poll retro_set_input_poll; - public epretro_set_input_state retro_set_input_state; - public epretro_init retro_init; - /// - /// Dispose() calls this, so you shouldn't - /// - public epretro_deinit retro_deinit; - public epretro_api_version retro_api_version; - public epretro_get_system_info retro_get_system_info; - public epretro_get_system_av_info retro_get_system_av_info; - public epretro_set_controller_port_device retro_set_controller_port_device; - public epretro_reset retro_reset; - public epretro_run retro_run; - public epretro_serialize_size retro_serialize_size; - public epretro_serialize retro_serialize; - public epretro_unserialize retro_unserialize; - public epretro_cheat_reset retro_cheat_reset; - public epretro_cheat_set retro_cheat_set; - public epretro_load_game retro_load_game; - public epretro_load_game_special retro_load_game_special; - public epretro_unload_game retro_unload_game; - public epretro_get_region retro_get_region; - public epretro_get_memory_data retro_get_memory_data; - public epretro_get_memory_size retro_get_memory_size; - #endregion - - public void Dispose() - { - dll.Dispose(); - } - - InstanceDll dll; - public LibRetro(string modulename) - { - dll = new InstanceDll(modulename); - if (!ConnectAllEntryPoints()) - { - dll.Dispose(); - throw new Exception("ConnectAllEntryPoints() failed. The console may contain more details."); - } - } - - private static IEnumerable GetAllEntryPoints() - { - return typeof(LibRetro).GetFields().Where((field) => field.FieldType.Name.StartsWith("epretro")); - } - - private void ClearAllEntryPoints() - { - foreach (var field in GetAllEntryPoints()) - { - field.SetValue(this, null); - } - } - - private bool ConnectAllEntryPoints() - { - bool succeed = true; - foreach (var field in GetAllEntryPoints()) - { - string fieldname = field.Name; - IntPtr entry = dll.GetProcAddress(fieldname); - if (entry != IntPtr.Zero) - { - field.SetValue(this, Marshal.GetDelegateForFunctionPointer(entry, field.FieldType)); - } - else - { - Console.WriteLine("Couldn't bind libretro entry point {0}", fieldname); - succeed = false; - } - } - return succeed; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.Reflection; + +#pragma warning disable 649, 169 + +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores +{ + /// + /// libretro related shims + /// + public class LibRetro : IDisposable + { + public const int RETRO_API_VERSION = 1; + + public enum RETRO_DEVICE + { + NONE = 0, + JOYPAD = 1, + MOUSE = 2, + KEYBOARD = 3, + LIGHTGUN = 4, + ANALOG = 5, + POINTER = 6, + SENSOR_ACCELEROMETER = 7 + }; + + public enum RETRO_DEVICE_ID_JOYPAD + { + B = 0, + Y = 1, + SELECT = 2, + START = 3, + UP = 4, + DOWN = 5, + LEFT = 6, + RIGHT = 7, + A = 8, + X = 9, + L = 10, + R = 11, + L2 = 12, + R2 = 13, + L3 = 14, + R3 = 15 + }; + + public enum RETRO_LOG_LEVEL : int //exact type size is unclear + { + DEBUG = 0, + INFO, + WARN, + ERROR, + + DUMMY = Int32.MaxValue + }; + + public enum RETRO_DEVICE_ID_ANALOG + { + // LEFT / RIGHT? + X = 0, + Y = 1 + }; + + public enum RETRO_DEVICE_ID_MOUSE + { + X = 0, + Y = 1, + LEFT = 2, + RIGHT = 3 + }; + + public enum RETRO_DEVICE_ID_LIGHTGUN + { + X = 0, + Y = 1, + TRIGGER = 2, + CURSOR = 3, + TURBO = 4, + PAUSE = 5, + START = 6 + }; + + public enum RETRO_DEVICE_ID_POINTER + { + X = 0, + Y = 1, + PRESSED = 2 + }; + + public enum RETRO_DEVICE_ID_SENSOR_ACCELEROMETER + { + X = 0, + Y = 1, + Z = 2 + }; + + public enum RETRO_REGION + { + NTSC = 0, + PAL = 1 + }; + + public enum RETRO_MEMORY + { + SAVE_RAM = 0, + RTC = 1, + SYSTEM_RAM = 2, + VIDEO_RAM = 3, + }; + + public enum RETRO_KEY + { + UNKNOWN = 0, + FIRST = 0, + BACKSPACE = 8, + TAB = 9, + CLEAR = 12, + RETURN = 13, + PAUSE = 19, + ESCAPE = 27, + SPACE = 32, + EXCLAIM = 33, + QUOTEDBL = 34, + HASH = 35, + DOLLAR = 36, + AMPERSAND = 38, + QUOTE = 39, + LEFTPAREN = 40, + RIGHTPAREN = 41, + ASTERISK = 42, + PLUS = 43, + COMMA = 44, + MINUS = 45, + PERIOD = 46, + SLASH = 47, + _0 = 48, + _1 = 49, + _2 = 50, + _3 = 51, + _4 = 52, + _5 = 53, + _6 = 54, + _7 = 55, + _8 = 56, + _9 = 57, + COLON = 58, + SEMICOLON = 59, + LESS = 60, + EQUALS = 61, + GREATER = 62, + QUESTION = 63, + AT = 64, + LEFTBRACKET = 91, + BACKSLASH = 92, + RIGHTBRACKET = 93, + CARET = 94, + UNDERSCORE = 95, + BACKQUOTE = 96, + a = 97, + b = 98, + c = 99, + d = 100, + e = 101, + f = 102, + g = 103, + h = 104, + i = 105, + j = 106, + k = 107, + l = 108, + m = 109, + n = 110, + o = 111, + p = 112, + q = 113, + r = 114, + s = 115, + t = 116, + u = 117, + v = 118, + w = 119, + x = 120, + y = 121, + z = 122, + DELETE = 127, + + KP0 = 256, + KP1 = 257, + KP2 = 258, + KP3 = 259, + KP4 = 260, + KP5 = 261, + KP6 = 262, + KP7 = 263, + KP8 = 264, + KP9 = 265, + KP_PERIOD = 266, + KP_DIVIDE = 267, + KP_MULTIPLY = 268, + KP_MINUS = 269, + KP_PLUS = 270, + KP_ENTER = 271, + KP_EQUALS = 272, + + UP = 273, + DOWN = 274, + RIGHT = 275, + LEFT = 276, + INSERT = 277, + HOME = 278, + END = 279, + PAGEUP = 280, + PAGEDOWN = 281, + + F1 = 282, + F2 = 283, + F3 = 284, + F4 = 285, + F5 = 286, + F6 = 287, + F7 = 288, + F8 = 289, + F9 = 290, + F10 = 291, + F11 = 292, + F12 = 293, + F13 = 294, + F14 = 295, + F15 = 296, + + NUMLOCK = 300, + CAPSLOCK = 301, + SCROLLOCK = 302, + RSHIFT = 303, + LSHIFT = 304, + RCTRL = 305, + LCTRL = 306, + RALT = 307, + LALT = 308, + RMETA = 309, + LMETA = 310, + LSUPER = 311, + RSUPER = 312, + MODE = 313, + COMPOSE = 314, + + HELP = 315, + PRINT = 316, + SYSREQ = 317, + BREAK = 318, + MENU = 319, + POWER = 320, + EURO = 321, + UNDO = 322, + + LAST + }; + + [Flags] + public enum RETRO_MOD + { + NONE = 0, + SHIFT = 1, + CTRL = 2, + ALT = 4, + META = 8, + NUMLOCK = 16, + CAPSLOCK = 32, + SCROLLLOCK = 64 + }; + + [Flags] + public enum RETRO_SIMD + { + SSE = (1 << 0), + SSE2 = (1 << 1), + VMX = (1 << 2), + VMX128 = (1 << 3), + AVX = (1 << 4), + NEON = (1 << 5), + SSE3 = (1 << 6), + SSSE3 = (1 << 7), + MMX = (1 << 8), + MMXEXT = (1 << 9), + SSE4 = (1 << 10), + SSE42 = (1 << 11), + AVX2 = (1 << 12), + VFPU = (1 << 13), + PS = (1 << 14), + AES = (1 << 15), + VFPV3 = (1 << 16), + VFPV4 = (1 << 17), + } + + public enum RETRO_ENVIRONMENT + { + SET_ROTATION = 1, + GET_OVERSCAN = 2, + GET_CAN_DUPE = 3, + SET_MESSAGE = 6, + SHUTDOWN = 7, + SET_PERFORMANCE_LEVEL = 8, + GET_SYSTEM_DIRECTORY = 9, + SET_PIXEL_FORMAT = 10, + SET_INPUT_DESCRIPTORS = 11, + SET_KEYBOARD_CALLBACK = 12, + SET_DISK_CONTROL_INTERFACE = 13, + SET_HW_RENDER = 14, + GET_VARIABLE = 15, + SET_VARIABLES = 16, + GET_VARIABLE_UPDATE = 17, + SET_SUPPORT_NO_GAME = 18, + GET_LIBRETRO_PATH = 19, + SET_AUDIO_CALLBACK = 22, + SET_FRAME_TIME_CALLBACK = 21, + GET_RUMBLE_INTERFACE = 23, + GET_INPUT_DEVICE_CAPABILITIES = 24, + //25,26 are experimental + GET_LOG_INTERFACE = 27, + GET_PERF_INTERFACE = 28, + GET_LOCATION_INTERFACE = 29, + GET_CORE_ASSETS_DIRECTORY = 30, + GET_SAVE_DIRECTORY = 31, + SET_SYSTEM_AV_INFO = 32, + SET_PROC_ADDRESS_CALLBACK = 33, + SET_SUBSYSTEM_INFO = 34, + SET_CONTROLLER_INFO = 35, + SET_MEMORY_MAPS = 36 | EXPERIMENTAL, + SET_GEOMETRY = 37, + GET_USERNAME = 38, + GET_LANGUAGE = 39, + + EXPERIMENTAL = 0x10000 + }; + + public enum retro_hw_context_type + { + RETRO_HW_CONTEXT_NONE = 0, + RETRO_HW_CONTEXT_OPENGL = 1, + RETRO_HW_CONTEXT_OPENGLES2 = 2, + RETRO_HW_CONTEXT_OPENGL_CORE = 3, + RETRO_HW_CONTEXT_OPENGLES3 = 4, + RETRO_HW_CONTEXT_OPENGLES_VERSION = 5, + + RETRO_HW_CONTEXT_DUMMY = Int32.MaxValue + }; + + public struct retro_hw_render_callback + { + public uint context_type; //retro_hw_context_type + public IntPtr context_reset; //retro_hw_context_reset_t + public IntPtr get_current_framebuffer; //retro_hw_get_current_framebuffer_t + public IntPtr get_proc_address; //retro_hw_get_proc_address_t + [MarshalAs(UnmanagedType.U1)] public byte depth; + [MarshalAs(UnmanagedType.U1)] public bool stencil; + [MarshalAs(UnmanagedType.U1)] public bool bottom_left_origin; + public uint version_major; + public uint version_minor; + [MarshalAs(UnmanagedType.U1)] public bool cache_context; + public IntPtr context_destroy; //retro_hw_context_reset_t + [MarshalAs(UnmanagedType.U1)] public bool debug_context; + }; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate long retro_hw_context_reset_t(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr retro_hw_get_current_framebuffer_t(); + + //not used + //[UnmanagedFunctionPointer(CallingConvention.Cdecl)] + //public delegate void retro_proc_address_t(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr retro_hw_get_proc_address_t(string sym); + + struct retro_memory_map + { + public IntPtr descriptors; //retro_memory_descriptor * + public uint num_descriptors; + }; + + struct retro_memory_descriptor + { + ulong flags; + IntPtr ptr; + IntPtr offset; //size_t + IntPtr start; //size_t + IntPtr select; //size_t + IntPtr disconnect; //size_t + IntPtr len; //size_t + string addrspace; + }; + + public enum RETRO_PIXEL_FORMAT + { + XRGB1555 = 0, + XRGB8888 = 1, + RGB565 = 2 + }; + + public struct retro_message + { + public string msg; + public uint frames; + } + + public struct retro_input_descriptor + { + public uint port; + public uint device; + public uint index; + public uint id; + } + + public struct retro_system_info + { + public string library_name; + public string library_version; + public string valid_extensions; + [MarshalAs(UnmanagedType.U1)] + public bool need_fullpath; + [MarshalAs(UnmanagedType.U1)] + public bool block_extract; + } + + public struct retro_game_geometry + { + public uint base_width; + public uint base_height; + public uint max_width; + public uint max_height; + public float aspect_ratio; + } + + public struct retro_system_timing + { + public double fps; + public double sample_rate; + } + + public struct retro_system_av_info + { + public retro_game_geometry geometry; + public retro_system_timing timing; + } + + public struct retro_variable + { + public string key; + public string value; + } + + public struct retro_game_info + { + public string path; + public IntPtr data; + public uint size; + public string meta; + } + + //untested + public struct retro_perf_counter + { + public string ident; + public ulong start; + public ulong total; + public ulong call_cnt; + + [MarshalAs(UnmanagedType.U1)] + public bool registered; + }; + + //perf callbacks + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate long retro_perf_get_time_usec_t(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate long retro_perf_get_counter_t(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate ulong retro_get_cpu_features_t(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void retro_perf_log_t(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void retro_perf_register_t(ref retro_perf_counter counter); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void retro_perf_start_t(ref retro_perf_counter counter); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void retro_perf_stop_t(ref retro_perf_counter counter); + + //for GET_PERF_INTERFACE + public struct retro_perf_callback + { + public retro_perf_get_time_usec_t get_time_usec; + public retro_get_cpu_features_t get_cpu_features; + public retro_perf_get_counter_t get_perf_counter; + public retro_perf_register_t perf_register; + public retro_perf_start_t perf_start; + public retro_perf_stop_t perf_stop; + public retro_perf_log_t perf_log; + } + + #region callback prototypes + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void retro_log_printf_t(RETRO_LOG_LEVEL level, string fmt, IntPtr a0, IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, IntPtr a7, IntPtr a8, IntPtr a9, IntPtr a10, IntPtr a11, IntPtr a12, IntPtr a13, IntPtr a14, IntPtr a15); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return:MarshalAs(UnmanagedType.U1)] + public delegate bool retro_environment_t(RETRO_ENVIRONMENT cmd, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void retro_video_refresh_t(IntPtr data, uint width, uint height, uint pitch); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void retro_audio_sample_t(short left, short right); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate uint retro_audio_sample_batch_t(IntPtr data, uint frames); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void retro_input_poll_t(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate short retro_input_state_t(uint port, uint device, uint index, uint id); + #endregion + + #region entry point prototypes + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_set_environment(retro_environment_t cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_set_video_refresh(retro_video_refresh_t cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_set_audio_sample(retro_audio_sample_t cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_set_audio_sample_batch(retro_audio_sample_batch_t cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_set_input_poll(retro_input_poll_t cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_set_input_state(retro_input_state_t cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_init(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_deinit(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate uint epretro_api_version(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_get_system_info(ref retro_system_info info); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_get_system_av_info(ref retro_system_av_info info); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_set_controller_port_device(uint port, uint device); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_reset(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_run(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate uint epretro_serialize_size(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + public delegate bool epretro_serialize(IntPtr data, uint size); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + public delegate bool epretro_unserialize(IntPtr data, uint size); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_cheat_reset(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_cheat_set(uint index, [MarshalAs(UnmanagedType.U1)]bool enabled, string code); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + public delegate bool epretro_load_game(ref retro_game_info game); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + public delegate bool epretro_load_game_special(uint game_type, ref retro_game_info info, uint num_info); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void epretro_unload_game(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate uint epretro_get_region(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr epretro_get_memory_data(RETRO_MEMORY id); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate uint epretro_get_memory_size(RETRO_MEMORY id); + #endregion + + #region entry points + // these are all hooked up by reflection on dll load + public epretro_set_environment retro_set_environment; + public epretro_set_video_refresh retro_set_video_refresh; + public epretro_set_audio_sample retro_set_audio_sample; + public epretro_set_audio_sample_batch retro_set_audio_sample_batch; + public epretro_set_input_poll retro_set_input_poll; + public epretro_set_input_state retro_set_input_state; + public epretro_init retro_init; + /// + /// Dispose() calls this, so you shouldn't + /// + public epretro_deinit retro_deinit; + public epretro_api_version retro_api_version; + public epretro_get_system_info retro_get_system_info; + public epretro_get_system_av_info retro_get_system_av_info; + public epretro_set_controller_port_device retro_set_controller_port_device; + public epretro_reset retro_reset; + public epretro_run retro_run; + public epretro_serialize_size retro_serialize_size; + public epretro_serialize retro_serialize; + public epretro_unserialize retro_unserialize; + public epretro_cheat_reset retro_cheat_reset; + public epretro_cheat_set retro_cheat_set; + public epretro_load_game retro_load_game; + public epretro_load_game_special retro_load_game_special; + public epretro_unload_game retro_unload_game; + public epretro_get_region retro_get_region; + public epretro_get_memory_data retro_get_memory_data; + public epretro_get_memory_size retro_get_memory_size; + #endregion + + public void Dispose() + { + dll.Dispose(); + } + + InstanceDll dll; + public LibRetro(string modulename) + { + dll = new InstanceDll(modulename); + if (!ConnectAllEntryPoints()) + { + dll.Dispose(); + throw new Exception("ConnectAllEntryPoints() failed. The console may contain more details."); + } + } + + private static IEnumerable GetAllEntryPoints() + { + return typeof(LibRetro).GetFields().Where((field) => field.FieldType.Name.StartsWith("epretro")); + } + + private void ClearAllEntryPoints() + { + foreach (var field in GetAllEntryPoints()) + { + field.SetValue(this, null); + } + } + + private bool ConnectAllEntryPoints() + { + bool succeed = true; + foreach (var field in GetAllEntryPoints()) + { + string fieldname = field.Name; + IntPtr entry = dll.GetProcAddress(fieldname); + if (entry != IntPtr.Zero) + { + field.SetValue(this, Marshal.GetDelegateForFunctionPointer(entry, field.FieldType)); + } + else + { + Console.WriteLine("Couldn't bind libretro entry point {0}", fieldname); + succeed = false; + } + } + return succeed; + } + } +} diff --git a/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.InputCallbacks.cs b/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.InputCallbacks.cs new file mode 100644 index 0000000000..3a867d4b24 --- /dev/null +++ b/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.InputCallbacks.cs @@ -0,0 +1,219 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Common.BufferExtensions; + +namespace BizHawk.Emulation.Cores +{ + partial class LibRetroEmulator + { + //meanings (they are kind of hazy, but once we're done implementing this it will be completely defined by example) + //port = console physical port? + //device = logical device type + //index = sub device index? (multitap?) + //id = button id (or key id) + short retro_input_state(uint port, uint device, uint index, uint id) + { + //helpful debugging + //Console.WriteLine("{0} {1} {2} {3}", port, device, index, id); + + switch ((LibRetro.RETRO_DEVICE)device) + { + case LibRetro.RETRO_DEVICE.POINTER: + { + switch ((LibRetro.RETRO_DEVICE_ID_POINTER)id) + { + case LibRetro.RETRO_DEVICE_ID_POINTER.X: return (short)Controller.GetFloat("Pointer X"); + case LibRetro.RETRO_DEVICE_ID_POINTER.Y: return (short)Controller.GetFloat("Pointer Y"); + case LibRetro.RETRO_DEVICE_ID_POINTER.PRESSED: return (short)(Controller["Pointer Pressed"] ? 1 : 0); + } + return 0; + } + + case LibRetro.RETRO_DEVICE.KEYBOARD: + { + string button = ""; + switch ((LibRetro.RETRO_KEY)id) + { + case LibRetro.RETRO_KEY.BACKSPACE: button = "Backspace"; break; + case LibRetro.RETRO_KEY.TAB: button = "Tab"; break; + case LibRetro.RETRO_KEY.CLEAR: button = "Clear"; break; + case LibRetro.RETRO_KEY.RETURN: button = "Return"; break; + case LibRetro.RETRO_KEY.PAUSE: button = "Pause"; break; + case LibRetro.RETRO_KEY.ESCAPE: button = "Escape"; break; + case LibRetro.RETRO_KEY.SPACE: button = "Space"; break; + case LibRetro.RETRO_KEY.EXCLAIM: button = "Exclaim"; break; + case LibRetro.RETRO_KEY.QUOTEDBL: button = "QuoteDbl"; break; + case LibRetro.RETRO_KEY.HASH: button = "Hash"; break; + case LibRetro.RETRO_KEY.DOLLAR: button = "Dollar"; break; + case LibRetro.RETRO_KEY.AMPERSAND: button = "Ampersand"; break; + case LibRetro.RETRO_KEY.QUOTE: button = "Quote"; break; + case LibRetro.RETRO_KEY.LEFTPAREN: button = "LeftParen"; break; + case LibRetro.RETRO_KEY.RIGHTPAREN: button = "RightParen"; break; + case LibRetro.RETRO_KEY.ASTERISK: button = "Asterisk"; break; + case LibRetro.RETRO_KEY.PLUS: button = "Plus"; break; + case LibRetro.RETRO_KEY.COMMA: button = "Comma"; break; + case LibRetro.RETRO_KEY.MINUS: button = "Minus"; break; + case LibRetro.RETRO_KEY.PERIOD: button = "Period"; break; + case LibRetro.RETRO_KEY.SLASH: button = "Slash"; break; + case LibRetro.RETRO_KEY._0: button = "0"; break; + case LibRetro.RETRO_KEY._1: button = "1"; break; + case LibRetro.RETRO_KEY._2: button = "2"; break; + case LibRetro.RETRO_KEY._3: button = "3"; break; + case LibRetro.RETRO_KEY._4: button = "4"; break; + case LibRetro.RETRO_KEY._5: button = "5"; break; + case LibRetro.RETRO_KEY._6: button = "6"; break; + case LibRetro.RETRO_KEY._7: button = "7"; break; + case LibRetro.RETRO_KEY._8: button = "8"; break; + case LibRetro.RETRO_KEY._9: button = "9"; break; + case LibRetro.RETRO_KEY.COLON: button = "Colon"; break; + case LibRetro.RETRO_KEY.SEMICOLON: button = "Semicolon"; break; + case LibRetro.RETRO_KEY.LESS: button = "Less"; break; + case LibRetro.RETRO_KEY.EQUALS: button = "Equals"; break; + case LibRetro.RETRO_KEY.GREATER: button = "Greater"; break; + case LibRetro.RETRO_KEY.QUESTION: button = "Question"; break; + case LibRetro.RETRO_KEY.AT: button = "At"; break; + case LibRetro.RETRO_KEY.LEFTBRACKET: button = "LeftBracket"; break; + case LibRetro.RETRO_KEY.BACKSLASH: button = "Backslash"; break; + case LibRetro.RETRO_KEY.RIGHTBRACKET: button = "RightBracket"; break; + case LibRetro.RETRO_KEY.CARET: button = "Caret"; break; + case LibRetro.RETRO_KEY.UNDERSCORE: button = "Underscore"; break; + case LibRetro.RETRO_KEY.BACKQUOTE: button = "Backquote"; break; + case LibRetro.RETRO_KEY.a: button = "A"; break; + case LibRetro.RETRO_KEY.b: button = "B"; break; + case LibRetro.RETRO_KEY.c: button = "C"; break; + case LibRetro.RETRO_KEY.d: button = "D"; break; + case LibRetro.RETRO_KEY.e: button = "E"; break; + case LibRetro.RETRO_KEY.f: button = "F"; break; + case LibRetro.RETRO_KEY.g: button = "G"; break; + case LibRetro.RETRO_KEY.h: button = "H"; break; + case LibRetro.RETRO_KEY.i: button = "I"; break; + case LibRetro.RETRO_KEY.j: button = "J"; break; + case LibRetro.RETRO_KEY.k: button = "K"; break; + case LibRetro.RETRO_KEY.l: button = "L"; break; + case LibRetro.RETRO_KEY.m: button = "M"; break; + case LibRetro.RETRO_KEY.n: button = "N"; break; + case LibRetro.RETRO_KEY.o: button = "O"; break; + case LibRetro.RETRO_KEY.p: button = "P"; break; + case LibRetro.RETRO_KEY.q: button = "Q"; break; + case LibRetro.RETRO_KEY.r: button = "R"; break; + case LibRetro.RETRO_KEY.s: button = "S"; break; + case LibRetro.RETRO_KEY.t: button = "T"; break; + case LibRetro.RETRO_KEY.u: button = "U"; break; + case LibRetro.RETRO_KEY.v: button = "V"; break; + case LibRetro.RETRO_KEY.w: button = "W"; break; + case LibRetro.RETRO_KEY.x: button = "X"; break; + case LibRetro.RETRO_KEY.y: button = "Y"; break; + case LibRetro.RETRO_KEY.z: button = "Z"; break; + case LibRetro.RETRO_KEY.DELETE: button = "Delete"; break; + + case LibRetro.RETRO_KEY.KP0: button = "KP0"; break; + case LibRetro.RETRO_KEY.KP1: button = "KP1"; break; + case LibRetro.RETRO_KEY.KP2: button = "KP2"; break; + case LibRetro.RETRO_KEY.KP3: button = "KP3"; break; + case LibRetro.RETRO_KEY.KP4: button = "KP4"; break; + case LibRetro.RETRO_KEY.KP5: button = "KP5"; break; + case LibRetro.RETRO_KEY.KP6: button = "KP6"; break; + case LibRetro.RETRO_KEY.KP7: button = "KP7"; break; + case LibRetro.RETRO_KEY.KP8: button = "KP8"; break; + case LibRetro.RETRO_KEY.KP9: button = "KP9"; break; + case LibRetro.RETRO_KEY.KP_PERIOD: button = "KP_Period"; break; + case LibRetro.RETRO_KEY.KP_DIVIDE: button = "KP_Divide"; break; + case LibRetro.RETRO_KEY.KP_MULTIPLY: button = "KP_Multiply"; break; + case LibRetro.RETRO_KEY.KP_MINUS: button = "KP_Minus"; break; + case LibRetro.RETRO_KEY.KP_PLUS: button = "KP_Plus"; break; + case LibRetro.RETRO_KEY.KP_ENTER: button = "KP_Enter"; break; + case LibRetro.RETRO_KEY.KP_EQUALS: button = "KP_Equals"; break; + + case LibRetro.RETRO_KEY.UP: button = "Up"; break; + case LibRetro.RETRO_KEY.DOWN: button = "Down"; break; + case LibRetro.RETRO_KEY.RIGHT: button = "Right"; break; + case LibRetro.RETRO_KEY.LEFT: button = "Left"; break; + case LibRetro.RETRO_KEY.INSERT: button = "Insert"; break; + case LibRetro.RETRO_KEY.HOME: button = "Home"; break; + case LibRetro.RETRO_KEY.END: button = "End"; break; + case LibRetro.RETRO_KEY.PAGEUP: button = "PageUp"; break; + case LibRetro.RETRO_KEY.PAGEDOWN: button = "PageDown"; break; + + case LibRetro.RETRO_KEY.F1: button = "F1"; break; + case LibRetro.RETRO_KEY.F2: button = "F2"; break; + case LibRetro.RETRO_KEY.F3: button = "F3"; break; + case LibRetro.RETRO_KEY.F4: button = "F4"; break; + case LibRetro.RETRO_KEY.F5: button = "F5"; break; + case LibRetro.RETRO_KEY.F6: button = "F6"; break; + case LibRetro.RETRO_KEY.F7: button = "F7"; break; + case LibRetro.RETRO_KEY.F8: button = "F8"; break; + case LibRetro.RETRO_KEY.F9: button = "F9"; break; + case LibRetro.RETRO_KEY.F10: button = "F10"; break; + case LibRetro.RETRO_KEY.F11: button = "F11"; break; + case LibRetro.RETRO_KEY.F12: button = "F12"; break; + case LibRetro.RETRO_KEY.F13: button = "F13"; break; + case LibRetro.RETRO_KEY.F14: button = "F14"; break; + case LibRetro.RETRO_KEY.F15: button = "F15"; break; + + case LibRetro.RETRO_KEY.NUMLOCK: button = "NumLock"; break; + case LibRetro.RETRO_KEY.CAPSLOCK: button = "CapsLock"; break; + case LibRetro.RETRO_KEY.SCROLLOCK: button = "ScrollLock"; break; + case LibRetro.RETRO_KEY.RSHIFT: button = "RShift"; break; + case LibRetro.RETRO_KEY.LSHIFT: button = "LShift"; break; + case LibRetro.RETRO_KEY.RCTRL: button = "RCtrl"; break; + case LibRetro.RETRO_KEY.LCTRL: button = "LCtrl"; break; + case LibRetro.RETRO_KEY.RALT: button = "RAlt"; break; + case LibRetro.RETRO_KEY.LALT: button = "LAlt"; break; + case LibRetro.RETRO_KEY.RMETA: button = "RMeta"; break; + case LibRetro.RETRO_KEY.LMETA: button = "LMeta"; break; + case LibRetro.RETRO_KEY.LSUPER: button = "LSuper"; break; + case LibRetro.RETRO_KEY.RSUPER: button = "RSuper"; break; + case LibRetro.RETRO_KEY.MODE: button = "Mode"; break; + case LibRetro.RETRO_KEY.COMPOSE: button = "Compose"; break; + + case LibRetro.RETRO_KEY.HELP: button = "Help"; break; + case LibRetro.RETRO_KEY.PRINT: button = "Print"; break; + case LibRetro.RETRO_KEY.SYSREQ: button = "SysReq"; break; + case LibRetro.RETRO_KEY.BREAK: button = "Break"; break; + case LibRetro.RETRO_KEY.MENU: button = "Menu"; break; + case LibRetro.RETRO_KEY.POWER: button = "Power"; break; + case LibRetro.RETRO_KEY.EURO: button = "Euro"; break; + case LibRetro.RETRO_KEY.UNDO: button = "Undo"; break; + } + + return (short)(Controller["Key " + button] ? 1 : 0); + } + + case LibRetro.RETRO_DEVICE.JOYPAD: + { + //The JOYPAD is sometimes called RetroPad (and we'll call it that in user-facing stuff cos retroarch does) + //It is essentially a Super Nintendo controller, but with additional L2/R2/L3/R3 buttons, similar to a PS1 DualShock. + + string button = ""; + switch ((LibRetro.RETRO_DEVICE_ID_JOYPAD)id) + { + case LibRetro.RETRO_DEVICE_ID_JOYPAD.A: button = "A"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.B: button = "B"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.X: button = "X"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.Y: button = "Y"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.UP: button = "Up"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.DOWN: button = "Down"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.LEFT: button = "Left"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.RIGHT: button = "Right"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.L: button = "L"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.R: button = "R"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.SELECT: button = "Select"; break; + case LibRetro.RETRO_DEVICE_ID_JOYPAD.START: button = "Start"; break; + } + + return (short)(GetButton(port+1, "RetroPad", button) ? 1 : 0); + } + default: + return 0; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/LibRetroEmulator.cs b/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.cs similarity index 82% rename from BizHawk.Emulation.Cores/LibRetroEmulator.cs rename to BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.cs index 3946fdc706..e67ff90ede 100644 --- a/BizHawk.Emulation.Cores/LibRetroEmulator.cs +++ b/BizHawk.Emulation.Cores/Libretro/LibRetroEmulator.cs @@ -1,918 +1,915 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Runtime.InteropServices; -using Newtonsoft.Json; - -using BizHawk.Common; -using BizHawk.Emulation.Common; -using BizHawk.Common.BufferExtensions; - -namespace BizHawk.Emulation.Cores -{ - [CoreAttributes("Libretro", "natt&zeromus")] - public unsafe class LibRetroEmulator : IEmulator, ISettable, - ISaveRam, IStatable, IVideoProvider, IInputPollable - { - #region Settings - - Settings _Settings = new Settings(); - SyncSettings _SyncSettings; - - public class SyncSettings - { - public SyncSettings Clone() - { - return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)); - } - - public SyncSettings() - { - } - } - - - public class Settings - { - public void Validate() - { - } - - public Settings() - { - SettingsUtil.SetDefaultValues(this); - } - - public Settings Clone() - { - return (Settings)MemberwiseClone(); - } - } - - public Settings GetSettings() - { - return _Settings.Clone(); - } - - public SyncSettings GetSyncSettings() - { - return _SyncSettings.Clone(); - } - - public bool PutSettings(Settings o) - { - _Settings.Validate(); - _Settings = o; - - //TODO - store settings into core? or we can just keep doing it before frameadvance - - return false; - } - - public bool PutSyncSettings(SyncSettings o) - { - bool reboot = false; - - //we could do it this way roughly if we need to - //if(JsonConvert.SerializeObject(o.FIOConfig) != JsonConvert.SerializeObject(_SyncSettings.FIOConfig) - - _SyncSettings = o; - - return reboot; - } - - #endregion - - #region callbacks - - unsafe void retro_log_printf(LibRetro.RETRO_LOG_LEVEL level, string fmt, IntPtr a0, IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, IntPtr a7, IntPtr a8, IntPtr a9, IntPtr a10, IntPtr a11, IntPtr a12, IntPtr a13, IntPtr a14, IntPtr a15) - { - //avert your eyes, these things were not meant to be seen in c# - //I'm not sure this is a great idea. It would suck for silly logging to be unstable. But.. I dont think this is unstable. The sprintf might just print some garbledy stuff. - var args = new IntPtr[] { a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 }; - int idx = 0; - Console.Write(Sprintf.sprintf(fmt, () => args[idx++])); - } - - unsafe bool retro_environment(LibRetro.RETRO_ENVIRONMENT cmd, IntPtr data) - { - Console.WriteLine(cmd); - switch (cmd) - { - case LibRetro.RETRO_ENVIRONMENT.SET_ROTATION: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_OVERSCAN: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_CAN_DUPE: - return true; - case LibRetro.RETRO_ENVIRONMENT.SET_MESSAGE: - { - LibRetro.retro_message msg = new LibRetro.retro_message(); - Marshal.PtrToStructure(data, msg); - if (!string.IsNullOrEmpty(msg.msg)) - Console.WriteLine("LibRetro Message: {0}", msg.msg); - return true; - } - case LibRetro.RETRO_ENVIRONMENT.SHUTDOWN: - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_PERFORMANCE_LEVEL: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_SYSTEM_DIRECTORY: - //please write an example of a core that crashes without this (fmsx malfunctions..) - //"this is optional, but many cores will silently malfunction without it as they can't load their firmware files" - //an alternative (alongside where the saverams and such will go?) - //*((IntPtr*)data.ToPointer()) = unmanagedResources.StringToHGlobalAnsi(CoreComm.CoreFileProvider.GetGameBasePath()); - *((IntPtr*)data.ToPointer()) = SystemDirectoryAtom; - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_PIXEL_FORMAT: - { - LibRetro.RETRO_PIXEL_FORMAT fmt = 0; - int[] tmp = new int[1]; - Marshal.Copy(data, tmp, 0, 1); - fmt = (LibRetro.RETRO_PIXEL_FORMAT)tmp[0]; - switch (fmt) - { - case LibRetro.RETRO_PIXEL_FORMAT.RGB565: - case LibRetro.RETRO_PIXEL_FORMAT.XRGB1555: - case LibRetro.RETRO_PIXEL_FORMAT.XRGB8888: - pixelfmt = fmt; - Console.WriteLine("New pixel format set: {0}", pixelfmt); - return true; - default: - Console.WriteLine("Unrecognized pixel format: {0}", (int)pixelfmt); - return false; - } - } - case LibRetro.RETRO_ENVIRONMENT.SET_INPUT_DESCRIPTORS: - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_KEYBOARD_CALLBACK: - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_DISK_CONTROL_INTERFACE: - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_HW_RENDER: - // this can be done in principle, but there's no reason to right now - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_VARIABLE: - { - void** variables = (void**)data.ToPointer(); - IntPtr pKey = new IntPtr(*variables++); - string key = Marshal.PtrToStringAnsi(pKey); - Console.WriteLine("Requesting variable: {0}", key); - //always return default - //TODO: cache settings atoms - if(!Description.Variables.ContainsKey(key)) - return false; - //HACK: return pointer for desmume mouse, i want to implement that first - if (key == "desmume_pointer_type") - { - *variables = unmanagedResources.StringToHGlobalAnsi("touch").ToPointer(); - return true; - } - *variables = unmanagedResources.StringToHGlobalAnsi(Description.Variables[key].DefaultOption).ToPointer(); - return true; - } - case LibRetro.RETRO_ENVIRONMENT.SET_VARIABLES: - { - void** variables = (void**)data.ToPointer(); - for (; ; ) - { - IntPtr pKey = new IntPtr(*variables++); - IntPtr pValue = new IntPtr(*variables++); - if(pKey == IntPtr.Zero) - break; - string key = Marshal.PtrToStringAnsi(pKey); - string value = Marshal.PtrToStringAnsi(pValue); - var vd = new VariableDescription() { Name = key}; - var parts = value.Split(';'); - vd.Description = parts[0]; - vd.Options = parts[1].TrimStart(' ').Split('|'); - Description.Variables[vd.Name] = vd; - } - } - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_VARIABLE_UPDATE: - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_SUPPORT_NO_GAME: - environmentInfo.SupportNoGame = true; - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_LIBRETRO_PATH: - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_AUDIO_CALLBACK: - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_FRAME_TIME_CALLBACK: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_RUMBLE_INTERFACE: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_INPUT_DEVICE_CAPABILITIES: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_LOG_INTERFACE: - *(IntPtr*)data = Marshal.GetFunctionPointerForDelegate(retro_log_printf_cb); - return true; - case LibRetro.RETRO_ENVIRONMENT.GET_PERF_INTERFACE: - //some builds of fmsx core crash without this set - Marshal.StructureToPtr(retro_perf_callback, data, false); - return true; - case LibRetro.RETRO_ENVIRONMENT.GET_LOCATION_INTERFACE: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_CORE_ASSETS_DIRECTORY: - return false; - case LibRetro.RETRO_ENVIRONMENT.GET_SAVE_DIRECTORY: - //supposedly optional like everything else here, but without it ?? crashes (please write which case) - //this will suffice for now. if we find evidence later it's needed we can stash a string with - //unmanagedResources and CoreFileProvider - //*((IntPtr*)data.ToPointer()) = IntPtr.Zero; - return false; - case LibRetro.RETRO_ENVIRONMENT.SET_CONTROLLER_INFO: - return true; - default: - Console.WriteLine("Unknkown retro_environment command {0}", (int)cmd); - return false; - } - } - void retro_input_poll() - { - IsLagFrame = false; - } - - private bool GetButton(uint pnum, string type, string button) - { - string key = string.Format("P{0} {1} {2}", pnum, type, button); - bool b = Controller[key]; - if (b == true) - { - return true; //debugging placeholder - } - else return false; - } - - //meanings (they are kind of hazy, but once we're done implementing this it will be completely defined by example) - //port = console physical port? - //device = logical device type - //index = sub device index? (multitap?) - //id = button id - short retro_input_state(uint port, uint device, uint index, uint id) - { - //helpful debugging - //Console.WriteLine("{0} {1} {2} {3}", port, device, index, id); - - switch ((LibRetro.RETRO_DEVICE)device) - { - case LibRetro.RETRO_DEVICE.POINTER: - { - switch ((LibRetro.RETRO_DEVICE_ID_POINTER)id) - { - case LibRetro.RETRO_DEVICE_ID_POINTER.X: return (short)Controller.GetFloat("Pointer X"); - case LibRetro.RETRO_DEVICE_ID_POINTER.Y: return (short)Controller.GetFloat("Pointer Y"); - case LibRetro.RETRO_DEVICE_ID_POINTER.PRESSED: return (short)(Controller["Pointer Pressed"] ? 1 : 0); - } - return 0; - } - - case LibRetro.RETRO_DEVICE.JOYPAD: - { - //The JOYPAD is sometimes called RetroPad (and we'll call it that in user-facing stuff cos retroarch does) - //It is essentially a Super Nintendo controller, but with additional L2/R2/L3/R3 buttons, similar to a PS1 DualShock. - - string button = ""; - switch ((LibRetro.RETRO_DEVICE_ID_JOYPAD)id) - { - case LibRetro.RETRO_DEVICE_ID_JOYPAD.A: button = "A"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.B: button = "B"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.X: button = "X"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.Y: button = "Y"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.UP: button = "Up"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.DOWN: button = "Down"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.LEFT: button = "Left"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.RIGHT: button = "Right"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.L: button = "L"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.R: button = "R"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.SELECT: button = "Select"; break; - case LibRetro.RETRO_DEVICE_ID_JOYPAD.START: button = "Start"; break; - } - - return (short)(GetButton(port+1, "RetroPad", button) ? 1 : 0); - } - default: - return 0; - } - } - - LibRetro.retro_environment_t retro_environment_cb; - LibRetro.retro_video_refresh_t retro_video_refresh_cb; - LibRetro.retro_audio_sample_t retro_audio_sample_cb; - LibRetro.retro_audio_sample_batch_t retro_audio_sample_batch_cb; - LibRetro.retro_input_poll_t retro_input_poll_cb; - LibRetro.retro_input_state_t retro_input_state_cb; - LibRetro.retro_log_printf_t retro_log_printf_cb; - - LibRetro.retro_perf_callback retro_perf_callback = new LibRetro.retro_perf_callback(); - - #endregion - - class RetroEnvironmentInfo - { - public bool SupportNoGame; - } - - //disposable resources - private LibRetro retro; - private UnmanagedResourceHeap unmanagedResources = new UnmanagedResourceHeap(); - - /// - /// Cached information sent to the frontend by environment calls - /// - RetroEnvironmentInfo environmentInfo = new RetroEnvironmentInfo(); - - public class RetroDescription - { - /// - /// String containing a friendly display name for the core, but we probably shouldn't use this. I decided it's better to get the user used to using filenames as core 'codenames' instead. - /// - public string LibraryName; - - /// - /// String containing a friendly version number for the core library - /// - public string LibraryVersion; - - /// - /// List of extensions as "sfc|smc|fig" which this core accepts. - /// - public string ValidExtensions; - - /// - /// Whether the core needs roms to be specified as paths (can't take rom data buffersS) - /// - public bool NeedsRomAsPath; - - /// - /// Whether the core needs roms stored as archives (e.g. arcade roms). We probably shouldn't employ the dearchiver prompts when opening roms for these cores. - /// - public bool NeedsArchives; - - /// - /// Whether the core can be run without a game provided (e.g. stand-alone games, like 2048) - /// - public bool SupportsNoGame; - - /// - /// Variables defined by the core - /// - public Dictionary Variables = new Dictionary(); - } - - public class VariableDescription - { - public string Name; - public string Description; - public string[] Options; - public string DefaultOption { get { return Options[0]; } } - - public override string ToString() - { - return string.Format("{0} ({1}) = ({2})", Name, Description, string.Join("|", Options)); - } - } - - public readonly RetroDescription Description = new RetroDescription(); - - //path configuration - string CoresDirectory; - string SystemDirectory; - IntPtr SystemDirectoryAtom; - - public LibRetroEmulator(CoreComm nextComm, string modulename) - { - CoresDirectory = Path.GetDirectoryName(new FileInfo(modulename).FullName); - SystemDirectory = Path.Combine(CoresDirectory, "System"); - SystemDirectoryAtom = unmanagedResources.StringToHGlobalAnsi(SystemDirectory); - - ServiceProvider = new BasicServiceProvider(this); - - _SyncSettings = new SyncSettings(); - - retro_environment_cb = new LibRetro.retro_environment_t(retro_environment); - retro_video_refresh_cb = new LibRetro.retro_video_refresh_t(retro_video_refresh); - retro_audio_sample_cb = new LibRetro.retro_audio_sample_t(retro_audio_sample); - retro_audio_sample_batch_cb = new LibRetro.retro_audio_sample_batch_t(retro_audio_sample_batch); - retro_input_poll_cb = new LibRetro.retro_input_poll_t(retro_input_poll); - retro_input_state_cb = new LibRetro.retro_input_state_t(retro_input_state); - retro_log_printf_cb = new LibRetro.retro_log_printf_t(retro_log_printf); - - //no way (need new mechanism) to check for SSSE3, MMXEXT, SSE4, SSE42 - retro_perf_callback.get_cpu_features = new LibRetro.retro_get_cpu_features_t(() => (ulong)( - (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsXMMIAvailable) ? LibRetro.RETRO_SIMD.SSE : 0) | - (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsXMMI64Available) ? LibRetro.RETRO_SIMD.SSE2 : 0) | - (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsSSE3Available) ? LibRetro.RETRO_SIMD.SSE3 : 0) | - (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsMMXAvailable) ? LibRetro.RETRO_SIMD.MMX : 0) - ) ); - retro_perf_callback.get_perf_counter = new LibRetro.retro_perf_get_counter_t(() => System.Diagnostics.Stopwatch.GetTimestamp()); - retro_perf_callback.get_time_usec = new LibRetro.retro_perf_get_time_usec_t(() => DateTime.Now.Ticks / 10); - retro_perf_callback.perf_log = new LibRetro.retro_perf_log_t( () => {} ); - retro_perf_callback.perf_register = new LibRetro.retro_perf_register_t((ref LibRetro.retro_perf_counter counter) => { }); - retro_perf_callback.perf_start = new LibRetro.retro_perf_start_t((ref LibRetro.retro_perf_counter counter) => { }); - retro_perf_callback.perf_stop = new LibRetro.retro_perf_stop_t((ref LibRetro.retro_perf_counter counter) => { }); - - retro = new LibRetro(modulename); - - try - { - CoreComm = nextComm; - - //this series of steps may be mystical. - LibRetro.retro_system_info system_info = new LibRetro.retro_system_info(); - retro.retro_get_system_info(ref system_info); - - retro.retro_set_environment(retro_environment_cb); - - retro.retro_init(); - - retro.retro_set_video_refresh(retro_video_refresh_cb); - retro.retro_set_audio_sample(retro_audio_sample_cb); - retro.retro_set_audio_sample_batch(retro_audio_sample_batch_cb); - retro.retro_set_input_poll(retro_input_poll_cb); - retro.retro_set_input_state(retro_input_state_cb); - - //compile descriptive information - Description.NeedsArchives = system_info.block_extract; - Description.NeedsRomAsPath = system_info.need_fullpath; - Description.LibraryName = system_info.library_name; - Description.LibraryVersion = system_info.library_version; - Description.ValidExtensions = system_info.valid_extensions; - Description.SupportsNoGame = environmentInfo.SupportNoGame; - //variables need to be done ahead of time, when theyre set through the environment - //some retro_init (for example, desmume) will continue to use variables (and maybe other parts of the environment) from within retro_init - } - catch - { - retro.Dispose(); - retro = null; - throw; - } - - - } - - public IEmulatorServiceProvider ServiceProvider { get; private set; } - - - - public bool LoadData(byte[] data) - { - LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); - fixed (byte* p = &data[0]) - { - gi.data = (IntPtr)p; - gi.meta = ""; - gi.path = ""; - gi.size = (uint)data.Length; - return LoadWork(ref gi); - } - } - - public bool LoadPath(string path) - { - LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); - gi.path = path; //is this the right encoding? seems to be ok - return LoadWork(ref gi); - } - - public bool LoadNoGame() - { - LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); - return LoadWork(ref gi); - } - - bool LoadWork(ref LibRetro.retro_game_info gi) - { - if (!retro.retro_load_game(ref gi)) - { - Console.WriteLine("retro_load_game() failed"); - return false; - } - - //TODO - libretro cores can return a varying serialize size over time. I tried to get them to write it in the docs... - savebuff = new byte[retro.retro_serialize_size()]; - savebuff2 = new byte[savebuff.Length + 13]; - - LibRetro.retro_system_av_info av = new LibRetro.retro_system_av_info(); - retro.retro_get_system_av_info(ref av); - - BufferWidth = (int)av.geometry.base_width; - BufferHeight = (int)av.geometry.base_height; - vidbuff = new int[av.geometry.max_width * av.geometry.max_height]; - dar = av.geometry.aspect_ratio; - - // TODO: more precise - CoreComm.VsyncNum = (int)(10000000 * av.timing.fps); - CoreComm.VsyncDen = 10000000; - - SetupResampler(av.timing.fps, av.timing.sample_rate); - - ControllerDefinition = CreateControllerDefinition(_SyncSettings); - - return true; - } - - public static ControllerDefinition CreateControllerDefinition(SyncSettings syncSettings) - { - ControllerDefinition definition = new ControllerDefinition(); - definition.Name = "LibRetro Controls"; // <-- for compatibility - - foreach(var item in new[] { - "P1 {0} Up", "P1 {0} Down", "P1 {0} Left", "P1 {0} Right", "P1 {0} Select", "P1 {0} Start", "P1 {0} Y", "P1 {0} B", "P1 {0} X", "P1 {0} A", "P1 {0} L", "P1 {0} R", - "P2 {0} Up", "P2 {0} Down", "P2 {0} Left", "P2 {0} Right", "P2 {0} Select", "P2 {0} Start", "P2 {0} Y", "P2 {0} B", "P2 {0} X", "P2 {0} A", "P2 {0} L", "P2 {0} R", - }) - definition.BoolButtons.Add(string.Format(item,"RetroPad")); - - definition.BoolButtons.Add("Pointer Pressed"); - definition.FloatControls.Add("Pointer X"); - definition.FloatControls.Add("Pointer Y"); - definition.FloatRanges.Add(new ControllerDefinition.FloatRange(-32767, 0, 32767)); - definition.FloatRanges.Add(new ControllerDefinition.FloatRange(-32767, 0, 32767)); - - return definition; - } - - public ControllerDefinition ControllerDefinition { get; private set; } - public IController Controller { get; set; } - - public void FrameAdvance(bool render, bool rendersound = true) - { - //TODO - consider changing directory and using Libretro subdir of bizhawk as a kind of sandbox, for the duration of the run? - IsLagFrame = true; - Frame++; - nsamprecv = 0; - retro.retro_run(); - //Console.WriteLine("[{0}]", nsamprecv); - } - - public int Frame { get; private set; } - - public string SystemId - { - get { return "Libretro"; } - } - - public bool DeterministicEmulation - { - // who knows - get { return true; } - } - - public string BoardName - { - get { return null; } - } - - #region ISaveRam - //TODO - terrible things will happen if this changes at runtime - - byte[] saverambuff = new byte[0]; - - public byte[] CloneSaveRam() - { - int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); - if (saverambuff.Length != size) - saverambuff = new byte[size]; - - IntPtr src = retro.retro_get_memory_data(LibRetro.RETRO_MEMORY.SAVE_RAM); - if (src == IntPtr.Zero) - return null; - - Marshal.Copy(src, saverambuff, 0, size); - return (byte[])saverambuff.Clone(); - } - - public void StoreSaveRam(byte[] data) - { - int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); - - if (size == 0) - return; - - IntPtr dst = retro.retro_get_memory_data(LibRetro.RETRO_MEMORY.SAVE_RAM); - if (dst == IntPtr.Zero) - throw new Exception("retro_get_memory_data(RETRO_MEMORY_SAVE_RAM) returned NULL"); - - Marshal.Copy(data, 0, dst, size); - } - - public bool SaveRamModified - { - [FeatureNotImplemented] - get - { - //if we dont have saveram, it isnt modified. otherwise, assume iti s - int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); - if (size == 0) - return false; - return true; - } - - [FeatureNotImplemented] - set { throw new NotImplementedException(); } - } - - #endregion - - public void ResetCounters() - { - Frame = 0; - LagCount = 0; - IsLagFrame = false; - } - - #region savestates - - private byte[] savebuff; - private byte[] savebuff2; - - public void SaveStateText(System.IO.TextWriter writer) - { - var temp = SaveStateBinary(); - temp.SaveAsHex(writer); - } - - public void LoadStateText(System.IO.TextReader reader) - { - string hex = reader.ReadLine(); - byte[] state = new byte[hex.Length / 2]; - state.ReadFromHex(hex); - LoadStateBinary(new BinaryReader(new MemoryStream(state))); - } - - public void SaveStateBinary(System.IO.BinaryWriter writer) - { - //is this the only way we know of to detect unavailable savestates? - if (savebuff.Length > 0) - { - fixed (byte* ptr = &savebuff[0]) - { - if (!retro.retro_serialize((IntPtr)ptr, (uint)savebuff.Length)) - throw new Exception("retro_serialize() failed"); - } - } - - writer.Write(savebuff.Length); - writer.Write(savebuff); - // other variables - writer.Write(Frame); - writer.Write(LagCount); - writer.Write(IsLagFrame); - } - - public void LoadStateBinary(System.IO.BinaryReader reader) - { - int newlen = reader.ReadInt32(); - if (newlen > savebuff.Length) - throw new Exception("Unexpected buffer size"); - reader.Read(savebuff, 0, newlen); - if (savebuff.Length > 0) - { - fixed (byte* ptr = &savebuff[0]) - { - if (!retro.retro_unserialize((IntPtr)ptr, (uint)newlen)) - throw new Exception("retro_unserialize() failed"); - } - } - // other variables - Frame = reader.ReadInt32(); - LagCount = reader.ReadInt32(); - IsLagFrame = reader.ReadBoolean(); - } - - public byte[] SaveStateBinary() - { - var ms = new System.IO.MemoryStream(savebuff2, true); - var bw = new System.IO.BinaryWriter(ms); - SaveStateBinary(bw); - bw.Flush(); - ms.Close(); - return savebuff2; - } - - public bool BinarySaveStatesPreferred { get { return true; } } - - #endregion - - public CoreComm CoreComm - { - get; - private set; - } - - #region memory access - - void SetupDebuggingStuff() - { - } - - public MemoryDomainList MemoryDomains { get; private set; } - - #endregion - - public void Dispose() - { - if (resampler != null) - { - resampler.Dispose(); - resampler = null; - } - if (retro != null) - { - retro.Dispose(); - retro = null; - } - unmanagedResources.Dispose(); - unmanagedResources = null; - } - - #region ISoundProvider - - public ISoundProvider SoundProvider { get { return null; } } - public ISyncSoundProvider SyncSoundProvider { get { return resampler; } } - public bool StartAsyncSound() { return false; } - public void EndAsyncSound() { } - - SpeexResampler resampler; - - short[] sampbuff = new short[0]; - - // debug - int nsamprecv = 0; - - void SetupResampler(double fps, double sps) - { - Console.WriteLine("FPS {0} SPS {1}", fps, sps); - - // todo: more precise? - uint spsnum = (uint)sps * 1000; - uint spsden = (uint)1000; - - resampler = new SpeexResampler(5, 44100 * spsden, spsnum, (uint)sps, 44100, null, null); - } - - void retro_audio_sample(short left, short right) - { - resampler.EnqueueSample(left, right); - nsamprecv++; - } - - uint retro_audio_sample_batch(IntPtr data, uint frames) - { - if (sampbuff.Length < frames * 2) - sampbuff = new short[frames * 2]; - Marshal.Copy(data, sampbuff, 0, (int)(frames * 2)); - resampler.EnqueueSamples(sampbuff, (int)frames); - nsamprecv += (int)frames; - // what is the return from this used for? - return frames; - } - - #endregion - - #region IVideoProvider - - float dar; - int[] vidbuff; - LibRetro.RETRO_PIXEL_FORMAT pixelfmt = LibRetro.RETRO_PIXEL_FORMAT.XRGB1555; - - void Blit555(short* src, int* dst, int width, int height, int pitch) - { - for (int j = 0; j < height; j++) - { - short* row = src; - for (int i = 0; i < width; i++) - { - short ci = *row; - int r = ci & 0x001f; - int g = ci & 0x03e0; - int b = ci & 0x7c00; - - r = (r << 3) | (r >> 2); - g = (g >> 2) | (g >> 7); - b = (b >> 7) | (b >> 12); - int co = r | g | b | unchecked((int)0xff000000); - - *dst = co; - dst++; - row++; - } - src += pitch; - } - } - - void Blit565(short* src, int* dst, int width, int height, int pitch) - { - for (int j = 0; j < height; j++) - { - short* row = src; - for (int i = 0; i < width; i++) - { - short ci = *row; - int r = ci & 0x001f; - int g = (ci & 0x07e0)>>5; - int b = (ci & 0xf800)>>11; - - r = (r << 3) | (r >> 2); - g = (g << 2) | (g >> 4); - b = (b << 3) | (b >> 2); - int co = (b<<16) | (g<<8) | r; - - *dst = co; - dst++; - row++; - } - src += pitch; - } - } - - void Blit888(int* src, int* dst, int width, int height, int pitch) - { - for (int j = 0; j < height; j++) - { - int* row = src; - for (int i = 0; i < width; i++) - { - int ci = *row; - int co = ci | unchecked((int)0xff000000); - *dst = co; - dst++; - row++; - } - src += pitch; - } - } - - void retro_video_refresh(IntPtr data, uint width, uint height, uint pitch) - { - if (data == IntPtr.Zero) // dup frame - return; - if (width * height > vidbuff.Length) - { - Console.WriteLine("Unexpected libretro video buffer overrun?"); - return; - } - fixed (int* dst = &vidbuff[0]) - { - if (pixelfmt == LibRetro.RETRO_PIXEL_FORMAT.XRGB8888) - Blit888((int*)data, dst, (int)width, (int)height, (int)pitch / 4); - else if (pixelfmt == LibRetro.RETRO_PIXEL_FORMAT.RGB565) - Blit565((short*)data, dst, (int)width, (int)height, (int)pitch / 2); - else - Blit555((short*)data, dst, (int)width, (int)height, (int)pitch / 2); - } - } - - - public int[] GetVideoBuffer() - { - return vidbuff; - } - - public int VirtualWidth - { - get - { - if(dar==0) - return BufferWidth; - else if (dar > 1.0f) - return (int)(BufferHeight * dar); - else - return BufferWidth; - } - } - public int VirtualHeight - { - get - { - if(dar==0) - return BufferHeight; - if (dar < 1.0f) - return (int)(BufferWidth / dar); - else - return BufferHeight; - } - } - - public int BufferWidth { get; private set; } - public int BufferHeight { get; private set; } - public int BackgroundColor { get { return unchecked((int)0xff000000); } } - - #endregion - - #region IInputPollable - public int LagCount { get; set; } - public bool IsLagFrame { get; private set; } - public IInputCallbackSystem InputCallbacks - { - [FeatureNotImplemented] - get - { throw new NotImplementedException(); } - } - #endregion - } -} +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using Newtonsoft.Json; + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Common.BufferExtensions; + +namespace BizHawk.Emulation.Cores +{ + [CoreAttributes("Libretro", "natt&zeromus")] + public unsafe partial class LibRetroEmulator : IEmulator, ISettable, + ISaveRam, IStatable, IVideoProvider, IInputPollable + { + #region Settings + + Settings _Settings = new Settings(); + SyncSettings _SyncSettings; + + public class SyncSettings + { + public SyncSettings Clone() + { + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)); + } + + public SyncSettings() + { + } + } + + + public class Settings + { + public void Validate() + { + } + + public Settings() + { + SettingsUtil.SetDefaultValues(this); + } + + public Settings Clone() + { + return (Settings)MemberwiseClone(); + } + } + + public Settings GetSettings() + { + return _Settings.Clone(); + } + + public SyncSettings GetSyncSettings() + { + return _SyncSettings.Clone(); + } + + public bool PutSettings(Settings o) + { + _Settings.Validate(); + _Settings = o; + + //TODO - store settings into core? or we can just keep doing it before frameadvance + + return false; + } + + public bool PutSyncSettings(SyncSettings o) + { + bool reboot = false; + + //we could do it this way roughly if we need to + //if(JsonConvert.SerializeObject(o.FIOConfig) != JsonConvert.SerializeObject(_SyncSettings.FIOConfig) + + _SyncSettings = o; + + return reboot; + } + + #endregion + + #region callbacks + + unsafe void retro_log_printf(LibRetro.RETRO_LOG_LEVEL level, string fmt, IntPtr a0, IntPtr a1, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, IntPtr a7, IntPtr a8, IntPtr a9, IntPtr a10, IntPtr a11, IntPtr a12, IntPtr a13, IntPtr a14, IntPtr a15) + { + //avert your eyes, these things were not meant to be seen in c# + //I'm not sure this is a great idea. It would suck for silly logging to be unstable. But.. I dont think this is unstable. The sprintf might just print some garbledy stuff. + var args = new IntPtr[] { a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 }; + int idx = 0; + Console.Write(Sprintf.sprintf(fmt, () => args[idx++])); + } + + unsafe bool retro_environment(LibRetro.RETRO_ENVIRONMENT cmd, IntPtr data) + { + Console.WriteLine(cmd); + switch (cmd) + { + case LibRetro.RETRO_ENVIRONMENT.SET_ROTATION: + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_OVERSCAN: + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_CAN_DUPE: + //gambatte requires this + *(bool*)data.ToPointer() = true; + return true; + case LibRetro.RETRO_ENVIRONMENT.SET_MESSAGE: + { + LibRetro.retro_message msg = new LibRetro.retro_message(); + Marshal.PtrToStructure(data, msg); + if (!string.IsNullOrEmpty(msg.msg)) + Console.WriteLine("LibRetro Message: {0}", msg.msg); + return true; + } + case LibRetro.RETRO_ENVIRONMENT.SHUTDOWN: + return false; + case LibRetro.RETRO_ENVIRONMENT.SET_PERFORMANCE_LEVEL: + Console.WriteLine("Core suggested SET_PERFORMANCE_LEVEL {0}", *(uint*)data.ToPointer()); + return true; + case LibRetro.RETRO_ENVIRONMENT.GET_SYSTEM_DIRECTORY: + //mednafen NGP neopop fails to launch with no system directory + Directory.CreateDirectory(SystemDirectory); //just to be safe, it seems likely that cores will crash without a created system directory + Console.WriteLine("returning system directory: " + SystemDirectory); + *((IntPtr*)data.ToPointer()) = SystemDirectoryAtom; + return true; + case LibRetro.RETRO_ENVIRONMENT.SET_PIXEL_FORMAT: + { + LibRetro.RETRO_PIXEL_FORMAT fmt = 0; + int[] tmp = new int[1]; + Marshal.Copy(data, tmp, 0, 1); + fmt = (LibRetro.RETRO_PIXEL_FORMAT)tmp[0]; + switch (fmt) + { + case LibRetro.RETRO_PIXEL_FORMAT.RGB565: + case LibRetro.RETRO_PIXEL_FORMAT.XRGB1555: + case LibRetro.RETRO_PIXEL_FORMAT.XRGB8888: + pixelfmt = fmt; + Console.WriteLine("New pixel format set: {0}", pixelfmt); + return true; + default: + Console.WriteLine("Unrecognized pixel format: {0}", (int)pixelfmt); + return false; + } + } + case LibRetro.RETRO_ENVIRONMENT.SET_INPUT_DESCRIPTORS: + return false; + case LibRetro.RETRO_ENVIRONMENT.SET_KEYBOARD_CALLBACK: + return false; + case LibRetro.RETRO_ENVIRONMENT.SET_DISK_CONTROL_INTERFACE: + return false; + case LibRetro.RETRO_ENVIRONMENT.SET_HW_RENDER: + { + //mupen64plus needs this, as well as 3dengine + LibRetro.retro_hw_render_callback* info = (LibRetro.retro_hw_render_callback*)data.ToPointer(); + Console.WriteLine("SET_HW_RENDER: {0}, version={1}.{2}, dbg/cache={3}/{4}, depth/stencil = {5}/{6}{7}", info->context_type, info->version_minor, info->version_major, info->debug_context, info->cache_context, info->depth, info->stencil, info->bottom_left_origin ? " (bottomleft)" : ""); + return true; + } + case LibRetro.RETRO_ENVIRONMENT.GET_VARIABLE: + { + void** variables = (void**)data.ToPointer(); + IntPtr pKey = new IntPtr(*variables++); + string key = Marshal.PtrToStringAnsi(pKey); + Console.WriteLine("Requesting variable: {0}", key); + //always return default + //TODO: cache settings atoms + if(!Description.Variables.ContainsKey(key)) + return false; + //HACK: return pointer for desmume mouse, i want to implement that first + if (key == "desmume_pointer_type") + { + *variables = unmanagedResources.StringToHGlobalAnsi("touch").ToPointer(); + return true; + } + *variables = unmanagedResources.StringToHGlobalAnsi(Description.Variables[key].DefaultOption).ToPointer(); + return true; + } + case LibRetro.RETRO_ENVIRONMENT.SET_VARIABLES: + { + void** variables = (void**)data.ToPointer(); + for (; ; ) + { + IntPtr pKey = new IntPtr(*variables++); + IntPtr pValue = new IntPtr(*variables++); + if(pKey == IntPtr.Zero) + break; + string key = Marshal.PtrToStringAnsi(pKey); + string value = Marshal.PtrToStringAnsi(pValue); + var vd = new VariableDescription() { Name = key}; + var parts = value.Split(';'); + vd.Description = parts[0]; + vd.Options = parts[1].TrimStart(' ').Split('|'); + Description.Variables[vd.Name] = vd; + } + } + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_VARIABLE_UPDATE: + return false; + case LibRetro.RETRO_ENVIRONMENT.SET_SUPPORT_NO_GAME: + environmentInfo.SupportNoGame = true; + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_LIBRETRO_PATH: + return false; + case LibRetro.RETRO_ENVIRONMENT.SET_AUDIO_CALLBACK: + return false; + case LibRetro.RETRO_ENVIRONMENT.SET_FRAME_TIME_CALLBACK: + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_RUMBLE_INTERFACE: + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_INPUT_DEVICE_CAPABILITIES: + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_LOG_INTERFACE: + *(IntPtr*)data = Marshal.GetFunctionPointerForDelegate(retro_log_printf_cb); + return true; + case LibRetro.RETRO_ENVIRONMENT.GET_PERF_INTERFACE: + //some builds of fmsx core crash without this set + Marshal.StructureToPtr(retro_perf_callback, data, false); + return true; + case LibRetro.RETRO_ENVIRONMENT.GET_LOCATION_INTERFACE: + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_CORE_ASSETS_DIRECTORY: + return false; + case LibRetro.RETRO_ENVIRONMENT.GET_SAVE_DIRECTORY: + //supposedly optional like everything else here, but without it ?? crashes (please write which case) + //this will suffice for now. if we find evidence later it's needed we can stash a string with + //unmanagedResources and CoreFileProvider + //mednafen NGP neopop, desmume, and others, request this, and falls back on the system directory if it isn't provided + //desmume crashes if the directory doesn't exist + Directory.CreateDirectory(SaveDirectory); + Console.WriteLine("returning save directory: " + SaveDirectory); + *((IntPtr*)data.ToPointer()) = SaveDirectoryAtom; + return true; + case LibRetro.RETRO_ENVIRONMENT.SET_CONTROLLER_INFO: + return true; + case LibRetro.RETRO_ENVIRONMENT.SET_MEMORY_MAPS: + return false; + default: + Console.WriteLine("Unknkown retro_environment command {0}", (int)cmd); + return false; + } + } + void retro_input_poll() + { + IsLagFrame = false; + } + + private bool GetButton(uint pnum, string type, string button) + { + string key = string.Format("P{0} {1} {2}", pnum, type, button); + bool b = Controller[key]; + if (b == true) + { + return true; //debugging placeholder + } + else return false; + } + + LibRetro.retro_environment_t retro_environment_cb; + LibRetro.retro_video_refresh_t retro_video_refresh_cb; + LibRetro.retro_audio_sample_t retro_audio_sample_cb; + LibRetro.retro_audio_sample_batch_t retro_audio_sample_batch_cb; + LibRetro.retro_input_poll_t retro_input_poll_cb; + LibRetro.retro_input_state_t retro_input_state_cb; + LibRetro.retro_log_printf_t retro_log_printf_cb; + + LibRetro.retro_perf_callback retro_perf_callback = new LibRetro.retro_perf_callback(); + + #endregion + + class RetroEnvironmentInfo + { + public bool SupportNoGame; + } + + //disposable resources + private LibRetro retro; + private UnmanagedResourceHeap unmanagedResources = new UnmanagedResourceHeap(); + + /// + /// Cached information sent to the frontend by environment calls + /// + RetroEnvironmentInfo environmentInfo = new RetroEnvironmentInfo(); + + public class RetroDescription + { + /// + /// String containing a friendly display name for the core, but we probably shouldn't use this. I decided it's better to get the user used to using filenames as core 'codenames' instead. + /// + public string LibraryName; + + /// + /// String containing a friendly version number for the core library + /// + public string LibraryVersion; + + /// + /// List of extensions as "sfc|smc|fig" which this core accepts. + /// + public string ValidExtensions; + + /// + /// Whether the core needs roms to be specified as paths (can't take rom data buffersS) + /// + public bool NeedsRomAsPath; + + /// + /// Whether the core needs roms stored as archives (e.g. arcade roms). We probably shouldn't employ the dearchiver prompts when opening roms for these cores. + /// + public bool NeedsArchives; + + /// + /// Whether the core can be run without a game provided (e.g. stand-alone games, like 2048) + /// + public bool SupportsNoGame; + + /// + /// Variables defined by the core + /// + public Dictionary Variables = new Dictionary(); + } + + public class VariableDescription + { + public string Name; + public string Description; + public string[] Options; + public string DefaultOption { get { return Options[0]; } } + + public override string ToString() + { + return string.Format("{0} ({1}) = ({2})", Name, Description, string.Join("|", Options)); + } + } + + public readonly RetroDescription Description = new RetroDescription(); + + //path configuration + string SystemDirectory, SaveDirectory; + IntPtr SystemDirectoryAtom, SaveDirectoryAtom; + + public LibRetroEmulator(CoreComm nextComm, string modulename) + { + ServiceProvider = new BasicServiceProvider(this); + + _SyncSettings = new SyncSettings(); + + retro_environment_cb = new LibRetro.retro_environment_t(retro_environment); + retro_video_refresh_cb = new LibRetro.retro_video_refresh_t(retro_video_refresh); + retro_audio_sample_cb = new LibRetro.retro_audio_sample_t(retro_audio_sample); + retro_audio_sample_batch_cb = new LibRetro.retro_audio_sample_batch_t(retro_audio_sample_batch); + retro_input_poll_cb = new LibRetro.retro_input_poll_t(retro_input_poll); + retro_input_state_cb = new LibRetro.retro_input_state_t(retro_input_state); + retro_log_printf_cb = new LibRetro.retro_log_printf_t(retro_log_printf); + + //no way (need new mechanism) to check for SSSE3, MMXEXT, SSE4, SSE42 + retro_perf_callback.get_cpu_features = new LibRetro.retro_get_cpu_features_t(() => (ulong)( + (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsXMMIAvailable) ? LibRetro.RETRO_SIMD.SSE : 0) | + (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsXMMI64Available) ? LibRetro.RETRO_SIMD.SSE2 : 0) | + (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsSSE3Available) ? LibRetro.RETRO_SIMD.SSE3 : 0) | + (Win32PInvokes.IsProcessorFeaturePresent(Win32PInvokes.ProcessorFeature.InstructionsMMXAvailable) ? LibRetro.RETRO_SIMD.MMX : 0) + ) ); + retro_perf_callback.get_perf_counter = new LibRetro.retro_perf_get_counter_t(() => System.Diagnostics.Stopwatch.GetTimestamp()); + retro_perf_callback.get_time_usec = new LibRetro.retro_perf_get_time_usec_t(() => DateTime.Now.Ticks / 10); + retro_perf_callback.perf_log = new LibRetro.retro_perf_log_t( () => {} ); + retro_perf_callback.perf_register = new LibRetro.retro_perf_register_t((ref LibRetro.retro_perf_counter counter) => { }); + retro_perf_callback.perf_start = new LibRetro.retro_perf_start_t((ref LibRetro.retro_perf_counter counter) => { }); + retro_perf_callback.perf_stop = new LibRetro.retro_perf_stop_t((ref LibRetro.retro_perf_counter counter) => { }); + + retro = new LibRetro(modulename); + + try + { + CoreComm = nextComm; + + //this series of steps may be mystical. + LibRetro.retro_system_info system_info = new LibRetro.retro_system_info(); + retro.retro_get_system_info(ref system_info); + + //the dosbox core calls GET_SYSTEM_DIRECTORY and GET_SAVE_DIRECTORY from retro_set_environment. + //so, lets set some temporary values (which we'll replace) + SystemDirectory = Path.GetDirectoryName(modulename); + SystemDirectoryAtom = unmanagedResources.StringToHGlobalAnsi(SystemDirectory); + SaveDirectory = Path.GetDirectoryName(modulename); + SaveDirectoryAtom = unmanagedResources.StringToHGlobalAnsi(SaveDirectory); + retro.retro_set_environment(retro_environment_cb); + + retro.retro_set_video_refresh(retro_video_refresh_cb); + retro.retro_set_audio_sample(retro_audio_sample_cb); + retro.retro_set_audio_sample_batch(retro_audio_sample_batch_cb); + retro.retro_set_input_poll(retro_input_poll_cb); + retro.retro_set_input_state(retro_input_state_cb); + + //compile descriptive information + Description.NeedsArchives = system_info.block_extract; + Description.NeedsRomAsPath = system_info.need_fullpath; + Description.LibraryName = system_info.library_name; + Description.LibraryVersion = system_info.library_version; + Description.ValidExtensions = system_info.valid_extensions; + Description.SupportsNoGame = environmentInfo.SupportNoGame; + } + catch + { + retro.Dispose(); + retro = null; + throw; + } + + + } + + public IEmulatorServiceProvider ServiceProvider { get; private set; } + + + + public bool LoadData(byte[] data) + { + LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); + fixed (byte* p = &data[0]) + { + gi.data = (IntPtr)p; + gi.meta = ""; + gi.path = ""; + gi.size = (uint)data.Length; + return LoadWork(ref gi); + } + } + + public bool LoadPath(string path) + { + LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); + gi.path = path; //is this the right encoding? seems to be ok + return LoadWork(ref gi); + } + + public bool LoadNoGame() + { + LibRetro.retro_game_info gi = new LibRetro.retro_game_info(); + return LoadWork(ref gi); + } + + bool LoadWork(ref LibRetro.retro_game_info gi) + { + //defer this until loading because during the LibRetroEmulator constructor, we dont have access to the game name and so paths can't be selected + //this cannot be done until set_environment is complete + if (CoreComm.CoreFileProvider == null) + { + SaveDirectory = SystemDirectory = ""; + } + else + { + SystemDirectory = CoreComm.CoreFileProvider.GetRetroSystemPath(); + SaveDirectory = CoreComm.CoreFileProvider.GetRetroSaveRAMDirectory(); + SystemDirectoryAtom = unmanagedResources.StringToHGlobalAnsi(SystemDirectory); + SaveDirectoryAtom = unmanagedResources.StringToHGlobalAnsi(SaveDirectory); + } + + //defer this until loading because it triggers the core to read save and system paths + //if any cores did that from set_environment then i'm assured we can call set_environment again here before retro_init and it should work + //--alcaro says any cores that can't handle that should be considered a bug + //UPDATE: dosbox does that, so lets try it + retro.retro_set_environment(retro_environment_cb); + retro.retro_init(); + + if (!retro.retro_load_game(ref gi)) + { + Console.WriteLine("retro_load_game() failed"); + return false; + } + + //TODO - libretro cores can return a varying serialize size over time. I tried to get them to write it in the docs... + savebuff = new byte[retro.retro_serialize_size()]; + savebuff2 = new byte[savebuff.Length + 13]; + + LibRetro.retro_system_av_info av = new LibRetro.retro_system_av_info(); + retro.retro_get_system_av_info(ref av); + + BufferWidth = (int)av.geometry.base_width; + BufferHeight = (int)av.geometry.base_height; + vidbuff = new int[av.geometry.max_width * av.geometry.max_height]; + dar = av.geometry.aspect_ratio; + + // TODO: more precise + CoreComm.VsyncNum = (int)(10000000 * av.timing.fps); + CoreComm.VsyncDen = 10000000; + + SetupResampler(av.timing.fps, av.timing.sample_rate); + + ControllerDefinition = CreateControllerDefinition(_SyncSettings); + + return true; + } + + public static ControllerDefinition CreateControllerDefinition(SyncSettings syncSettings) + { + ControllerDefinition definition = new ControllerDefinition(); + definition.Name = "LibRetro Controls"; // <-- for compatibility + + foreach(var item in new[] { + "P1 {0} Up", "P1 {0} Down", "P1 {0} Left", "P1 {0} Right", "P1 {0} Select", "P1 {0} Start", "P1 {0} Y", "P1 {0} B", "P1 {0} X", "P1 {0} A", "P1 {0} L", "P1 {0} R", + "P2 {0} Up", "P2 {0} Down", "P2 {0} Left", "P2 {0} Right", "P2 {0} Select", "P2 {0} Start", "P2 {0} Y", "P2 {0} B", "P2 {0} X", "P2 {0} A", "P2 {0} L", "P2 {0} R", + }) + definition.BoolButtons.Add(string.Format(item,"RetroPad")); + + definition.BoolButtons.Add("Pointer Pressed"); //TODO: this isnt showing up in the binding panel. I dont want to find out why. + definition.FloatControls.Add("Pointer X"); + definition.FloatControls.Add("Pointer Y"); + definition.FloatRanges.Add(new ControllerDefinition.FloatRange(-32767, 0, 32767)); + definition.FloatRanges.Add(new ControllerDefinition.FloatRange(-32767, 0, 32767)); + + foreach (var key in new[]{ + "Key Backspace", "Key Tab", "Key Clear", "Key Return", "Key Pause", "Key Escape", + "Key Space", "Key Exclaim", "Key QuoteDbl", "Key Hash", "Key Dollar", "Key Ampersand", "Key Quote", "Key LeftParen", "Key RightParen", "Key Asterisk", "Key Plus", "Key Comma", "Key Minus", "Key Period", "Key Slash", + "Key 0", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", + "Key Colon", "Key Semicolon", "Key Less", "Key Equals", "Key Greater", "Key Question", "Key At", "Key LeftBracket", "Key Backslash", "Key RightBracket", "Key Caret", "Key Underscore", "Key Backquote", + "Key A", "Key B", "Key C", "Key D", "Key E", "Key F", "Key G", "Key H", "Key I", "Key J", "Key K", "Key L", "Key M", "Key N", "Key O", "Key P", "Key Q", "Key R", "Key S", "Key T", "Key U", "Key V", "Key W", "Key X", "Key Y", "Key Z", + "Key Delete", + "Key KP0", "Key KP1", "Key KP2", "Key KP3", "Key KP4", "Key KP5", "Key KP6", "Key KP7", "Key KP8", "Key KP9", + "Key KP_Period", "Key KP_Divide", "Key KP_Multiply", "Key KP_Minus", "Key KP_Plus", "Key KP_Enter", "Key KP_Equals", + "Key Up", "Key Down", "Key Right", "Key Left", "Key Insert", "Key Home", "Key End", "Key PageUp", "Key PageDown", + "Key F1", "Key F2", "Key F3", "Key F4", "Key F5", "Key F6", "Key F7", "Key F8", "Key F9", "Key F10", "Key F11", "Key F12", "Key F13", "Key F14", "Key F15", + "Key NumLock", "Key CapsLock", "Key ScrollLock", "Key RShift", "Key LShift", "Key RCtrl", "Key LCtrl", "Key RAlt", "Key LAlt", "Key RMeta", "Key LMeta", "Key LSuper", "Key RSuper", "Key Mode", "Key Compose", + "Key Help", "Key Print", "Key SysReq", "Key Break", "Key Menu", "Key Power", "Key Euro", "Key Undo" + }) + { + definition.BoolButtons.Add(key); + definition.CategoryLabels[key] = "RetroKeyboard"; + } + + return definition; + } + + public ControllerDefinition ControllerDefinition { get; private set; } + public IController Controller { get; set; } + + public void FrameAdvance(bool render, bool rendersound = true) + { + //TODO - consider changing directory and using Libretro subdir of bizhawk as a kind of sandbox, for the duration of the run? + IsLagFrame = true; + Frame++; + nsamprecv = 0; + retro.retro_run(); + //Console.WriteLine("[{0}]", nsamprecv); + } + + public int Frame { get; private set; } + + public string SystemId + { + get { return "Libretro"; } + } + + public bool DeterministicEmulation + { + // who knows + get { return true; } + } + + public string BoardName + { + get { return null; } + } + + #region ISaveRam + //TODO - terrible things will happen if this changes at runtime + + byte[] saverambuff = new byte[0]; + + public byte[] CloneSaveRam() + { + int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); + if (saverambuff.Length != size) + saverambuff = new byte[size]; + + IntPtr src = retro.retro_get_memory_data(LibRetro.RETRO_MEMORY.SAVE_RAM); + if (src == IntPtr.Zero) + return null; + + Marshal.Copy(src, saverambuff, 0, size); + return (byte[])saverambuff.Clone(); + } + + public void StoreSaveRam(byte[] data) + { + int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); + + if (size == 0) + return; + + IntPtr dst = retro.retro_get_memory_data(LibRetro.RETRO_MEMORY.SAVE_RAM); + if (dst == IntPtr.Zero) + throw new Exception("retro_get_memory_data(RETRO_MEMORY_SAVE_RAM) returned NULL"); + + Marshal.Copy(data, 0, dst, size); + } + + public bool SaveRamModified + { + [FeatureNotImplemented] + get + { + //if we dont have saveram, it isnt modified. otherwise, assume it is + int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM); + if (size == 0) + return false; + return true; + } + + [FeatureNotImplemented] + set { throw new NotImplementedException(); } + } + + #endregion + + public void ResetCounters() + { + Frame = 0; + LagCount = 0; + IsLagFrame = false; + } + + #region savestates + + private byte[] savebuff; + private byte[] savebuff2; + + public void SaveStateText(System.IO.TextWriter writer) + { + var temp = SaveStateBinary(); + temp.SaveAsHex(writer); + } + + public void LoadStateText(System.IO.TextReader reader) + { + string hex = reader.ReadLine(); + byte[] state = new byte[hex.Length / 2]; + state.ReadFromHex(hex); + LoadStateBinary(new BinaryReader(new MemoryStream(state))); + } + + public void SaveStateBinary(System.IO.BinaryWriter writer) + { + //is this the only way we know of to detect unavailable savestates? + if (savebuff.Length > 0) + { + fixed (byte* ptr = &savebuff[0]) + { + if (!retro.retro_serialize((IntPtr)ptr, (uint)savebuff.Length)) + throw new Exception("retro_serialize() failed"); + } + } + + writer.Write(savebuff.Length); + writer.Write(savebuff); + // other variables + writer.Write(Frame); + writer.Write(LagCount); + writer.Write(IsLagFrame); + } + + public void LoadStateBinary(System.IO.BinaryReader reader) + { + int newlen = reader.ReadInt32(); + if (newlen > savebuff.Length) + throw new Exception("Unexpected buffer size"); + reader.Read(savebuff, 0, newlen); + if (savebuff.Length > 0) + { + fixed (byte* ptr = &savebuff[0]) + { + if (!retro.retro_unserialize((IntPtr)ptr, (uint)newlen)) + throw new Exception("retro_unserialize() failed"); + } + } + // other variables + Frame = reader.ReadInt32(); + LagCount = reader.ReadInt32(); + IsLagFrame = reader.ReadBoolean(); + } + + public byte[] SaveStateBinary() + { + var ms = new System.IO.MemoryStream(savebuff2, true); + var bw = new System.IO.BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + ms.Close(); + return savebuff2; + } + + public bool BinarySaveStatesPreferred { get { return true; } } + + #endregion + + public CoreComm CoreComm + { + get; + private set; + } + + #region memory access + + void SetupDebuggingStuff() + { + } + + public MemoryDomainList MemoryDomains { get; private set; } + + #endregion + + public void Dispose() + { + if (resampler != null) + { + resampler.Dispose(); + resampler = null; + } + if (retro != null) + { + retro.Dispose(); + retro = null; + } + unmanagedResources.Dispose(); + unmanagedResources = null; + } + + #region ISoundProvider + + public ISoundProvider SoundProvider { get { return null; } } + public ISyncSoundProvider SyncSoundProvider { get { return resampler; } } + public bool StartAsyncSound() { return false; } + public void EndAsyncSound() { } + + SpeexResampler resampler; + + short[] sampbuff = new short[0]; + + // debug + int nsamprecv = 0; + + void SetupResampler(double fps, double sps) + { + Console.WriteLine("FPS {0} SPS {1}", fps, sps); + + // todo: more precise? + uint spsnum = (uint)sps * 1000; + uint spsden = (uint)1000; + + resampler = new SpeexResampler(5, 44100 * spsden, spsnum, (uint)sps, 44100, null, null); + } + + void retro_audio_sample(short left, short right) + { + resampler.EnqueueSample(left, right); + nsamprecv++; + } + + uint retro_audio_sample_batch(IntPtr data, uint frames) + { + if (sampbuff.Length < frames * 2) + sampbuff = new short[frames * 2]; + Marshal.Copy(data, sampbuff, 0, (int)(frames * 2)); + resampler.EnqueueSamples(sampbuff, (int)frames); + nsamprecv += (int)frames; + // what is the return from this used for? + return frames; + } + + #endregion + + #region IVideoProvider + + float dar; + int[] vidbuff; + LibRetro.RETRO_PIXEL_FORMAT pixelfmt = LibRetro.RETRO_PIXEL_FORMAT.XRGB1555; + + void Blit555(short* src, int* dst, int width, int height, int pitch) + { + for (int j = 0; j < height; j++) + { + short* row = src; + for (int i = 0; i < width; i++) + { + short ci = *row; + int r = ci & 0x001f; + int g = ci & 0x03e0; + int b = ci & 0x7c00; + + r = (r << 3) | (r >> 2); + g = (g >> 2) | (g >> 7); + b = (b >> 7) | (b >> 12); + int co = r | g | b | unchecked((int)0xff000000); + + *dst = co; + dst++; + row++; + } + src += pitch; + } + } + + void Blit565(short* src, int* dst, int width, int height, int pitch) + { + for (int j = 0; j < height; j++) + { + short* row = src; + for (int i = 0; i < width; i++) + { + short ci = *row; + int r = ci & 0x001f; + int g = (ci & 0x07e0)>>5; + int b = (ci & 0xf800)>>11; + + r = (r << 3) | (r >> 2); + g = (g << 2) | (g >> 4); + b = (b << 3) | (b >> 2); + int co = (b<<16) | (g<<8) | r; + + *dst = co; + dst++; + row++; + } + src += pitch; + } + } + + void Blit888(int* src, int* dst, int width, int height, int pitch) + { + for (int j = 0; j < height; j++) + { + int* row = src; + for (int i = 0; i < width; i++) + { + int ci = *row; + int co = ci | unchecked((int)0xff000000); + *dst = co; + dst++; + row++; + } + src += pitch; + } + } + + void retro_video_refresh(IntPtr data, uint width, uint height, uint pitch) + { + if (data == IntPtr.Zero) // dup frame + return; + if (width * height > vidbuff.Length) + { + Console.WriteLine("Unexpected libretro video buffer overrun?"); + return; + } + fixed (int* dst = &vidbuff[0]) + { + if (pixelfmt == LibRetro.RETRO_PIXEL_FORMAT.XRGB8888) + Blit888((int*)data, dst, (int)width, (int)height, (int)pitch / 4); + else if (pixelfmt == LibRetro.RETRO_PIXEL_FORMAT.RGB565) + Blit565((short*)data, dst, (int)width, (int)height, (int)pitch / 2); + else + Blit555((short*)data, dst, (int)width, (int)height, (int)pitch / 2); + } + } + + + public int[] GetVideoBuffer() + { + return vidbuff; + } + + public int VirtualWidth + { + get + { + if(dar<=0) + return BufferWidth; + else if (dar > 1.0f) + return (int)(BufferHeight * dar); + else + return BufferWidth; + } + } + public int VirtualHeight + { + get + { + if(dar<=0) + return BufferHeight; + if (dar < 1.0f) + return (int)(BufferWidth / dar); + else + return BufferHeight; + } + } + + public int BufferWidth { get; private set; } + public int BufferHeight { get; private set; } + public int BackgroundColor { get { return unchecked((int)0xff000000); } } + + #endregion + + #region IInputPollable + public int LagCount { get; set; } + public bool IsLagFrame { get; private set; } + public IInputCallbackSystem InputCallbacks + { + [FeatureNotImplemented] + get + { throw new NotImplementedException(); } + } + #endregion + } +} diff --git a/Dist/changelog.txt b/Dist/changelog.txt index c9ddda8ddb..6f74db9a89 100644 --- a/Dist/changelog.txt +++ b/Dist/changelog.txt @@ -3,8 +3,10 @@ next ========================================= *EmuHawk +**Add libretro player, compatible with selected cores +**Support Code-Data Logger for GB/GBC, SMS/GG, SNES, and Genesis +**Add GameShark cheat converter **Add custom exception display box, so exception info can be clipboarded out -**Support Code-Data Logger for GB/GBC, SMS/GG, and Genesis **Cheat Dialog: Fix flakiness in value-editing **Stop FP precision conflicts between lua scripts and D3D Display method **DispMethod D3D: More leniency in compilation of optional shaders (so it's able to run on more low spec systems) @@ -12,8 +14,13 @@ next **Validate user shaders at selection time **Support user custom AR selection **Add --load-state commandline +**Streamline editing RAM Watches +**Tidy main form context menu +**Add more options for U+D/L+R forbid/mutex **Fix #525 - Memorywatch hex textbox now remembers values across memdomain switches **Fix #526 - Hex editor repainting fails and garbage rendering +**Fix #535 - domain list does not update when changing cores +**Fix #537 - Annoyance with "always on top" **Tastudio (TODO - editorialize this section) ***color stated frames on WasLag too. @@ -33,6 +40,8 @@ next **Lua **Fix gameExtraPadding coordinate translation **Clarify script pause/stop state in UI and logic +**Fix forms.destroyall() and call it when lua console closes +**Fix error in sizing of lua draw buffers with SetGameExtraPadding (and probably ClientExtraPadding) use *PSX **Fix #530 "AV Resizing shows black screen with PSX" @@ -40,6 +49,7 @@ next *SNES **Fix crashes in GFX debugger (including fix #529) **Recommend proper SNES PAR +**Build dlls without msvcrt14 dependencies (to run on more systems) *Genesis **Fix missing scrollbars in VDP viewer diff --git a/libsnes/vs2015/libsnes.vcxproj b/libsnes/vs2015/libsnes.vcxproj index 668837a1fe..75f893b5a6 100644 --- a/libsnes/vs2015/libsnes.vcxproj +++ b/libsnes/vs2015/libsnes.vcxproj @@ -81,12 +81,12 @@ libsneshawk-32-compatibility - ..\..\output\dll + ..\..\output\dll\ .obj\$(Configuration)\ libsneshawk-32-performance - ..\..\output\dll + ..\..\output\dll\ .obj\$(Configuration)\ @@ -135,6 +135,7 @@ true StreamingSIMDExtensions Fast + MultiThreaded true @@ -146,7 +147,7 @@ Level3 - MaxSpeed + Full true true false @@ -154,6 +155,10 @@ $(ProjectDir)../bsnes HOOKS;BIZHAWK;PROFILE_COMPATIBILITY;GAMEBOY;%(PreprocessorDefinitions) None + MultiThreaded + Speed + true + AnySuitable true @@ -318,6 +323,7 @@ + true true @@ -709,7 +715,6 @@ - diff --git a/libsnes/vs2015/libsnes.vcxproj.filters b/libsnes/vs2015/libsnes.vcxproj.filters index b60aa69a48..58dd9fbf95 100644 --- a/libsnes/vs2015/libsnes.vcxproj.filters +++ b/libsnes/vs2015/libsnes.vcxproj.filters @@ -139,9 +139,6 @@ {1d1cf6c9-9e1b-402c-b1cc-7ed8866092e7} - - {d2cbafd7-caaf-404c-9fdb-4adaaf5c1687} - {be676f5c-dcb3-4a39-911c-4e102dfa25d8} @@ -166,6 +163,9 @@ {a2db2d0e-68ec-4fa0-91fc-2bcc8ac33d32} + + {70659d45-e958-48ff-94a3-8a57aa76dd61} + @@ -392,9 +392,6 @@ target-libsnes - - libco_msvc_win32 - snes\cpu\core @@ -569,6 +566,9 @@ snes\dsp + + libco + diff --git a/output/defctrl.json b/output/defctrl.json index f52ba215ab..9177d35e7f 100644 --- a/output/defctrl.json +++ b/output/defctrl.json @@ -26,6 +26,141 @@ "P2 RetroPad L": "", "P2 RetroPad R": "", "Pointer Pressed": "WMouse L", + "Key Backspace": "Backspace", + "Key Tab": "Tab", + "Key Clear": "", + "Key Return": "Return", + "Key Pause": "", + "Key Escape": "Escape", + "Key Space": "Space", + "Key Exclaim": "", + "Key QuoteDbl": "", + "Key Hash": "", + "Key Dollar": "", + "Key Ampersand": "", + "Key Quote": "", + "Key LeftParen": "", + "Key RightParen": "", + "Key Asterisk": "", + "Key Plus": "", + "Key Comma": "Comma", + "Key Minus": "Minus", + "Key Period": "Period", + "Key Slash": "Slash", + "Key 0": "D0", + "Key 1": "D1", + "Key 2": "D2", + "Key 3": "D3", + "Key 4": "D4", + "Key 5": "D5", + "Key 6": "D6", + "Key 7": "D7", + "Key 8": "D8", + "Key 9": "D9", + "Key Colon": "", + "Key Semicolon": "Semicolon", + "Key Less": "", + "Key Equals": "Equals", + "Key Greater": "", + "Key Question": "", + "Key At": "", + "Key LeftBracket": "LeftBracket", + "Key Backslash": "Backslash", + "Key RightBracket": "RightBracket", + "Key Caret": "", + "Key Underscore": "", + "Key Backquote": "Grave", + "Key A": "A", + "Key B": "B", + "Key C": "C", + "Key D": "D", + "Key E": "E", + "Key F": "F", + "Key G": "G", + "Key H": "H", + "Key I": "I", + "Key J": "J", + "Key K": "K", + "Key L": "L", + "Key M": "M", + "Key N": "N", + "Key O": "O", + "Key P": "P", + "Key Q": "Q", + "Key R": "R", + "Key S": "S", + "Key T": "T", + "Key U": "U", + "Key V": "V", + "Key W": "W", + "Key X": "X", + "Key Y": "Y", + "Key Z": "Z", + "Key Delete": "Delete", + "Key KP0": "NumberPad0", + "Key KP1": "NumberPad1", + "Key KP2": "NumberPad2", + "Key KP3": "NumberPad3", + "Key KP4": "NumberPad4", + "Key KP5": "NumberPad5", + "Key KP6": "NumberPad6", + "Key KP7": "NumberPad7", + "Key KP8": "NumberPad8", + "Key KP9": "NumberPad9", + "Key KP_Period": "NumberPadPeriod", + "Key KP_Divide": "NumberPadSlash", + "Key KP_Multiply": "NumberPadStar", + "Key KP_Minus": "NumberPadMinus", + "Key KP_Plus": "NumberPadPlus", + "Key KP_Enter": "NumberPadEnter", + "Key KP_Equals": "", + "Key Up": "UpArrow", + "Key Down": "DownArrow", + "Key Left": "LeftArrow", + "Key Insert": "LeftControl", + "Key Home": "Home", + "Key End": "End", + "Key PageUp": "PageUp", + "Key PageDown": "PageDown", + "Key F1": "F1", + "Key F2": "F2", + "Key F3": "F3", + "Key F4": "F4", + "Key F5": "F5", + "Key F6": "F6", + "Key F7": "F7", + "Key F8": "F8", + "Key F9": "F9", + "Key F10": "F10", + "Key F11": "F11", + "Key F12": "F12", + "Key F13": "", + "Key F14": "", + "Key F15": "", + "Key NumLock": "NumberLock", + "Key CapsLock": "CapsLock", + "Key ScrollLock": "ScrollLock", + "Key RShift": "RightShift", + "Key LShift": "LeftShift", + "Key RCtrl": "RightControl", + "Key LCtrl": "LeftControl", + "Key RAlt": "RightAlt", + "Key LAlt": "LeftAlt", + "Key RMeta": "", + "Key LMeta": "", + "Key LSuper": "", + "Key RSuper": "", + "Key Mode": "", + "Key Compose": "", + "Key Help": "", + "Key Print": "", + "Key SysReq": "", + "Key Break": "", + "Key Menu": "Applications", + "Key Power": "", + "Key Euro": "", + "Key Undo": "", + "Key Right": "RightArrow" }, "NES Controller": { "P1 Up": "UpArrow, J1 POV1U, X1 DpadUp, X1 LStickUp", diff --git a/output/dll/libsneshawk-32-compatibility.dll b/output/dll/libsneshawk-32-compatibility.dll index 2f41379eb2..b8e06af9bd 100644 Binary files a/output/dll/libsneshawk-32-compatibility.dll and b/output/dll/libsneshawk-32-compatibility.dll differ diff --git a/output/dll/libsneshawk-32-performance.dll b/output/dll/libsneshawk-32-performance.dll index 4effa733ba..559da54ce0 100644 Binary files a/output/dll/libsneshawk-32-performance.dll and b/output/dll/libsneshawk-32-performance.dll differ