diff --git a/src/paker/paker.cpp b/src/paker/paker.cpp
new file mode 100644
index 0000000..2363f6e
--- /dev/null
+++ b/src/paker/paker.cpp
@@ -0,0 +1,133 @@
+/*
+ * AIM paker (for AIM1 game only)
+ * Copyright (C) 2024 lzwdgc
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+int main(int argc, char *argv[]) {
+ cl::opt name(cl::Positional, cl::desc(""), cl::Required);
+ cl::list in_files(cl::Positional, cl::desc(""), cl::Required, cl::OneOrMore);
+
+ cl::ParseCommandLineOptions(argc, argv);
+
+ struct file {
+ path fn;
+ String alias;
+ auto operator<=>(const file &) const = default;
+ };
+ std::set files;
+ for (auto &&f : in_files) {
+ auto s = f;
+ boost::replace_all(s, "/", "\\");
+ boost::to_lower(s);
+ auto v = split_string(s, "=");
+ if (v.size() > 2 || v.empty()) {
+ throw std::runtime_error("bad input filename: "s + f);
+ }
+ if (v.size() == 1) {
+ path p{s};
+ String alias;
+ // some heuristics
+ if (p.extension() == ".qst" || p.extension() == ".scr") {
+ alias = "script\\bin\\" + s;
+ }
+ files.emplace(v[0], alias);
+ } else if (v.size() == 2) {
+ files.emplace(v[0], v[1]);
+ }
+ }
+
+ size_t total{};
+ for (auto &[f,_] : files) {
+ total += fs::file_size(f);
+ }
+ uint32_t block_size = pak::default_block_size;
+ uint32_t block_size_len = sizeof(block_size);
+ block_size = total + block_size_len;
+ uint32_t block_size_minus_len = block_size - block_size_len; // minus block len
+ auto get_nsegs = [&](auto sz) {
+ auto nsegs = sz / block_size_minus_len;
+ if (nsegs == 0 || (sz % block_size_minus_len) != 0) {
+ ++nsegs;
+ }
+ return nsegs;
+ };
+ auto nsegs = get_nsegs(total);
+ total = 0;
+ total += files.size() * sizeof(pak::file_description);
+ total += nsegs * (sizeof(pak::segment) + block_size);
+ total += sizeof(pak);
+
+ pak p{};
+ p.block_size = block_size;
+ p.n_files = files.size();
+ p.n_blocks = nsegs;
+
+ primitives::templates2::mmap_file f{name, primitives::templates2::mmap_file::rw{}};
+ f.alloc_raw(total);
+
+ stream s{f};
+ s = p;
+ size_t offset = 0;
+ for (auto &[name,alias] : files) {
+ pak::file_description d;
+ auto str = alias.empty() ? name.string() : alias;
+ strcpy(d.name, str.c_str());
+ d.size = fs::file_size(name);
+ d.offset = offset;
+ offset += d.size;
+ s = d;
+ }
+ for (int i = 0; i < nsegs; ++i) {
+ pak::segment seg{};
+ seg.algorithm = 0; // NOTE: no compression!
+ seg.offset = sizeof(pak) + files.size() * sizeof(pak::file_description) + nsegs * sizeof(pak::segment) + i * block_size;
+ s = seg;
+ }
+ //uint32_t current_seg_len = 0;
+ s = block_size_minus_len; // for single seg
+ for (auto &[name,_] : files) {
+ std::cout << "processing: " << name << "\n";
+ primitives::templates2::mmap_file f{name};
+ auto sz = f.sz;
+ uint32_t sz_to_copy = sz;//sz > block_size_minus_len ? block_size_minus_len : sz;
+ //s = block_size_minus_len;
+ memcpy(s.p, f.p, sz_to_copy);
+ s.skip(sz_to_copy);
+ //current_seg_len += block_size_len + sz_to_copy;
+ }
+ f.close();
+ fs::resize_file(name, total);
+
+ return 0;
+}