summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAki <please@ignore.pl>2022-08-11 21:46:05 +0200
committerAki <please@ignore.pl>2022-08-11 21:46:05 +0200
commitff972b194ff36252e9974327f66f05ea394fd704 (patch)
treed48504c4c48e1d14476646caeb5b0f8f3ef5182e
parentca3cabf0670f1d313412f6eb0db7dad3a5e6be56 (diff)
downloadstarshatter-ff972b194ff36252e9974327f66f05ea394fd704.zip
starshatter-ff972b194ff36252e9974327f66f05ea394fd704.tar.gz
starshatter-ff972b194ff36252e9974327f66f05ea394fd704.tar.bz2
Implemented inserting entries into archive
-rw-r--r--ArchiveEx/Archive.cpp128
-rw-r--r--ArchiveEx/Archive.h3
2 files changed, 131 insertions, 0 deletions
diff --git a/ArchiveEx/Archive.cpp b/ArchiveEx/Archive.cpp
index 0a99982..68375f2 100644
--- a/ArchiveEx/Archive.cpp
+++ b/ArchiveEx/Archive.cpp
@@ -113,6 +113,114 @@ Archive::Find(const char* filepath) const
}
+int
+Archive::Update(const char* filepath)
+{
+ const std::size_t namelen = std::strlen(filepath);
+ if (NAMELEN <= namelen)
+ return -1;
+ UniqueFileHandle source {std::fopen(filepath, "rb"), &std::fclose};
+ int err = std::fseek(source.get(), 0, SEEK_END);
+ if (-1 == err)
+ return -1;
+ const std::size_t uncompressed_size = std::ftell(source.get());
+ err = std::fseek(source.get(), 0, SEEK_SET);
+ if (-1 == err || 0 == uncompressed_size)
+ return -1;
+ auto uncompressed = std::make_unique<Bytef[]>(uncompressed_size);
+ const auto read_result = std::fread(uncompressed.get(), 1, uncompressed_size, source.get());
+ if (read_result != uncompressed_size)
+ return -1;
+ source.reset(nullptr);
+ uLongf compressed_size = uncompressed_size * 1.1 + 12;
+ auto compressed = std::make_unique<Bytef[]>(compressed_size);
+ err = compress(compressed.get(), &compressed_size, uncompressed.get(), uncompressed_size);
+ if (Z_OK != err)
+ return -1;
+ uncompressed.reset();
+ int index;
+ const int previous_index = Find(filepath);
+ if (-1 != previous_index) {
+ for (auto& block : blocks) {
+ if (previous_index == block)
+ block = UNUSED_BLOCK;
+ }
+ index = previous_index;
+ }
+ else {
+ index = entries.size();
+ entries.emplace_back();
+ }
+ const auto destination_block = FindFreeSpot(compressed_size);
+ const auto total_blocks = BytesToBlocks(compressed_size);
+ if (blocks.size() < destination_block + total_blocks)
+ blocks.resize(destination_block + total_blocks, UNUSED_BLOCK);
+ for (std::size_t i = destination_block; i < destination_block + total_blocks; ++i)
+ blocks[i] = index;
+ std::strcpy(entries[index].name, filepath);
+ entries[index].original_size = uncompressed_size;
+ entries[index].compressed_size = compressed_size;
+ entries[index].offset = destination_block * BLOCK_SIZE;
+ UniqueFileHandle archive {std::fopen(path, "rb+"), &std::fclose};
+ err = std::fseek(archive.get(), sizeof(Header) + entries[index].offset, SEEK_SET);
+ if (-1 == err)
+ return -1;
+ const auto write_result = std::fwrite(compressed.get(), 1, compressed_size, archive.get());
+ if (write_result != compressed_size)
+ return -1;
+ archive.reset(nullptr);
+ WriteMeta();
+ return index;
+}
+
+
+void
+Archive::WriteMeta()
+{
+ const std::size_t total_entries = entries.size();
+ const std::size_t dirsize = total_entries + DIRECTORY_MARGIN;
+ const std::size_t uncompressed_size = dirsize * sizeof(Entry);
+ entries.reserve(dirsize);
+ uLongf compressed_size = uncompressed_size * 1.1 + 12;
+ auto compressed = std::make_unique<Bytef[]>(compressed_size);
+ int err = compress(
+ compressed.get(),
+ &compressed_size,
+ reinterpret_cast<Bytef*>(entries.data()),
+ uncompressed_size);
+ if (Z_OK != err)
+ throw "could not compress directory metadata";
+ for (auto& block : blocks) {
+ if (DIRECTORY_BLOCK == block)
+ block = UNUSED_BLOCK;
+ }
+ const auto destination_block = FindFreeSpot(compressed_size);
+ const auto total_blocks = BytesToBlocks(compressed_size);
+ if (blocks.size() < destination_block + total_blocks)
+ blocks.resize(destination_block + total_blocks, UNUSED_BLOCK);
+ for (std::size_t i = destination_block; i < destination_block + total_blocks; ++i)
+ blocks[i] = DIRECTORY_BLOCK;
+ const std::size_t offset = destination_block * BLOCK_SIZE;
+ header.total_entries = total_entries;
+ header.directory.total_blocks = total_blocks; // Unused
+ header.directory.compressed_size = compressed_size;
+ header.directory.offset = offset;
+ UniqueFileHandle archive {std::fopen(path, "rb+"), &std::fclose};
+ err = std::fseek(archive.get(), 0, SEEK_SET);
+ if (-1 == err)
+ throw "could not write header (seek)";
+ err = std::fwrite(&header, sizeof(Header), 1, archive.get());
+ if (1 != err)
+ throw "could not write header (write)";
+ err = std::fseek(archive.get(), sizeof(Header) + offset, SEEK_SET);
+ if (-1 == err)
+ throw "could not write directory (seek)";
+ err = std::fwrite(compressed.get(), 1, compressed_size, archive.get());
+ if (static_cast<int>(compressed_size) != err)
+ throw "could not write directory (write)";
+}
+
+
void
Archive::UpdateBlockMap()
{
@@ -136,6 +244,26 @@ Archive::UpdateBlockMap()
}
+std::size_t
+Archive::FindFreeSpot(const std::size_t bytes) const
+{
+ const std::size_t blocks_needed = BytesToBlocks(bytes);
+ std::size_t index = 1;
+ std::size_t so_far = 0;
+ for (; index <= blocks.size(); ++index) {
+ if (UNUSED_BLOCK == blocks[index - 1])
+ so_far++;
+ else
+ so_far = 0;
+ if (blocks_needed <= so_far)
+ break;
+ }
+ if (blocks.size() < index)
+ index = blocks.size();
+ return index - so_far;
+}
+
+
void
Archive::PrintNamesOfEntries() const
{
diff --git a/ArchiveEx/Archive.h b/ArchiveEx/Archive.h
index e19c718..5330aea 100644
--- a/ArchiveEx/Archive.h
+++ b/ArchiveEx/Archive.h
@@ -40,7 +40,10 @@ public:
int Expand(int index, std::uint8_t*& buffer, bool null_terminated=false) const;
int Expand(const char* filepath, std::uint8_t*& buffer, bool null_terminated=false) const;
int Find(const char* filepath) const;
+ int Update(const char* filepath);
+ void WriteMeta();
void UpdateBlockMap();
+ std::size_t FindFreeSpot(std::size_t bytes) const;
void PrintNamesOfEntries() const;
void PrintBlocks() const;
std::size_t DirectoryBlocks() const;