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_sdl.cpp b/core/hostevents_sdl.cpp index 91de0aac55..b0859e5382 100644 --- a/core/hostevents_sdl.cpp +++ b/core/hostevents_sdl.cpp @@ -23,6 +23,7 @@ along with this program. If not, see . #include #include #include +#include #include #include @@ -322,6 +323,33 @@ void EventManager::poll_events() { } break; + case SDL_DROPFILE: { + const char* path = event.drop.file; + CdromInsertionResult result = gMachineObj + ? gMachineObj->insert_cdrom_image(path) + : CdromInsertionResult::NO_DRIVE; + + switch (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/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..96d19347a5 100644 --- a/devices/common/scsi/scsiblockcmds.cpp +++ b/devices/common/scsi/scsiblockcmds.cpp @@ -179,11 +179,11 @@ 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) { + if (this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT) { LOG_F(INFO, "START_STOP_UNIT: medium eject requested"); } } 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..ef9a693240 100644 --- a/devices/common/scsi/scsicdrom.h +++ b/devices/common/scsi/scsicdrom.h @@ -44,6 +44,7 @@ class ScsiCdrom : public ScsiPhysDevice, public ScsiCdromCmds { protected: bool is_device_ready() override { return this->is_ready; } + 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..ef6097de3a 100644 --- a/devices/common/scsi/scsicdromcmds.cpp +++ b/devices/common/scsi/scsicdromcmds.cpp @@ -62,6 +62,43 @@ void ScsiCdromCmds::process_command() { phy_impl->switch_phase(next_phase); } +int ScsiCdromCmds::test_unit_ready() { + if (this->take_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(); +} + +int ScsiCdromCmds::start_stop_unit() { + bool load_eject = this->cdb_ptr[4] & ScsiStartStopUnit::LOAD_EJECT; + bool start = this->cdb_ptr[4] & ScsiStartStopUnit::START; + + if (!load_eject) + return ScsiPhase::STATUS; + + if (start) { + LOG_F(INFO, "START_STOP_UNIT: medium load requested"); + return ScsiPhase::STATUS; + } + + 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(); + } + + return ScsiPhase::STATUS; +} + 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..28fa36f3d1 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; + int start_stop_unit() override; int get_apple_page_49(uint8_t subpage, uint8_t ctrl, uint8_t *out_ptr, int avail_len); diff --git a/devices/storage/blockstoragedevice.cpp b/devices/storage/blockstoragedevice.cpp index f37ef47abf..c427ac76f7 100644 --- a/devices/storage/blockstoragedevice.cpp +++ b/devices/storage/blockstoragedevice.cpp @@ -39,16 +39,18 @@ BlockStorageDevice::BlockStorageDevice(const uint32_t cache_blocks, } BlockStorageDevice::~BlockStorageDevice() { - this->img_file.close(); + if (this->is_ready) + this->img_file.close(); } int BlockStorageDevice::set_host_file(std::string file_path) { - this->is_ready = false; - if (!this->img_file.open(file_path)) return -1; + this->is_ready = false; this->size_bytes = this->img_file.size(); + this->remain_size = 0; + this->write_size = 0; this->set_fpos(0); @@ -66,10 +68,15 @@ int BlockStorageDevice::set_block_size(const int blk_size) { return -1; } - this->cache_size = this->cache_blocks * this->raw_blk_size; + uint32_t new_cache_size = this->cache_blocks * this->raw_blk_size; + // Preserve pointers held by an in-progress transfer when the cache geometry + // does not change during a media replacement. + if (new_cache_size != this->cache_size) { + this->cache_size = new_cache_size; - // allocate data cache and fill it with zeroes - this->data_cache = std::unique_ptr(new char[this->cache_size] ()); + // allocate data cache and fill it with zeroes + this->data_cache = std::unique_ptr(new char[this->cache_size] ()); + } return 0; } diff --git a/devices/storage/cdromdrive.cpp b/devices/storage/cdromdrive.cpp index 18379e1114..9b3943bcd1 100644 --- a/devices/storage/cdromdrive.cpp +++ b/devices/storage/cdromdrive.cpp @@ -32,10 +32,18 @@ CdromDrive::CdromDrive() : BlockStorageDevice(31, CDR_STD_DATA_SIZE, 0xfffffffe) 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()); +bool CdromDrive::insert_image(const std::string& filename, bool notify_guest) { + if (this->medium_present()) { + LOG_F(ERROR, "Cannot insert CD-ROM image while media is present"); + return false; + } + + if (this->set_host_file(filename) < 0) { + LOG_F(ERROR, "Could not open CD-ROM image file, %s", filename.c_str()); + return false; + } + this->data_offset = 0; this->detect_raw_image(); // create single track descriptor @@ -45,6 +53,30 @@ 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*/}; + + this->media_changed |= notify_guest; + return true; +} + +bool CdromDrive::eject_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; + this->raw_blk_size = this->block_size; + this->data_offset = 0; + this->num_tracks = 0; + this->media_changed = 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..5b8ac9ef28 100644 --- a/devices/storage/cdromdrive.h +++ b/devices/storage/cdromdrive.h @@ -81,7 +81,14 @@ class CdromDrive : public BlockStorageDevice { bool medium_present() { return this->is_ready; } - void insert_image(std::string filename); + bool insert_image(const std::string& filename, bool notify_guest = false); + bool eject_image(); + + bool take_media_change() { + bool changed = this->media_changed; + this->media_changed = false; + return changed; + } protected: uint8_t hex_to_bcd(const uint8_t val); @@ -101,6 +108,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_changed = false; uint8_t prevent_jump = 0; // prevent jumper not present uint8_t more_support = 0; uint16_t max_rd_speed = 706; // defaults to 4x diff --git a/machines/machinebase.cpp b/machines/machinebase.cpp index e6d07f29e3..c74f6ab4ea 100644 --- a/machines/machinebase.cpp +++ b/machines/machinebase.cpp @@ -20,6 +20,7 @@ along with this program. If not, see . */ #include +#include #include #include @@ -98,6 +99,28 @@ HWComponent* MachineBase::get_comp_by_type(HWCompType type) { } } +CdromInsertionResult MachineBase::insert_cdrom_image(const std::string& path, + uint32_t drive) { + uint32_t drive_index = 0; + + for (auto& device : this->device_map) { + auto* cdrom = dynamic_cast(device.second.get()); + if (!cdrom) + continue; + + if (drive_index++ == drive) { + if (cdrom->medium_present()) + return CdromInsertionResult::MEDIA_PRESENT; + + return cdrom->insert_image(path, true) + ? CdromInsertionResult::SUCCESS + : CdromInsertionResult::IMAGE_OPEN_FAILED; + } + } + + return CdromInsertionResult::NO_DRIVE; +} + int MachineBase::postinit_devices() { // Allow additional devices to be registered by device_postinit, in which diff --git a/machines/machinebase.h b/machines/machinebase.h index a7c177e466..60d1536831 100644 --- a/machines/machinebase.h +++ b/machines/machinebase.h @@ -27,6 +27,7 @@ along with this program. If not, see . #ifndef MACHINE_BASE_H #define MACHINE_BASE_H +#include #include #include #include @@ -34,6 +35,13 @@ along with this program. If not, see . class HWComponent; enum HWCompType : uint64_t; +enum class CdromInsertionResult { + SUCCESS, + NO_DRIVE, + MEDIA_PRESENT, + IMAGE_OPEN_FAILED, +}; + class MachineBase { public: MachineBase(std::string name); @@ -45,6 +53,8 @@ class MachineBase { HWComponent* get_comp_by_name(std::string name); HWComponent* get_comp_by_name_optional(std::string name); HWComponent* get_comp_by_type(HWCompType type); + CdromInsertionResult insert_cdrom_image(const std::string& path, + uint32_t drive = 0); int postinit_devices(); private: diff --git a/utils/imgfile_sdl.cpp b/utils/imgfile_sdl.cpp index a3904cb220..1073751188 100644 --- a/utils/imgfile_sdl.cpp +++ b/utils/imgfile_sdl.cpp @@ -67,8 +67,12 @@ ImgFile::~ImgFile() = default; bool ImgFile::open(const std::string &img_path) { - impl->backend = make_imgfile_backend(img_path); - return !!impl->backend; + auto backend = make_imgfile_backend(img_path); + if (!backend) + return false; + + impl->backend = std::move(backend); + return true; } void ImgFile::close()