diff --git a/CMakeLists.txt b/CMakeLists.txt index c19576986a..dd092d6631 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,13 @@ if (APPLE) # MACOSX_BUNDLE_ICON_FILE "icon.icns" ) + if (NOT IOS) + set_target_properties(dingusppc PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/cmake/DingusPPCInfo.plist.in" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUNDLE_ID}" + ) + endif() + find_library(COCOA_LIBRARY Cocoa) find_library(IOKIT_LIBRARY IOKit) find_library(COREAUDIO_LIBRARY CoreAudio) diff --git a/cmake/DingusPPCInfo.plist.in b/cmake/DingusPPCInfo.plist.in new file mode 100644 index 0000000000..c3001e9d43 --- /dev/null +++ b/cmake/DingusPPCInfo.plist.in @@ -0,0 +1,70 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeName + CD-ROM image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + LSItemContentTypes + + public.iso-image + com.roxio.disk-image-toast + + + + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + UTImportedTypeDeclarations + + + UTTypeConformsTo + + public.disk-image + + UTTypeDescription + Toast disk image + UTTypeIdentifier + com.roxio.disk-image-toast + UTTypeTagSpecification + + public.filename-extension + + toast + + + + + + diff --git a/core/hostevents.h b/core/hostevents.h index d9b10888c0..2d1ef8ee1f 100644 --- a/core/hostevents.h +++ b/core/hostevents.h @@ -25,6 +25,7 @@ along with this program. If not, see . #include #include +#include class WindowEvent { public: @@ -119,6 +120,20 @@ class GamepadEvent { uint8_t button; }; +enum class CdromInsertionResult { + SUCCESS, + NO_DRIVE, + MEDIA_PRESENT, + IMAGE_OPEN_FAILED, +}; + +class CdromImageEvent { +public: + std::string image_path; + CdromInsertionResult result = CdromInsertionResult::NO_DRIVE; + bool handled = false; +}; + class EventManager { public: static EventManager* get_instance() { @@ -131,6 +146,9 @@ class EventManager { void poll_events(); void set_keyboard_locale(uint32_t keyboard_id); void post_keyboard_state_events(); + void post_cdrom_event(CdromImageEvent& event) { + _cdrom_signal.emit(event); + } template void add_window_handler(T *inst, void (T::*func)(const WindowEvent&)) { @@ -152,6 +170,11 @@ class EventManager { _gamepad_signal.connect_method(inst, func); } + template + void add_cdrom_handler(T* inst, void (T::*func)(CdromImageEvent&)) { + _cdrom_signal.connect_method(inst, func); + } + template void add_post_handler(T *inst, void (T::*func)()) { _post_signal.connect_method(inst, func); @@ -162,6 +185,7 @@ class EventManager { _mouse_signal.disconnect_all(); _keyboard_signal.disconnect_all(); _gamepad_signal.disconnect_all(); + _cdrom_signal.disconnect_all(); _post_signal.disconnect_all(); } @@ -179,6 +203,7 @@ class EventManager { CoreSignal _mouse_signal; CoreSignal _keyboard_signal; CoreSignal _gamepad_signal; + CoreSignal _cdrom_signal; CoreSignal<> _post_signal; uint64_t events_captured = 0; diff --git a/core/hostevents_sdl.cpp b/core/hostevents_sdl.cpp index 91de0aac55..8c9ea07a56 100644 --- a/core/hostevents_sdl.cpp +++ b/core/hostevents_sdl.cpp @@ -322,6 +322,35 @@ void EventManager::poll_events() { } break; + case SDL_DROPFILE: { + const char* path = event.drop.file; + // TODO: Distinguish CD-ROM and floppy images once dynamic + // floppy insertion is supported. + CdromImageEvent cdrom_event{}; + cdrom_event.image_path = path; + this->post_cdrom_event(cdrom_event); + + switch (cdrom_event.result) { + case CdromInsertionResult::SUCCESS: + LOG_F(INFO, "Inserted CD-ROM image: %s", path); + break; + case CdromInsertionResult::NO_DRIVE: + LOG_F(ERROR, "Cannot insert CD-ROM image; no CD-ROM drive is available: %s", + path); + break; + case CdromInsertionResult::MEDIA_PRESENT: + LOG_F(ERROR, "Cannot insert CD-ROM image; eject the current media first: %s", + path); + break; + case CdromInsertionResult::IMAGE_OPEN_FAILED: + LOG_F(ERROR, "Cannot insert CD-ROM image: %s", path); + break; + } + + SDL_free(event.drop.file); + } + break; + default: unhandled_events++; } diff --git a/devices/common/ata/atapicdrom.cpp b/devices/common/ata/atapicdrom.cpp index b3ca23d8b4..d43750ae50 100644 --- a/devices/common/ata/atapicdrom.cpp +++ b/devices/common/ata/atapicdrom.cpp @@ -71,7 +71,8 @@ int AtapiCdrom::device_postinit() { std::string cdr_image_path = GET_STR_PROP("cdr_img"); if (!cdr_image_path.empty()) { - this->insert_image(cdr_image_path); + if (!this->insert_image(cdr_image_path)) + return -1; } return 0; diff --git a/devices/common/ata/atapicdrom.h b/devices/common/ata/atapicdrom.h index e5631dd377..faa427c759 100644 --- a/devices/common/ata/atapicdrom.h +++ b/devices/common/ata/atapicdrom.h @@ -38,7 +38,7 @@ class AtapiCdrom : public AtapiBaseDevice, public ScsiCdromCmds { return std::unique_ptr(new AtapiCdrom("ATAPI-CDROM")); } - bool is_device_ready() override { return this->is_ready; } + bool is_device_ready() override { return this->medium_present(); } uint8_t not_ready_reason() override { return ScsiError::MEDIUM_NOT_PRESENT; } int device_postinit() override; diff --git a/devices/common/scsi/scsi.h b/devices/common/scsi/scsi.h index c6844529bd..e026871723 100644 --- a/devices/common/scsi/scsi.h +++ b/devices/common/scsi/scsi.h @@ -175,6 +175,14 @@ enum ScsiCommand : uint8_t { READ_CD = 0xBE, }; +/** START STOP UNIT command flags in CDB byte 4. */ +namespace ScsiStartStopUnit { + enum : uint8_t { + START = 1 << 0, + LOAD_EJECT = 1 << 1, + }; +} + enum ScsiSense : uint8_t { NO_SENSE = 0x0, RECOVERED = 0x1, @@ -200,8 +208,10 @@ enum ScsiError : uint8_t { INVALID_CDB = 0x24, INVALID_LUN = 0x25, WRITE_PROTECT = 0x27, + MEDIUM_CHANGED = 0x28, SAVING_NOT_SUPPORTED = 0x39, MEDIUM_NOT_PRESENT = 0x3A, + REMOVAL_PREVENTED = 0x53, }; /** SCSI device types used in INQUIRY. */ diff --git a/devices/common/scsi/scsiblockcmds.cpp b/devices/common/scsi/scsiblockcmds.cpp index adda7c62f4..58186c8904 100644 --- a/devices/common/scsi/scsiblockcmds.cpp +++ b/devices/common/scsi/scsiblockcmds.cpp @@ -179,17 +179,20 @@ int ScsiBlockCmds::write() { } int ScsiBlockCmds::start_stop_unit() { - if (this->cdb_ptr[4] & 1) { - if (this->cdb_ptr[4] & 2) + if (this->cdb_ptr[4] & ScsiStartStopUnit::START) { + if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) LOG_F(INFO, "START_STOP_UNIT: medium load requested"); } else { - if (this->cdb_ptr[4] & 2) { - LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); - } + if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) + this->eject_medium(); } return ScsiPhase::STATUS; } +void ScsiBlockCmds::eject_medium() { + LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); +} + int ScsiBlockCmds::read_capacity() { uint32_t lba = this->get_lba(); diff --git a/devices/common/scsi/scsiblockcmds.h b/devices/common/scsi/scsiblockcmds.h index 464b945eb4..fe83b73d2c 100644 --- a/devices/common/scsi/scsiblockcmds.h +++ b/devices/common/scsi/scsiblockcmds.h @@ -63,6 +63,7 @@ class ScsiBlockCmds : public ScsiCommonCmds { protected: virtual void process_command() override; + virtual void eject_medium(); uint32_t get_lba(); diff --git a/devices/common/scsi/scsibus.cpp b/devices/common/scsi/scsibus.cpp index 432ddef29b..4e341fa0d9 100644 --- a/devices/common/scsi/scsibus.cpp +++ b/devices/common/scsi/scsibus.cpp @@ -354,7 +354,8 @@ void ScsiBus::attach_scsi_devices(const std::string bus_suffix) gMachineObj->add_device(scsi_device_name, std::unique_ptr(scsi_device)); this->register_device(scsi_id, scsi_device); - scsi_device->insert_image(path); + if (!scsi_device->insert_image(path)) + ABORT_F("Could not insert CD-ROM image, %s", path.c_str()); } else { LOG_F(ERROR, "%s: Too many devices. CD-ROM \"%s\" was not added.", diff --git a/devices/common/scsi/scsicdrom.h b/devices/common/scsi/scsicdrom.h index a46a75c8ec..bdf42bd272 100644 --- a/devices/common/scsi/scsicdrom.h +++ b/devices/common/scsi/scsicdrom.h @@ -43,7 +43,8 @@ class ScsiCdrom : public ScsiPhysDevice, public ScsiCdromCmds { } protected: - bool is_device_ready() override { return this->is_ready; } + bool is_device_ready() override { return this->medium_present(); } + uint8_t not_ready_reason() override { return ScsiError::MEDIUM_NOT_PRESENT; } void mode_select_6(uint8_t param_len); diff --git a/devices/common/scsi/scsicdromcmds.cpp b/devices/common/scsi/scsicdromcmds.cpp index 481a298957..67946c8f40 100644 --- a/devices/common/scsi/scsicdromcmds.cpp +++ b/devices/common/scsi/scsicdromcmds.cpp @@ -62,6 +62,30 @@ void ScsiCdromCmds::process_command() { phy_impl->switch_phase(next_phase); } +int ScsiCdromCmds::test_unit_ready() { + if (this->consume_media_change()) { + this->sense_key = ScsiSense::UNIT_ATTENTION; + this->asc = ScsiError::MEDIUM_CHANGED; + this->ascq = 0; + phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key); + return ScsiPhase::STATUS; + } + + return ScsiCommonCmds::test_unit_ready(); +} + +void ScsiCdromCmds::eject_medium() { + if (this->drive_locked) { + LOG_F(INFO, "START_STOP_UNIT: medium eject prevented"); + this->field_ptr_valid = false; + this->bit_ptr_valid = false; + this->illegal_request(ScsiError::REMOVAL_PREVENTED, 2, false); + } else { + LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); + this->eject_image(); + } +} + int ScsiCdromCmds:: read_toc() { uint8_t start_track, session_num; int tot_tracks, resp_len = 0; diff --git a/devices/common/scsi/scsicdromcmds.h b/devices/common/scsi/scsicdromcmds.h index 85a91db571..f3cd2bffe0 100644 --- a/devices/common/scsi/scsicdromcmds.h +++ b/devices/common/scsi/scsicdromcmds.h @@ -35,6 +35,8 @@ class ScsiCdromCmds : public ScsiBlockCmds, public CdromDrive { protected: virtual void process_command() override; + int test_unit_ready() override; + void eject_medium() override; int get_apple_page_49(uint8_t subpage, uint8_t ctrl, uint8_t *out_ptr, int avail_len); diff --git a/devices/common/scsi/scsihd.cpp b/devices/common/scsi/scsihd.cpp index 0e3013f447..fde42cba56 100644 --- a/devices/common/scsi/scsihd.cpp +++ b/devices/common/scsi/scsihd.cpp @@ -56,7 +56,7 @@ ScsiHardDisk::ScsiHardDisk(std::string name, int my_id) : ScsiPhysDevice(name, m } void ScsiHardDisk::insert_image(std::string filename) { - if (this->set_host_file(filename) < 0) + if (!this->attach_image(filename)) ABORT_F("%s: could not open image file %s", this->name.c_str(), filename.c_str()); this->is_writeable = true; diff --git a/devices/common/scsi/scsihd.h b/devices/common/scsi/scsihd.h index 908c783df9..65ff6cf834 100644 --- a/devices/common/scsi/scsihd.h +++ b/devices/common/scsi/scsihd.h @@ -40,7 +40,7 @@ class ScsiHardDisk : public ScsiPhysDevice, public BlockStorageDevice, public Sc void process_command() override; protected: - bool is_device_ready() override { return this->is_ready; } + bool is_device_ready() override { return this->image_attached(); } void mode_select_6(uint8_t param_len); diff --git a/devices/storage/blockstoragedevice.cpp b/devices/storage/blockstoragedevice.cpp index f37ef47abf..599113ecba 100644 --- a/devices/storage/blockstoragedevice.cpp +++ b/devices/storage/blockstoragedevice.cpp @@ -39,22 +39,45 @@ BlockStorageDevice::BlockStorageDevice(const uint32_t cache_blocks, } BlockStorageDevice::~BlockStorageDevice() { - this->img_file.close(); + this->detach_image(); } -int BlockStorageDevice::set_host_file(std::string file_path) { - this->is_ready = false; - +bool BlockStorageDevice::attach_image(const std::string& file_path) { if (!this->img_file.open(file_path)) - return -1; + return false; - this->size_bytes = this->img_file.size(); + this->is_ready = false; + this->size_bytes = this->img_file.size(); + this->remain_size = 0; + this->write_size = 0; + this->data_offset = 0; this->set_fpos(0); this->is_ready = true; - return this->set_block_size(this->block_size); + if (this->set_block_size(this->block_size) < 0) { + this->detach_image(); + return false; + } + + return true; +} + +bool BlockStorageDevice::detach_image() { + if (!this->is_ready) + return false; + + this->is_ready = false; + this->img_file.close(); + + this->size_bytes = 0; + this->size_blocks = 0; + this->cur_fpos = 0; + this->write_size = 0; + this->remain_size = 0; + + return true; } int BlockStorageDevice::set_block_size(const int blk_size) { diff --git a/devices/storage/blockstoragedevice.h b/devices/storage/blockstoragedevice.h index ed4fa6158a..db061e6e95 100644 --- a/devices/storage/blockstoragedevice.h +++ b/devices/storage/blockstoragedevice.h @@ -36,7 +36,10 @@ class BlockStorageDevice { const uint64_t max_blocks=UINT64_MAX); ~BlockStorageDevice(); - int set_host_file(std::string file_path); + bool attach_image(const std::string& file_path); + bool detach_image(); + bool image_attached() const { return this->is_ready; } + int set_block_size(const int blk_size); int set_fpos(const uint64_t lba); diff --git a/devices/storage/cdromdrive.cpp b/devices/storage/cdromdrive.cpp index 18379e1114..a6549e6474 100644 --- a/devices/storage/cdromdrive.cpp +++ b/devices/storage/cdromdrive.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . /** @file Virtual CD-ROM device implementation. */ +#include #include #include @@ -29,12 +30,46 @@ along with this program. If not, see . #include CdromDrive::CdromDrive() : BlockStorageDevice(31, CDR_STD_DATA_SIZE, 0xfffffffe) { + EventManager::get_instance()->add_cdrom_handler( + this, &CdromDrive::insert_image_event_handler); + this->is_writeable = false; } -void CdromDrive::insert_image(std::string filename) { - if (this->set_host_file(filename) < 0) - ABORT_F("Could not open CD-ROM image file, %s", filename.c_str()); +void CdromDrive::insert_image_event_handler(CdromImageEvent& event) { + if (event.handled) + return; + + if (this->medium_present()) { + event.result = CdromInsertionResult::MEDIA_PRESENT; + return; + } + + event.handled = true; + if (this->insert_image(event.image_path)) { + this->media_change_pending = true; + event.result = CdromInsertionResult::SUCCESS; + } else { + event.result = CdromInsertionResult::IMAGE_OPEN_FAILED; + } +} + +bool CdromDrive::consume_media_change() { + bool pending = this->media_change_pending; + this->media_change_pending = false; + return pending; +} + +bool CdromDrive::insert_image(const std::string& filename) { + if (this->medium_present()) { + LOG_F(ERROR, "Cannot insert CD-ROM image while media is present"); + return false; + } + + if (!this->attach_image(filename)) { + LOG_F(ERROR, "Could not open CD-ROM image file, %s", filename.c_str()); + return false; + } this->detect_raw_image(); @@ -45,6 +80,19 @@ void CdromDrive::insert_image(std::string filename) { // create Lead-out descriptor containing all data this->tracks[1] = {LEAD_OUT_TRK_NUM, /*.trk_num*/ 0x14, /*.adr_ctrl*/ static_cast(this->size_blocks + 1) /*.start_lba*/}; + + return true; +} + +bool CdromDrive::eject_image() { + if (!this->detach_image()) + return false; + + this->num_tracks = 0; + this->media_change_pending = false; + std::memset(this->tracks, 0, sizeof(this->tracks)); + + return true; } bool CdromDrive::detect_raw_image() { diff --git a/devices/storage/cdromdrive.h b/devices/storage/cdromdrive.h index 4fbfb32783..66ac2914f7 100644 --- a/devices/storage/cdromdrive.h +++ b/devices/storage/cdromdrive.h @@ -74,16 +74,22 @@ constexpr auto CDROM_MAX_TRACKS = 100; constexpr auto LEAD_OUT_TRK_NUM = 0xAA; constexpr auto CDR_STD_DATA_SIZE = 2048; +class CdromImageEvent; + class CdromDrive : public BlockStorageDevice { public: CdromDrive(); virtual ~CdromDrive() = default; - bool medium_present() { return this->is_ready; } + bool medium_present() const { return this->image_attached(); } - void insert_image(std::string filename); + bool insert_image(const std::string& filename); + bool eject_image(); protected: + void insert_image_event_handler(CdromImageEvent& event); + bool consume_media_change(); + uint8_t hex_to_bcd(const uint8_t val); AddrMsf lba_to_msf(const int lba); bool detect_raw_image(); @@ -101,6 +107,7 @@ class CdromDrive : public BlockStorageDevice { uint8_t sw_lock_sup = 1; // drive supports locking/unlocking via SW uint8_t sw_eject_sup = 1; // drive supports ejecting via SW uint8_t drive_locked = 0; // 1 - drive is currently locked + bool media_change_pending = false; // pending guest notification uint8_t prevent_jump = 0; // prevent jumper not present uint8_t more_support = 0; uint16_t max_rd_speed = 706; // defaults to 4x