xtRomLoader.cpp (5743B)
1 #include "xtRomLoader.h" 2 3 #include <cassert> 4 #include <map> 5 #include <algorithm> 6 #include <cstring> 7 8 #include "baseLib/filesystem.h" 9 10 #include "wLib/wRom.h" 11 12 namespace xt 13 { 14 constexpr uint32_t g_1Kb = 1024; 15 16 constexpr uint32_t g_romSizeFull = 256 * g_1Kb; 17 18 constexpr uint32_t g_romSizeHalf = 128 * g_1Kb; 19 20 constexpr uint32_t g_midiSizeMin = 166 * g_1Kb; 21 constexpr uint32_t g_midiSizeMax = 171 * g_1Kb; 22 23 Rom RomLoader::findROM() 24 { 25 std::vector<File> allFiles; 26 27 { 28 // full roms are 256k in size 29 auto filesFull = findFiles(".bin", g_romSizeFull, g_romSizeFull); 30 31 for (auto& file : filesFull) 32 { 33 if(detectFileType(file) && detectVersion(file)) 34 allFiles.push_back(std::move(file)); 35 } 36 } 37 38 { 39 // half roms are 128k in size, we need exactly two files to combine them 40 auto filesHalf = findFiles(".bin", g_romSizeHalf, g_romSizeHalf); 41 42 for(uint32_t i=0; i<static_cast<uint32_t>(filesHalf.size());) 43 { 44 auto& file = filesHalf[i]; 45 if(!detectFileType(file) || (file.type != FileType::HalfRomA && file.type != FileType::HalfRomB)) 46 filesHalf.erase(filesHalf.begin() + i); 47 else 48 ++i; 49 } 50 51 if(filesHalf.size() > 2) 52 { 53 // remove possible duplicates 54 for(size_t i=0; i<filesHalf.size(); ++i) 55 { 56 for(size_t j=i+1; j<filesHalf.size(); ++j) 57 { 58 if(filesHalf[i].data == filesHalf[j].data) 59 { 60 filesHalf.erase(filesHalf.begin() + static_cast<ptrdiff_t>(j)); 61 --j; 62 } 63 } 64 } 65 } 66 67 if(filesHalf.size() == 2) 68 { 69 File& a = filesHalf.front(); 70 File& b = filesHalf.back(); 71 72 if(a.type == FileType::HalfRomB && b.type == FileType::HalfRomA) 73 std::swap(a,b); 74 75 if(a.type == FileType::HalfRomA && b.type == FileType::HalfRomB) 76 { 77 File result; 78 result.data.reserve(g_romSizeFull); 79 80 for(size_t i=0; i<g_romSizeHalf; ++i) 81 { 82 result.data.push_back(a.data[i]); 83 result.data.push_back(b.data[i]); 84 } 85 86 assert(result.data[0] == 0xc0 && result.data[1] == 0xde); 87 88 result.name = a.name + '_' + b.name; 89 result.type = FileType::FullRom; 90 91 if(detectVersion(result)) 92 allFiles.push_back(std::move(result)); 93 } 94 } 95 } 96 97 { 98 // midi file OS update contain about half of the rom, wavetables are missing so an OS update cannot 99 // be used on its own, but it can be used to upgrade an older rom by replacing the upper half with 100 // the content of the midi OS update 101 auto filesMidi = findFiles(".mid", g_midiSizeMin, g_midiSizeMax); 102 103 for (auto& file : filesMidi) 104 { 105 std::vector<uint8_t> data; 106 if(!wLib::ROM::loadFromMidiData(data, file.data)) 107 continue; 108 if(!removeBootloader(data)) 109 continue; 110 file.data = data; 111 if(detectFileType(file) && detectVersion(file)) 112 allFiles.emplace_back(std::move(file)); 113 } 114 } 115 116 if(allFiles.empty()) 117 return Rom::invalid(); 118 119 std::map<FileType, std::vector<File>> romsByType; 120 for (auto& file : allFiles) 121 { 122 romsByType[file.type].push_back(file); 123 } 124 125 auto& fullRoms = romsByType[FileType::FullRom]; 126 127 if(fullRoms.empty()) 128 return Rom::invalid(); 129 130 File best; 131 132 // use the highest version that we have 133 for (auto& fullRom : fullRoms) 134 { 135 if(fullRom.version > best.version) 136 best = std::move(fullRom); 137 } 138 139 // apply OS update if we have any and the version of that upgrade is higher 140 auto& midiUpgrades = romsByType[FileType::MidiUpdate]; 141 142 File bestMidi; 143 for (auto& midi : midiUpgrades) 144 { 145 if(midi.version > bestMidi.version && midi.version > best.version) 146 bestMidi = std::move(midi); 147 } 148 149 if(bestMidi.version) 150 { 151 assert(bestMidi.data.size() <= best.data.size()); 152 ::memcpy(best.data.data(), bestMidi.data.data(), bestMidi.data.size()); 153 best.version = bestMidi.version; 154 best.name += "_upgraded_" + bestMidi.name; 155 } 156 157 return {best.name, best.data}; 158 } 159 160 std::vector<RomLoader::File> RomLoader::findFiles(const std::string& _extension, const size_t _sizeMin, const size_t _sizeMax) 161 { 162 const auto fileNames = synthLib::RomLoader::findFiles(_extension, _sizeMin, _sizeMax); 163 164 std::vector<File> files; 165 166 for (const auto& name : fileNames) 167 { 168 File f; 169 if(!baseLib::filesystem::readFile(f.data, name)) 170 continue; 171 172 f.name = baseLib::filesystem::getFilenameWithoutPath(name); 173 files.emplace_back(std::move(f)); 174 } 175 return files; 176 } 177 178 bool RomLoader::detectFileType(File& _file) 179 { 180 const auto& data = _file.data; 181 182 switch (data.size()) 183 { 184 case g_romSizeFull: 185 // full rom starts with C0DE 186 if(data[0] != 0xc0 || data[1] != 0xde) 187 return false; 188 _file.type = FileType::FullRom; 189 return true; 190 case g_romSizeHalf: 191 // rom half starts with either C0 or DE 192 if(data[0] == 0xc0 && data[1] == 0x00) 193 _file.type = FileType::HalfRomA; 194 else if(data[0] == 0xde && data[1] == 0x00) 195 _file.type = FileType::HalfRomB; 196 else 197 return false; 198 return true; 199 default: 200 // OS update starts with C0DE too 201 if(data[0] != 0xc0 || data[1] != 0xde) 202 return false; 203 _file.type = FileType::MidiUpdate; 204 return true; 205 } 206 } 207 208 bool RomLoader::removeBootloader(std::vector<uint8_t>& _data) 209 { 210 if(_data.size() < 2) 211 return false; 212 213 const std::vector<uint8_t> pattern{0xc0, 0xde, 0x00, 0x00}; 214 215 const auto it = std::search(_data.begin(), _data.end(), pattern.begin(), pattern.end()); 216 217 if(it == _data.end()) 218 return false; 219 220 if(it != _data.begin()) 221 _data.erase(_data.begin(), it); 222 223 return true; 224 } 225 226 bool RomLoader::detectVersion(File& _file) 227 { 228 _file.version = 0; 229 230 const auto& data = _file.data; 231 if(data.size() < 0x33) 232 return false; 233 if(data[0x30] != '.') 234 return false; 235 236 _file.version = (data[0x2f] - '0') * 100 + (data[0x31] - '0') * 10 + (data[0x32] - '0'); 237 238 return _file.version > 200; 239 } 240 }