diff --git a/src/tm_converter2/tm_converter2.cpp b/src/tm_converter2/tm_converter2.cpp
new file mode 100644
index 0000000..9a77a2c
--- /dev/null
+++ b/src/tm_converter2/tm_converter2.cpp
@@ -0,0 +1,192 @@
+/*
+ * AIM tm_converter
+ * Copyright (C) 2015 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
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include
+#endif
+
+/*
+aim icons
+TEX_KT_ICONS.TM
+64 x 32
+16 icon in a column
+*/
+
+// TODO: add dxt5 compressor from stb libs (stb_dxt5)
+// see org.sw.demo.stb.all
+
+using namespace std;
+
+#pragma pack(push, 1)
+struct tm_file {
+ int32_t width;
+ int32_t height;
+ // https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dformat
+#ifdef _WIN32
+ D3DFORMAT d3dformat;
+#else
+ uint32_t d3dformat;
+#endif
+ // see for some infos https://learn.microsoft.com/en-us/previous-versions/ms889290(v=msdn.10)
+ // IDirect3DDevice8::CreateTexture
+ // or not? or number of helper images or total imgs
+ uint32_t levels;
+ // or not only dxt5? maybe enum?
+ uint32_t dxt5_compression;
+ // TEX_NIGHT13.TM has 1 here
+ // TEX_kt_icons.TM has 8 here
+ // maybe levels?
+ // number of anims? icon variants?
+ uint32_t unk0;
+ uint8_t unkX[0x4C-0x18];
+};
+#pragma pack(pop)
+
+void convert(const path &fn)
+{
+ primitives::templates2::mmap_file f{fn};
+ stream s{f};
+ tm_file tm = s;
+
+ // format check
+ switch (tm.d3dformat) {
+ case D3DFMT_A8R8G8B8:
+ break;
+ case D3DFMT_X8R8G8B8:
+ break;
+ default:
+ SW_UNIMPLEMENTED;
+ }
+
+ // compression check
+ switch (tm.dxt5_compression) {
+ case 0:
+ break;
+ case 1:
+ break;
+ default:
+ SW_UNIMPLEMENTED;
+ }
+
+ auto save = [&](auto &&sub) {
+ cv::Mat m(tm.width, tm.height, CV_8UC4);
+ if (tm.dxt5_compression) {
+ // this have some one off results
+ // probaby we did different rounding in current (v1) impl
+ BlockDecompressImageDXT5(tm.width, tm.height, s.p, (unsigned long *)m.data);
+ s.skip(tm.width * tm.height);
+ // only for dxt5
+ m.forEach([](auto &v, auto *pos) {
+ // ARGB -> BGRA (big endian)
+ // but in bytes (little endian)
+ // 0xBGRA -> 0xABGR
+ auto &val = *(uint32_t *)&v;
+ val = std::rotr(val, 8);
+ });
+ if (tm.d3dformat == D3DFMT_X8R8G8B8) {
+ // and shrink if possible
+ cv::cvtColor(m, m, cv::COLOR_BGRA2BGR);
+ }
+ }
+ else
+ {
+ // 'simple' files consists of:
+ // 1. 'normal' image (bitmap)
+ // 2. some kind of mask?
+ // 3. rest? levels?
+ // but every tm has multiple levels or so
+
+ int size = tm.width * tm.height * 2;
+ auto dst = m.data;
+ for (int i = 0; i < size; ++i) {
+ uint8_t c = s;
+ uint8_t lo = c & 0x0F;
+ uint8_t hi = (c & 0xF0) >> 4;
+ *dst++ = (lo << 4) | lo;
+ *dst++ = (hi << 4) | hi;
+ }
+ }
+ // opencv can't save to tga directly
+ cv::imwrite((path(fn) += sub + ".bmp"s).string(), m);
+ };
+ save("");
+ // not sure how to parse rest, probably dx8 generated texture levels
+ // and we dont need them
+ return;
+
+ if (!tm.dxt5_compression) {
+ save("_mask"); // what is it?
+ }
+ for (int i = 0; i < tm.unk0; ++i) {
+ // obviosly small
+ tm.width /= 2;
+ tm.height /= 2;
+ save(std::format("_small{}", i + 1));
+ if (!tm.dxt5_compression) {
+ save(std::format("_small{}_mask", i + 1));
+ }
+ }
+
+ if (s.p - f.p != f.sz) {
+ int a = 5;
+ a++;
+ SW_UNIMPLEMENTED;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ cl::list list(cl::Positional, cl::desc(""), cl::Required, cl::OneOrMore);
+
+ cl::ParseCommandLineOptions(argc, argv);
+
+ for (auto &&p : list) {
+ if (fs::is_regular_file(p)) {
+ convert(p);
+ } else if (fs::is_directory(p)) {
+ auto files = enumerate_files_like(p, ".*\\.TM", false);
+ for (auto &f : files) {
+ std::cout << "processing: " << to_printable_string(f) << "\n";
+ convert(f);
+ }
+ } else {
+ throw std::runtime_error("Bad fs object");
+ }
+ }
+ return 0;
+}
diff --git a/sw.cpp b/sw.cpp
index 3a621ed..4e71a46 100644
--- a/sw.cpp
+++ b/sw.cpp
@@ -57,6 +57,10 @@ void build(Solution &s)
add_exe_with_common("script2txt");
add_exe_with_common("txt2script");
add_exe_with_common("tm_converter");
+ add_exe_with_common("tm_converter2") +=
+ "org.sw.demo.BenjaminDobell.s3tc_dxt_decompression-master"_dep,
+ "org.sw.demo.intel.opencv.highgui"_dep
+ ;
add_exe("name_generator");
add_exe_with_common("save_loader");
add_exe_with_common("bms_converter");