1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-23 03:32:32 +00:00

Merge pull request #815 from TomHarte/WOZ2

Support WOZ 2 disk images
This commit is contained in:
Thomas Harte 2020-07-19 23:22:55 -04:00 committed by GitHub
commit 9de43dac95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 270 additions and 221 deletions

View File

@ -18,11 +18,21 @@ using namespace Storage::Disk;
WOZ::WOZ(const std::string &file_name) :
file_(file_name) {
const char signature[8] = {
constexpr const char signature1[8] = {
'W', 'O', 'Z', '1',
char(0xff), 0x0a, 0x0d, 0x0a
};
if(!file_.check_signature(signature, 8)) throw Error::InvalidFormat;
constexpr const char signature2[8] = {
'W', 'O', 'Z', '2',
char(0xff), 0x0a, 0x0d, 0x0a
};
const bool isWoz1 = file_.check_signature(signature1, 8);
file_.seek(0, SEEK_SET);
const bool isWoz2 = file_.check_signature(signature2, 8);
if(!isWoz1 && !isWoz2) throw Error::InvalidFormat;
type_ = isWoz2 ? Type::WOZ2 : Type::WOZ1;
// Get the file's CRC32.
const uint32_t crc = file_.get32le();
@ -52,13 +62,22 @@ WOZ::WOZ(const std::string &file_name) :
switch(chunk_id) {
case CK("INFO"): {
const uint8_t version = file_.get8();
if(version != 1) break;
if(version > 2) break;
is_3_5_disk_ = file_.get8() == 2;
is_read_only_ = file_.get8() == 1;
/* Ignored:
/*
Ignored:
1 byte: Synchronized; 1 = Cross track sync was used during imaging.
1 byte: Cleaned; 1 = MC3470 fake bits have been removed.
32 bytes: Cretor; a UTF-8 string.
32 bytes: Creator; a UTF-8 string.
And, if version 2, following the creator:
1 byte number of disk sides
1 byte boot sector format
1 byte optimal bit timing
2 bytes compatible hardware
2 bytes minimum required RAM
2 bytes largest track
*/
} break;
@ -99,11 +118,15 @@ long WOZ::file_offset(Track::Address address) {
if(track_map_[table_position] == 0xff) return NoSuchTrack;
// Seek to the real track.
return tracks_offset_ + track_map_[table_position] * 6656;
switch(type_) {
case Type::WOZ1: return tracks_offset_ + track_map_[table_position] * 6656;
default:
case Type::WOZ2: return tracks_offset_ + track_map_[table_position] * 8;
}
}
std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
long offset = file_offset(address);
const long offset = file_offset(address);
if(offset == NoSuchTrack) return nullptr;
// Seek to the real track.
@ -113,18 +136,34 @@ std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
std::lock_guard lock_guard(file_.get_file_access_mutex());
file_.seek(offset, SEEK_SET);
// In WOZ a track is up to 6646 bytes of data, followed by a two-byte record of the
switch(type_) {
case Type::WOZ1:
// In WOZ 1, a track is up to 6646 bytes of data, followed by a two-byte record of the
// number of bytes that actually had data in them, then a two-byte count of the number
// of bits that were used. Other information follows but is not intended for emulation.
track_contents = file_.read(6646);
file_.seek(2, SEEK_CUR);
number_of_bits = std::min(file_.get16le(), uint16_t(6646*8));
break;
case Type::WOZ2: {
// In WOZ 2 an extra level of indirection allows for variable track sizes.
const uint16_t starting_block = file_.get16le();
file_.seek(2, SEEK_CUR); // Skip the block count; the amount of data to read is implied by the number of bits.
number_of_bits = file_.get32le();
file_.seek(starting_block * 512, SEEK_SET);
track_contents = file_.read((number_of_bits + 7) >> 3);
} break;
}
}
return std::make_shared<PCMTrack>(PCMSegment(number_of_bits, track_contents));
}
void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
if(type_ == Type::WOZ2) return;
for(const auto &pair: tracks) {
// Decode the track and store, patching into the post_crc_contents_.
auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000));
@ -155,5 +194,5 @@ void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tra
}
bool WOZ::get_is_read_only() {
return file_.get_is_known_read_only();
return file_.get_is_known_read_only() || is_read_only_ || type_ == Type::WOZ2; // WOZ 2 disks are currently read only.
}

View File

@ -34,6 +34,9 @@ class WOZ: public DiskImage {
private:
Storage::FileHolder file_;
enum class Type {
WOZ1, WOZ2
} type_ = Type::WOZ1;
bool is_read_only_ = false;
bool is_3_5_disk_ = false;
uint8_t track_map_[160];
@ -49,7 +52,7 @@ class WOZ: public DiskImage {
the track does not exit.
*/
long file_offset(Track::Address address);
constexpr static long NoSuchTrack = 0; // This is definitely an offset a track can't lie at.
constexpr static long NoSuchTrack = 0; // This is an offset a track definitely can't lie at.
};
}

View File

@ -250,6 +250,15 @@ void Drive::run_for(const Cycles cycles) {
// MARK: - Track timed event loop
void Drive::get_next_event(float duration_already_passed) {
/*
Quick word on random-bit generation logic below; it seeks to obey the following logic:
if there is a gap of 15µs between recorded bits, start generating flux transitions
at random intervals thereafter, unless and until one is within 5µs of the next real transition.
This behaviour is based on John Morris' observations of an MC3470, as described in his WOZ
file format documentation https://applesaucefdc.com/woz/reference2/
*/
if(!disk_) {
current_event_.type = Track::Event::IndexHole;
current_event_.length = 1.0f;
@ -268,18 +277,19 @@ void Drive::get_next_event(float duration_already_passed) {
// If gain has now been turned up so as to generate noise, generate some noise.
if(random_interval_ > 0.0f) {
current_event_.type = Track::Event::FluxTransition;
current_event_.length = float(2 + (random_source_&1)) / 1000000.0f;
current_event_.length = float(2 + (random_source_&1)) / 1'000'000.0f;
random_source_ = (random_source_ >> 1) | (random_source_ << 63);
if(random_interval_ < current_event_.length) {
current_event_.length = random_interval_;
// If this random transition is closer than 5µs to the next real bit,
// discard it.
if(random_interval_ - 5.0f / 1'000'000.f < current_event_.length) {
random_interval_ = 0.0f;
} else {
random_interval_ -= current_event_.length;
}
set_next_event_time_interval(current_event_.length);
return;
}
}
if(track_) {
const auto track_event = track_->get_next_event();
@ -299,9 +309,9 @@ void Drive::get_next_event(float duration_already_passed) {
// convert it into revolutions per second; this is achieved by multiplying by rotational_multiplier_
float interval = std::max((current_event_.length - duration_already_passed) * rotational_multiplier_, 0.0f);
// An interval greater than 15ms => adjust gain up the point where noise starts happening.
// Seed that up and leave a 15ms gap until it starts.
constexpr float safe_gain_period = 15.0f / 1000000.0f;
// An interval greater than 15µs => adjust gain up the point where noise starts happening.
// Seed that up and leave a 15µs gap until it starts.
constexpr float safe_gain_period = 15.0f / 1'000'000.0f;
if(interval >= safe_gain_period) {
random_interval_ = interval - safe_gain_period;
interval = safe_gain_period;

View File

@ -12,6 +12,8 @@
#include <array>
using namespace Storage::Encodings::AppleGCR;
namespace {
const uint8_t six_and_two_unmapping[] = {
@ -55,9 +57,181 @@ uint8_t unmap_five_and_three(uint8_t source) {
return five_and_three_unmapping[source - 0xab];
}
std::unique_ptr<Sector> decode_macintosh_sector(const std::array<uint_fast8_t, 8> &header, const std::unique_ptr<Sector> &original) {
// There must be at least 704 bytes to decode from.
if(original->data.size() < 704) return nullptr;
// Attempt a six-and-two unmapping of the header.
std::array<uint_fast8_t, 5> decoded_header;
for(size_t c = 0; c < decoded_header.size(); ++c) {
decoded_header[c] = unmap_six_and_two(header[c]);
if(decoded_header[c] == 0xff) {
return nullptr;
}
}
// Allocate a sector.
auto sector = std::make_unique<Sector>();
sector->data.resize(704);
// Test the checksum.
if(decoded_header[4] != (decoded_header[0] ^ decoded_header[1] ^ decoded_header[2] ^ decoded_header[3]))
sector->has_header_checksum_error = true;
// Decode the header.
sector->address.track = uint8_t(decoded_header[0] | ((decoded_header[2]&0x1f) << 6));
sector->address.sector = decoded_header[1];
sector->address.format = decoded_header[3];
sector->address.is_side_two = decoded_header[2] & 0x20;
// Reverse the GCR encoding of the sector contents to get back to 6-bit data.
for(size_t index = 0; index < sector->data.size(); ++index) {
sector->data[index] = unmap_six_and_two(original->data[index]);
if(sector->data[index] == 0xff) {
return nullptr;
}
}
// The first byte in the sector is a repeat of the sector number; test it
// for correctness.
if(sector->data[0] != sector->address.sector) {
return nullptr;
}
// Cf. the corresponding section of Encoder.cpp for logic below.
int checksum[3] = {0, 0, 0};
for(size_t c = 0; c < 175; ++c) {
// Calculate the rolling checcksum in order to decode the bytes.
checksum[0] = (checksum[0] << 1) | (checksum[0] >> 7);
// All offsets are +1 below, to skip the initial sector number duplicate.
const uint8_t top_bits = sector->data[1 + c*4];
// Decode first byte.
sector->data[0 + c * 3] = uint8_t((sector->data[2 + c*4] + ((top_bits & 0x30) << 2)) ^ checksum[0]);
checksum[2] += sector->data[0 + c * 3] + (checksum[0] >> 8);
// Decode second byte;
sector->data[1 + c * 3] = uint8_t((sector->data[3 + c*4] + ((top_bits & 0x0c) << 4)) ^ checksum[2]);
checksum[1] += sector->data[1 + c * 3] + (checksum[2] >> 8);
// Decode third byte, if there is one.
if(c != 174) {
sector->data[2 + c * 3] = uint8_t((sector->data[4 + c*4] + ((top_bits & 0x03) << 6)) ^ checksum[1]);
checksum[0] += sector->data[2 + c * 3] + (checksum[1] >> 8);
}
// Reset carries.
checksum[0] &= 0xff;
checksum[1] &= 0xff;
checksum[2] &= 0xff;
}
// Test the checksum.
if(
checksum[0] != uint8_t(sector->data[703] + ((sector->data[700] & 0x03) << 6)) ||
checksum[1] != uint8_t(sector->data[702] + ((sector->data[700] & 0x0c) << 4)) ||
checksum[2] != uint8_t(sector->data[701] + ((sector->data[700] & 0x30) << 2))
) sector->has_data_checksum_error = true;
// Report success.
sector->data.resize(524);
sector->encoding = Sector::Encoding::Macintosh;
return sector;
}
using namespace Storage::Encodings::AppleGCR;
std::unique_ptr<Sector> decode_appleii_sector(const std::array<uint_fast8_t, 8> &header, const std::unique_ptr<Sector> &original, bool is_five_and_three) {
// There must be at least 411 bytes to decode a five-and-three sector from;
// there must be only 343 if this is a six-and-two sector.
const size_t data_size = is_five_and_three ? 411 : 343;
if(original->data.size() < data_size) return nullptr;
// Check for apparent four and four encoding.
uint_fast8_t header_mask = 0xff;
for(auto c : header) header_mask &= c;
header_mask &= 0xaa;
if(header_mask != 0xaa) return nullptr;
// Allocate a sector and fill the header fields.
auto sector = std::make_unique<Sector>();
sector->data.resize(data_size);
sector->address.volume = ((header[0] << 1) | 1) & header[1];
sector->address.track = ((header[2] << 1) | 1) & header[3];
sector->address.sector = ((header[4] << 1) | 1) & header[5];
// Check the header checksum.
const uint_fast8_t checksum = ((header[6] << 1) | 1) & header[7];
if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) return nullptr;
// Unmap the sector contents.
for(size_t index = 0; index < data_size; ++index) {
sector->data[index] = is_five_and_three ? unmap_five_and_three(original->data[index]) : unmap_six_and_two(original->data[index]);
if(sector->data[index] == 0xff) {
return nullptr;
}
}
// Undo the XOR step on sector contents and check that checksum.
for(std::size_t c = 1; c < sector->data.size(); ++c) {
sector->data[c] ^= sector->data[c-1];
}
if(sector->data.back()) return nullptr;
// Having checked the checksum, remove it.
sector->data.resize(sector->data.size() - 1);
if(is_five_and_three) {
// TODO: the below is almost certainly incorrect; Beneath Apple DOS partly documents
// the process, enough to give the basic outline below of how five source bytes are
// mapped to eight five-bit quantities, but isn't clear on the order those bytes will
// end up in on disk.
std::vector<uint8_t> buffer(256);
for(size_t c = 0; c < 0x33; ++c) {
const uint8_t *const base = &sector->data[0x032 - c];
buffer[(c * 5) + 0] = uint8_t((base[0x000] << 3) | (base[0x100] >> 2));
buffer[(c * 5) + 1] = uint8_t((base[0x033] << 3) | (base[0x133] >> 2));
buffer[(c * 5) + 2] = uint8_t((base[0x066] << 3) | (base[0x166] >> 2));
buffer[(c * 5) + 3] = uint8_t((base[0x099] << 3) | ((base[0x100] & 2) << 1) | (base[0x133] & 2) | ((base[0x166] & 2) >> 1));
buffer[(c * 5) + 4] = uint8_t((base[0x0cc] << 3) | ((base[0x100] & 1) << 2) | ((base[0x133] & 1) << 1) | (base[0x166] & 1));
}
buffer[255] = uint8_t((sector->data[0x0ff] << 3) | (sector->data[0x199] >> 2));
sector->data = std::move(buffer);
sector->encoding = Sector::Encoding::FiveAndThree;
} else {
// Undo the 6 and 2 mapping.
constexpr uint8_t bit_reverse[] = {0, 2, 1, 3};
#define unmap(byte, nibble, shift) \
sector->data[86 + byte] = uint8_t(\
(sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]);
for(std::size_t c = 0; c < 84; ++c) {
unmap(c, c, 0);
unmap(c+86, c, 2);
unmap(c+172, c, 4);
}
unmap(84, 84, 0);
unmap(170, 84, 2);
unmap(85, 85, 0);
unmap(171, 85, 2);
#undef unmap
// Throw away the collection of two-bit chunks from the start of the sector.
sector->data.erase(sector->data.begin(), sector->data.end() - 256);
sector->encoding = Sector::Encoding::SixAndTwo;
}
// Return successfully.
return sector;
}
}
std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment(const Disk::PCMSegment &segment) {
std::map<std::size_t, Sector> result;
@ -112,7 +286,7 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
if(scanner[2] == data_prologue[2]) {
new_sector = std::make_unique<Sector>();
new_sector->data.reserve(710);
} else {
} else { // i.e. the third symbol is from either of the header prologues.
sector_location = size_t(bit % segment.data.size());
header_delay = 200; // Allow up to 200 bytes to find the body, if the
// track split comes in between.
@ -120,23 +294,12 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
}
} else {
if(new_sector) {
// Check whether the value just read is a legal GCR byte, in six-and-two
// encoding (which is a strict superset of five-and-three).
// Check whether the value just read is a legal GCR byte, for this sector;
// if not, or if
const bool is_invalid = is_five_and_three ? (unmap_five_and_three(value) == 0xff) : (unmap_six_and_two(value) == 0xff);
if(is_invalid) {
// The second byte of the standard epilogue is illegal, so this still may
// be a valid sector. If the final byte was the first byte of an epilogue,
// chop it off and see whether the sector is otherwise intelligible.
if(new_sector->data.empty() || new_sector->data.back() != epilogue[0]) {
// No sector found; reset scanning procedure.
new_sector.reset();
pointer = scanning_sentinel;
continue;
}
// Chop off the last byte.
new_sector->data.resize(new_sector->data.size() - 1);
if(is_invalid || new_sector->data.size() >= 704) {
// The second byte of the standard epilogue is 'illegal', as is the first byte of
// all prologues. So either a whole sector has been captured up to now, or it hasn't.
// Move the sector elsewhere for processing; there's definitely no way to proceed with
// the prospective sector if it doesn't parse.
@ -144,185 +307,19 @@ std::map<std::size_t, Sector> Storage::Encodings::AppleGCR::sectors_from_segment
new_sector.reset();
pointer = scanning_sentinel;
// Check for valid decoding options.
switch(sector->data.size()) {
default: // This is not a decodeable sector.
break;
case 411: // Potentially this is an Apple II five-and-three sector.
case 343: { // Potentially this is an Apple II six-and-two sector.
// Check for apparent four and four encoding.
uint_fast8_t header_mask = 0xff;
for(auto c : header) header_mask &= c;
header_mask &= 0xaa;
if(header_mask != 0xaa) continue;
sector->address.volume = ((header[0] << 1) | 1) & header[1];
sector->address.track = ((header[2] << 1) | 1) & header[3];
sector->address.sector = ((header[4] << 1) | 1) & header[5];
// Check the header checksum.
// The 0x11 is reverse engineered from the game 'Alien Rain' and is present even on the boot sector,
// so probably isn't copy protection?
uint_fast8_t checksum = (((header[6] << 1) | 1) & header[7]) ^ (is_five_and_three ? 0x11 : 0x00);
if(checksum != (sector->address.volume^sector->address.track^sector->address.sector)) continue;
// Unmap the sector contents.
bool out_of_bounds = false;
for(auto &c : sector->data) {
c = is_five_and_three ? unmap_five_and_three(c) : unmap_six_and_two(c);
if(c == 0xff) {
out_of_bounds = true;
break;
}
}
if(out_of_bounds) continue;
// Undo the XOR step on sector contents and check that checksum.
for(std::size_t c = 1; c < sector->data.size(); ++c) {
sector->data[c] ^= sector->data[c-1];
}
if(sector->data.back()) continue;
// Having checked the checksum, remove it.
sector->data.resize(sector->data.size() - 1);
if(is_five_and_three) {
// TODO: the above is almost certainly incorrect; Beneath Apple DOS partly documents
// the process, enough to give the basic outline below of how five source bytes are
// mapped to eight five-bit quantities, but isn't clear on the order those bytes will
// end up in on disk.
std::vector<uint8_t> buffer(256);
for(size_t c = 0; c < 0x33; ++c) {
const uint8_t *const base = &sector->data[0x032 - c];
buffer[(c * 5) + 0] = uint8_t((base[0x000] << 3) | (base[0x100] >> 2));
buffer[(c * 5) + 1] = uint8_t((base[0x033] << 3) | (base[0x133] >> 2));
buffer[(c * 5) + 2] = uint8_t((base[0x066] << 3) | (base[0x166] >> 2));
buffer[(c * 5) + 3] = uint8_t((base[0x099] << 3) | ((base[0x100] & 2) << 1) | (base[0x133] & 2) | ((base[0x166] & 2) >> 1));
buffer[(c * 5) + 4] = uint8_t((base[0x0cc] << 3) | ((base[0x100] & 1) << 2) | ((base[0x133] & 1) << 1) | (base[0x166] & 1));
}
buffer[255] = uint8_t((sector->data[0x0ff] << 3) | (sector->data[0x199] >> 2));
sector->data = std::move(buffer);
sector->encoding = Sector::Encoding::FiveAndThree;
} else {
// Undo the 6 and 2 mapping.
const uint8_t bit_reverse[] = {0, 2, 1, 3};
#define unmap(byte, nibble, shift) \
sector->data[86 + byte] = uint8_t(\
(sector->data[86 + byte] << 2) | bit_reverse[(sector->data[nibble] >> shift)&3]);
for(std::size_t c = 0; c < 84; ++c) {
unmap(c, c, 0);
unmap(c+86, c, 2);
unmap(c+172, c, 4);
}
unmap(84, 84, 0);
unmap(170, 84, 2);
unmap(85, 85, 0);
unmap(171, 85, 2);
#undef unmap
// Throw away the collection of two-bit chunks from the start of the sector.
sector->data.erase(sector->data.begin(), sector->data.end() - 256);
sector->encoding = Sector::Encoding::SixAndTwo;
}
// Add this sector to the map.
result.insert(std::make_pair(sector_location, std::move(*sector)));
} break;
case 704: { // Potentially this is a Macintosh sector.
// Attempt a six-and-two unmapping of the header.
std::array<uint_fast8_t, 5> decoded_header;
bool out_of_bounds = false;
for(size_t c = 0; c < decoded_header.size(); ++c) {
decoded_header[c] = unmap_six_and_two(header[c]);
if(decoded_header[c] == 0xff) {
out_of_bounds = true;
break;
}
}
if(out_of_bounds) {
// Potentially this is a Macintosh sector.
auto macintosh_sector = decode_macintosh_sector(header, sector);
if(macintosh_sector) {
result.insert(std::make_pair(sector_location, std::move(*macintosh_sector)));
continue;
}
// Test the checksum.
if(decoded_header[4] != (decoded_header[0] ^ decoded_header[1] ^ decoded_header[2] ^ decoded_header[3]))
sector->has_header_checksum_error = true;
// Decode the header.
sector->address.track = uint8_t(decoded_header[0] | ((decoded_header[2]&0x1f) << 6));
sector->address.sector = decoded_header[1];
sector->address.format = decoded_header[3];
sector->address.is_side_two = decoded_header[2] & 0x20;
// Reverse the GCR encoding of the sector contents to get back to 6-bit data.
for(auto &c: sector->data) {
c = unmap_six_and_two(c);
if(c == 0xff) {
out_of_bounds = true;
break;
}
}
if(out_of_bounds) {
continue;
// Apple II then?
auto appleii_sector = decode_appleii_sector(header, sector, is_five_and_three);
if(appleii_sector) {
result.insert(std::make_pair(sector_location, std::move(*appleii_sector)));
}
// The first byte in the sector is a repeat of the sector number; test it
// for correctness.
if(sector->data[0] != sector->address.sector) {
continue;
}
// Cf. the corresponding section of Encoder.cpp for logic below.
int checksum[3] = {0, 0, 0};
for(size_t c = 0; c < 175; ++c) {
// Calculate the rolling checcksum in order to decode the bytes.
checksum[0] = (checksum[0] << 1) | (checksum[0] >> 7);
// All offsets are +1 below, to skip the initial sector number duplicate.
const uint8_t top_bits = sector->data[1 + c*4];
// Decode first byte.
sector->data[0 + c * 3] = uint8_t((sector->data[2 + c*4] + ((top_bits & 0x30) << 2)) ^ checksum[0]);
checksum[2] += sector->data[0 + c * 3] + (checksum[0] >> 8);
// Decode second byte;
sector->data[1 + c * 3] = uint8_t((sector->data[3 + c*4] + ((top_bits & 0x0c) << 4)) ^ checksum[2]);
checksum[1] += sector->data[1 + c * 3] + (checksum[2] >> 8);
// Decode third byte, if there is one.
if(c != 174) {
sector->data[2 + c * 3] = uint8_t((sector->data[4 + c*4] + ((top_bits & 0x03) << 6)) ^ checksum[1]);
checksum[0] += sector->data[2 + c * 3] + (checksum[1] >> 8);
}
// Reset carries.
checksum[0] &= 0xff;
checksum[1] &= 0xff;
checksum[2] &= 0xff;
}
// Test the checksum.
if(
checksum[0] != uint8_t(sector->data[703] + ((sector->data[700] & 0x03) << 6)) ||
checksum[1] != uint8_t(sector->data[702] + ((sector->data[700] & 0x0c) << 4)) ||
checksum[2] != uint8_t(sector->data[701] + ((sector->data[700] & 0x30) << 2))
) sector->has_data_checksum_error = true;
// Chop to size, and that's that.
sector->data.resize(524);
// Add this sector to the map.
sector->encoding = Sector::Encoding::Macintosh;
result.insert(std::make_pair(sector_location, std::move(*sector)));
} break;
}
} else {
new_sector->data.push_back(value);
}