gdrom: add support for hardware CD-ROM devices

Use libcdio to read CD/DVD/BD drives (linux, windows, bsd)
Get rid of old ioctl win32 driver.
Add detected CDROM devices to game list.
Issue #1654
This commit is contained in:
Flyinghead 2024-12-11 11:25:50 +01:00
parent f5389bcd0b
commit 34961daaec
14 changed files with 328 additions and 393 deletions

View File

@ -14,13 +14,13 @@ jobs:
include:
- operating_system: freebsd
version: '14.2'
pkginstall: sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2
pkginstall: sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip lua54 miniupnpc ninja pkgconf pulseaudio sdl2 libcdio
- operating_system: netbsd
version: '10.0'
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache cmake gcc12 git libao libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2 && export PATH=/usr/pkg/gcc12/bin:$PATH
pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache cmake gcc12 git libao libzip lua54 miniupnpc ninja-build pkgconf pulseaudio SDL2 libcdio && export PATH=/usr/pkg/gcc12/bin:$PATH
- operating_system: openbsd
version: '7.6'
pkginstall: sudo pkg_add ccache cmake git libao libzip miniupnpc ninja pkgconf pulseaudio sdl2
pkginstall: sudo pkg_add ccache cmake git libao libzip miniupnpc ninja pkgconf pulseaudio sdl2 libcdio
exclude:
- architecture: arm64
@ -44,5 +44,5 @@ jobs:
environment_variables: CCACHE_DIR
run: |
${{ matrix.pkginstall }}
cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja
cmake -B build -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release -G Ninja
cmake --build build --config Release

View File

@ -20,11 +20,11 @@ jobs:
- {name: i686-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release}
- {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DUSE_DISCORD=ON, destDir: osx, buildType: RelWithDebInfo}
- {name: apple-ios, os: macos-latest, shell: sh, cmakeArgs: -DCMAKE_SYSTEM_NAME=iOS -G Xcode, destDir: ios, buildType: Release}
- {name: x86_64-pc-linux-gnu, os: ubuntu-20.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, destDir: linux, buildType: RelWithDebInfo}
- {name: x86_64-pc-linux-gnu, os: ubuntu-20.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: linux, buildType: RelWithDebInfo}
- {name: x86_64-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release}
- {name: x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON, destDir: win, buildType: RelWithDebInfo}
- {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-latest, shell: sh, cmakeArgs: -DLIBRETRO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release}
- {name: libretro-x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -G Ninja, buildType: Release}
- {name: x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON -DUSE_LIBCDIO=ON, destDir: win, buildType: RelWithDebInfo}
- {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-latest, shell: sh, cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release}
- {name: libretro-x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -G Ninja, buildType: Release}
steps:
- name: Set up build environment (macOS)
@ -45,7 +45,7 @@ jobs:
run: |
sudo add-apt-repository ppa:christianrauch/libdecoration
sudo apt-get update
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev
sudo apt-get -y install ccache libao-dev libasound2-dev libevdev-dev libgl1-mesa-dev liblua5.3-dev libminiupnpc-dev libpulse-dev libsdl2-dev libudev-dev libzip-dev ninja-build libcurl4-openssl-dev libcdio-dev
sudo apt-get -y install libwayland-dev libdecor-0-dev libaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxkbcommon-dev libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
if: runner.os == 'Linux'
@ -53,7 +53,7 @@ jobs:
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
install: git make mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-lua mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-toolchain
install: git make mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-lua mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-toolchain mingw-w64-x86_64-libcdio
if: matrix.config.shell == 'msys2 {0}'
- name: Set up build environment (Windows, Visual Studio)

View File

@ -9,12 +9,12 @@
variables:
GIT_SUBMODULE_STRATEGY: recursive
CORENAME: flycast
CORE_ARGS: -DLIBRETRO=ON -DCMAKE_BUILD_TYPE=Release
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_BUILD_TYPE=Release
.core-defs-linux:
extends: .core-defs
variables:
CORE_ARGS: -DLIBRETRO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -DCMAKE_BUILD_TYPE=Release
CORE_ARGS: -DLIBRETRO=ON -DUSE_LIBCDIO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -DCMAKE_BUILD_TYPE=Release
.core-defs-osx-x64:
extends: .core-defs
@ -36,7 +36,7 @@
.core-defs-android:
extends: .core-defs
script:
- cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI -DANDROID_ARM_MODE=arm "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR
- cmake -DLIBRETRO=ON -DCMAKE_BUILD_TYPE=Release -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI -DANDROID_ARM_MODE=arm "$CMAKE_SOURCE_ROOT" -B$BUILD_DIR
- cmake --build $BUILD_DIR --target ${CORENAME}_libretro --config Release -- -j $NUMPROC
- mv $BUILD_DIR/${CORENAME}_libretro.so $LIBNAME
- if [ $STRIP_CORE_LIB -eq 1 ]; then $NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip $LIBNAME; fi

View File

@ -76,6 +76,7 @@ option(ENABLE_GDB_SERVER "Build with GDB debugging support" OFF)
option(ENABLE_DC_PROFILER "Build with support for target machine (SH4) profiler" OFF)
option(ENABLE_FC_PROFILER "Build with support for host app (Flycast) profiler" OFF)
option(USE_DISCORD "Use Discord Presence API" OFF)
option(USE_LIBCDIO "Use libcdio for CDROM access" OFF)
if(IOS AND NOT LIBRETRO)
set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE)
@ -672,6 +673,23 @@ if(NOT LIBZIP_FOUND OR NINTENDO_SWITCH)
target_link_libraries(${PROJECT_NAME} PRIVATE libzip::zip)
endif()
if(USE_LIBCDIO)
if(PKG_CONFIG_FOUND)
pkg_check_modules(CDIO IMPORTED_TARGET libcdio)
if(CDIO_FOUND)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_LIBCDIO)
target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::CDIO)
endif()
endif()
if(NOT CDIO_FOUND)
find_package(libcdio)
if(TARGET libcdio::libcdio)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_LIBCDIO)
target_link_libraries(${PROJECT_NAME} PRIVATE libcdio::libcdio)
endif()
endif()
endif()
if(WIN32)
target_include_directories(${PROJECT_NAME} PRIVATE core/deps/dirent)
endif()
@ -1062,13 +1080,13 @@ endif()
target_sources(${PROJECT_NAME} PRIVATE
core/imgread/cdi.cpp
core/imgread/cdio.cpp
core/imgread/chd.cpp
core/imgread/common.cpp
core/imgread/common.h
core/imgread/cue.cpp
core/imgread/gdi.cpp
core/imgread/ImgReader.cpp
core/imgread/ioctl.cpp
core/imgread/iso9660.h
core/imgread/isofs.cpp
core/imgread/isofs.h)

234
core/imgread/cdio.cpp Executable file
View File

@ -0,0 +1,234 @@
/*
Copyright 2024 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "build.h"
#ifdef USE_LIBCDIO
#include "types.h"
#include "imgread/common.h"
#include <cstring>
#include <cdio/cdio.h>
#include <cdio/logging.h>
#include <vector>
namespace hostfs
{
const std::vector<std::string>& getCdromDrives()
{
static std::vector<std::string> cdromDevices;
static bool devicesFetched;
if (devicesFetched)
return cdromDevices;
devicesFetched = true;
// Set a custom log handler
cdio_log_set_handler([](cdio_log_level_t level, const char message[]) {
switch (level)
{
case CDIO_LOG_DEBUG:
DEBUG_LOG(GDROM, "%s", message);
break;
case CDIO_LOG_INFO:
INFO_LOG(GDROM, "%s", message);
break;
case CDIO_LOG_WARN:
WARN_LOG(GDROM, "%s", message);
break;
case CDIO_LOG_ERROR:
case CDIO_LOG_ASSERT:
ERROR_LOG(GDROM, "%s", message);
break;
}
});
// Get the list of all hardware devices
char **list = cdio_get_devices(DRIVER_DEVICE);
if (list != nullptr)
{
for (char **dev = &list[0]; *dev != nullptr; dev++)
cdromDevices.push_back(*dev);
cdio_free_device_list(list);
}
return cdromDevices;
}
}
struct CdioDrive;
struct CdioTrack : public TrackFile
{
CdioTrack(CdioDrive& disk, bool audio)
: disk(disk), audio(audio) {}
bool Read(u32 FAD, u8 *dst, SectorFormat *sector_type, u8 *subcode, SubcodeFormat *subcode_type);
CdioDrive& disk;
bool audio;
};
struct CdioDrive : public Disc
{
bool open(const char *path)
{
const std::vector<std::string>& devices = hostfs::getCdromDrives();
if (!devices.empty())
{
// If the list isn't empty, check that an entry exists for the current path
bool found = false;
for (const std::string& dev : devices)
if (dev == path) {
found = true;
break;
}
if (!found)
return false;
}
pCdio = cdio_open(path, DRIVER_DEVICE);
if (pCdio == nullptr) {
WARN_LOG(GDROM, "Can't open CD device %s", path);
return false;
}
track_t firstTrk = cdio_get_first_track_num(pCdio);
track_t lastTrk = cdio_get_last_track_num(pCdio);
if (firstTrk == CDIO_INVALID_TRACK || lastTrk == CDIO_INVALID_TRACK)
{
WARN_LOG(GDROM, "Can't find first and/or last track");
close();
return false;
}
Session session;
session.StartFAD = 150;
session.FirstTrack = firstTrk;
sessions.push_back(session);
type = CdDA; // TODO more CD types
for (int i = firstTrk; i <= lastTrk; i++)
{
lba_t lba = cdio_get_track_lba(pCdio, i);
if (lba == CDIO_INVALID_LBA)
{
WARN_LOG(GDROM, "Can't find track %d", i);
close();
return false;
}
track_format_t format = cdio_get_track_format(pCdio, i);
bool copy = true;
if (format == TRACK_FORMAT_AUDIO) {
track_flag_t copyFlag = cdio_get_track_copy_permit(pCdio, i);
copy = copyFlag == CDIO_TRACK_FLAG_TRUE;
}
else if (!tracks.empty() && !tracks.back().isDataTrack())
{
// session 1 lead-out & session 2 lead-in and pre-gap
tracks.back().EndFAD -= 11400;
type = CdRom_XA;
Session session;
session.StartFAD = lba;
session.FirstTrack = i;
sessions.push_back(session);
}
Track t;
t.ADR = 1; // FIXME correct?
t.CTRL = format == TRACK_FORMAT_AUDIO ? 0 : CDIO_CDROM_DATA_TRACK;
t.StartFAD = lba;
lsn_t last = cdio_get_track_last_lsn(pCdio, i);
if (last == CDIO_INVALID_LSN)
WARN_LOG(GDROM, "Can't get last lsn of track %d", i);
else
t.EndFAD = cdio_lsn_to_lba(last);
if (i == firstTrk)
sessions.front().StartFAD = t.StartFAD;
INFO_LOG(GDROM, "Track #%d: start %d end %d format %d copy %d", i, t.StartFAD, t.EndFAD, format, copy);
t.file = new CdioTrack(*this, format == TRACK_FORMAT_AUDIO);
tracks.push_back(t);
}
lba_t leadout = cdio_get_track_lba(pCdio, CDIO_CDROM_LEADOUT_TRACK);
if (leadout == CDIO_INVALID_LBA)
{
WARN_LOG(GDROM, "Can't find leadout track");
close();
return false;
}
LeadOut.StartFAD = leadout;
LeadOut.ADR = 1;
LeadOut.CTRL = CDIO_CDROM_DATA_TRACK;
return true;
}
void close()
{
if (pCdio != nullptr) {
cdio_destroy(pCdio);
pCdio = nullptr;
}
}
~CdioDrive() {
close();
}
CdIo_t *pCdio = nullptr;
};
bool CdioTrack::Read(u32 FAD, u8 *dst, SectorFormat *sector_type, u8 *subcode, SubcodeFormat *subcode_type)
{
lsn_t lsn = cdio_lba_to_lsn(FAD);
if (audio)
{
*sector_type = SECFMT_2352;
if (cdio_read_audio_sector(disk.pCdio, dst, lsn) != DRIVER_OP_SUCCESS) {
WARN_LOG(GDROM, "Read audio fad %d failed", FAD);
return false;
}
}
else
{
*sector_type = SECFMT_2048_MODE2_FORM1;
if (cdio_read_mode2_sector(disk.pCdio, dst, lsn, false) != DRIVER_OP_SUCCESS)
{
if (cdio_read_mode1_sector(disk.pCdio, dst, lsn, false) != DRIVER_OP_SUCCESS) {
WARN_LOG(GDROM, "Read data fad %d failed", FAD);
return false;
}
*sector_type = SECFMT_2048_MODE1;
}
}
return true;
}
Disc *cdio_parse(const char *file, std::vector<u8> *digest)
{
INFO_LOG(GDROM, "Opening CDIO device %s", file);
CdioDrive *disk = new CdioDrive();
if (disk->open(file))
{
if (digest != nullptr)
digest->clear();
return disk;
}
else {
delete disk;
return nullptr;
}
}
#endif // USE_LIBCDIO

View File

@ -9,7 +9,7 @@ Disc* chd_parse(const char* file, std::vector<u8> *digest);
Disc* gdi_parse(const char* file, std::vector<u8> *digest);
Disc* cdi_parse(const char* file, std::vector<u8> *digest);
Disc* cue_parse(const char* file, std::vector<u8> *digest);
Disc* ioctl_parse(const char* file, std::vector<u8> *digest);
Disc *cdio_parse(const char *file, std::vector<u8> *digest);
static u32 NullDriveDiscType;
Disc* disc;
@ -21,8 +21,8 @@ constexpr Disc* (*drivers[])(const char* path, std::vector<u8> *digest)
gdi_parse,
cdi_parse,
cue_parse,
#if defined(_WIN32) && !defined(TARGET_UWP)
ioctl_parse,
#ifdef USE_LIBCDIO
cdio_parse,
#endif
};
@ -278,7 +278,7 @@ bool Disc::readSector(u32 FAD, u8 *dst, SectorFormat *sector_type, u8 *subcode,
void Disc::ReadSectors(u32 FAD, u32 count, u8* dst, u32 fmt, LoadProgress *progress)
{
u8 temp[2448];
u8 temp[2352];
SectorFormat secfmt;
SubcodeFormat subfmt;

View File

@ -1,360 +0,0 @@
#include "build.h"
#if defined(_WIN32) && !defined(TARGET_UWP)
#include "types.h"
#include "common.h"
#include <cstddef>
#include <windows.h>
#include <ntddcdrm.h>
#include <ntddscsi.h>
#ifdef _MSC_VER
#define _NTSCSI_USER_MODE_
#include <scsi.h>
#undef _NTSCSI_USER_MODE_
#else
#define CD_RAW_READ_SUBCODE_SIZE ( 96)
#pragma pack(push, cdb, 1)
typedef union _CDB {
struct _READ_CD {
UCHAR OperationCode; // 0xBE - SCSIOP_READ_CD
UCHAR RelativeAddress : 1;
UCHAR Reserved0 : 1;
UCHAR ExpectedSectorType : 3;
UCHAR Lun : 3;
UCHAR StartingLBA[4];
UCHAR TransferBlocks[3];
UCHAR Reserved2 : 1;
UCHAR ErrorFlags : 2;
UCHAR IncludeEDC : 1;
UCHAR IncludeUserData : 1;
UCHAR HeaderCode : 2;
UCHAR IncludeSyncData : 1;
UCHAR SubChannelSelection : 3;
UCHAR Reserved3 : 5;
UCHAR Control;
} READ_CD;
} CDB, *PCDB;
#pragma pack(pop, cdb)
#define READ_TOC_FORMAT_FULL_TOC 0x02
#define SCSIOP_READ 0x28
#define SCSIOP_READ_CD 0xBE
#endif
#define RAW_SECTOR_SIZE 2352
#define CD_SECTOR_SIZE 2048
#define SECTORS_AT_READ 20
#define CD_BLOCKS_PER_SECOND 75
struct spti_s
{
SCSI_PASS_THROUGH_DIRECT sptd;
DWORD alignmentDummy;
BYTE senseBuf[0x12];
};
ULONG msf2fad(const UCHAR Addr[4])
{
ULONG Sectors = ( Addr[0] * (CD_BLOCKS_PER_SECOND*60) ) + ( Addr[1]*CD_BLOCKS_PER_SECOND) + Addr[2];
return Sectors;
}
// Msf: Hours, Minutes, Seconds, Frames
ULONG AddressToSectors( UCHAR Addr[4] );
bool spti_SendCommand(HANDLE hand,spti_s& s,SCSI_ADDRESS& ioctl_addr)
{
s.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
s.sptd.PathId = ioctl_addr.PathId;
s.sptd.TargetId = ioctl_addr.TargetId;
s.sptd.Lun = ioctl_addr.Lun;
s.sptd.TimeOutValue = 30;
//s.sptd.CdbLength = 0x0A;
s.sptd.SenseInfoLength = 0x12;
s.sptd.SenseInfoOffset = offsetof(spti_s, senseBuf);
// s.sptd.DataIn = SCSI_IOCTL_DATA_IN;
// s.sptd.DataTransferLength = 0x800;
// s.sptd.DataBuffer = pdata;
DWORD bytesReturnedIO = 0;
if(!DeviceIoControl(hand, IOCTL_SCSI_PASS_THROUGH_DIRECT, &s, sizeof(s), &s, sizeof(s), &bytesReturnedIO, NULL))
return false;
if(s.sptd.ScsiStatus)
return false;
return true;
}
bool spti_Read10(HANDLE hand,void * pdata,u32 sector,SCSI_ADDRESS& ioctl_addr)
{
spti_s s;
memset(&s,0,sizeof(spti_s));
s.sptd.Cdb[0] = SCSIOP_READ;
s.sptd.Cdb[1] = (ioctl_addr.Lun&7) << 5;// | DPO ; DPO = 8
s.sptd.Cdb[2] = (BYTE)(sector >> 0x18 & 0xFF); // MSB
s.sptd.Cdb[3] = (BYTE)(sector >> 0x10 & 0xFF);
s.sptd.Cdb[4] = (BYTE)(sector >> 0x08 & 0xFF);
s.sptd.Cdb[5] = (BYTE)(sector >> 0x00 & 0xFF); // LSB
s.sptd.Cdb[7] = 0;
s.sptd.Cdb[8] = 1;
s.sptd.CdbLength = 0x0A;
s.sptd.DataIn = SCSI_IOCTL_DATA_IN;
s.sptd.DataTransferLength = 0x800;
s.sptd.DataBuffer = pdata;
return spti_SendCommand(hand,s,ioctl_addr);
}
bool spti_ReadCD(HANDLE hand,void * pdata,u32 sector,SCSI_ADDRESS& ioctl_addr)
{
spti_s s;
memset(&s,0,sizeof(spti_s));
CDB& r = *(PCDB)s.sptd.Cdb;
r.READ_CD.OperationCode = SCSIOP_READ_CD;
r.READ_CD.StartingLBA[0] = (BYTE)(sector >> 0x18 & 0xFF);
r.READ_CD.StartingLBA[1] = (BYTE)(sector >> 0x10 & 0xFF);
r.READ_CD.StartingLBA[2] = (BYTE)(sector >> 0x08 & 0xFF);
r.READ_CD.StartingLBA[3] = (BYTE)(sector >> 0x00 & 0xFF);
// 1 sector
r.READ_CD.TransferBlocks[0] = 0;
r.READ_CD.TransferBlocks[1] = 0;
r.READ_CD.TransferBlocks[2] = 1;
// 0xF8
r.READ_CD.IncludeSyncData = 1;
r.READ_CD.HeaderCode = 3;
r.READ_CD.IncludeUserData = 1;
r.READ_CD.IncludeEDC = 1;
r.READ_CD.SubChannelSelection = 1;
s.sptd.CdbLength = 12;
s.sptd.DataIn = SCSI_IOCTL_DATA_IN;
s.sptd.DataTransferLength = 2448;
s.sptd.DataBuffer = pdata;
return spti_SendCommand(hand,s,ioctl_addr);
}
struct PhysicalDrive;
struct PhysicalTrack:TrackFile
{
PhysicalDrive* disc;
PhysicalTrack(PhysicalDrive* disc) { this->disc=disc; }
bool Read(u32 FAD,u8* dst,SectorFormat* sector_type,u8* subcode,SubcodeFormat* subcode_type) override;
};
struct PhysicalDrive:Disc
{
HANDLE drive;
SCSI_ADDRESS scsi_addr;
bool use_scsi;
PhysicalDrive()
{
drive=INVALID_HANDLE_VALUE;
memset(&scsi_addr,0,sizeof(scsi_addr));
use_scsi=false;
}
bool Build(char* path)
{
drive = CreateFile( path, GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL, OPEN_EXISTING, 0, NULL);
if ( INVALID_HANDLE_VALUE == drive )
return false; //failed to open
printf(" Opened device %s, reading TOC ...",path);
// Get track-table and parse it
CDROM_READ_TOC_EX tocrq={0};
tocrq.Format = READ_TOC_FORMAT_FULL_TOC;
tocrq.Msf=1;
tocrq.SessionTrack=1;
u8 buff[2048];
CDROM_TOC_FULL_TOC_DATA *ftd=(CDROM_TOC_FULL_TOC_DATA*)buff;
ULONG BytesRead;
memset(buff,0,sizeof(buff));
int code = DeviceIoControl(drive,IOCTL_CDROM_READ_TOC_EX,&tocrq,sizeof(tocrq),ftd, 2048, &BytesRead, NULL);
// CDROM_TOC toc;
int currs=-1;
if (0==code)
{
printf(" failed\n");
//failed to read toc
CloseHandle(drive);
return false;
}
else
{
printf(" done !\n");
type=CdRom_XA;
BytesRead-=sizeof(CDROM_TOC_FULL_TOC_DATA);
BytesRead/=sizeof(ftd->Descriptors[0]);
for (u32 i=0;i<BytesRead;i++)
{
if (ftd->Descriptors[i].Point==0xA2)
{
this->EndFAD=msf2fad(ftd->Descriptors[i].Msf);
continue;
}
if (ftd->Descriptors[i].Point>=1 && ftd->Descriptors[i].Point<=0x63 &&
ftd->Descriptors[i].Adr==1)
{
u32 trackn=ftd->Descriptors[i].Point-1;
verify(trackn==tracks.size());
Track t;
t.ADR=ftd->Descriptors[i].Adr;
t.CTRL=ftd->Descriptors[i].Control;
t.StartFAD=msf2fad(ftd->Descriptors[i].Msf);
t.file = new PhysicalTrack(this);
tracks.push_back(t);
if (currs!=ftd->Descriptors[i].SessionNumber)
{
currs=ftd->Descriptors[i].SessionNumber;
verify(sessions.size()==(currs-1));
Session s;
s.FirstTrack=trackn+1;
s.StartFAD=t.StartFAD;
sessions.push_back(s);
}
}
}
LeadOut.StartFAD = EndFAD;
LeadOut.ADR = 1;
LeadOut.CTRL = 4;
}
DWORD bytesReturnedIO = 0;
BOOL resultIO = DeviceIoControl(drive, IOCTL_SCSI_GET_ADDRESS, NULL, 0, &scsi_addr, sizeof(scsi_addr), &bytesReturnedIO, NULL);
//done !
if (resultIO)
use_scsi=true;
else
use_scsi=false;
return true;
}
};
bool PhysicalTrack::Read(u32 FAD,u8* dst,SectorFormat* sector_type,u8* subcode,SubcodeFormat* subcode_type)
{
static u8 temp[2500];
u32 LBA=FAD-150;
if (disc->use_scsi)
{
if (!spti_ReadCD(disc->drive, temp,LBA,disc->scsi_addr))
{
if (spti_Read10(disc->drive, dst,LBA,disc->scsi_addr))
{
//sector read success, just user data
*sector_type=SECFMT_2048_MODE2_FORM1; //m2f1 seems more common ? is there some way to detect it properly here?
return true;
}
}
else
{
//sector read success, with subcode
memcpy(dst, temp, 2352);
memcpy(subcode, temp + 2352, CD_RAW_READ_SUBCODE_SIZE);
*sector_type=SECFMT_2352;
*subcode_type=SUBFMT_96;
return true;
}
}
//hmm, spti failed/cannot be used
//try IOCTL_CDROM_RAW_READ
static __RAW_READ_INFO Info;
Info.SectorCount=1;
Info.DiskOffset.QuadPart = LBA * CD_SECTOR_SIZE; //CD_SECTOR_SIZE, even though we read RAW sectors. Its how winapi works.
ULONG Dummy;
//try all 3 track modes, starting from the one that succeeded last time (Info is static) to save time !
for (int tr=0;tr<3;tr++)
{
if ( 0 == DeviceIoControl( disc->drive, IOCTL_CDROM_RAW_READ, &Info, sizeof(Info), dst, RAW_SECTOR_SIZE, &Dummy, NULL ) )
{
Info.TrackMode=(TRACK_MODE_TYPE)((Info.TrackMode+1)%3); //try next mode
}
else
{
//sector read success
*sector_type=SECFMT_2352;
return true;
}
}
//finally, try ReadFile
if (SetFilePointer(disc->drive,LBA*2048,0,FILE_BEGIN)!=INVALID_SET_FILE_POINTER)
{
DWORD BytesRead;
if (FALSE!=ReadFile(disc->drive,dst,2048,&BytesRead,0) && BytesRead==2048)
{
//sector read success, just user data
*sector_type=SECFMT_2048_MODE2_FORM1; //m2f1 seems more common ? is there some way to detect it properly here?
return true;
}
}
printf("IOCTL: Totally failed to read sector @LBA %d\n", LBA);
return false;
}
Disc* ioctl_parse(const char* file, std::vector<u8> *digest)
{
if (strlen(file)==3 && GetDriveType(file)==DRIVE_CDROM)
{
printf("Opening device %s ...",file);
char fn[]={ '\\', '\\', '.', '\\', file[0],':', '\0' };
PhysicalDrive* rv = new PhysicalDrive();
if (rv->Build(fn))
{
if (digest != nullptr)
digest->clear();
return rv;
}
else
{
delete rv;
return 0;
}
}
else
{
return 0;
}
}
#endif

View File

@ -315,6 +315,13 @@ void saveScreenshot(const std::string& name, const std::vector<u8>& data)
#endif
#ifndef USE_LIBCDIO
const std::vector<std::string>& getCdromDrives() {
static std::vector<std::string> empty;
return empty;
}
#endif
} // namespace hostfs
void os_CreateWindow()

View File

@ -63,6 +63,7 @@ namespace hostfs
std::string getShaderCachePath(const std::string& filename);
void saveScreenshot(const std::string& name, const std::vector<u8>& data);
const std::vector<std::string>& getCdromDrives();
#ifdef __ANDROID__
void importHomeDirectory();
void exportHomeDirectory();

View File

@ -194,24 +194,43 @@ public:
info.size = st.st_size;
info.updateTime = st.st_mtime;
#else // _WIN32
std::string lpath(path);
if (path.length() == 2 && isalpha(path[0]) && path[1] == ':')
/* D: -> \\.\D:\ */
lpath = "\\\\.\\" + path + "\\";
else if (path.substr(0, 4) == "\\\\.\\" && path.length() == 6)
/* \\.\D: -> \\.\D:\ */
lpath += "\\";
nowide::wstackstring wname;
if (wname.convert(path.c_str()))
if (wname.convert(lpath.c_str()))
{
WIN32_FILE_ATTRIBUTE_DATA fileAttribs;
if (GetFileAttributesExW(wname.get(), GetFileExInfoStandard, &fileAttribs))
if (lpath.substr(0, 4) == "\\\\.\\")
{
info.isDirectory = (fileAttribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info.size = fileAttribs.nFileSizeLow + ((u64)fileAttribs.nFileSizeHigh << 32);
u64 t = ((u64)fileAttribs.ftLastWriteTime.dwHighDateTime << 32) | fileAttribs.ftLastWriteTime.dwLowDateTime;
info.updateTime = t / 10000000 - 11644473600LL; // 100-nano to secs minus (unix epoch - windows epoch)
// Win32 device namespace
UINT type = GetDriveTypeW(wname.get());
if (type != DRIVE_CDROM)
throw StorageException("Invalid device " + lpath.substr(4, 2));
info.isDirectory = false;
info.isWritable = false;
}
else
{
const int error = GetLastError();
if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND)
INFO_LOG(COMMON, "Cannot get attributes of '%s' error 0x%x", path.c_str(), error);
_set_errno(error);
throw StorageException("Cannot get attributes of " + path);
WIN32_FILE_ATTRIBUTE_DATA fileAttribs;
if (GetFileAttributesExW(wname.get(), GetFileExInfoStandard, &fileAttribs))
{
info.isDirectory = (fileAttribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info.size = fileAttribs.nFileSizeLow + ((u64)fileAttribs.nFileSizeHigh << 32);
u64 t = ((u64)fileAttribs.ftLastWriteTime.dwHighDateTime << 32) | fileAttribs.ftLastWriteTime.dwLowDateTime;
info.updateTime = t / 10000000 - 11644473600LL; // 100-nano to secs minus (unix epoch - windows epoch)
}
else
{
const int error = GetLastError();
if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND)
INFO_LOG(COMMON, "Cannot get attributes of '%s' error 0x%x", lpath.c_str(), error);
_set_errno(error);
throw StorageException("Cannot get attributes of " + lpath);
}
}
}
else

View File

@ -152,9 +152,22 @@ void GameScanner::fetch_game_list()
}
std::string dcbios = hostfs::findFlash("dc_", "%bios.bin;%boot.bin");
{
const std::vector<std::string>& cdromDrives = hostfs::getCdromDrives();
LockGuard _(mutex);
// CD-ROM devices
for (auto it = cdromDrives.rbegin(); it != cdromDrives.rend(); ++it)
{
std::string name;
if (it->substr(0, 4) == "\\\\.\\")
name = it->substr(4);
else
name = *it;
game_list.insert(game_list.begin(), { name, *it, name, "", true });
}
// Dreamcast BIOS
if (!dcbios.empty())
game_list.insert(game_list.begin(), { "Dreamcast BIOS" });
// Arcade games
game_list.insert(game_list.end(), arcade_game_list.begin(), arcade_game_list.end());
}
if (running)

View File

@ -26,11 +26,13 @@
#include <unordered_map>
#include <unordered_set>
struct GameMedia {
struct GameMedia
{
std::string name; // Display name
std::string path; // Full path to rom. May be an encoded uri
std::string fileName; // Last component of the path, decoded
std::string gameName; // for arcade games only, description from the rom list
bool device = false; // Corresponds to a physical cdrom device
};
class GameScanner

View File

@ -3275,7 +3275,7 @@ static void gui_display_content()
if (gui_state == GuiState::SelectDisk)
{
std::string extension = get_file_extension(game.path);
if (extension != "gdi" && extension != "chd"
if (!game.device && extension != "gdi" && extension != "chd"
&& extension != "cdi" && extension != "cue")
// Only dreamcast disks
continue;
@ -3285,7 +3285,7 @@ static void gui_display_content()
}
std::string gameName = game.name;
GameBoxart art;
if (config::BoxartDisplayMode)
if (config::BoxartDisplayMode && !game.device)
{
art = boxart.getBoxartAndLoad(game);
gameName = art.name;

View File

@ -72,6 +72,7 @@ SHLIBS=(
libsqlite3.so.0
libcrypt.so.1
libbsd.so.0
libcdio.so.18
)
if [ ! -f appimagetool-x86_64.AppImage ]; then