From 53fcc09c0820a43767d50efed6a3a2cf654e36d1 Mon Sep 17 00:00:00 2001 From: zeromus Date: Sun, 8 Nov 2015 19:18:08 -0600 Subject: [PATCH] various bugfixes to system/save pathing and support CAN_DUPE, to stabilize the gambatte and neopop cores --- BizHawk.Client.Common/CoreFileProvider.cs | 11 +++ BizHawk.Client.Common/PathManager.cs | 32 +++++++ BizHawk.Client.Common/RomLoader.cs | 43 ++++++---- BizHawk.Client.Common/config/PathEntry.cs | 9 +- BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs | 5 +- .../Interfaces/ICoreFileProvider.cs | 10 ++- BizHawk.Emulation.Cores/LibRetro.cs | 83 +++++++++++++++++++ BizHawk.Emulation.Cores/LibRetroEmulator.cs | 62 +++++++++----- 8 files changed, 208 insertions(+), 47 deletions(-) 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/PathEntry.cs b/BizHawk.Client.Common/config/PathEntry.cs index 2f814d4b32..3efb1eba33 100644 --- a/BizHawk.Client.Common/config/PathEntry.cs +++ b/BizHawk.Client.Common/config/PathEntry.cs @@ -314,10 +314,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.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.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/LibRetro.cs b/BizHawk.Emulation.Cores/LibRetro.cs index 5bdcfca37a..44e02479e4 100644 --- a/BizHawk.Emulation.Cores/LibRetro.cs +++ b/BizHawk.Emulation.Cores/LibRetro.cs @@ -337,6 +337,89 @@ namespace BizHawk.Emulation.Cores EXPERIMENTAL = 0x10000 }; + struct retro_memory_map + { + public IntPtr descriptors; //retro_memory_descriptor * + public uint num_descriptors; + }; + + struct retro_memory_descriptor + { + ulong flags; + + /* Pointer to the start of the relevant ROM or RAM chip. + * It's strongly recommended to use 'offset' if possible, rather than + * doing math on the pointer. + * + * If the same byte is mapped my multiple descriptors, their descriptors + * must have the same pointer. + * If 'start' does not point to the first byte in the pointer, put the + * difference in 'offset' instead. + * + * May be NULL if there's nothing usable here (e.g. hardware registers and + * open bus). No flags should be set if the pointer is NULL. + * It's recommended to minimize the number of descriptors if possible, + * but not mandatory. */ + IntPtr ptr; + IntPtr offset; //size_t + + /* This is the location in the emulated address space + * where the mapping starts. */ + IntPtr start; //size_t + + /* Which bits must be same as in 'start' for this mapping to apply. + * The first memory descriptor to claim a certain byte is the one + * that applies. + * A bit which is set in 'start' must also be set in this. + * Can be zero, in which case each byte is assumed mapped exactly once. + * In this case, 'len' must be a power of two. */ + IntPtr select; //size_t + + /* If this is nonzero, the set bits are assumed not connected to the + * memory chip's address pins. */ + IntPtr disconnect; //size_t + + /* This one tells the size of the current memory area. + * If, after start+disconnect are applied, the address is higher than + * this, the highest bit of the address is cleared. + * + * If the address is still too high, the next highest bit is cleared. + * Can be zero, in which case it's assumed to be infinite (as limited + * by 'select' and 'disconnect'). */ + IntPtr len; //size_t + + /* To go from emulated address to physical address, the following + * order applies: + * Subtract 'start', pick off 'disconnect', apply 'len', add 'offset'. + * + * The address space name must consist of only a-zA-Z0-9_-, + * should be as short as feasible (maximum length is 8 plus the NUL), + * and may not be any other address space plus one or more 0-9A-F + * at the end. + * However, multiple memory descriptors for the same address space is + * allowed, and the address space name can be empty. NULL is treated + * as empty. + * + * Address space names are case sensitive, but avoid lowercase if possible. + * The same pointer may exist in multiple address spaces. + * + * Examples: + * blank+blank - valid (multiple things may be mapped in the same namespace) + * 'Sp'+'Sp' - valid (multiple things may be mapped in the same namespace) + * 'A'+'B' - valid (neither is a prefix of each other) + * 'S'+blank - valid ('S' is not in 0-9A-F) + * 'a'+blank - valid ('a' is not in 0-9A-F) + * 'a'+'A' - valid (neither is a prefix of each other) + * 'AR'+blank - valid ('R' is not in 0-9A-F) + * 'ARB'+blank - valid (the B can't be part of the address either, because + * there is no namespace 'AR') + * blank+'B' - not valid, because it's ambigous which address space B1234 + * would refer to. + * The length can't be used for that purpose; the frontend may want + * to append arbitrary data to an address, without a separator. */ + string addrspace; + }; + public enum RETRO_PIXEL_FORMAT { XRGB1555 = 0, diff --git a/BizHawk.Emulation.Cores/LibRetroEmulator.cs b/BizHawk.Emulation.Cores/LibRetroEmulator.cs index 3946fdc706..d78fe16427 100644 --- a/BizHawk.Emulation.Cores/LibRetroEmulator.cs +++ b/BizHawk.Emulation.Cores/LibRetroEmulator.cs @@ -106,6 +106,8 @@ namespace BizHawk.Emulation.Cores 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: { @@ -118,14 +120,14 @@ namespace BizHawk.Emulation.Cores case LibRetro.RETRO_ENVIRONMENT.SHUTDOWN: return false; case LibRetro.RETRO_ENVIRONMENT.SET_PERFORMANCE_LEVEL: - return false; + Console.WriteLine("Core suggested SET_PERFORMANCE_LEVEL {0}", *(uint*)data.ToPointer()); + return true; 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()); + //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 false; + return true; case LibRetro.RETRO_ENVIRONMENT.SET_PIXEL_FORMAT: { LibRetro.RETRO_PIXEL_FORMAT fmt = 0; @@ -222,10 +224,16 @@ namespace BizHawk.Emulation.Cores //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; + //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; @@ -379,16 +387,11 @@ namespace BizHawk.Emulation.Cores public readonly RetroDescription Description = new RetroDescription(); //path configuration - string CoresDirectory; - string SystemDirectory; - IntPtr SystemDirectoryAtom; + string SystemDirectory, SaveDirectory; + IntPtr SystemDirectoryAtom, SaveDirectoryAtom; 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(); @@ -427,8 +430,6 @@ namespace BizHawk.Emulation.Cores 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); @@ -442,8 +443,6 @@ namespace BizHawk.Emulation.Cores 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 { @@ -487,6 +486,25 @@ namespace BizHawk.Emulation.Cores 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 + retro.retro_init(); + if (!retro.retro_load_game(ref gi)) { Console.WriteLine("retro_load_game() failed"); @@ -605,7 +623,7 @@ namespace BizHawk.Emulation.Cores [FeatureNotImplemented] get { - //if we dont have saveram, it isnt modified. otherwise, assume iti s + //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; @@ -877,7 +895,7 @@ namespace BizHawk.Emulation.Cores { get { - if(dar==0) + if(dar<=0) return BufferWidth; else if (dar > 1.0f) return (int)(BufferHeight * dar); @@ -889,7 +907,7 @@ namespace BizHawk.Emulation.Cores { get { - if(dar==0) + if(dar<=0) return BufferHeight; if (dar < 1.0f) return (int)(BufferWidth / dar);