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);