diff --git a/Makefiles/Makefile.SDL.Win32.GCC b/Makefiles/Makefile.SDL.Win32.GCC index 79a5abb..6462c45 100644 --- a/Makefiles/Makefile.SDL.Win32.GCC +++ b/Makefiles/Makefile.SDL.Win32.GCC @@ -138,7 +138,7 @@ OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \ $(OBJ_DIR)/Crypto.o \ $(OBJ_DIR)/Logger.o \ $(OBJ_DIR)/tinyxml2.o \ - $(OBJ_DIR)/ByteSwap.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/ConfigBuilders.o $(OBJ_DIR)/GameLoader.o + $(OBJ_DIR)/ByteSwap.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/ConfigBuilders.o $(OBJ_DIR)/GameLoader.o $(OBJ_DIR)/ROMSet.o # If built-in debugger enabled, include all debugging classes diff --git a/Src/GameLoader.cpp b/Src/GameLoader.cpp index 33036d7..c094f0b 100644 --- a/Src/GameLoader.cpp +++ b/Src/GameLoader.cpp @@ -258,7 +258,8 @@ bool GameLoader::LoadGamesFromXML(const Util::Config::Node &xml) ErrorLog("%s: Ignoring redefinition of game '%s'.", m_xml_filename.c_str(), game_name.c_str()); continue; } - RegionsByName_t ®ions_by_name = m_regions_by_game[game_name]; + RegionsByName_t ®ions_by_name = m_regions_by_game[game_name]; + PatchesByRegion_t &patches_by_region = m_patches_by_game[game_name]; PopulateGameInfo(&m_game_info_by_game[game_name], game_node); for (auto &roms_node: game_node) @@ -307,6 +308,30 @@ bool GameLoader::LoadGamesFromXML(const Util::Config::Node &xml) ErrorLog("%s: No files defined in region '%s' of '%s'.", m_xml_filename.c_str(), region->region_name.c_str(), game_name.c_str()); else regions_by_name[region->region_name] = region; + } + + // ROM patches, if any + for (auto &patches_node: roms_node) + { + if (patches_node.Key() != "patches") + continue; + + for (auto &patch_node: patches_node) + { + if (MissingAttrib(*this, patch_node, "region") || + MissingAttrib(*this, patch_node, "bits") || + MissingAttrib(*this, patch_node, "offset") || + MissingAttrib(*this, patch_node, "value")) + continue; + std::string region = patch_node["region"].ValueAs(); + unsigned bits = patch_node["bits"].ValueAs(); + uint32_t offset = patch_node["offset"].ValueAs(); + uint64_t value = patch_node["value"].ValueAs(); + if (bits != 8 && bits != 16 && bits != 32 && bits != 64) + ErrorLog("%s: Ignoring ROM patch in '%s' with invalid bit length. Must be 8, 16, 32, or 64!", m_xml_filename.c_str(), game_name.c_str()); + else + patches_by_region[region].push_back(ROM::BigEndianPatch(offset, value, bits)); + } } } @@ -439,7 +464,10 @@ bool GameLoader::LoadDefinitionXML(const std::string &filename) m_xml_filename = filename; Util::Config::Node xml("xml"); if (Util::Config::FromXMLFile(&xml, filename)) + { + ErrorLog("Game and ROM set definitions could not be loaded! ROMs will not be detected."); return true; + } return ParseXML(xml); } @@ -722,6 +750,8 @@ bool GameLoader::LoadROMs(ROMSet *rom_set, const std::string &game_name, const Z ErrorLog("Cannot load unknown game '%s'. Is it defined in '%s'?", game_name.c_str(), m_xml_filename.c_str()); return true; } + + // Load up the ROMs auto ®ions_by_name = IsChildSet(it->second) ? m_regions_by_merged_game.find(game_name)->second : m_regions_by_game.find(game_name)->second; LogROMDefinition(game_name, regions_by_name); bool error = false; @@ -733,12 +763,25 @@ bool GameLoader::LoadROMs(ROMSet *rom_set, const std::string &game_name, const Z error |= true; else { + // Load up the ROM region auto &rom = rom_set->rom_by_region[region->region_name]; rom.data.reset(new uint8_t[region_size], std::default_delete()); rom.size = region_size; error |= LoadRegion(&rom, region, zip); } - } + } + + // Attach the patches and do some more error checking here + auto &patches_by_region = m_patches_by_game.find(game_name)->second; + for (auto &v: patches_by_region) + { + auto ®ion_name = v.first; + auto &patches = v.second; + if (regions_by_name.find(region_name) == regions_by_name.end()) + ErrorLog("%s: Ignoring ROM patch for undefined region '%s' in '%s'.", m_xml_filename.c_str(), region_name.c_str(), game_name.c_str()); + else if (rom_set->rom_by_region.find(region_name) != rom_set->rom_by_region.end()) + rom_set->rom_by_region[region_name].patches = patches; + } return error; } diff --git a/Src/GameLoader.h b/Src/GameLoader.h index 9000fb7..d1b4922 100644 --- a/Src/GameLoader.h +++ b/Src/GameLoader.h @@ -38,7 +38,13 @@ private: bool FindFileIndexByOffset(size_t *idx, uint32_t offset) const; }; - std::map m_game_info_by_game; // ROMs not loaded here + // Game information from XML + std::map m_game_info_by_game; + + // ROM patches by game and by region. Cannot place into Region because child + // sets do not inherit parent patches, which may complicate merging. + typedef std::map> PatchesByRegion_t; + std::map m_patches_by_game; // Parsed XML typedef std::map RegionsByName_t; @@ -46,7 +52,7 @@ private: std::map m_regions_by_merged_game; // only child sets merged w/ parents std::string m_xml_filename; - // Zip file info + // Single compressed file inside of a zip archive struct ZippedFile { unzFile zf = nullptr; @@ -56,6 +62,7 @@ private: uint32_t crc32 = 0; }; + // Multiple zip archives struct ZipArchive { std::vector zipfilenames; diff --git a/Src/Model3/Model3.cpp b/Src/Model3/Model3.cpp index 774298a..c4ac180 100644 --- a/Src/Model3/Model3.cpp +++ b/Src/Model3/Model3.cpp @@ -2548,104 +2548,6 @@ void CModel3::Reset(void) Initialization, Shutdown, and ROM Management ******************************************************************************/ -// Apply patches to games -static void Patch(uint8_t *crom, const Game &game) -{ - if (game.name == "scudplus" || game.name == "scudplusa") // No patching? - { - // Base offset of program in CROM: 0x710000 - } - else if (game.name == "lemans24") - { - // Base offset of program in CROM: 6473C0 - *(UINT32 *) &crom[0x6D8C4C] = 0x00000002; // comm. mode: 00=master, 01=slave, 02=satellite - *(UINT32 *) &crom[0x73FE38] = 0x38840004; // an actual bug in the game code - *(UINT32 *) &crom[0x73EB5C] = 0x60000000; - *(UINT32 *) &crom[0x73EDD0] = 0x60000000; - *(UINT32 *) &crom[0x73EDC4] = 0x60000000; - } - else if (game.name == "lostwsga") - { - *(UINT32 *) &crom[0x7374f4] = 0x38840004; // an actual bug in the game code - } - else if (game.name == "vs215" || game.name == "vs215o" || game.name == "vs29815" || game.name == "vs29915") - { - // VS215 is a modification of VS2 that runs on Step 1.5 hardware. I - // suspect the code here is trying to detect the system type but am too - // lazy to figure it out right now. - *(UINT32 *) &crom[0x7001A8] = 0x48000000+(0xFFF01630-0xFFF001A8); // force a jump to FFF01630 - } - else if (game.name == "vs298") - { - // Base offset of program in CROM: 600000 - // Inexplicably, at PC=AFC1C, a call is made to FC78, which is right in the middle of some - // totally unrelated initialization code (ASIC checks). This causes an invalid pointer to be fetched. - // Perhaps FC78 should be overwritten with other program data by then? Why is it not? - // Or, 300138 needs to be written with a non-zero value, it is loaded from EEPROM but is 0. - *(UINT32 *) &crom[0x6AFC1C] = 0x60000000; - } - else if (game.name == "srally2") - { - *(UINT32 *) &crom[0x7C0C4] = 0x60000000; - *(UINT32 *) &crom[0x7C0C8] = 0x60000000; - *(UINT32 *) &crom[0x7C0CC] = 0x60000000; - } - else if (game.name == "harleya") - { - *(UINT32 *) &crom[0x50E8D4] = 0x60000000; - *(UINT32 *) &crom[0x50E8F4] = 0x60000000; - *(UINT32 *) &crom[0x50FB84] = 0x60000000; - } - else if (game.name == "harley") - { - *(UINT32 *) &crom[0x50ECB4] = 0x60000000; - *(UINT32 *) &crom[0x50ECD4] = 0x60000000; - *(UINT32 *) &crom[0x50FF64] = 0x60000000; - } - else if (game.name == "swtrilgy") - { - *(UINT32 *) &crom[0xF0E48] = 0x60000000; - *(UINT32 *) &crom[0x043DC] = 0x48000090; // related to joystick feedback - *(UINT32 *) &crom[0x029A0] = 0x60000000; - *(UINT32 *) &crom[0x02A0C] = 0x60000000; - } - else if (game.name == "swtrilgya") - { - *(UINT32 *) &crom[0xF6DD0] = 0x60000000; // from MAME - } - else if (game.name == "eca" || game.name == "ecau") - { - *(UINT32 *) &crom[0x535580] = 0x60000000; - //*(UINT32 *) &crom[0x5023B4] = 0x60000000; - //*(UINT32 *) &crom[0x5023D4] = 0x60000000; - } -} - -// Reverses all aligned 16-bit words, thereby switching their endianness (assumes buffer size is divisible by 2) -static void Reverse16(uint8_t *buf, size_t size) -{ - for (size_t i = 0; i < size; i += 2) - { - uint8_t tmp = buf[i+0]; - buf[i+0] = buf[i+1]; - buf[i+1] = tmp; - } -} - -// Reverses all aligned 32-bit words, thereby switching their endianness (assumes buffer size is divisible by 4) -static void Reverse32(uint8_t *buf, size_t size) -{ - for (size_t i = 0; i < size; i += 4) - { - uint8_t tmp1 = buf[i+0]; - uint8_t tmp2 = buf[i+1]; - buf[i+0] = buf[i+3]; - buf[i+1] = buf[i+2]; - buf[i+2] = tmp2; - buf[i+3] = tmp1; - } -} - // Offsets of memory regions within Model 3's pool #define OFFSET_RAM 0 // 8 MB #define OFFSET_CROM 0x800000 // 8 MB (fixed CROM) @@ -2681,8 +2583,6 @@ bool CModel3::LoadGame(const Game &game, const ROMSet &rom_set) * - Fixed CROM: 8MB. If < 8MB, loaded only in high part of space and low * part is a mirror of (banked) CROM0. * - Sample ROM: 16MB. If <= 8MB, mirror to high 8MB. - * - * ROMs are released after being loaded. */ if (rom_set.get_rom("vrom").size <= 32*0x100000) { @@ -2807,10 +2707,6 @@ bool CModel3::LoadGame(const Game &game, const ROMSet &rom_set) // Security board encryption device m_cryptoDevice.Init(game.encryption_key, std::bind(&CModel3::ReadSecurityRAM, this, std::placeholders::_1)); - // Apply ROM patches - //TODO: place these in XML - Patch(crom, game); - // Print game information std::set extra_hw; if (DSB) diff --git a/Src/ROMSet.cpp b/Src/ROMSet.cpp new file mode 100644 index 0000000..ff1bfb1 --- /dev/null +++ b/Src/ROMSet.cpp @@ -0,0 +1,48 @@ +#include "ROMSet.h" +#include "OSD/Logger.h" +#include + +void ROM::CopyTo(uint8_t *dest, size_t dest_size, bool apply_patches ) const +{ + if (!data || !size || !dest || !dest_size) + return; + + size_t bytes_to_copy = std::min(size, dest_size); + const uint8_t *src = data.get(); + memcpy(dest, src, bytes_to_copy); + + if (apply_patches) + { + for (auto &patch: patches) + { + unsigned bytes = patch.bits / 8; + if (patch.offset + bytes > dest_size) + { + ErrorLog("Ignored ROM patch to offset 0x%x in region 0x%x bytes long.", patch.offset, dest_size); + continue; + } + uint64_t value = patch.value; + switch (patch.bits) + { + case 8: + case 16: + case 32: + case 64: + for (size_t i = 0; i < bytes; i++) + { + dest[patch.offset + bytes - 1 - i] = value & 0xff; + value >>= 8; + } + break; + default: + break; + } + } + } +} + +ROM ROMSet::get_rom(const std::string ®ion) const +{ + auto it = rom_by_region.find(region); + return it == rom_by_region.end() ? ROM() : it->second; +} diff --git a/Src/ROMSet.h b/Src/ROMSet.h index 01dbd92..979deb0 100644 --- a/Src/ROMSet.h +++ b/Src/ROMSet.h @@ -4,34 +4,37 @@ #include #include #include -#include -#include +#include +#include // Holds a single ROM region struct ROM { + struct BigEndianPatch + { + uint32_t offset; + uint64_t value; + unsigned bits; + + BigEndianPatch(uint32_t o, uint64_t v, unsigned b) + : offset(o), + value(v), + bits(b) + {} + }; + std::shared_ptr data; + std::vector patches; size_t size = 0; - void CopyTo(uint8_t *dest, size_t dest_size) const - { - if (!data || !size || !dest || !dest_size) - return; - size_t bytes_to_copy = std::min(size, dest_size); - const uint8_t *src = data.get(); - memcpy(dest, src, bytes_to_copy); - } + void CopyTo(uint8_t *dest, size_t dest_size, bool apply_patches = true) const; }; struct ROMSet { std::map rom_by_region; - ROM get_rom(const std::string ®ion) const - { - auto it = rom_by_region.find(region); - return it == rom_by_region.end() ? ROM() : it->second; - } + ROM get_rom(const std::string ®ion) const; }; #endif // INCLUDED_ROMSET_H