From de4fa37c1f873d1dc1ad500ffd3ccd874cfc5d8c Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 16 Aug 2026 17:31:51 +0100 Subject: [PATCH 1/5] Add ability to auto-insert disks in S6D2. Signed-off-by: Andrea Odetti --- source/frontends/libretro/README.md | 68 +++++++++++++++++++- source/frontends/libretro/diskcontrol.cpp | 69 +++++++++++++++++---- source/frontends/libretro/diskcontrol.h | 3 +- source/frontends/libretro/retroregistry.cpp | 21 +++++++ source/frontends/libretro/retroregistry.h | 1 + 5 files changed, 147 insertions(+), 15 deletions(-) diff --git a/source/frontends/libretro/README.md b/source/frontends/libretro/README.md index 0db74a3fa..eda9fea04 100644 --- a/source/frontends/libretro/README.md +++ b/source/frontends/libretro/README.md @@ -47,6 +47,70 @@ In order to have a better experience with the keyboard, one should probably enab Easiest way to run from the ``build`` folder: ``retroarch -L source/frontends/libretro/applewin_libretro.so ../bin/MASTER.DSK`` -It supports playlists files `.m3u` (see https://docs.libretro.com/library/vice/#m3u-and-disk-control alttough not all options are implemented). - The core can be statically linked in Linux and MSYS2, pass `-DSTATIC_LINKING=ON` to `cmake`, or disable networking `-DENABLE_NETWORKING=OFF`. + +## M3U Playlists and Disk Control + +The core cupports libretro [Disk Control Interface](https://docs.libretro.com/guides/disc-swapping/), allowing disk swaps from the frontend UI. + +### M3U format + +A `.m3u` file lists disk images, one per line. Paths are relative to the M3U file's directory, similar to [VICE](https://docs.libretro.com/library/vice/#m3u-and-disk-control). + +``` +Ultima V - Disk A.dsk +Ultima V - Disk B.dsk +``` + +### Supported directives + +| Directive | Description | +| --------- | ----------- | +| `#` | Comment (line is ignored) | +| `#SAVEDISK:` | Creates a writable blank disk in the save directory | +| `#LABEL:` | Sets the display label for the next file entry | +| `#EXTINF:` | Standard M3U extended info - label follows the first comma | +| `\|` (pipe in filename) | `path.dsk\|Label` sets a display label for that entry | + +Label priority: `#LABEL:` / `#EXTINF:` override pipe labels. + +#### `#SAVEDISK:` + +Adds a writable save disk to the playlist. The disk image is created (as a blank `.dsk`) in the save directory on first use and reused on subsequent launches. + +``` +Game - Side A.dsk +Game - Side B.dsk +#SAVEDISK:Character +#SAVEDISK: +``` + +The label after `#SAVEDISK:` is optional. If omitted, an automatic index is used. The resulting label shown in the frontend is always prefixed with "Save Disk ". + +Note: the created disk is unformatted (all zeros). Most Apple II games that request a save disk will format it themselves. + +#### Pipe-delimited labels + +``` +Game - Side A (Disk 1 of 3).dsk|Side A +Game - Side B (Disk 2 of 3).dsk|Side B +``` + +The text after `|` becomes the display label in the frontend's disk control UI instead of the filename stem. + +### MultiDrive + +When enabled, the second disk in the playlist is automatically inserted into DRIVE_2 at load time. This is useful for games that expect both drives populated (e.g. Ultima, Wizardry). + +MultiDrive is triggered by either: +- Naming the M3U file with `(MD)` in the stem, e.g. `Ultima V (MD).m3u` +- Enabling the **Floppy MultiDrive** core option + +Save disks (`#SAVEDISK:`) are never auto-inserted into DRIVE_2. + +### Core options + +| Option | Values | Description | +| ------ | ------ | ----------- | +| Playlist Start Disk | First, Previous | Whether to start from disk 0 or resume from the previously used disk | +| Floppy MultiDrive | disabled, enabled | Auto-insert second disk into DRIVE_2 for all playlists | diff --git a/source/frontends/libretro/diskcontrol.cpp b/source/frontends/libretro/diskcontrol.cpp index 2622b9e2b..2b4ae0e81 100644 --- a/source/frontends/libretro/diskcontrol.cpp +++ b/source/frontends/libretro/diskcontrol.cpp @@ -15,8 +15,11 @@ namespace { const std::string M3U_COMMENT("#"); + const std::string M3U_LABEL("#LABEL:"); + const std::string M3U_EXTINF("#EXTINF:"); const std::string M3U_SAVEDISK("#SAVEDISK:"); - const std::string M3U_SAVEDISK_LABEL("Save Disk "); + + const std::string SAVEDISK_LABEL("Save Disk "); bool startsWith(const std::string &value, const std::string &prefix) { @@ -88,7 +91,7 @@ namespace ra2 const bool writeProtected = IMAGE_FORCE_WRITE_PROTECTED; const bool createIfNecessary = IMAGE_DONT_CREATE; - if (insertFloppyDisk(path, writeProtected, createIfNecessary)) + if (insertFloppyDisk(DRIVE_1, path, writeProtected, createIfNecessary)) { myIndex = 0; myImages.clear(); @@ -117,11 +120,13 @@ namespace ra2 const std::string playlistStem = playlistPath.stem().string(); std::string line; + std::string pendingLabel; while (std::getline(playlist, line)) { // should we trim initial spaces? if (startsWith(line, M3U_SAVEDISK)) { + pendingLabel.clear(); const size_t index = myImages.size() + 1; const std::string filename = StrFormat("%s.save%" SIZE_T_FMT ".dsk", playlistStem.c_str(), index); @@ -134,12 +139,25 @@ namespace ra2 } // Always prefix with "Save Disk" - const std::string label = M3U_SAVEDISK_LABEL + labelSuffix; + const std::string label = SAVEDISK_LABEL + labelSuffix; const std::filesystem::path imagePath = savePath / filename; // TODO: this disk is NOT formatted - myImages.push_back({imagePath.string(), label, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_CREATE}); + myImages.push_back({imagePath.string(), label, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_CREATE, true}); + } + else if (startsWith(line, M3U_LABEL)) + { + pendingLabel = line.substr(M3U_LABEL.size()); + } + else if (startsWith(line, M3U_EXTINF)) + { + // #EXTINF: standard - label follows the first comma + const size_t comma = line.find(',', M3U_EXTINF.size()); + if (comma != std::string::npos) + { + pendingLabel = line.substr(comma + 1); + } } else if (!startsWith(line, M3U_COMMENT)) { @@ -147,11 +165,18 @@ namespace ra2 std::string label; getLabelAndPath(line, imagePath, label); + // #LABEL: or #EXTINF: override pipe label + if (!pendingLabel.empty()) + { + label = pendingLabel; + pendingLabel.clear(); + } + if (imagePath.is_relative()) { imagePath = parent / imagePath; } - myImages.push_back({imagePath.string(), label, IMAGE_FORCE_WRITE_PROTECTED, IMAGE_DONT_CREATE}); + myImages.push_back({imagePath.string(), label, IMAGE_FORCE_WRITE_PROTECTED, IMAGE_DONT_CREATE, false}); } } @@ -175,19 +200,38 @@ namespace ra2 // this is safe even if myImages is empty myEjected = true; - return setEjectedState(false); + const bool inserted = setEjectedState(false); + + // MultiDrive support: if M3U filename constains "(MD") or option is enabled, insert second disk into DRIVE_2 + // Only when starting from disk 0 to avoid conflicts with Previous resume + // Skip save disks - those are for manual swap only + if (inserted && myIndex == 0 && myImages.size() > 1 && + (playlistStem.find("(MD") != std::string::npos || ra2::getFloppyMultiDrive())) + { + const auto &image = myImages[1]; + if (!image.isSaveDisk) + { + insertFloppyDisk(DRIVE_2, image.path, image.writeProtected, image.createIfNecessary); + } + } + + return inserted; } - bool DiskControl::insertFloppyDisk(const std::string &path, const bool writeProtected, bool const createIfNecessary) + bool DiskControl::insertFloppyDisk( + const Drive_e drive, const std::string &path, const bool writeProtected, bool const createIfNecessary) { CardManager &cardManager = GetCardMgr(); Disk2InterfaceCard *disk2Card = dynamic_cast(cardManager.GetObj(SLOT6)); if (disk2Card) { - const ImageError_e error = disk2Card->InsertDisk(DRIVE_1, path, writeProtected, createIfNecessary); + const ImageError_e error = disk2Card->InsertDisk(drive, path, writeProtected, createIfNecessary); + const bool result = (error == eIMAGE_ERROR_NONE); + + ra2::log_cb(RETRO_LOG_INFO, "Insert into drive %d: %s -> %d\n", drive + 1, path.c_str(), result); - if (error == eIMAGE_ERROR_NONE) + if (result) { storeCurrentDiskFolder(path); return true; @@ -241,11 +285,10 @@ namespace ra2 } else { + const auto &image = myImages[myIndex]; // inserted - result = insertFloppyDisk( - myImages[myIndex].path, myImages[myIndex].writeProtected, myImages[myIndex].createIfNecessary); + result = insertFloppyDisk(DRIVE_1, image.path, image.writeProtected, image.createIfNecessary); myEjected = !result; - ra2::log_cb(RETRO_LOG_INFO, "Insert new disk: %s -> %d\n", myImages[myIndex].path.c_str(), result); } } @@ -361,6 +404,7 @@ namespace ra2 writeString(buffer, image.label); buffer.get() = image.writeProtected; buffer.get() = image.createIfNecessary; + buffer.get() = image.isSaveDisk; } } @@ -379,6 +423,7 @@ namespace ra2 readString(buffer, image.label); image.writeProtected = buffer.get(); image.createIfNecessary = buffer.get(); + image.isSaveDisk = buffer.get(); } } diff --git a/source/frontends/libretro/diskcontrol.h b/source/frontends/libretro/diskcontrol.h index 915510d51..0fc0a9a47 100644 --- a/source/frontends/libretro/diskcontrol.h +++ b/source/frontends/libretro/diskcontrol.h @@ -22,6 +22,7 @@ namespace ra2 std::string label; bool writeProtected = IMAGE_FORCE_WRITE_PROTECTED; bool createIfNecessary = IMAGE_DONT_CREATE; + bool isSaveDisk = false; }; class DiskControl @@ -61,7 +62,7 @@ namespace ra2 size_t myIndex; std::string myCurrentDiskFolder; - bool insertFloppyDisk(const std::string &path, const bool writeProtected, bool const createIfNecessary); + bool insertFloppyDisk(const Drive_e drive, const std::string &path, const bool writeProtected, bool const createIfNecessary); bool insertHardDisk(const std::string &path); void storeCurrentDiskFolder(const std::string &path); diff --git a/source/frontends/libretro/retroregistry.cpp b/source/frontends/libretro/retroregistry.cpp index 88a451f49..b80d42623 100644 --- a/source/frontends/libretro/retroregistry.cpp +++ b/source/frontends/libretro/retroregistry.cpp @@ -24,6 +24,7 @@ namespace const char *REG_RA2 = "ra2"; const char *REGVALUE_KEYBOARD_TYPE = "Keyboard type"; const char *REGVALUE_PLAYLIST_START = "Playlist start"; + const char *REGVALUE_FLOPPY_MULTI_DRIVE = "Floppy multidrive"; const char *REGVALUE_MOUSE_SPEED_00 = "Mouse speed"; const char *CATEGORY_SYSTEM = "system"; @@ -306,6 +307,19 @@ namespace REG_RA2, REGVALUE_PLAYLIST_START, }, + { + { + "floppy_multidrive", + "Floppy MultiDrive", + CATEGORY_SYSTEM, + { + {"disabled", 0}, + {"enabled", 1}, + }, + }, + REG_RA2, + REGVALUE_FLOPPY_MULTI_DRIVE, + }, { { "keyboard_type", @@ -561,6 +575,13 @@ namespace ra2 return registry; } + bool getFloppyMultiDrive() + { + uint32_t value = 0; + RegLoadValue(REG_RA2, REGVALUE_FLOPPY_MULTI_DRIVE, true, &value); + return value != 0; + } + KeyboardType getKeyboardEmulationType() { uint32_t value = static_cast(KeyboardType::ASCII); diff --git a/source/frontends/libretro/retroregistry.h b/source/frontends/libretro/retroregistry.h index 7fda4f41c..a8af09ac2 100644 --- a/source/frontends/libretro/retroregistry.h +++ b/source/frontends/libretro/retroregistry.h @@ -23,6 +23,7 @@ namespace ra2 KeyboardType getKeyboardEmulationType(); PlaylistStartDisk getPlaylistStartDisk(); + bool getFloppyMultiDrive(); double getMouseSpeed(); bool is280Lines(); From 837f8ad72d9b99cdabf057fe999e9d4bdca2af93 Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 16 Aug 2026 18:10:53 +0100 Subject: [PATCH 2/5] Add DiskControl interface for S6D2. Signed-off-by: Andrea Odetti --- source/frontends/libretro/diskcontrol.cpp | 115 ++++++++++++++------ source/frontends/libretro/diskcontrol.h | 13 ++- source/frontends/libretro/libretro.cpp | 11 +- source/frontends/libretro/retroregistry.cpp | 27 ++++- source/frontends/libretro/retroregistry.h | 1 + 5 files changed, 126 insertions(+), 41 deletions(-) diff --git a/source/frontends/libretro/diskcontrol.cpp b/source/frontends/libretro/diskcontrol.cpp index 2b4ae0e81..059aa7b64 100644 --- a/source/frontends/libretro/diskcontrol.cpp +++ b/source/frontends/libretro/diskcontrol.cpp @@ -68,8 +68,8 @@ namespace ra2 { DiskControl::DiskControl() - : myEjected(true) - , myIndex(0) + : myEjected{true, true} + , myIndex{0, 0} { } @@ -78,6 +78,11 @@ namespace ra2 return myCurrentDiskFolder; } + Drive_e DiskControl::activeDrive() const + { + return ra2::getDiskControlDrive(); + } + void DiskControl::storeCurrentDiskFolder(const std::string &path) { // a bit of a workaround to a save state issue, where the disk folder is lost @@ -86,19 +91,19 @@ namespace ra2 myCurrentDiskFolder = filePath.parent_path().string(); } - bool DiskControl::insertDisk(const std::string &path) + bool DiskControl::insertDisk(const Drive_e drive, const std::string &path) { const bool writeProtected = IMAGE_FORCE_WRITE_PROTECTED; const bool createIfNecessary = IMAGE_DONT_CREATE; - if (insertFloppyDisk(DRIVE_1, path, writeProtected, createIfNecessary)) + if (insertFloppyDisk(drive, path, writeProtected, createIfNecessary)) { - myIndex = 0; + myIndex[drive] = 0; myImages.clear(); const std::filesystem::path filePath(path); myImages.push_back({filePath.string(), filePath.stem().string(), writeProtected, createIfNecessary}); - myEjected = false; + myEjected[drive] = false; return true; } @@ -144,7 +149,8 @@ namespace ra2 const std::filesystem::path imagePath = savePath / filename; // TODO: this disk is NOT formatted - myImages.push_back({imagePath.string(), label, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_CREATE, true}); + myImages.push_back( + {imagePath.string(), label, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_CREATE, true}); } else if (startsWith(line, M3U_LABEL)) { @@ -181,7 +187,7 @@ namespace ra2 } // insert the first image by default - myIndex = 0; + myIndex[DRIVE_1] = 0; const PlaylistStartDisk playlistStartDisk = getPlaylistStartDisk(); @@ -191,7 +197,7 @@ namespace ra2 if (!ourInitialPath.empty() && ourInitialIndex < myImages.size() && myImages[ourInitialIndex].path == ourInitialPath) { - myIndex = ourInitialIndex; + myIndex[DRIVE_1] = ourInitialIndex; // do we need to reset for next time? ourInitialPath.clear(); ourInitialIndex = 0; @@ -199,19 +205,21 @@ namespace ra2 } // this is safe even if myImages is empty - myEjected = true; - const bool inserted = setEjectedState(false); + myEjected[DRIVE_1] = true; + const bool inserted = setEjectedStateForDrive(DRIVE_1, false); // MultiDrive support: if M3U filename constains "(MD") or option is enabled, insert second disk into DRIVE_2 // Only when starting from disk 0 to avoid conflicts with Previous resume // Skip save disks - those are for manual swap only - if (inserted && myIndex == 0 && myImages.size() > 1 && + if (inserted && myIndex[DRIVE_1] == 0 && myImages.size() > 1 && (playlistStem.find("(MD") != std::string::npos || ra2::getFloppyMultiDrive())) { const auto &image = myImages[1]; if (!image.isSaveDisk) { insertFloppyDisk(DRIVE_2, image.path, image.writeProtected, image.createIfNecessary); + myEjected[DRIVE_2] = false; + myIndex[DRIVE_2] = 1; } } @@ -261,12 +269,19 @@ namespace ra2 bool DiskControl::getEjectedState() const { - return myEjected; + const Drive_e drive = activeDrive(); + return myEjected[drive]; } bool DiskControl::setEjectedState(bool ejected) { - if (myEjected == ejected) + const Drive_e drive = activeDrive(); + return setEjectedStateForDrive(drive, ejected); + } + + bool DiskControl::setEjectedStateForDrive(const Drive_e drive, bool ejected) + { + if (myEjected[drive] == ejected) { return true; } @@ -275,20 +290,31 @@ namespace ra2 Disk2InterfaceCard *disk2Card = dynamic_cast(cardManager.GetObj(SLOT6)); bool result = false; - if (disk2Card && myIndex < myImages.size()) + if (disk2Card && myIndex[drive] < myImages.size()) { if (ejected) { - disk2Card->EjectDisk(DRIVE_1); + disk2Card->EjectDisk(drive); result = true; - myEjected = ejected; + myEjected[drive] = ejected; } else { - const auto &image = myImages[myIndex]; + // if the other dirve has the same disk, eject it first to avoid conflicts + const Drive_e otherDrive = (drive == DRIVE_1) ? DRIVE_2 : DRIVE_1; + if (!myEjected[otherDrive] && myIndex[otherDrive] < myImages.size() && + myIndex[otherDrive] == myIndex[drive]) + { + disk2Card->EjectDisk(otherDrive); + myEjected[otherDrive] = true; + ra2::log_cb( + RETRO_LOG_INFO, "Ejecting drive %d to avoid conflict with drive %d\n", otherDrive + 1, + drive + 1); + } + const auto &image = myImages[myIndex[drive]]; // inserted - result = insertFloppyDisk(DRIVE_1, image.path, image.writeProtected, image.createIfNecessary); - myEjected = !result; + result = insertFloppyDisk(drive, image.path, image.writeProtected, image.createIfNecessary); + myEjected[drive] = !result; } } @@ -297,14 +323,16 @@ namespace ra2 size_t DiskControl::getImageIndex() const { - return myIndex; + const Drive_e drive = activeDrive(); + return myIndex[drive]; } bool DiskControl::setImageIndex(size_t index) { - if (myEjected) + const Drive_e drive = activeDrive(); + if (myEjected[drive]) { - myIndex = index; + myIndex[drive] = index; return true; } else @@ -320,7 +348,8 @@ namespace ra2 bool DiskControl::replaceImageIndex(size_t index, const std::string &path) { - if (myEjected && myIndex < myImages.size()) + const Drive_e drive = activeDrive(); + if (myEjected[drive] && myIndex[drive] < myImages.size()) { const std::filesystem::path filePath(path); @@ -338,16 +367,17 @@ namespace ra2 bool DiskControl::removeImageIndex(size_t index) { - if (myEjected && myIndex < myImages.size()) + const Drive_e drive = activeDrive(); + if (myEjected[drive] && myIndex[drive] < myImages.size()) { myImages.erase(myImages.begin() + index); - if (myImages.empty() || myIndex == index) + if (myImages.empty() || myIndex[drive] == index) { - myIndex = myImages.size(); + myIndex[drive] = myImages.size(); } - else if (myIndex > index) + else if (myIndex[drive] > index) { - --myIndex; + --myIndex[drive]; } return true; } @@ -381,7 +411,18 @@ namespace ra2 { if (index < myImages.size()) { - strncpy(label, myImages[index].label.c_str(), len); + const Drive_e drive = activeDrive(); + const Drive_e otherDrive = (drive == DRIVE_1) ? DRIVE_2 : DRIVE_1; + + std::string displayLabel = "S6D" + std::to_string(drive + 1) + " - " + myImages[index].label; + + // Mark the label with an asterisk if the same disk is inserted in the other drive + if (!myEjected[otherDrive] && myIndex[otherDrive] == index) + { + displayLabel += " <*>"; + } + + strncpy(label, displayLabel.c_str(), len); label[len - 1] = 0; return true; } @@ -394,8 +435,11 @@ namespace ra2 void DiskControl::serialise(Buffer &buffer) const { writeString(buffer, myCurrentDiskFolder); - buffer.get() = myEjected; - buffer.get() = myIndex; + for (int d = 0; d < NUM_DRIVES; ++d) + { + buffer.get() = myEjected[d]; + buffer.get() = myIndex[d]; + } buffer.get() = myImages.size(); for (DiskInfo const &image : myImages) @@ -411,8 +455,11 @@ namespace ra2 void DiskControl::deserialise(Buffer &buffer) { readString(buffer, myCurrentDiskFolder); - myEjected = buffer.get(); - myIndex = buffer.get(); + for (int d = 0; d < NUM_DRIVES; ++d) + { + myEjected[d] = buffer.get(); + myIndex[d] = buffer.get(); + } size_t const numberOfImages = buffer.get(); myImages.clear(); myImages.resize(numberOfImages); diff --git a/source/frontends/libretro/diskcontrol.h b/source/frontends/libretro/diskcontrol.h index 0fc0a9a47..dc64cea18 100644 --- a/source/frontends/libretro/diskcontrol.h +++ b/source/frontends/libretro/diskcontrol.h @@ -44,7 +44,7 @@ namespace ra2 bool addImageIndex(); // these 2 functions update the images for the Disc Control Interface - bool insertDisk(const std::string &path); + bool insertDisk(const Drive_e drive, const std::string &path); bool insertPlaylist(const std::string &path); bool getImagePath(unsigned index, char *path, size_t len) const; @@ -58,11 +58,16 @@ namespace ra2 private: std::vector myImages; - bool myEjected; - size_t myIndex; + // per-drive state for multi-drive support + bool myEjected[NUM_DRIVES]; + size_t myIndex[NUM_DRIVES]; + std::string myCurrentDiskFolder; - bool insertFloppyDisk(const Drive_e drive, const std::string &path, const bool writeProtected, bool const createIfNecessary); + Drive_e activeDrive() const; + bool setEjectedStateForDrive(const Drive_e drive, bool state); + bool insertFloppyDisk( + const Drive_e drive, const std::string &path, const bool writeProtected, bool const createIfNecessary); bool insertHardDisk(const std::string &path); void storeCurrentDiskFolder(const std::string &path); diff --git a/source/frontends/libretro/libretro.cpp b/source/frontends/libretro/libretro.cpp index feacdaee9..41034275f 100644 --- a/source/frontends/libretro/libretro.cpp +++ b/source/frontends/libretro/libretro.cpp @@ -35,33 +35,39 @@ namespace bool retro_set_eject_state(bool ejected) { + ourGame->updateVariables(); ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, ejected); return ourGame->getDiskControl().setEjectedState(ejected); } bool retro_get_eject_state() { + ourGame->updateVariables(); return ourGame->getDiskControl().getEjectedState(); } unsigned retro_get_image_index() { + ourGame->updateVariables(); return ourGame->getDiskControl().getImageIndex(); } bool retro_set_image_index(unsigned index) { + ourGame->updateVariables(); ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index); return ourGame->getDiskControl().setImageIndex(index); } unsigned retro_get_num_images() { + ourGame->updateVariables(); return ourGame->getDiskControl().getNumImages(); } bool retro_replace_image_index(unsigned index, const struct retro_game_info *info) { + ourGame->updateVariables(); ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%s)\n", __FUNCTION__, info->path); if (info->path) { @@ -75,6 +81,7 @@ namespace bool retro_add_image_index() { + ourGame->updateVariables(); ra2::log_cb(RETRO_LOG_INFO, "RA2: %s\n", __FUNCTION__); return ourGame->getDiskControl().addImageIndex(); } @@ -88,12 +95,14 @@ namespace bool retro_get_image_path(unsigned index, char *path, size_t len) { + ourGame->updateVariables(); ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index); return ourGame->getDiskControl().getImagePath(index, path, len); } bool retro_get_image_label(unsigned index, char *label, size_t len) { + ourGame->updateVariables(); ra2::log_cb(RETRO_LOG_INFO, "RA2: %s (%d)\n", __FUNCTION__, index); return ourGame->getDiskControl().getImageLabel(index, label, len); } @@ -341,7 +350,7 @@ bool retro_load_game(const retro_game_info *info) } else { - ok = game->getDiskControl().insertDisk(gamePath); + ok = game->getDiskControl().insertDisk(DRIVE_1, gamePath); } ra2::log_cb(RETRO_LOG_INFO, "Game path: %s -> %d\n", info->path, ok); } diff --git a/source/frontends/libretro/retroregistry.cpp b/source/frontends/libretro/retroregistry.cpp index b80d42623..133158ab5 100644 --- a/source/frontends/libretro/retroregistry.cpp +++ b/source/frontends/libretro/retroregistry.cpp @@ -25,16 +25,19 @@ namespace const char *REGVALUE_KEYBOARD_TYPE = "Keyboard type"; const char *REGVALUE_PLAYLIST_START = "Playlist start"; const char *REGVALUE_FLOPPY_MULTI_DRIVE = "Floppy multidrive"; + const char *REGVALUE_DISK_CONTROL_DRIVE = "Disk control drive"; const char *REGVALUE_MOUSE_SPEED_00 = "Mouse speed"; const char *CATEGORY_SYSTEM = "system"; const char *CATEGORY_INPUT = "input"; const char *CATEGORY_RETROPAD_MAPPING = "retropad"; + const char *CATEGORY_DISK_CONTROL = "disk_control"; retro_core_option_v2_category ourOptionCatsUS[] = { {CATEGORY_SYSTEM, "System", "Configure system options."}, {CATEGORY_INPUT, "Input", "Configure input options."}, {CATEGORY_RETROPAD_MAPPING, "RetroPad Mapping", "Configure RetroPad mapping options."}, + {CATEGORY_DISK_CONTROL, "Disk Control", "Configure disk control options."}, {nullptr, nullptr, nullptr}, }; @@ -298,7 +301,7 @@ namespace { "playlist_start", "Playlist Start Disk", - CATEGORY_SYSTEM, + CATEGORY_DISK_CONTROL, { {"First", static_cast(ra2::PlaylistStartDisk::First)}, {"Previous", static_cast(ra2::PlaylistStartDisk::Previous)}, @@ -311,7 +314,7 @@ namespace { "floppy_multidrive", "Floppy MultiDrive", - CATEGORY_SYSTEM, + CATEGORY_DISK_CONTROL, { {"disabled", 0}, {"enabled", 1}, @@ -320,6 +323,19 @@ namespace REG_RA2, REGVALUE_FLOPPY_MULTI_DRIVE, }, + { + { + "disk_control_drive", + "Disk Control Drive", + CATEGORY_DISK_CONTROL, + { + {"Drive 1", DRIVE_1}, + {"Drive 2", DRIVE_2}, + }, + }, + REG_RA2, + REGVALUE_DISK_CONTROL_DRIVE, + }, { { "keyboard_type", @@ -596,6 +612,13 @@ namespace ra2 return static_cast(value); } + Drive_e getDiskControlDrive() + { + uint32_t value = DRIVE_1; + RegLoadValue(REG_RA2, REGVALUE_DISK_CONTROL_DRIVE, true, &value); + return (value == DRIVE_2) ? DRIVE_2 : DRIVE_1; + } + double getMouseSpeed() { uint32_t value = 100; diff --git a/source/frontends/libretro/retroregistry.h b/source/frontends/libretro/retroregistry.h index a8af09ac2..2ed227c54 100644 --- a/source/frontends/libretro/retroregistry.h +++ b/source/frontends/libretro/retroregistry.h @@ -24,6 +24,7 @@ namespace ra2 KeyboardType getKeyboardEmulationType(); PlaylistStartDisk getPlaylistStartDisk(); bool getFloppyMultiDrive(); + Drive_e getDiskControlDrive(); double getMouseSpeed(); bool is280Lines(); From 3509623bb96903554a1c67d067d622abd13548ff Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 16 Aug 2026 18:31:04 +0100 Subject: [PATCH 3/5] Update readme. Signed-off-by: Andrea Odetti --- source/frontends/libretro/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/frontends/libretro/README.md b/source/frontends/libretro/README.md index eda9fea04..0408656a9 100644 --- a/source/frontends/libretro/README.md +++ b/source/frontends/libretro/README.md @@ -51,7 +51,7 @@ The core can be statically linked in Linux and MSYS2, pass `-DSTATIC_LINKING=ON` ## M3U Playlists and Disk Control -The core cupports libretro [Disk Control Interface](https://docs.libretro.com/guides/disc-swapping/), allowing disk swaps from the frontend UI. +The core cupports libretro [Disk Control Interface](https://docs.libretro.com/guides/disc-swapping/), allowing disk swaps from the frontend UI (the drive is selectable via a core option). ### M3U format @@ -114,3 +114,4 @@ Save disks (`#SAVEDISK:`) are never auto-inserted into DRIVE_2. | ------ | ------ | ----------- | | Playlist Start Disk | First, Previous | Whether to start from disk 0 or resume from the previously used disk | | Floppy MultiDrive | disabled, enabled | Auto-insert second disk into DRIVE_2 for all playlists | +| Disk Control Drive | Drive 1, 2 | Active drive for the Disk Control Interface. | From bc07fc30980657d5c1b3d80412e2e13eb8abab8d Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 23 Aug 2026 12:08:06 +0100 Subject: [PATCH 4/5] Move disk options higher in the list. Signed-off-by: Andrea Odetti --- source/frontends/libretro/diskcontrol.cpp | 3 +-- source/frontends/libretro/retroregistry.cpp | 28 ++++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/source/frontends/libretro/diskcontrol.cpp b/source/frontends/libretro/diskcontrol.cpp index 059aa7b64..596d8a48d 100644 --- a/source/frontends/libretro/diskcontrol.cpp +++ b/source/frontends/libretro/diskcontrol.cpp @@ -302,8 +302,7 @@ namespace ra2 { // if the other dirve has the same disk, eject it first to avoid conflicts const Drive_e otherDrive = (drive == DRIVE_1) ? DRIVE_2 : DRIVE_1; - if (!myEjected[otherDrive] && myIndex[otherDrive] < myImages.size() && - myIndex[otherDrive] == myIndex[drive]) + if (!myEjected[otherDrive] && (myIndex[otherDrive] == myIndex[drive])) { disk2Card->EjectDisk(otherDrive); myEjected[otherDrive] = true; diff --git a/source/frontends/libretro/retroregistry.cpp b/source/frontends/libretro/retroregistry.cpp index 133158ab5..d79172648 100644 --- a/source/frontends/libretro/retroregistry.cpp +++ b/source/frontends/libretro/retroregistry.cpp @@ -35,9 +35,9 @@ namespace retro_core_option_v2_category ourOptionCatsUS[] = { {CATEGORY_SYSTEM, "System", "Configure system options."}, + {CATEGORY_DISK_CONTROL, "Disk Control", "Configure disk control options."}, {CATEGORY_INPUT, "Input", "Configure input options."}, {CATEGORY_RETROPAD_MAPPING, "RetroPad Mapping", "Configure RetroPad mapping options."}, - {CATEGORY_DISK_CONTROL, "Disk Control", "Configure disk control options."}, {nullptr, nullptr, nullptr}, }; @@ -297,6 +297,19 @@ namespace REG_CONFIG, REGVALUE_VIDEO_REFRESH_RATE, // reset required }, + { + { + "disk_control_drive", + "Disk Control Drive", + CATEGORY_DISK_CONTROL, + { + {"Drive 1", DRIVE_1}, + {"Drive 2", DRIVE_2}, + }, + }, + REG_RA2, + REGVALUE_DISK_CONTROL_DRIVE, + }, { { "playlist_start", @@ -323,19 +336,6 @@ namespace REG_RA2, REGVALUE_FLOPPY_MULTI_DRIVE, }, - { - { - "disk_control_drive", - "Disk Control Drive", - CATEGORY_DISK_CONTROL, - { - {"Drive 1", DRIVE_1}, - {"Drive 2", DRIVE_2}, - }, - }, - REG_RA2, - REGVALUE_DISK_CONTROL_DRIVE, - }, { { "keyboard_type", From d5c53c7b06bf8796fc123dcb4c13292562adf5d8 Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 23 Aug 2026 12:10:57 +0100 Subject: [PATCH 5/5] Correct Readme. Signed-off-by: Andrea Odetti --- source/frontends/libretro/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/frontends/libretro/README.md b/source/frontends/libretro/README.md index 0408656a9..4be3d068a 100644 --- a/source/frontends/libretro/README.md +++ b/source/frontends/libretro/README.md @@ -112,6 +112,6 @@ Save disks (`#SAVEDISK:`) are never auto-inserted into DRIVE_2. | Option | Values | Description | | ------ | ------ | ----------- | -| Playlist Start Disk | First, Previous | Whether to start from disk 0 or resume from the previously used disk | -| Floppy MultiDrive | disabled, enabled | Auto-insert second disk into DRIVE_2 for all playlists | | Disk Control Drive | Drive 1, 2 | Active drive for the Disk Control Interface. | +| Playlist Start Disk | First, Previous | Whether to start from disk 1 or resume from the previously used disk | +| Floppy MultiDrive | Disabled, Enabled | Auto-insert second disk into DRIVE_2 for all playlists |