/* DingusPPC - The Experimental PowerPC Macintosh emulator Copyright (C) 2018-26 The DingusPPC Development Team (See CREDITS.MD for more details) (You may also contact divingkxt or powermax2286 on Discord) This program 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 3 of the License, or (at your option) any later version. This program 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 this program. If not, see . */ /** @file Factory for creating different machines. Author: Max Poliakovski */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; map> gMachineSettings; static const map PropHelp = { {"rambank1_size", "specifies RAM bank 1 size in MB"}, {"rambank2_size", "specifies RAM bank 2 size in MB"}, {"rambank3_size", "specifies RAM bank 3 size in MB"}, {"rambank4_size", "specifies RAM bank 4 size in MB"}, {"rambank5_size", "specifies RAM bank 5 size in MB"}, {"rambank6_size", "specifies RAM bank 6 size in MB"}, {"rambank7_size", "specifies RAM bank 7 size in MB"}, {"rambank8_size", "specifies RAM bank 8 size in MB"}, {"rambank9_size", "specifies RAM bank 9 size in MB"}, {"rambank10_size", "specifies RAM bank 10 size in MB"}, {"rambank11_size", "specifies RAM bank 11 size in MB"}, {"rambank12_size", "specifies RAM bank 12 size in MB"}, {"rambank0_size", "specifies onboard RAM bank size in MB"}, {"gfxmem_size", "specifies video memory size in MB"}, {"fdd_img", "specifies path to floppy disk image"}, {"fdd_fmt", "specifies floppy disk format"}, {"fdd_wr_prot", "toggles floppy disk's write protection"}, {"hdd_img", "specifies path(s) to hard disk image(s)"}, {"hdd_img2", "specifies path(s) to secondary hard disk image(s)"}, {"cdr_config", "CD-ROM device path in [bus]:[device#] format"}, {"cdr_img", "specifies path(s) to CD-ROM image(s)"}, {"cdr_img2", "specifies path(s) to secondary CD-ROM image(s)"}, {"mon_id", "specifies which monitor to emulate"}, {"pci_GPU", "specifies PCI device for Beige G3 grackle device @12"}, {"pci_J12", "insert a PCI device into 32-bit 66MHz slot J12"}, {"pci_J11", "insert a PCI device into 64-bit 33MHz slot J11"}, {"pci_J10", "insert a PCI device into 64-bit 33MHz slot J10"}, {"pci_J9", "insert a PCI device into 64-bit 33MHz slot J9"}, {"pci_FireWire", "insert a PCI device into PCI slot reserved for Yosemite FireWire"}, {"pci_UltraATA", "insert a PCI device into PCI slot reserved for Yosemite Ultra ATA"}, {"pci_USB", "insert a PCI device into PCI slot reserved for Yosemite USB"}, {"pci_PERCH", "insert a PCI device into PERCH slot"}, {"pci_A1", "insert a PCI device into A1 slot"}, {"pci_B1", "insert a PCI device into B1 slot"}, {"pci_C1", "insert a PCI device into C1 slot"}, {"pci_E1", "insert a PCI device into E1 slot"}, {"pci_F1", "insert a PCI device into F1 slot"}, {"pci_D2", "insert a PCI device into D2 slot"}, {"pci_E2", "insert a PCI device into E2 slot"}, {"pci_F2", "insert a PCI device into F2 slot"}, {"vci_D", "insert a VCI device 0x0D"}, {"vci_E", "insert a VCI device 0x0E"}, {"serial_backend", "specifies the backend for the serial port"}, {"emmo", "enables/disables factory HW tests during startup"}, {"cpu", "specifies CPU"}, {"adb_devices", "specifies which ADB device(s) to attach"}, {"pds", "specify device for the processsor direct slot"}, }; static uint32_t adler32slow(char *buf, size_t len) { uint32_t sum1 = 1; uint32_t sum2 = 0; while (len--) { sum1 = (sum1 + *(uint8_t*)buf++) % 65521; sum2 = (sum2 + sum1) % 65521; } return sum1 + 65536 * sum2; } static uint32_t adler32(char *buf, size_t len) { // inspired by zlib adler32 implementation #define DO1(buf,i) {sum1 += ((uint8_t*)(buf))[i]; sum2 += sum1;} #define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); #define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); #define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); #define DO16(buf) DO8(buf,0); DO8(buf,8); #define MOD(a) a %= base constexpr uint64_t nmax = 0x16ABF600; // use 5552 for 32-bit constexpr uint64_t base = 0xFFF1; uint64_t sum1 = 1; uint64_t sum2 = 0; while (len >= nmax) { len -= nmax; uint64_t n = nmax / 16; do { DO16(buf); buf += 16; } while (--n); MOD(sum1); MOD(sum2); } while (len >= 16) { len -= 16; DO16(buf); buf += 16; } while (len--) { sum1 += *(uint8_t*)buf++; sum2 += sum1; } MOD(sum1); MOD(sum2); return uint32_t(sum1 | (sum2 << 16)); } static uint32_t oldworldchecksum(char *buf, size_t len) { uint32_t ck = 0; while (len) { ck += READ_WORD_BE_A(buf); buf += 2; len -= 2; } return ck; } bool MachineFactory::add(const string& machine_id, MachineDescription desc) { if (get_registry().find(machine_id) != get_registry().end()) { return false; } get_registry()[machine_id] = desc; return true; } static uint64_t oldworldchecksum64(char *buf, size_t len, size_t config_info_offset) { uint64_t ck = 0; for (size_t i = 0; i < len; i += 8, buf += 8) { if (i < config_info_offset || i >= config_info_offset + 40) ck += READ_QWORD_BE_A(buf); } return ck; } void MachineFactory::list_machines() { cout << endl << "Supported machines:" << endl << endl; for (auto& m : get_registry()) { cout << setw(13) << right << m.first << "\t\t" << m.second.description << endl; } cout << endl; } void MachineFactory::create_device(string& dev_name, DeviceDescription& dev) { for (auto& subdev_name : dev.subdev_list) { create_device(subdev_name, DeviceRegistry::get_descriptor(subdev_name)); } gMachineObj->add_device(dev_name, dev.m_create_func()); } int MachineFactory::create(string& mach_id) { auto it = get_registry().find(mach_id); if (it == get_registry().end()) { LOG_F(ERROR, "Unknown machine id %s", mach_id.c_str()); return -1; } LOG_F(INFO, "Initializing %s hardware...", it->second.description.c_str()); // initialize global machine object gMachineObj.reset(new MachineBase(it->second.name)); // create and register sound server gMachineObj->add_device("SoundServer", std::unique_ptr(new SoundServer())); // recursively create device objects create_device(it->second.machine_root, DeviceRegistry::get_descriptor(it->second.machine_root)); if (!gMachineObj->get_comp_by_name(it->second.machine_root)) { LOG_F(ERROR, "Machine initialization function failed!"); gMachineObj->clear_devices(); return -1; } // post-initialize all devices if (gMachineObj->postinit_devices()) { LOG_F(ERROR, "Could not post-initialize devices!"); return -1; } LOG_F(INFO, "Initialization completed."); return 0; } void MachineFactory::list_properties() { cout << endl; for (auto& mach : get_registry()) { cout << mach.second.description << " supported properties:" << endl << endl; list_device_settings(DeviceRegistry::get_descriptor(mach.second.machine_root)); } cout << endl; } void MachineFactory::list_device_settings(DeviceDescription& dev) { for (auto& d : dev.subdev_list) { list_device_settings(DeviceRegistry::get_descriptor(d)); } print_settings(dev.properties); } void MachineFactory::print_settings(const PropMap& prop_map) { string help; for (auto& p : prop_map) { if (PropHelp.find(p.first) != PropHelp.end()) help = PropHelp.at(p.first); else help = ""; cout << setw(13) << right << p.first << "\t\t" << help << endl; cout << setw(13) << "\t\t\t" "Valid values: "; switch(p.second->get_type()) { case PROP_TYPE_INTEGER: cout << dynamic_cast(p.second)->get_valid_values_as_str() << endl; break; case PROP_TYPE_STRING: cout << dynamic_cast(p.second)->get_valid_values_as_str() << endl; break; case PROP_TYPE_BINARY: cout << dynamic_cast(p.second)->get_valid_values_as_str() << endl; break; default: break; } cout << endl; } } void MachineFactory::register_device_settings(const std::string& name) { auto dev = DeviceRegistry::get_descriptor(name); for (auto& d : dev.subdev_list) { register_device_settings(d); } register_settings(dev.properties); if (!dev.properties.empty()) { std::cout << "Device " << name << " Settings" << endl << std::string(36, '-') << endl; for (auto& p : dev.properties) { std::cout << std::setw(24) << std::right << p.first << " : " << gMachineSettings[p.first]->get_string() << endl; } } } int MachineFactory::register_machine_settings(const std::string& id) { auto it = get_registry().find(id); if (it != get_registry().end()) { gMachineSettings.clear(); register_device_settings(it->second.machine_root); } else { LOG_F(ERROR, "Unknown machine id %s", id.c_str()); return -1; } return 0; } void MachineFactory::register_settings(const PropMap& props) { for (auto& p : props) { if (gMachineSettings.count(p.first)) { // This is a hack. Need to implement hierarchical paths and per device properties. LOG_F(ERROR, "Duplicate setting \"%s\".", p.first.c_str()); } else { auto clone = p.second->clone(); auto override_value = get_setting_value(p.first); if (override_value) { clone->set_string(*override_value); } gMachineSettings[p.first] = std::unique_ptr(clone); } } } size_t MachineFactory::read_boot_rom(string& rom_filepath, char *rom_data) { ifstream rom_file; size_t file_size; rom_file.open(rom_filepath, ios::in | ios::binary); if (rom_file.fail()) { LOG_F(ERROR, "Could not open the specified ROM file."); file_size = 0; goto bail_out; } rom_file.seekg(0, rom_file.end); file_size = rom_file.tellg(); if (file_size < 64 * 1024 || file_size > 4 * 1024 * 1024) { LOG_F(ERROR, "Unexpected ROM file size: %zu bytes. Expected size is 1 or 4 megabytes.", file_size); file_size = 0; goto bail_out; } if (rom_data) { rom_file.seekg(0, ios::beg); rom_file.read(rom_data, file_size); } bail_out: rom_file.close(); return file_size; } string MachineFactory::machine_name_from_rom(char *rom_data, size_t rom_size) { uint32_t date = 0; uint16_t major_version = 0; uint16_t minor_version = 0; uint32_t firmware_version = 0; uint32_t nw_product_id = 0; uint32_t ow_checksum_stored = 0; uint32_t ow_checksum_calculated = 0; uint64_t ow_checksum64_stored = 0; uint64_t ow_checksum64_calculated = 0; uint32_t nw_start_checksum_stored = 0; uint32_t nw_start_checksum_calculated = 0; uint32_t nw_config_checksum_stored = 0; uint32_t nw_config_checksum_calculated = 0; uint32_t nw_recovery_checksum_stored = 0; uint32_t nw_recovery_checksum_calculated = 0; uint32_t nw_romimage_checksum_stored = 0; uint32_t nw_romimage_checksum_calculated = 0; uint16_t nw_config_signature = 0; bool has_nw_config = false; bool is_nw = false; uint32_t nw_subconfig_checksum_calculated = 0; char expected_ow[24]; char expected_ow64[40]; char expected_start[24]; char expected_config[24]; char expected_recovery[24]; char expected_romimage[24]; auto checksum_verbosity = loguru::Verbosity_INFO; auto checksum_verbosity2 = loguru::Verbosity_INFO; expected_ow[0] = expected_ow64[0] = 0; expected_start[0] = expected_config[0] = expected_recovery[0] = expected_romimage[0] = 0; uint32_t config_info_offset; char rom_id_str[17]; rom_id_str[0] = '\0'; int match_pass; int num_matches = 0; int best_match_count = 0; string machine_name = ""; // set this to false if you want to print all info only when there's no ROM match bool print_all_info = true; /* read firmware version from file */ date = READ_DWORD_BE_A(&rom_data[8]); nw_config_signature = READ_WORD_BE_A(&rom_data[0x3f00]); has_nw_config = nw_config_signature == 0xc99c || nw_config_signature == 0xc03c; if (has_nw_config || (date > 0x19990000 && date < 0x20060000)) { is_nw = true; firmware_version = READ_DWORD_BE_A(&rom_data[4]); { nw_recovery_checksum_calculated = adler32(&rom_data[0x8000], 0x77ffc); nw_recovery_checksum_stored = READ_DWORD_BE_A(&rom_data[0x7fffc]); nw_romimage_checksum_calculated = adler32(&rom_data[0x80000], 0x7fffc); nw_romimage_checksum_stored = READ_DWORD_BE_A(&rom_data[0xffffc]); } if (has_nw_config) { nw_start_checksum_calculated = adler32(&rom_data[0], 0x3efc); nw_start_checksum_stored = READ_DWORD_BE_A(&rom_data[0x3efc]); nw_config_checksum_calculated = adler32(&rom_data[0x3f00], 0x7c); nw_config_checksum_stored = READ_DWORD_BE_A(&rom_data[0x3f7c]); nw_subconfig_checksum_calculated = adler32(&rom_data[0x3f0c], 0x70); nw_product_id = (READ_WORD_BE_A(&rom_data[0x3f02]) << 8) | rom_data[0x3f13]; } else { firmware_version &= 0xffff; // the upper 2 bytes might be a machine type: 0=iMac, 1=PowerMac, 2=PowerBook nw_start_checksum_calculated = adler32(&rom_data[0], 0x3ffc); nw_start_checksum_stored = READ_DWORD_BE_A(&rom_data[0x3ffc]); nw_config_checksum_calculated = 0; nw_config_checksum_stored = 0; nw_subconfig_checksum_calculated = 0; nw_product_id = 0; } if (nw_start_checksum_calculated != nw_start_checksum_stored) snprintf(expected_start, sizeof(expected_start), " (expected 0x%04x)", nw_start_checksum_stored); if (nw_config_checksum_calculated != nw_config_checksum_stored) snprintf(expected_config, sizeof(expected_config), " (expected 0x%04x)", nw_config_checksum_stored); if (nw_recovery_checksum_calculated != nw_recovery_checksum_stored) snprintf(expected_recovery, sizeof(expected_recovery), " (expected 0x%04x)", nw_recovery_checksum_stored); if (nw_romimage_checksum_calculated != nw_romimage_checksum_stored) snprintf(expected_romimage, sizeof(expected_romimage), " (expected 0x%04x)", nw_romimage_checksum_stored); } else { date = 0; major_version = READ_WORD_BE_A(&rom_data[8]); minor_version = 0; if (uint8_t(major_version) >= 0x7A) minor_version = READ_WORD_BE_A(&rom_data[0x12]); firmware_version = (major_version << 16) | minor_version; ow_checksum_calculated = oldworldchecksum(&rom_data[4], std::min(rom_size - 4, (size_t)0x2ffffc)); ow_checksum_stored = READ_DWORD_BE_A(&rom_data[0]); if (ow_checksum_calculated != ow_checksum_stored) snprintf(expected_ow, sizeof(expected_ow), " (expected 0x%08x)", ow_checksum_stored); if (rom_size >= 0x400000) { /* read ConfigInfo offset from file */ config_info_offset = READ_DWORD_BE_A(&rom_data[0x300080]); if (0x300000ULL + config_info_offset <= rom_size - 0x64 - 16) { ow_checksum64_calculated = oldworldchecksum64(&rom_data[0], rom_size, 0x300000 + config_info_offset); ow_checksum64_stored = READ_QWORD_BE_A(&rom_data[0x300020 + config_info_offset]); if (ow_checksum64_calculated != ow_checksum64_stored) snprintf(expected_ow64, sizeof(expected_ow64), " (expected 0x%016llx)", ow_checksum64_stored); /* read ConfigInfo.BootstrapVersion field as C string */ memcpy(rom_id_str, &rom_data[0x300064 + config_info_offset], 16); rom_id_str[16] = 0; for (int i = 0; i < 16; i++) if (rom_id_str[i] < ' ' || rom_id_str[i] > '~') rom_id_str[i] = '.'; } } } for (match_pass = 0; match_pass < 2; match_pass++) { int match_index = 0; for (rom_info *info = &rom_identity[0]; info->firmware_size_k; info++) { if ( (info->firmware_version && info->firmware_version == firmware_version) || (info->nw_product_id && info->nw_product_id == nw_product_id ) ) { int match_count = 1 + (info->ow_expected_checksum && info->ow_expected_checksum == ow_checksum_stored) + (info->ow_expected_checksum && info->ow_expected_checksum == ow_checksum_calculated) + (info->ow_expected_checksum64 && info->ow_expected_checksum64 == ow_checksum64_stored) + (info->ow_expected_checksum64 && info->ow_expected_checksum64 == ow_checksum64_calculated) + (info->nw_subconfig_expected_checksum && info->nw_subconfig_expected_checksum == nw_subconfig_checksum_calculated) + (info->id_str && strcmp(rom_id_str, info->id_str) == 0) ; if (!match_pass) { if (match_count >= best_match_count) { if (match_count > best_match_count) { best_match_count = match_count; num_matches = 0; } num_matches++; } } else { if (num_matches == 0) { LOG_F(ERROR, "Unknown ROM"); print_all_info = true; break; } if (match_count == best_match_count) { match_index++; LOG_F(INFO, "Found match (%d/%d):", match_index, num_matches); if (info->rom_description) LOG_F(INFO, " ROM description: %s", info->rom_description); if (info->dppc_description) LOG_F(INFO, " Machine identified from ROM: %s", info->dppc_description); if ( info->nw_firmware_updater_name && info->nw_openfirmware_name && strcmp(info->nw_firmware_updater_name, info->nw_openfirmware_name) == 0 ) { LOG_F(INFO, " Code Name: %s", info->nw_firmware_updater_name); } else { if (info->nw_firmware_updater_name) LOG_F(INFO, " Code Name (from Firmware Updater): %s", info->nw_firmware_updater_name); if (info->nw_openfirmware_name) LOG_F(INFO, " Code Name (from Open Firmware): %s", info->nw_openfirmware_name); } if (info->nw_product_id) { LOG_F(INFO, " Product ID: 0x%04x.%02x = %s%d,%d", nw_product_id >> 8, nw_product_id & 0xff, (nw_product_id >> 20) == 0 ? "PowerMac" : (nw_product_id >> 20) == 1 ? "PowerBook" : (nw_product_id >> 20) == 2 ? "PowerMac" : (nw_product_id >> 20) == 3 ? "PowerBook" : (nw_product_id >> 20) == 4 ? "RackMac" : "???", (nw_product_id >> 14) & 31, (nw_product_id >> 8) & 31 ); } if (info->nw_subconfig_expected_checksum) { LOG_F(INFO, " Config Checksum: 0x%08x", nw_subconfig_checksum_calculated); } if (rom_size != info->firmware_size_k * 1024) { LOG_F(ERROR, " Unexpected ROM file size: %zu bytes. Expected size is %d %s.", rom_size, info->firmware_size_k & 0x3ff ? info->firmware_size_k : info->firmware_size_k / 1024, info->firmware_size_k & 0x3ff ? "kiB" : "MiB" ); } if (info->dppc_machine) { if (machine_name.empty()) { machine_name = info->dppc_machine; } } else LOG_F(ERROR, " This ROM is not supported."); } } // if match_pass } // if match } // for rom_info } // for match_pass if (print_all_info) { if (is_nw) { LOG_F(INFO, "Info from ROM:"); LOG_F(INFO, " ROM Date: %04x-%02x-%02x", date >> 16, (date >> 8) & 0xff, date & 0xff); if (firmware_version < 0xffff) LOG_F(INFO, " ROM Version: %x.%03x", (firmware_version >> 12) & 15, firmware_version & 0xfff); else LOG_F(INFO, " ROM Version: %x.%x.%03x", firmware_version >> 16, (firmware_version >> 12) & 15, firmware_version & 0xfff); if (has_nw_config) { LOG_F(INFO, " Product ID: 0x%04x.%02x 0x%08x = %s%d,%d", nw_product_id >> 8, nw_product_id & 0xff, nw_subconfig_checksum_calculated, (nw_product_id >> 20) == 0 ? "PowerMac" : (nw_product_id >> 20) == 1 ? "PowerBook" : (nw_product_id >> 20) == 2 ? "PowerMac" : (nw_product_id >> 20) == 3 ? "PowerBook" : (nw_product_id >> 20) == 4 ? "RackMac" : "???", (nw_product_id >> 14) & 31, (nw_product_id >> 8) & 31 ); } } else { if (uint8_t(major_version) >= 0x7A) LOG_F(INFO, " ROM Version: %04x.%04x", major_version, minor_version); else LOG_F(INFO, " ROM Version: %04x", major_version); if (rom_id_str[0]) LOG_F(INFO, " ConfigInfo.BootstrapVersion: \"%s\"", rom_id_str); } } if (expected_ow[0] || expected_start[0] || expected_config[0] || expected_recovery[0] || expected_romimage[0]) checksum_verbosity = loguru::Verbosity_ERROR; if (expected_ow64[0]) checksum_verbosity2 = loguru::Verbosity_ERROR; if (print_all_info || checksum_verbosity != loguru::Verbosity_INFO || checksum_verbosity2 != loguru::Verbosity_INFO ) { if (is_nw) { if (has_nw_config) { VLOG_F(checksum_verbosity, " ROM Checksums: 0x%08x%s, 0x%08x%s, 0x%08x%s, 0x%08x%s", nw_start_checksum_calculated, expected_start, nw_config_checksum_calculated, expected_config, nw_recovery_checksum_calculated, expected_recovery, nw_romimage_checksum_calculated, expected_romimage ); } else { VLOG_F(checksum_verbosity, " ROM Checksums: 0x%08x%s, 0x%08x%s, 0x%08x%s", nw_start_checksum_calculated, expected_start, nw_recovery_checksum_calculated, expected_recovery, nw_romimage_checksum_calculated, expected_romimage ); } } else { VLOG_F(checksum_verbosity, " ROM Checksum: 0x%08x%s", ow_checksum_calculated, expected_ow ); if (ow_checksum64_calculated || expected_ow64[0]) VLOG_F(checksum_verbosity2, " ROM Checksum (64-bit): 0x%016llx%s", ow_checksum64_calculated, expected_ow64 ); } } return machine_name; } /* Read ROM file content and transfer it to the dedicated ROM region */ int MachineFactory::load_boot_rom(char *rom_data, size_t rom_size) { int result = 0; uint32_t rom_load_addr; //AddressMapEntry *rom_reg; if (rom_size == 0x400000UL) { // Old World ROMs rom_load_addr = 0xFFC00000UL; } else if (rom_size == 0x100000UL) { // New World ROMs rom_load_addr = 0xFFF00000UL; } else { LOG_F(ERROR, "Unexpected ROM File size: %zu bytes.", rom_size); result = -1; } if (!result) { MemCtrlBase* mem_ctrl = dynamic_cast( gMachineObj->get_comp_by_type(HWCompType::MEM_CTRL)); if ((/*rom_reg = */mem_ctrl->find_rom_region())) { mem_ctrl->set_data(rom_load_addr, (uint8_t*)rom_data, (uint32_t)rom_size); } else { LOG_F(ERROR, "Could not locate physical ROM region!"); result = -1; } } return result; } int MachineFactory::create_machine_for_id(string& id, char *rom_data, size_t rom_size) { if (MachineFactory::create(id) < 0) { return -1; } if (load_boot_rom(rom_data, rom_size) < 0) { return -1; } return 0; } GetSettingValueFunc MachineFactory::get_setting_value;