mirror of
https://github.com/libretro/same_cdi.git
synced 2026-11-07 12:37:58 -10:00
Add disc control and M3U playlists
Multi-disc titles such as Creature Shock ask for the next disc while running, but the core could only be given one image and had no way to change it without restarting the content. Register the libretro disk control interface, preferring the extended version (labels, initial-image restore) and falling back to the basic one. Content can be a single CHD, ISO or CUE, or an .m3u playlist whose entries are resolved relative to the playlist. Eject and insert go through the MAME CD-ROM image device, which now reports media changes on a callback instead of the machine being reset, so the application keeps its RAM. On a change, the CDIC drops the outstanding read and byte-order detection belonging to the old disc and picks up the new file handle, and the SLAVE reports the removal before the insertion, even when both happen while emulation is paused, so the firmware sees the tray cycle. A command strobe to DBUF no longer restarts the sector bank sequence: the driver remembers the last bank it was given, and seeing it twice made the first volume descriptor read after a swap return stale data, so the application asked for the disc again. The SLAVE also re-raises its interrupt for any response still pending, so a pointer packet cannot swallow a simultaneous drive status packet. Loading a state keeps whatever disc is inserted. libretro states now end with an identity of the mounted disc; restoring one taken with another disc reports a media change so the application rereads. Older states without it fall back to comparing the CDIC sector buffers against the mounted disc, and only report a change when both buffers hold valid Mode 2 sectors that differ from it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
da688beae2
commit
21f9b7701d
10 files changed
+493
-16
No files matched your search
@@ -7,6 +7,32 @@ SAME_CDI is a ***S***ingle ***A***rcade/***M***achine ***E***mulator for libretr
|
||||
=======
|
||||
Game files can be either CHD, ISO, or BIN/CUE. Game support/functionality is whatever the MAME version included supports.
|
||||
|
||||
*MULTI-DISC GAMES*
|
||||
=======
|
||||
Discs can be changed without resetting the running game using RetroArch's
|
||||
**Quick Menu > Disc Control**. When loading a single CHD, ISO or CUE, use
|
||||
**Load New Disc** to browse to another image (select the CUE for BIN/CUE sets).
|
||||
Resume the game and confirm its insert-disc prompt if required.
|
||||
|
||||
For a list of selectable discs, load an `.m3u` text file containing one image
|
||||
path per line. Relative paths are resolved from the playlist's directory;
|
||||
absolute paths, blank lines and `#` comments are also supported. For example,
|
||||
place this playlist in the directory containing the two disc folders:
|
||||
|
||||
```text
|
||||
Creature Shock (Europe) (Disc 1)/Creature Shock (Europe) (Disc 1).cue
|
||||
Creature Shock (Europe) (Disc 2)/Creature Shock (Europe) (Disc 2).cue
|
||||
```
|
||||
|
||||
Use **Eject Disc**, choose **Current Disc Index**, then **Insert Disc**. The
|
||||
extended interface supplies disc filenames as labels and supports RetroArch's
|
||||
remembered initial disc; older frontends can use the basic interface.
|
||||
Loading a save state keeps the currently inserted disc. When restoring an
|
||||
insert-disc prompt with the requested disc already mounted, the core reports
|
||||
the media change so the game can refresh its disc information. New saves record
|
||||
the disc identity; older saves use a conservative sector-buffer check. For
|
||||
states taken during gameplay, insert the matching disc before restoring.
|
||||
|
||||
*BIOS*
|
||||
=======
|
||||
BIOS file (cdimono1.zip) is required, and can go either in the same directory as your ```CHD|ISO|CUE``` files or in the ```retroarch_system_dir/same_cdi/bios/``` directory
|
||||
|
||||
@@ -31,7 +31,8 @@ cdrom_image_device::cdrom_image_device(const machine_config &mconfig, device_typ
|
||||
device_image_interface(mconfig, *this),
|
||||
m_cdrom_handle(nullptr),
|
||||
m_extension_list(nullptr),
|
||||
m_interface(nullptr)
|
||||
m_interface(nullptr),
|
||||
m_media_change_cb(*this)
|
||||
{
|
||||
}
|
||||
//-------------------------------------------------
|
||||
@@ -61,6 +62,7 @@ void cdrom_image_device::device_config_complete()
|
||||
|
||||
void cdrom_image_device::device_start()
|
||||
{
|
||||
m_media_change_cb.resolve_safe();
|
||||
// try to locate the CHD from a DISK_REGION
|
||||
chd_file *chd = machine().rom_load().get_disk_handle(owner()->tag() );
|
||||
if( chd != nullptr )
|
||||
@@ -112,6 +114,7 @@ image_init_result cdrom_image_device::call_load()
|
||||
if (!m_cdrom_handle)
|
||||
goto error;
|
||||
|
||||
m_media_change_cb(1);
|
||||
return image_init_result::PASS;
|
||||
|
||||
error:
|
||||
@@ -129,4 +132,5 @@ void cdrom_image_device::call_unload()
|
||||
m_cdrom_handle = nullptr;
|
||||
if( m_self_chd.opened() )
|
||||
m_self_chd.close();
|
||||
m_media_change_cb(0);
|
||||
}
|
||||
@@ -31,6 +31,13 @@ public:
|
||||
virtual ~cdrom_image_device();
|
||||
|
||||
void set_interface(const char *interface) { m_interface = interface; }
|
||||
auto media_change_callback() { return m_media_change_cb.bind(); }
|
||||
void notify_media_change()
|
||||
{
|
||||
m_media_change_cb(0);
|
||||
if (m_cdrom_handle)
|
||||
m_media_change_cb(1);
|
||||
}
|
||||
|
||||
// image-level overrides
|
||||
virtual image_init_result call_load() override;
|
||||
@@ -64,6 +71,7 @@ protected:
|
||||
cdrom_file *m_cdrom_handle;
|
||||
const char *m_extension_list;
|
||||
const char *m_interface;
|
||||
devcb_write_line m_media_change_cb;
|
||||
};
|
||||
|
||||
// device type definition
|
||||
|
||||
@@ -608,7 +608,10 @@ void cdi_state::cdimono1(machine_config &config)
|
||||
|
||||
cdimono1_dvc(config);
|
||||
|
||||
CDROM(config, "cdrom").set_interface("cdi_cdrom");
|
||||
auto &cdrom = CDROM(config, "cdrom");
|
||||
cdrom.set_interface("cdi_cdrom");
|
||||
cdrom.media_change_callback().set(m_cdic, FUNC(cdicdic_device::media_changed));
|
||||
cdrom.media_change_callback().append(m_slave_hle, FUNC(cdislave_hle_device::media_changed));
|
||||
SOFTWARE_LIST(config, "cd_list").set_original("cdi");
|
||||
}
|
||||
|
||||
|
||||
@@ -552,9 +552,9 @@ void cdicdic_device::descramble_sector(uint8_t *buffer)
|
||||
}
|
||||
}
|
||||
|
||||
bool cdicdic_device::is_valid_sector(const uint8_t *buffer)
|
||||
bool cdicdic_device::is_valid_sector(const uint8_t *buffer, uint32_t lba)
|
||||
{
|
||||
const uint32_t real_lba = m_curr_lba + 150;
|
||||
const uint32_t real_lba = lba + 150;
|
||||
const uint8_t mins = real_lba / (60 * 75);
|
||||
const uint8_t secs = (real_lba / 75) % 60;
|
||||
const uint8_t frac = real_lba % 75;
|
||||
@@ -698,6 +698,14 @@ uint8_t cdicdic_device::get_sector_count_for_coding(uint8_t coding)
|
||||
|
||||
void cdicdic_device::process_disc_sector()
|
||||
{
|
||||
// The console image device owns this handle and may replace it at runtime.
|
||||
if (m_cdrom_dev)
|
||||
m_cd = m_cdrom_dev->get_cdrom_file();
|
||||
if (!m_cd)
|
||||
{
|
||||
cancel_disc_read();
|
||||
return;
|
||||
}
|
||||
const uint32_t real_lba = m_curr_lba + 150;
|
||||
const uint8_t mins = real_lba / (60 * 75);
|
||||
const uint8_t secs = (real_lba / 75) % 60;
|
||||
@@ -723,13 +731,13 @@ void cdicdic_device::process_disc_sector()
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid_sector(buffer))
|
||||
if (!is_valid_sector(buffer, m_curr_lba))
|
||||
{
|
||||
uint8_t descramble_buffer[2560];
|
||||
memcpy(descramble_buffer, buffer, sizeof(descramble_buffer));
|
||||
descramble_sector(descramble_buffer);
|
||||
|
||||
if (is_valid_sector(descramble_buffer))
|
||||
if (is_valid_sector(descramble_buffer, m_curr_lba))
|
||||
memcpy(buffer, descramble_buffer, sizeof(descramble_buffer));
|
||||
}
|
||||
|
||||
@@ -1102,6 +1110,15 @@ void cdicdic_device::regs_w(offs_t offset, uint16_t data, uint16_t mem_mask)
|
||||
break;
|
||||
|
||||
case 0x3ffe/2:
|
||||
// Accepting a command must not restart the sector-buffer sequence.
|
||||
// The driver remembers the last delivered bank; seeing it twice
|
||||
// makes it consume the other bank as a missed sector. In particular,
|
||||
// the first volume-descriptor read after a disc swap then returns
|
||||
// stale data and the application asks for the disc a second time.
|
||||
// Keep the bank on command strobes; ordinary writes (including a
|
||||
// stop/reset) retain their existing behaviour.
|
||||
if (data & mem_mask & 0x8000)
|
||||
mem_mask &= ~0x0001;
|
||||
COMBINE_DATA(&m_data_buffer);
|
||||
if (m_data_buffer & 0x8000)
|
||||
handle_cdic_command();
|
||||
@@ -1145,6 +1162,64 @@ void cdicdic_device::cancel_disc_read()
|
||||
m_disc_spinup_counter = 0;
|
||||
}
|
||||
|
||||
void cdicdic_device::media_changed(int state)
|
||||
{
|
||||
// Do not reset the machine: multi-disc applications must keep their RAM.
|
||||
// Byte order detection and the outstanding read belong to the old medium.
|
||||
m_cd = m_cdrom_dev->get_cdrom_file();
|
||||
m_cd_byteswap = false;
|
||||
cancel_disc_read();
|
||||
}
|
||||
|
||||
bool cdicdic_device::legacy_media_mismatch()
|
||||
{
|
||||
// Old frontend states have no medium identity. Only infer a mismatch if
|
||||
// both data banks have valid Mode 2 headers and differ from this medium.
|
||||
// A matching bank, writable scratch data, or an unreadable sector is not
|
||||
// sufficient evidence to disturb a restored read or audio playback.
|
||||
if (!m_cdrom_dev || !m_cdrom_dev->get_cdrom_file())
|
||||
return false;
|
||||
auto *cd = m_cdrom_dev->get_cdrom_file();
|
||||
for (unsigned bank = 0; bank < 2; ++bank)
|
||||
{
|
||||
uint8_t saved[2560] = { 0 };
|
||||
std::fill_n(saved + 1, 10, 0xff);
|
||||
const auto *ram = reinterpret_cast<const uint16_t *>(m_ram.get() + bank * 0xa00);
|
||||
for (unsigned i = 0; i < SECTOR_SIZE - SECTOR_HEADER; ++i)
|
||||
saved[SECTOR_HEADER + i] = ram[i / 2] >> ((i & 1) ? 0 : 8);
|
||||
if (saved[SECTOR_MODE] != 2)
|
||||
return false;
|
||||
unsigned msf[3];
|
||||
for (unsigned i = 0; i < 3; ++i)
|
||||
{
|
||||
const uint8_t bcd = saved[SECTOR_HEADER + i];
|
||||
if ((bcd & 15) > 9 || (bcd >> 4) > 9)
|
||||
return false;
|
||||
msf[i] = (bcd >> 4) * 10 + (bcd & 15);
|
||||
}
|
||||
const unsigned frame = (msf[0] * 60 + msf[1]) * 75 + msf[2];
|
||||
if (msf[1] >= 60 || msf[2] >= 75 || frame < 150)
|
||||
return false;
|
||||
const unsigned lba = frame - 150;
|
||||
if (!is_valid_sector(saved, lba))
|
||||
return false;
|
||||
if (lba >= cdrom_get_track_start(cd, 0xaa))
|
||||
continue;
|
||||
uint8_t current[2560] = { 0 };
|
||||
if (!cdrom_read_data(cd, lba, current, CD_TRACK_RAW_DONTCARE))
|
||||
return false;
|
||||
if (current[0] == 0xff && current[1] == 0)
|
||||
for (unsigned i = 0; i < SECTOR_SIZE; i += 2)
|
||||
std::swap(current[i], current[i + 1]);
|
||||
if (!is_valid_sector(current, lba))
|
||||
descramble_sector(current);
|
||||
if (!is_valid_sector(current, lba) ||
|
||||
!memcmp(saved + SECTOR_HEADER, current + SECTOR_HEADER, SECTOR_SIZE - SECTOR_HEADER))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void cdicdic_device::handle_cdic_command()
|
||||
{
|
||||
switch (m_command)
|
||||
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
uint8_t intack_r();
|
||||
bool intreq() const;
|
||||
void atten_w(uint32_t state);
|
||||
void media_changed(int state);
|
||||
bool legacy_media_mismatch();
|
||||
|
||||
protected:
|
||||
// device-level overrides
|
||||
@@ -206,7 +208,7 @@ private:
|
||||
void process_audio_map();
|
||||
|
||||
void descramble_sector(uint8_t *buffer);
|
||||
bool is_valid_sector(const uint8_t *buffer);
|
||||
static bool is_valid_sector(const uint8_t *buffer, uint32_t lba);
|
||||
bool is_mode2_sector_selected(const uint8_t *buffer);
|
||||
bool is_mode2_audio_selected(const uint8_t *buffer);
|
||||
|
||||
|
||||
@@ -57,6 +57,29 @@ void cdislave_hle_device::prepare_readback(const attotime &delay, uint8_t channe
|
||||
m_interrupt_timer->adjust(delay);
|
||||
}
|
||||
|
||||
void cdislave_hle_device::media_changed(int state)
|
||||
{
|
||||
if (machine().phase() != machine_phase::RUNNING)
|
||||
return;
|
||||
// Frontends can eject and insert while emulation is paused. The firmware
|
||||
// must see the removal before the new disc, even if no frame ran between
|
||||
// the callbacks. Keep the removal in the existing (saved) response buffer;
|
||||
// slave_r will report the inserted medium after this packet is consumed.
|
||||
if (state && m_channel[3].m_out_count && m_channel[3].m_out_cmd == 0xb0 &&
|
||||
m_channel[3].m_out_buf[2] == 0x03 && m_channel[3].m_out_buf[3] == 0x11)
|
||||
return;
|
||||
prepare_readback(attotime::zero, 3, 4, 0xb0, 0x00, state ? 0x02 : 0x03, state ? 0x15 : 0x11, 0xb0);
|
||||
}
|
||||
|
||||
void cdislave_hle_device::disc_status()
|
||||
{
|
||||
// Do not replace a pending removal with a status query for the new disc.
|
||||
if (m_channel[3].m_out_count && m_channel[3].m_out_cmd == 0xb0)
|
||||
return;
|
||||
const bool present = !m_cdrom || m_cdrom->get_cdrom_file();
|
||||
prepare_readback(attotime::from_hz(4), 3, 4, 0xb0, 0x00, present ? 0x02 : 0x03, present ? 0x15 : 0x11, 0xb0);
|
||||
}
|
||||
|
||||
INPUT_CHANGED_MEMBER( cdislave_hle_device::mouse_update )
|
||||
{
|
||||
const uint8_t button_state = m_mousebtn->read();
|
||||
@@ -136,11 +159,27 @@ uint16_t cdislave_hle_device::slave_r(offs_t offset)
|
||||
}
|
||||
m_channel[offset].m_out_index++;
|
||||
m_channel[offset].m_out_count--;
|
||||
// All readback channels share the interrupt line. A pointer response
|
||||
// must not acknowledge a simultaneous drive-status response as well.
|
||||
// Reassert for any packet whose first byte has not yet been consumed.
|
||||
for (const auto &channel : m_channel)
|
||||
if (channel.m_out_count && channel.m_out_index == 0)
|
||||
switch (channel.m_out_cmd)
|
||||
{
|
||||
case 0xb0: case 0xb1: case 0xf0:
|
||||
case 0xf3: case 0xf4: case 0xf7:
|
||||
m_interrupt_timer->adjust(attotime::zero);
|
||||
break;
|
||||
}
|
||||
if (!m_channel[offset].m_out_count)
|
||||
{
|
||||
const bool removed = offset == 3 && m_channel[offset].m_out_cmd == 0xb0 &&
|
||||
m_channel[offset].m_out_buf[2] == 0x03 && m_channel[offset].m_out_buf[3] == 0x11;
|
||||
m_channel[offset].m_out_index = 0;
|
||||
m_channel[offset].m_out_cmd = 0;
|
||||
memset(m_channel[offset].m_out_buf, 0, 4);
|
||||
if (removed && m_cdrom && m_cdrom->get_cdrom_file())
|
||||
prepare_readback(attotime::from_hz(10000), 3, 4, 0xb0, 0x00, 0x02, 0x15, 0xb0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -328,7 +367,7 @@ void cdislave_hle_device::slave_w(offs_t offset, uint16_t data)
|
||||
memset(m_in_buf, 0, 17);
|
||||
m_in_index = 0;
|
||||
m_in_count = 0;
|
||||
prepare_readback(attotime::from_hz(4), 3, 4, 0xb0, 0x00, 0x02, 0x15, 0xb0);
|
||||
disc_status();
|
||||
break;
|
||||
//case 0xb1: // Request Disc Base
|
||||
//memset(m_in_buf, 0, 17);
|
||||
@@ -403,6 +442,7 @@ cdislave_hle_device::cdislave_hle_device(const machine_config &mconfig, const ch
|
||||
: device_t(mconfig, CDI_SLAVE_HLE, tag, owner, clock)
|
||||
, m_int_callback(*this)
|
||||
, m_dmadac(*this, ":dac%u", 1U)
|
||||
, m_cdrom(*this, ":cdrom")
|
||||
, m_atten_w(*this)
|
||||
, m_mousex(*this, "MOUSEX")
|
||||
, m_mousey(*this, "MOUSEY")
|
||||
|
||||
@@ -21,6 +21,7 @@ TODO:
|
||||
#pragma once
|
||||
|
||||
#include "sound/dmadac.h"
|
||||
#include "imagedev/chd_cd.h"
|
||||
|
||||
//**************************************************************************
|
||||
// TYPE DEFINITIONS
|
||||
@@ -46,6 +47,7 @@ public:
|
||||
uint16_t slave_r(offs_t offset);
|
||||
void slave_w(offs_t offset, uint16_t data);
|
||||
void slave_w_mouse(offs_t offset, uint16_t data);
|
||||
void media_changed(int state);
|
||||
|
||||
protected:
|
||||
// device-level overrides
|
||||
@@ -62,10 +64,12 @@ private:
|
||||
bool m_pal = true; // Physical SLAVE standard input, not guest-writable state.
|
||||
void prepare_readback(const attotime &delay, uint8_t channel, uint8_t count, uint8_t data0, uint8_t data1, uint8_t data2, uint8_t data3, uint8_t cmd);
|
||||
void set_mouse_position();
|
||||
void disc_status();
|
||||
|
||||
devcb_write_line m_int_callback;
|
||||
|
||||
required_device_array<dmadac_sound_device, 2> m_dmadac;
|
||||
optional_device<cdrom_image_device> m_cdrom;
|
||||
devcb_write32 m_atten_w;
|
||||
|
||||
required_ioport m_mousex;
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:vibecodekun
|
||||
// Libretro disc list and tray control. Included by libretro.cpp.
|
||||
#ifndef SAME_CDI_DISK_CONTROL_H
|
||||
#define SAME_CDI_DISK_CONTROL_H
|
||||
|
||||
#include "imagedev/chd_cd.h"
|
||||
#include "corefile.h"
|
||||
#include "corestr.h"
|
||||
#include "machine/cdicdic.h"
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace disk_control {
|
||||
|
||||
static std::vector<std::string> images;
|
||||
static unsigned index = 0;
|
||||
static bool ejected = false;
|
||||
static bool loaded = false;
|
||||
static unsigned initial_index = 0;
|
||||
static std::string initial_path;
|
||||
|
||||
static void clear()
|
||||
{
|
||||
images.clear();
|
||||
index = 0;
|
||||
ejected = loaded = false;
|
||||
initial_index = 0;
|
||||
initial_path.clear();
|
||||
}
|
||||
|
||||
static bool is_disc(const std::string &path)
|
||||
{
|
||||
const auto dot = path.find_last_of('.');
|
||||
if (dot == std::string::npos)
|
||||
return false;
|
||||
const char *ext = path.c_str() + dot;
|
||||
return !core_stricmp(ext, ".cue") || !core_stricmp(ext, ".chd") || !core_stricmp(ext, ".iso");
|
||||
}
|
||||
|
||||
static cdrom_image_device *drive()
|
||||
{
|
||||
auto *manager = mame_machine_manager::instance();
|
||||
return loaded && manager && manager->machine()
|
||||
? manager->machine()->root_device().subdevice<cdrom_image_device>("cdrom") : nullptr;
|
||||
}
|
||||
|
||||
// Frontend state extension: the medium itself stays selected by the frontend.
|
||||
// Record its identity so restoring RAM from another disc can notify the guest
|
||||
// without unloading content or disturbing same-medium runahead restores.
|
||||
static constexpr size_t state_size = 16;
|
||||
|
||||
static uint64_t media_identity()
|
||||
{
|
||||
auto *cdrom = drive();
|
||||
if (!cdrom || !cdrom->get_cdrom_file())
|
||||
return 0;
|
||||
uint64_t hash = UINT64_C(14695981039346656037);
|
||||
const char *path = cdrom->filename();
|
||||
if (path)
|
||||
for (; *path; ++path)
|
||||
{
|
||||
const uint8_t c = *path == '\\' ? '/' : uint8_t(*path);
|
||||
hash = (hash ^ c) * UINT64_C(1099511628211);
|
||||
}
|
||||
return hash ? hash : 1;
|
||||
}
|
||||
|
||||
static void write_state(uint8_t *data)
|
||||
{
|
||||
memcpy(data, "CDIMEDIA", 8);
|
||||
const uint64_t id = media_identity();
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
data[8 + i] = uint8_t(id >> (i * 8));
|
||||
}
|
||||
|
||||
static bool valid_state(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size >= state_size && !memcmp(data, "CDIMEDIA", 8))
|
||||
return true;
|
||||
// Older serializers could zero-pad a larger frontend buffer. Preserve
|
||||
// that compatibility, but reject a truncated or damaged media extension.
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
if (data[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void restored(const uint8_t *data, size_t size)
|
||||
{
|
||||
auto *cdrom = drive();
|
||||
if (!cdrom)
|
||||
return;
|
||||
bool mismatch = false;
|
||||
if (size >= state_size && !memcmp(data, "CDIMEDIA", 8))
|
||||
{
|
||||
uint64_t saved = 0;
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
saved |= uint64_t(data[8 + i]) << (i * 8);
|
||||
mismatch = saved != media_identity();
|
||||
}
|
||||
else if (auto *cdic = cdrom->machine().root_device().subdevice<cdicdic_device>("cdic"))
|
||||
mismatch = cdic->legacy_media_mismatch();
|
||||
if (mismatch)
|
||||
cdrom->notify_media_change();
|
||||
}
|
||||
|
||||
static bool set_eject_state(bool state)
|
||||
{
|
||||
auto *cdrom = drive();
|
||||
if (!cdrom)
|
||||
return false;
|
||||
if (state == ejected)
|
||||
return true;
|
||||
if (state)
|
||||
cdrom->unload();
|
||||
else if (index < images.size())
|
||||
{
|
||||
if (images[index].empty() || cdrom->load(images[index]) != image_init_result::PASS)
|
||||
{
|
||||
if (log_cb)
|
||||
log_cb(RETRO_LOG_ERROR, "Unable to insert disc: %s\n", images[index].c_str());
|
||||
return false; // Keep the tray open so the frontend can retry.
|
||||
}
|
||||
}
|
||||
ejected = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_eject_state() { return ejected; }
|
||||
static unsigned get_image_index() { return index; }
|
||||
static unsigned get_num_images() { return unsigned(images.size()); }
|
||||
|
||||
static bool set_image_index(unsigned value)
|
||||
{
|
||||
if (!loaded || !ejected)
|
||||
return false;
|
||||
// Every out-of-range index means an empty tray in the libretro API.
|
||||
index = value < images.size() ? value : unsigned(images.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool replace_image_index(unsigned value, const retro_game_info *info)
|
||||
{
|
||||
if (!loaded || !ejected || value >= images.size())
|
||||
return false;
|
||||
if (info)
|
||||
{
|
||||
if (!info->path || !is_disc(info->path))
|
||||
return false;
|
||||
images[value] = info->path;
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool removed_selected = index == value;
|
||||
images.erase(images.begin() + value);
|
||||
if (removed_selected)
|
||||
index = unsigned(images.size());
|
||||
else if (index > value)
|
||||
--index;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool add_image_index()
|
||||
{
|
||||
if (!loaded || images.size() == std::numeric_limits<unsigned>::max())
|
||||
return false;
|
||||
const bool no_disc = index == images.size();
|
||||
images.emplace_back();
|
||||
if (no_disc)
|
||||
index = unsigned(images.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool set_initial_image(unsigned value, const char *path)
|
||||
{
|
||||
if (loaded || !path || !*path)
|
||||
return false;
|
||||
initial_index = value;
|
||||
initial_path = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool copy_string(const std::string &value, char *out, size_t size)
|
||||
{
|
||||
if (!out || !size)
|
||||
return false;
|
||||
out[0] = '\0';
|
||||
if (value.empty() || value.size() >= size)
|
||||
return false;
|
||||
memcpy(out, value.c_str(), value.size() + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_image_path(unsigned value, char *out, size_t size)
|
||||
{
|
||||
return copy_string(value < images.size() ? images[value] : std::string(), out, size);
|
||||
}
|
||||
|
||||
static bool get_image_label(unsigned value, char *out, size_t size)
|
||||
{
|
||||
if (value >= images.size())
|
||||
return copy_string({}, out, size);
|
||||
std::string label = images[value];
|
||||
const auto slash = label.find_last_of("/\\");
|
||||
if (slash != std::string::npos)
|
||||
label.erase(0, slash + 1);
|
||||
const auto dot = label.find_last_of('.');
|
||||
if (dot != std::string::npos)
|
||||
label.erase(dot);
|
||||
return copy_string(label, out, size);
|
||||
}
|
||||
|
||||
static void register_interface()
|
||||
{
|
||||
static retro_disk_control_ext_callback extended = {
|
||||
set_eject_state, get_eject_state, get_image_index, set_image_index,
|
||||
get_num_images, replace_image_index, add_image_index,
|
||||
set_initial_image, get_image_path, get_image_label
|
||||
};
|
||||
static retro_disk_control_callback basic = {
|
||||
set_eject_state, get_eject_state, get_image_index, set_image_index,
|
||||
get_num_images, replace_image_index, add_image_index
|
||||
};
|
||||
unsigned version = 0;
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION, &version) && version >= 1 &&
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE, &extended))
|
||||
return;
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE, &basic);
|
||||
}
|
||||
|
||||
// Resolve playlist entries relative to the playlist, not the process directory.
|
||||
static bool prepare(const char *path)
|
||||
{
|
||||
images.clear();
|
||||
index = 0;
|
||||
ejected = loaded = false;
|
||||
if (!path || !*path)
|
||||
return false;
|
||||
const std::string content(path);
|
||||
const auto dot = content.find_last_of('.');
|
||||
if (dot != std::string::npos && !core_stricmp(content.c_str() + dot, ".m3u"))
|
||||
{
|
||||
util::core_file::ptr file;
|
||||
if (util::core_file::open(content, OPEN_FLAG_READ, file))
|
||||
return false;
|
||||
const auto slash = content.find_last_of("/\\");
|
||||
const std::string directory = slash == std::string::npos ? "" : content.substr(0, slash + 1);
|
||||
// core_file::gets does not terminate a full buffer and normalises EOL to CR.
|
||||
char buffer[4096] = {};
|
||||
while (file->gets(buffer, sizeof(buffer) - 1))
|
||||
{
|
||||
std::string line(buffer);
|
||||
if (line.size() == sizeof(buffer) - 1 && line.back() != '\r')
|
||||
return false;
|
||||
if (line.compare(0, 3, "\xef\xbb\xbf") == 0)
|
||||
line.erase(0, 3);
|
||||
const auto first = line.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos || line[first] == '#')
|
||||
continue;
|
||||
line = line.substr(first, line.find_last_not_of(" \t\r\n") - first + 1);
|
||||
if (!is_disc(line))
|
||||
return false;
|
||||
if (line[0] != '/' && line[0] != '\\' && !(line.size() > 1 && line[1] == ':'))
|
||||
line = directory + line;
|
||||
images.push_back(std::move(line));
|
||||
}
|
||||
}
|
||||
else if (is_disc(content))
|
||||
images.push_back(content);
|
||||
if (images.empty())
|
||||
return false;
|
||||
if (initial_index < images.size() && images[initial_index] == initial_path)
|
||||
index = initial_index;
|
||||
initial_index = 0;
|
||||
initial_path.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace disk_control
|
||||
#endif
|
||||
@@ -87,6 +87,8 @@ unsigned int videoBuffer[4096*3072];
|
||||
retro_video_refresh_t video_cb = NULL;
|
||||
retro_environment_t environ_cb = NULL;
|
||||
|
||||
#include "disk_control.h"
|
||||
|
||||
/* FIXME: re-add way to handle OGL */
|
||||
#if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES)
|
||||
#include "retroogl.c"
|
||||
@@ -630,7 +632,7 @@ void retro_get_system_info(struct retro_system_info *info)
|
||||
|
||||
info->library_name = "SAME_CDI";
|
||||
info->library_version = bare_build_version;
|
||||
info->valid_extensions = "chd|iso|cue";
|
||||
info->valid_extensions = "chd|iso|cue|m3u";
|
||||
info->need_fullpath = true;
|
||||
info->block_extract = true;
|
||||
}
|
||||
@@ -679,6 +681,8 @@ extern int mmain2(int argc, const char *argv[]);
|
||||
|
||||
void retro_init (void)
|
||||
{
|
||||
disk_control::clear();
|
||||
disk_control::register_interface();
|
||||
struct retro_log_callback log;
|
||||
const char *system_dir = NULL;
|
||||
const char *content_dir = NULL;
|
||||
@@ -750,6 +754,7 @@ void retro_deinit(void)
|
||||
{
|
||||
printf("RETRO DEINIT\n");
|
||||
if(retro_load_ok)retro_finish();
|
||||
disk_control::clear();
|
||||
retro_load_ok = false;
|
||||
mfirst=1;
|
||||
}
|
||||
@@ -853,6 +858,18 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
{
|
||||
char basename[256];
|
||||
|
||||
if (!info || !disk_control::prepare(info->path))
|
||||
{
|
||||
disk_control::clear();
|
||||
return false;
|
||||
}
|
||||
const std::string &disc_path = disk_control::images[disk_control::index];
|
||||
if (disc_path.size() >= sizeof(RPATH))
|
||||
{
|
||||
disk_control::clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
check_variables();
|
||||
|
||||
//FIXME: re-add way to handle 16/32 bit
|
||||
@@ -881,8 +898,8 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
#endif
|
||||
|
||||
extract_basename(basename, info->path, sizeof(basename));
|
||||
extract_directory(g_rom_dir, info->path, sizeof(g_rom_dir));
|
||||
strcpy(RPATH,info->path);
|
||||
extract_directory(g_rom_dir, disc_path.c_str(), sizeof(g_rom_dir));
|
||||
strcpy(RPATH, disc_path.c_str());
|
||||
|
||||
/* Bring MAME up here rather than on the first retro_run().
|
||||
*
|
||||
@@ -908,6 +925,7 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
{
|
||||
retro_pause = -1;
|
||||
retro_load_ok = false;
|
||||
disk_control::clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -915,11 +933,13 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
update_runtime_variables();
|
||||
}
|
||||
|
||||
disk_control::loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void retro_unload_game(void)
|
||||
{
|
||||
disk_control::clear();
|
||||
if ( mame_machine_manager::instance() != NULL && mame_machine_manager::instance()->machine() != NULL &&
|
||||
mame_machine_manager::instance()->machine()->options().autosave() &&
|
||||
(mame_machine_manager::instance()->machine()->system().flags & MACHINE_SUPPORTS_SAVE) != 0)
|
||||
@@ -935,7 +955,7 @@ size_t retro_serialize_size(void)
|
||||
ram_state::get_size(mame_machine_manager::instance()->machine()->save()) > 0)
|
||||
return ram_state::get_size(mame_machine_manager::instance()->machine()->save()) +
|
||||
mame_machine_manager::instance()->machine()->sound().state_size() +
|
||||
mame_machine_manager::instance()->machine()->ioport().state_size();
|
||||
mame_machine_manager::instance()->machine()->ioport().state_size() + disk_control::state_size;
|
||||
return 0;
|
||||
}
|
||||
bool retro_serialize(void *data, size_t size)
|
||||
@@ -947,10 +967,11 @@ bool retro_serialize(void *data, size_t size)
|
||||
size_t base = ram_state::get_size(machine.save());
|
||||
size_t audio_size = machine.sound().state_size();
|
||||
size_t input_size = machine.ioport().state_size();
|
||||
if (!data || size < base + audio_size + input_size)
|
||||
if (!data || size < base + audio_size + input_size + disk_control::state_size)
|
||||
return false;
|
||||
// Frontends may provide a larger buffer; initialise its padding too.
|
||||
memset(data, 0, size);
|
||||
disk_control::write_state(static_cast<u8 *>(data) + base + audio_size + input_size);
|
||||
return machine.save().write_buffer(data, base) == STATERR_NONE &&
|
||||
machine.sound().write_state(static_cast<u8 *>(data) + base, audio_size) &&
|
||||
machine.ioport().write_state(static_cast<u8 *>(data) + base + audio_size, input_size);
|
||||
@@ -970,12 +991,14 @@ bool retro_unserialize(const void *data, size_t size)
|
||||
return false;
|
||||
// A state holding only the MAME save layout comes from an older
|
||||
// build and loads as before. Anything longer carries the audio and
|
||||
// input trailers, which are validated before anything is changed.
|
||||
// input trailers, and then the inserted disc's identity, all of which
|
||||
// are validated before anything is changed.
|
||||
bool has_audio = size >= base + audio_size + input_size;
|
||||
const u8 *audio = static_cast<const u8 *>(data) + base;
|
||||
if ((size != base && !has_audio) ||
|
||||
(has_audio && (!machine.sound().read_state(audio, audio_size, true) ||
|
||||
!machine.ioport().read_state(audio + audio_size, input_size, true))))
|
||||
!machine.ioport().read_state(audio + audio_size, input_size, true) ||
|
||||
!disk_control::valid_state(audio + audio_size + input_size, size - base - audio_size - input_size))))
|
||||
return false;
|
||||
// Restore input before device postload re-baselines the SLAVE pointer.
|
||||
std::vector<u8> previous_input;
|
||||
@@ -993,7 +1016,16 @@ bool retro_unserialize(const void *data, size_t size)
|
||||
}
|
||||
// Legacy states use the stream postload fallback. New states replace
|
||||
// that approximation with their captured history after device postload.
|
||||
return !has_audio || machine.sound().read_state(audio, audio_size);
|
||||
const bool restored = !has_audio || machine.sound().read_state(audio, audio_size);
|
||||
if (restored)
|
||||
{
|
||||
// Loading a state keeps the disc that is inserted now. If the state
|
||||
// was taken with another disc in the drive, tell the guest the
|
||||
// medium changed so that it rereads the disc it now has.
|
||||
const size_t media_offset = has_audio ? base + audio_size + input_size : base;
|
||||
disk_control::restored(static_cast<const u8 *>(data) + media_offset, size - media_offset);
|
||||
}
|
||||
return restored;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user