From 34961daaec20bf35ceba648b2adba10db4ead81d Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 11 Dec 2024 11:25:50 +0100 Subject: [PATCH] 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 --- .github/workflows/bsd.yml | 8 +- .github/workflows/c-cpp.yml | 12 +- .gitlab-ci.yml | 6 +- CMakeLists.txt | 20 +- core/imgread/cdio.cpp | 234 +++++++++++++++++++++++ core/imgread/common.cpp | 8 +- core/imgread/ioctl.cpp | 360 ----------------------------------- core/oslib/oslib.cpp | 7 + core/oslib/oslib.h | 1 + core/oslib/storage.cpp | 43 +++-- core/ui/game_scanner.cpp | 13 ++ core/ui/game_scanner.h | 4 +- core/ui/gui.cpp | 4 +- shell/linux/make-appimage.sh | 1 + 14 files changed, 328 insertions(+), 393 deletions(-) create mode 100755 core/imgread/cdio.cpp delete mode 100644 core/imgread/ioctl.cpp diff --git a/.github/workflows/bsd.yml b/.github/workflows/bsd.yml index 580bbe90ea..7beecc5839 100644 --- a/.github/workflows/bsd.yml +++ b/.github/workflows/bsd.yml @@ -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 diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 3954dee584..79ec624ff4 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -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) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d2f50b97ed..b3cc580a8b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7114589fbf..89fc30afb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/core/imgread/cdio.cpp b/core/imgread/cdio.cpp new file mode 100755 index 0000000000..4d4f71b4bd --- /dev/null +++ b/core/imgread/cdio.cpp @@ -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 . + */ +#include "build.h" +#ifdef USE_LIBCDIO +#include "types.h" +#include "imgread/common.h" +#include +#include +#include +#include + +namespace hostfs +{ + +const std::vector& getCdromDrives() +{ + static std::vector 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& 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 *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 diff --git a/core/imgread/common.cpp b/core/imgread/common.cpp index 6e71992413..e40c24d5fc 100644 --- a/core/imgread/common.cpp +++ b/core/imgread/common.cpp @@ -9,7 +9,7 @@ Disc* chd_parse(const char* file, std::vector *digest); Disc* gdi_parse(const char* file, std::vector *digest); Disc* cdi_parse(const char* file, std::vector *digest); Disc* cue_parse(const char* file, std::vector *digest); -Disc* ioctl_parse(const char* file, std::vector *digest); +Disc *cdio_parse(const char *file, std::vector *digest); static u32 NullDriveDiscType; Disc* disc; @@ -21,8 +21,8 @@ constexpr Disc* (*drivers[])(const char* path, std::vector *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; diff --git a/core/imgread/ioctl.cpp b/core/imgread/ioctl.cpp deleted file mode 100644 index 6b879e0eff..0000000000 --- a/core/imgread/ioctl.cpp +++ /dev/null @@ -1,360 +0,0 @@ -#include "build.h" -#if defined(_WIN32) && !defined(TARGET_UWP) -#include "types.h" -#include "common.h" - -#include -#include - -#include -#include - -#ifdef _MSC_VER -#define _NTSCSI_USER_MODE_ -#include -#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;iDescriptors[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 *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 diff --git a/core/oslib/oslib.cpp b/core/oslib/oslib.cpp index cb2c3aaa9a..4595ed42d6 100644 --- a/core/oslib/oslib.cpp +++ b/core/oslib/oslib.cpp @@ -315,6 +315,13 @@ void saveScreenshot(const std::string& name, const std::vector& data) #endif +#ifndef USE_LIBCDIO +const std::vector& getCdromDrives() { + static std::vector empty; + return empty; +} +#endif + } // namespace hostfs void os_CreateWindow() diff --git a/core/oslib/oslib.h b/core/oslib/oslib.h index 7074c8346e..e55fffea31 100644 --- a/core/oslib/oslib.h +++ b/core/oslib/oslib.h @@ -63,6 +63,7 @@ namespace hostfs std::string getShaderCachePath(const std::string& filename); void saveScreenshot(const std::string& name, const std::vector& data); + const std::vector& getCdromDrives(); #ifdef __ANDROID__ void importHomeDirectory(); void exportHomeDirectory(); diff --git a/core/oslib/storage.cpp b/core/oslib/storage.cpp index 97d21f1ce6..dbb1546d94 100644 --- a/core/oslib/storage.cpp +++ b/core/oslib/storage.cpp @@ -194,24 +194,43 @@ class StdStorage : public Storage 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 diff --git a/core/ui/game_scanner.cpp b/core/ui/game_scanner.cpp index 0dba947380..d56dd179eb 100644 --- a/core/ui/game_scanner.cpp +++ b/core/ui/game_scanner.cpp @@ -152,9 +152,22 @@ void GameScanner::fetch_game_list() } std::string dcbios = hostfs::findFlash("dc_", "%bios.bin;%boot.bin"); { + const std::vector& 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) diff --git a/core/ui/game_scanner.h b/core/ui/game_scanner.h index f6f5901d8f..e92b17ddbb 100644 --- a/core/ui/game_scanner.h +++ b/core/ui/game_scanner.h @@ -26,11 +26,13 @@ #include #include -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 diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index e826a6b77f..286dc66fab 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -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; diff --git a/shell/linux/make-appimage.sh b/shell/linux/make-appimage.sh index 67f594dc88..e8dddbfdd0 100755 --- a/shell/linux/make-appimage.sh +++ b/shell/linux/make-appimage.sh @@ -72,6 +72,7 @@ SHLIBS=( libsqlite3.so.0 libcrypt.so.1 libbsd.so.0 + libcdio.so.18 ) if [ ! -f appimagetool-x86_64.AppImage ]; then