From 56524ef6a59350fb0c7fe5dd9cbc53148de1fde0 Mon Sep 17 00:00:00 2001 From: Craig Carnell <1188869+cscd98@users.noreply.github.com> Date: Sat, 29 Aug 2026 21:06:25 +0100 Subject: [PATCH] Add hard disk and m3u support to retro core --- src/retro/CMakeLists.txt | 4 +- src/retro/disk_control.c | 129 ++++++++++++++++++++++++++++++++++++++ src/retro/disk_control.h | 1 + src/retro/harddisk.c | 130 +++++++++++++++++++++++++++++++++++++++ src/retro/harddisk.h | 14 +++++ src/retro/main_retro.c | 62 +++++++++++++++---- src/retro/main_retro.h | 2 +- 7 files changed, 328 insertions(+), 14 deletions(-) create mode 100644 src/retro/harddisk.c create mode 100644 src/retro/harddisk.h diff --git a/src/retro/CMakeLists.txt b/src/retro/CMakeLists.txt index 9a68cec3b..40515346d 100644 --- a/src/retro/CMakeLists.txt +++ b/src/retro/CMakeLists.txt @@ -1,7 +1,7 @@ include_directories(. ../.. ../includes ../debug ../falcon) -add_library(UiRetro OBJECT audio.c disk_control.c joy_ui.c gui_event.c keymap.c main_retro.c - microphone.c options.c screen.c statusbar.c timing.c) +add_library(UiRetro OBJECT audio.c disk_control.c harddisk.c joy_ui.c gui_event.c keymap.c + main_retro.c microphone.c options.c screen.c statusbar.c timing.c) target_include_directories(UiRetro PRIVATE ${LIBRETRO_INCLUDE_DIR}) set_target_properties(UiRetro PROPERTIES POSITION_INDEPENDENT_CODE TRUE) diff --git a/src/retro/disk_control.c b/src/retro/disk_control.c index 29738e96f..2b452cc94 100644 --- a/src/retro/disk_control.c +++ b/src/retro/disk_control.c @@ -72,6 +72,115 @@ static void DiskControl_UpdateLabel(unsigned int idx) DiskImages[idx].label[sizeof(DiskImages[idx].label) - 1] = '\0'; } +/** + * Return true if 'p' looks like an absolute path. + */ +static bool DiskControl_PathIsAbsolute(const char *p) +{ + if (!p || !p[0]) + return false; + if (p[0] == '/' || p[0] == '\\') + return true; +#if defined(_WIN32) || defined(WIN32) + if (p[1] == ':') + return true; +#endif + return false; +} + +/** + * Parse a .m3u file + */ +static unsigned int DiskControl_ParseM3U(const char *m3u_path) +{ + uint8_t *raw; + long size = 0; + char *text, *p; + char dir[FILENAME_MAX], name[FILENAME_MAX], ext[FILENAME_MAX]; + + DiskImagesCount = 0; + + raw = File_ReadAsIs(m3u_path, &size); + if (!raw || size <= 0) + { + Log_Printf(LOG_ERROR, "DiskControl: could not read m3u file '%s'\n", m3u_path); + free(raw); + return 0; + } + + text = malloc((size_t)size + 1); + if (!text) + { + free(raw); + return 0; + } + memcpy(text, raw, (size_t)size); + text[size] = '\0'; + free(raw); + + File_SplitPath(m3u_path, dir, name, ext); + + p = text; + while (*p) + { + char *line, *eol; + char full[FILENAME_MAX]; + + while (*p == '\r' || *p == '\n') + p++; + if (!*p) + break; + + line = p; + eol = strpbrk(p, "\r\n"); + if (eol) + { + *eol = '\0'; + p = eol + 1; + } + else + { + p += strlen(p); + } + + while (*line == ' ' || *line == '\t') + line++; + if (!*line || *line == '#') + continue; + + if (DiskControl_PathIsAbsolute(line)) + { + strncpy(full, line, sizeof(full) - 1); + full[sizeof(full) - 1] = '\0'; + } + else + { + char *built = File_MakePath(dir, line, NULL); + if (!built) + continue; + strncpy(full, built, sizeof(full) - 1); + full[sizeof(full) - 1] = '\0'; + free(built); + } + + if (!DiskControl_EnsureCapacity(DiskImagesCount + 1)) + break; + + strncpy(DiskImages[DiskImagesCount].path, full, + sizeof(DiskImages[DiskImagesCount].path) - 1); + DiskImages[DiskImagesCount].path[sizeof(DiskImages[DiskImagesCount].path) - 1] = '\0'; + DiskControl_UpdateLabel(DiskImagesCount); + DiskImagesCount++; + } + + free(text); + + Log_Printf(LOG_INFO, "DiskControl: m3u '%s' parsed, %u file(s) found.\n", + m3u_path, DiskImagesCount); + + return DiskImagesCount; +} + /** * (Re-)insert the image at CurrentImageIndex into floppy drive A */ @@ -253,6 +362,26 @@ void DiskControl_NewGame(const char *path) } } +void DiskControl_NewGameM3U(const char *m3u_path) +{ + unsigned int n; + + bTrayEjected = false; + + n = DiskControl_ParseM3U(m3u_path); + if (n == 0) + { + DiskImagesCount = 0; + CurrentImageIndex = 0; + Floppy_SetDiskFileNameNone(0); + Floppy_EjectDiskFromDrive(0); + return; + } + + CurrentImageIndex = 0; + DiskControl_ApplyCurrentImage(); +} + void DiskControl_UnInit(void) { free(DiskImages); diff --git a/src/retro/disk_control.h b/src/retro/disk_control.h index 117ca0c22..b0450c8e9 100644 --- a/src/retro/disk_control.h +++ b/src/retro/disk_control.h @@ -9,6 +9,7 @@ void DiskControl_Init(void); void DiskControl_NewGame(const char *path); +void DiskControl_NewGameM3U(const char *m3u_path); void DiskControl_UnInit(void); #endif /* HATARI_RETRO_DISK_CONTROL_H */ diff --git a/src/retro/harddisk.c b/src/retro/harddisk.c new file mode 100644 index 000000000..c938d181d --- /dev/null +++ b/src/retro/harddisk.c @@ -0,0 +1,130 @@ +/* + Hatari - harddisk.c + + This file is distributed under the GNU General Public License, version 2 + or at your option any later version. Read the file gpl.txt for details. +*/ +#include +#include +#include + +#if HAVE_STRINGS_H +#include +#endif + +#include "main.h" +#include "main_retro.h" +#include "harddisk.h" +#include "configuration.h" +#include "file.h" +#include "floppy.h" +#include "gemdos.h" +#include "hdc.h" +#include "ide.h" +#include "log.h" +#include "ncr5380.h" +#include "reset.h" + +/** + * Insert a raw HD image into the IDE master slot and reboot so TOS + * picks it up. + */ +void HardDisk_InsertIde(const char *path) +{ + Ide_UnInit(); + + ConfigureParams.Ide[0].bUseDevice = true; + ConfigureParams.Ide[0].nByteSwap = BYTESWAP_AUTO; + ConfigureParams.Ide[0].nBlockSize = 512; + strncpy(ConfigureParams.Ide[0].sDeviceFile, path, + sizeof(ConfigureParams.Ide[0].sDeviceFile) - 1); + ConfigureParams.Ide[0].sDeviceFile[sizeof(ConfigureParams.Ide[0].sDeviceFile) - 1] = '\0'; + File_MakeAbsoluteName(ConfigureParams.Ide[0].sDeviceFile); + + Ide_Init(); + + Log_Printf(LOG_INFO, "HardDisk (%s) inserted into IDE master slot.\n", path); + + Reset_Cold(); +} + +/** + * Insert a raw HD image into ACSI slot 0 and reboot so TOS picks it up. + */ +void HardDisk_InsertAcsi(const char *path) +{ + HDC_UnInit(); + Ncr5380_UnInit(); + + ConfigureParams.Acsi[0].bUseDevice = true; + ConfigureParams.Acsi[0].nBlockSize = 512; + ConfigureParams.Acsi[0].nScsiVersion = 1; + strncpy(ConfigureParams.Acsi[0].sDeviceFile, path, + sizeof(ConfigureParams.Acsi[0].sDeviceFile) - 1); + ConfigureParams.Acsi[0].sDeviceFile[sizeof(ConfigureParams.Acsi[0].sDeviceFile) - 1] = '\0'; + File_MakeAbsoluteName(ConfigureParams.Acsi[0].sDeviceFile); + + HDC_Init(); + Ncr5380_Init(); + + Log_Printf(LOG_INFO, "HardDisk (%s) inserted into ACSI slot 0.\n", path); + + Reset_Cold(); +} + +/** + * Enable GEMDOS HD directory emulation + */ +void HardDisk_SetGemdosDrive(const char *path) +{ + char dir[FILENAME_MAX]; + size_t len = strlen(path); + + if (len >= 4 && strcasecmp(path + len - 4, ".gem") == 0) + len -= 4; + if (len >= sizeof(dir)) + len = sizeof(dir) - 1; + memcpy(dir, path, len); + dir[len] = '\0'; + + GemDOS_UnInitDrives(); + + strncpy(ConfigureParams.HardDisk.szHardDiskDirectories[0], dir, + sizeof(ConfigureParams.HardDisk.szHardDiskDirectories[0]) - 1); + ConfigureParams.HardDisk.szHardDiskDirectories[0][sizeof(ConfigureParams.HardDisk.szHardDiskDirectories[0]) - 1] = '\0'; + File_CleanFileName(ConfigureParams.HardDisk.szHardDiskDirectories[0]); + File_MakeAbsoluteName(ConfigureParams.HardDisk.szHardDiskDirectories[0]); + ConfigureParams.HardDisk.bUseHardDiskDirectories = true; + ConfigureParams.HardDisk.bBootFromHardDisk = true; + + GemDOS_InitDrives(); + + Log_Printf(LOG_INFO, "GEMDOS HDD emulation set to '%s'.\n", + ConfigureParams.HardDisk.szHardDiskDirectories[0]); + + if (retro_system_directory && retro_system_directory[0]) + { + char boot_path[FILENAME_MAX]; + + snprintf(boot_path, sizeof(boot_path), "%s%chatari%cBOOT.ST", + retro_system_directory, PATHSEP, PATHSEP); + + if (File_Exists(boot_path)) + { + Floppy_SetDiskFileName(0, boot_path, NULL); + Floppy_InsertDiskIntoDrive(0); + } + else + { + Log_Printf(LOG_WARN, "GEMDOS HDD boot floppy '%s' not found, " + "content may not autoboot.\n", boot_path); + } + } + else + { + Log_Printf(LOG_WARN, "GEMDOS HDD: no system directory available, " + "cannot look for BOOT.ST helper floppy.\n"); + } + + Reset_Cold(); +} diff --git a/src/retro/harddisk.h b/src/retro/harddisk.h new file mode 100644 index 000000000..41e0dc46f --- /dev/null +++ b/src/retro/harddisk.h @@ -0,0 +1,14 @@ +/* + Hatari - harddisk.h + + This file is distributed under the GNU General Public License, version 2 + or at your option any later version. Read the file gpl.txt for details. +*/ +#ifndef HATARI_RETRO_HARDDISK_H +#define HATARI_RETRO_HARDDISK_H + +void HardDisk_InsertIde(const char *path); +void HardDisk_InsertAcsi(const char *path); +void HardDisk_SetGemdosDrive(const char *path); + +#endif /* HATARI_RETRO_HARDDISK_H */ diff --git a/src/retro/main_retro.c b/src/retro/main_retro.c index 1aa0fe080..34f460272 100644 --- a/src/retro/main_retro.c +++ b/src/retro/main_retro.c @@ -1,4 +1,5 @@ +#include #include #include #include @@ -12,6 +13,7 @@ #include "disk_control.h" #include "file.h" #include "floppy.h" +#include "harddisk.h" #include "memorySnapShot.h" #include "m68000.h" #include "options.h" @@ -153,7 +155,7 @@ RETRO_API void retro_get_system_info(struct retro_system_info *info) info->library_name = "hatari"; info->library_version = HATARI_VERSION; info->need_fullpath = true; - info->valid_extensions = "st|msa|dim|stx"; + info->valid_extensions = "st|msa|dim|stx|ipf|m3u|ide|vhd|gem"; } RETRO_API void retro_get_system_av_info(struct retro_system_av_info *info) @@ -294,18 +296,56 @@ RETRO_API void retro_cheat_set(unsigned index, bool enabled, const char *code) RETRO_API bool retro_load_game(const struct retro_game_info *game) { - if (game) - Floppy_SetDiskFileName(0, game->path, NULL); + if (game && game->path) + { + const char *path = game->path; + + if (File_DoesFileExtensionMatch(path, ".m3u")) + { + DiskControl_NewGameM3U(path); + } + else if (File_DoesFileExtensionMatch(path, ".ide")) + { + Floppy_SetDiskFileNameNone(0); + DiskControl_NewGame(NULL); + HardDisk_InsertIde(path); + } + else if (File_DoesFileExtensionMatch(path, ".vhd")) + { + Floppy_SetDiskFileNameNone(0); + DiskControl_NewGame(NULL); + HardDisk_InsertAcsi(path); + } + else if (File_DoesFileExtensionMatch(path, ".gem")) + { + DiskControl_NewGame(NULL); + HardDisk_SetGemdosDrive(path); + } + else + { +#ifndef HAVE_CAPSIMAGE + if (File_DoesFileExtensionMatch(path, ".ipf")) + { + Log_Printf(LOG_WARN, "IPF image '%s' requested, but this build " + "was compiled without CAPSImage support.\n", + path); + } +#endif + Floppy_SetDiskFileName(0, path, NULL); + Floppy_InsertDiskIntoDrive(0); + + /* insert disk B also */ + if (ConfigureParams.DiskImage.szDiskFileName[1][0]) + Floppy_InsertDiskIntoDrive(1); + + DiskControl_NewGame(path); + } + } else + { Floppy_SetDiskFileNameNone(0); - - Floppy_InsertDiskIntoDrive(0); - - // insert disk B also - if (ConfigureParams.DiskImage.szDiskFileName[1][0]) - Floppy_InsertDiskIntoDrive(1); - - DiskControl_NewGame(game ? game->path : NULL); + DiskControl_NewGame(NULL); + } Retro_SetInputDescriptors(); diff --git a/src/retro/main_retro.h b/src/retro/main_retro.h index 51744ff1d..71e794010 100644 --- a/src/retro/main_retro.h +++ b/src/retro/main_retro.h @@ -6,7 +6,7 @@ */ extern bool has_cpu_config_changed; - +extern const char *retro_system_directory; extern retro_environment_t environment_cb; extern retro_video_refresh_t video_refresh_cb; extern retro_input_state_t input_state_cb;