various bugfixes to system/save pathing and support CAN_DUPE, to stabilize the gambatte and neopop cores

This commit is contained in:
zeromus 2015-11-08 19:18:08 -06:00
parent ecee3365cc
commit 53fcc09c08
8 changed files with 208 additions and 47 deletions

View File

@ -32,6 +32,17 @@ namespace BizHawk.Client.Common
return PathManager.SaveRamPath(Global.Game); return PathManager.SaveRamPath(Global.Game);
} }
public string GetRetroSaveRAMDirectory()
{
return PathManager.RetroSaveRAMDirectory(Global.Game);
}
public string GetRetroSystemPath()
{
return PathManager.RetroSystemPath(Global.Game);
}
public string GetGameBasePath() public string GetGameBasePath()
{ {
return PathManager.GetGameBasePath(Global.Game); return PathManager.GetGameBasePath(Global.Game);

View File

@ -292,6 +292,38 @@ namespace BizHawk.Client.Common
return Path.Combine(MakeAbsolutePath(pathEntry.Path, game.System), name) + ".SaveRAM"; 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) public static string GetGameBasePath(GameInfo game)
{ {
var name = FilesystemSafeName(game); var name = FilesystemSafeName(game);

View File

@ -255,39 +255,49 @@ namespace BizHawk.Client.Common
if (AsLibretro) 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); string codePathPart = Path.GetFileNameWithoutExtension(nextComm.LaunchLibretroCore);
var retro = new LibRetroEmulator(nextComm, nextComm.LaunchLibretroCore); var retro = new LibRetroEmulator(nextComm, nextComm.LaunchLibretroCore);
nextEmulator = retro; 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)) 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 //if we are allowed to run NoGame and we dont have a game, boot up the core that way
bool ret = retro.LoadNoGame(); bool ret = retro.LoadNoGame();
Global.Game = oldGame;
if (!ret) if (!ret)
{ {
DoLoadErrorCallback("LibretroNoGame failed to load. This is weird", "Libretro"); DoLoadErrorCallback("LibretroNoGame failed to load. This is weird", "Libretro");
retro.Dispose(); retro.Dispose();
return false; return false;
} }
//game name == name of core
gameName = codePathPart;
} }
else else
{ {
bool ret; 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 //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?) //(but do we ever need to actually load the contents of the archive file into ram?)
if (retro.Description.NeedsArchives) if (retro.Description.NeedsArchives)
{ {
if (file.IsArchiveMember) if (file.IsArchiveMember)
throw new InvalidOperationException("Should not have bound file member for libretro block_extract core"); throw new InvalidOperationException("Should not have bound file member for libretro block_extract core");
retro.LoadPath(file.FullPathWithoutMember); ret = retro.LoadPath(file.FullPathWithoutMember);
} }
else else
{ {
@ -303,6 +313,9 @@ namespace BizHawk.Client.Common
if (ret) if (ret)
ret = retro.LoadData(file.ReadAllBytes()); ret = retro.LoadData(file.ReadAllBytes());
} }
}
Global.Game = oldGame;
if (!ret) if (!ret)
{ {
@ -310,14 +323,8 @@ namespace BizHawk.Client.Common
retro.Dispose(); retro.Dispose();
return false; return false;
} }
} }
//game name == name of core + extensionless_game_filename
gameName = Path.Combine(codePathPart, Path.GetFileNameWithoutExtension(file.Name));
}
game = new GameInfo { Name = gameName, System = "Libretro" };
} }
else else

View File

@ -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 = "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 = "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 = "System", Path = Path.Combine(".", "System"), Ordinal = 2 },
new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 3 }, new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 3 },
new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 }, new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 4 },
new PathEntry { System = "Libretro", SystemDisplayName = "Libretro", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 }, 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 },
}; };
} }

View File

@ -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 //scan the current libretro core to see if it can be launched with NoGame,and other stuff
try 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; btnLibretroLaunchGame.Enabled = true;
if (retro.Description.SupportsNoGame) if (retro.Description.SupportsNoGame)

View File

@ -18,9 +18,15 @@ namespace BizHawk.Emulation.Common
string DllPath(); string DllPath();
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
string GetSaveRAMPath(); /// <returns></returns>
string GetRetroSaveRAMDirectory();
/// <summary>
/// produces a path for use as a libretro system path (different for each core)
/// </summary>
string GetRetroSystemPath();
string GetGameBasePath(); string GetGameBasePath();

View File

@ -337,6 +337,89 @@ namespace BizHawk.Emulation.Cores
EXPERIMENTAL = 0x10000 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 public enum RETRO_PIXEL_FORMAT
{ {
XRGB1555 = 0, XRGB1555 = 0,

View File

@ -106,6 +106,8 @@ namespace BizHawk.Emulation.Cores
case LibRetro.RETRO_ENVIRONMENT.GET_OVERSCAN: case LibRetro.RETRO_ENVIRONMENT.GET_OVERSCAN:
return false; return false;
case LibRetro.RETRO_ENVIRONMENT.GET_CAN_DUPE: case LibRetro.RETRO_ENVIRONMENT.GET_CAN_DUPE:
//gambatte requires this
*(bool*)data.ToPointer() = true;
return true; return true;
case LibRetro.RETRO_ENVIRONMENT.SET_MESSAGE: case LibRetro.RETRO_ENVIRONMENT.SET_MESSAGE:
{ {
@ -118,14 +120,14 @@ namespace BizHawk.Emulation.Cores
case LibRetro.RETRO_ENVIRONMENT.SHUTDOWN: case LibRetro.RETRO_ENVIRONMENT.SHUTDOWN:
return false; return false;
case LibRetro.RETRO_ENVIRONMENT.SET_PERFORMANCE_LEVEL: 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: case LibRetro.RETRO_ENVIRONMENT.GET_SYSTEM_DIRECTORY:
//please write an example of a core that crashes without this (fmsx malfunctions..) //mednafen NGP neopop fails to launch with no system directory
//"this is optional, but many cores will silently malfunction without it as they can't load their firmware files" Directory.CreateDirectory(SystemDirectory); //just to be safe, it seems likely that cores will crash without a created system directory
//an alternative (alongside where the saverams and such will go?) Console.WriteLine("returning system directory: " + SystemDirectory);
//*((IntPtr*)data.ToPointer()) = unmanagedResources.StringToHGlobalAnsi(CoreComm.CoreFileProvider.GetGameBasePath());
*((IntPtr*)data.ToPointer()) = SystemDirectoryAtom; *((IntPtr*)data.ToPointer()) = SystemDirectoryAtom;
return false; return true;
case LibRetro.RETRO_ENVIRONMENT.SET_PIXEL_FORMAT: case LibRetro.RETRO_ENVIRONMENT.SET_PIXEL_FORMAT:
{ {
LibRetro.RETRO_PIXEL_FORMAT fmt = 0; 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) //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 //this will suffice for now. if we find evidence later it's needed we can stash a string with
//unmanagedResources and CoreFileProvider //unmanagedResources and CoreFileProvider
//*((IntPtr*)data.ToPointer()) = IntPtr.Zero; //mednafen NGP neopop, desmume, and others, request this, and falls back on the system directory if it isn't provided
return false; //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: case LibRetro.RETRO_ENVIRONMENT.SET_CONTROLLER_INFO:
return true; return true;
case LibRetro.RETRO_ENVIRONMENT.SET_MEMORY_MAPS:
return false;
default: default:
Console.WriteLine("Unknkown retro_environment command {0}", (int)cmd); Console.WriteLine("Unknkown retro_environment command {0}", (int)cmd);
return false; return false;
@ -379,16 +387,11 @@ namespace BizHawk.Emulation.Cores
public readonly RetroDescription Description = new RetroDescription(); public readonly RetroDescription Description = new RetroDescription();
//path configuration //path configuration
string CoresDirectory; string SystemDirectory, SaveDirectory;
string SystemDirectory; IntPtr SystemDirectoryAtom, SaveDirectoryAtom;
IntPtr SystemDirectoryAtom;
public LibRetroEmulator(CoreComm nextComm, string modulename) 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); ServiceProvider = new BasicServiceProvider(this);
_SyncSettings = new SyncSettings(); _SyncSettings = new SyncSettings();
@ -427,8 +430,6 @@ namespace BizHawk.Emulation.Cores
retro.retro_set_environment(retro_environment_cb); retro.retro_set_environment(retro_environment_cb);
retro.retro_init();
retro.retro_set_video_refresh(retro_video_refresh_cb); retro.retro_set_video_refresh(retro_video_refresh_cb);
retro.retro_set_audio_sample(retro_audio_sample_cb); retro.retro_set_audio_sample(retro_audio_sample_cb);
retro.retro_set_audio_sample_batch(retro_audio_sample_batch_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.LibraryVersion = system_info.library_version;
Description.ValidExtensions = system_info.valid_extensions; Description.ValidExtensions = system_info.valid_extensions;
Description.SupportsNoGame = environmentInfo.SupportNoGame; 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 catch
{ {
@ -487,6 +486,25 @@ namespace BizHawk.Emulation.Cores
bool LoadWork(ref LibRetro.retro_game_info 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
retro.retro_init();
if (!retro.retro_load_game(ref gi)) if (!retro.retro_load_game(ref gi))
{ {
Console.WriteLine("retro_load_game() failed"); Console.WriteLine("retro_load_game() failed");
@ -605,7 +623,7 @@ namespace BizHawk.Emulation.Cores
[FeatureNotImplemented] [FeatureNotImplemented]
get 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); int size = (int)retro.retro_get_memory_size(LibRetro.RETRO_MEMORY.SAVE_RAM);
if (size == 0) if (size == 0)
return false; return false;
@ -877,7 +895,7 @@ namespace BizHawk.Emulation.Cores
{ {
get get
{ {
if(dar==0) if(dar<=0)
return BufferWidth; return BufferWidth;
else if (dar > 1.0f) else if (dar > 1.0f)
return (int)(BufferHeight * dar); return (int)(BufferHeight * dar);
@ -889,7 +907,7 @@ namespace BizHawk.Emulation.Cores
{ {
get get
{ {
if(dar==0) if(dar<=0)
return BufferHeight; return BufferHeight;
if (dar < 1.0f) if (dar < 1.0f)
return (int)(BufferWidth / dar); return (int)(BufferWidth / dar);