diff --git a/Storage/Disk/Parsers/CPM.cpp b/Storage/Disk/Parsers/CPM.cpp index b75d55e28..bc325e4a1 100644 --- a/Storage/Disk/Parsers/CPM.cpp +++ b/Storage/Disk/Parsers/CPM.cpp @@ -46,77 +46,109 @@ std::unique_ptr Storage::Disk::CPM::GetCatalogue( catalogue_allocation_bitmap <<= 1; } - std::unique_ptr result(new Catalogue); - bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256; - size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size; + struct CatalogueEntry { + uint8_t user_number; + std::string name; + std::string type; + bool read_only; + bool system; + size_t extent; + uint8_t number_of_records; + size_t catalogue_index; - // From the catalogue, create files. - std::map, size_t> indices_by_name; - File empty_file; + bool operator < (const CatalogueEntry &rhs) const { + return std::tie(user_number, name, type, extent) < std::tie(rhs.user_number, rhs.name, rhs.type, rhs.extent); + } + bool is_same_file(const CatalogueEntry &rhs) const { + return std::tie(user_number, name, type) == std::tie(rhs.user_number, rhs.name, rhs.type); + } + }; + + // From the catalogue, get catalogue entries. + std::vector catalogue_entries; for(size_t c = 0; c < catalogue.size(); c += 32) { // Skip this file if it's deleted; this is marked by it having 0xe5 as its user number if(catalogue[c] == 0xe5) continue; - // Check whether this file has yet been seen; if not then add it to the list - std::vector descriptor; - size_t index; - descriptor.insert(descriptor.begin(), &catalogue[c], &catalogue[c + 12]); - auto iterator = indices_by_name.find(descriptor); - if(iterator != indices_by_name.end()) { - index = iterator->second; - } else { - File new_file; - new_file.user_number = catalogue[c]; - for(size_t s = 0; s < 8; s++) new_file.name.push_back((char)catalogue[c + s + 1]); - for(size_t s = 0; s < 3; s++) new_file.type.push_back((char)catalogue[c + s + 9] & 0x7f); - new_file.read_only = catalogue[c + 9] & 0x80; - new_file.system = catalogue[c + 10] & 0x80; - index = result->files.size(); - result->files.push_back(new_file); - indices_by_name[descriptor] = index; + catalogue_entries.emplace_back(); + CatalogueEntry &entry = catalogue_entries.back(); + entry.user_number = catalogue[c]; + entry.name.insert(entry.name.begin(), &catalogue[c+1], &catalogue[c+9]); + for(size_t s = 0; s < 3; s++) entry.type.push_back((char)catalogue[c + s + 9] & 0x7f); + entry.read_only = catalogue[c + 9] & 0x80; + entry.system = catalogue[c + 10] & 0x80; + entry.extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5)); + entry.number_of_records = catalogue[c + 15]; + entry.catalogue_index = c; + } + + // Sort the catalogue entries and then map to files. + std::sort(catalogue_entries.begin(), catalogue_entries.end()); + + std::unique_ptr result(new Catalogue); + + bool has_long_allocation_units = (parameters.tracks * parameters.sectors_per_track * (int)sector_size / parameters.block_size) >= 256; + size_t bytes_per_catalogue_entry = (has_long_allocation_units ? 16 : 8) * (size_t)parameters.block_size; + int sectors_per_block = parameters.block_size / (int)sector_size; + int records_per_sector = (int)sector_size / 128; + + auto entry = catalogue_entries.begin(); + while(entry != catalogue_entries.end()) { + // Find final catalogue entry that relates to the same file. + auto final_entry = entry + 1; + while(final_entry != catalogue_entries.end() && final_entry->is_same_file(*entry)) { + final_entry++; } + final_entry--; - // figure out where this data needs to be pasted in - size_t extent = (size_t)(catalogue[c + 12] + (catalogue[c + 14] << 5)); - int number_of_records = catalogue[c + 15]; + // Create file. + result->files.emplace_back(); + File &new_file = result->files.back(); + new_file.user_number = entry->user_number; + new_file.name = std::move(entry->name); + new_file.type = std::move(entry->type); + new_file.read_only = entry->read_only; + new_file.system = entry->system; - size_t required_size = extent * bytes_per_catalogue_entry + (size_t)number_of_records * 128; - if(result->files[index].data.size() < required_size) { - result->files[index].data.resize(required_size); - } + // Create storage for data. + size_t required_size = final_entry->extent * bytes_per_catalogue_entry + (size_t)final_entry->number_of_records * 128; + new_file.data.resize(required_size); - int sectors_per_block = parameters.block_size / (int)sector_size; - int records_per_sector = (int)sector_size / 128; - int record = 0; - for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < number_of_records; block++) { - int block_number; - if(has_long_allocation_units) { - block_number = catalogue[c + 16 + (block << 1)] + (catalogue[c + 16 + (block << 1) + 1] << 8); - } else { - block_number = catalogue[c + 16 + block]; - } - if(!block_number) { - record += parameters.block_size / 128; - continue; - } - int first_sector = block_number * sectors_per_block; - - sector = first_sector % parameters.sectors_per_track; - track = first_sector / parameters.sectors_per_track; - - for(int s = 0; s < sectors_per_block && record < number_of_records; s++) { - Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector)); - if(!sector_contents) break; - sector++; - if(sector == parameters.sectors_per_track) { - sector = 0; - track++; + // Accumulate all data. + while(entry <= final_entry) { + int record = 0; + for(size_t block = 0; block < (has_long_allocation_units ? 8 : 16) && record < entry->number_of_records; block++) { + int block_number; + if(has_long_allocation_units) { + block_number = catalogue[entry->catalogue_index + 16 + (block << 1)] + (catalogue[entry->catalogue_index + 16 + (block << 1) + 1] << 8); + } else { + block_number = catalogue[entry->catalogue_index + 16 + block]; } + if(!block_number) { + record += parameters.block_size / 128; + continue; + } + int first_sector = block_number * sectors_per_block; - int records_to_copy = std::min(number_of_records - record, records_per_sector); - memcpy(&result->files[index].data[extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128); - record += records_to_copy; + sector = first_sector % parameters.sectors_per_track; + track = first_sector / parameters.sectors_per_track; + + for(int s = 0; s < sectors_per_block && record < entry->number_of_records; s++) { + Storage::Encodings::MFM::Sector *sector_contents = parser.get_sector(0, (uint8_t)track, (uint8_t)(parameters.first_sector + sector)); + if(!sector_contents) break; + sector++; + if(sector == parameters.sectors_per_track) { + sector = 0; + track++; + } + + int records_to_copy = std::min(entry->number_of_records - record, records_per_sector); + memcpy(&new_file.data[entry->extent * bytes_per_catalogue_entry + (size_t)record * 128], sector_contents->data.data(), (size_t)records_to_copy * 128); + record += records_to_copy; + } } + + entry++; } }