changeset 1:0b3630a29ad8

Initial version based on previous repository. Project was ported to QT6 and in now cmake based.
author Ideenmodellierer <tiefenrauscher@web.de>
date Thu, 27 Nov 2025 18:40:28 +0100
parents 76ccd6ce50c0
children 177f640940f2
files AES/Adler16.h AES/rijndael.cpp AES/rijndael.h AES/unit_test.cpp AES/unit_test.pro CmakeLists.txt HardwareOperations.cpp HardwareOperations.h HexFile.cpp HexFile.h Images/Settings.ai Images/Settings.png Images/Settings.svg Images/app_OSTC_Companion.icns Images/app_OSTC_Companion.ico Images/app_OSTC_Companion.png Images/app_OSTC_Companion.svg Images/bluetooth.psd Images/companion_ostc_3.png Images/dox_Companion.png Images/frog_160x120.png Images/frog_160x120.psd Images/inst_OSTC_Companion.ico Images/inst_OSTC_Companion.png Images/inst_OSTC_Companion.svg Images/ostc2c_160x120.png Images/ostc2c_160x120.psd Images/ostc2p_160x120.png Images/ostc2p_160x120.psd Images/ostc3_160x120.png Images/ostc3_160x120.psd Images/ostc3p_160x120.png Images/ostc3p_160x120.psd Images/ostc4_160x120.png Images/ostc4_160x120.psd Images/ostc_cr_160x120.png Images/ostc_cr_160x120.psd Images/ostc_sport_160x120.png Images/ostc_sport_160x120.psd Images/tux_256x256.png Images/unknown.png Images/usb.psd LogEditor.ui MainWindow.cpp MainWindow.h MainWindow.ui OSTC2Operations.cpp OSTC2Operations.h OSTC2cOperations.cpp OSTC2cOperations.h OSTC3Operations.cpp OSTC3Operations.h OSTC3pOperations.cpp OSTC3pOperations.h OSTC4Operations.cpp OSTC4Operations.h OSTCFrogOperations.cpp OSTCFrogOperations.h OSTCSportOperations.cpp OSTCSportOperations.h OSTC_CR_Operations.cpp OSTC_CR_Operations.h Resources.qrc Serial.cpp Serial.h Settings.ui SettingsDialog.cpp SettingsDialog.h Translations/companion_DE.qm Translations/companion_DE.ts Translations/companion_ES.qm Translations/companion_ES.ts Translations/companion_FR.qm Translations/companion_FR.ts Translations/companion_IT.qm Translations/companion_IT.ts Translations/companion_RU.qm Translations/companion_RU.ts Utils/Exception.cpp Utils/Exception.h Utils/Export.h Utils/Log.cpp Utils/Log.h Utils/LogAppender.cpp Utils/LogAppender.h Utils/LogConsole.cpp Utils/LogConsole.h Utils/LogEvent.cpp Utils/LogEvent.h Utils/LogFile.cpp Utils/LogFile.h Utils/ProgressEvent.cpp Utils/ProgressEvent.h VERSION.txt editlogdialog.cpp editlogdialog.h icon.rc main.cpp o3pack.cpp ui_LogEditor - Kopie.h ui_LogEditor.h ui_MainWindow.h ui_Settings.h
diffstat 103 files changed, 14500 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AES/Adler16.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,110 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Adler16.h
+/// \brief Adler checksum, on 2x8 bits.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2015-07-31 Creation.
+//  2016-05-24  jDG: BSD-2 version.
+
+#ifndef ADLER16_H
+#define ADLER16_H
+
+#include <QByteArray>
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief Addler checksum, on 2xN bits.
+template <typename T>
+class AdlerTemplate
+{
+    T _a, _b;
+
+public:
+    //------------------------------------------------------------------------
+    /// \brief Initialize checksum with a given seed.
+    inline AdlerTemplate(T a, T b)
+      : _a(a), _b(b)
+    {}
+
+    //------------------------------------------------------------------------
+    /// \brief Re-initialize checksum with a given seed.
+    inline void reset(T a, T b)
+    {   _a = a;
+        _b = b;
+    }
+
+    //------------------------------------------------------------------------
+    /// \brief Hash a single byte/word
+    inline void add(int c)
+    {
+        _a += (T)(c & 0xFF);
+        _b += _a;
+    }
+
+    //------------------------------------------------------------------------
+    /// \brief Hash a block of bytes.
+    /// \note \a size is always in BYTE units.
+    inline void add(const void* buffer, size_t size)
+    {
+        for(size_t i = 0; i<size; ++i)
+            add( ((const unsigned char*)buffer)[i] );
+    }
+
+    //------------------------------------------------------------------------
+    /// \brief Hash all the byte/words for a Qt buffer.
+    inline void add(const QByteArray& buffer)
+    {
+        add(buffer.data(), buffer.size());
+    }
+
+    //------------------------------------------------------------------------
+    /// \brief Returns hash result.
+    inline T a() const { return _a; }
+    inline T b() const { return _b; }
+
+    //------------------------------------------------------------------------
+    /// Check Hash result.
+    /// \returns TRUE if has do match the given values.
+    inline bool check(T a, T b)
+    {
+        return (_a == a) && (_b == b);
+    }
+};
+
+/// \brief 2x8 bits implementation.
+typedef AdlerTemplate<unsigned char>  Adler16;
+
+/// \brief 2x16 bits implementation.
+typedef AdlerTemplate<unsigned short> Adler32;
+
+#endif // ADDLER16_H
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AES/rijndael.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,1285 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   rijndael.cpp
+/// \brief  Public Domain AES encryption/decryption
+/// \author Philip J. Erdelsky <pje@efgh.com>, JD Gascuel, and others.
+///
+/// \copyright (c) 2015 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "rijndael.h"
+
+#ifdef WIN32
+#   include <Windows.h>     // GetTickCount64()
+#else
+#   include <sys/time.h>    // gettimeofday()
+#endif
+
+namespace Rijndael {
+
+#define FULL_UNROLL
+
+static const Word32 Te0[256] =
+{
+  0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
+  0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
+  0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
+  0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
+  0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
+  0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
+  0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
+  0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
+  0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
+  0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
+  0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
+  0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
+  0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
+  0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
+  0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
+  0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
+  0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
+  0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
+  0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
+  0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
+  0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
+  0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
+  0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
+  0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
+  0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
+  0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
+  0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
+  0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
+  0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
+  0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
+  0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
+  0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
+  0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
+  0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
+  0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
+  0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
+  0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
+  0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
+  0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
+  0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
+  0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
+  0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
+  0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
+  0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
+  0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
+  0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
+  0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
+  0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
+  0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
+  0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
+  0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
+  0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
+  0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
+  0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
+  0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
+  0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
+  0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
+  0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
+  0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
+  0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
+  0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
+  0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
+  0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
+  0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
+};
+
+static const Word32 Te1[256] =
+{
+  0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
+  0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
+  0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
+  0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
+  0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
+  0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
+  0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
+  0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
+  0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
+  0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
+  0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
+  0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
+  0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
+  0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
+  0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
+  0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
+  0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
+  0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
+  0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
+  0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
+  0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
+  0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
+  0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
+  0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
+  0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
+  0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
+  0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
+  0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
+  0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
+  0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
+  0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
+  0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
+  0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
+  0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
+  0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
+  0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
+  0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
+  0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
+  0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
+  0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
+  0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
+  0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
+  0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
+  0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
+  0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
+  0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
+  0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
+  0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
+  0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
+  0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
+  0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
+  0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
+  0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
+  0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
+  0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
+  0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
+  0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
+  0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
+  0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
+  0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
+  0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
+  0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
+  0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
+  0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
+};
+
+static const Word32 Te2[256] =
+{
+  0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
+  0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
+  0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
+  0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
+  0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
+  0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
+  0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
+  0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
+  0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
+  0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
+  0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
+  0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
+  0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
+  0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
+  0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
+  0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
+  0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
+  0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
+  0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
+  0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
+  0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
+  0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
+  0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
+  0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
+  0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
+  0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
+  0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
+  0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
+  0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
+  0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
+  0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
+  0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
+  0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
+  0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
+  0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
+  0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
+  0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
+  0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
+  0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
+  0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
+  0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
+  0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
+  0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
+  0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
+  0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
+  0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
+  0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
+  0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
+  0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
+  0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
+  0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
+  0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
+  0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
+  0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
+  0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
+  0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
+  0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
+  0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
+  0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
+  0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
+  0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
+  0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
+  0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
+  0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
+};
+
+static const Word32 Te3[256] =
+{
+  0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
+  0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
+  0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
+  0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
+  0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
+  0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
+  0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
+  0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
+  0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
+  0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
+  0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
+  0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
+  0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
+  0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
+  0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
+  0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
+  0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
+  0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
+  0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
+  0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
+  0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
+  0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
+  0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
+  0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
+  0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
+  0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
+  0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
+  0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
+  0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
+  0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
+  0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
+  0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
+  0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
+  0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
+  0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
+  0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
+  0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
+  0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
+  0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
+  0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
+  0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
+  0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
+  0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
+  0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
+  0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
+  0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
+  0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
+  0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
+  0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
+  0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
+  0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
+  0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
+  0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
+  0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
+  0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
+  0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
+  0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
+  0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
+  0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
+  0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
+  0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
+  0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
+  0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
+  0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
+};
+
+static const Word32 Te4[256] =
+{
+  0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU,
+  0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U,
+  0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU,
+  0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U,
+  0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU,
+  0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U,
+  0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU,
+  0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U,
+  0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U,
+  0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU,
+  0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U,
+  0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U,
+  0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U,
+  0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU,
+  0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U,
+  0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U,
+  0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU,
+  0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U,
+  0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U,
+  0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U,
+  0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU,
+  0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU,
+  0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U,
+  0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU,
+  0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU,
+  0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U,
+  0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU,
+  0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U,
+  0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU,
+  0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U,
+  0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U,
+  0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U,
+  0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU,
+  0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U,
+  0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU,
+  0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U,
+  0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU,
+  0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U,
+  0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U,
+  0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU,
+  0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU,
+  0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU,
+  0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U,
+  0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U,
+  0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU,
+  0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U,
+  0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU,
+  0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U,
+  0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU,
+  0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U,
+  0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU,
+  0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU,
+  0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U,
+  0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU,
+  0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U,
+  0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU,
+  0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U,
+  0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U,
+  0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U,
+  0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU,
+  0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU,
+  0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U,
+  0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU,
+  0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U,
+};
+
+static const Word32 Td0[256] =
+{
+  0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U,
+  0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U,
+  0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U,
+  0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU,
+  0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U,
+  0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U,
+  0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU,
+  0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U,
+  0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU,
+  0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U,
+  0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U,
+  0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U,
+  0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U,
+  0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU,
+  0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U,
+  0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU,
+  0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U,
+  0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU,
+  0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U,
+  0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U,
+  0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U,
+  0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU,
+  0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U,
+  0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU,
+  0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U,
+  0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU,
+  0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U,
+  0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU,
+  0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU,
+  0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U,
+  0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU,
+  0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U,
+  0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU,
+  0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U,
+  0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U,
+  0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U,
+  0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU,
+  0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U,
+  0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U,
+  0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU,
+  0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U,
+  0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U,
+  0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U,
+  0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U,
+  0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U,
+  0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU,
+  0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U,
+  0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U,
+  0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U,
+  0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U,
+  0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U,
+  0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU,
+  0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU,
+  0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU,
+  0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU,
+  0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U,
+  0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U,
+  0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU,
+  0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU,
+  0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U,
+  0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU,
+  0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U,
+  0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U,
+  0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U,
+};
+
+static const Word32 Td1[256] =
+{
+  0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU,
+  0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U,
+  0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU,
+  0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U,
+  0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U,
+  0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U,
+  0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U,
+  0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U,
+  0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U,
+  0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU,
+  0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU,
+  0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU,
+  0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U,
+  0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU,
+  0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U,
+  0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U,
+  0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U,
+  0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU,
+  0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU,
+  0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U,
+  0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU,
+  0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U,
+  0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU,
+  0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU,
+  0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U,
+  0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U,
+  0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U,
+  0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU,
+  0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U,
+  0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU,
+  0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U,
+  0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U,
+  0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U,
+  0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU,
+  0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U,
+  0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U,
+  0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U,
+  0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U,
+  0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U,
+  0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U,
+  0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU,
+  0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU,
+  0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U,
+  0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU,
+  0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U,
+  0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU,
+  0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU,
+  0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U,
+  0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU,
+  0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U,
+  0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U,
+  0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U,
+  0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U,
+  0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U,
+  0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U,
+  0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U,
+  0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU,
+  0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U,
+  0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U,
+  0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU,
+  0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U,
+  0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U,
+  0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U,
+  0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U,
+};
+
+static const Word32 Td2[256] =
+{
+  0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U,
+  0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U,
+  0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U,
+  0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U,
+  0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU,
+  0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U,
+  0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U,
+  0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U,
+  0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U,
+  0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU,
+  0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U,
+  0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U,
+  0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU,
+  0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U,
+  0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U,
+  0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U,
+  0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U,
+  0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U,
+  0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U,
+  0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU,
+  0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U,
+  0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U,
+  0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U,
+  0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U,
+  0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U,
+  0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU,
+  0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU,
+  0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U,
+  0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU,
+  0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U,
+  0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU,
+  0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU,
+  0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU,
+  0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU,
+  0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U,
+  0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U,
+  0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U,
+  0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U,
+  0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U,
+  0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U,
+  0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U,
+  0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU,
+  0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU,
+  0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U,
+  0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U,
+  0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU,
+  0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU,
+  0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U,
+  0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U,
+  0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U,
+  0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U,
+  0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U,
+  0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U,
+  0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U,
+  0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU,
+  0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U,
+  0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U,
+  0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U,
+  0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U,
+  0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U,
+  0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U,
+  0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU,
+  0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U,
+  0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U,
+};
+
+static const Word32 Td3[256] =
+{
+  0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU,
+  0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU,
+  0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U,
+  0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U,
+  0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU,
+  0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU,
+  0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U,
+  0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU,
+  0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U,
+  0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU,
+  0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U,
+  0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U,
+  0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U,
+  0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U,
+  0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U,
+  0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU,
+  0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU,
+  0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U,
+  0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U,
+  0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU,
+  0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU,
+  0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U,
+  0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U,
+  0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U,
+  0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U,
+  0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU,
+  0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U,
+  0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U,
+  0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU,
+  0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU,
+  0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U,
+  0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U,
+  0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U,
+  0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU,
+  0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U,
+  0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U,
+  0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U,
+  0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U,
+  0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U,
+  0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U,
+  0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U,
+  0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU,
+  0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U,
+  0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U,
+  0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU,
+  0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU,
+  0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U,
+  0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU,
+  0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U,
+  0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U,
+  0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U,
+  0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U,
+  0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U,
+  0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U,
+  0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU,
+  0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU,
+  0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU,
+  0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU,
+  0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U,
+  0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U,
+  0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U,
+  0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU,
+  0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U,
+  0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U,
+};
+
+static const Word32 Td4[256] =
+{
+  0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U,
+  0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U,
+  0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU,
+  0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU,
+  0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U,
+  0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U,
+  0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U,
+  0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU,
+  0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U,
+  0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU,
+  0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU,
+  0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU,
+  0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U,
+  0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U,
+  0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U,
+  0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U,
+  0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U,
+  0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U,
+  0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU,
+  0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U,
+  0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U,
+  0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU,
+  0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U,
+  0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U,
+  0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U,
+  0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU,
+  0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U,
+  0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U,
+  0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU,
+  0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U,
+  0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U,
+  0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU,
+  0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U,
+  0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU,
+  0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU,
+  0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U,
+  0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U,
+  0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U,
+  0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U,
+  0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU,
+  0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U,
+  0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U,
+  0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU,
+  0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU,
+  0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU,
+  0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U,
+  0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU,
+  0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U,
+  0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U,
+  0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U,
+  0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U,
+  0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU,
+  0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U,
+  0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU,
+  0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU,
+  0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU,
+  0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU,
+  0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U,
+  0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU,
+  0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U,
+  0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU,
+  0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U,
+  0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U,
+  0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU,
+};
+
+static const Word32 rcon[] =
+{
+  0x01000000, 0x02000000, 0x04000000, 0x08000000,
+  0x10000000, 0x20000000, 0x40000000, 0x80000000,
+  0x1B000000, 0x36000000,
+  /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */
+};
+
+#define GETU32(plaintext) (((Word32)(plaintext)[0] << 24) ^ \
+                    ((Word32)(plaintext)[1] << 16) ^ \
+                    ((Word32)(plaintext)[2] <<  8) ^ \
+                    ((Word32)(plaintext)[3]))
+
+#define PUTU32(ciphertext, st) { (ciphertext)[0] = (Byte)((st) >> 24); \
+                         (ciphertext)[1] = (Byte)((st) >> 16); \
+                         (ciphertext)[2] = (Byte)((st) >>  8); \
+                         (ciphertext)[3] = (Byte)(st); }
+
+/**
+ * Expand the cipher key into the encryption key schedule.
+ *
+ */
+void AES::setupEncrypt(Word32 *rk, const Byte* key, int keybits)
+{
+  rk[0] = GETU32(key     );
+  rk[1] = GETU32(key +  4);
+  rk[2] = GETU32(key +  8);
+  rk[3] = GETU32(key + 12);
+  if (keybits == 128)
+  {
+    for (int i=0;;)
+    {
+      Word32 temp = rk[3];
+      rk[4] = rk[0] ^
+        (Te4[(temp >> 16) & 0xff] & 0xff000000) ^
+        (Te4[(temp >>  8) & 0xff] & 0x00ff0000) ^
+        (Te4[(temp      ) & 0xff] & 0x0000ff00) ^
+        (Te4[(temp >> 24)       ] & 0x000000ff) ^
+        rcon[i];
+      rk[5] = rk[1] ^ rk[4];
+      rk[6] = rk[2] ^ rk[5];
+      rk[7] = rk[3] ^ rk[6];
+      if (++i == 10)
+        return;
+      rk += 4;
+    }
+  }
+  rk[4] = GETU32(key + 16);
+  rk[5] = GETU32(key + 20);
+  if (keybits == 192)
+  {
+    for (int i=0;;)
+    {
+      Word32 temp = rk[ 5];
+      rk[ 6] = rk[ 0] ^
+        (Te4[(temp >> 16) & 0xff] & 0xff000000) ^
+        (Te4[(temp >>  8) & 0xff] & 0x00ff0000) ^
+        (Te4[(temp      ) & 0xff] & 0x0000ff00) ^
+        (Te4[(temp >> 24)       ] & 0x000000ff) ^
+        rcon[i];
+      rk[ 7] = rk[ 1] ^ rk[ 6];
+      rk[ 8] = rk[ 2] ^ rk[ 7];
+      rk[ 9] = rk[ 3] ^ rk[ 8];
+      if (++i == 8)
+        return;
+      rk[10] = rk[ 4] ^ rk[ 9];
+      rk[11] = rk[ 5] ^ rk[10];
+      rk += 6;
+    }
+  }
+  rk[6] = GETU32(key + 24);
+  rk[7] = GETU32(key + 28);
+  if (keybits == 256)
+  {
+    for (int i=0;;)
+    {
+      Word32 temp = rk[ 7];
+      rk[ 8] = rk[ 0] ^
+        (Te4[(temp >> 16) & 0xff] & 0xff000000) ^
+        (Te4[(temp >>  8) & 0xff] & 0x00ff0000) ^
+        (Te4[(temp      ) & 0xff] & 0x0000ff00) ^
+        (Te4[(temp >> 24)       ] & 0x000000ff) ^
+        rcon[i];
+      rk[ 9] = rk[ 1] ^ rk[ 8];
+      rk[10] = rk[ 2] ^ rk[ 9];
+      rk[11] = rk[ 3] ^ rk[10];
+      if (++i == 7)
+        return;
+      temp = rk[11];
+      rk[12] = rk[ 4] ^
+        (Te4[(temp >> 24)       ] & 0xff000000) ^
+        (Te4[(temp >> 16) & 0xff] & 0x00ff0000) ^
+        (Te4[(temp >>  8) & 0xff] & 0x0000ff00) ^
+        (Te4[(temp      ) & 0xff] & 0x000000ff);
+      rk[13] = rk[ 5] ^ rk[12];
+      rk[14] = rk[ 6] ^ rk[13];
+      rk[15] = rk[ 7] ^ rk[14];
+      rk += 8;
+    }
+  }
+}
+
+/**
+ * Expand the cipher key into the decryption key schedule.
+ *
+ * @return the number of rounds for the given cipher key size.
+ */
+void AES::setupDecrypt(Word32* rk, const Byte *key, int keybits)
+{
+  int i, j;
+
+  /* expand the cipher key: */
+  setupEncrypt(rk, key, keybits);
+  int nrounds = NROUNDS(keybits);
+
+  /* invert the order of the round keys: */
+  for (i = 0, j = 4*nrounds; i < j; i += 4, j -= 4)
+  {
+    Word32 temp;
+    temp = rk[i    ]; rk[i    ] = rk[j    ]; rk[j    ] = temp;
+    temp = rk[i + 1]; rk[i + 1] = rk[j + 1]; rk[j + 1] = temp;
+    temp = rk[i + 2]; rk[i + 2] = rk[j + 2]; rk[j + 2] = temp;
+    temp = rk[i + 3]; rk[i + 3] = rk[j + 3]; rk[j + 3] = temp;
+  }
+  /* apply the inverse MixColumn transform to all round keys but the first and the last: */
+  for (i = 1; i < nrounds; i++)
+  {
+    rk += 4;
+    rk[0] =
+      Td0[Te4[(rk[0] >> 24)       ] & 0xff] ^
+      Td1[Te4[(rk[0] >> 16) & 0xff] & 0xff] ^
+      Td2[Te4[(rk[0] >>  8) & 0xff] & 0xff] ^
+      Td3[Te4[(rk[0]      ) & 0xff] & 0xff];
+    rk[1] =
+      Td0[Te4[(rk[1] >> 24)       ] & 0xff] ^
+      Td1[Te4[(rk[1] >> 16) & 0xff] & 0xff] ^
+      Td2[Te4[(rk[1] >>  8) & 0xff] & 0xff] ^
+      Td3[Te4[(rk[1]      ) & 0xff] & 0xff];
+    rk[2] =
+      Td0[Te4[(rk[2] >> 24)       ] & 0xff] ^
+      Td1[Te4[(rk[2] >> 16) & 0xff] & 0xff] ^
+      Td2[Te4[(rk[2] >>  8) & 0xff] & 0xff] ^
+      Td3[Te4[(rk[2]      ) & 0xff] & 0xff];
+    rk[3] =
+      Td0[Te4[(rk[3] >> 24)       ] & 0xff] ^
+      Td1[Te4[(rk[3] >> 16) & 0xff] & 0xff] ^
+      Td2[Te4[(rk[3] >>  8) & 0xff] & 0xff] ^
+      Td3[Te4[(rk[3]      ) & 0xff] & 0xff];
+  }
+}
+
+void AES::encrypt(Word32* rk, int nrounds,
+                  const Block plaintext, Block ciphertext)
+{
+  Word32 s0, s1, s2, s3, t0, t1, t2, t3;
+  #ifndef FULL_UNROLL
+    int r;
+  #endif /* ?FULL_UNROLL */
+  /*
+   * map byte array block to cipher state
+   * and add initial round key:
+  */
+  s0 = GETU32(plaintext     ) ^ rk[0];
+  s1 = GETU32(plaintext +  4) ^ rk[1];
+  s2 = GETU32(plaintext +  8) ^ rk[2];
+  s3 = GETU32(plaintext + 12) ^ rk[3];
+  #ifdef FULL_UNROLL
+    /* round 1: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7];
+    /* round 2: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11];
+    /* round 3: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15];
+    /* round 4: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19];
+    /* round 5: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23];
+    /* round 6: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27];
+    /* round 7: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31];
+    /* round 8: */
+    s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32];
+    s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33];
+    s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34];
+    s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35];
+    /* round 9: */
+    t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36];
+    t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37];
+    t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38];
+    t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39];
+    if (nrounds > 10)
+    {
+      /* round 10: */
+      s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[40];
+      s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[41];
+      s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[42];
+      s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[43];
+      /* round 11: */
+      t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[44];
+      t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[45];
+      t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[46];
+      t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[47];
+      if (nrounds > 12)
+      {
+        /* round 12: */
+        s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >>  8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[48];
+        s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >>  8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[49];
+        s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >>  8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[50];
+        s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >>  8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[51];
+        /* round 13: */
+        t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >>  8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[52];
+        t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >>  8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[53];
+        t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >>  8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[54];
+        t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >>  8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[55];
+      }
+    }
+    rk += nrounds << 2;
+  #else  /* !FULL_UNROLL */
+    /*
+    * nrounds - 1 full rounds:
+    */
+    r = nrounds >> 1;
+    for (;;)
+    {
+      t0 =
+        Te0[(s0 >> 24)       ] ^
+        Te1[(s1 >> 16) & 0xff] ^
+        Te2[(s2 >>  8) & 0xff] ^
+        Te3[(s3      ) & 0xff] ^
+        rk[4];
+      t1 =
+        Te0[(s1 >> 24)       ] ^
+        Te1[(s2 >> 16) & 0xff] ^
+        Te2[(s3 >>  8) & 0xff] ^
+        Te3[(s0      ) & 0xff] ^
+        rk[5];
+      t2 =
+        Te0[(s2 >> 24)       ] ^
+        Te1[(s3 >> 16) & 0xff] ^
+        Te2[(s0 >>  8) & 0xff] ^
+        Te3[(s1      ) & 0xff] ^
+        rk[6];
+      t3 =
+        Te0[(s3 >> 24)       ] ^
+        Te1[(s0 >> 16) & 0xff] ^
+        Te2[(s1 >>  8) & 0xff] ^
+        Te3[(s2      ) & 0xff] ^
+        rk[7];
+        rk += 8;
+        if (--r == 0)
+            break;
+      s0 =
+        Te0[(t0 >> 24)       ] ^
+        Te1[(t1 >> 16) & 0xff] ^
+        Te2[(t2 >>  8) & 0xff] ^
+        Te3[(t3      ) & 0xff] ^
+        rk[0];
+      s1 =
+        Te0[(t1 >> 24)       ] ^
+        Te1[(t2 >> 16) & 0xff] ^
+        Te2[(t3 >>  8) & 0xff] ^
+        Te3[(t0      ) & 0xff] ^
+        rk[1];
+      s2 =
+        Te0[(t2 >> 24)       ] ^
+        Te1[(t3 >> 16) & 0xff] ^
+        Te2[(t0 >>  8) & 0xff] ^
+        Te3[(t1      ) & 0xff] ^
+        rk[2];
+      s3 =
+        Te0[(t3 >> 24)       ] ^
+        Te1[(t0 >> 16) & 0xff] ^
+        Te2[(t1 >>  8) & 0xff] ^
+        Te3[(t2      ) & 0xff] ^
+        rk[3];
+     }
+ #endif /* ?FULL_UNROLL */
+  /*
+  * apply last round and
+  * map cipher state to byte array block:
+  */
+  s0 =
+    (Te4[(t0 >> 24)       ] & 0xff000000) ^
+    (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) ^
+    (Te4[(t2 >>  8) & 0xff] & 0x0000ff00) ^
+    (Te4[(t3      ) & 0xff] & 0x000000ff) ^
+    rk[0];
+  PUTU32(ciphertext     , s0);
+  s1 =
+    (Te4[(t1 >> 24)       ] & 0xff000000) ^
+    (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) ^
+    (Te4[(t3 >>  8) & 0xff] & 0x0000ff00) ^
+    (Te4[(t0      ) & 0xff] & 0x000000ff) ^
+    rk[1];
+  PUTU32(ciphertext +  4, s1);
+  s2 =
+    (Te4[(t2 >> 24)       ] & 0xff000000) ^
+    (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) ^
+    (Te4[(t0 >>  8) & 0xff] & 0x0000ff00) ^
+    (Te4[(t1      ) & 0xff] & 0x000000ff) ^
+    rk[2];
+  PUTU32(ciphertext +  8, s2);
+  s3 =
+    (Te4[(t3 >> 24)       ] & 0xff000000) ^
+    (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) ^
+    (Te4[(t1 >>  8) & 0xff] & 0x0000ff00) ^
+    (Te4[(t2      ) & 0xff] & 0x000000ff) ^
+    rk[3];
+  PUTU32(ciphertext + 12, s3);
+}
+
+void AES::decrypt(Word32* rk, int nrounds,
+                  const Block ciphertext, Block plaintext)
+{
+  Word32 s0, s1, s2, s3, t0, t1, t2, t3;
+  #ifndef FULL_UNROLL
+    int r;
+  #endif /* ?FULL_UNROLL */
+
+  /*
+  * map byte array block to cipher state
+  * and add initial round key:
+  */
+    s0 = GETU32(ciphertext     ) ^ rk[0];
+    s1 = GETU32(ciphertext +  4) ^ rk[1];
+    s2 = GETU32(ciphertext +  8) ^ rk[2];
+    s3 = GETU32(ciphertext + 12) ^ rk[3];
+  #ifdef FULL_UNROLL
+    /* round 1: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[ 4];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[ 5];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[ 6];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[ 7];
+    /* round 2: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[ 8];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[ 9];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[10];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[11];
+    /* round 3: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[12];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[13];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[14];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[15];
+    /* round 4: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[16];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[17];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[18];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[19];
+    /* round 5: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[20];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[21];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[22];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[23];
+    /* round 6: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[24];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[25];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[26];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[27];
+    /* round 7: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[28];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[29];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[30];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[31];
+    /* round 8: */
+    s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[32];
+    s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[33];
+    s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[34];
+    s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[35];
+    /* round 9: */
+    t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[36];
+    t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[37];
+    t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[38];
+    t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[39];
+    if (nrounds > 10)
+    {
+      /* round 10: */
+      s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[40];
+      s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[41];
+      s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[42];
+      s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[43];
+      /* round 11: */
+      t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[44];
+      t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[45];
+      t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[46];
+      t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[47];
+      if (nrounds > 12)
+      {
+        /* round 12: */
+        s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >>  8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[48];
+        s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >>  8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[49];
+        s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >>  8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[50];
+        s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >>  8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[51];
+        /* round 13: */
+        t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >>  8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[52];
+        t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >>  8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[53];
+        t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >>  8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[54];
+        t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >>  8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[55];
+      }
+    }
+    rk += nrounds << 2;
+  #else  /* !FULL_UNROLL */
+    /*
+    * nrounds - 1 full rounds:
+    */
+    r = nrounds >> 1;
+    for (;;)
+    {
+      t0 =
+        Td0[(s0 >> 24)       ] ^
+        Td1[(s3 >> 16) & 0xff] ^
+        Td2[(s2 >>  8) & 0xff] ^
+        Td3[(s1      ) & 0xff] ^
+        rk[4];
+      t1 =
+        Td0[(s1 >> 24)       ] ^
+        Td1[(s0 >> 16) & 0xff] ^
+        Td2[(s3 >>  8) & 0xff] ^
+        Td3[(s2      ) & 0xff] ^
+        rk[5];
+      t2 =
+        Td0[(s2 >> 24)       ] ^
+        Td1[(s1 >> 16) & 0xff] ^
+        Td2[(s0 >>  8) & 0xff] ^
+        Td3[(s3      ) & 0xff] ^
+        rk[6];
+      t3 =
+        Td0[(s3 >> 24)       ] ^
+        Td1[(s2 >> 16) & 0xff] ^
+        Td2[(s1 >>  8) & 0xff] ^
+        Td3[(s0      ) & 0xff] ^
+        rk[7];
+      rk += 8;
+      if (--r == 0)
+          break;
+      s0 =
+        Td0[(t0 >> 24)       ] ^
+        Td1[(t3 >> 16) & 0xff] ^
+        Td2[(t2 >>  8) & 0xff] ^
+        Td3[(t1      ) & 0xff] ^
+        rk[0];
+      s1 =
+        Td0[(t1 >> 24)       ] ^
+        Td1[(t0 >> 16) & 0xff] ^
+        Td2[(t3 >>  8) & 0xff] ^
+        Td3[(t2      ) & 0xff] ^
+        rk[1];
+      s2 =
+        Td0[(t2 >> 24)       ] ^
+        Td1[(t1 >> 16) & 0xff] ^
+        Td2[(t0 >>  8) & 0xff] ^
+        Td3[(t3      ) & 0xff] ^
+        rk[2];
+      s3 =
+        Td0[(t3 >> 24)       ] ^
+        Td1[(t2 >> 16) & 0xff] ^
+        Td2[(t1 >>  8) & 0xff] ^
+        Td3[(t0      ) & 0xff] ^
+        rk[3];
+    }
+  #endif /* ?FULL_UNROLL */
+  /*
+  * apply last round and
+  * map cipher state to byte array block:
+  */
+  s0 =
+    (Td4[(t0 >> 24)       ] & 0xff000000) ^
+    (Td4[(t3 >> 16) & 0xff] & 0x00ff0000) ^
+    (Td4[(t2 >>  8) & 0xff] & 0x0000ff00) ^
+    (Td4[(t1      ) & 0xff] & 0x000000ff) ^
+    rk[0];
+  PUTU32(plaintext     , s0);
+  s1 =
+    (Td4[(t1 >> 24)       ] & 0xff000000) ^
+    (Td4[(t0 >> 16) & 0xff] & 0x00ff0000) ^
+    (Td4[(t3 >>  8) & 0xff] & 0x0000ff00) ^
+    (Td4[(t2      ) & 0xff] & 0x000000ff) ^
+    rk[1];
+  PUTU32(plaintext +  4, s1);
+  s2 =
+    (Td4[(t2 >> 24)       ] & 0xff000000) ^
+    (Td4[(t1 >> 16) & 0xff] & 0x00ff0000) ^
+    (Td4[(t0 >>  8) & 0xff] & 0x0000ff00) ^
+    (Td4[(t3      ) & 0xff] & 0x000000ff) ^
+    rk[2];
+  PUTU32(plaintext +  8, s2);
+  s3 =
+    (Td4[(t3 >> 24)       ] & 0xff000000) ^
+    (Td4[(t2 >> 16) & 0xff] & 0x00ff0000) ^
+    (Td4[(t1 >>  8) & 0xff] & 0x0000ff00) ^
+    (Td4[(t0      ) & 0xff] & 0x000000ff) ^
+    rk[3];
+  PUTU32(plaintext + 12, s3);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+/// Randomness idea inspired from ANSI X9.17 proposed PRNG, see wikipedia
+/// PRNG page...
+static Word32 CRYPT(Word32* rk, int keybits, Word32 x)
+{
+    // Pad the block by self repetition.
+    Block in;
+    ((Word32*)&in)[0] = x;
+    ((Word32*)&in)[1] = x *= 2147483647;
+    ((Word32*)&in)[2] = x *= 2147483647;
+    ((Word32*)&in)[3] = x *= 2147483647;
+
+    Block out;
+    AES::encrypt(rk, NROUNDS(keybits), in, out);
+    return ((Word32*)&out)[3];
+}
+
+Word32 AES::get_random(Word32* rk, int keybits)
+{
+#ifdef WIN32
+    LONGLONG counter = 0;
+    QueryPerformanceCounter( (LARGE_INTEGER*)&counter );
+    Word32 time = (Word32)(counter % 2147483647);
+#else
+    timeval ts;
+    gettimeofday(&ts,0);
+    Word32 time = (Word32) ts.tv_usec;  // microseconds.
+#endif
+
+    // X9.17 propose to use triple-DES for that... Keep it simpler with our
+    // ECB block stuff:
+    static Word32 seed = 0x61448126;    // Needs to be none zero.
+    Word32 tmp = CRYPT(rk, keybits, time);
+    Word32 x   = CRYPT(rk, keybits, tmp ^ seed);
+          seed = CRYPT(rk, keybits, tmp ^ x);
+
+    return x;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+} // namespace Rijndael
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AES/rijndael.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,296 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   rijndael.h
+/// \brief  Public Domain AES encryption/decryption
+/// \author Philip J. Erdelsky <pje@efgh.com>, JD Gascuel, and others.
+///
+/// \copyright (c) 2015 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2002-09-03 PJE: original source http://www.efgh.com/software/rijndael.htm
+//  2015-03-14 jDG: Import into OSTC_Companion, major C++ rewrites to insure
+//                  key length is not messed up. Added ECb and CFB modes.
+
+#ifndef RIJNDAEL_H
+#define RIJNDAEL_H
+
+namespace Rijndael {
+
+#define KEYLENGTH(keybits) ((keybits)/8)
+#define RKLENGTH(keybits)  ((keybits)/8+28)
+#define NROUNDS(keybits)   ((keybits)/32+6)
+
+typedef unsigned long Word32;
+typedef unsigned char Byte;
+typedef Byte Block[16];
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief Public Domain AES encryption/decryption rewritten.
+///
+/// The Rijndael encryption algorithm has been designed to replace the aging
+/// DES algorithm. Like DES, it is a block cipher. It uses 128-bit, 192-bit
+/// or 256-bit keys. This implementation encrypts 128-bit blocks.
+/// (DES used 56-bit keys and 64-bit blocks.)
+///
+/// The code in this package is a modified version of an implementation
+/// placed in the public domain by the following persons:
+/// + Vincent Rijmen vincent.rijmen@esat.kuleuven.ac.be
+/// + Antoon Bosselaers antoon.bosselaers@esat.kuleuven.ac.be
+/// + Paulo Barreto paulo.barreto@terra.com.br
+///
+/// See details in http://www.efgh.com/software/rijndael.htm
+///
+struct AES
+{
+    static void setupEncrypt(Word32* rk, const Byte *key, int keybits);
+    static void setupDecrypt(Word32* rk, const Byte *key, int keybits);
+    static void encrypt(Word32 *rk, int nrounds, const Block plaintext, Block ciphertext);
+    static void decrypt(Word32 *rk, int nrounds, const Block ciphertext, Block plaintext);
+    static Word32 get_random(Word32 *rk, int keybits);
+};
+
+//////////////////////////////////////////////////////////////////////////////
+template<int keybits>
+class ECB : private AES
+{
+    /// \brief Internal state
+    ///
+    /// storage for encryption buffer, required space:
+    /// keybits  32-bit words required
+    ///     128      44
+    ///     192      52
+    ///     256      60
+    /// \sa RKLENGTH macro.
+    Word32 rk[RKLENGTH(keybits)];
+
+public:
+    typedef Byte Key[KEYLENGTH(keybits)];
+
+    ECB();
+
+    //------------------------------------------------------------------------
+    /// \brief Initialize encryption state
+    ///
+    ///
+    /// \param[in] key: AES key, where length is:
+    ///                 keybits  number of bytes
+    ///                     128      16
+    ///                     192      24
+    ///                     256      32
+    ///                 \sa KEYLENGTH macro.
+    ///
+    /// \code
+    ///     Rijndael::ECB<128>::Key key = "my big secret";
+    ///     Rijndael::ECB<128> enc;
+    ///     enc.setupEncrypt(key);
+    ///     ...
+    ///     Rijndael::Block plain = "Hello World!";
+    ///     Rijndael::Block result;
+    ///     enc.encrypt(plain, result);
+    /// \endcode
+    void setupEncrypt(const Key key);
+
+    //------------------------------------------------------------------------
+    /// \brief Encrypt a block of 16 bytes.
+    ///
+    /// \param[in]   plaintext: The 16 bytes block to encrypt.
+    /// \param[out] ciphertext: Space to store the 16 bytes of encrypted data.
+    ///
+    void encrypt(const Block plaintext, Block ciphertext);
+
+    //------------------------------------------------------------------------
+    /// \brief Initialize decryption state.
+    ///
+    /// \param[in] key: AES key, where length is:
+    ///                 keybits  number of bytes
+    ///                     128      16
+    ///                     192      24
+    ///                     256      32
+    ///                 \sa KEYLENGTH macro.
+    ///
+    /// \code
+    ///     Rijndael::ECB<128>::Key key = "my big secret";
+    ///     Rijndael::ECB<128> dec;
+    ///     dec.setupDecrypt(key);
+    ///     ...
+    ///     Rijndael::Block cipher = ...;
+    ///     Rijndael::Block result;
+    ///     Rijndael::decrypt(cipher, result);
+    /// \endcode
+    void setupDecrypt(const Key key);
+
+    //------------------------------------------------------------------------
+    /// \brief Decrypt a block of 16 bytes.
+    ///
+    /// \param[in]  ciphertext: The 16 bytes block of data to decrypt.
+    /// \param[out]  plaintext: Space to store the 16 bytes result block.
+    ///
+    void decrypt(const Block ciphertext, Block plaintext);
+
+    //----------------------------------------------------------------------------
+    /// \brief Crypto base PRNG
+    ///
+    /// Based on wall-clock value and current key, but should be a crypto-secure
+    /// generator.
+    Word32 get_random();
+};
+
+//////////////////////////////////////////////////////////////////////////////
+template<int keybits>
+class CFB : public ECB<keybits>
+{
+    /// Initialization vector (salt), updated in CFB mode
+    /// for the next block of text.
+    Block iv;
+
+public:
+    typedef Byte Key[KEYLENGTH(keybits)];
+    typedef Block IV;
+
+    //------------------------------------------------------------------------
+    /// \brief Initialize encryption/decription state
+    ///
+    ///
+    /// \param[in] key: AES key, where length is:
+    ///                 keybits  number of bytes
+    ///                     128      16
+    ///                     192      24
+    ///                     256      32
+    ///                 \sa KEYLENGTH macro.
+    /// \param[in]  iv: initialization vector. Some randomness needed to
+    ///                 enforce the sequence is non replayable.
+    ///
+    /// \code
+    ///     Rijndael::CFB<128>::Key key = "my big secret";
+    ///     Rijndael::CFB<128> enc(key, iv);
+    ///     ...
+    ///     Rijndael::Block plain = "Hello World!";
+    ///     Rijndael::Block cipher, again;
+    ///     enc.encrypt(plain, cipher);
+    ///     enc.decrypt(cipher, again);
+    /// \endcode
+    CFB(const Key key, const IV iv);
+
+    //------------------------------------------------------------------------
+    /// \brief Encrypt a block of 16 bytes, with IV (CFB mode)
+    ///
+    /// \param[in]   plaintext: The 16 bytes block to encrypt.
+    /// \param[out] ciphertext: Space to store the 16 bytes of encrypted data.
+    ///
+    void encrypt(const Block plaintext, Block ciphertext);
+
+    //----------------------------------------------------------------------------
+    /// \brief Decrypt a block of 16 bytes, with IV (CFB mode)
+    ///
+    /// \param[in]  ciphertext: The 16 bytes block of data to decrypt.
+    /// \param[out]  plaintext: Space to store the 16 bytes result block.
+    ///
+    void decrypt(const Block ciphertext, Block plaintext);
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+template<int keybits>
+ECB<keybits>::ECB()
+{
+    for(int i=0; i<RKLENGTH(keybits); ++i)
+        rk[i] = 0;
+}
+
+template<int keybits>
+void ECB<keybits>::setupEncrypt(const Key key)
+{
+    AES::setupEncrypt(rk, key, keybits);
+}
+
+template<int keybits>
+void ECB<keybits>::encrypt(const Block plaintext, Block ciphertext)
+{
+    AES::encrypt(rk, NROUNDS(keybits), plaintext, ciphertext);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+template<int keybits>
+void ECB<keybits>::setupDecrypt(const Key key)
+{
+    AES::setupDecrypt(rk, key, keybits);
+}
+
+template<int keybits>
+void ECB<keybits>::decrypt(const Block ciphertext, Block plaintext)
+{
+    AES::decrypt(rk, NROUNDS(keybits), ciphertext, plaintext);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+template<int keybits>
+Word32 ECB<keybits>::get_random()
+{
+    return AES::get_random(rk, keybits);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+template<int keybits>
+CFB<keybits>::CFB(const Key key, const IV _iv)
+{
+    ECB<keybits>::setupEncrypt(key);
+    for(int i=0; i<16; ++i)
+        iv[i] = _iv[i];
+}
+
+template<int keybits>
+void CFB<keybits>::encrypt(const Block plaintext, Block ciphertext)
+{
+    Block tmp;
+    ECB<keybits>::encrypt(iv, tmp);
+    for(int i=0; i<16; ++i)
+    {
+        ciphertext[i] = plaintext[i] ^ tmp[i];
+        iv[i]         = ciphertext[i];
+    }
+}
+
+template<int keybits>
+void CFB<keybits>::decrypt(const Block ciphertext, Block plaintext)
+{
+    Block tmp;
+    ECB<keybits>::encrypt(iv, tmp);
+    for(int i=0; i<16; ++i)
+    {
+        plaintext[i] = ciphertext[i] ^ tmp[i];
+        iv[i]        = ciphertext[i];
+    }
+}
+
+}
+#endif // RIJNDAEL_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AES/unit_test.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,215 @@
+////////////////////////////////////////////////////////////////////////
+/// \file   AES/unit_test.cpp
+/// \brief  Test AES block mode, and CFB mode.
+/// \author (c) jD Gascuel
+///
+/// $Id$
+////////////////////////////////////////////////////////////////////////
+// File History:
+//  2015-03-14 [jDG] Creation.
+
+#include "Rijndael.h"
+#include "gtest/gtest.h"
+
+#include <set>
+
+/// Vectors from http://www.inconteam.com/software-development/41-encryption/55-aes-test-vectors#aes-ecb-128
+
+static const Rijndael::Block plain[4] = {
+    {0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a},
+    {0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51},
+    {0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef},
+    {0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10}
+};
+
+static const Rijndael::ECB<128>::Key key128 = {
+    0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
+};
+
+static const Rijndael::Block cipher128[4] = {
+    {0x3a, 0xd7, 0x7b, 0xb4, 0x0d, 0x7a, 0x36, 0x60, 0xa8, 0x9e, 0xca, 0xf3, 0x24, 0x66, 0xef, 0x97},
+    {0xf5, 0xd3, 0xd5, 0x85, 0x03, 0xb9, 0x69, 0x9d, 0xe7, 0x85, 0x89, 0x5a, 0x96, 0xfd, 0xba, 0xaf},
+    {0x43, 0xb1, 0xcd, 0x7f, 0x59, 0x8e, 0xce, 0x23, 0x88, 0x1b, 0x00, 0xe3, 0xed, 0x03, 0x06, 0x88},
+    {0x7b, 0x0c, 0x78, 0x5e, 0x27, 0xe8, 0xad, 0x3f, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5d, 0xd4}
+};
+
+static const Rijndael::ECB<256>::Key key256 = {
+    0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
+    0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4
+};
+
+static const Rijndael::Block cipher256[4] = {
+    {0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8},
+    {0x59, 0x1c, 0xcb, 0x10, 0xd4, 0x10, 0xed, 0x26, 0xdc, 0x5b, 0xa7, 0x4a, 0x31, 0x36, 0x28, 0x70},
+    {0xb6, 0xed, 0x21, 0xb9, 0x9c, 0xa6, 0xf4, 0xf9, 0xf1, 0x53, 0xe7, 0xb1, 0xbe, 0xaf, 0xed, 0x1d},
+    {0x23, 0x30, 0x4b, 0x7a, 0x39, 0xf9, 0xf3, 0xff, 0x06, 0x7d, 0x8d, 0x8f, 0x9e, 0x24, 0xec, 0xc7}
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static const Rijndael::Block IV[4] = {
+    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
+    {0x3B, 0x3F, 0xD9, 0x2E, 0xB7, 0x2D, 0xAD, 0x20, 0x33, 0x34, 0x49, 0xF8, 0xE8, 0x3C, 0xFB, 0x4A},
+    {0xC8, 0xA6, 0x45, 0x37, 0xA0, 0xB3, 0xA9, 0x3F, 0xCD, 0xE3, 0xCD, 0xAD, 0x9F, 0x1C, 0xE5, 0x8B},
+    {0x26, 0x75, 0x1F, 0x67, 0xA3, 0xCB, 0xB1, 0x40, 0xB1, 0x80, 0x8C, 0xF1, 0x87, 0xA4, 0xF4, 0xDF}
+};
+
+static const Rijndael::Block cfb_cipher128[4] = {
+    {0x3b, 0x3f, 0xd9, 0x2e, 0xb7, 0x2d, 0xad, 0x20, 0x33, 0x34, 0x49, 0xf8, 0xe8, 0x3c, 0xfb, 0x4a},
+    {0xc8, 0xa6, 0x45, 0x37, 0xa0, 0xb3, 0xa9, 0x3f, 0xcd, 0xe3, 0xcd, 0xad, 0x9f, 0x1c, 0xe5, 0x8b},
+    {0x26, 0x75, 0x1f, 0x67, 0xa3, 0xcb, 0xb1, 0x40, 0xb1, 0x80, 0x8c, 0xf1, 0x87, 0xa4, 0xf4, 0xdf},
+    {0xc0, 0x4b, 0x05, 0x35, 0x7c, 0x5d, 0x1c, 0x0e, 0xea, 0xc4, 0xc6, 0x6f, 0x9f, 0xf7, 0xf2, 0xe6}
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, ECB_PRNG) {
+    Rijndael::ECB<128> enc;
+    enc.setupEncrypt(key128);
+
+    const int samples = 100*1000;
+    std::set<Rijndael::Word32> dict;
+
+    int collisions = 0;
+    for(int i=0; i<samples; ++i) {
+        Rijndael::Word32 x = enc.get_random();
+
+        // Should not hit the zero value:
+        ASSERT_NE(0, x);
+
+        // Should scarcely produce the same value twice
+        if(dict.find(x) != dict.end())
+            ++collisions;
+        else
+            dict.insert(x);
+    }
+    // Observed: 250e-6
+    ASSERT_LE(float(collisions)/float(samples), 500e-6f);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, ECB_encrypt_128) {
+    Rijndael::ECB<128> enc;
+    enc.setupEncrypt(key128);
+
+    for(int test=0; test<4; ++test) {
+        Rijndael::Block result = {0};
+        enc.encrypt(plain[test], result);
+        for(int c=0; c<16; ++c)
+            EXPECT_EQ(cipher128[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, ECB_decrypt_128) {
+    Rijndael::ECB<128> dec;
+    dec.setupDecrypt(key128);
+
+    for(int test=0; test<4; ++test) {
+        Rijndael::Block result = {0};
+        dec.decrypt(cipher128[test], result);
+        for(int c=0; c<16; ++c)
+            EXPECT_EQ(plain[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, ECB_encrypt_256) {
+    Rijndael::ECB<256> enc;
+    enc.setupEncrypt(key256);
+
+    for(int test=0; test<4; ++test) {
+        Rijndael::Block result = {0};
+        enc.encrypt(plain[test], result);
+        for(int c=0; c<16; ++c)
+            ASSERT_EQ(cipher256[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, ECB_decrypt_256) {
+    Rijndael::ECB<256> dec;
+    dec.setupDecrypt(key256);
+
+    for(int test=0; test<4; ++test) {
+        Rijndael::Block result = {0};
+        dec.decrypt(cipher256[test], result);
+        for(int c=0; c<16; ++c)
+            ASSERT_EQ(plain[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, CFB_encrypt_128) {
+    for(int test=0; test<4; ++test) {
+        Rijndael::CFB<128> enc(key128, IV[test]);
+
+        Rijndael::Block result = {0};
+        enc.encrypt(plain[test], result);
+        for(int c=0; c<16; ++c)
+        {
+            ASSERT_EQ(cfb_cipher128[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+        }
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, CFB_decrypt_128) {
+    for(int test=0; test<4; ++test) {
+        Rijndael::CFB<128> dec(key128, IV[test]);
+
+        Rijndael::Block result = {0};
+        dec.decrypt(cfb_cipher128[test], result);
+        for(int c=0; c<16; ++c)
+        {
+            ASSERT_EQ(plain[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+        }
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, CFB_encrypt_128_chained) {
+    Rijndael::CFB<128> enc(key128, IV[0]);
+
+    for(int test=0; test<4; ++test) {
+        Rijndael::Block result = {0};
+        enc.encrypt(plain[test], result);
+        for(int c=0; c<16; ++c)
+        {
+            ASSERT_EQ(cfb_cipher128[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+        }
+    }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+TEST(Rijndael, CFB_decrypt_128_chained) {
+    Rijndael::CFB<128> dec(key128, IV[0]);
+
+    for(int test=0; test<4; ++test) {
+        Rijndael::Block result = {0};
+        dec.decrypt(cfb_cipher128[test], result);
+        for(int c=0; c<16; ++c)
+        {
+            ASSERT_EQ(plain[test][c], result[c])
+                << "Test " << test << ": byte " << c;
+        }
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AES/unit_test.pro	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,36 @@
+##############################################################################
+# \file   AES/unit_test.pro
+# \brief  Test AES block mode, and CFB mode.
+# \author JD Gascuel
+#
+# $Id$
+##############################################################################
+# HISTORY:
+# 2015/03/14 jDG : Creation.
+
+TARGET = unit_test
+CONFIG *= console
+
+##############################################################################
+# Here, we need dependencies from GoogleTest, and another project...
+GTEST=D:/Dev/Dependencies/gtest-1.6.0
+
+# Visual Studio 2012 : the number of variadic template parameters defaults to 5,
+# force to 10
+*msvc2012 : DEFINES *= _VARIADIC_MAX=10
+
+INCLUDEPATH *= $${GTEST}/include $${GTEST}/gtest-1.6.0
+HEADERS *= \
+    $${GTEST}/include/gtest/gtest.h
+SOURCES *= \
+    $${GTEST}/gtest-1.6.0/src/gtest_main.cc \
+    $${GTEST}/gtest-1.6.0/src/gtest-all.cc
+
+##############################################################################
+# Then we have our proper stuff:
+HEADERS *= \
+    rijndael.h
+
+SOURCES *= \
+    rijndael.cpp  \
+    unit_test.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CmakeLists.txt	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,234 @@
+cmake_minimum_required(VERSION 3.21)
+
+project(OSTCCompanion VERSION 1.0 LANGUAGES CXX)
+
+project(OSTCCompanion LANGUAGES CXX)
+
+# ----------------------------------------
+# C++ Standard
+# ----------------------------------------
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+# ----------------------------------------
+# Qt6 Modules
+# ----------------------------------------
+find_package(Qt6 REQUIRED COMPONENTS
+    Core
+    Gui
+    Widgets
+    Network
+    Svg
+    Xml
+    SerialPort
+    LinguistTools
+)
+
+
+# ----------------------------------------
+# Sources, Header, UI and Ressources
+# ----------------------------------------
+set(SOURCES
+    main.cpp
+    AES/rijndael.cpp
+    editlogdialog.cpp
+    Utils/Exception.cpp
+    Utils/Log.cpp
+    Utils/LogAppender.cpp
+    Utils/LogConsole.cpp
+    Utils/LogFile.cpp
+    Utils/ProgressEvent.cpp
+    MainWindow.cpp
+    Serial.cpp
+    HexFile.cpp
+    SettingsDialog.cpp
+    HardwareOperations.cpp
+    OSTCFrogOperations.cpp
+    OSTCSportOperations.cpp
+    OSTC2cOperations.cpp
+    OSTC2Operations.cpp
+    OSTC3Operations.cpp
+    OSTC3pOperations.cpp
+    OSTC4Operations.cpp
+    OSTC_CR_Operations.cpp
+)
+
+set(HEADERS
+    AES/rijndael.h
+    AES/Adler16.h
+    Utils/Exception.h
+    Utils/Log.h
+    Utils/LogAppender.h
+    Utils/LogConsole.h
+    Utils/LogFile.h
+    Utils/ProgressEvent.h
+    MainWindow.h
+    Serial.h
+    HexFile.h
+    SettingsDialog.h
+    HardwareOperations.h
+    OSTCFrogOperations.h
+    OSTCSportOperations.h
+    OSTC2cOperations.h
+    OSTC2Operations.h
+    OSTC3Operations.h
+    OSTC3pOperations.h
+    OSTC4Operations.h
+    OSTC_CR_Operations.h
+    editlogdialog.h
+    ui_LogEditor.h
+)
+
+set(UIS
+    LogEditor.ui
+    MainWindow.ui
+    Settings.ui
+)
+
+set(RESOURCES
+    Resources.qrc
+    icon.rc
+)
+
+set(TRANSLATIONS
+    Translations/companion_DE.ts
+    Translations/companion_FR.ts
+    Translations/companion_ES.ts
+    Translations/companion_IT.ts
+    Translations/companion_RU.ts
+)
+
+set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/icon.rc")
+
+# ----------------------------------------
+# Executable erstellen
+# ----------------------------------------
+qt_add_executable(${PROJECT_NAME}
+    ${SOURCES}
+    ${HEADERS}
+    ${UIS}
+    ${RESOURCES}
+    Utils/Export.h
+    ${app_icon_resource_windows}
+)
+
+target_include_directories(${PROJECT_NAME} PRIVATE
+    ${CMAKE_CURRENT_SOURCE_DIR}       # Root
+    ${CMAKE_CURRENT_SOURCE_DIR}/Utils
+    ${CMAKE_CURRENT_SOURCE_DIR}/AES
+)
+
+# ----------------------------------------
+# Qt Libraries verlinken
+# ----------------------------------------
+target_link_libraries(${PROJECT_NAME}
+    PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Svg Qt6::Xml Qt6::SerialPort
+)
+
+# ----------------------------------------
+# Plattform-spezifische Anpassungen
+# ----------------------------------------
+if(WIN32)
+    target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS=1 BUILDING_OSTC_COMPANION)
+elseif(APPLE)
+    target_compile_options(${PROJECT_NAME} PRIVATE -std=c++11 -Wno-inconsistent-missing-override)
+elseif(UNIX)
+    target_compile_options(${PROJECT_NAME} PRIVATE -std=c++11)
+    set_target_properties(${PROJECT_NAME} PROPERTIES
+        BUILD_RPATH "\$ORIGIN/lib"
+    )
+endif()
+
+# ----------------------------------------
+# Debug/Release-Builds
+# ----------------------------------------
+if(CMAKE_BUILD_TYPE MATCHES Debug)
+    target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG=1)
+endif()
+
+# ----------------------------------------
+# Version aus Datei einlesen (optional)
+# ----------------------------------------
+file(STRINGS "${CMAKE_SOURCE_DIR}/VERSION.txt" VERSION_CONTENT)
+string(REGEX MATCH "MAJOR=([0-9]+)" _ ${VERSION_CONTENT})
+set(MAJOR_VERSION ${CMAKE_MATCH_1})
+string(REGEX MATCH "MINOR=([0-9]+)" _ ${VERSION_CONTENT})
+set(MINOR_VERSION ${CMAKE_MATCH_1})
+string(REGEX MATCH "PATCH=([0-9]+)" _ ${VERSION_CONTENT})
+set(PATCH_VERSION ${CMAKE_MATCH_1})
+string(REGEX MATCH "BETA=([0-9]+)" _ ${VERSION_CONTENT})
+set(BETA_VERSION ${CMAKE_MATCH_1})
+
+
+message("MAJOR_VERSION = '${MAJOR_VERSION}'")
+
+target_compile_definitions(${PROJECT_NAME} PRIVATE
+    MAJOR_VERSION=${MAJOR_VERSION}
+    MINOR_VERSION=${MINOR_VERSION}
+    PATCH_VERSION=${PATCH_VERSION}
+    BETA_VERSION=${BETA_VERSION}
+)
+
+# Show folder in Qt Creator
+file(GLOB ICON_FILES "${CMAKE_SOURCE_DIR}/Images/*")
+add_custom_target(Resources ALL SOURCES ${ICON_FILES})
+
+
+qt6_add_translation(QM_FILES ${TRANSLATIONS})
+
+if(CMAKE_BUILD_TYPE MATCHES Release)
+    target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG=1)
+
+    message("Build release and installer")
+
+    set(CPACK_IFW_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/Images/inst_OSTC_Companion.ico")
+
+    install(FILES ${CMAKE_SOURCE_DIR}/Images/app_OSTC_Companion.ico
+            DESTINATION .          # relativ zum Package root
+            COMPONENT Core)
+
+    install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin COMPONENT Core)
+
+    qt_generate_deploy_app_script(
+        TARGET ${PROJECT_NAME}
+        OUTPUT_SCRIPT deploy_script
+        NO_UNSUPPORTED_PLATFORM_ERROR
+    )
+
+    install(SCRIPT ${deploy_script}
+        COMPONENT Core)
+
+    # CPack IFW Konfiguration
+    set(CPACK_GENERATOR "IFW")
+    set(CPACK_IFW_ROOT "D:/Programme/QT/Tools/QtInstallerFramework/4.10")
+    set(CPACK_PACKAGE_VERSION_MAJOR "3")
+    set(CPACK_PACKAGE_VERSION_MINOR "3")
+    set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
+    set(CPACK_PACKAGE_VENDOR "Heinrichs Weikamp")
+    set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "OSTC Companion Application")
+    set (CPACK_IFW_PACKAGE_NAME "${PROJECT_NAME} 3.3.1")
+    set (CPACK_IFW_PACKAGE_TITLE "${PROJECT_NAME} Installer")
+    set (CPACK_IFW_PACKAGE_PUBLISHER "Heinrichs Weikamp")
+    set (CPACK_IFW_PACKAGE_WIZARD_STYLE "Modern")
+    set (CPACK_IFW_PACKAGE_WIZARD_SHOW_PAGE_LIST OFF)
+
+    set (CPACK_IFW_PACKAGE_START_MENU_DIRECTORY "OSTC-Tools")
+
+    include(CPack)
+    include(CPackIFW)
+
+    # Hauptkomponente
+    cpack_add_component(Core
+        DISPLAY_NAME "OSTC Companion"
+        DESCRIPTION "OSTC maintenance application"
+        REQUIRED
+    )
+
+    cpack_ifw_configure_component(Core ESSENTIAL FORCED_INSTALLATION)
+endif()
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HardwareOperations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,377 @@
+/////////////////////////////////////////////////////////////////////////////
+/// \file   HardwareOperations.cpp
+/// \brief  Abstract operations for HW dive computers.
+/// \author JD Gascuel.
+/// \sa OSTC3Operations.cpp
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+///////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "HardwareOperations.h"
+#include "MainWindow.h"
+#include "Utils/Log.h"
+#include <QSerialPortInfo>
+
+#ifdef WIN32
+#   include <winreg.h>
+#endif
+
+#include <QDir>
+
+//////////////////////////////////////////////////////////////////////////////
+
+unsigned char HardwareOperations::retryCommand(Serial& serial,
+                                               unsigned char cmd,
+                                               int retries)
+{
+    for(int retry=0; retry<retries; ++retry)
+    {
+        serial.writeByte( cmd );    // Send command
+        serial.sleep(25);           // Allow 25msec lag.
+
+        try {
+            unsigned char echo = serial.readByte();
+            if( echo == cmd || echo == 'M' || echo == 'L' )
+                return echo;            // Got it, or unknown command...
+        }
+        catch(const ReadTimeout&) {
+            continue;
+        }
+
+        serial.sleep(100);         // Cleanup any pending stuff,
+        serial.purge();            // and retry...
+    }
+
+    return 0xFF;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+HardwareOperations::HardwareDescriptor
+HardwareOperations::hardwareDescriptor()
+{
+    unsigned char echo  = 0;
+    unsigned int  hardFeatures = 0xFF; // timeout response...
+    unsigned int  softFeatures = 0;
+    unsigned int  model        = 0;
+    unsigned char ok    = 0;
+
+    try {
+        //---- First: try the new extended hardware query --------------------
+        echo = retryCommand(_serial, 0x60, 1); // BACKQUOTE char
+        if( echo == 0x60 ) {
+            uchar extendedDescriptor[6];
+            _serial.readBlock(extendedDescriptor, sizeof extendedDescriptor);
+
+
+            hardFeatures = extendedDescriptor[0] * 256 + extendedDescriptor[1];
+            softFeatures = extendedDescriptor[2] * 256 + extendedDescriptor[3];
+            model        = extendedDescriptor[4];
+            ok           = extendedDescriptor[5];
+        }
+        else
+        {
+            // Did we have a timeout ?
+            // In that case, some hwOS versions fails and need a reset of
+            // the connection mode...
+            if( echo == 0xFF )
+            {
+                echo = retryCommand(_serial, 0xBB);     // Try to reconnect
+                if( echo == 0xBB )
+                    echo = _serial.readByte();          // Eat 4d prompt
+            }
+
+            // Then try the OLD hardware descriptor command...
+            echo = retryCommand(_serial, 'j');  // 0x6A
+            if( echo == 'j' ) {
+                hardFeatures = _serial.readByte();
+                ok    = _serial.readByte();
+            }
+        }
+    }
+    catch(const ReadTimeout&) {}
+
+    if( (echo != 0x60 && echo != 'j')
+     || (ok != 'M' && ok != 'L')
+    ) {
+        LOG_TRACE("Old OSTC not responding...");
+        return HW_UNKNOWN_OSTC;
+    }
+
+    switch(hardFeatures) {
+    case HW_Frog        : LOG_TRACE("Frog found"); break;
+    case HW_OSTCSport_a : LOG_TRACE("OSTC Sport found"); break;
+    case HW_OSTC2c      : LOG_TRACE("OSTC 2c found"); break;
+    case HW_OSTC2_a:
+    case HW_OSTC2_c     : LOG_TRACE("OSTC 2 found"); break;
+    case HW_OSTCcR_a:
+    case HW_OSTCcR_b    : LOG_TRACE("OSTC cR found"); break;
+    case HW_OSTC3       : LOG_TRACE("OSTC 3 found"); break;
+    case HW_OSTC3p_a    : LOG_TRACE("OSTC 3+ found"); break;
+    case HW_OSTC4       : LOG_TRACE("OSTC 4 found"); break;
+
+    case HW_OSTCSport_b : LOG_TRACE("OSTC Sport, OSTC 2 or OSTC 3 found."); break;
+
+    case 0xFF: case 0x4C: case 0x4D:
+        LOG_TRACE("old OSTC not responding...");
+        return HW_UNKNOWN_OSTC;
+
+    default:
+     //   LOG_TRACE("Unknown hardware feature =" << QString().sprintf("0x%04x", hardFeatures));
+        LOG_TRACE("Unknown hardware feature =" << QString::asprintf("0x%04x", hardFeatures));
+        break;
+    }
+
+    if( echo == 0x60 ) {
+        LOG_TRACE("    software feature = "  <<  QString::asprintf("0x%04x", softFeatures));
+        LOG_TRACE("    model = "  <<  QString::asprintf("0x%02x", model));
+    }
+
+    return (HardwareDescriptor)hardFeatures;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QStringList HardwareOperations::listBluetoothPorts() const
+{
+    assert(supported() & BLUETOOTH);
+    QStringList list;
+    QString PortDesc;
+    const auto serialPortInfos = QSerialPortInfo::availablePorts();
+
+#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
+    // TODO: Linux USB search...
+    QRegExp pTemplate = portTemplate();
+    QDir dev("/dev");
+    QStringList all = dev.entryList(QStringList() << "tty.*",
+                                    QDir::NoDotAndDotDot|QDir::System|QDir::Writable,
+                                    QDir::Name);
+
+    for(int i=0; i<(int)all.count(); ++i) {
+        if( pTemplate.indexIn(all[i]) >= 0 ) {
+            LOG_TRACE("Port " << all[i]);
+            list += all[i];
+        }
+        else
+            LOG_DEBUG("...  " << all[i]);
+    }
+#else
+    /* Check the descriptors of the available COMs for Bluetooth tag */
+    for (const QSerialPortInfo &serialPortInfo : serialPortInfos) {
+         PortDesc = serialPortInfo.description();
+         if( PortDesc.contains("Bluetooth"))
+             list += serialPortInfo.portName();
+    }
+
+    if( list.isEmpty() ) /* no port identified => fallback to old detection function */
+    {
+        for(int i=1; i<300; ++i)
+        {
+            QString port = QString("COM%1").arg(i);
+
+            // First: try to read default configuration...
+            COMMCONFIG config = {0};
+            config.dwSize   = sizeof config;
+            config.wVersion = 1;
+            DWORD len = sizeof config;
+
+            QByteArray fixed = "\\\\.\\" + port.toLocal8Bit();
+            if( GetDefaultCommConfigA(fixed.constData(), &config, &len) ) {
+                if( config.dwProviderSubType == PST_RS232 )
+                    list += port;
+            }
+        }
+
+        //---- Second chance
+        //     overide usual MS bug, by looking into the registry for more
+        //     BLUETOOTH ports...
+        {
+            HKEY key;
+            const char registryPath[] = "HARDWARE\\DEVICEMAP\\SERIALCOMM";
+            if( RegOpenKeyExA(HKEY_LOCAL_MACHINE,           // PWD
+                             registryPath,                  // "SOFTWARE\Intel\PSIB"
+                             0,                             // Options
+                             KEY_READ,                      // Desired SAM: See 32bits view.
+                             &key) == ERROR_SUCCESS
+            ) {
+                for(DWORD i = 0; ++i;) {
+                    char nameBuffer[128] = {0};
+                    DWORD nameLen = sizeof nameBuffer;
+                    unsigned char dataBuffer[128] = {0};
+                    DWORD dataLen = sizeof dataBuffer;
+                    long rc = RegEnumValueA(key, i,
+                                  nameBuffer, &nameLen,
+                                  nullptr,
+                                  nullptr,
+                                  dataBuffer, &dataLen);
+                    if( rc != ERROR_SUCCESS )
+                        break;
+
+                    QString name = QString(nameBuffer);
+                    QString port = QString((char*)dataBuffer);
+                    LOG_TRACE("Resource " << i << ": " << name << ", " << port);
+                    if( name.contains("\\BtModem") || name.contains("\\BthModem") || name.contains("\\BtPort") ) {
+                        list += port + " (Bluetooth)";
+                        LOG_TRACE("Port " << name);
+                    }
+                    else
+                        LOG_DEBUG("...  " << name);
+                }
+                RegCloseKey(key);
+            }
+        }
+    }
+#endif
+
+    return list;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QStringList HardwareOperations::listUSBPorts() const
+{
+    assert( !(supported() & BLUETOOTH) );
+    QStringList list;
+
+#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
+    // TODO: Linux USB search...
+    QDir dev("/dev");
+    QRegExp pTemplate = portTemplate();
+    QStringList all = dev.entryList(QStringList() << "tty.*",
+                                    QDir::System|QDir::Writable|QDir::NoDotAndDotDot,
+                                    QDir::Name);
+    for(int i=0; i<(int)all.count(); ++i) {
+        if( pTemplate.indexIn(all[i]) >= 0 ) {
+            LOG_TRACE("Port " << all[i]);
+            list += all[i];
+        }
+        else
+            LOG_TRACE("... " << all[i]);
+    }
+#else
+    //---- First chance: Try the normal port list:
+    for(int i=1; i<300; ++i)
+    {
+        QString port = QString("COM%1").arg(i);
+
+        // First: try to read default configuration...
+        COMMCONFIG config;
+        memset(&config, 0, sizeof config);
+        config.dwSize   = sizeof config;
+        config.wVersion = 1;
+        DWORD len = sizeof config;
+
+        QByteArray fixed = "\\\\.\\" + port.toLocal8Bit();
+        if( GetDefaultCommConfigA(fixed.constData(), &config, &len) ) {
+            LOG_TRACE("Port " << port << " subtype=" << int(config.dwProviderSubType) );
+            if( config.dwProviderSubType == PST_RS232 )
+                list += port;
+        } else if( len != sizeof config )
+            LOG_THROW("Required " << len << " bytes.");
+        else if( HRESULT rc = GetLastError() )
+            if( rc != 87 )
+                LOG_TRACE("Port " << port << "  error=" << rc );
+
+    }
+    //---- Second chance
+    //     overide usual MS bug, by looking into the registry for more
+    //     USB serial ports...
+    {
+        HKEY key;
+        const char registryPath[] = "HARDWARE\\DEVICEMAP\\SERIALCOMM";
+        if( RegOpenKeyExA(HKEY_LOCAL_MACHINE,           // PWD
+                         registryPath,                  // path
+                         0,                             // Options
+                         KEY_READ,                      // Desired SAM: See 32bits view.
+                         &key) == ERROR_SUCCESS
+        ) {
+            for(DWORD i = 0;; ++i) {
+                char nameBuffer[128] = {0};
+                DWORD nameLen = sizeof nameBuffer;
+                unsigned char dataBuffer[128] = {0};
+                DWORD dataLen = sizeof dataBuffer;
+                long rc = RegEnumValueA(key, i,
+                              nameBuffer, &nameLen,
+                              nullptr,
+                              nullptr,
+                              dataBuffer, &dataLen);
+
+                if( rc == ERROR_NO_MORE_ITEMS )
+                    break;
+
+                if( rc != ERROR_SUCCESS )
+                    LOG_THROW( "Enumeration error" );
+
+                QString name = QString(nameBuffer);
+                QString port = QString((char*)dataBuffer);
+                LOG_TRACE("Resource " << i << ": " << name << ", " << port);
+
+                if( name.contains("\\VCP") )
+                    list += port + " (USB)";
+            }
+            RegCloseKey(key);
+        }
+    }
+#endif
+
+    return list;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+//QString HardwareOperations::scanNewPort()
+//{
+//    static QStringList oldPorts;
+
+//    //---- Get current ports, type-less (ie. strip (USB) or (Bluetooth) ------
+//    QStringList newPorts;
+//    foreach(QString port, listPorts())
+//        newPorts += port.section(" ", 0, 0);
+
+//    //---- Simplify all ports not in the list anymore -----------------------
+//    for(int p=0; p<oldPorts.count(); ++p) {
+//        QString port = oldPorts[p];
+//        if( ! newPorts.contains(port) )
+//            oldPorts.removeAll(port);
+//    }
+
+//    //---- Check for new port in the list -----------------------------------
+//    for(int p=0; p<newPorts.count(); ++p) {
+//        QString port = newPorts[p];
+//        // Did we find a new port ?
+//        if( ! oldPorts.contains(port) ) {
+//            oldPorts += port;
+//            return port;
+//        }
+//    }
+
+//    return QString::null;
+//}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HardwareOperations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,401 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   HardwareOperations.h
+/// \brief  API for HW dive computer drivers.
+/// \author JD Gascuel.
+/// \sa     OSTC3Operations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2012-09-22 : [jDG] Initial version, made for OSTC Companion.
+//  2014-07-07 : [jDG] Cleanups for Subsurface google-summer-of-code.
+//  2014-07-25 : [jDG] BSD 2-clause license.
+//  2016-07-14 : ÍjDG] Adding codes for hardware variants of OSTC2/3/4.
+
+#ifndef HARDWARE_OPERATIONS_H
+#define HARDWARE_OPERATIONS_H
+
+#include <QDateTime>
+#include <QImage>
+#include <QString>
+#include <QStringList>
+#include <QSize>
+#include <QRegularExpression>
+
+#ifdef TEST_MODE
+#   include "test/SerialTest.h"
+#else
+#   include "Serial.h"
+#endif
+
+class HexFile;
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief  API for HW dive computer drivers.
+///
+/// This class include high level commands used by the *Companion* GUI,
+/// and other generic services:
+/// - List of currently known serial ports:
+///   listBluetoothPorts() or listUSBPorts().
+///
+/// - Send a command, and wait for the proper acknowledge: retryCommand().
+///
+/// \sa OSTCFrogOperations, OSTC2cOperations, OSTC3Operations,
+/// \nosubgrouping
+class HardwareOperations
+{
+protected:
+#ifdef TEST_MODE
+    /// \brief Fake communication port used for mockup operations.
+    SerialTest  _serial;
+#else
+    /// \brief Communication port used for all I/O operations.
+    ///
+    /// Note that in *emulation mode*, aka TEST_MODE, this is replaced by
+    /// an instance of SerialTest.
+    Serial      _serial;
+#endif
+
+public:
+    //////////////////////////////////////////////////////////////////////////
+    /// \name Class management
+    /// \{
+    ///
+    /// Managing the hierarchy of driver classes used to handle the various
+    /// existing H&W devices.
+
+    /// \brief mandatory (and empty) virtual descructor in the base class.
+    virtual ~HardwareOperations() {}
+
+    //------------------------------------------------------------------------
+    /// \brief The name of the computer driver.
+    /// \returns the name of a particular driver implementation,
+    /// eg. "OSTCSport", "OSTC2c", "OSTC3", ...
+    virtual QString model() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Gives access to serial port in use.
+    /// Used for other high and low level operations, eg. in *OSTC Planner*.
+    inline Serial& serial() { return _serial; }
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+    /// \name Introspection
+    /// \{
+    ///
+    /// Methods available to ask the connected device what it does support,
+    /// and how to manage it.
+
+    //------------------------------------------------------------------------
+    /// \brief Optional features present in the dive computer hardware.
+    ///
+    /// 8 bit mask. See HardwareDescriptor for known combinations.
+    ///
+    enum HardwareOption {
+        HW_CHARGEABLE_BATTERY= 0x01,    ///< Recharge option.
+        HW_LIGHT_SENSOR      = 0x02,    ///< Detects light level, and tune screen.
+        HW_S8_COM            = 0x04,    ///< Analog connector to O2 cells.
+        HW_OPTICAL_COM       = 0x08,    ///< Digital connector to O2 cells.
+        HW_BLUETOOTH_COM     = 0x10,    ///< Bluetooth, hence no USB connection.
+        HW_DUALCORE          = 0x20,    ///< Dual core processor.
+    };
+
+    ///-----------------------------------------------------------------------
+    /// \brief Dive computer set of features.
+    ///
+    /// Set of features present on a given H&W dive computers.
+    /// \note that several versions exists of a given computer, and there
+    /// is not a uniq mapping of a given feature set to a dive computer
+    /// (eg. 0x13 is ambiguous).
+    ///
+    enum HardwareDescriptor {
+        HW_UNKNOWN_OSTC = 0,
+        HW_Frog        = HW_BLUETOOTH_COM,
+        HW_OSTCSport_a = HW_LIGHT_SENSOR | HW_BLUETOOTH_COM,
+        HW_OSTCSport_b = HW_OSTCSport_a | HW_CHARGEABLE_BATTERY,
+        HW_OSTC2c      = HW_CHARGEABLE_BATTERY,
+        HW_OSTC2_a     = HW_CHARGEABLE_BATTERY | HW_BLUETOOTH_COM,
+        HW_OSTC2_b     = HW_OSTCSport_b,
+        HW_OSTC2_c     = HW_OSTC2_b | HW_OPTICAL_COM,
+        HW_OSTC3       = HW_LIGHT_SENSOR | HW_OPTICAL_COM,
+        HW_OSTC3p_a    = HW_LIGHT_SENSOR | HW_OPTICAL_COM | HW_BLUETOOTH_COM,
+        HW_OSTC3p_b    = HW_OSTCSport_b,
+        HW_OSTCcR_a    = HW_CHARGEABLE_BATTERY | HW_S8_COM,
+        HW_OSTCcR_b    = HW_OSTCcR_a | HW_LIGHT_SENSOR,
+        HW_OSTC4       = HW_DUALCORE|HW_BLUETOOTH_COM|
+                         HW_OPTICAL_COM|HW_LIGHT_SENSOR|HW_CHARGEABLE_BATTERY
+    };
+
+    //------------------------------------------------------------------------
+    /// \brief Ask the connect device for its hardware options.
+    ///
+    /// This is used to guess the device model, even if there is no unicity.
+    HardwareDescriptor hardwareDescriptor();
+
+    //------------------------------------------------------------------------
+    /// \brief Features supported by OSTC Companion on the connected device.
+    ///
+    /// Each driver (instance of this class) is requested to tell *Companion*
+    /// what are the supported command.
+    ///
+    enum CompanionFeatures {
+        PARAMETERS   = (1<<0),  ///< Download/Edit/Upload various parameters.
+        DATE         = (1<<1),  ///< Set date & time.
+        NAME         = (1<<2),  ///< Set custom text displayed on main screen.
+        ICON         = (1<<3),  ///< Set custom image displayed on main screen.
+        DUMPSCREEN   = (1<<4),  ///< Makes copy of current screen.
+        FIRMWARE     = (1<<5),  ///< Do firmware upgrades.
+        HELIUM_DIVE  = (1<<6),  ///< Computes deco stops for trimix dives.
+        CCR_DIVE     = (1<<7),  ///< Computes deco stops for rebreather dives.
+        BLUETOOTH    = (1<<8),  ///< Use Bluetooh communication (instead of USB)
+        VPM_MODEL    = (1<<9),  ///< Also propose VPM deco stops.
+        SIGNAL_CHECK = (1<<10)  ///< Echo signal quality for bluetooth computer
+    };
+
+    /// \brief Tells what is supported for a given computer.
+    virtual CompanionFeatures supported() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Length of the custom text displayed by the device.
+    ///
+    /// \returns (w,h), where \a w is text width (in chars), and \a h is the
+    /// number of lines.
+    /// Used by *Companion* GUI to format user input.
+    virtual QSize nameSize() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief filename matching template for compatible firmware.
+    ///
+    /// Used by the *Upgrade Firmware...* command to propose only firmwares
+    /// designed for the connected device.
+    virtual QString firmwareTemplate() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Read in the specific firmware file format.
+    ///
+    /// History is a bit complex here, and the published firmware have
+    /// different file formats (due to support tool, and/or need for
+    /// encryption).
+    /// So each driver have to implement its specific loader.
+    virtual void loadFirmware(HexFile&, const QString& fileName) const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Regular expression to filter *USB* or *Bluetooth* port names.
+    ///
+    /// Used to propose only the list of ports that matches the serial ports
+    /// compatible with USB/Bluetooth emulation and the connected dive
+    /// computer.
+ //   virtual QRegExp portTemplate() const = 0;
+    virtual QRegularExpression portTemplate() const = 0;
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+    /// \name High level commands
+    /// \{
+    ///
+    /// Commands that implement the specific protocol for a device, to perform
+    /// all services needed by *OSTC Copmanion*.
+
+    //------------------------------------------------------------------------
+    /// \brief Open download mode communication to the dive computer.
+    ///
+    /// Open comm port, start download mode, check the blessed reply,
+    /// and get the computer identity (for description() ).
+    ///
+    /// \note this mode allows common commands to be processed, but not
+    ///       firmware upgrade.
+    ///
+    /// \returns TRUE is everything went well.
+    /// \sa connectServiceMode() and disconnect().
+    virtual bool connect() = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Open service mode communication to the dive computer.
+    ///
+    /// Open comm port, start service mode, check the blessed reply.
+    /// \note this mode is mandatory to allow upgradeFW().
+    ///
+    /// \returns TRUE is everything went well.
+    /// \sa connect() and disconnect().
+    virtual void connectServiceMode() = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Echo a message on the connected device screen.
+    ///
+    /// Used on most devices to display commands as they are processed,
+    /// so the user can see *OSTC Companion* is working properly, by seeing
+    /// progress on the dive computer itself.
+    virtual void writeText(const QString& msg) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Set HW dive computer date and time.
+    virtual void setDate(const QDateTime& date) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Set HW dive computer user text.
+    virtual void setName(const QString& newName) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Display signal quality at OSTC.
+    virtual void getSignal() = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Read all header from OSTC
+    virtual void getAllHeader(unsigned char* pBuffer) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Write all header from OSTC
+    virtual void writeAllHeader(unsigned char* pBuffer) = 0;
+
+
+    //------------------------------------------------------------------------
+    /// \brief Read all samples from OSTC
+    virtual void getAllSamples(unsigned char* pBuffer) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Write all samples to OSTC
+    virtual void writeAllSamples(unsigned char* pBuffer) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Set HW dive computer icon set
+    ///
+    /// *Currently only supported by Frog dive computer*.
+    virtual void setIcons(const QString& fileName) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Take a snapshot of the connected computer's screen.
+    ///
+    /// *Currently only supported by OSTC mk2/2n/2c dive computers*.
+    virtual QImage dumpScreen() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Upgrade HW dive computer firmware.
+    ///
+    /// \note needs service mode connection.
+    /// \sa connectServiceMode().
+    virtual void upgradeFW(const QString& fileName) = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Close connection.
+    ///
+    /// Exit service mode, and close everything.
+    /// \p closing should be set when ending Companion, so
+    /// an error make a won't crash if the interface is already
+    /// deleted.
+    virtual bool disconnect(bool closing = false) = 0;
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+    /// \name Low level protocol
+    /// \{
+    ///
+    /// Command and methods that have to be implemented for each device
+    /// to retrieve device descriptions.
+
+    //------------------------------------------------------------------------
+    /// \brief List all communication ports
+    ///
+    /// That are (or might be) used by HW dive computers.
+    virtual QStringList listPorts() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief Send a command, wait ack, and retry on error.
+    ///
+    /// Service common to all current H&W dive computer: send a command byte,
+    /// and wait it is dully acknowledged.
+    /// Allow up to 10x retries when the computer does not answer anything,
+    /// or reply something else.
+    ///
+    /// \param [in,out] serial: the connected port to use.
+    /// \param [in]        cmd: command byte to send.
+    /// \param [in]    retries: Optional max number of retries. Default to 10.
+    ///
+    /// \returns the ack byte (if any), or 0xFF if never received.
+    static  unsigned char retryCommand(Serial& serial,
+                                             unsigned char cmd,
+                                             int retries = 10);
+
+    //------------------------------------------------------------------------
+    /// \brief Read and check connected dive computer identity.
+    ///
+    /// Read fw's version, serial number and custom text from connected computer.
+    ///
+    /// \throws when the connected device does not matches the driver
+    /// implementation.
+    ///
+    /// \sa description(), firmware(), serialNumber() and customtext().
+    virtual void getIdentity() = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief The fw's version found during the last getIdentty().
+    /// \sa getIDentity().
+    virtual int firmware() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief The serial number found during the last getIdentty().
+    /// \sa getIDentity().
+    virtual int serialNumber() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief The user-defined string found during the last getIdentty().
+    /// \sa getIDentity().
+    virtual QString customText() const = 0;
+
+    //------------------------------------------------------------------------
+    /// \brief A human readable description of connected computer.
+    ///
+    /// Returns driver name, and identity data found during the last call to
+    /// getIdentity().
+    ///
+    /// \sa model(), getIntentity()
+    virtual QString description() = 0;
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+
+protected:
+    //------------------------------------------------------------------------
+    /// \brief List serial ports for *Bluetooth* based devices.
+    ///
+    /// Ask *OS* for the list of *Bluetooth* serial emulations whose name
+    /// matches the portTemplate() regular expression.
+    ///
+    QStringList listBluetoothPorts() const;
+
+    //------------------------------------------------------------------------
+    /// \brief List serial ports for USB based devices.
+    ///
+    /// Ask *OS* for the list of USB serial emulation currently connected whose name
+    /// matches the portTemplate() regular expression.
+    QStringList listUSBPorts() const;
+
+};
+
+#endif // COMPUTEROPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HexFile.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,358 @@
+/////////////////////////////////////////////////////////////////////////////
+/// \file   HexFile.cpp
+/// \brief  Read .hex file in "Intel HEX" format.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2012-2016, JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "HexFile.h"
+
+#include "Utils/Log.h"
+#include "Utils/ProgressEvent.h"
+
+#include <QCoreApplication>
+#include <QTextStream>
+#include <QtDebug>
+
+//////////////////////////////////////////////////////////////////////////////
+// Crypto++ objects needed:
+
+typedef unsigned char byte;
+
+#include "AES/rijndael.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+HexFile::HexFile()
+: _memSize(0),
+#ifdef FROG_MASTER
+  _baseAddress(0),
+#endif
+  _buffer(0)
+{}
+
+HexFile::~HexFile()
+{
+    delete[] _buffer; _buffer = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool HexFile::allocate(size_t memSize, unsigned char fill)
+{
+    delete[] _buffer;
+    _buffer = new unsigned char[_memSize = memSize];
+    Q_CHECK_PTR(_buffer);
+
+    memset(_buffer, fill, memSize);
+    return true;
+}
+
+bool HexFile::sqwiz(size_t newMemSize)
+{
+    if( newMemSize >= _memSize ) {
+        LOG_THROW( "Squiz failed" );
+    }
+    _memSize = newMemSize;
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static size_t bytes = 0;
+
+void HexFile::readLine()
+{
+    QByteArray line = _file.readLine();
+    if( line[0] != ':' )
+        LOG_THROW( "Bad HEX format" );
+
+    unsigned char checksum = 0;
+    bool ok = true;
+    for(int i=1; ok && i<line.length(); i+=2) {
+        if( line[i]=='\r' || line[i]=='\n' )
+            break;
+        checksum += line.mid(i,2).toInt(&ok,16);
+    }
+    if( ! ok )
+        LOG_THROW( "Bad HEX header" );
+    if( checksum != 0 )
+        LOG_THROW( "Bad HEX checksum" );
+
+    int len  = line.mid(1,2).toInt(0, 16);
+    int addr = line.mid(3,2).toInt(0, 16) << 8
+             | line.mid(5,2).toInt(0, 16);
+    int type = line.mid(7,2).toInt(0, 16);
+
+    switch( type )
+    {
+    case 00: //---- Data record ----------------------------------------------
+        if( _baseAddress == 0x300000 )  // Skip configuration bits.
+            return;
+        if( _baseAddress == 0xF00000 )  // Skip internal prom reset.
+            return;
+
+        for(int i=0; i<len; i++, ++addr)
+        {
+            size_t a = _baseAddress + addr;
+            if( a >= _memSize )
+                LOG_THROW( "BAD HEX address" );
+
+            if( _buffer[a] != 0xFF )
+                LOG_THROW( "Double write" );
+
+            _buffer[a] = line.mid(9+i*2,2).toInt(&ok,16);
+            if( !ok )
+                LOG_THROW( "Bad HEX byte" );
+            bytes++;
+        }
+        break;
+
+    case 01: //---- END OF FILE record ---------------------------------------
+        _file.seek(-1); // Force to end of file.
+        break;
+
+    case 02: //---- Segment address record -----------------------------------
+        if( len != 2 || addr != 0 )
+            LOG_THROW( "Bad HEX Segment Address record" );
+        _baseAddress = line.mid(9,4).toInt(0,16) << 4;
+        break;
+
+    case 04: //---- Extended Linear Address Record ---------------------------
+        if( len != 2 || addr != 0 )
+            LOG_THROW( "Bad HEX Extended Linear Address Record" );
+        _baseAddress = line.mid( 9,2).toInt(0,16) << 20
+                     | line.mid(11,2).toInt(0,16) << 16;
+        break;
+
+    default:
+        LOG_THROW("Bad HEX subtype");
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void HexFile::load(const QString& fileName)
+{
+    Q_ASSERT(_buffer);
+
+    _file.setFileName(fileName);
+    if( ! _file.open(QIODevice::ReadOnly) )
+        LOG_THROW("Can't open HEX file");
+
+    bytes = 0;
+    while( ! _file.atEnd() )
+    {
+        PROGRESS(_file.pos(), _file.size());
+        readLine();
+    }
+    PROGRESS_RESET();
+
+    _file.close();
+
+    LOG_TRACE( int(bytes/1024.0f) << "KB loaded ("
+            << int(bytes * 100.0f / _memSize) << "% of firmware area).");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+const unsigned char* HexFile::data() const
+{
+    Q_ASSERT(_buffer);
+    return _buffer;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned int HexFile::checksum() const
+{
+    Q_ASSERT(_buffer);
+
+    unsigned short low = 0;
+    unsigned short high = 0;
+    for(size_t i=0; i < _memSize; ++i)
+    {
+        low  += _buffer[i];
+        high += low;
+    }
+    return (((unsigned int)high) << 16) + low;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef FROG_MASTER
+void HexFile::saveEncrypted(const QString&, byte [16])
+{
+    LOG_THROW( "No encryption" );
+}
+#else
+void HexFile::saveEncrypted(const QString& fileName, byte secretKey[16])
+{
+    _file.setFileName(fileName);
+    if( ! _file.open(QIODevice::WriteOnly|QIODevice::Truncate) )
+        LOG_THROW( "Can't save to encrypted file" );
+
+    QTextStream out(&_file);
+    out.setIntegerBase(16);
+    out.setPadChar('0');
+
+    //---- Generates 128 bits of random initialization vector ----------------
+    Rijndael::CFB<128>::IV iv = {0};
+    Rijndael::ECB<128> PRNG; PRNG.setupEncrypt(secretKey);
+    for(int i=0; i<sizeof iv; ++i)
+        iv[i] = PRNG.get_random() % 256;
+
+    size_t bytes = 0;   // encrypted fake address
+    for(int i0 = 0; i0 < sizeof iv; i0+=0x10)
+    {
+        out << qSetFieldWidth(1) << ":"
+            << qSetFieldWidth(6) << bytes
+            << qSetFieldWidth(2);
+        for(int i=0; i<0x10; i++)
+            out << iv[i0+i];
+        out << qSetFieldWidth(1) << "\n";
+        bytes += 0x10;
+    }
+
+    //---- Create stream encryptor -------------------------------------------
+    Rijndael::CFB<128> enc(secretKey, iv);
+
+    //---- Process data ------------------------------------------------------
+    PROGRESS(0, _memSize);
+
+    byte encrypted[32];
+    for(size_t addr = 0; addr < _memSize; addr += 0x10)
+    {
+        PROGRESS(addr, _memSize);
+
+        enc.encrypt(_buffer + addr, encrypted);
+        out << qSetFieldWidth(1) << ":"
+            << qSetFieldWidth(6) << bytes
+            << qSetFieldWidth(2);
+        for(int i=0; i<16; i++)
+            out << encrypted[i];
+        out << qSetFieldWidth(1) << "\n";
+        bytes += 16;
+    }
+
+    //---- Process data ------------------------------------------------------
+    unsigned int sum = checksum(); // 33.29.BD.1D
+    out << qSetFieldWidth(1) << ":"
+        << qSetFieldWidth(6) << bytes
+        << qSetFieldWidth(2)
+        << ((sum    ) & 0xff)
+        << ((sum>> 8) & 0xff)
+        << ((sum>>16) & 0xff)
+        << ((sum>>24) & 0xff)
+        << qSetFieldWidth(1) << "\n"
+        ;
+
+    qDebug().nospace() << int( bytes/1024.0f) << "KB saved into "
+                       << fileName.section('/', -1);
+
+    PROGRESS_RESET();
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+void HexFile::loadEncrypted(const QString& fileName, unsigned char secretKey[16])
+{
+    Q_ASSERT(_buffer);
+
+    _file.setFileName(fileName);
+    if( ! _file.open(QIODevice::ReadOnly) )
+        LOG_THROW( "Cannot open HEX file "  << fileName );
+
+    //---- Read 128 bits of initialization vector ----------------------------
+    Rijndael::CFB<128>::IV iv = {0};
+
+    unsigned int bytes = 0;
+    bool ok = true;
+    for(size_t i0 = 0; i0 < sizeof iv; i0+=0x10)
+    {
+        QByteArray line = _file.readLine();
+        if( line[0] != ':' )
+            LOG_THROW( "Bad HEX line" );
+
+        unsigned int readAddr = line.mid(1,6).toInt(&ok, 16);
+        if( !ok || readAddr != bytes )
+            LOG_THROW( "Bad HEX address" );
+
+        for(int i=0; i<0x10; i++)
+            iv[i0+i] = line.mid(2*i+7,2).toInt(&ok, 16);
+        if( !ok )
+            LOG_THROW( "Bad HEX file format" );
+
+        bytes += 0x10;
+    }
+
+    //---- Create stream decryptor -------------------------------------------
+    Rijndael::CFB<128> dec(secretKey, iv);
+
+    //---- Process data ------------------------------------------------------
+    PROGRESS(0, (int)_memSize);
+
+    Rijndael::Block encrypted;
+    for(size_t addr = 0; addr < _memSize; addr += 0x10)
+    {
+        PROGRESS((int)addr, (int)_memSize);
+
+        QByteArray line = _file.readLine();
+        if( line[0] != ':' )
+            LOG_THROW( "Bad HEX line" );
+
+        unsigned int readAddr = line.mid(1,6).toInt(&ok, 16);
+        if( !ok || readAddr != bytes )
+            LOG_THROW( "Bad HEX address" );
+
+        for(int i=0; i<0x10; i++)
+            encrypted[i] = line.mid(2*i+7,2).toInt(&ok, 16);
+        if( !ok )
+            LOG_THROW( "Bad HEX file format" );
+        bytes += 0x10;
+
+        dec.decrypt(encrypted, *(Rijndael::Block*)(_buffer+addr));
+    }
+    QByteArray line = _file.readLine();
+    if( bytes != (unsigned int)line.mid(1,6).toInt(&ok, 16) || !ok )
+        LOG_THROW( "Bad HEX address" );
+
+    unsigned int sum =  line.mid( 7,2).toInt(&ok, 16)
+                     + (line.mid( 9,2).toInt(&ok, 16) << 8 )
+                     + (line.mid(11,2).toInt(&ok, 16) << 16)
+                     + (line.mid(13,2).toInt(&ok, 16) << 24);
+    if( sum != checksum() )
+        LOG_THROW( "Bad HEX checksum" );
+
+    PROGRESS_RESET();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HexFile.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,92 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   HexFile.h
+/// \brief  Read .hex file in "Intel HEX" format.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2012-2016, JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2012-02-12 : [jDG] Creation for Frog Companion firmware upload.
+//  2012-09-22 : [jDG] Updated for OSTC Companion (OSTC3).
+//  2014-07-07 : [jDG] Cleanups for Subsurface google-summer-of-code.
+//  2014-07-25 : [jDG] BSD 2-clause license.
+
+#include <QtGlobal>
+#include <QFile>
+
+class QProgressBar;
+
+/////////////////////////////////////////////////////////////////////////////
+/// \brief  Read .hex file in "Intel HEX" format.
+///
+/// \note that OSTC3 firmware is NOT distributed in Intel HEX format,
+///       but is first flatened, then encrypted, then written back
+///       in a similar looking format.
+class  HexFile
+{
+    QFile   _file;
+    size_t  _memSize;
+    size_t  _baseAddress;   ///< Base for extended address modes.
+
+    unsigned char* _buffer;
+
+    /// \brief Read and interpret on line of the HEX file.
+    void readLine();
+
+public:
+    /// \brief Create an empty object.
+    HexFile();
+    ~HexFile();
+
+    /// \brief Allocate initialized space.
+    bool allocate(size_t memSize, unsigned char fill = 0xFF);
+    bool sqwiz(size_t newMemSize);
+
+    /// \brief Load the .hex file into the internal buffer.
+    /// \return TRUE is ok, FAlSE if some error occurs.
+    void load(const QString& fileName);
+
+    /// \brief Return a pointer to the loaded data.
+    const unsigned char* data() const;
+
+    /// \brief Returns the 4 bytes checksum of the loaded data.
+    ///
+    /// This is a simple Adler32 checksum: The lower 16bits are the sum of
+    /// all the bytes (modulo 65536). And the second 16bits are the sum of
+    /// the running sum itself (also modulo 65536).
+    unsigned int checksum() const;
+
+    /// \brief Save to encrypted HEX file.
+    void saveEncrypted(const QString& fileName, unsigned char secretKey[16]);
+
+    /// \brief Reload encrypted HEX file.
+    void loadEncrypted(const QString& fileName, unsigned char secretKey[16]);
+};
Binary file Images/Settings.ai has changed
Binary file Images/Settings.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/Settings.svg	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="170.079px" height="170.078px" viewBox="0 0 170.079 170.078" style="enable-background:new 0 0 170.079 170.078;"
+	 xml:space="preserve">
+<path style="fill:#6D6E71;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;" d="M128.24,75.113l25.45-16.467
+	c-0.388-0.995-0.794-1.981-1.223-2.956l-29.639,6.353c-0.559-0.901-1.151-1.779-1.771-2.637l17.203-24.94
+	c-0.739-0.772-1.495-1.527-2.268-2.267l-24.94,17.203c-0.857-0.62-1.735-1.213-2.637-1.771l6.352-29.639
+	c-0.975-0.429-1.96-0.835-2.956-1.223L95.346,42.22c-1.026-0.242-2.064-0.454-3.117-0.623L86.76,11.808
+	c-0.536-0.012-1.067-0.041-1.605-0.041c-0.538,0-1.067,0.029-1.602,0.041l-5.469,29.789c-1.053,0.169-2.091,0.381-3.118,0.623
+	L58.5,16.77c-0.996,0.388-1.981,0.794-2.956,1.223l6.353,29.639c-0.902,0.558-1.78,1.151-2.638,1.771l-24.94-17.203
+	c-0.772,0.739-1.528,1.495-2.267,2.268l17.203,24.939c-0.62,0.858-1.213,1.736-1.771,2.639L17.846,55.69
+	c-0.429,0.975-0.835,1.961-1.223,2.957l25.45,16.466c-0.242,1.027-0.454,2.064-0.623,3.117l-29.79,5.469
+	c-0.011,0.535-0.041,1.065-0.041,1.604s0.029,1.068,0.041,1.604l29.791,5.469c0.169,1.054,0.381,2.092,0.623,3.119l-25.451,16.465
+	c0.388,0.996,0.794,1.982,1.223,2.957l29.639-6.354c0.558,0.902,1.151,1.78,1.771,2.638l-17.205,24.94
+	c0.74,0.772,1.495,1.528,2.267,2.268l24.941-17.204c0.857,0.619,1.735,1.212,2.638,1.771l-6.353,29.64
+	c0.974,0.43,1.96,0.835,2.956,1.223l16.467-25.451c1.026,0.241,2.064,0.453,3.117,0.622l5.469,29.791
+	c0.534,0.012,1.064,0.041,1.602,0.041c0.538,0,1.069-0.029,1.605-0.041l5.469-29.791c1.053-0.169,2.091-0.381,3.117-0.623
+	l16.467,25.452c0.996-0.388,1.981-0.794,2.956-1.223l-6.353-29.641c0.901-0.558,1.779-1.15,2.637-1.771l24.941,17.205
+	c0.771-0.74,1.527-1.495,2.267-2.268l-17.204-24.941c0.62-0.857,1.213-1.735,1.771-2.637l29.64,6.353
+	c0.43-0.975,0.835-1.961,1.223-2.957l-25.451-16.465c0.242-1.027,0.454-2.065,0.623-3.118l29.79-5.468
+	c0.012-0.536,0.041-1.066,0.041-1.604s-0.029-1.068-0.04-1.604l-29.791-5.469C128.693,77.178,128.481,76.14,128.24,75.113z"/>
+<circle style="fill:#A7A9AC;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;" cx="85.156" cy="85.304" r="31.307"/>
+<circle style="stroke:#000000;stroke-linecap:round;stroke-linejoin:round;" cx="85.156" cy="85.304" r="17.95"/>
+</svg>
Binary file Images/app_OSTC_Companion.icns has changed
Binary file Images/app_OSTC_Companion.ico has changed
Binary file Images/app_OSTC_Companion.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/app_OSTC_Companion.svg	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="636.965px" height="500.445px" viewBox="0 0 636.965 500.445" enable-background="new 0 0 636.965 500.445"
+	 xml:space="preserve">
+<g id="Layer_3">
+	<path fill="#EF3B39" d="M142.953,210.713c-20.592-26.411-29.991-70.277-104.71-96.446c-21.283-6-8-13.333,4-8.875
+		c70.11,32.084,114.913,22.379,66-34.792c-6.853-8.011,5.662-13.975,9.333-8c43.818,71.302,94.428,59.961,75.249-19.667
+		c-2.391-9.922,12.209-10.83,12.479-2.333c2.438,76.919,52.221,65.316,69.272,6.632c2.909-7.196,20.862-16.575,11.356,3.901
+		c-27.426,53.644-5.663,100.083-7.082,114.013L142.953,210.713z"/>
+	<circle fill="#EF3B39" cx="16.96" cy="98.458" r="5.266"/>
+	<circle fill="#EF3B39" cx="104.8" cy="52.497" r="5.265"/>
+	<circle fill="#EF3B39" cx="197.174" cy="25.392" r="5.265"/>
+	<circle fill="#EF3B39" cx="290.173" cy="26.907" r="5.265"/>
+</g>
+<g id="Calque_1">
+	<g>
+		<path d="M547.047,303.156c25.313,0,66.168,74.165,76.834,69.829c4.842-1.966-2.816-25.503-25.166-103.164
+			c-8.227-28.581-4.236-79.333-6.668-100.667c-2.953-25.932-12.607-4.102-13.33,25.5c-0.834,34.167-9.322,71.628-22.666,68.335
+			c-26.119-6.443-92.004-97.335-255.004-106.002c-171.436-9.114-200.432,103.387-183.963,144.669
+			c12.964,32.5,22.964,42.329,43.631,57c-1.5-23.835,1.333-20.671,26.333-3c2.833-16.835-22.333-37.171,21-5.838
+			c1.375,1.088,1.247-2.93,1.25-4.162c0.049-20.517-3.969-21.938,11.75-7c2.394,2.271,3.844,0.774,4-1.5
+			c0.655-9.49,0.5-17.335,13.168,0.162c2.166-11.662,1.57-11.532,10.832-5.662c3.386,2.146,12.408-6.895,22.834-24.171
+			c-15.797,56.413-21.918,71.585-27.25,43.547c-0.281-1.479-2.012-2.23-2.07-0.349c-0.24,7.678-1.377,20.271-3.014,20.557
+			c-4.332,0.75-5.578-12.536-13.078-17.454c-4.668,8.335-1.379,19.028-5.504,19.619c-4.084,0.585-3.918-7.335-13.833-13.085
+			c-4.333,15.166,6.25,30.166-18.083,11.498c-9.61-7.368-5.001,7.451-4,9c2.88,4.452,7.537,19.903,74,15.336
+			C353.05,389.689,527.553,303.156,547.047,303.156z"/>
+		<path fill="#FFFFFF" d="M194.715,254.155c13.552-8.521,29.008,1.334,32.333,10.666c5.6,15.722-4,41.999-22.667,39.334
+			C175.246,299.992,183.048,261.486,194.715,254.155z"/>
+		<path d="M195.716,271.09c3.8-2.39,8.134,0.375,9.067,2.99c1.57,4.405-0.358,12.426-6.356,11.027
+			C190.387,283.24,192.444,273.144,195.716,271.09z"/>
+	</g>
+</g>
+</svg>
Binary file Images/bluetooth.psd has changed
Binary file Images/companion_ostc_3.png has changed
Binary file Images/dox_Companion.png has changed
Binary file Images/frog_160x120.png has changed
Binary file Images/frog_160x120.psd has changed
Binary file Images/inst_OSTC_Companion.ico has changed
Binary file Images/inst_OSTC_Companion.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Images/inst_OSTC_Companion.svg	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="636.965px" height="500.445px" viewBox="0 0 636.965 500.445" enable-background="new 0 0 636.965 500.445"
+	 xml:space="preserve">
+<g id="Boite">
+	<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="425.7139" y1="179.2048" x2="425.7139" y2="418.5559">
+		<stop  offset="0" style="stop-color:#E8C330"/>
+		<stop  offset="1" style="stop-color:#BA7C00"/>
+	</linearGradient>
+	<polygon fill="url(#SVGID_1_)" stroke="#231F20" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="
+		517.705,418.555 333.723,338.772 333.723,179.205 517.705,258.988 	"/>
+	<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="241.731" y1="224.6086" x2="241.731" y2="374.9467">
+		<stop  offset="0" style="stop-color:#E8C330"/>
+		<stop  offset="1" style="stop-color:#BA7C00"/>
+	</linearGradient>
+	<polygon fill="url(#SVGID_2_)" stroke="#231F20" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="
+		149.74,418.555 333.723,338.772 333.723,179.205 149.74,258.988 	"/>
+	<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="425.7139" y1="258.988" x2="425.7139" y2="498.3396">
+		<stop  offset="0" style="stop-color:#E8C330"/>
+		<stop  offset="1" style="stop-color:#BA7C00"/>
+	</linearGradient>
+	<polygon fill="url(#SVGID_3_)" stroke="#231F20" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="
+		333.723,498.34 517.705,418.555 517.705,258.988 333.723,338.772 	"/>
+	<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="241.731" y1="258.988" x2="241.7309" y2="498.3396">
+		<stop  offset="0" style="stop-color:#E8C330"/>
+		<stop  offset="1" style="stop-color:#BA7C00"/>
+	</linearGradient>
+	<polygon fill="url(#SVGID_4_)" stroke="#231F20" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="
+		333.723,498.34 149.74,418.555 149.74,258.988 333.723,338.772 	"/>
+	<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="253.0269" y1="314.3044" x2="169.0851" y2="364.6226">
+		<stop  offset="0" style="stop-color:#E8C330"/>
+		<stop  offset="1" style="stop-color:#BA7C00"/>
+	</linearGradient>
+	<polygon fill="url(#SVGID_5_)" stroke="#231F20" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="
+		285.394,412.135 101.41,332.35 149.74,258.988 333.723,338.772 	"/>
+	<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="370.5898" y1="321.696" x2="538.437" y2="358.1064">
+		<stop  offset="0" style="stop-color:#E8C330"/>
+		<stop  offset="1" style="stop-color:#BA7C00"/>
+	</linearGradient>
+	<polygon fill="url(#SVGID_6_)" stroke="#231F20" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" points="
+		564.895,338.772 380.913,418.555 333.723,338.772 517.705,258.988 	"/>
+</g>
+<g>
+	<g id="Layer_3">
+		<path fill="#EF3B39" d="M179.968,143.068c-14.767-18.94-21.507-50.397-75.088-69.164c-15.263-4.302-5.737-9.561,2.868-6.364
+			c50.277,23.007,82.406,16.048,47.33-24.95c-4.915-5.745,4.06-10.021,6.693-5.737c31.423,51.132,67.716,43,53.962-14.103
+			c-1.715-7.115,8.755-7.767,8.949-1.673c1.748,55.16,37.449,46.839,49.676,4.756c2.086-5.161,14.961-11.887,8.143,2.797
+			c-19.667,38.469-4.06,71.771-5.078,81.76L179.968,143.068z"/>
+		<circle fill="#EF3B39" cx="89.617" cy="62.568" r="3.776"/>
+		<circle fill="#EF3B39" cx="152.608" cy="29.609" r="3.776"/>
+		<circle fill="#EF3B39" cx="218.851" cy="10.171" r="3.775"/>
+		<circle fill="#EF3B39" cx="285.542" cy="11.258" r="3.776"/>
+	</g>
+	<g id="Calque_1">
+		<g>
+			<path d="M469.749,209.36c18.152,0,47.451,53.185,55.1,50.076c3.472-1.41-2.02-18.288-18.047-73.98
+				c-5.899-20.496-3.038-56.891-4.782-72.189c-2.117-18.597-9.041-2.941-9.56,18.286c-0.598,24.502-6.684,51.366-16.254,49.004
+				c-18.73-4.62-65.978-69.8-182.866-76.016C170.4,98.004,149.607,178.68,161.417,208.284c9.296,23.306,16.468,30.355,31.288,40.876
+				c-1.076-17.092,0.957-14.824,18.884-2.152c2.031-12.073-16.016-26.656,15.059-4.186c0.986,0.78,0.894-2.101,0.896-2.985
+				c0.035-14.711-2.847-15.732,8.426-5.019c1.717,1.628,2.756,0.555,2.869-1.076c0.47-6.806,0.358-12.432,9.442,0.117
+				c1.554-8.364,1.126-8.271,7.768-4.061c2.428,1.539,8.898-4.944,16.375-17.333c-11.328,40.456-15.717,51.334-19.542,31.229
+				c-0.202-1.061-1.443-1.6-1.484-0.25c-0.172,5.506-0.987,14.538-2.161,14.742c-3.106,0.537-4-8.991-9.378-12.518
+				c-3.348,5.978-0.989,13.646-3.947,14.069c-2.929,0.42-2.81-5.259-9.92-9.383c-3.107,10.875,4.482,21.633-12.968,8.246
+				c-6.892-5.284-3.586,5.344-2.868,6.454c2.065,3.193,5.404,14.273,53.066,10.998C330.632,271.415,455.771,209.36,469.749,209.36z"
+				/>
+			<path fill="#FFFFFF" d="M217.087,174.221c9.718-6.11,20.802,0.956,23.187,7.648c4.016,11.274-2.868,30.119-16.255,28.207
+				C203.125,207.091,208.721,179.478,217.087,174.221z"/>
+			<path d="M217.805,186.365c2.725-1.713,5.833,0.269,6.502,2.145c1.126,3.16-0.257,8.911-4.558,7.907
+				C213.983,195.077,215.459,187.839,217.805,186.365z"/>
+		</g>
+	</g>
+</g>
+</svg>
Binary file Images/ostc2c_160x120.png has changed
Binary file Images/ostc2c_160x120.psd has changed
Binary file Images/ostc2p_160x120.png has changed
Binary file Images/ostc2p_160x120.psd has changed
Binary file Images/ostc3_160x120.png has changed
Binary file Images/ostc3_160x120.psd has changed
Binary file Images/ostc3p_160x120.png has changed
Binary file Images/ostc3p_160x120.psd has changed
Binary file Images/ostc4_160x120.png has changed
Binary file Images/ostc4_160x120.psd has changed
Binary file Images/ostc_cr_160x120.png has changed
Binary file Images/ostc_cr_160x120.psd has changed
Binary file Images/ostc_sport_160x120.png has changed
Binary file Images/ostc_sport_160x120.psd has changed
Binary file Images/tux_256x256.png has changed
Binary file Images/unknown.png has changed
Binary file Images/usb.psd has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LogEditor.ui	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>editLogWnd</class>
+ <widget class="QDialog" name="editLogWnd">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>853</width>
+    <height>385</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>OSTC Companion settings</string>
+  </property>
+  <widget class="QLabel" name="noPortLabel">
+   <property name="geometry">
+    <rect>
+     <x>9</x>
+     <y>34</y>
+     <width>16</width>
+     <height>16</height>
+    </rect>
+   </property>
+   <property name="sizePolicy">
+    <sizepolicy hsizetype="Ignored" vsizetype="Preferred">
+     <horstretch>0</horstretch>
+     <verstretch>0</verstretch>
+    </sizepolicy>
+   </property>
+   <property name="textFormat">
+    <enum>Qt::TextFormat::RichText</enum>
+   </property>
+   <property name="alignment">
+    <set>Qt::AlignmentFlag::AlignCenter</set>
+   </property>
+   <property name="openExternalLinks">
+    <bool>true</bool>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="WriteAllHeader">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>80</y>
+     <width>101</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>WriteAllHeader</string>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="okB">
+   <property name="geometry">
+    <rect>
+     <x>220</x>
+     <y>330</y>
+     <width>75</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>OK</string>
+   </property>
+   <property name="shortcut">
+    <string>Return</string>
+   </property>
+   <property name="autoDefault">
+    <bool>false</bool>
+   </property>
+   <property name="default">
+    <bool>true</bool>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="ReadAllSamples">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>230</y>
+     <width>101</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>ReadAllSamples</string>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="WriteAllSamples">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>260</y>
+     <width>101</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>WriteAllSamples</string>
+   </property>
+  </widget>
+  <widget class="QProgressBar" name="HeaderUsage">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>20</y>
+     <width>131</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="value">
+    <number>24</number>
+   </property>
+  </widget>
+  <widget class="QProgressBar" name="SampleUsage">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>190</y>
+     <width>131</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="value">
+    <number>24</number>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="pushButton">
+   <property name="geometry">
+    <rect>
+     <x>30</x>
+     <y>50</y>
+     <width>101</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>ReadAllHeader</string>
+   </property>
+  </widget>
+  <widget class="QTableWidget" name="SectorView">
+   <property name="geometry">
+    <rect>
+     <x>180</x>
+     <y>10</y>
+     <width>151</width>
+     <height>131</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="pushButton_2">
+   <property name="geometry">
+    <rect>
+     <x>110</x>
+     <y>330</y>
+     <width>75</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Save Dump</string>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="LoadDump">
+   <property name="geometry">
+    <rect>
+     <x>20</x>
+     <y>330</y>
+     <width>75</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Load Dump</string>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label">
+   <property name="geometry">
+    <rect>
+     <x>520</x>
+     <y>10</y>
+     <width>47</width>
+     <height>13</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Info</string>
+   </property>
+  </widget>
+  <widget class="QTextBrowser" name="textBrowser_2">
+   <property name="geometry">
+    <rect>
+     <x>570</x>
+     <y>10</y>
+     <width>256</width>
+     <height>361</height>
+    </rect>
+   </property>
+   <property name="acceptRichText">
+    <bool>true</bool>
+   </property>
+  </widget>
+  <widget class="QTableWidget" name="SampleView">
+   <property name="geometry">
+    <rect>
+     <x>180</x>
+     <y>180</y>
+     <width>171</width>
+     <height>41</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="ResetSampleAddr">
+   <property name="geometry">
+    <rect>
+     <x>340</x>
+     <y>10</y>
+     <width>101</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>ResetSampleAddr</string>
+   </property>
+  </widget>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>okB</sender>
+   <signal>clicked()</signal>
+   <receiver>editLogWnd</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>250</x>
+     <y>140</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>373</x>
+     <y>100</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>languageSlot(int)</slot>
+  <slot>resetSettingsSlot()</slot>
+  <slot>updatePortsSlot()</slot>
+ </slots>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MainWindow.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,536 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   MainWindow.cpp
+/// \brief  GUI for OSTC Companion.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2014 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "MainWindow.h"
+
+#include "Utils/LogAppender.h"
+#include "Utils/ProgressEvent.h"
+
+#include "ui_MainWindow.h"
+#include "SettingsDialog.h"
+#include "editLogDialog.h"
+
+#include <QString>
+#include <QDateTime>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMessageBox>
+#include <QPlainTextEdit>
+#include <QProgressBar>
+#include <QSettings>
+#include <QTextCursor>
+#include "OSTCFrogOperations.h"
+#include "OSTC2cOperations.h"
+#include "OSTC2Operations.h"
+#include "OSTCSportOperations.h"
+#include "OSTC3Operations.h"
+#include "OSTC3pOperations.h"
+#include "OSTC4Operations.h"
+#include "OSTC_CR_Operations.h"
+
+extern QSettings* settings;
+
+//////////////////////////////////////////////////////////////////////////////
+
+class EXPORT LogWindow
+    : public LogAppender
+{
+    MainWindow* _window;
+
+    //---- The <<printing>> function -----------------------------------------
+    void operator()(const Log &log) override {
+        QString message = log.message;
+
+        message.replace("< ", "&lt; ")
+               .replace(" >", " &gt;");
+        if( ! message.isEmpty() )
+            _window->statusMessage(message);
+    }
+
+    //---- Reimplementing mandatory methds -----------------------------------
+    const char *type() const override {
+        return "File";
+    }
+    Log::Level defaultMinLevel() const override {
+        return Log::LEVEL_INFO;
+    }
+    const char* defaultFormat() const override {
+        return "%m";
+    }
+
+public:
+    LogWindow(MainWindow* window)
+      : LogAppender(0, NULL),
+        _window(window)
+    {}
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+MainWindow::MainWindow()
+  : QMainWindow(NULL),
+    _ui(new Ui::MainWindow),
+    _op(0)
+{
+    // Connect the Log system to this window:
+    new LogWindow(this);
+    // Connect the progress system to this window:
+    ProgressManager::getInstance()->setMainWindow(this);
+
+    _ui->setupUi(this);
+    _ui->progressBar->show();
+
+    _ui->editLog->setVisible(false);
+
+    // Auto-select last model:
+    QString model = settings->value("Interface/computerType").toString();
+        _ui->computerType->setCurrentIndex(0);
+
+    if( model == "ostc2c" )
+        _ui->computerType->setCurrentIndex(0);
+    else if( model == "hwOS (Bluetooth)" )
+        _ui->computerType->setCurrentIndex(1);
+    else if( model == "hwOS (USB)" )
+        _ui->computerType->setCurrentIndex(2);
+    else if( model == "ostc4" )
+        _ui->computerType->setCurrentIndex(3);
+
+    changeTypeSlot();
+
+#ifdef Q_OS_MAC
+    {
+        QMenuBar *menuBar = new QMenuBar(this);
+        QMenu* help = menuBar->addMenu(tr("&Help"));
+            help->addAction(tr("Preferences..."), this, SLOT(settingsSlot()));
+    }
+#endif
+
+    setWindowTitle(QString("OSTC Companion v%1.%2 %3")
+                       .arg(MAJOR_VERSION)
+                       .arg(MINOR_VERSION)  // kein sprintf nötig, arg konvertiert automatisch
+                       .arg(BETA_VERSION
+                                ? QString(" beta %1").arg(PATCH_VERSION)
+                                : QString::number(PATCH_VERSION))
+                   );
+
+   _ui->companionUrlL->setText( QString("<html>%1 "
+        "<a href='https://ostc-planner.net/wp/companion"
+#if defined(Q_OS_IOS)
+            "?os=ios"
+#elif  defined(Q_OS_OSX)
+            "?os=mac"
+#elif defined(Q_OS_WIN)
+            "?os=win"
+#elif defined(Q_OS_ANDROID)
+            "?os=android"
+#elif defined(Q_OS_LINUX)
+            "?os=linux"
+#else
+            "?os=other"
+#endif
+#if defined(Q_OS_DARWIN64) || defined(Q_OS_WIN64) || defined(Q_OS_LINUX )
+            "&arch=64"
+#else
+            "&arch=32"
+#endif
+            "&version=%2.%3.%4"
+            "&model=%6"
+          "'>ostc-planner.net/wp/companion"
+          "</a>"
+        "</html>")
+            .arg( tr("Official web site") )
+            .arg(QString::number(MAJOR_VERSION))
+            .arg(QString::number(MINOR_VERSION))
+            .arg(BETA_VERSION ? QString("beta%1").arg(PATCH_VERSION)
+                              : QString("%1")    .arg(PATCH_VERSION))
+            .arg(_op->model())
+    );
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+MainWindow::~MainWindow()
+{
+    delete _ui;
+    delete _op;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool MainWindow::event(QEvent* e)
+{
+    if( ProgressEvent* p = dynamic_cast<ProgressEvent*>(e) )
+    {
+        QProgressBar* w = _ui->progressBar;
+
+        if( p->current > p->maximum && p->maximum > 0)
+        {
+            w->setMaximum(p->maximum);  // Remove throttling mode, if any.
+            w->reset();
+        }
+        else
+        {
+            if( ! w->isEnabled() )
+                w->setEnabled(true);
+            if( w->maximum() != p->maximum )
+                w->setMaximum(p->maximum);  // Start throttling if max==0
+            w->setValue(p->current);
+        }
+        return true;
+    }
+
+    return QMainWindow::event(e);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::changeTypeSlot()
+{
+    QString name;
+
+    //---- Setup a new driver ------------------------------------------------
+    delete _op;
+    _op = 0;
+    switch( _ui->computerType->currentIndex() ) {
+    case 0: name = "ostc2c";
+            _op = new OSTC2cOperations;
+        break;
+    case 1:  name = "hwOS (USB)";
+             _op = new OSTC3Operations;
+        break;
+    case 2: name = "hwOS (Bluetooth)";
+             _op = new OSTC3pOperations;
+        break;
+    case 3: name = "ostc4";
+        _op = new OSTC4Operations;
+        break;
+
+    default:
+        qWarning("Internal error: unknown computer type");
+        return;
+    }
+    LOG_INFO(tr("%1 selected.").arg(_op->model()));
+
+    settings->setValue("Interface/computerType", name);
+    settings->sync();
+
+    // backword compatibility >= translate name if necessary
+    if ( name =="hwOS (Bluetooth)") name = "ostc3p";
+     if( name =="hwOS (USB)") name = "ostc3";
+
+
+    _ui->computerImage->setPixmap( QPixmap(":/Images/" + name + "_160x120.png"));
+
+    updateStatus();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::settingsSlot()
+{
+    Settings* s = new Settings(this, _op);
+    s->exec();
+    delete s;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::connectSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        LOG_INFO("Connecting...");
+
+        //---- Already connected ? ----------------------------------------------
+        if( ! _op->description().isEmpty() )
+            _op->disconnect();
+
+        //---- (Re)connect ------------------------------------------------------
+        if( _op->connect() ) {
+
+            if( Settings::autoSetDateTime )
+                dateSlot();
+
+            LOG_INFO("Connected: " + _op->description() );
+        }
+        updateStatus();
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::closeSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        LOG_INFO("Disconnecting...");
+        if( _op->disconnect() )
+            LOG_INFO("Disconnected.");
+        updateStatus();
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::updateStatus()
+{
+    bool ok = _op;
+    _ui->connectButton->setEnabled( ok );
+
+    // ON when supported but NOT connected, OFF once connected.
+    _ui->upgradeButton->setEnabled( ok
+                                && (_op->supported() & HardwareOperations::FIRMWARE)
+                                && !_op->serial().isOpen());
+
+    // Only allow buttons when connected:
+    ok &= _op->serial().isOpen();
+    _ui->dateButton   ->setEnabled( ok && (_op->supported() & HardwareOperations::DATE) );
+    _ui->nameButton   ->setEnabled( ok && (_op->supported() & HardwareOperations::NAME) );
+    _ui->iconButton   ->setEnabled( ok && (_op->supported() & HardwareOperations::ICON) );
+    _ui->signalButton ->setEnabled( ok && (_op->supported() & HardwareOperations::SIGNAL_CHECK) );
+    _ui->closeButton  ->setEnabled( ok );
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::dateSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        QDateTime date = QDateTime::currentDateTime();
+
+        LOG_INFO(tr("Settings date & time..."));
+        _op->setDate( date );
+        LOG_INFO( QString("Date set to %1")
+                 .arg(date.toString("yyyy/MM/dd hh:mm:ss")) );
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::nameSlot()
+{
+    Q_ASSERT( _op );
+    try {
+        LOG_INFO(tr("Settings name..."));
+
+        //---- Get old name, and reformat to multi-lines ---------------------
+        QString oldName = _op->description().section(", ", 2);
+        QString oldText;
+        QSize size = _op->nameSize();
+
+        for(int l=0; l<size.height(); ++l) {
+            QString line = oldName.left( size.width() ).leftJustified(size.width());
+            if( line.contains("\n") ) {
+                line = line.section("\n", 0, 0);
+                oldName = oldName.mid(line.length());
+            }
+            else {
+                oldName = oldName.mid(line.length());
+                if( oldName[0] == '\n' )
+                    oldName = oldName.mid(1);
+            }
+            oldText += line + "|\n";
+        }
+
+        //---- Ask user ------------------------------------------------------
+
+        QInputDialog* d = new QInputDialog(this);
+        d->setWindowTitle("Set Computer Name...");
+        d->setInputMode(QInputDialog::TextInput);
+        d->setOptions(QInputDialog::UsePlainTextEditForTextInput);
+        d->setTextValue(oldText);
+
+        QPlainTextEdit* edit = d->findChild<QPlainTextEdit*>();
+        assert(edit);
+        edit->setStyleSheet(
+            "background-color: black;"
+            "color: green;"
+            "font: 14pt 'Courier New';"
+        );
+
+        if( d->exec() != QDialog::Accepted )
+            return;
+
+        QString newText = d->textValue();
+        delete d;
+
+        //---- Reformat to single padded string ------------------------------
+        QStringList lines = newText.split("\n");
+        QString name;
+        for(int l=0; l<size.height(); ++l) {
+            if( l < lines.count() )
+                name += lines[l].leftJustified(size.width(), ' ', true);
+            else
+                name += QString(size.width(), ' ');
+        }
+
+        //---- Send result ---------------------------------------------------
+        _op->setName( name );
+        _op->getIdentity();
+        LOG_INFO( QString("Name set to '%1'")
+            .arg(_op->description().section(',', 2)).replace("\n", "|") );
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::iconSlot()
+{
+    Q_ASSERT( _op );
+    try {
+        LOG_INFO(tr("Settings icons..."));
+
+        QString fileName = QFileDialog::getOpenFileName(this,
+                                                        "Icon File...",
+                                                        QString(),
+                                                        "Images (*.gif *.png *.tif *.tiff *.jpg *.jpeg *.ico *.svg);;"
+                                                        "GIF Image (*.gif);;"
+                                                        "PNG Image (*.png);;"
+                                                        "TIFF Image (*.tif *.tiff);;"
+                                                        "JPEG Image (*.jpg *.jpeg);;"
+                                                        "Icon Image (*.ico);;"
+                                                        "SVG Image (*.svg);;"
+                                                        "Anything (*.*)");
+        if( ! fileName.isEmpty() )
+            _op->setIcons(fileName);
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::upgradeSlot()
+{
+    Q_ASSERT( _op );
+
+    try {
+        LOG_INFO(tr("Upgrading firmware..."));
+
+        QString hexFile = QFileDialog::getOpenFileName(0,
+                                                       "Hex File...",
+                                                       Settings::currentPath,
+                                                       _op->firmwareTemplate());
+        if( hexFile.isEmpty() )
+            return;
+
+        Settings::currentPath = QFileInfo(hexFile).absoluteDir().path();
+        Settings::save();
+
+        if( _op )
+            _op->upgradeFW(hexFile);
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void MainWindow::statusMessage(const QString& msg)
+{
+    {   // Move cursor to end of document.
+        QTextCursor c = _ui->console->textCursor();
+        c.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
+        _ui->console->setTextCursor(c);
+    }
+    _ui->console->appendHtml(msg);
+    _ui->console->ensureCursorVisible();
+
+    qApp->processEvents(QEventLoop::AllEvents, 50);
+}
+
+void MainWindow::retranslate()
+{
+    _ui->retranslateUi(this);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+void MainWindow::on_signalButton_clicked()
+{
+    Q_ASSERT( _op );
+
+    try {
+            LOG_INFO(tr("Request Bluetooth signal strength..."));
+            _op->getSignal();
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+
+void MainWindow::on_editLog_clicked()
+{
+    EditLogDialog* eL = new EditLogDialog(this, _op);
+    eL->exec();
+    delete eL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MainWindow.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,127 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   MainWindow.h
+/// \brief  GUI for OSTC Companion.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2014 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-03-17 : [jDG] Initial version.
+//  2013-03-26 : [jDG] Added scan feature for HW factory programming.
+//  2014-07-07 : [jDG] Cleanups for Subsurface google-summer-of-code.
+//  2014-07-25 : [jDG] BSD 2-clause license.
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+namespace Ui {
+class MainWindow;
+}
+class HardwareOperations;
+class QProgressBar;
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief  GUI for OSTC Companion.
+///
+/// Implement the *Companion* user interface.
+///
+class MainWindow : public QMainWindow
+{
+    Q_OBJECT
+    Ui::MainWindow*     _ui;    ///< Compiled interface.
+    HardwareOperations* _op;    ///< Driver for the connected dive computer.
+
+    //------------------------------------------------------------------------
+    /// \brief Update buttons enable/disable state.
+    /// According to connection status (not/normal/service), and high level
+    /// commands supported by a given computer model.
+    void updateStatus();
+
+    //------------------------------------------------------------------------
+    /// \brief Display a message on the embedded console.
+    ///
+    void statusMessage(const QString &msg);
+    friend class LogWindow;
+
+    //------------------------------------------------------------------------
+    /// \brief Manage asynchronous ProgressEvent events.
+    ///
+    bool event(QEvent *e) override;
+
+public:
+    //////////////////////////////////////////////////////////////////////////
+    /// \brief Constructor.
+    ///
+    /// Starts everything, and instanciate the driver found in settings.
+    MainWindow();
+
+    //------------------------------------------------------------------------
+    /// \brief Destructor.
+    /// Release interface and driver.
+    ~MainWindow();
+
+    //------------------------------------------------------------------------
+    /// \brief Handle Language changes.
+    /// Re-apply translations to all interface elements.
+    void retranslate();
+
+public slots:
+    /// \brief User changed computer model.
+    void changeTypeSlot();
+
+    /// \brief Pop SettingsDialog preferences interface.
+    void settingsSlot();
+
+    /// \brief Try to connect to the device.
+    void connectSlot();
+
+    /// \brief Try to set Date & Time on the connected device.
+    void dateSlot();
+
+    /// \brief Try to change custom text on the connected device.
+    void nameSlot();
+
+    /// \brief Try to set custom image on the connected device.
+    void iconSlot();
+
+    /// \brief Try to upgrade firmware of the connected device.
+    void upgradeSlot();
+
+    /// \brief Close current connection to the device.
+    void closeSlot();
+private slots:
+    void on_signalButton_clicked();
+    void on_editLog_clicked();
+};
+
+#endif // MAINWINDOW_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MainWindow.ui	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,525 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>451</width>
+    <height>418</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string notr="true">OSTC Companion</string>
+  </property>
+  <property name="windowIcon">
+   <iconset>
+    <normaloff>:/Images/app_OSTC_Companion.svg</normaloff>:/Images/app_OSTC_Companion.svg</iconset>
+  </property>
+  <widget class="QWidget" name="centralWidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="7" column="0" colspan="4">
+     <widget class="QProgressBar" name="progressBar">
+      <property name="alignment">
+       <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
+      </property>
+      <property name="textVisible">
+       <bool>true</bool>
+      </property>
+     </widget>
+    </item>
+    <item row="0" column="1">
+     <widget class="QComboBox" name="computerType">
+      <property name="toolTip">
+       <string>H&amp;W dive computer model.</string>
+      </property>
+      <property name="currentIndex">
+       <number>0</number>
+      </property>
+      <item>
+       <property name="text">
+        <string notr="true">OSTC2 mk.2/2n/2c</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string notr="true">OSTC hwOS (USB)</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string notr="true">OSTC hwOS (Bluetooth)</string>
+       </property>
+      </item>
+      <item>
+       <property name="text">
+        <string notr="true">OSTC4</string>
+       </property>
+      </item>
+     </widget>
+    </item>
+    <item row="0" column="3" rowspan="6">
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QPushButton" name="connectButton">
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>23</height>
+           </size>
+          </property>
+          <property name="toolTip">
+           <string>Open or re-open the last selected  USB or Bluetooth port (See Preferences).
+NOTE: make sure to connect the serial cable, or to set the bluetooth mode first...</string>
+          </property>
+          <property name="styleSheet">
+           <string notr="true">QPushButton {
+	border: 1px solid blue;
+    border-radius: 6px;
+	background: qlineargradient(x1:0, y1:0, x2:0, y2:1, 
+		stop:0.00 white,
+		stop:0.55 #55aaff,
+		stop:0.56 #4964ff,
+		stop:1.00 #55aaff)
+}</string>
+          </property>
+          <property name="text">
+           <string>Connect</string>
+          </property>
+          <property name="autoDefault">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <widget class="QPushButton" name="dateButton">
+        <property name="toolTip">
+         <string>Set the H&amp;W computer date and time.
+Can be automatic at each connect, if asked to in the Preferences.
+(Valid once device is connected).</string>
+        </property>
+        <property name="text">
+         <string>Set Date &amp;&amp; Time</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="nameButton">
+        <property name="toolTip">
+         <string>Change the name displayed on the H&amp;W device.
+(Valid once device is connected).</string>
+        </property>
+        <property name="text">
+         <string>Set Name...</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="iconButton">
+        <property name="toolTip">
+         <string>Upload a customization icon.
+Only supported on Frog yet.
+(Valid once device is connected).</string>
+        </property>
+        <property name="text">
+         <string>Set Icon...</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="signalButton">
+        <property name="text">
+         <string>Check Signal</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="upgradeButton">
+        <property name="toolTip">
+         <string>Ask for a firmware file, and upload it to the H&amp;W device.
+Can be done before or after device (re-)connection.</string>
+        </property>
+        <property name="text">
+         <string>Upgrade Firmware...</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="closeButton">
+        <property name="toolTip">
+         <string>Close USB or Bluetooth connection to the device.</string>
+        </property>
+        <property name="text">
+         <string>Close</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="verticalSpacer">
+        <property name="orientation">
+         <enum>Qt::Orientation::Vertical</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Policy::Minimum</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>105</width>
+          <height>8</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="quitButton">
+        <property name="text">
+         <string>Quit</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+    <item row="6" column="0" colspan="4">
+     <widget class="QLabel" name="companionUrlL">
+      <property name="text">
+       <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;See us on &lt;a href=&quot;https://ostc-planner.net/wp/companion/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;ostc-planner.net/wp/companion&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+      </property>
+      <property name="openExternalLinks">
+       <bool>true</bool>
+      </property>
+     </widget>
+    </item>
+    <item row="2" column="0">
+     <spacer name="horizontalSpacer_2">
+      <property name="orientation">
+       <enum>Qt::Orientation::Horizontal</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>40</width>
+        <height>20</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item row="0" column="0">
+     <widget class="QToolButton" name="settingsButton">
+      <property name="toolTip">
+       <string>Open the Preferences menu.</string>
+      </property>
+      <property name="text">
+       <string>...</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:/Images/Settings.svg</normaloff>:/Images/Settings.svg</iconset>
+      </property>
+     </widget>
+    </item>
+    <item row="8" column="0" colspan="4">
+     <widget class="QPlainTextEdit" name="console">
+      <property name="minimumSize">
+       <size>
+        <width>0</width>
+        <height>46</height>
+       </size>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Shadow::Sunken</enum>
+      </property>
+      <property name="verticalScrollBarPolicy">
+       <enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOn</enum>
+      </property>
+      <property name="horizontalScrollBarPolicy">
+       <enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
+      </property>
+      <property name="undoRedoEnabled">
+       <bool>false</bool>
+      </property>
+      <property name="readOnly">
+       <bool>true</bool>
+      </property>
+      <property name="centerOnScroll">
+       <bool>false</bool>
+      </property>
+      <property name="tabStopWidth" stdset="0">
+       <number>4</number>
+      </property>
+     </widget>
+    </item>
+    <item row="2" column="1">
+     <widget class="QLabel" name="computerImage">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="minimumSize">
+       <size>
+        <width>160</width>
+        <height>120</height>
+       </size>
+      </property>
+      <property name="maximumSize">
+       <size>
+        <width>160</width>
+        <height>120</height>
+       </size>
+      </property>
+      <property name="text">
+       <string/>
+      </property>
+      <property name="pixmap">
+       <pixmap>:/Images/frog_160x120.png</pixmap>
+      </property>
+     </widget>
+    </item>
+    <item row="2" column="2">
+     <spacer name="horizontalSpacer_3">
+      <property name="orientation">
+       <enum>Qt::Orientation::Horizontal</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>40</width>
+        <height>20</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item row="0" column="2">
+     <spacer name="horizontalSpacer">
+      <property name="orientation">
+       <enum>Qt::Orientation::Horizontal</enum>
+      </property>
+      <property name="sizeType">
+       <enum>QSizePolicy::Policy::Minimum</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>8</width>
+        <height>20</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item row="5" column="1">
+     <spacer name="verticalSpacer_3">
+      <property name="orientation">
+       <enum>Qt::Orientation::Vertical</enum>
+      </property>
+      <property name="sizeType">
+       <enum>QSizePolicy::Policy::Minimum</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>20</width>
+        <height>0</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item row="1" column="1">
+     <spacer name="verticalSpacer_2">
+      <property name="orientation">
+       <enum>Qt::Orientation::Vertical</enum>
+      </property>
+      <property name="sizeType">
+       <enum>QSizePolicy::Policy::Minimum</enum>
+      </property>
+      <property name="sizeHint" stdset="0">
+       <size>
+        <width>20</width>
+        <height>0</height>
+       </size>
+      </property>
+     </spacer>
+    </item>
+    <item row="5" column="2">
+     <widget class="QPushButton" name="editLog">
+      <property name="enabled">
+       <bool>false</bool>
+      </property>
+      <property name="text">
+       <string>Edit Log</string>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>computerType</sender>
+   <signal>activated(int)</signal>
+   <receiver>MainWindow</receiver>
+   <slot>changeTypeSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>113</x>
+     <y>29</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>34</x>
+     <y>67</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>quitButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>351</x>
+     <y>210</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>320</x>
+     <y>211</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>dateButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>dateSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>406</x>
+     <y>57</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>324</x>
+     <y>81</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>nameButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>nameSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>406</x>
+     <y>80</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>324</x>
+     <y>111</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>iconButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>iconSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>406</x>
+     <y>103</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>325</x>
+     <y>141</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>upgradeButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>upgradeSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>406</x>
+     <y>149</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>323</x>
+     <y>171</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>connectButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>connectSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>405</x>
+     <y>33</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>323</x>
+     <y>53</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>settingsButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>settingsSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>31</x>
+     <y>30</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>191</x>
+     <y>63</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>closeButton</sender>
+   <signal>clicked()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>closeSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>305</x>
+     <y>186</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>357</x>
+     <y>183</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>changeTypeSlot()</slot>
+  <slot>dateSlot()</slot>
+  <slot>nameSlot()</slot>
+  <slot>iconSlot()</slot>
+  <slot>upgradeSlot()</slot>
+  <slot>connectSlot()</slot>
+  <slot>settingsSlot()</slot>
+  <slot>scanSlot()</slot>
+  <slot>closeSlot()</slot>
+ </slots>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC2Operations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,141 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC2Operations.cpp
+/// \brief  Implementing various operations for the new OSTC2 (hwOS) dive computer
+/// \author JD Gascuel.
+/// \sa     ComputerOperations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "OSTC2Operations.h"
+#include "HexFile.h"
+#include "Utils/Log.h"
+
+#define FIRMWARE_SIZE       0x17F40
+
+#include <QApplication>
+#include <QProgressBar>
+#include <QRegularExpression>
+extern QProgressBar* progress;
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTC2Operations::OSTC2Operations()
+{
+    emulatorName = "OSTC2p";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QStringList OSTC2Operations::listPorts() const
+{
+    return listBluetoothPorts();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+#if 0
+QRegExp OSTC2Operations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty.OSTC[0-9]+.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, redhat, CentOS and SUSE (google dixit)
+    return QRegExp("ttyUSB.*", Qt::CaseSensitive);
+#elif defined(Q_OS_WIN)
+    return QRegExp("COM.*", Qt::CaseSensitive);
+#endif
+}
+#endif
+QRegularExpression OSTC2Operations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegularExpression("tty.OSTC[0-9]+.*",
+                              QRegularExpression::CaseInsensitiveOption);
+#elif defined(Q_OS_LINUX)
+    // Debian, Ubuntu, RedHat, CentOS, SUSE
+    return QRegularExpression("ttyUSB.*"); // default: case-sensitive
+#elif defined(Q_OS_WIN)
+    return QRegularExpression("COM.*");    // default: case-sensitive
+#endif
+}
+//////////////////////////////////////////////////////////////////////////////
+
+//void OSTC2pOperations::loadFirmware(HexFile& hex, const QString& fileName) const
+//{
+//    hex.allocate(FIRMWARE_SIZE);
+//    hex.load(fileName, progress);
+//    progress->reset();
+//}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTC2Operations::model() const
+{
+    return "OSTC2";
+}
+
+HardwareOperations::CompanionFeatures OSTC2Operations::supported() const
+{
+    // No ICON, no DUMPSCREEN.
+    return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
+                    |HELIUM_DIVE|CCR_DIVE|BLUETOOTH);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QSize OSTC2Operations::nameSize() const
+{
+    return QSize(12, 5);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2Operations::getIdentity()
+{
+    descriptionString.clear();
+
+    LOG_TRACE("Getting model...");
+    HardwareDescriptor hw = hardwareDescriptor();
+    if( hw != HW_OSTC2_a && hw != HW_OSTC2_b && hw != HW_OSTC2_c )
+        LOG_THROW("Not an OSTC2.");
+
+    LOG_TRACE("Getting identity...");
+    getCommonIdentity();
+
+    LOG_TRACE("Found " << descriptionString);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTC2Operations::firmwareTemplate() const
+{
+    return "*_firmware.hex";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC2Operations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,73 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC2Operations.h
+/// \brief  Implementing various operations for H&W new OSTC2 (BLE) dive computer
+/// \author JD Gascuel.
+/// \sa     ComputerOperations.h
+///
+/// \copyright (c) 2015-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2015-03-15 : [jDG] Creation, from OSTC3Operations.h
+
+#ifndef OSTC_2p_OPERATIONS_H
+#define OSTC_2p_OPERATIONS_H
+
+#include "OSTC3Operations.h"
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief new OSTC2 (Bluetooth) is just an OSTC3 in a different package.
+class  OSTC2Operations
+  : public OSTC3Operations
+{
+    /// \brief Returns "OSTC2"
+    QString model() const override;
+    CompanionFeatures supported() const override;
+
+    QSize nameSize() const override;
+
+    //------------------------------------------------------------------------
+    /// \brief Read OSTC2 (hwOS based) computer firmware, serial and custom text.
+    /// Everything is formated for the description() string.
+    /// \throws if something goes wrong.
+    void getIdentity() override;
+
+    QStringList listPorts() const override;
+   // QRegExp portTemplate() const override;
+    QRegularExpression portTemplate() const override;
+
+    QString firmwareTemplate() const override;
+
+    //////////////////////////////////////////////////////////////////////////
+public:
+    OSTC2Operations();
+};
+
+#endif // OSTC_2p_OPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC2cOperations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,542 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC2cOperations.h
+/// \brief  Implementing various operations for H&W OSTC2, mk2, 2n, 2c dive computer
+/// \author JD Gascuel.
+/// \sa     ComputerOperations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "OSTC2cOperations.h"
+
+#include "HexFile.h"
+
+#include "SettingsDialog.h"
+
+#include "Utils/Exception.h"
+#include "Utils/Log.h"
+#include "Utils/ProgressEvent.h"
+
+#include <QDateTime>
+#include <QRegularExpression>
+
+#define FIRMWARE_SIZE       0x17F40
+
+// 64 bytes on Mk.2/2n/2c:
+#define FIRMWARE_BLOCK_SIZE 0x40
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTC2cOperations::OSTC2cOperations()
+  : HardwareOperations(),
+    _computerFirmware(0),
+    _computerSerial(0)
+{
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2cOperations::readBank0(byte bank0[])
+{
+    _serial.sleep(100);
+    _serial.purge();
+    _serial.writeByte('g');
+    _serial.readBlock(bank0, 256);
+}
+
+void OSTC2cOperations::writeBank0(const byte bank0[])
+{
+    _serial.sleep(100);
+    _serial.purge();
+    _serial.writeByte('d');
+    int reply = _serial.readByte();
+    if( reply != 'd' )
+        LOG_THROW("Write start");
+
+    for(int a=4; a<256; ++a) {
+        _serial.writeByte(bank0[a]);
+        reply = _serial.readByte();
+        if( reply != bank0[a] )
+            LOG_THROW("Write bank0 @ " << a);
+    }
+
+    _serial.sleep(500); // Allow OSTC2c some time to reboot
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2cOperations::readBank1(byte bank1[256])
+{
+    _serial.sleep(100);
+    _serial.purge();
+    _serial.writeByte('j');
+    _serial.readBlock(bank1, 256);
+}
+
+void OSTC2cOperations::getIdentity()
+{
+    byte bank0[256], bank1[256];
+    memset(bank0, 0xFF, sizeof bank0);
+    memset(bank1, 0xFF, sizeof bank1);
+
+    //---- Get a memory dump:
+    try {
+        readBank0(bank0);
+        readBank1(bank1);
+    } catch(ReadTimeout) {
+        LOG_THROW("No reply from OSTC Mk.2, 2n or 2c...");
+    }
+
+    // Sanity check:
+    if( bank0[65] == 0xFF || bank1[1] > 99 || bank1[2] > 99 )
+        LOG_THROW("Not an OSTC Mk.2, 2n or 2c...");
+
+    _computerSerial   = bank0[0] + bank0[1]*256;
+    _computerFirmware = bank1[1] * 100 + bank1[2];
+    _customText       = QString::fromLatin1((char*)bank0+65, 25).section('}', 0,0);
+
+    _description = QString("%1 #%2, v%3.%4, %5")
+        .arg(model())
+        .arg(_computerSerial)
+        .arg(bank1[1]).arg(bank1[2], 2, 10, QChar('0'))
+        .arg( _customText );
+}
+
+int OSTC2cOperations::firmware() const
+{
+    return _computerFirmware;
+}
+
+int OSTC2cOperations::serialNumber() const
+{
+    return _computerSerial;
+}
+
+QString OSTC2cOperations::customText() const
+{
+    return _customText;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+#if 0
+QRegExp OSTC2cOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty.usbserial-.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, redhat, CentOS and SUSE (google dixit)
+    return QRegExp("ttyUSB.*", Qt::CaseSensitive);
+#elif defined(Q_OS_WIN)
+    return QRegExp("COM.*", Qt::CaseSensitive);
+#endif
+}
+#endif
+QRegularExpression OSTC2cOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegularExpression("tty.usbserial-.*",
+                              QRegularExpression::CaseInsensitiveOption);
+#elif defined(Q_OS_LINUX)
+    return QRegularExpression("ttyUSB.*"); // default: case-sensitive
+#elif defined(Q_OS_WIN)
+    return QRegularExpression("COM.*");    // default: case-sensitive
+#endif
+}
+QStringList OSTC2cOperations::listPorts() const
+{
+    return listUSBPorts();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTC2cOperations::model() const
+{
+    return "OSTC2c";
+}
+
+QString OSTC2cOperations::description()
+{
+    return _description;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+HardwareOperations::CompanionFeatures OSTC2cOperations::supported() const
+{
+    // No ICON.
+    return CompanionFeatures(PARAMETERS|DATE|NAME|DUMPSCREEN|FIRMWARE
+                     |HELIUM_DIVE|CCR_DIVE);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTC2cOperations::firmwareTemplate() const
+{
+    return "mk2_*.hex";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool OSTC2cOperations::connect()
+{
+    try {
+        LOG_TRACE("Connecting " << Settings::port);
+        _serial.open(Settings::port, "OSTC2c");
+        getIdentity();
+        return true;
+    }
+    catch(const Exception& e){
+        _serial.close();
+        LOG_THROW("Cannot connect " << Settings::port << ": " << e.what());
+    }
+    return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QSize OSTC2cOperations::nameSize() const
+{
+    return QSize(25, 1);
+}
+
+void OSTC2cOperations::writeText(const QString& msg)
+{
+    // No hardware support for that. Just skip it, as it is done when
+    // writting bank1...
+    LOG_TRACE(msg);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2cOperations::connectServiceMode()
+{
+    try {
+        _serial.open(Settings::port, "OSTC2c");
+
+        _serial.writeByte(0xC1);
+        _serial.sleep(500);                    // 0.50 sec for bootloader to start.
+
+        //---- Send C1C1 to start upgrading firmware -----------------------------
+        unsigned char pic = 0;
+        unsigned char ok = '?';
+        for(int retry=0; retry < 10; ++retry) {
+            _serial.writeByte(0xC1);
+            _serial.sleep(5);
+            _serial.writeByte(0xC1);
+            _serial.sleep(5);
+
+            try {
+                pic = _serial.readByte();
+                ok  = _serial.readByte();
+                break;
+            }
+            catch(const ReadTimeout&) {
+                if( retry == 9 )
+                    LOG_THROW("Cannot start firmware upgrade: timeout.");
+
+                LOG_INFO("Connecting OSTC2c (" << (retry+1) << "/10)...");
+            }
+        }
+
+        //---- Check PIC type ----------------------------------------------------
+        LOG_TRACE("Pic = " << int(pic));
+        if( ! pic )
+            LOG_THROW("Cannot sync firmware upgrade. Cannot detect chip");
+
+        if( pic != 0x57
+         || ok  != 'K'
+        )
+            LOG_THROW( "Cannot sync firmware upgrade. Bad chip " << int(pic) << " " << ok);
+
+        _serial.sleep(50);                       // Wait 0.050 sec here.
+        return;
+    }
+    catch(const Exception& e) {
+        _serial.close();
+        LOG_THROW("Cannot connect " << Settings::port << ": " << e.what());
+    }
+    return;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool OSTC2cOperations::disconnect(bool /*closing*/)
+{
+    if( ! _serial.isOpen() ) return false;
+
+    LOG_TRACE("Disconnecting.");
+
+    _serial.purge();
+    _serial.close();
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2cOperations::setDate(const QDateTime &date)
+{
+    unsigned char buffer[6];
+    buffer[0] = date.time().hour();
+    buffer[1] = date.time().minute();
+    buffer[2] = date.time().second();
+    buffer[3] = date.date().month();
+    buffer[4] = date.date().day();
+    buffer[5] = date.date().year() % 100;
+
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('b'); // 0x62
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 'b' )
+        LOG_THROW( "sync time" );
+
+    _serial.writeBlock( buffer, sizeof buffer);
+    _serial.sleep(100);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2cOperations::setName(const QString &newName)
+{
+    QByteArray padded = (newName + QString(25, '}'))
+            .left(25)
+            .toLatin1();
+
+    byte bank0[256] = {0};
+    readBank0(bank0);
+    memcpy(bank0+65, padded.constData(), 25);
+    writeBank0(bank0);
+
+    // Then get the new identity:
+    getIdentity();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2cOperations::setIcons(const QString &/*fileName*/)
+{
+    LOG_THROW("Not supported");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QImage OSTC2cOperations::dumpScreen() const
+{
+    QImage image(320, 240, QImage::Format_RGB32);
+
+     //---- Send dump screen command -------------------------------------
+    unsigned char reply;
+    _serial.writeByte('l');
+    reply = _serial.readByte();
+
+    if( reply != 'l' )
+        LOG_THROW( "Dumpscreen command failed: " << (int)reply );
+
+    //---- Read image ----------------------------------------------------
+    int percent = 0;
+    try {
+        for(int x=0; x<320; ++x)
+        {
+            int p = x/16;       // 5% steps
+            if( p != percent )
+            {
+                PROGRESS(p, 320/16);
+                percent = p;
+            }
+            int pix = 0, count = 0;
+
+            for(int y=0; y<240; ++y, --count)
+            {
+                if( count <= 0 )
+                {
+                    count = _serial.readByte();
+
+                    if( (count & 0x80) == 0 )
+                        pix = 0;
+                    else if( (count & 0xC0) == 0xC0 )
+                    {
+                        pix = 0xFFFF;
+                        count &= 0x3F;
+                    }
+                    else
+                    {
+                        unsigned char bpix[2];
+                        _serial.readBlock(bpix, 2);
+                        pix = (bpix[0] << 8) + bpix[1];
+                        count &= 0x3F;
+                    }
+                    count++;
+                }
+                // Bit extension 5bits --> 8bits:
+                //      12345123.45 = (12345 * 0b100001) / 4
+                //      12345612.3456 = (123456 * 0xb1000001) / 16
+                int r = (31 & (pix >> 11)) * 255/31;
+                int g = (63 & (pix >>  5)) * 255/63;
+                int b = (31 &  pix       ) * 255/31;
+                image.setPixel(x, y, qRgb( r, g, b));
+            }
+        }
+    }
+    catch( ReadTimeout ) {
+        LOG_THROW("Missing image data...");
+    }
+
+    //---- Done ----------------------------------------------------------
+    PROGRESS_RESET();
+    LOG_INFO( "Screen dumped." );
+    return image;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC2cOperations::loadFirmware(HexFile& hex, const QString& fileName) const
+{
+    hex.allocate(FIRMWARE_SIZE);
+    hex.load(fileName);
+
+    //---- Patch Firmware intialization GOTO ---------------------------------
+    memcpy((void*)(hex.data() + 0x17F38),   // To bootloader vector
+           (void*)(hex.data() + 0x00000),   // From fw reset code
+           8);                              // Up to 8 bytes...
+
+    static unsigned char byteCode[8] = {
+        0xA0, 0xEF, 0xBF, 0xF0,             // goto 0x1F740 (bootloader 19k)
+        0x00, 0x00,                         // nop
+        0x00, 0x00                          // nop
+    };
+    memcpy((void*)(hex.data() + 0x00000),   // To OSTC reset vector
+           (void*)(byteCode),               // From go back to bootloader.
+           8);                              // Up to 8 bytes...
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void
+uploadBlock(Serial& serial, const HexFile& hex, size_t addr)
+{
+    const unsigned char count = FIRMWARE_BLOCK_SIZE;
+    assert( 0 < count && count < 255 );
+    assert((addr+count) <= FIRMWARE_SIZE );
+    unsigned char reply = 0;
+
+    unsigned char header[4];
+    header[0] = 0x1F & (addr >> 16);
+    header[1] = 0xFF & (addr >>  8);
+    header[2] = 0xFF & (addr);
+    header[3] = count;
+
+    unsigned char crc = header[0] + header[1] + header[2] + header[3];
+    for(int i=0; i<count; ++i)
+        crc += hex.data()[addr+i];
+    crc = -crc; // Sum should make zero.
+
+    try {
+        serial.writeBlock(header, sizeof header);
+        serial.writeBlock(hex.data()+addr, count);
+        serial.writeByte(crc);
+    } catch(...) {
+        goto WriteFailed;
+    }
+
+    serial.sleep(20);        // 18msec for a FLASH row write, plus VAT.
+
+    reply = serial.readByte();
+    if( reply != 'K' )
+    {
+        serial.close();
+        LOG_THROW( QString("Bad checksum at 0x%1").arg((unsigned int)addr, 0, 16, QChar('0')));
+    }
+    return;
+
+WriteFailed:
+    serial.close();
+
+    LOG_THROW( QString("Write failed") );
+}
+
+void OSTC2cOperations::upgradeFW(const QString& fileName)
+{
+    //---- Load and check firmware ---------------------------------------
+    LOG_TRACE("Loading firmware '" << fileName << "'.");
+    HexFile hex;
+    loadFirmware(hex, fileName);
+
+    //---- Enter uart_115k_bootloader ----------------------------------------
+    connectServiceMode();
+
+    //---- Let's do it -------------------------------------------------------
+    int percent = 0;
+
+    for(size_t addr = FIRMWARE_BLOCK_SIZE; addr < FIRMWARE_SIZE; addr += FIRMWARE_BLOCK_SIZE)
+    {
+        int p = int((addr*200.0f) / float(FIRMWARE_SIZE) + 0.5f);
+        if( p > percent )
+        {
+            PROGRESS(percent, 200);
+            percent = p;
+        }
+        uploadBlock(_serial, hex, addr);
+    }
+    PROGRESS(200, 200);
+    uploadBlock(_serial, hex, 0);
+
+    PROGRESS_RESET();
+    LOG_INFO("Upgrade FW send.");
+
+    // More than 500ms --> receive timeout.
+    _serial.sleep(600);
+}
+
+void OSTC2cOperations::getSignal()
+{
+    return;
+}
+
+void OSTC2cOperations::getAllHeader(unsigned char* pBuffer)
+{
+    return;
+}
+void OSTC2cOperations::writeAllHeader(unsigned char* pBuffer)
+{
+    return;
+}
+
+void OSTC2cOperations::getAllSamples(unsigned char* pBuffer)
+{
+    return;
+}
+void OSTC2cOperations::writeAllSamples(unsigned char* pBuffer)
+{
+    return;
+}
+//////////////////////////////////////////////////////////////////////////////
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC2cOperations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,121 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC2cOperations.h
+/// \brief  Implementing various operations for H&W OSTC2, mk2, 2n, 2c dive computer
+/// \author JD Gascuel.
+/// \sa     ComputerOperations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-10-26 : [jDG] Creation.
+//  2015-03-16 : [jDG] Renamed OSTC2c
+
+#ifndef OSTC_2c_OPERATIONS_H
+#define OSTC_2c_OPERATIONS_H
+
+#include "HardwareOperations.h"
+
+//////////////////////////////////////////////////////////////////////////////
+
+class  OSTC2cOperations
+  : public HardwareOperations
+{
+    int     _computerFirmware;
+    int     _computerSerial;
+    QString _customText;
+    QString _description;
+
+    typedef unsigned char byte;
+
+    //////////////////////////////////////////////////////////////////////////
+    /// \{ \section Low level OSTC2 operations.
+
+    void readBank0(byte bank0[256]);
+    void readBank1(byte bank1[256]);
+    void writeBank0(const byte bank0[256]);
+
+    void getIdentity() override;
+
+    /// \brief The fw version found during the last getIdentty().
+    int firmware() const override;
+
+    /// \brief The serial number found during the last getIdentty().
+    int serialNumber() const override;
+
+    /// \brief The user-defined string found during the last getIdentty().
+    QString customText() const override;
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+    /// \{ \section Configuration management.
+
+    //QRegExp portTemplate() const override;
+    QRegularExpression portTemplate() const override;
+    QStringList listPorts() const override;
+
+    QString model() const override;
+    QString description() override;
+    CompanionFeatures supported() const override;
+
+    QString firmwareTemplate() const override;
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+    /// \{ \section OSTC2 high-level commands.
+
+    bool connect() override;
+    void connectServiceMode() override;
+    void writeText(const QString &msg) override;
+    QSize nameSize() const override;
+    void setDate(const QDateTime& date) override;
+    void setName(const QString& newName) override;
+    void getSignal() override;
+    void getAllHeader(unsigned char* pBuffer) override;
+    void writeAllHeader(unsigned char* pBuffer) override;
+    void getAllSamples(unsigned char* pBuffer) override;
+    void writeAllSamples(unsigned char* pBuffer) override;
+
+    void setIcons(const QString& fileName) override;
+    QImage dumpScreen() const override;
+
+    void upgradeFW(const QString& fileName) override;
+    void loadFirmware(HexFile& hex, const QString& fileName) const override;
+
+    bool disconnect(bool closing = false)  override;
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+
+public:
+    OSTC2cOperations();
+};
+
+#endif // OSTC_2c_OPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC3Operations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,685 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC3Operations.cpp
+/// \brief  Implementing various operations for OSTC3 dive computer
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "OSTC3Operations.h"
+
+#include "Utils/Exception.h"
+#include "Utils/Log.h"
+#include "Utils/ProgressEvent.h"
+
+#include "SettingsDialog.h"
+#include "HexFile.h"
+
+#include <QApplication>
+#include <QDateTime>
+#include <QDir>
+#include <QRegularExpression>
+
+// Byte extration, compatible littleendian or bigendian.
+#define LOW(x)      ((unsigned char)((x) % 256))
+#define HIGH(x)     ((unsigned char)((x / (1<<8)) % 256))
+#define UPPER(x)    ((unsigned char)((x / (1<<16)) % 256))
+#define UP32(x)     ((unsigned char)((x / (1<<24)) % 256))
+
+#define FIRMWARE_AREA   0x3E0000
+#define FIRMWARE_SIZE   0x01E000        // 120KB
+#define FIRMWARE_BLOCK    0x1000        //   4KB
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTC3Operations::OSTC3Operations()
+  : descriptionString(""),
+    emulatorName("OSTC3"),
+    _computerFirmware(0),
+    _computerSerial(0),
+    _connectMode(CLOSED_MODE)
+{
+    memset(computerText, 0, sizeof computerText);
+}
+
+OSTC3Operations::~OSTC3Operations()
+{
+    if( _connectMode != CLOSED_MODE )
+        disconnect(true);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QStringList OSTC3Operations::listPorts() const
+{
+    return listUSBPorts();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool OSTC3Operations::connect()
+{
+    LOG_TRACE( "Enter download mode..." );
+
+    try {
+        _connectMode = CLOSED_MODE;
+        _serial.open( Settings::port, emulatorName);
+        _serial.sleep(333);    // Initial 1/3 sec. delay to first comm.
+
+        for(int retry=0; retry < 10; ++retry)
+        {
+            // Allow for 0.1sec extra delay
+            try {
+                _serial.writeByte(0xBB);
+            } catch(const WriteTimeout& ) {
+                // Bluetooth not present: one can open the pseudo COM port,
+                // but we will have a timeout on first write byte...
+                if( retry < 9 ) {
+                    LOG_INFO("Cannot connect to " << Settings::port <<" (" << (retry+1) << "/10)...");
+                    _serial.sleep(1000);
+                    continue;
+                }
+                LOG_THROW("Cannot connect to " << model() <<".");
+                return false;
+            }
+
+            _serial.sleep(100);
+
+            //---- Check acknowledge, w/o fatal timeouts.
+            unsigned char ok   = 0;
+            unsigned char echo = 0;
+            try {
+                echo = _serial.readByte();
+
+                // Already in connect() mode ???
+                if( echo == 'M' )
+                    break;
+
+                ok = _serial.readByte();
+            }
+            catch(const ReadTimeout&) {
+                LOG_INFO("Retry " << (retry+1) << "/10...");
+            }
+
+            if( echo != 0xBB || ok != 0x4D ) {  // DOWNLOAD modes only.
+                if( retry < 9 )
+                {
+                    _serial.purge();
+                    _serial.sleep(400);
+                    continue;
+                }
+                LOG_THROW("Unable to enter hwOS service mode");
+                return false;
+            }
+            break;
+        }
+        getIdentity();
+
+        QString banner = Log::applicationName();
+        writeText(banner);
+        LOG_TRACE("Connected.");
+        _connectMode = DOWNLOAD_MODE;
+        return true;
+    }
+    catch(const Exception& e) {
+        disconnect();
+        LOG_THROW("Port " << Settings::port << ": " << e.what());
+    }
+    return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+bool OSTC3Operations::disconnect(bool /*closing*/)
+{
+    if( _connectMode == CLOSED_MODE ) return false;
+
+    descriptionString.clear();  // cleanup for interface updateStatus()
+    _connectMode = CLOSED_MODE;
+
+    _serial.purge();
+    _serial.writeByte(0xFF);    // Exit communications, just in case...
+    _serial.sleep(100);
+
+    _serial.purge();
+    _serial.close();
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::getIdentity()
+{
+    descriptionString.clear();
+
+    LOG_TRACE("Getting model...");
+    HardwareDescriptor hw = hardwareDescriptor();
+ //   if( hw != HW_OSTC3 )
+ //        LOG_THROW("Not an OSTC3.");
+
+    LOG_TRACE("Getting identity...");
+    getCommonIdentity();
+
+    LOG_INFO("Found " << descriptionString.trimmed());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::getCommonIdentity()
+{
+    unsigned char echo = retryCommand(_serial, 'i'); // 0x69
+    if( echo != 'i' )
+        LOG_THROW("Bad identity reply (1)");
+
+    unsigned char header[4 + 60 + 1] = {0};
+    _serial.readBlock(header, sizeof header);
+    if( header[64] != 0x4D )    // DOWNLOAD modes only.
+        LOG_THROW("Bad identity reply (2)");
+
+    _computerSerial   = header[0] + header[1]*256;
+    _computerFirmware = header[2] * 100 + header[3];
+    memcpy(computerText, header+4, sizeof computerText);
+
+    descriptionString = QString("%1 #%2, fw %3.%4, %5")
+            .arg(model())
+            .arg(serialNumber(), 4, 10, QChar('0'))
+            .arg(firmware() / 100).arg(firmware() % 100, 2, 10, QChar('0'))
+            .arg( QString::fromLatin1((char*)(computerText), 60)
+                .replace(QChar('\0'), " ") );
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+int OSTC3Operations::firmware() const
+{
+    return _computerFirmware;
+}
+
+int OSTC3Operations::serialNumber() const
+{
+    return _computerSerial;
+}
+
+QString OSTC3Operations::customText() const
+{
+    return QString::fromLatin1(computerText, sizeof computerText);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QSize OSTC3Operations::nameSize() const
+{
+    return QSize(12, 5);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::writeText(const QString& msg)
+{
+    QByteArray buffer = msg.leftJustified(16, ' ', true).toLatin1();
+    LOG_TRACE("Echoing string '" << buffer << "'");
+
+    // 2014-10-27 jDG: On OSTC3 v1.60, after an ERASE AREA, we do get
+    //                 a spurious L here (instead of n)...
+    unsigned char echo = retryCommand(_serial, 'n'); // 0x6E Echo string.
+    if( echo != 'n' )
+        LOG_THROW("Bad message reply (1)");
+
+    _serial.writeBlock((const unsigned char*)buffer.data(), 16);
+    _serial.sleep(25);  // Allow 25msec to display the message...
+
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4C && ok != 0x4D )      // DOWNLOAD or SERVICE modes.
+        LOG_THROW("Bad message reply (2)");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::setDate(const QDateTime &date)
+{
+    LOG_TRACE("Set Date " << date.toString("MM/dd/yyyy hh:mm"));
+
+    unsigned char buffer[6];
+    buffer[0] = date.time().hour();
+    buffer[1] = date.time().minute();
+    buffer[2] = date.time().second();
+    buffer[3] = date.date().month();
+    buffer[4] = date.date().day();
+    buffer[5] = date.date().year() % 100;
+
+    unsigned char echo = retryCommand(_serial, 'b'); // 0x62 Sync date
+    if( echo != 'b' )
+        LOG_THROW("Bad clock reply (1)");
+
+    _serial.writeBlock(buffer, sizeof buffer);
+    _serial.sleep(5);
+
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4D )    // DOWNLOAD mode only.
+        LOG_THROW("Bad clock reply (2)");
+
+    writeText( "Set " + date.toString("MM/dd hh:mm") );
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::setName(const QString &newName)
+{
+    LOG_TRACE("Set Name '" << newName << "'");
+
+    char buffer[60];
+    memset(buffer, 0, sizeof buffer);
+    strncpy(buffer, newName.toLatin1().constData(), sizeof buffer);
+
+    unsigned char echo = retryCommand(_serial, 'c'); // 0x63 Send custom text
+    if( echo != 'c' )
+        LOG_THROW("Bad text reply (1)");
+
+    _serial.writeBlock((unsigned char*)buffer, sizeof buffer);
+    _serial.sleep(5);
+
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4D )    // DOWNLOAD modes only.
+        LOG_THROW("Bad text reply (2)");
+
+    getIdentity();
+    // Echo the first line of customtext:
+    writeText(newName.left(12).trimmed());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::setIcons(const QString &)
+{
+    LOG_THROW("Set icons: Not yet implemented");
+}
+
+QImage OSTC3Operations::dumpScreen() const
+{
+    LOG_THROW("Dump screen: Not yet implemented");
+    return QImage();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+static unsigned char ostc3SecretKey[16] = {
+    241,233, 176, 48,
+     69,111, 190, 85,
+    255,231, 248, 49,
+     19,108, 242,254
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::eraseRange(unsigned int addr, unsigned int size)
+{
+    // Convert size to number of pages, rounded up.
+    size = ((size + 4095) / 4096);
+
+    // Erase just the needed pages.
+    unsigned char buffer[4];
+    buffer[0] = UPPER(addr);
+    buffer[1] = HIGH(addr);
+    buffer[2] = LOW(addr);
+    buffer[3] = LOW(size);
+
+    unsigned char reply = retryCommand(_serial, 'B');    // Command 'B'
+    if( reply != 0x42 )
+        LOG_THROW("eraseRange (1)");
+
+    _serial.writeBlock(buffer, 4);
+
+    // Wait (120/4)ms by block of 4K, plus 3% VAT to be sure.
+    _serial.sleep(40 + size * 31);
+
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4c )                    // SERVICE MODE acknowledge.
+        LOG_THROW("eraseRange (2)");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::writeBlock(unsigned int addr,
+                                 const unsigned char *data,
+                                 unsigned int size)
+{
+    unsigned char buffer[3];
+    buffer[0] = UPPER(addr);
+    buffer[1] = HIGH(addr);
+    buffer[2] = LOW(addr);
+
+    unsigned char reply = retryCommand(_serial, '0');    // 0x30
+    if( reply != '0' )
+        LOG_THROW("startWrite");
+
+    _serial.writeBlock(buffer, sizeof buffer);
+    _serial.sleep(2);
+
+    _serial.writeBlock(data, size);
+    // Approximated EEPROM write time some 1sec timeout ??
+    // 1KB = 240 + 1000 = 1240 : Ok.
+    // 4KB = 1.1sec            : Ok.
+    _serial.sleep(1100);
+
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4c && ok != 0x4d )      // DOWNLOAD or SERVICE modes.
+        LOG_THROW("stopWrite");
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::readBlock(unsigned int addr, unsigned char* ptr, unsigned int size)
+{
+    unsigned char buffer[6];
+    buffer[0] = UPPER(addr);
+    buffer[1] = HIGH(addr);
+    buffer[2] = LOW(addr);
+    buffer[3] = UPPER(size);
+    buffer[4] = HIGH(size);
+    buffer[5] = LOW(size);
+
+    unsigned char reply = retryCommand(_serial, 0x20);   // Command ' '
+    if( reply != 0x20 )
+        LOG_THROW("readBytes");
+
+    _serial.writeBlock(buffer, sizeof buffer);
+    _serial.sleep(500);         // Allow some time to start emitting...
+    _serial.readBlock(ptr, size);
+
+    // Note: in that case, the OK byte is send AFTER the data block.
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4C )                    // SERVICE modes only.
+        LOG_THROW("readBytes (2)");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::upgradeFirmware(unsigned int checksum)
+{
+    unsigned char buffer[5];
+    buffer[0] = LOW(checksum);
+    buffer[1] = HIGH(checksum);
+    buffer[2] = UPPER(checksum);
+    buffer[3] = UP32(checksum);
+
+    // Compute magic checksum's checksum.
+    buffer[4] = 0x55;
+    buffer[4] ^= buffer[0]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+    buffer[4] ^= buffer[1]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+    buffer[4] ^= buffer[2]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+    buffer[4] ^= buffer[3]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+
+    unsigned char reply = retryCommand(_serial, 0x50);    // 'P' : send FW to bootloader
+    if( reply != 0x50 )
+        LOG_THROW("Flashing start (1)");
+
+    _serial.writeBlock(buffer, sizeof buffer);
+   unsigned char ok = _serial.readByte();
+    if( ok != 0x4C )                    // SERVICE modes only.
+        LOG_THROW("Flashing start (2)");
+
+    // NOTE: the device never return, because it always do a reset,
+    //       with ot without reprogramming...
+    _serial.sleep(500);
+    _serial.close();
+    descriptionString.clear();
+}
+
+QString OSTC3Operations::firmwareTemplate() const
+{
+    return "*_firmware.hex";
+}
+
+#if 0
+QRegExp OSTC3Operations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty.usbserial-.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, redhat, CentOS and SUSE (google dixit)
+    return QRegExp("ttyUSB.*", Qt::CaseSensitive);
+#elif defined(Q_OS_WIN)
+    return QRegExp("COM.*", Qt::CaseSensitive);
+#endif
+}
+#endif
+
+QRegularExpression OSTC3Operations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegularExpression("tty.usbserial-.*",
+                              QRegularExpression::CaseInsensitiveOption);
+#elif defined(Q_OS_LINUX)
+    // Debian, Ubuntu, RedHat, CentOS, SUSE
+    return QRegularExpression("ttyUSB.*"); // default: case-sensitive
+#elif defined(Q_OS_WIN)
+    return QRegularExpression("COM.*");    // default: case-sensitive
+#endif
+}
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::connectServiceMode()
+{
+    LOG_TRACE( "Enter service mode..." );
+
+    // NOTE: Service mode requires a special starting sequence, different from
+    //       the usual download mode state.
+    //       Also, the general acknowledge byte is changed to 0x4c 'L'.
+
+    assert( _connectMode != SERVICE_MODE );
+    _serial.open( Settings::port, emulatorName);
+    _serial.sleep(333); // Initial 1/3 sec before trying service mode...
+
+    for(int retry=0; retry < 10; ++retry)
+    {
+        unsigned char serviceMode[] = { 0xAA, 0xAB, 0xCD, 0xEF };
+
+        try {
+            _serial.writeBlock(serviceMode, sizeof serviceMode);
+        }
+        catch(const WriteTimeout&) {
+            // Bluetooth not present: one can open the pseudo COM port,
+            // but we will have a timeout on first write byte...
+            if( retry < 9 ) {
+                LOG_INFO("Cannot connect to " << Settings::port <<" (" << (retry+1) << "/10)...");
+                _serial.sleep(1000);
+                continue;
+            }
+            LOG_THROW("Cannot connect to " << model() << ".");
+            return;
+        }
+
+        // Allow for 0.1sec extra delay
+        _serial.sleep(100);
+
+        //---- Check acknowledge:
+        unsigned char echo = 0;
+        try {
+            echo = _serial.readByte();
+        }
+        catch(...) {}
+
+        if( echo != 0x4b ) {
+serviceModeFailed:
+            if( retry < 9 )
+            {
+                _serial.purge();
+                _serial.sleep(400);
+                continue;
+            }
+            LOG_THROW("Unable to enter " << model() <<" service mode");
+        }
+
+        echo = _serial.readByte();
+        if( echo != 0xAB )
+            goto serviceModeFailed;
+        echo = _serial.readByte();
+        if( echo != 0xCD )
+            goto serviceModeFailed;
+        echo = _serial.readByte();
+        if( echo != 0xEF )
+            goto serviceModeFailed;
+        echo = _serial.readByte();
+        if( echo != 0x4c )              // SERVICE modes only.
+            goto serviceModeFailed;
+        break;
+    }
+    _connectMode = SERVICE_MODE;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3Operations::upgradeFW(const QString &fileName)
+{
+    //---- Load and check firmware -------------------------------------------
+    LOG_INFO("Loading firmware.");
+    HexFile hex;
+    loadFirmware(hex, fileName);
+
+    //---- Enter Service Mode ------------------------------------------------
+    connectServiceMode();
+
+    //---- Erase old Firmware ------------------------------------------------
+    PROGRESS(0, FIRMWARE_SIZE);
+    LOG_INFO("Erasing Firmware.");
+
+    writeText(" Erasing FW...");
+    eraseRange(FIRMWARE_AREA, FIRMWARE_SIZE);
+
+    //---- Upload Firmware ---------------------------------------------------
+    LOG_INFO("Uploading firmware.");
+    writeText(" Uploading...");
+
+    for(int len = 0x00000; len < FIRMWARE_SIZE; len += FIRMWARE_BLOCK)
+    {
+        unsigned char percent = int(len * 100.0f / FIRMWARE_SIZE + 0.5f);
+        writeText( QString(" Uploading %1%")
+                       .arg(percent, 2) );
+        PROGRESS(percent, 100);
+
+        writeBlock(FIRMWARE_AREA+len, hex.data()+len, FIRMWARE_BLOCK);
+    }
+    PROGRESS(100, 100);
+
+    //---- Verify firmware ---------------------------------------------------
+    LOG_INFO("Verify firmware.");
+    writeText(" Verifying...");
+    {
+        unsigned char* buffer = new unsigned char[FIRMWARE_SIZE];
+        Q_CHECK_PTR(buffer);
+
+        for(int len = 0x00000; len < FIRMWARE_SIZE; len += FIRMWARE_BLOCK)
+        {
+            unsigned char percent = int(len * 100.0f / FIRMWARE_SIZE + 0.5f);
+            writeText( QString(" Verifying %1%")
+                        .arg(percent, 2) );
+            PROGRESS(percent, 100);
+
+            readBlock(FIRMWARE_AREA+len, buffer+len, FIRMWARE_BLOCK);
+        }
+        PROGRESS(100, 100);
+        qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 10);
+
+        for(int i=0; i<FIRMWARE_SIZE; ++i)
+            if( buffer[i] != hex.data()[i] )
+            {
+                writeText(" Verify FAILED");
+                LOG_THROW("readback is different");
+            }
+
+        delete[] buffer;
+    }
+    PROGRESS_THROTTLE();
+
+    //---- Flashing firmware -------------------------------------------------
+    LOG_INFO("Programming.");
+    writeText(" Programming...");
+    upgradeFirmware( hex.checksum() );
+
+    //---- Done --------------------------------------------------------------
+    // Low-level close, to avoid trying to send a 0xFF byte...
+    _serial.close();
+    _connectMode = CLOSED_MODE;
+    LOG_INFO("Upgrade done.");
+    PROGRESS_RESET();
+}
+
+void OSTC3Operations::loadFirmware(HexFile& hex, const QString& fileName) const
+{
+    hex.allocate(FIRMWARE_SIZE);
+    hex.loadEncrypted(fileName, ostc3SecretKey);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTC3Operations::model() const
+{
+    return "OSTC hwOS (USB)";
+}
+
+QString OSTC3Operations::description()
+{
+    return descriptionString;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+HardwareOperations::CompanionFeatures OSTC3Operations::supported() const
+{
+    // No ICON, no DUMPSCREEN, no VPM
+    return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
+                     |HELIUM_DIVE|CCR_DIVE);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+void OSTC3Operations::getSignal()
+{
+    return;
+}
+void OSTC3Operations::getAllHeader(unsigned char* pBuffer)
+{
+    return;
+}
+void OSTC3Operations::writeAllHeader(unsigned char* pBuffer)
+{
+    return;
+}
+
+void OSTC3Operations::getAllSamples(unsigned char* pBuffer)
+{
+    return;
+}
+void OSTC3Operations::writeAllSamples(unsigned char* pBuffer)
+{
+    return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC3Operations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,193 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC3Operations.h
+/// \brief  Implementing various operations for H&W OSTC3 dive computer
+/// \author JD Gascuel.
+/// \sa     HardwareOperations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-03-17 : [jDG] Creation.
+//  2014-07-07 : [jDG] Cleanups for Subsurface google-summer-of-code.
+//  2014-07-25 : [jDG] BSD 2-clause license.
+
+#ifndef OSTC3OPERATIONS_H
+#define OSTC3OPERATIONS_H
+
+#include "HardwareOperations.h"
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief  Implementing various low-level operations for OSTC3 dive computer
+///
+/// \sa OSTCSportOperations, OSTC2Operations, OSTC3pOperations,
+///     OSTC4Operations, OSTCcROperations.
+class  OSTC3Operations
+  : public HardwareOperations
+{
+    //////////////////////////////////////////////////////////////////////////
+    /// \{ \section Configuration management.
+
+   // QRegExp portTemplate() const override;
+    QRegularExpression portTemplate() const override;
+    QStringList listPorts() const override;
+    QString model() const override;
+    QString description() override;
+    CompanionFeatures supported() const override;
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+protected:
+
+    //------------------------------------------------------------------------
+    /// \brief Erase OSTC3 PROM memory.
+    /// PROM memory should be erased beforeprogramming the new firmware.
+    /// This command is used to erase a set of 4KB pages.
+    /// \param[in] addr: First address (24bits) to erase. Should be a multiple of 4096.
+    /// \param[in] size: Number of bytes to erase. Will be rounded (up) to a multiple of 4096.
+    /// \throws if something goes wrong.
+    void eraseRange(unsigned int addr, unsigned int size);
+
+    //------------------------------------------------------------------------
+    /// \brief Write a big block of bytes to OSTC3 RAM memory.
+    ///
+    /// \param[in] addr: First address (24bits) to write to.
+    /// \param[in] data: Bytes to write.
+    /// \param[in] size: Number of bytes to write.
+    /// \throws if something goes wrong.
+    void writeBlock(unsigned int addr,
+                    const unsigned char *data, unsigned int size);
+
+    //------------------------------------------------------------------------
+    /// \brief Read-back a big block of bytes from OSTC3 RAM memory.
+    ///
+    /// \param[in] addr: First address (24bits) to read from.
+    /// \param[in] ptr : Where to store bytes read.
+    /// \param[in] size: Number of bytes to read.
+    /// \throws if something goes wrong.
+    void readBlock (unsigned int addr, unsigned char *ptr, unsigned int size);
+
+    //------------------------------------------------------------------------
+    /// \brief Burn firmare.
+    ///
+    /// Firmware should be first uploaded to RAM memory (\sa writeBlock() ),
+    /// in area 0x3E0000 .. 0x3FE000,
+    /// then the firmware command will tell the OSTC3 to use that to reprogramm
+    /// itself.
+    /// A validation checksum is done to make sure a valid data have been
+    /// uploaded.
+    ///
+    /// \param[in] checksum: Adler32 checksum of the new firmware.
+    /// \throws if something goes wrong.
+    void upgradeFirmware(unsigned int checksum);
+
+public:
+    OSTC3Operations();
+    ~OSTC3Operations();
+
+    /// \brief The fw version found during the last getIdentty().
+    int firmware() const override;
+
+    /// \brief The serial number found during the last getIdentty().
+    int serialNumber() const override;
+
+    /// \brief The user-defined string found during the last getIdentty().
+    QString customText() const override;
+
+    //////////////////////////////////////////////////////////////////////////
+    /// \{ \section OSTC3 low-level service mode commands.
+    ///
+    /// Low level commands are used to directly speak to the OSTC3 firmware.
+    /// They all throw an Exception if some error occurs.
+
+    //------------------------------------------------------------------------
+    /// \brief Custom text size (lines and columns).
+    QSize nameSize() const override;
+
+    //------------------------------------------------------------------------
+    /// \brief Read OSTC3 computer firmware, serial and custom text.
+    /// Everything is formated for the description() string.
+    /// \throws if something goes wrong.
+    void getIdentity() override;
+
+    //------------------------------------------------------------------------
+    /// \brief Display a short text on OSTC3 while on service mode.
+    /// OSTC3 can handle 16 chars. So the string is automatically
+    /// padded with spaces to clean any leftover.
+    /// \throws if something goes wrong.
+    void writeText(const QString &msg) override;
+
+    QString firmwareTemplate() const override;
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+    /// \{ \section OSTC3 high-level commands.
+
+    bool connect() override;
+    bool disconnect(bool closing = false) override;
+    void setDate(const QDateTime& date) override;
+    void setName(const QString& newName) override;
+    void getSignal() override;
+    void getAllHeader(unsigned char* pBuffer) override;
+    void writeAllHeader(unsigned char* pBuffer) override;
+    void getAllSamples(unsigned char* pBuffer) override;
+    void writeAllSamples(unsigned char* pBuffer) override;
+    void setIcons(const QString& fileName) override;
+    QImage dumpScreen() const override;
+    void upgradeFW(const QString& fileName) override;
+
+    void loadFirmware(HexFile& hex, const QString& fileName) const override;
+
+    /// \}
+
+    //////////////////////////////////////////////////////////////////////////
+protected:
+    QString     descriptionString;
+    QString     emulatorName;
+    char        computerText[60];
+
+    virtual void getCommonIdentity();
+
+    void connectServiceMode() override;
+
+    //////////////////////////////////////////////////////////////////////////
+private:
+    unsigned short _computerFirmware;
+    unsigned short _computerSerial;
+
+protected:
+    enum Mode {
+        CLOSED_MODE = 0,        ///< Not yet open.
+        DOWNLOAD_MODE,          ///< Open in normal mode.
+        SERVICE_MODE            ///< Open in FIRMWARE UPGRADE mode.
+    } _connectMode;
+};
+
+#endif // OSTC3OPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC3pOperations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,126 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC3pOperations.h
+/// \brief  Implementing various operations for new H&W OSTC3 (BLE) dive computer
+/// \author JD Gascuel.
+/// \sa     OSTC3Operations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "OSTC3pOperations.h"
+#include "Utils/Log.h"
+#include <QRegularExpression>
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTC3pOperations::OSTC3pOperations()
+{
+    emulatorName = "OSTC3p";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QStringList OSTC3pOperations::listPorts() const
+{
+    return listBluetoothPorts();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC3pOperations::getIdentity()
+{
+    descriptionString.clear();
+
+    LOG_TRACE("Getting model...");
+    HardwareDescriptor hw = hardwareDescriptor();
+
+//    if( hw != HW_OSTC3p_a && hw != HW_OSTC3p_b )
+ //       LOG_THROW("Not an OSTC3+.");
+
+    LOG_TRACE("Getting identity...");
+    getCommonIdentity();
+
+    LOG_TRACE("Found " << descriptionString);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTC3pOperations::model() const
+{
+    return "OSTC hwOS (Bluetooth)";
+}
+
+QString OSTC3pOperations::firmwareTemplate() const
+{
+    return "*_firmware.hex";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+HardwareOperations::CompanionFeatures OSTC3pOperations::supported() const
+{
+    // No ICON, no DUMPSCREEN, no VPM
+    return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
+                     |HELIUM_DIVE|CCR_DIVE|BLUETOOTH);
+}
+#if 0
+QRegExp OSTC3pOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty.OSTC[0-9][0-9]+.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, redhat, CentOS and SUSE (google dixit)
+    return QRegExp("ttyUSB.*", Qt::CaseSensitive);
+#elif defined(Q_OS_WIN)
+    return QRegExp("COM.*", Qt::CaseSensitive);
+#endif
+}
+#endif
+
+#include <QRegularExpression>
+
+QRegularExpression OSTC3pOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegularExpression("tty.OSTC[0-9][0-9]+.*",
+                              QRegularExpression::CaseInsensitiveOption);
+#elif defined(Q_OS_LINUX)
+    // Debian, Ubuntu, RedHat, CentOS, SUSE
+    return QRegularExpression("ttyUSB.*"); // default: case-sensitive
+#elif defined(Q_OS_WIN)
+    return QRegularExpression("COM.*");    // default: case-sensitive
+#endif
+}
+
+void OSTC3pOperations::getSignal()
+{
+    return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC3pOperations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,62 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC3pOperations.h
+/// \brief  Implementing various operations for new H&W OSTC3 (BLE) dive computer
+/// \author JD Gascuel.
+/// \sa     OSTC3Operations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2015-03-16 : [jDG] Creation.
+
+#ifndef OSTC_3p_OPERATIONS_H
+#define OSTC_3p_OPERATIONS_H
+
+#include "OSTC3Operations.h"
+
+class  OSTC3pOperations
+  : public OSTC3Operations
+{
+    // Re-implemented various stuff
+    QStringList listPorts() const override;
+    HardwareOperations::CompanionFeatures supported() const override;
+    QString firmwareTemplate() const override;
+  //  QRegExp portTemplate() const override;
+    QRegularExpression portTemplate() const override;
+    QString model() const override;
+    void getIdentity() override;
+    void getSignal() override;
+
+public:
+    OSTC3pOperations();
+};
+
+#endif // OSTC_3p_OPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC4Operations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,591 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC4Operations.h
+/// \brief  Implementing various operations for H&W OSTC4 dive computer
+/// \author JD Gascuel.
+/// \sa     OSTC3Operations, HardwareOperations.h
+///
+/// \copyright (c) 2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+#include <QSettings>
+#include <QRegularExpression>
+#include "OSTC4Operations.h"
+
+#include "Utils/Log.h"
+#include "Utils/Exception.h"
+#include "Utils/ProgressEvent.h"
+
+extern QSettings* settings;
+
+#define FIRMWARE_BLOCK            0x40        // 64 bytes
+#define FIRMWARE_BLOCK_DELAY        15        // 15 msec.
+#define FIRMWARE_BLOCK_FAST      0x300        // 4096 bytes
+
+//////////////////////////////////////////////////////////////////////////////
+
+QSize OSTC4Operations::nameSize() const
+{
+    return QSize(12, 4);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTC4Operations::model() const
+{
+    return "OSTC4";
+}
+
+HardwareOperations::CompanionFeatures OSTC4Operations::supported() const
+{
+    // No ICON, no DUMPSCREEN
+    return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
+                     |HELIUM_DIVE|CCR_DIVE|BLUETOOTH|VPM_MODEL|SIGNAL_CHECK);
+}
+
+QString OSTC4Operations::firmwareTemplate() const
+{
+    return "ostc4*.bin";
+}
+
+QStringList OSTC4Operations::listPorts() const
+{
+    return listBluetoothPorts();
+}
+#if 0
+QRegExp OSTC4Operations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty[.]OSTC4-.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, redhat, CentOS and SUSE (google dixit)
+    return QRegExp("ttyUSB.*", Qt::CaseSensitive);
+#elif defined(Q_OS_WIN)
+    return QRegExp("COM.*", Qt::CaseSensitive);
+#endif
+}
+#endif
+
+
+QRegularExpression OSTC4Operations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegularExpression("tty[.]OSTC4-.*",
+                              QRegularExpression::CaseInsensitiveOption);
+#elif defined(Q_OS_LINUX)
+    // Debian, Ubuntu, RedHat, CentOS, SUSE
+    return QRegularExpression("ttyUSB.*"); // default: case-sensitive
+#elif defined(Q_OS_WIN)
+    return QRegularExpression("COM.*");    // default: case-sensitive
+#endif
+}
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC4Operations::getIdentity()
+{
+    descriptionString.clear();
+
+    LOG_TRACE("Getting model...");
+    HardwareDescriptor hw = hardwareDescriptor();
+    if( hw != HW_OSTC4 )
+        LOG_THROW("Not an OSTC4.");
+
+    LOG_TRACE("Getting identity...");
+    getCommonIdentity();
+
+    //---- Main firmware -----------------------------------------------------
+    QString mainFW;
+    {
+        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
+        if( echo != 'k' )
+            LOG_THROW("Bad firmware details.");
+
+        unsigned char reply[4+1];
+        _serial.writeByte(0xFF);    // Main firmware
+        _serial.readBlock(reply, sizeof reply);
+        if( reply[4] != 0x4D )
+            LOG_THROW("Bad main firmware reply.");
+
+        mainFW = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
+    }
+
+    //---- RTE firmware ------------------------------------------------------
+    QString rteFW;
+    {
+        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
+        if( echo != 'k' )
+            LOG_THROW("Bad firmware details.");
+
+        unsigned char reply[4+1];
+        _serial.writeByte(0xFE);    // RTE firmware
+        _serial.readBlock(reply, sizeof reply);
+        if( reply[4] != 0x4D )
+            LOG_THROW("Bad RTE firmware reply.");
+
+        rteFW = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
+    }
+
+    //---- Font Package ------------------------------------------------------
+    QString fontPKG;
+    {
+        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
+        if( echo != 'k' )
+            LOG_THROW("Bad firmware details.");
+
+        unsigned char reply[4+1];
+        _serial.writeByte(0x10);    // Font Package
+        _serial.readBlock(reply, sizeof reply);
+        if( reply[4] != 0x4D )
+            LOG_THROW("Bad Font package reply.");
+
+        fontPKG = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
+    }
+
+    //---- OSTC4 specific description ----------------------------------------
+    descriptionString = QString("%1 #%2, fw %3 (main %4/RTE %5/Font %6), %7")
+            .arg(model())
+            .arg(serialNumber(), 4, 10, QChar('0'))
+            .arg( QString::asprintf("%d.%02d", firmware() / 100, firmware() % 100))
+            .arg( mainFW )
+            .arg( rteFW )
+            .arg( fontPKG )
+            .arg( QString::fromLatin1((char*)(computerText), 60)
+                .replace(QChar('\0'), "")
+                .trimmed() );
+
+    LOG_INFO("Found " << descriptionString);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTC4Operations::OSTC4Operations()
+  : fileChecksum(0,0)
+{
+    emulatorName = "OSTC4";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC4Operations::upgradeFW(const QString &fileName)
+{
+    LOG_INFO("Starting OSTC4 firmware upgrade...");
+
+    openFirmware(fileName, true);
+    openFirmware(fileName, false);
+
+    LOG_INFO("Please wait for OSTC4 to complete installation...");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+///     FIRMWARE UPGRADE                                                   ///
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+
+#define SWAP4BYTES(x)  \
+    uint( (((x) & 0x000000FF) << 24)  \
+        | (((x) & 0x0000FF00) <<  8)  \
+        | (((x) & 0x00FF0000) >>  8)  \
+        | (((x) & 0xFF000000) >> 24))
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC4Operations::openFirmware(const QString &fileName, bool dryRun)
+{
+    LOG_TRACE("Opening OSTC4 firmware '" << fileName << "'...");
+
+    bool forceFirmwareUpdate;
+    bool forceRTEUpdate;
+    bool forceFontlibUpdate;
+    bool useFastMode;
+    bool firmwareSupportFastMode = false;
+    int size;
+    int blockSize;
+    int transferDelay;
+
+    // Previous trial failed ?
+    if( _file.isOpen() ) _file.close();
+
+    _file.setFileName(fileName);
+    if( ! _file.open(QIODevice::ReadOnly) )
+        LOG_THROW( "Cannot open BIN file " << fileName );
+
+
+    //  Get settings to overwrite version check
+    forceFirmwareUpdate = settings->value("OSTC/forceFirmwareUpdate", false).toBool();
+    forceRTEUpdate = settings->value("OSTC/forceRTEUpdate", false).toBool();
+    forceFontlibUpdate = settings->value("OSTC/forceFontlibUpdate", false).toBool();
+
+    useFastMode = settings->value("OSTC/useFastMode", false).toBool();
+
+    //---- Check consistency -------------------------------------------------
+    fileChecksum.reset(0, 0);
+    for(int part=1; part<4; ++part)
+    {
+        // Less than 3 parts ?
+        if( (_file.size() - _file.pos()) < 16 )
+            break;
+
+        if( dryRun )
+            LOG_TRACE("Checking part " << part <<"...");
+
+        FirmwareOSTC4 header;
+        QByteArray bin;
+        loadFirmwarePart(header, bin, part, dryRun);
+
+        QString name = QString::asprintf("%s v%d.%02d.%02d%s",
+             header.type==0x10 ? "Font"
+           : header.type==0xFF ? "FW  "
+           : header.type==0xFE ? "RTE "
+           :                     "??? ",
+            header.version.x, header.version.y, header.version.z,
+            header.version.beta ? " beta" : "");
+
+        //---- On first pass, just check file structure ----------------------
+        if( dryRun )
+            continue;
+
+        //---- On second pass, do upload new stuff ---------------------------
+        if( _connectMode != SERVICE_MODE )
+            connectServiceMode();
+        if( _connectMode != SERVICE_MODE  )
+            LOG_THROW("Cannot connect OSTC4 service mode...");
+
+        //---- Check if needed ? ---------------------------------------------
+        LOG_TRACE("Check part " << part << " is needed ?");
+        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
+        if( echo != 'k' )
+            LOG_THROW("Bad firmware details.");
+
+        unsigned char reply[4+1];
+        _serial.writeByte(header.type);     // type of firmware part
+        _serial.readBlock(reply, sizeof reply);
+        if( reply[4] != 0x4C )              // SERVICE MODE only.
+            LOG_THROW("Bad firmware reply.");
+
+        if(((reply[0] *100 + reply[1] * 10 + reply[2]) > 151 ) && (header.type==0xFF)) /* suitable firmware version? First supported in 1.5.2 */
+        {
+            firmwareSupportFastMode = true;
+        }
+
+        if(( header.version.x == reply[0]
+         && header.version.y == reply[1]
+         && header.version.z == reply[2] )
+                && (!(forceFirmwareUpdate && header.type==0xFF))
+                && (!(forceRTEUpdate && header.type==0xFE))
+                && (!(forceFontlibUpdate && header.type==0x10))
+                )
+        {
+            LOG_INFO("    part " << part << ": " << name << " already up-to-date.");
+            continue;
+        }
+        if((useFastMode) && (firmwareSupportFastMode))      /* is fast mode supported ? */
+        {
+            blockSize = FIRMWARE_BLOCK_FAST;
+            transferDelay = 1;
+        }
+        else {
+            blockSize = FIRMWARE_BLOCK;
+            transferDelay = FIRMWARE_BLOCK_DELAY;
+        }
+        //---- Upload firwmare -----------------------------------------------
+        const int steps = 3 + (bin.size()+blockSize-1) / blockSize;
+        int step = 0;
+        PROGRESS(step++, steps);
+
+        LOG_INFO("    uploading part " << part
+                 << ": " << name
+                 << "...");
+        writeText( QString("Upload Part %1").arg(part) );
+
+        //---- Command
+        PROGRESS(step++, steps);
+        echo = retryCommand(_serial, 's'); // 0x73 OSTC4 FW Upgrade
+        if( echo != 's' )
+            LOG_THROW("Bad OSTC4 FW upgrade commande.");
+
+        //---- Header
+        PROGRESS(step++, steps);
+        _serial.writeBlock((uchar*)&header, sizeof header);
+         _serial.sleep(500);
+        //---- Data
+        for(int len = 0x00000; len < bin.size(); len += blockSize)
+        {
+            if(transferDelay)
+            {
+                _serial.sleep(transferDelay);  // 15 msec between 64B blocks...
+            }
+            PROGRESS(step++, steps);
+
+            size = qMin(blockSize,
+                            bin.size() - len);
+
+            LOG_TRACE("Fill " << size);
+            _serial.writeBlock((uchar*)bin.data()+len, (unsigned int)size);
+        }
+
+        PROGRESS(steps, steps); // 100%
+
+        //---- Wait acknowledge ----------------------------------------------
+        // RTE seems to miss the acknowledge byte ...
+        if( header.type != 0xFE ) {
+            PROGRESS_THROTTLE();
+
+            for(int wait=0; wait<2*60; ++wait)  // Up to 2 min...
+            {
+                try {
+                    unsigned char reply = _serial.readByte();
+                    if( reply == 0x4C )
+                        break;
+                }
+                catch( const ReadTimeout&) {}
+            }
+        }
+
+        LOG_INFO("    part " << part << ": " << name << " uploaded.");
+    }
+
+    //---- Done --------------------------------------------------------------
+    // Low-level close, to avoid trying to send a 0xFF byte...
+    if( !dryRun ) {
+        _serial.close();
+        _connectMode = CLOSED_MODE;
+    }
+
+    PROGRESS_RESET();
+
+    //---- Check FILE checksum on first pass ---------------------------------
+    if( dryRun ) {
+        uint expected;
+        if( 4 != _file.read((char*)&expected, sizeof expected) )
+            LOG_THROW("Missing file checksum.");
+
+        uint checksum = fileChecksum.a() | (fileChecksum.b() << 16);
+
+        if( checksum != expected )
+            LOG_ERROR( QString::asprintf("File checksum ERROR: expected = %04X, actual = %04X", expected, checksum) );
+        else if( dryRun )
+            LOG_TRACE( QString::asprintf("File checksum = %04X (OK)", checksum) );
+
+        if( ! _file.atEnd() )
+            LOG_THROW("Extra data after last block.");
+    }
+
+    _file.close();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTC4Operations::loadFirmwarePart(FirmwareOSTC4& header,
+                                       QByteArray& bin,
+                                       int part,
+                                       bool dryRun)
+{
+    memset(&header, 0, sizeof header);
+
+    //---- Read part header and check consistency ----------------------------
+    if( sizeof header != _file.read((char*)&header, sizeof header) )
+        LOG_THROW("EOF in header.");
+    fileChecksum.add(&header, sizeof header);
+
+    uint checksum = SWAP4BYTES(header.length)
+                  + SWAP4BYTES(*(uint*)&header.type);
+    if( checksum != header.checksum )
+        LOG_THROW( QString::asprintf("Inconsistent header (%04X != %04X).", checksum, header.checksum) );
+
+    if( SWAP4BYTES(header.length) > (1024 * 1024) || (SWAP4BYTES(header.length) % 4) )
+        LOG_THROW("Inconsistent header (part=" << part << ", size=" << SWAP4BYTES(header.length) << ").");
+
+    if( dryRun )
+        switch( header.type ) {
+        case 0x10:  LOG_TRACE("... font");       break;
+        case 0xFF:  LOG_TRACE("... firmware");   break;
+        case 0xFE:  LOG_TRACE("... RTE");        break;
+        default:
+    //        LOG_THROW("Inconsistent header (part=" << part << ", type=0x" << QString::asprintf(int(header.type), 16) << ").");
+            LOG_THROW("Inconsistent header (part=" << part << ", type=0x" << QString::asprintf("Value = %d", header.type) << ").");
+            break;
+        }
+
+    if( dryRun )
+        LOG_TRACE("... version " << QString::asprintf("%d.%02d.%02d.%s",
+            header.version.x, header.version.y, header.version.z,
+            header.version.beta ? " beta" : ""));
+
+    //---- Read Binary Data --------------------------------------------------
+    if( dryRun )
+        LOG_TRACE("... size " << SWAP4BYTES(header.length) << " bytes.");
+    bin.resize(SWAP4BYTES(header.length) + 4);
+    assert((uint)bin.size() == SWAP4BYTES(header.length) + 4);
+
+    if( bin.size() != _file.read(bin.data(), bin.size()) )
+        LOG_THROW("EOF in data.");
+
+    if( dryRun )
+        LOG_TRACE("... checksum " << QString::asprintf("%08x",
+            *(uint*)(bin.data() + SWAP4BYTES(header.length))));
+    fileChecksum.add(bin);
+}
+
+void OSTC4Operations::getSignal()
+{
+    char buffer[60];
+    memset(buffer, 0, sizeof buffer);
+    unsigned char echo = retryCommand(_serial, 'l'); // 0x6c request bluetooth signal
+    if( echo != 0x6C )
+        LOG_THROW("Bad text reply (1)" << echo);
+
+    _serial.sleep(1000);
+
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4D )    // DOWNLOAD mode only.
+        LOG_THROW("Bad clock reply (2)");
+}
+
+
+void OSTC4Operations::getAllHeader(unsigned char* pBuffer)
+{
+    unsigned char index = 0;
+    char buffer[60];
+    memset(buffer, 0, sizeof buffer);
+
+    if( _connectMode != SERVICE_MODE )
+        connectServiceMode();
+    if( _connectMode != SERVICE_MODE  )
+        LOG_THROW("Cannot connect OSTC4 service mode...");
+
+
+    unsigned char echo = retryCommand(_serial, 0x85); // 0x85 request all headers (dump)
+    if( echo != 0x85 )
+        LOG_THROW("Bad text reply (1)" << echo);
+
+    _serial.sleep(1000);
+
+    for(index = 0; index < 8; index++)      // Expect 8 blocks a 0x8000
+    {
+        _serial.readBlock(pBuffer + (index * 0x8000), 0x8000);
+        LOG_INFO(("."));
+    }
+    *(pBuffer + (index * 0x8000)) = _serial.readByte();  // get lastdiveID
+    LOG_INFO(QString::asprintf("%d",*(pBuffer + (index * 0x8000))));
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4C )    //Service mode only.
+        LOG_THROW("Bad clock reply (2)");
+}
+
+void OSTC4Operations::writeAllHeader(unsigned char* pBuffer)
+{
+    unsigned char index = 0;
+    char buffer[60];
+    memset(buffer, 0, sizeof buffer);
+
+    if( _connectMode != SERVICE_MODE )
+        connectServiceMode();
+    if( _connectMode != SERVICE_MODE  )
+        LOG_THROW("Cannot connect OSTC4 service mode...");
+
+
+    unsigned char echo = retryCommand(_serial, 0x86); // 0x86 request all headers (dump)
+    if( echo != 0x86 )
+        LOG_THROW("Bad text reply (1)" << echo);
+
+    _serial.sleep(1000);
+
+    for(index = 0; index < 8; index++)      // Expect 8 blocks a 0x8000
+    {
+        _serial.writeBlock(pBuffer + (index * 0x8000), 0x8000);
+        LOG_INFO(("."));
+    }
+    _serial.writeByte(*(pBuffer + (index * 0x8000)));  // write lastdiveID
+
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4C )    //Service mode only.
+        LOG_THROW("Bad clock reply (2)");
+}
+
+void OSTC4Operations::getAllSamples(unsigned char* pBuffer)
+{
+    unsigned short index = 0;
+    char buffer[60];
+    memset(buffer, 0, sizeof buffer);
+
+    if( _connectMode != SERVICE_MODE )
+        connectServiceMode();
+    if( _connectMode != SERVICE_MODE  )
+        LOG_THROW("Cannot connect OSTC4 service mode...");
+
+
+    unsigned char echo = retryCommand(_serial, 0x88); // 0x88 request all samples (dump)
+    if( echo != 0x88 )
+        LOG_THROW("Bad text reply (1)" << echo);
+
+    _serial.sleep(500);
+
+    for(index = 0; index < 384; index++)      // Expect 8 blocks a 0x8000
+    {
+        _serial.readBlock(pBuffer + (index * 0x8000), 0x8000);
+        LOG_INFO(QString::asprintf("%d",index));
+    }
+    _serial.readBlock(pBuffer + (index * 0x8000), 4);  // get lastdiveID
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4C )    //Service mode only.
+        LOG_THROW("Bad clock reply (2)");
+}
+
+
+void OSTC4Operations::writeAllSamples(unsigned char* pBuffer)
+{
+    unsigned short index = 0;
+    char buffer[60];
+    memset(buffer, 0, sizeof buffer);
+
+    if( _connectMode != SERVICE_MODE )
+        connectServiceMode();
+    if( _connectMode != SERVICE_MODE  )
+        LOG_THROW("Cannot connect OSTC4 service mode...");
+
+
+    unsigned char echo = retryCommand(_serial, 0x89); // 0x88 request all samples (dump)
+    if( echo != 0x89 )
+        LOG_THROW("Bad text reply (1)" << echo);
+
+    _serial.sleep(500);
+
+    for(index = 0; index < 384; index++)      // Expect 8 blocks a 0x8000 384
+    {
+        _serial.writeBlock((const unsigned char*)(pBuffer + (index * 0x8000)), 0x8000);
+        LOG_INFO(QString::asprintf("%d",index));
+    }
+    _serial.writeBlock(pBuffer + (index * 0x8000), 4);  // set lastdiveID
+    unsigned char ok = _serial.readByte();
+    if( ok != 0x4C )    //Service mode only.
+        LOG_THROW("Bad clock reply (2)");
+}
+
+//////////////////////////////////////////////////////////////////////////////
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC4Operations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,107 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC4Operations.h
+/// \brief  Implementing various operations for H&W OSTC4 dive computer
+/// \author JD Gascuel.
+/// \sa     OSTC3Operations, HardwareOperations.h
+///
+/// \copyright (c) 2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2015-09-28  jDG: Creation initial (RadioComm) version.
+//  2016-04-25  jDG: Final H&W version.
+
+#ifndef OSTC4_OPERATIONS_H
+#define OSTC4_OPERATIONS_H
+
+#include "OSTC3Operations.h"
+#include <QRegularExpression>
+
+#include "AES/Adler16.h"
+#include "Export.h"
+#include <QFile>
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief  Implementing various operations for H&W OSTC4 dive computer
+class OSTC4Operations
+  : public OSTC3Operations
+{
+    //------------------------------------------------------------------------
+    /// \brief Custom text size (lines and columns).
+    EXPORT QSize nameSize() const override;
+
+    //------------------------------------------------------------------------
+    /// \brief Read OSTC4 computer firmware, serial and custom text.
+    /// Everything is formated for the description() string.
+    /// \throws if something goes wrong.
+    EXPORT void getIdentity() override;
+
+    EXPORT QString model() const override;
+
+    /// \brief Tells what is supported for a given computer.
+    EXPORT CompanionFeatures supported() const override;
+
+    struct FirmwareOSTC4 {
+        uint    length;
+        uchar   type, dummy5, dummy6, dummy7;
+        uint    checksum;
+        struct {
+            unsigned char x, y, z, beta;
+        }       version;
+    };
+
+    QFile   _file;
+
+    /// \param[in] dryRun: If TRUE, do not upload FW, just check file structure.
+    void openFirmware(const QString& fileName, bool dryRun);
+
+    void loadFirmwarePart(FirmwareOSTC4& header,
+                          QByteArray& bin, int part,
+                          bool dryRun);
+
+    void getSignal() override;
+    void getAllHeader(unsigned char* pBuffer) override;
+    void writeAllHeader(unsigned char* pBuffer) override;
+    void getAllSamples(unsigned char* pBuffer) override;
+    void writeAllSamples(unsigned char* pBuffer) override;
+
+    EXPORT QString firmwareTemplate() const override;
+   // EXPORT QRegExp portTemplate() const override;
+    EXPORT QRegularExpression portTemplate() const override;
+    EXPORT QStringList listPorts() const override;
+    EXPORT void upgradeFW(const QString &fileName) override;
+
+    Adler32 fileChecksum;
+
+public:
+    EXPORT OSTC4Operations();
+};
+
+#endif // OSTC4_OPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTCFrogOperations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,578 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTCFrogOperations.cpp
+/// \brief  Implementing various operations for H&W Frog dive computer
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "OSTCFrogOperations.h"
+
+#include "Utils/Log.h"
+#include "Utils/Exception.h"
+
+#include "HexFile.h"
+#include "SettingsDialog.h"
+
+#include <QApplication>
+#include <QDateTime>
+#include <QProgressBar>
+#include <QStringList>
+
+// Byte extration, compatible littleendian or bigendian.
+#define LOW(x)      ((unsigned char)((x) % 256))
+#define HIGH(x)     ((unsigned char)((x / (1<<8)) % 256))
+#define UPPER(x)    ((unsigned char)((x / (1<<16)) % 256))
+#define UP32(x)     ((unsigned char)((x / (1<<24)) % 256))
+
+#define IMAGE_ROUNDING  1024
+#define FIRMWARE_AREA   0x3E0000
+#define FIRMWARE_SIZE   0x01D000
+
+extern QProgressBar* progress;
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTCFrogOperations::OSTCFrogOperations()
+  : _firmware(0),
+    _serialNumber(0),
+    _isOpen(false),
+    _commandMode(false)
+{
+}
+
+OSTCFrogOperations::~OSTCFrogOperations()
+{
+    if( _isOpen )
+        disconnect(true);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+///                                                                        ///
+///     PORT management                                                    ///
+///                                                                        ///
+//////////////////////////////////////////////////////////////////////////////
+
+//QRegExp OSTCFrogOperations::portTemplate() const
+QRegularExpression OSTCFrogOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty[.]frog.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, and SUSE (google dixit).
+    // Obviously, needs the rfcomm package. "hcitool scan" or lsusb to have
+    // a list of connected stuff...
+    return QRegExp("rfcomm.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_WIN)
+//    return QRegExp("COM.*", Qt::CaseSensitive);
+    return QRegularExpression(
+        "COM([0-9]+)",
+        QRegularExpression::CaseInsensitiveOption
+        );
+#endif
+}
+
+QStringList OSTCFrogOperations::listPorts() const
+{
+    return listBluetoothPorts();
+}
+
+QString OSTCFrogOperations::firmwareTemplate() const
+{
+    return "*frog.firmware.hex";
+}
+
+QString OSTCFrogOperations::model() const
+{
+    return "Frog";
+}
+
+QString OSTCFrogOperations::description()
+{
+    return _description;
+}
+
+QImage OSTCFrogOperations::dumpScreen() const
+{
+    LOG_THROW("Not implemented...");
+    return QImage();
+}
+
+HardwareOperations::CompanionFeatures OSTCFrogOperations::supported() const
+{
+    // No ICON, no PARAMETER, no FIRMWARE, no DUMPSCREEN yet...
+    return CompanionFeatures(NAME|DATE);
+}
+
+bool OSTCFrogOperations::connect()
+{
+    try {
+        //---- Open the serial port-------------------------------------------
+        LOG_TRACE( QString("Open port %1...").arg(Settings::port) );
+        _isOpen = false;
+
+        //---- Execute the start protocol ------------------------------------
+        static char animation[] = "/-\\|";
+
+        int reply = 0;
+        for(int retry=0; retry < 30; ++retry) {
+            _serial.open( Settings::port, "Frog");
+            _serial.sleep(100);
+            _serial.purge();
+            _serial.writeByte(0xBB);
+            try {
+                reply = _serial.readByte();
+                if( reply == 0x4D )
+                    break;
+            }
+            catch(...) {}
+            LOG_TRACE( QString("Starting... %1").arg(animation[retry % sizeof animation]));
+        }
+        if( reply != 0x4D )
+            LOG_THROW("Not started");
+
+        //---- Enquire about Frog id -----------------------------------------
+        getIdentity();
+
+        //---- Everything is ok ----------------------------------------------
+        _isOpen = true;
+        return true;
+    }
+    catch(const Exception& e) {
+        _serial.close();
+        LOG_THROW("Cannot connect " << Settings::port << ": " << e.what());
+    }
+    _isOpen = false;
+    return false;
+}
+
+void OSTCFrogOperations::connectServiceMode()
+{
+    connect();
+}
+
+bool OSTCFrogOperations::disconnect(bool /*closing*/)
+{
+    if( !_isOpen ) return false;
+
+    _serial.purge();
+    _serial.writeByte(0xFF);
+    _serial.sleep(100);
+
+    _serial.purge();
+    _serial.close();
+
+    _description.clear();       // cleanup for interface updateStatus()
+    _isOpen = false;
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+///                                                                        ///
+///     LOW Level commands                                                 ///
+///                                                                        ///
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::beginCommands()
+{
+    Q_ASSERT( !_commandMode );
+    static char animation[] = "/-\\|";
+    for(int i=0;; ++i)
+    {
+        if( i == 100 ) // 20.0 sec loop ?
+            LOG_THROW("Bad reply to open command");
+
+        _serial.sleep(100);
+        _serial.purge();
+        _serial.writeByte(0xAA);        // Start byte
+
+        int reply = 0;
+        try {
+            reply = _serial.readByte();
+        } catch(...) {}
+        if(  reply == 0x4B )
+            goto Started;
+
+        LOG_TRACE(QString("Connecting %1")
+            .arg(animation[i%4]));
+        _serial.sleep(200);
+        continue;
+
+Started:
+        unsigned char buffer[] = "\xAA\xAB\xAC";
+        _serial.writeBlock(buffer, 3);
+
+        try {
+            unsigned char reply = _serial.readByte();
+            if( reply == 0x4C ) {
+                _commandMode = true;
+                return;
+            }
+        } catch(...) {}
+
+        _serial.sleep(200);
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::endCommands()
+{
+    Q_ASSERT( _commandMode );
+
+    _serial.sleep(100);
+    _serial.purge();
+    _serial.writeByte(0xFF);                // Exit service mode
+    _serial.sleep(10);
+
+    unsigned char buffer = _serial.readByte();
+    if( buffer != 0xFF )
+        LOG_THROW("End failed");
+
+    _commandMode = false;
+    disconnect();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::eraseRange(unsigned int addr, unsigned int size)
+{
+    Q_ASSERT( _commandMode );
+
+    // Convert size to number of pages, rounded up.
+    size = ((size + 4095) / 4096);
+    if( size < 256 || addr != 0x300000 )
+    {
+        unsigned char buffer[4];
+        // Erase just the needed pages.
+        buffer[0] = UPPER(addr);
+        buffer[1] = HIGH(addr);
+        buffer[2] = LOW(addr);
+        buffer[3] = LOW(size);
+        _serial.writeByte(0x42);            // Command
+        _serial.sleep(10);
+
+        _serial.writeBlock(buffer, 4);
+        // Wait (120/4)ms by block of 4K, plus 3% VAT to be sure.
+        _serial.sleep(40 + size * 31);
+    }
+    else
+    {
+        // Erase the whole 512KB of icon memory...
+        _serial.writeByte(0x41);
+        _serial.sleep(3000);
+    }
+
+    try {
+        (void)_serial.readByte();
+    } catch(...) {}
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::startWrite(unsigned int addr)
+{
+    Q_ASSERT( _commandMode );
+
+    unsigned char buffer[3];
+    buffer[0] = UPPER(addr);
+    buffer[1] = HIGH(addr);
+    buffer[2] = LOW(addr);
+
+    _serial.writeByte(0x30);
+    _serial.sleep(10);
+
+    _serial.writeBlock(buffer, 3);
+    _serial.sleep(10);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::stopWrite()
+{
+    Q_ASSERT( _commandMode );
+
+    _serial.flush();
+    _serial.sleep(200); // Should be > 100ms.
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 0x4C )
+        LOG_THROW("stopWrite");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::readBytes(unsigned int addr,
+                                   unsigned char* ptr,
+                                   unsigned int size)
+{
+    Q_ASSERT( _commandMode );
+
+    unsigned char buffer[6];
+    buffer[0] = UPPER(addr);
+    buffer[1] = HIGH(addr);
+    buffer[2] = LOW(addr);
+    buffer[3] = UPPER(size);
+    buffer[4] = HIGH(size);
+    buffer[5] = LOW(size);
+
+    _serial.writeByte(0x20);
+    _serial.sleep(10);
+
+    _serial.writeBlock(buffer, 6);
+    _serial.sleep(10);
+
+    unsigned int len = _serial.readBlock(ptr, size);
+    if( len < size )
+        LOG_THROW("readBytes too short");
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 0x4C )
+        LOG_THROW("readBytes");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+///                                                                        ///
+///     HIGH Level commands                                                ///
+///                                                                        ///
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::getIdentity()
+{
+    //---- get model
+    HardwareDescriptor hw = hardwareDescriptor();
+    if( hw != HW_UNKNOWN_OSTC && hw != HW_Frog )
+        LOG_THROW("Not a Frog.");
+
+    //---- get identity
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('i'); // 0x63
+
+    unsigned char buffer[1+2+2+13+1] = {0};
+    unsigned len = _serial.readBlock(buffer, sizeof buffer);
+
+    if( len != sizeof buffer || buffer[0] != 'i' || buffer[18] != 0x4D )
+        LOG_THROW("get identity data");
+
+    _serialNumber = buffer[1] + buffer[2]*256;
+    _firmware     = buffer[3]*256 + buffer[4];
+
+    _description = QString("%1 #%2, v%3.%4, %5")
+            .arg(model())
+            .arg(_serialNumber, 4, 10, QChar('0'))
+            .arg(_firmware / 256).arg(_firmware % 256)
+            .arg( QString::fromLatin1((char*)buffer+5, 13)
+                    .replace(QChar('\0'), "")
+                    .trimmed() );
+
+    LOG_TRACE("Found " << _description);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::writeText(const QString& _msg)
+{
+    // Pad to 15 chars:
+    QByteArray ascii = (_msg + QString(15, QChar(' '))).left(15).toLatin1();
+
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('n');     // 0x6E
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 'n' )
+        LOG_THROW("message start");
+
+    _serial.writeBlock((unsigned char *)ascii.constData(), 15);
+    reply = _serial.readByte();
+    if( reply != 0x4D )
+        LOG_THROW("message end");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::setDate(const QDateTime &date)
+{
+    unsigned char buffer[6];
+    buffer[0] = date.time().hour();
+    buffer[1] = date.time().minute();
+    buffer[2] = date.time().second();
+    buffer[3] = date.date().month();
+    buffer[4] = date.date().day();
+    buffer[5] = date.date().year() % 100;
+
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('b'); // 0x62
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 'b' )
+        LOG_THROW("sync time");
+
+    _serial. writeBlock( buffer, sizeof buffer);
+    reply = _serial.readByte();
+    if( reply != 0x4D )
+        LOG_THROW("sync time end");
+
+    writeText("Set " + date.toString("MM/dd hh:mm"));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QSize OSTCFrogOperations::nameSize() const
+{
+    return QSize(13, 1);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::setName(const QString &newName)
+{
+    QByteArray padded = (newName+QString(13, QChar(' '))).left(13).toLatin1();
+
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('c');     // 0x63
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 'c' )
+        LOG_THROW("set custom text");
+
+    _serial.writeBlock((unsigned char*)padded.constData(), 13);
+    reply = _serial.readByte();
+    if( reply != 0x4D )
+        LOG_THROW("custom text end");
+
+    // Re-read new name:
+    getIdentity();
+    writeText(customText());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::setIcons(const QString &/*fileName*/)
+{
+    //    beginCommands();
+    //    eraseRange(0x000000, 0x00000);
+    //    startWrite(0x000000);
+    //    stopWrite();
+    //    endCommands();
+
+    LOG_THROW( "Set icons: Not yet implemented." );
+}
+
+int OSTCFrogOperations::firmware() const
+{
+    return _firmware;
+}
+
+int OSTCFrogOperations::serialNumber() const
+{
+    return _serialNumber;
+}
+
+QString OSTCFrogOperations::customText() const
+{
+    return _description.section(',', 2).trimmed();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static unsigned char frogSecretKey[16] = {
+    111, 85, 190, 69,
+    108,254, 242, 19,
+    231, 49, 248,255,
+    233, 48, 176,241
+};
+
+void OSTCFrogOperations::loadFirmware(HexFile &hex, const QString &fileName) const
+{
+    hex.allocate(FIRMWARE_SIZE);
+    hex.loadEncrypted(fileName, frogSecretKey);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::upgradeFW(const QString &fileName)
+{
+    try {
+        //---- Load and check firmware ---------------------------------------
+        LOG_TRACE("Loading firmware...");
+
+        HexFile hex;
+        loadFirmware(hex, fileName);
+        unsigned int checksum = hex.checksum();
+
+        beginCommands();
+        writeText("Frog Companion");
+        getIdentity();
+
+        unsigned char buffer[5];
+        buffer[0] = LOW(checksum);
+        buffer[1] = HIGH(checksum);
+        buffer[2] = UPPER(checksum);
+        buffer[3] = UP32(checksum);
+
+        // Compute magic checksum's checksum.
+        buffer[4] = 0x55;
+        buffer[4] ^= buffer[0]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+        buffer[4] ^= buffer[1]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+        buffer[4] ^= buffer[2]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+        buffer[4] ^= buffer[3]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+
+        _serial.sleep(100);         // Make sure last command is finished.
+        _serial.purge();
+        _serial.writeByte('P'); // 0x50
+
+        unsigned char reply = _serial.readByte();
+        if( reply != 'P' )
+            LOG_THROW( "Programming start" );
+
+        _serial.writeBlock(buffer, sizeof buffer);
+        _serial.sleep(4000);
+
+        // NOTE: the device never return, because it always to a reset,
+        //       with ot without reprogramming...
+        _serial.close();
+        _isOpen = false;
+    }
+    catch(const Exception& e) {
+        LOG_TRACE(QString("Cannot upgrade: <font color='red'>%1</font>")
+            .arg(e.what()));
+
+        // Unknown state: so make a hard cleanup:
+        _commandMode = false;
+        _isOpen = false;
+        _serial.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTCFrogOperations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,94 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTCFrogOperations.h
+/// \brief  Implementing various operations for H&W Frog dive computer
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2011-10-01 : Separate from FrogTools main.cpp
+//  2014-10-23 : Mix with OSTC Companion code.
+
+#ifndef OSTC_FROG_OPERATIONS_H
+#define OSTC_FROG_OPERATIONS_H
+
+#include "HardwareOperations.h"
+
+class  OSTCFrogOperations
+  : public HardwareOperations
+{
+    int     _firmware;
+    int     _serialNumber;
+    QString _description;
+    bool    _isOpen;
+    bool    _commandMode;
+
+    //---- Low level commands
+    void beginCommands();
+    void endCommands();
+    void eraseRange(unsigned int addr, unsigned int size);
+    void startWrite(unsigned int addr);
+    void stopWrite();
+    void readBytes(unsigned int addr, unsigned char *ptr, unsigned int size);
+
+
+    //---- Port management
+    //QRegExp portTemplate() const override;
+    QRegularExpression portTemplate() const override;
+    QStringList listPorts() const override;
+    HardwareOperations::CompanionFeatures supported() const override;
+
+    //---- High level commands reimplemented
+    bool connect() override;
+    void connectServiceMode() override;
+    void getIdentity() override;
+    void writeText(const QString &_msg) override;
+    void setDate(const QDateTime& date) override;
+    QSize nameSize() const override;
+    void setName(const QString& newName) override;
+    void setIcons(const QString& fileName) override;
+    int firmware() const override;
+    int serialNumber() const override;
+    QString customText() const override;
+    void loadFirmware(HexFile& hex, const QString& fileName) const override;
+    void upgradeFW(const QString& fileName) override;
+    QString firmwareTemplate() const override;
+    bool disconnect(bool closing = false) override;
+    QString model() const override;
+    QString description() override;
+    QImage dumpScreen() const override;
+
+public:
+    OSTCFrogOperations();
+    ~OSTCFrogOperations();
+};
+
+#endif // OSTC_FROG_OPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTCSportOperations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,136 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTCSportOperations.cpp
+/// \brief  Implementing various operations for OSTC3 dive computer
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "OSTCSportOperations.h"
+
+#include "Utils/Log.h"
+
+#include <QStringList>
+#include <QRegularExpression>
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTCSportOperations::OSTCSportOperations()
+{
+    emulatorName = "OSTC_Sport";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+#if 0
+QRegExp OSTCSportOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty.OSTCs.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, and SUSE (google dixit).
+    // Obviously, needs the rfcomm package. "hcitool scan" or lsusb to have
+    // a list of connected stuff...
+    return QRegExp("rfcomm.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_WIN)
+    return QRegExp("COM.*", Qt::CaseSensitive);
+#endif
+}
+#endif
+QRegularExpression OSTCSportOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegularExpression("tty.OSTCs.*",
+                              QRegularExpression::CaseInsensitiveOption);
+#elif defined(Q_OS_LINUX)
+    // Debian, Ubuntu, SUSE, benötigt rfcomm-Paket
+    return QRegularExpression("rfcomm.*",
+                              QRegularExpression::CaseInsensitiveOption);
+#elif defined(Q_OS_WIN)
+    return QRegularExpression("COM.*"); // default: case-sensitive
+#endif
+}
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCSportOperations::getIdentity()
+{
+    descriptionString.clear();
+
+    LOG_TRACE("Getting model...");
+    HardwareDescriptor hw = hardwareDescriptor();
+    if( hw != HW_UNKNOWN_OSTC && hw != HW_OSTCSport_a && hw != HW_OSTCSport_b )
+        LOG_THROW( "Not an OSTC Sport." );
+
+    LOG_TRACE("Getting identity...");
+    getCommonIdentity();
+
+    // OTC Sport fw is between 10.00  and 19.99 (coded 100x Hi + Lo)
+    //   and serial is between 10.000 and 19.999
+    if( hw == HW_UNKNOWN_OSTC
+     && (    firmware()     < 1000  || firmware()     > 1999
+          || serialNumber() < 10000 || serialNumber() > 20000) )
+          LOG_THROW( "Not an OSTC Sport (fw "
+                       << (firmware()/100) << "." <<  QString::asprintf("%02d", firmware()%100) << ", #"
+                       << serialNumber() << ").");
+    hw = HW_OSTCSport_a;
+
+    LOG_TRACE("Found " << descriptionString);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QStringList OSTCSportOperations::listPorts() const
+{
+    return listBluetoothPorts();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTCSportOperations::firmwareTemplate() const
+{
+    return "*_ostc_sport_firmware.hex";
+}
+
+QSize OSTCSportOperations::nameSize() const
+{
+    return QSize(12, 5);
+}
+
+QString OSTCSportOperations::model() const
+{
+    return "OSTCSport";
+}
+
+HardwareOperations::CompanionFeatures OSTCSportOperations::supported() const
+{
+    // No ICON, no DUMPSCREEN, no HELIUM, no CCR
+    return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
+                     |BLUETOOTH);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTCSportOperations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,63 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTCSportOperations.h
+/// \brief  Implementing various operations for H&W OSTC Sport dive computer
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#ifndef OSTC_SPORTOPERATIONS_H
+#define OSTC_SPORTOPERATIONS_H
+
+#include "OSTC3Operations.h"
+
+//#include <QRegExp>
+#include <QRegularExpression>
+
+class  OSTCSportOperations
+  : public OSTC3Operations
+{
+//    QRegExp portTemplate() const override;
+    QRegularExpression portTemplate() const override;
+    QStringList listPorts() const override;
+    QString firmwareTemplate() const override;
+    QSize nameSize() const  override;
+    void getIdentity() override;
+
+    QString model() const override;
+
+    CompanionFeatures supported() const override;
+
+public:
+    OSTCSportOperations();
+};
+
+#endif // OSTC_SPORTOPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC_CR_Operations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,86 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC_CR_Operations.cpp
+/// \brief  Implementing various operations for H&W OSTC-cR dive computer
+/// \author JD Gascuel.
+/// \sa     ComputerOperations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "OSTC_CR_Operations.h"
+
+#include "Utils/Log.h"
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTCcROperations::OSTCcROperations()
+{
+    emulatorName = "OSTC_CR";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTCcROperations::model() const
+{
+    return "OSTC_cR";
+}
+
+HardwareOperations::CompanionFeatures OSTCcROperations::supported() const
+{
+    // No ICON, no DUMPSCREEN
+    return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
+                    |HELIUM_DIVE|CCR_DIVE);
+}
+
+void OSTCcROperations::getIdentity()
+{
+    descriptionString.clear();
+
+    LOG_TRACE("Getting model...");
+    HardwareDescriptor hw = hardwareDescriptor();
+    if( hw != HW_UNKNOWN_OSTC && hw != HW_OSTCcR_a && hw != HW_OSTCcR_b )
+        LOG_THROW("Not an OSTC cR.");
+
+    LOG_TRACE("Getting identity...");
+    getCommonIdentity();
+
+    if( hw == HW_UNKNOWN_OSTC && (firmware() > 0x0A00 || serialNumber() > 10000) )
+        LOG_THROW("Not an OSTC cR");
+
+    LOG_TRACE("Found " << descriptionString);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString OSTCcROperations::firmwareTemplate() const
+{
+    return "*_ostc3_firmware.hex";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC_CR_Operations.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,67 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTC_CR_Operations.h
+/// \brief  Implementing various operations for H&W OSTC-cR dive computer
+/// \author JD Gascuel.
+/// \sa     ComputerOperations.h
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2014-11-08 : [jDG] Creation, from OSTC3Operations.h
+
+#ifndef OSTC3_S8_OPERATIONS_H
+#define OSTC3_S8_OPERATIONS_H
+
+#include "OSTC3Operations.h"
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief  Implementing various low-level operations for OSTC3-S8 dive computer
+class  OSTCcROperations
+  : public OSTC3Operations
+{
+    /// \brief Returns "OSTC_cR"
+    QString model() const override;
+    CompanionFeatures supported() const override;
+
+    //------------------------------------------------------------------------
+    /// \brief Read OSTC3-S8 computer firmware, serial and custom text.
+    /// Everything is formated for the description() string.
+    /// \throws if something goes wrong.
+    void getIdentity() override;
+
+    QString firmwareTemplate() const  override;
+
+    //////////////////////////////////////////////////////////////////////////
+public:
+    OSTCcROperations();
+};
+
+#endif // OSTC3_S8_OPERATIONS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources.qrc	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,19 @@
+<RCC>
+    <qresource prefix="/">
+        <file>Images/Settings.svg</file>
+        <file>Images/app_OSTC_Companion.svg</file>
+        <file>Images/frog_160x120.png</file>
+        <file>Images/ostc_sport_160x120.png</file>
+        <file>Images/ostc2c_160x120.png</file>
+        <file>Images/ostc2p_160x120.png</file>
+        <file>Images/ostc3_160x120.png</file>
+        <file>Images/ostc3p_160x120.png</file>
+        <file>Images/ostc_cr_160x120.png</file>
+        <file>Images/ostc4_160x120.png</file>
+        <file>Translations/companion_DE.qm</file>
+        <file>Translations/companion_ES.qm</file>
+        <file>Translations/companion_FR.qm</file>
+        <file>Translations/companion_IT.qm</file>
+        <file>Translations/companion_RU.qm</file>
+    </qresource>
+</RCC>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Serial.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,426 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Serial.cpp
+/// \brief  RS232 serial i/o.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "Serial.h"
+
+#include "Utils/Log.h"
+#include "Utils/Exception.h"
+
+#include <QString>
+
+#ifdef WIN32
+#   define  S_WRITE(p, b, li, lo)   WriteFile(p, b, li, &lo, NULL)
+#   define  S_READ(p, b, li, lo)    ReadFile(p, b, li, &lo, NULL)
+#   define  S_LEN                   DWORD
+#   define  S_FLUSH(p)              FlushFileBuffers(p)
+#   define  S_PURGE(p)              PurgeComm(p, PURGE_RXCLEAR | PURGE_TXCLEAR)
+#   define  S_CLOSE(p)              CloseHandle(p)
+#else
+#   include <fcntl.h>
+#   include <termios.h>
+#   include <sys/ioctl.h>
+#   include <unistd.h>
+
+#   define  INVALID_HANDLE_VALUE    (-1)
+#   define  S_WRITE(p, b, li, lo)   (lo = write(p, b, li))
+#   define  S_READ(p, b, li, lo)    (lo = read (p, b, li))
+#   define  S_LEN                   ssize_t
+#   define  S_FLUSH(p)              tcdrain(p)
+#   define  S_PURGE(p)              tcflush(p,TCIOFLUSH)
+#   define  S_CLOSE(p)              ::close(p)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+Serial::~Serial()
+{}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::open(const QString& port, const QString &type)
+{
+    if( _isOpen ) return;
+    LOG_TRACE("Open " << port << " ...");
+
+    _hSerial = INVALID_HANDLE_VALUE;
+
+    //------------------------------------------------------------------------
+    // Sanity checks.
+    if( port.isEmpty() )
+        LOG_THROW( "Port is not defined." );
+    if( type.isEmpty() )
+        LOG_THROW( "Port type is not defined." );
+
+    bool usbMode = type.contains("ostc2c",    Qt::CaseInsensitive)
+                || (   type.contains("ostc3",    Qt::CaseInsensitive)
+                   && !type.contains("ostc3p",   Qt::CaseInsensitive))
+                || type.contains("ostc_cr",  Qt::CaseInsensitive);
+    LOG_TRACE((usbMode ? "Fast USB" : "Slow Bluetooth") << " connection mode.");
+
+    //------------------------------------------------------------------------
+#ifdef Q_OS_WIN
+    // BUGFIX: COM ports above COM9 are not automatically recognized,
+    //         hence we have to prepend DEVICE NAMESPACE...
+    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+    QByteArray com = port.toLatin1() + "\0";
+    if( !com.startsWith("\\\\.\\") )
+        com = "\\\\.\\" + com;
+
+    _hSerial = CreateFileA(com.data(),
+                         GENERIC_READ | GENERIC_WRITE,
+                         0,             // Exclusive access.
+                         NULL,          // No security
+                         OPEN_EXISTING,
+                         FILE_ATTRIBUTE_DEVICE | FILE_FLAG_NO_BUFFERING,
+                         0);
+    if(_hSerial==INVALID_HANDLE_VALUE)
+    {
+        if(GetLastError()==ERROR_FILE_NOT_FOUND)
+            LOG_THROW( "Unknown port" );
+        LOG_THROW( "Unable to open port" );
+    }
+    S_PURGE(_hSerial);
+
+    DCB dcbSerial = {sizeof(dcbSerial), 0};
+    if( !GetCommState(_hSerial, &dcbSerial) )
+        LOG_THROW( "Unable to get COM port config" );
+
+    dcbSerial.BaudRate     = CBR_115200;
+    dcbSerial.ByteSize     = 8;
+    dcbSerial.Parity       = NOPARITY;
+    dcbSerial.StopBits     = ONESTOPBIT;
+    dcbSerial.fOutxCtsFlow = DTR_CONTROL_ENABLE;                 // NO HARDWARE FLOW CONTROL
+    dcbSerial.fRtsControl  = RTS_CONTROL_ENABLE; //RTS_CONTROL_DISABLE;   // NO HARDWARE FLOW CONTROL
+
+    if( !SetCommState(_hSerial, &dcbSerial) )
+        LOG_THROW( "Unable to set COM port config" );
+
+    COMMTIMEOUTS timeouts={0};
+    if( usbMode ) {
+        timeouts.ReadTotalTimeoutConstant  =  500;  // 0.5 sec
+        timeouts.WriteTotalTimeoutConstant = 1000;  // 1.0 sec
+    } else {
+        timeouts.ReadTotalTimeoutConstant  = 2000;  // 2.0 sec timeout.
+    }
+    if( !SetCommTimeouts(_hSerial, &timeouts) )
+        LOG_THROW( "Unable to configure port" );
+
+    LOG_TRACE("Connection:");
+    LOG_TRACE("    " << dcbSerial.BaudRate << " bauds.");
+    LOG_TRACE("    " << (int)dcbSerial.ByteSize << " bits, "
+                     << (dcbSerial.Parity ? "+parity, " : "no parity, ")
+                     << (dcbSerial.StopBits ? QString(" +%1").arg(dcbSerial.StopBits) : QString("no")) << " stops bits.");
+    LOG_TRACE("    CTS is " << (dcbSerial.fOutxCtsFlow ? "ON." : "OFF."));
+    LOG_TRACE("    RTS is " << ((dcbSerial.fRtsControl == RTS_CONTROL_HANDSHAKE) ? "ON."
+                              : (dcbSerial.fRtsControl == RTS_CONTROL_ENABLE)    ? "FORCED."
+                              :                                                    "OFF."));
+    LOG_TRACE("    Read  timeout " << timeouts.ReadTotalTimeoutConstant  << " msec.");
+    LOG_TRACE("    Write timeout " << timeouts.WriteTotalTimeoutConstant << " msec.");
+#endif
+
+    //------------------------------------------------------------------------
+#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
+    QByteArray p = port.toLatin1();
+    if( ! p.startsWith("/dev/") )
+        p = "/dev/" + p;
+    _hSerial = ::open( p.constData(), O_RDWR | O_NOCTTY | O_NONBLOCK, 0);
+    if( _hSerial<0 )
+        LOG_THROW( "Unable to open port " << p);
+
+    if( ioctl(_hSerial, TIOCEXCL) < 0 )
+        LOG_THROW( "Port in use" );
+
+    // Once opened, clearing the O_NONBLOCK flag.
+    if( fcntl(_hSerial, F_SETFL, 0) < 0 )
+        LOG_THROW( "Can't reset non-blocking I/O" );
+
+    struct termios termios;
+    tcgetattr(_hSerial, &termios);
+
+    cfmakeraw(&termios);
+    if( cfsetspeed(&termios, B115200) < 0 )
+        LOG_THROW( "Bad port speed" );
+
+    termios.c_cflag |= CLOCAL| CS8 | CREAD;     // No DTR/DSR/DCD, 8bit.
+    termios.c_cflag &= ~( PARENB | CSTOPB       // No parity, one stop bit.
+                    | CCTS_OFLOW | CRTS_IFLOW); // No hardware flow control.
+
+    if( usbMode ) {
+        // FAST USB: Fix timeout to 0.5 seconde:
+        termios.c_cc[VMIN]  =  0;   // Pure timout mode (no char needed).
+        termios.c_cc[VTIME] =  5;   // 0.5s timeout after last received byte.
+    } else {
+        // SLOW BLUETOOTH: Fix timeout to 2.0 sec:
+        termios.c_cc[VMIN]  =  0;   // Pure timout mode (no char needed).
+        termios.c_cc[VTIME] = 20;   // 2.0sec timeout after last received byte.
+    }
+
+    if( tcsetattr(_hSerial, TCSANOW, &termios) < 0 )
+        LOG_THROW( "Unable to configure port" );
+#endif
+
+    if( _hSerial == INVALID_HANDLE_VALUE )
+        LOG_THROW( "Port not open" );
+
+    _isOpen = true;
+    LOG_TRACE("Device open successfull.");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::close()
+{
+    if( !_isOpen ) return;
+    LOG_TRACE("Device close ...");
+
+#ifdef Q_OS_MAC
+    if( ioctl(_hSerial, TIOCNXCL) < 0 )
+        LOG_THROW( "Port in use" );
+#endif
+
+    if( _hSerial == INVALID_HANDLE_VALUE )
+        LOG_THROW( "Port not open" );
+    S_CLOSE(_hSerial);
+
+    _hSerial = INVALID_HANDLE_VALUE;
+    _isOpen  = false;
+    LOG_TRACE("Device close successfull.");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned char Serial::readByte() const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    unsigned char byte = 0;
+    S_LEN len = 0;
+    S_READ(_hSerial, &byte, 1, len);
+    if( len != 1 )
+        // LOG_THROW_E(ReadTimeout, "< timeout" );
+        LOG_THROW("< timeout" );
+
+    if( isprint(byte) )
+   //     LOG_DEBUG("< " << QString().sprintf("%02x '%c'", byte, byte) );
+        LOG_DEBUG("< " << QString::asprintf("%02x '%c'", byte, byte));
+    else
+        LOG_DEBUG("< " << QString::asprintf("%02x", byte) );
+
+    return byte;
+}
+
+void Serial::writeByte(unsigned char byte) const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    if( isprint(byte) )
+        LOG_DEBUG("> " << QString::asprintf("%02x '%c'", byte, byte) );
+    else
+        LOG_DEBUG("> " << QString::asprintf("%02x", byte) );
+
+    S_LEN len = 0;
+    S_WRITE(_hSerial, &byte, 1, len);
+    if( len != 1 )
+   //     LOG_THROW_E(WriteTimeout, "> timeout");
+         LOG_THROW("> timeout");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned short Serial::readShort() const
+{
+    unsigned char lo = readByte();
+    unsigned char hi = readByte();
+    return hi << 8 | lo;
+}
+
+void Serial::writeShort(unsigned short word) const
+{
+    unsigned char lo = word & 0xFF;
+    unsigned char hi = word >> 8;
+    writeByte(lo); writeByte(hi);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::writeInt24(unsigned int int24) const
+{
+    unsigned char lo = int24 & 0xFF;
+    unsigned char hi = (int24 >>  8) & 0xFF;
+    unsigned char up = (int24 >> 16) & 0xFF;
+    writeByte(lo); writeByte(hi); writeByte(up);
+}
+
+unsigned int Serial::readInt24() const
+{
+    unsigned char lo = readByte();
+    unsigned char hi = readByte();
+    unsigned char up = readByte();
+    return up << 16 | hi << 8 | lo;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+unsigned int Serial::readBlock(unsigned char* ptr, unsigned int size) const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    unsigned int bytes = 0;
+
+    bool timeout = false;
+    while( size > 0 )
+    {
+        // Allow up to 1.0sec for each 4K block.
+        S_LEN todo = (size > 4096) ? 4096 : size;
+        S_LEN done = 0;
+        S_READ(_hSerial, ptr+bytes, todo, done);
+
+        if( done == 0 ) {
+            timeout = true;
+            break;
+        }
+
+        size -= done;
+        bytes += (unsigned int)done;
+    }
+
+    if( Log::minLevel <= Log::LEVEL_DEBUG )
+    {
+        const int DUMP_LINE = 16;
+        const int DUMP_MAX  = 3*DUMP_LINE;
+        const unsigned int length = (bytes < DUMP_MAX) ? bytes : DUMP_MAX;
+        for(unsigned int i=0; i<length; i += DUMP_LINE)
+        {
+            LogAction logger(Log::LEVEL_DEBUG, __FILE__ ,__LINE__, "readBlock()");
+            logger << "< ";
+            for(unsigned int j=i; j<bytes && j<(i+DUMP_LINE); ++j)
+                logger << QString::asprintf("%02x ", ptr[j]);
+            for(unsigned int j=i; j<bytes && j<(i+DUMP_LINE); ++j)
+                logger << (isprint( ptr[j] )
+                          ? ptr[j]
+                          : (unsigned char)'.');
+        }
+        if( length < bytes )
+            LOG_DEBUG("< ... " << (bytes-length) << " more bytes.");
+    }
+
+    if( timeout )
+        LOG_THROW_E(ReadTimeout, "< block timeout (missing " << size << " bytes)");
+
+    return bytes;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::writeBlock(const unsigned char* ptr, unsigned int size) const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    if( Log::minLevel <= Log::LEVEL_DEBUG )
+    {
+        const int DUMP_LINE = 16;
+        const int DUMP_MAX  = 3*DUMP_LINE;
+        const unsigned int length = (size < DUMP_MAX) ? size : DUMP_MAX;
+        for(unsigned int i=0; i<length; i += DUMP_LINE)
+        {
+            LogAction logger(Log::LEVEL_DEBUG, __FILE__ ,__LINE__, "writeBlock()");
+            logger << "> ";
+            for(unsigned int j=i; j<size && j<(i+DUMP_LINE); ++j)
+                logger << QString::asprintf("%02x ", ptr[j]);
+            for(unsigned int j=i; j<size && j<(i+DUMP_LINE); ++j)
+                logger << (isprint( ptr[j] )
+                          ? ptr[j]
+                          : (unsigned char)'.');
+        }
+        if( length < size )
+            LOG_DEBUG("> ... " << (size-length) << " more bytes.");
+    }
+
+    while( size > 0 )
+    {
+        // Allow up to 1.0sec for each 4K block.
+        S_LEN chunck = (size > 4096) ? 4096 : size;
+        S_LEN len = 0;
+        S_WRITE(_hSerial, ptr, chunck, len);
+
+        if( len == 0  )
+            LOG_THROW_E(WriteTimeout, "> block timeout");
+
+        size -= len;
+        ptr  += len;
+    }
+
+    // Auto-fluh on each write.
+    flush();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::purge()
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    // Empty incomming buffer
+    S_PURGE(_hSerial);
+
+    LOG_TRACE("Device purged.");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::flush() const
+{
+    if( !_isOpen )
+        LOG_THROW( "Port not open" );
+
+    S_FLUSH(_hSerial);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void Serial::sleep(int msec) const
+{
+#ifdef WIN32
+    Sleep(msec);
+#else
+    usleep(msec*1000);
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Serial.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,99 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Serial.h
+/// \brief  RS232 serial i/o.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2012-09-22 : [jDG] Split from Frog.cpp to OSTC_Planner.cpp
+//  2013-03-17 : [jDG] Adapted from OSTC Planner to OSTC Companion.
+//  2014-07-07 : [jDG] Cleanups for Subsurface google-summer-of-code.
+//  2014-07-25 : [jDG] BSD 2-clause license.
+
+#ifndef SERIAL_H
+#define SERIAL_H
+
+class QString;
+
+#include <QtGlobal>         // Q_OS_WIN
+
+#ifdef Q_OS_WIN
+#ifndef NOMINMAX
+#   define NOMINMAX
+#endif
+//#   define  NOMINMAX
+#   include <windows.h>
+#   define  S_HANDLE                HANDLE
+#   undef NOMINMAX
+#else
+#   define  S_HANDLE                int
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+// \brief RS232 serial i/o
+class  Serial
+{
+    S_HANDLE    _hSerial;
+    bool        _isOpen;
+
+public:
+    inline Serial()
+        : _hSerial(0),
+          _isOpen(false)
+    {}
+
+    virtual ~Serial();
+
+    virtual void open(const QString &port, const QString& type);
+    virtual void close();
+
+    virtual void writeByte(unsigned char byte) const;
+    virtual unsigned char readByte() const;
+
+    virtual void writeBlock(const unsigned char* ptr, unsigned int size) const;
+    virtual unsigned int readBlock(unsigned char* ptr, unsigned int size) const;
+
+    virtual void purge();
+    virtual void flush() const;
+
+    void writeShort(unsigned short word) const;
+    unsigned short readShort() const;
+
+    void writeInt24(unsigned int int24) const;
+    unsigned int readInt24() const;
+
+    void sleep(int msec) const;
+
+    inline bool isOpen() const { return _isOpen; }
+};
+
+#endif // SERIAL_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Settings.ui	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,361 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Settings</class>
+ <widget class="QDialog" name="Settings">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>341</width>
+    <height>293</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>OSTC Companion settings</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="10" column="0" colspan="3">
+    <widget class="Line" name="line_3">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="6" column="0" colspan="2">
+    <widget class="QCheckBox" name="forceFirmwareUpdate">
+     <property name="text">
+      <string>Force Firmware update</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QComboBox" name="portMenu">
+     <property name="editable">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="11" column="0" colspan="3">
+    <widget class="QCheckBox" name="autoSetDateTimeCB">
+     <property name="toolTip">
+      <string>If checked, any configuration upload will also setup date and time.</string>
+     </property>
+     <property name="text">
+      <string>Auto setup of date &amp;&amp; time</string>
+     </property>
+    </widget>
+   </item>
+   <item row="8" column="0" colspan="2">
+    <widget class="QCheckBox" name="forceFontlibUpdate">
+     <property name="text">
+      <string>Force FontLib update</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="3">
+    <widget class="QLabel" name="noPortLabel">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Ignored" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="textFormat">
+      <enum>Qt::RichText</enum>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+     <property name="openExternalLinks">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QComboBox" name="languageMenu">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="insertPolicy">
+      <enum>QComboBox::NoInsert</enum>
+     </property>
+     <item>
+      <property name="text">
+       <string notr="true">Deutsch</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:/icons/Icons/german.png</normaloff>:/icons/Icons/german.png</iconset>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string notr="true">English</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:/icons/Icons/english.png</normaloff>:/icons/Icons/english.png</iconset>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string notr="true">Español</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:/icons/Icons/spanish.png</normaloff>:/icons/Icons/spanish.png</iconset>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string notr="true">Français</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:/icons/Icons/french.png</normaloff>:/icons/Icons/french.png</iconset>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string notr="true">Italiano</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:/icons/Icons/italian.png</normaloff>:/icons/Icons/italian.png</iconset>
+      </property>
+     </item>
+     <item>
+      <property name="text">
+       <string notr="true">Русский</string>
+      </property>
+      <property name="icon">
+       <iconset>
+        <normaloff>:/icons/Icons/russian.png</normaloff>:/icons/Icons/russian.png</iconset>
+      </property>
+     </item>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <widget class="QToolButton" name="updatePorts">
+     <property name="toolTip">
+      <string>Update port list with currently connected USB or Bluetooth devices.</string>
+     </property>
+     <property name="text">
+      <string notr="true">...</string>
+     </property>
+    </widget>
+   </item>
+   <item row="15" column="0" colspan="3">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="QPushButton" name="resetB">
+       <property name="text">
+        <string>Restore Defaults</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="okB">
+       <property name="text">
+        <string>OK</string>
+       </property>
+       <property name="shortcut">
+        <string>Return</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+       <property name="default">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cancelB">
+       <property name="text">
+        <string>Cancel</string>
+       </property>
+       <property name="shortcut">
+        <string>Ctrl+W</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Language:</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="7" column="0" colspan="2">
+    <widget class="QCheckBox" name="forceRTEUpdate">
+     <property name="text">
+      <string>Force RTE update</string>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="0" colspan="3">
+    <widget class="Line" name="line_4">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="text">
+      <string>Communication port:</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Expanding</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>0</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="14" column="0" colspan="3">
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="13" column="0" colspan="3">
+    <widget class="QCheckBox" name="useFastMode">
+     <property name="text">
+      <string>Enable fast mode if supported</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>okB</sender>
+   <signal>clicked()</signal>
+   <receiver>Settings</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>212</x>
+     <y>144</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>373</x>
+     <y>100</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cancelB</sender>
+   <signal>clicked()</signal>
+   <receiver>Settings</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>293</x>
+     <y>144</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>231</x>
+     <y>98</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>languageMenu</sender>
+   <signal>activated(int)</signal>
+   <receiver>Settings</receiver>
+   <slot>languageSlot(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>172</x>
+     <y>25</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>235</x>
+     <y>26</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>updatePorts</sender>
+   <signal>clicked()</signal>
+   <receiver>Settings</receiver>
+   <slot>updatePortsSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>295</x>
+     <y>52</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>170</x>
+     <y>97</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>resetB</sender>
+   <signal>clicked()</signal>
+   <receiver>Settings</receiver>
+   <slot>resetSettingsSlot()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>54</x>
+     <y>135</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>170</x>
+     <y>78</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>languageSlot(int)</slot>
+  <slot>resetSettingsSlot()</slot>
+  <slot>updatePortsSlot()</slot>
+ </slots>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SettingsDialog.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,255 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   SettingsDialog.cpp
+/// \brief  Preference dialog for OSTC Companion.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2014 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "SettingsDialog.h"
+#include "ui_Settings.h"
+
+#include "MainWindow.h"     // Needed to propagare retranslate()
+
+#include "Utils/Log.h"
+
+#include <QApplication>
+#include <QDialogButtonBox>
+#include <QDir>
+#include <QLibraryInfo>
+#include <QPushButton>
+#include <QSettings>
+#include <QTranslator>
+
+#ifdef Q_OS_WIN
+#   define NOMINMAX 1
+#   include <Windows.h>
+#   undef NOMINMAX
+#endif
+
+#include "Export.h"
+#include "HardwareOperations.h"
+
+QString EXPORT Settings::language = "";
+QString EXPORT Settings::port = "";
+QString EXPORT Settings::currentPath = "";
+
+bool EXPORT Settings::autoSetDateTime = true;
+
+bool EXPORT Settings::forceFirmwareUpdate = false;
+bool EXPORT Settings::forceRTEUpdate = false;
+bool EXPORT Settings::forceFontlibUpdate = false;
+
+bool EXPORT Settings::useFastMode = false;
+
+extern QSettings* settings;
+
+//////////////////////////////////////////////////////////////////////////////
+
+Settings::Settings(QWidget* parent, HardwareOperations *op)
+  : QDialog(parent),
+    _ui(new Ui::Settings),
+    _op(op)
+{
+    _ui->setupUi(this);
+    reload(this);
+}
+
+Settings::~Settings()
+{
+    delete _ui;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Settings::reload(Settings* dialog)
+{
+    //---- Restore options from settings -------------------------------------
+    language        = settings->value("Interface/lang",
+                        QLocale::system().name().right(2) ).toString();
+    port            = settings->value("OSTC/port", "").toString();
+    currentPath     = settings->value("OSTC/currentPath", "").toString();
+    autoSetDateTime = settings->value("OSTC/autoSetDateTime", true).toBool();
+    forceFirmwareUpdate = settings->value("OSTC/forceFirmwareUpdate", false).toBool();
+    forceRTEUpdate = settings->value("OSTC/forceRTEUpdate", false).toBool();
+    forceFontlibUpdate = settings->value("OSTC/forceFontlibUpdate", false).toBool();
+
+    useFastMode = settings->value("OSTC/useFastMode", false).toBool();
+
+    setLanguage();
+
+    if( !dialog )
+        return;
+
+    //---- Update interface --------------------------------------------------
+    if( language == "DE" ) dialog->_ui->languageMenu->setCurrentIndex(0);
+    if( language == "EN" ) dialog->_ui->languageMenu->setCurrentIndex(1);
+    if( language == "ES" ) dialog->_ui->languageMenu->setCurrentIndex(2);
+    if( language == "FR" ) dialog->_ui->languageMenu->setCurrentIndex(3);
+    if( language == "IT" ) dialog->_ui->languageMenu->setCurrentIndex(4);
+    if( language == "RU" ) dialog->_ui->languageMenu->setCurrentIndex(5);
+
+    dialog->updatePortsSlot();
+
+    dialog->_ui->autoSetDateTimeCB->setChecked( autoSetDateTime );
+    dialog->_ui->forceFirmwareUpdate->setChecked( forceFirmwareUpdate );
+    dialog->_ui->forceRTEUpdate->setChecked( forceRTEUpdate );
+    dialog->_ui->forceFontlibUpdate->setChecked( forceFontlibUpdate );
+    dialog->_ui->useFastMode->setChecked( useFastMode );
+
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Settings::languageSlot(int i)
+{
+    switch(i) {
+        case 0: language = "DE"; break;
+        case 1: language = "EN"; break;
+        case 2: language = "ES"; break;
+        case 3: language = "FR"; break;
+        case 4: language = "IT"; break;
+        case 5: language = "RU"; break;
+    }
+    setLanguage();
+}
+
+
+void Settings::updatePortsSlot()
+{
+    //---- search for possible ports ----------------------------------------
+    QStringList list;
+    if( _op ) { // Known driver type ?
+        list = _op->listPorts();
+
+#ifndef Q_OS_LINUX
+        if( list.isEmpty() )
+            _ui->noPortLabel->setText(
+                QString("<font color='red'>%1</font>: %2 - %3")
+                    .arg(tr("Warning"))
+                    .arg(tr("no port", "USB connection to OSTC not found"))
+                    .arg(tr("Did you installed the %1 driver ?")
+#ifdef Q_OS_WIN
+                        .arg("<a href='http://www.ftdichip.com/Drivers/CDM/CDM%20v2.12.00%20WHQL%20Certified.zip'>FTDI VCP</a>")));
+#elif defined(Q_OS_MACX)
+                        .arg("<a href='http://www.ftdichip.com/drivers/VCP/MacOSX/FTDIUSBSerialDriver_v2_2_18.dmg'>FTDI VCP</a>")));
+#else
+                        .arg("USB")));
+#endif
+        else
+#endif
+            _ui->noPortLabel->clear();
+    }
+
+    QString myPort =  port + " (current)";
+    if( ! port.isEmpty() )
+        list += myPort;
+    list.sort();
+
+    _ui->portMenu->clear();
+    _ui->portMenu->addItems(list);
+    _ui->portMenu->setCurrentText( port.isEmpty() ? "" : myPort );
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Settings::setLanguage()
+{
+    static QTranslator myappTranslator;
+    if( myappTranslator.load(":/Translations/companion_" + language) )
+        qApp->installTranslator(&myappTranslator);
+}
+
+void Settings::changeEvent(QEvent *e)
+{
+    if( e->type() == QEvent::LanguageChange )
+    {
+        _ui->retranslateUi(this);
+
+        // FIX: also update the warning text...
+        updatePortsSlot();
+
+        // FIX: propagate to main windows.
+        if( MainWindow* main = dynamic_cast<MainWindow*>(parent()) )
+            main->retranslate();
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Settings::accept()
+{
+    port = _ui->portMenu->currentText().section(" ", 0, 0);
+    autoSetDateTime = _ui->autoSetDateTimeCB->isChecked();
+    forceFirmwareUpdate = _ui->forceFirmwareUpdate->isChecked();
+    forceRTEUpdate = _ui->forceRTEUpdate->isChecked();
+    forceFontlibUpdate = _ui->forceFontlibUpdate->isChecked();
+
+    useFastMode = _ui->useFastMode->isChecked();
+    save();
+    QDialog::accept();
+}
+
+void Settings::save()
+{
+    settings->setValue("Interface/lang",       language);
+    settings->setValue("OSTC/port",            port);
+    settings->setValue("OSTC/currentPath",     currentPath);
+    settings->setValue("OSTC/autoSetDateTime", autoSetDateTime);
+    settings->setValue("OSTC/forceFirmwareUpdate", forceFirmwareUpdate);
+    settings->setValue("OSTC/forceRTEUpdate", forceRTEUpdate);
+    settings->setValue("OSTC/forceFontlibUpdate", forceFontlibUpdate);
+    settings->setValue("OSTC/useFastMode", useFastMode);
+    settings->sync();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Settings::reject()
+{
+    reload(this);
+    if( MainWindow* main = dynamic_cast<MainWindow*>(parent()) )
+        main->retranslate();
+    QDialog::reject();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Settings::resetSettingsSlot()
+{
+    //---- Clear everything -------------------------------------------------
+    settings->clear();
+    settings->sync();
+    //---- Set defaults
+    reload(this);
+    //---- Search connected ports
+    updatePortsSlot();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SettingsDialog.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,104 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   SettingsDialog.h
+/// \brief  Preference dialog for OSTC Companion.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2014 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-03-17 : [jDG] Copyed and adapted from OSTC Planner.
+//  2014-07-07 : [jDG] Cleanups for Subsurface google-summer-of-code.
+//  2014-07-25 : [jDG] BSD 2-clause license.
+
+//////////////////////////////////////////////////////////////////////////////
+// Import Qt GUI
+
+namespace Ui { class Settings; }
+
+#include <QDialog>
+
+class HardwareOperations;
+class QAbstractButton;
+class QSettings;
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief Implement preference dialog for OSTC Companion.
+class Settings
+  : public QDialog
+{
+    Q_OBJECT
+    Ui::Settings* _ui;
+    HardwareOperations* _op;
+
+    void changeEvent(QEvent *e);
+
+public:
+    Settings(QWidget* parent, HardwareOperations *op);
+    ~Settings();
+
+    static QString language;
+    static QString port;
+    static QString currentPath;
+
+    /// Setup date and time when connecting.
+    static bool autoSetDateTime;
+
+    /// Skip version check to make development easier
+    static bool forceFirmwareUpdate;
+    static bool forceRTEUpdate;
+    static bool forceFontlibUpdate;
+    static bool useFastMode;
+
+    /// \brief restore preferences from settings
+    static void reload(Settings *);
+
+    /// \brief Save preferences to settings.
+    static void save();
+
+    /// \brief update Qt translators for language.
+    static void setLanguage();
+
+public slots:
+    /// \brief dynamically change application's translation.
+    void languageSlot(int i);
+
+    /// \brief redo list of available ports.
+    void updatePortsSlot();
+
+    /// \brief save changes to settings.
+    void accept();
+
+    /// \brief restore saved preferences.
+    void reject();
+
+    /// \brief Reset all settings to default values
+    void resetSettingsSlot();
+};
Binary file Translations/companion_DE.qm has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Translations/companion_DE.ts	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,255 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="../MainWindow.ui" line="28"/>
+        <source>...</source>
+        <translation>...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="178"/>
+        <source>Set Date &amp;&amp; Time</source>
+        <translation>Datum und Uhrzeit stellen</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="146"/>
+        <source>Open or re-open the last selected  USB or Bluetooth port (See Preferences).
+NOTE: make sure to connect the serial cable, or to set the bluetooth mode first...</source>
+        <translation>Öffnen Sie den zuletzt gewählten USB oder Bluetooth Port (erneut). Siehe Einstellungen.
+Hinweis: Stellen Sie sicher, dass das Kabel angeschlossen bzw. der Bluetooth Mode gestartet ist... </translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="161"/>
+        <source>Connect</source>
+        <translation>Verbinden</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="173"/>
+        <source>Set the H&amp;W computer date and time.
+Can be automatic at each connect, if asked to in the Preferences.
+(Valid once device is connected).</source>
+        <translation>Einstellen von Datum und Uhrzeit bei HW Computern.
+Uhrzeit/Datum wird automatisch gesetzt, wenn in den Einstellungen aktiviert
+(Verfügbar, sobald ein Gerät verbunden).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="185"/>
+        <source>Change the name displayed on the H&amp;W device.
+(Valid once device is connected).</source>
+        <translation>Benutzerinfo welche im Gerät angezeigt wird.
+(Verfügbar, sobald ein Gerät verbunden).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="189"/>
+        <source>Set Name...</source>
+        <translation>Name...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="196"/>
+        <source>Upload a customization icon.
+Only supported on Frog yet.
+(Valid once device is connected).</source>
+        <translation>Nur beim frog verfügbar.
+(Verfügbar, sobald ein Gerät verbunden).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="201"/>
+        <source>Set Icon...</source>
+        <translation>Icon hochladen...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="208"/>
+        <source>Check Signal</source>
+        <translation>Prüfe Signalqualität</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="215"/>
+        <source>Ask for a firmware file, and upload it to the H&amp;W device.
+Can be done before or after device (re-)connection.</source>
+        <translation>Auswahl der Firmware Datei und Installation auf HW computer.
+(Verfügbar, sobald ein Gerät verbunden).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="219"/>
+        <source>Upgrade Firmware...</source>
+        <translation>Firmware aktualisieren...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="226"/>
+        <source>Close USB or Bluetooth connection to the device.</source>
+        <translation>USB oder Bluetooth Verbindung zum Gerät beenden.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="229"/>
+        <source>Close</source>
+        <translation>Schließen</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="252"/>
+        <source>Quit</source>
+        <translation>Ende</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="261"/>
+        <source>H&amp;W dive computer model.</source>
+        <translation>HW Tauchcomputermodell.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="25"/>
+        <source>Open the Preferences menu.</source>
+        <translation>Einstellungen öffnen.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="134"/>
+        <source>&amp;Help</source>
+        <translation>&amp;Hilfe</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="135"/>
+        <source>Preferences...</source>
+        <translation>Einstellungen...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="172"/>
+        <source>Official web site</source>
+        <translation>Offizielle Webseite</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="244"/>
+        <source>%1 selected.</source>
+        <translation>%1 ausgewählt.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="293"/>
+        <location filename="../MainWindow.cpp" line="312"/>
+        <location filename="../MainWindow.cpp" line="354"/>
+        <location filename="../MainWindow.cpp" line="427"/>
+        <location filename="../MainWindow.cpp" line="456"/>
+        <location filename="../MainWindow.cpp" line="485"/>
+        <location filename="../MainWindow.cpp" line="521"/>
+        <source>Error</source>
+        <translation>Fehler</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="347"/>
+        <source>Settings date &amp; time...</source>
+        <translation>Setzte Datum &amp;&amp; Uhrzeit...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="366"/>
+        <source>Settings name...</source>
+        <translation>Setzte Name...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="438"/>
+        <source>Settings icons...</source>
+        <translation>Setze Icons...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="468"/>
+        <source>Upgrading firmware...</source>
+        <translation>Firmware wird aktualisiert...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="516"/>
+        <source>Request Bluetooth signal strength...</source>
+        <translation>Frage Signalstärke an...</translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <location filename="../Settings.ui" line="234"/>
+        <source>Communication port:</source>
+        <translation>Verbindungsport:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="204"/>
+        <source>Language:</source>
+        <translation>Sprache:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="41"/>
+        <source>If checked, any configuration upload will also setup date and time.</source>
+        <translation>Wenn ausgewählt, wird Datum &amp;&amp; Uhrzeit automatisch gesetzt.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="44"/>
+        <source>Auto setup of date &amp;&amp; time</source>
+        <translation>Automatisches Einstellen von Datum &amp;&amp; Uhrzeit</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="51"/>
+        <source>Force FontLib update</source>
+        <translation>Aktualisierung von Fontlib erzwingen</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="141"/>
+        <source>Update port list with currently connected USB or Bluetooth devices.</source>
+        <translation>Aktualisiere Portliste von USB oder Bluetooth Geräten.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="14"/>
+        <source>OSTC Companion settings</source>
+        <translation>OSTC Companion Einstellungen</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="27"/>
+        <source>Force Firmware update</source>
+        <translation>Aktualisierung von Firmware erzwingen</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="156"/>
+        <source>Restore Defaults</source>
+        <translation>Werkseinstellungen</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="176"/>
+        <source>OK</source>
+        <translation>OK</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="179"/>
+        <source>Return</source>
+        <translation>Zurück</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="192"/>
+        <source>Cancel</source>
+        <translation>Abbrechen</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="195"/>
+        <source>Ctrl+W</source>
+        <translation>Strg+W</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="214"/>
+        <source>Force RTE update</source>
+        <translation>Aktualisierung von RTE erzwingen</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="267"/>
+        <source>Enable fast mode if supported</source>
+        <translation>Schnelle Aktualisierung aktivieren wenn unterstützt</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="156"/>
+        <source>Warning</source>
+        <translation>Warnung</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="157"/>
+        <source>no port</source>
+        <comment>USB connection to OSTC not found</comment>
+        <translation>USB Verbindung zum OSTC nicht gefunden</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="158"/>
+        <source>Did you installed the %1 driver ?</source>
+        <translation>Haben Sie den %1 Treiber installiert?</translation>
+    </message>
+</context>
+</TS>
Binary file Translations/companion_ES.qm has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Translations/companion_ES.ts	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="es">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="../MainWindow.ui" line="28"/>
+        <source>...</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="178"/>
+        <source>Set Date &amp;&amp; Time</source>
+        <translation>Establecer Fecha y Hora</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="146"/>
+        <source>Open or re-open the last selected  USB or Bluetooth port (See Preferences).
+NOTE: make sure to connect the serial cable, or to set the bluetooth mode first...</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="161"/>
+        <source>Connect</source>
+        <translation type="unfinished">Conectar</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="173"/>
+        <source>Set the H&amp;W computer date and time.
+Can be automatic at each connect, if asked to in the Preferences.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="185"/>
+        <source>Change the name displayed on the H&amp;W device.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="189"/>
+        <source>Set Name...</source>
+        <translation>Establecer Nombre...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="196"/>
+        <source>Upload a customization icon.
+Only supported on Frog yet.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="201"/>
+        <source>Set Icon...</source>
+        <translation>Establecer Icono...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="208"/>
+        <source>Check Signal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="215"/>
+        <source>Ask for a firmware file, and upload it to the H&amp;W device.
+Can be done before or after device (re-)connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="219"/>
+        <source>Upgrade Firmware...</source>
+        <translation>Actializar Firmware...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="226"/>
+        <source>Close USB or Bluetooth connection to the device.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="229"/>
+        <source>Close</source>
+        <translation>Cerrar</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="252"/>
+        <source>Quit</source>
+        <translation>Salir</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="261"/>
+        <source>H&amp;W dive computer model.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="25"/>
+        <source>Open the Preferences menu.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="134"/>
+        <source>&amp;Help</source>
+        <translation>&amp;Ayuda</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="135"/>
+        <source>Preferences...</source>
+        <translation>Preferencias...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="172"/>
+        <source>Official web site</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="244"/>
+        <source>%1 selected.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="293"/>
+        <location filename="../MainWindow.cpp" line="312"/>
+        <location filename="../MainWindow.cpp" line="354"/>
+        <location filename="../MainWindow.cpp" line="427"/>
+        <location filename="../MainWindow.cpp" line="456"/>
+        <location filename="../MainWindow.cpp" line="485"/>
+        <location filename="../MainWindow.cpp" line="521"/>
+        <source>Error</source>
+        <translation>Error</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="347"/>
+        <source>Settings date &amp; time...</source>
+        <translation>Ajustes fecha y hora...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="366"/>
+        <source>Settings name...</source>
+        <translation>Ajustes nombre...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="438"/>
+        <source>Settings icons...</source>
+        <translation>Ajustes iconos...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="468"/>
+        <source>Upgrading firmware...</source>
+        <translation>Actualizando firmware...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="516"/>
+        <source>Request Bluetooth signal strength...</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <location filename="../Settings.ui" line="234"/>
+        <source>Communication port:</source>
+        <translatorcomment>(to be kept short)</translatorcomment>
+        <translation>Puerto COM:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="204"/>
+        <source>Language:</source>
+        <translation>Idioma:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="41"/>
+        <source>If checked, any configuration upload will also setup date and time.</source>
+        <translation>Si está activado, cualquier actualización de la configuración modificará también la fecha y hora.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="44"/>
+        <source>Auto setup of date &amp;&amp; time</source>
+        <translation>Auto configuracion de fecha y hora</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="51"/>
+        <source>Force FontLib update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="141"/>
+        <source>Update port list with currently connected USB or Bluetooth devices.</source>
+        <translation>Actualizar la lista de puertos que actualmente tienen conectados dispositivos Bluetooth o USB.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="14"/>
+        <source>OSTC Companion settings</source>
+        <translation>Ajustes OSTC Companion</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="27"/>
+        <source>Force Firmware update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="156"/>
+        <source>Restore Defaults</source>
+        <translatorcomment>(to be kept short)</translatorcomment>
+        <translation>Restablecer config.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="176"/>
+        <source>OK</source>
+        <translation>Aceptar</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="179"/>
+        <source>Return</source>
+        <translation>Volver</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="192"/>
+        <source>Cancel</source>
+        <translation>Cancelar</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="195"/>
+        <source>Ctrl+W</source>
+        <translation>Ctrl+W</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="214"/>
+        <source>Force RTE update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="267"/>
+        <source>Enable fast mode if supported</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="156"/>
+        <source>Warning</source>
+        <translation>Advertencia</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="157"/>
+        <source>no port</source>
+        <comment>USB connection to OSTC not found</comment>
+        <translation>Puerto no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="158"/>
+        <source>Did you installed the %1 driver ?</source>
+        <translation>¿Ha instalado el driver para el dispositivo %1?</translation>
+    </message>
+</context>
+</TS>
Binary file Translations/companion_FR.qm has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Translations/companion_FR.ts	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="fr">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="../MainWindow.ui" line="28"/>
+        <source>...</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="178"/>
+        <source>Set Date &amp;&amp; Time</source>
+        <translation>Date &amp;&amp; Heure</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="146"/>
+        <source>Open or re-open the last selected  USB or Bluetooth port (See Preferences).
+NOTE: make sure to connect the serial cable, or to set the bluetooth mode first...</source>
+        <translation>Ouvre ou ré-ouvre la dernière connection USB ou Bluetooth sélectionnée (Voir les Préférences).
+ATTENTION: connecter le cable USB ou activer Bluetooth avant...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="161"/>
+        <source>Connect</source>
+        <translation>Connection</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="173"/>
+        <source>Set the H&amp;W computer date and time.
+Can be automatic at each connect, if asked to in the Preferences.
+(Valid once device is connected).</source>
+        <translation>Met à jours la date et l&apos;heure sur l&apos;ordi H&amp;W.
+Peut être automatisé à chaque connection, voir les Préférences.
+(Valide une fois que l&apos;ordi est connecté).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="185"/>
+        <source>Change the name displayed on the H&amp;W device.
+(Valid once device is connected).</source>
+        <translation>Change le nom affiché sur l&apos;ordi H&amp;W.
+(Valide une fois que l&apos;ordi est connecté).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="189"/>
+        <source>Set Name...</source>
+        <translation>Nom de l&apos;ordi...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="196"/>
+        <source>Upload a customization icon.
+Only supported on Frog yet.
+(Valid once device is connected).</source>
+        <translation>Configure l&apos;icon affiché sur l&apos;ordi.
+Supporté uniquement pour le Frog.
+(Valide une fois que l&apos;ordi est connecté).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="201"/>
+        <source>Set Icon...</source>
+        <translation>Image...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="208"/>
+        <source>Check Signal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="215"/>
+        <source>Ask for a firmware file, and upload it to the H&amp;W device.
+Can be done before or after device (re-)connection.</source>
+        <translation>Selectionne un fichier de firmware, et l&apos;envois à l&apos;ordi H&amp;W.
+(Valide avant ou aprés la connection à l&apos;ordi).</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="219"/>
+        <source>Upgrade Firmware...</source>
+        <translation>MàJ du firmware...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="226"/>
+        <source>Close USB or Bluetooth connection to the device.</source>
+        <translation>Ferme la connection USB ou Blutooth vers l&apos;ordi.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="229"/>
+        <source>Close</source>
+        <translation>Fermer la connection</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="252"/>
+        <source>Quit</source>
+        <translation>Quitter</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="261"/>
+        <source>H&amp;W dive computer model.</source>
+        <translation>Modèle d&apos;ordi H&amp;W connecté.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="25"/>
+        <source>Open the Preferences menu.</source>
+        <translation>Ouvre le menu Préférences.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="134"/>
+        <source>&amp;Help</source>
+        <translation>&amp;Aide</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="135"/>
+        <source>Preferences...</source>
+        <translation>Préférences...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="172"/>
+        <source>Official web site</source>
+        <translation>Site web officiel</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="244"/>
+        <source>%1 selected.</source>
+        <translation>%1 sélectionné.</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="293"/>
+        <location filename="../MainWindow.cpp" line="312"/>
+        <location filename="../MainWindow.cpp" line="354"/>
+        <location filename="../MainWindow.cpp" line="427"/>
+        <location filename="../MainWindow.cpp" line="456"/>
+        <location filename="../MainWindow.cpp" line="485"/>
+        <location filename="../MainWindow.cpp" line="521"/>
+        <source>Error</source>
+        <translation>Erreur</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="347"/>
+        <source>Settings date &amp; time...</source>
+        <translation>Configuration date &amp; heure...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="366"/>
+        <source>Settings name...</source>
+        <translation>Configuration du nom...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="438"/>
+        <source>Settings icons...</source>
+        <translation>Configuration image...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="468"/>
+        <source>Upgrading firmware...</source>
+        <translation>Mise à jourss du firmware...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="516"/>
+        <source>Request Bluetooth signal strength...</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <location filename="../Settings.ui" line="234"/>
+        <source>Communication port:</source>
+        <translation>Port comm :</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="204"/>
+        <source>Language:</source>
+        <translation>Langue :</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="41"/>
+        <source>If checked, any configuration upload will also setup date and time.</source>
+        <translation>Met à jours la date &amp; l&apos;heure à chaque connection.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="44"/>
+        <source>Auto setup of date &amp;&amp; time</source>
+        <translation>MàJ automatique date &amp;&amp; heure</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="51"/>
+        <source>Force FontLib update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="141"/>
+        <source>Update port list with currently connected USB or Bluetooth devices.</source>
+        <translation>Cherche les connections USB ou Bluetooth, et met la liste à jours.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="14"/>
+        <source>OSTC Companion settings</source>
+        <translation>Préférences OSTC Companion</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="27"/>
+        <source>Force Firmware update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="156"/>
+        <source>Restore Defaults</source>
+        <translation>Paramètres par défaut</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="176"/>
+        <source>OK</source>
+        <translation>OK</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="179"/>
+        <source>Return</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="192"/>
+        <source>Cancel</source>
+        <translation>Annuler</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="195"/>
+        <source>Ctrl+W</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="214"/>
+        <source>Force RTE update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="267"/>
+        <source>Enable fast mode if supported</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="156"/>
+        <source>Warning</source>
+        <translation>Attention</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="157"/>
+        <source>no port</source>
+        <comment>USB connection to OSTC not found</comment>
+        <translation>Pas de port de connection USB visible</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="158"/>
+        <source>Did you installed the %1 driver ?</source>
+        <translation>Est-ce que le driver %1 est installé ?</translation>
+    </message>
+</context>
+</TS>
Binary file Translations/companion_IT.qm has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Translations/companion_IT.ts	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="it">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="../MainWindow.ui" line="28"/>
+        <source>...</source>
+        <translation>...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="178"/>
+        <source>Set Date &amp;&amp; Time</source>
+        <translation>Imposta Data e Ora</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="146"/>
+        <source>Open or re-open the last selected  USB or Bluetooth port (See Preferences).
+NOTE: make sure to connect the serial cable, or to set the bluetooth mode first...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="161"/>
+        <source>Connect</source>
+        <translation type="unfinished">Connetti</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="173"/>
+        <source>Set the H&amp;W computer date and time.
+Can be automatic at each connect, if asked to in the Preferences.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="185"/>
+        <source>Change the name displayed on the H&amp;W device.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="189"/>
+        <source>Set Name...</source>
+        <translation>Imposta Nome...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="196"/>
+        <source>Upload a customization icon.
+Only supported on Frog yet.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="201"/>
+        <source>Set Icon...</source>
+        <translation>Imposta Logo...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="208"/>
+        <source>Check Signal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="215"/>
+        <source>Ask for a firmware file, and upload it to the H&amp;W device.
+Can be done before or after device (re-)connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="219"/>
+        <source>Upgrade Firmware...</source>
+        <translation>Aggiorna Firmware...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="226"/>
+        <source>Close USB or Bluetooth connection to the device.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="229"/>
+        <source>Close</source>
+        <translation>Chiudi</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="252"/>
+        <source>Quit</source>
+        <translation>Esci</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="261"/>
+        <source>H&amp;W dive computer model.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="25"/>
+        <source>Open the Preferences menu.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="134"/>
+        <source>&amp;Help</source>
+        <translation>&amp;Aiuto</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="135"/>
+        <source>Preferences...</source>
+        <translation>Preferenze...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="172"/>
+        <source>Official web site</source>
+        <translation>Sito Web Ufficiale</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="244"/>
+        <source>%1 selected.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="293"/>
+        <location filename="../MainWindow.cpp" line="312"/>
+        <location filename="../MainWindow.cpp" line="354"/>
+        <location filename="../MainWindow.cpp" line="427"/>
+        <location filename="../MainWindow.cpp" line="456"/>
+        <location filename="../MainWindow.cpp" line="485"/>
+        <location filename="../MainWindow.cpp" line="521"/>
+        <source>Error</source>
+        <translation>Errore</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="347"/>
+        <source>Settings date &amp; time...</source>
+        <translation>Imposto Data e Ora...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="366"/>
+        <source>Settings name...</source>
+        <translation>Imposto Nome...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="438"/>
+        <source>Settings icons...</source>
+        <translation>Imposto Logo...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="468"/>
+        <source>Upgrading firmware...</source>
+        <translation>Aggiorno Firmware...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="516"/>
+        <source>Request Bluetooth signal strength...</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <location filename="../Settings.ui" line="234"/>
+        <source>Communication port:</source>
+        <translation>Porta di Connessione:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="204"/>
+        <source>Language:</source>
+        <translation>Lingua:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="41"/>
+        <source>If checked, any configuration upload will also setup date and time.</source>
+        <translation>Seleziona per impostare data e ora ad ogni riconfigurazione.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="44"/>
+        <source>Auto setup of date &amp;&amp; time</source>
+        <translation>Sincronizza data e ora automaticamente</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="51"/>
+        <source>Force FontLib update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="141"/>
+        <source>Update port list with currently connected USB or Bluetooth devices.</source>
+        <translation>Aggiorna la lista dispositivi USB o Bluetooth connessi.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="14"/>
+        <source>OSTC Companion settings</source>
+        <translation>Impostazioni OSTC Companion</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="27"/>
+        <source>Force Firmware update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="156"/>
+        <source>Restore Defaults</source>
+        <translation>Ripristina</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="176"/>
+        <source>OK</source>
+        <translation>OK</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="179"/>
+        <source>Return</source>
+        <translation>Indietro</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="192"/>
+        <source>Cancel</source>
+        <translation>Annulla</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="195"/>
+        <source>Ctrl+W</source>
+        <translation>Ctrl+W</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="214"/>
+        <source>Force RTE update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="267"/>
+        <source>Enable fast mode if supported</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="156"/>
+        <source>Warning</source>
+        <translation>Attenzione</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="157"/>
+        <source>no port</source>
+        <comment>USB connection to OSTC not found</comment>
+        <translation>Dispositivo non rilevato</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="158"/>
+        <source>Did you installed the %1 driver ?</source>
+        <translation>Hai installato %1 drivers?</translation>
+    </message>
+</context>
+</TS>
Binary file Translations/companion_RU.qm has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Translations/companion_RU.ts	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,252 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="../MainWindow.ui" line="28"/>
+        <source>...</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="178"/>
+        <source>Set Date &amp;&amp; Time</source>
+        <translation>Установить дату &amp;&amp; время</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="146"/>
+        <source>Open or re-open the last selected  USB or Bluetooth port (See Preferences).
+NOTE: make sure to connect the serial cable, or to set the bluetooth mode first...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="161"/>
+        <source>Connect</source>
+        <translation type="unfinished">Подключить</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="173"/>
+        <source>Set the H&amp;W computer date and time.
+Can be automatic at each connect, if asked to in the Preferences.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="185"/>
+        <source>Change the name displayed on the H&amp;W device.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="189"/>
+        <source>Set Name...</source>
+        <translation>Установить имя...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="196"/>
+        <source>Upload a customization icon.
+Only supported on Frog yet.
+(Valid once device is connected).</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="201"/>
+        <source>Set Icon...</source>
+        <translation>Установить иконку...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="208"/>
+        <source>Check Signal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="215"/>
+        <source>Ask for a firmware file, and upload it to the H&amp;W device.
+Can be done before or after device (re-)connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="219"/>
+        <source>Upgrade Firmware...</source>
+        <translation>Обновить прошивку...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="226"/>
+        <source>Close USB or Bluetooth connection to the device.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="229"/>
+        <source>Close</source>
+        <translation>Закрыть</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="252"/>
+        <source>Quit</source>
+        <translation>Выход</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="261"/>
+        <source>H&amp;W dive computer model.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.ui" line="25"/>
+        <source>Open the Preferences menu.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="134"/>
+        <source>&amp;Help</source>
+        <translation>&amp;Помощь</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="135"/>
+        <source>Preferences...</source>
+        <translation>Настройки...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="172"/>
+        <source>Official web site</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="244"/>
+        <source>%1 selected.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="293"/>
+        <location filename="../MainWindow.cpp" line="312"/>
+        <location filename="../MainWindow.cpp" line="354"/>
+        <location filename="../MainWindow.cpp" line="427"/>
+        <location filename="../MainWindow.cpp" line="456"/>
+        <location filename="../MainWindow.cpp" line="485"/>
+        <location filename="../MainWindow.cpp" line="521"/>
+        <source>Error</source>
+        <translation>Ошибка</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="347"/>
+        <source>Settings date &amp; time...</source>
+        <translation>Установка даты &amp; времени...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="366"/>
+        <source>Settings name...</source>
+        <translation>Установка имени...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="438"/>
+        <source>Settings icons...</source>
+        <translation>Установка иконок...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="468"/>
+        <source>Upgrading firmware...</source>
+        <translation>Обновление прошивки...</translation>
+    </message>
+    <message>
+        <location filename="../MainWindow.cpp" line="516"/>
+        <source>Request Bluetooth signal strength...</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>Settings</name>
+    <message>
+        <location filename="../Settings.ui" line="234"/>
+        <source>Communication port:</source>
+        <translation>СОМ порт:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="204"/>
+        <source>Language:</source>
+        <translation>Язык:</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="41"/>
+        <source>If checked, any configuration upload will also setup date and time.</source>
+        <translation>Синхронизировать дату и время при выгрузке конфигурации.</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="44"/>
+        <source>Auto setup of date &amp;&amp; time</source>
+        <translation>Автоматически синхронизировать дату &amp;&amp; время</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="51"/>
+        <source>Force FontLib update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="141"/>
+        <source>Update port list with currently connected USB or Bluetooth devices.</source>
+        <translation>Обновить список портов подключения (USB или Bluetooth).</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="14"/>
+        <source>OSTC Companion settings</source>
+        <translation>Настройки OSTC Companion</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="27"/>
+        <source>Force Firmware update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="156"/>
+        <source>Restore Defaults</source>
+        <translatorcomment>Восстановить настройки по умолчанию</translatorcomment>
+        <translation>По умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="176"/>
+        <source>OK</source>
+        <translation>Ок</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="179"/>
+        <source>Return</source>
+        <translatorcomment>(Key code for accepting new settings)</translatorcomment>
+        <translation>Return</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="192"/>
+        <source>Cancel</source>
+        <translation>Отмена</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="195"/>
+        <source>Ctrl+W</source>
+        <translatorcomment>(Key code for rejecting new settings)</translatorcomment>
+        <translation>Ctrl+W</translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="214"/>
+        <source>Force RTE update</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../Settings.ui" line="267"/>
+        <source>Enable fast mode if supported</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="156"/>
+        <source>Warning</source>
+        <translation>Винимание</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="157"/>
+        <source>no port</source>
+        <comment>USB connection to OSTC not found</comment>
+        <translation>COM порт не найден</translation>
+    </message>
+    <message>
+        <location filename="../SettingsDialog.cpp" line="158"/>
+        <source>Did you installed the %1 driver ?</source>
+        <translation>Установлен ли драйвер порта %1 ?</translation>
+    </message>
+</context>
+</TS>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/Exception.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,56 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Exception.cpp
+/// \brief  Generic Exception.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "Exception.h"
+
+#include <QString>
+
+Exception::Exception(const char* msg)
+: _msg(strdup(msg))
+{}
+
+Exception::Exception(const QString& msg)
+    : _msg( strdup(msg.toUtf8().constData()) )
+{}
+
+Exception::~Exception() throw()
+{
+    if( _msg) free((void*)_msg);
+}
+
+const char* Exception::what() const throw()
+{
+    return _msg;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/Exception.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,82 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Exception.h
+/// \brief  Generic Exception.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-12-21  jDG: Creation.
+//  2016-05-24  jDG: BSD-2 version.
+
+#ifndef EXCEPTION_H
+#define EXCEPTION_H
+
+#include <QString>
+#include <exception>
+
+//////////////////////////////////////////////////////////////////////////////
+#define BUILD_OSTC_COMPANION 1
+
+#if defined(BUILD_OSTC_COMPANION)
+#  define EXPORT __declspec(dllexport)
+#else
+#  define EXPORT __declspec(dllimport)
+#endif
+
+class EXPORT Exception
+  : public std::exception
+{
+    const char* const _msg;
+public:
+    explicit Exception(const char* msg);
+    explicit Exception(const QString& msg);
+
+    // Final cleanup.
+    ~Exception() throw() override;
+
+    /// Message contains, in UTF8 format.
+    const char* what() const throw() override;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+#define DEFINE_EXCEPTION(name, parent) \
+struct EXPORT name                                              \
+  : public parent                                               \
+{   explicit inline name(const char* msg)   : parent(msg) {}    \
+    explicit inline name(const QString& msg): parent(msg) {}    \
+}
+
+DEFINE_EXCEPTION(Timeout, Exception);
+    DEFINE_EXCEPTION(ReadTimeout,  Timeout);
+    DEFINE_EXCEPTION(WriteTimeout, Timeout);
+
+#endif // EXCEPTION_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/Export.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,18 @@
+#ifndef EXPORT_H
+#define EXPORT_H
+
+
+#pragma once
+
+// Auf Windows unterscheidet man zwischen DLL-Export (build der DLL) und DLL-Import (Verwendung der DLL)
+#if defined(_WIN32)
+#if defined(BUILDING_OSTC_COMPANION)
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT __declspec(dllimport)
+#endif
+#else
+#define EXPORT
+#endif
+
+#endif // EXPORT_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/Log.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,281 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Log.cpp
+/// \brief  Basic logging tool
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "Utils/Log.h"
+
+#include "Utils/Exception.h"
+#include "Utils/LogConsole.h"
+#include "Utils/LogFile.h"
+
+#include "Utils/LogAppender.h"
+
+#include <QDir>
+#include <QStringList>
+#include <QRegularExpression>
+
+// 2013-08-30 jDG: Because of Linux compile,
+// we need to keep some minimal Qt4.8 compatibility...
+#if !defined(NO_WIDGETS) && QT_VERSION > 0x050100
+#   include <QWindow>
+#endif
+
+// Will be automaticallt set more verbose by File or Console appender,
+// if needed.
+Log::Level Log::minLevel = Log::LEVEL_WARNING;
+QString Log::_applicationPath;
+QString Log::_applicationName;
+
+//////////////////////////////////////////////////////////////////////////////
+
+static QtMessageHandler forwarder = 0;
+static void Qt5Error(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
+{
+    // Spurious error from Qt 5.4.1, when we DO NOT USE any SSL socket...
+    if( ctx.category && strcmp(ctx.category, "qt.network.ssl") == 0 )
+        return;
+
+    switch(type) {
+    case QtDebugMsg:    LogAction(Log::LEVEL_DEBUG,   ctx.file, ctx.line, ctx.function) << msg; break;
+    case QtInfoMsg:     LogAction(Log::LEVEL_TRACE,   ctx.file, ctx.line, ctx.function) << msg; break;
+    case QtWarningMsg:  LogAction(Log::LEVEL_WARNING, ctx.file, ctx.line, ctx.function) << msg; break;
+    case QtCriticalMsg: LogAction(Log::LEVEL_ERROR,   ctx.file, ctx.line, ctx.function) << msg; break;
+    case QtFatalMsg:    LogAction(Log::LEVEL_ERROR,   ctx.file, ctx.line, ctx.function) << msg; break;
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Log::init(int& argc, char* argv[])
+{
+    minLevel = Log::LEVEL_WARNING;
+
+    //---- Get the current application name ----------------------------------
+    if( argc > 0 && _applicationPath.isEmpty() )
+    {
+        _applicationPath = QDir::current().absoluteFilePath(
+                    QString::fromLatin1(argv[0])
+                .replace('\\', '/'));
+        _applicationName = _applicationPath.section('/', -1);
+        _applicationPath = _applicationPath.section('/', 0, -2);
+
+#ifdef Q_OS_WIN32
+        if( _applicationName.endsWith(".exe") )
+            _applicationName = _applicationName.section('.', 0, -2);
+#elif defined(Q_OS_MACX)
+        if( _applicationPath.endsWith("/MacOS") )
+            _applicationPath = _applicationPath.section('/', 0, -2);
+#elif defined(Q_OS_LINUX)
+        // Nothing special, yet.
+#else
+#       error Unknown OS not yet implemented
+#endif
+
+        // Pop "/build/kitName" tail added by the QtCreator build process:
+        if( _applicationPath.section('/', -2, -2) == "build" )
+            _applicationPath = _applicationPath.section('/', 0, -3);
+        if( _applicationPath.section('/', -1, -1) == "build" )
+            _applicationPath = _applicationPath.section('/', 0, -2);
+
+        // Pop /debug or /release tail too (Unix, Visual):
+     //   QRegExp tail("[.]?(debug|release)", Qt::CaseInsensitive);
+        QRegularExpression tail(
+            R"([.]?(debug|release))",
+            QRegularExpression::CaseInsensitiveOption
+            );
+
+    //    if( tail.exactMatch(_applicationPath.section('/', -1, -1)) )
+        QRegularExpressionMatch match =
+            tail.match(_applicationPath.section('/', -1, -1));
+        if (match.hasMatch())
+            _applicationPath = _applicationPath.section('/', 0, -2);
+        if( _applicationName.endsWith(" DEBUG", Qt::CaseInsensitive) )
+            _applicationName = _applicationName.left(_applicationName.length()-6);
+
+        // Pop "/bin/" tail added by the configured build process:
+        if( _applicationPath.section('/', -1) == "bin" )
+            _applicationPath = _applicationPath.section('/', 0, -2);
+
+        if( _applicationName.isEmpty() )
+            _applicationName = "log";
+    }
+
+    //---- Forward Qt's messages ---------------------------------------------
+    forwarder = qInstallMessageHandler(Qt5Error);
+
+    //---- Instanciate 2 appenders -------------------------------------------
+    new LogConsole(argc, argv);
+    new LogFile(argc, argv);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Log::close()
+{
+    LOG_TRACE("Closing logs.");
+    foreach(LogAppender* i, LogAppender::list())
+        delete i;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+Log::Log(Level level, const char *f, int line, const char *function)
+: level(level),
+  line(line),
+  function(function)
+{
+    // Skip trailing "../" in file name (due to QtCreator build dir):
+    while( f && f[0] == '.' && f[1] == '.' && (f[2] == '\\' || f[2] == '/') )
+        f += 3;
+    const_cast<QString&>(file) = QString::fromLatin1(f).replace('\\', '/');
+}
+
+LogAction::LogAction(Level level, const char *file, int line, const char *function)
+: Log(level, file, line, function)
+{}
+
+//////////////////////////////////////////////////////////////////////////////
+LogAction::~LogAction()
+{
+    LogAppender::all(*this);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+LogAction &LogAction::operator<<(char msg)
+{
+    if( message.length() < (LOG_MAX_MESSAGE_LENGTH-1) )
+        message += QChar(msg);  // UTF-8 by default.
+    return *this;
+}
+
+LogAction &LogAction::operator<<(unsigned char msg)
+{
+    if( message.length() < (LOG_MAX_MESSAGE_LENGTH-1) )
+        message += QChar(msg);  // UTF-8 by default.
+    return *this;
+}
+
+LogAction& LogAction::operator<<(const char* msg)
+{
+    if( message.length()+strlen(msg) < (LOG_MAX_MESSAGE_LENGTH) )
+        message += QString::fromUtf8(msg);
+    return *this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+LogAction& LogAction::operator<<(const QString& msg)
+{
+    if( message.length()+msg.length() < (LOG_MAX_MESSAGE_LENGTH) )
+        message += msg;
+    return *this;
+}
+
+LogAction &LogAction::operator <<(const QStringList& list)
+{
+    *this << "{";
+    for(int i=0; i<list.count(); ++i)
+    {
+        if( i > 0 )
+            *this << ", ";
+        *this << list[i];
+    }
+    *this << "}";
+
+    return *this;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+LogAction& LogAction::operator<<(const QByteArray& msg)
+{
+    operator<<(QString::fromUtf8(msg));
+    return *this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+LogAction& LogAction::operator<<(double value)
+{
+    operator<<(QString::number(value));
+    return *this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+LogAction& LogAction::operator<<(int value)
+{
+    operator<<(QString::number(value));
+    return *this;
+}
+
+LogAction& LogAction::operator<<(unsigned int value)
+{
+    operator<<(QString::number(value));
+    return *this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+LogAction &LogAction::operator <<(unsigned long value)
+{
+    operator<<(QString::number((qulonglong)value));
+    return *this;
+}
+
+LogAction &LogAction::operator <<(long value)
+{
+    operator<<(QString::number((qlonglong)value));
+    return *this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+LogAction &LogAction::operator <<(unsigned long long value)
+{
+    operator<<(QString::number(value));
+    return *this;
+}
+
+LogAction &LogAction::operator <<(long long value)
+{
+    operator<<(QString::number(value));
+    return *this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+LogAction &LogAction::operator <<(const void *ptr)
+{
+    operator<<("0x");
+    operator<<(QString::number((qulonglong)ptr, 16));
+
+    return *this;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/Log.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,251 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   Log.h
+/// \brief  Basic logging tool
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-07-01  jDG: Creation.
+//  2016-05-24  jDG: BSD-2 version.
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <QString>
+#include <QStringList>
+#include <QByteArray>
+
+//class QStringList;
+//class QByteArray;
+
+#define LOG_MAX_MESSAGE_LENGTH 2048
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief  Basic logging tool
+///
+///
+/// Provides easy and centralized log messages for the application.
+/// Typical usage:
+/// \code
+///     main(int argc, char* argv[])
+///     {
+///         Log::autoLoad(argc, argv);  // Read XML configuration.
+///         LOG_TRACE("... ");          // Just a tick where we are.
+///
+///         LOG_INFO("Main started");   // Visible message (users can see it).
+///         LOG_TRACE("Now argc=" << argc);
+///         LOG_TRACE("Invisible message, only for log files");
+///         LOG_DEBUG("Invisible, and logged only if run with a "-d" argument");
+///     }
+/// \endcode
+///
+/// \note that message destination can be multiple, and are the responsability
+/// of the LogAppender class hierarchy.
+///
+/// \sa LogAppender, LogConsole, LogFile.
+class  Log
+{
+    /// Application's reference.
+    static QString _applicationPath;
+    static QString _applicationName;
+
+public:
+    //////////////////////////////////////////////////////////////////////////
+    /// \{
+    /// \name Record message and context
+
+    //------------------------------------------------------------------------
+    /// \brief Log level: severity scale.
+    enum Level {
+        LEVEL_NOERROR = 0,
+        LEVEL_DEBUG,        ///< The most verbose one, used only during debug session.
+        LEVEL_TRACE,        ///< Messages to be put in silent log files, for post-mortem analysis.
+        LEVEL_INFO,         ///< General information send to the final user.
+        LEVEL_WARNING,      ///< Error reported to the user (eg. with a Qt warning box. \sa LogAppenderWindow).
+        LEVEL_THROW,        ///< Error that is mean to be catched, and corrected automatically.
+        LEVEL_ERROR         ///< Fatal error that cannot be recovered. The program should halt.
+    };
+
+    //------------------------------------------------------------------------
+    const Level       level;            ///< Event's log level.
+    const QString     file;             ///< Source file that fired the log event.
+    const int         line;             ///< Source line that fired the log event.
+    const QString     function;         ///< Function (and class) that fired the event.
+    QString           message;          ///< The log message itself.
+
+    /// \}
+    //////////////////////////////////////////////////////////////////////////
+    /// \{
+    /// \name Global services
+
+    //------------------------------------------------------------------------
+    /// \brief Minimal error level to log.
+    ///
+    /// Defaults to LEVEL_TRACE.
+    static Level minLevel;
+
+    //------------------------------------------------------------------------
+    /// \brief Initialize the log system, by loading some XML config.
+    ///
+    /// Might be used to change logging configuration of installed
+    /// applications.
+    //------------------------------------------------------------------------
+    /// Initialize the log system, by loading some XML config.
+    static void autoLoad(int argc, char *argv[]);
+
+    //------------------------------------------------------------------------
+    /// \brief Initialize the log system, without configurations.
+    ///
+    /// Then one can instanciate LogAppender classes in a static way.
+    static void init(int& argc, char* argv[]);
+
+    //------------------------------------------------------------------------
+    /// \brief End the log systems, closing all appenders.
+    static void close();
+
+    //------------------------------------------------------------------------
+    /// \brief Utility to retrieve where the executable was installed.
+    static inline QString applicationPath();
+
+    /// \brief Utility to retrieve the executable's name.
+    static inline QString applicationName();
+
+    /// \}
+
+protected:
+    /// \brief Constructor
+    ///
+    /// All message are to be created by a LogAction instance, responsible
+    /// to build-up the message, and to send it to LogAppender instances.
+    Log(Level level, const char *file, int line, const char *function);
+};
+
+inline QString Log::applicationPath() { return _applicationPath; }
+inline QString Log::applicationName() { return _applicationName; }
+
+//////////////////////////////////////////////////////////////////////////////
+/// \brief Creates a Log event.
+///
+/// Uses object's destructor to send the Log into all registered log appenders.
+class  LogAction
+  : public Log
+{
+public:
+    /// Starts a log line, with timestamp and source-stamp.
+    LogAction(Level level, const char* file, int line, const char* function);
+
+    /// Finish (and print) a log line.
+    ~LogAction();
+
+    /// Appends a single char to the log line.
+    LogAction& operator<<(char msg);
+    /// Appends a single char to the log line.
+    LogAction& operator<<(unsigned char msg);
+
+    /// Appends a C string to the log line.
+    LogAction& operator<<(const char* msg);
+    /// Appends a Qt's wide-char string to the log line, converted to Latin1.
+    LogAction& operator<<(const QString& msg);
+    /// Appends a list of Qt's wide-char strings.
+    LogAction& operator<<(const QStringList& msg);
+    /// Appends a Qt's single-byte-string to the log line.
+    LogAction& operator<<(const QByteArray& msg);
+
+    /// Appends a float/double to the log line.
+    LogAction& operator<<(double value);
+    /// Appends a signed int/short/byte to the log line.
+    LogAction& operator<<(int value);
+    /// Appends an unsigned int/short/byte to the log line.
+    LogAction& operator<<(unsigned int value);
+
+    // Resole ambiguities for the C++ compiler.
+    /// Appends a signed long to the log line.
+    LogAction& operator<<(long value);
+    /// Appends an unsigned long to the log line.
+    LogAction& operator<<(unsigned long value);
+    /// Appends a signed long long to the log line.
+    LogAction& operator<<(long long value);
+    /// Appends an unsigned long long to the log line.
+    LogAction& operator<<(unsigned long long value);
+
+    /// Print any pointer as an Hex address:
+    LogAction& operator<<(const void* ptr);
+};
+
+#ifndef LOG_FUNCTION_
+#   if defined __PRETTY_FUNCTION__
+#       define LOG_FUNCTION_ __PRETTY_FUNCTION__
+#   elif defined __FUNCSIG__
+#       define LOG_FUNCTION_ __FUNCSIG__
+#   elif defined __FUNCTION__
+#       define LOG_FUNCTION_ __FUNCTION__
+#   elif defined __func__
+#       define LOG_FUNCTION_ __func__
+#   elif defined LINUX
+#       define LOG_FUNCTION_ __PRETTY_FUNCTION__
+#   else
+#       define LOG_FUNCTION_ __FILE__
+#   endif
+#endif
+
+#define LOG_DEBUG(msg)   do { if( Log::minLevel <= Log::LEVEL_DEBUG  ) LogAction(Log::LEVEL_DEBUG,   __FILE__, __LINE__, LOG_FUNCTION_ ) << msg; } while(0)
+#define LOG_TRACE(msg)   do { if( Log::minLevel <= Log::LEVEL_TRACE  ) LogAction(Log::LEVEL_TRACE,   __FILE__, __LINE__, LOG_FUNCTION_ ) << msg; } while(0)
+#define LOG_INFO(msg)    do { if( Log::minLevel <= Log::LEVEL_INFO   ) LogAction(Log::LEVEL_INFO,    __FILE__, __LINE__, LOG_FUNCTION_ ) << msg; } while(0)
+#define LOG_WARNING(msg) do { if( Log::minLevel <= Log::LEVEL_WARNING) LogAction(Log::LEVEL_WARNING, __FILE__, __LINE__, LOG_FUNCTION_ ) << msg; } while(0)
+#define LOG_ERROR(msg)   do { if( Log::minLevel <= Log::LEVEL_ERROR  ) LogAction(Log::LEVEL_ERROR,   __FILE__, __LINE__, LOG_FUNCTION_ ) << msg; } while(0)
+
+#include <sstream>   // Für std::ostringstream
+#include "Exception.h"
+
+// Hilfsmakros für stringification
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+
+#define LOG_THROW_E(EXCEPTION, msg) \
+    do { \
+        LogAction log(Log::LEVEL_THROW, __FILE__, __LINE__, LOG_FUNCTION_ ); \
+        log << msg; \
+        throw EXCEPTION(log.message); \
+    } while(0)
+
+#define LOG_THROW(msg) \
+    do { \
+        LogAction log(Log::LEVEL_THROW, __FILE__, __LINE__, LOG_FUNCTION_ ); \
+        log << msg; \
+        throw ::Exception(log.message); \
+    } while(0)
+
+#define assert(e) \
+    do { if( !(e) )                 \
+        LOG_THROW("Assert: " #e);   \
+    } while(0)
+
+#endif // LOG_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogAppender.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,114 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   LogAppender.cpp
+/// \brief  Put Log message somewhere.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "Utils/LogAppender.h"
+
+#include <QDateTime>
+#include <QDir>
+#include <QSettings>
+
+//////////////////////////////////////////////////////////////////////////////
+/// List of all currently defined appenders
+/// NOTE: Use singleton to avoid messing-up with DLL on windows.
+
+QList<LogAppender *>& LogAppender::list()
+{
+    static QList<LogAppender *> appenderList;
+    return appenderList;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Add new instance to the list.
+// TODO: Lock for threads-safe ?
+LogAppender::LogAppender(int, char *[])
+  : _minLevel(Log::LEVEL_INFO)
+{
+    //---- Manage list of all appenders.
+    list().push_back(this);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Remove defunct instance from the list.
+// TODO: Lock for threads-safe ?
+LogAppender::~LogAppender() {
+    list().removeAll(this);
+}
+
+void LogAppender::setMinLevel(Log::Level level)
+{
+    _minLevel = level;
+    if( level < Log::minLevel )
+        Log::minLevel = level;
+}
+
+void LogAppender::setFormat(const char *format)
+{
+    _format = format;
+}
+
+QString LogAppender::format(const Log &log) const
+{
+    QString line = _format.isEmpty()
+            ? QString::fromUtf8(defaultFormat())
+            : QString::fromUtf8(_format);
+
+    line.replace("%d", QDate::currentDate().toString("yyyy/MM/dd").toLatin1());
+    line.replace("%t", QTime::currentTime().toString("hh:mm:ss.zzz").toLatin1());
+    line.replace("%o", (log.level == Log::LEVEL_TRACE  ) ? "TRACE  " :
+                       (log.level == Log::LEVEL_DEBUG  ) ? "DEBUG  " :
+                       (log.level == Log::LEVEL_INFO   ) ? "INFO   " :
+                       (log.level == Log::LEVEL_WARNING) ? "WARNING" :
+                       (log.level == Log::LEVEL_THROW  ) ? "THROW  " :
+                       (log.level == Log::LEVEL_ERROR  ) ? "ERROR  " : "-------");
+    line.replace("%l", QString::number(log.line).toLatin1());
+    line.replace("%f", log.file);
+    line.replace("%F", log.file.section('/', -1));
+    line.replace("%p", log.function);
+    line.replace("%m", log.message);
+
+    return line;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Send to all instanciated appenders.
+// TODO: Lock for threads-safe ?
+void LogAppender::all(const Log& log)
+{
+    foreach(LogAppender* i, list())
+    {
+        if(  log.level >= i->_minLevel )
+            (*i)(log);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogAppender.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,127 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   LogAppender.h
+/// \brief  Put Log message somewhere.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-11-15  jDG: Creation.
+//  2016-05-24  jDG: BSD-2 version.
+
+#include "Utils/Log.h"
+
+#ifndef LOGAPPENDER_H
+#define LOGAPPENDER_H
+
+#include <QByteArray>
+#include <QString>
+
+#include <QList>
+
+class LogFilter;
+
+//////////////////////////////////////////////////////////////////////////////
+
+class EXPORT LogAppender
+{
+    /// Send a message to this appender.
+    virtual void operator()(const Log& log) = 0;
+
+    /// Log::close() needs an access to the list, to remove all appenders.
+    friend void Log::close();
+
+    /// Manage a list of all existing appenders.
+    static QList<LogAppender *> &list();
+
+protected:
+    /// How to dislay logged data ?
+    QByteArray _format;
+    Log::Level _minLevel;
+
+    /// Default formating string for that appender.
+    virtual const char* type() const = 0;
+    virtual const char* defaultFormat() const = 0;
+    virtual Log::Level defaultMinLevel() const = 0;
+
+    /// Do the substtutions
+    /// Use the string set by setFormat() to format the \p log event.
+    QString format(const Log& log) const;
+
+public:
+    LogAppender(int argc, char *argv[]);
+    virtual ~LogAppender();
+
+    //------------------------------------------------------------------------
+    /// Filter messages by error level.
+    /// Sets the minimum level to use for this appender.
+    /// \p level is one of:
+    /// * Log::LEVEL_DEBUG   : The most verbose one, used only during debug session.
+    /// * Log::LEVEL_TRACE   : Messages to be put in silent log files, for post-mortem analysis.
+    /// * Log::LEVEL_INFO    : General information send to the final user.
+    /// * Log::LEVEL_WARNING : Error reported to the user (eg. with a Qt warning box. \sa LogAppenderWindow).
+    /// * Log::LEVEL_THROW   : Error that is mean to be catched, and corrected automatically.
+    /// * Log::LEVEL_ERROR   : Fatal error that cannot be recovered. The program should halt.
+    ///
+    /// The default depends from the appender.
+    void setMinLevel(Log::Level level);
+
+    //------------------------------------------------------------------------
+    /// Set current formating.
+    ///
+    /// How to format log line once appended.
+    /// Default depends of the appender used (\sa defaultFormat()).
+    /// Setting a null string returns to the appender's default.
+    ///
+    /// Valide substitutions are:
+    /// * %o : log level (one of DEBUG, TRACE, INFO, THROW, WARNING, ERROR).
+    /// * %d : log's date.
+    /// * %t : log's time.
+    /// * %f : source file where the error is logged (just trailling filename)
+    /// * %F : source file where the error is logged (full path)
+    /// * %l : source line where the error is logged.
+    /// * %p : the function that fired the log message.
+    /// * %m : the log message itself.
+    ///
+    /// Typical usage:
+    /// \code
+    ///     LogAppenderMemory mem(0,0);
+    ///     mem.setFormat("[%o] %f:%l %m");
+    ///     ...
+    ///     LOG_TRACE("Here");
+    /// \endcode
+    void setFormat(const char* format);
+
+    //------------------------------------------------------------------------
+    /// Send a message to all existing appenders.
+    static void all(const Log& log);
+};
+
+#endif // LOGAPPENDER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogConsole.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,144 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   LogConsole.cpp
+/// \brief  Put Log message onto standard console.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "LogConsole.h"
+
+#include "Log.h"
+
+//#include <QRegExp>
+#include <QRegularExpression>
+
+#include <stdio.h>
+
+#ifdef Q_OS_WIN32
+#   include <windows.h>
+#   include <io.h>
+#   include <fcntl.h>
+#   include <iostream>
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+// Nothing to do ...
+LogConsole::LogConsole(int argc, char *argv[])
+  : LogAppender(argc, argv)
+{
+    setMinLevel(defaultMinLevel());
+
+    for(int i=1; i<argc; ++i)
+    {
+        if( strcmp("-dd", argv[i]) == 0 )
+            setMinLevel(Log::LEVEL_DEBUG);
+        else if( strcmp("-d", argv[i]) == 0 )
+            setMinLevel(Log::LEVEL_TRACE);
+    }
+}
+
+LogConsole::~LogConsole()
+{}
+
+//////////////////////////////////////////////////////////////////////////////
+
+const char *LogConsole::type() const
+{
+    return "Console";
+}
+
+Log::Level LogConsole::defaultMinLevel() const
+{
+#ifdef DEBUG
+    return Log::LEVEL_TRACE;
+#else
+    return Log::LEVEL_INFO;
+#endif
+}
+
+const char *LogConsole::defaultFormat() const
+{
+    return "%t %m";
+}
+
+void LogConsole::operator()(const Log& log)
+{
+    QString msg = format(log);
+
+    // 2016-04-19 jDG:
+    // Remove bad tags from console output... but keep it nice:
+#if 0
+    msg.replace("<br>", "\n")
+       .replace(QRegExp("</?table[^>]*>"), "--------------------------------")
+       .replace(QRegExp("</tr>(?!\n)"),    "\n")
+       .replace("</td>", "\t");
+
+    QRegExp re("</?[a-zA-Z]+[^>]*/?>");
+    while( msg.contains(re) )
+        msg.replace(re, "");
+#endif
+    msg.replace("<br>", "\n")
+        .replace(QRegularExpression("</?table[^>]*>", QRegularExpression::CaseInsensitiveOption),
+                 "--------------------------------")
+        .replace(QRegularExpression("</tr>(?!\n)"),
+                 "\n")
+        .replace("</td>", "\t");
+
+    // allgemeiner Tag-Entferner
+    QRegularExpression re("</?[a-zA-Z]+[^>]*/?>");
+
+    while (re.match(msg).hasMatch()) {
+        msg.replace(re, "");
+    }
+    // 2014-03-21 jDG:
+    //
+    // In UNIX print everything in UTF-8, so that:
+    // * junitWrapper get it.
+    // * it is compatible with Linux console.
+    //
+    // In Windows, the console DOES NOT accept UTF_8, we just got giberrish...
+    // So defaults to Windows UNICODE which is Qt3's ucs2() or Qt4 toWCharArray()
+    //
+    // Note: it is correct for most Europe chars, but in corean (TCT example),
+    //       the line is truncated.
+
+#ifdef WIN32
+    wchar_t buffer[LOG_MAX_MESSAGE_LENGTH];
+    int len = msg.left(LOG_MAX_MESSAGE_LENGTH-2).toWCharArray(buffer);
+    buffer[len] = 0;
+    fprintf(stderr, "%S\n", buffer);
+#else
+    QByteArray utf8 = msg.toUtf8();
+    fprintf(stderr, "%s\n", utf8.constData());
+#endif
+    fflush(stderr);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogConsole.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,60 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   LogConsole.h
+/// \brief  Put Log message onto standard console.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-11-15  jDG: Creation.
+//  2016-05-24  jDG: BSD-2 version.
+
+#ifndef LOGAPPENDERCONSOLE_H
+#define LOGAPPENDERCONSOLE_H
+
+#include "Utils/LogAppender.h"
+class XmlTree;
+
+//////////////////////////////////////////////////////////////////////////////
+
+struct EXPORT LogConsole
+  : public LogAppender
+{
+    LogConsole(int argc, char *argv[]);
+    ~LogConsole();
+
+    const char* type() const override;
+    Log::Level defaultMinLevel() const override;
+    const char* defaultFormat() const override;
+
+    void operator()(const Log& log) override;
+};
+
+#endif // LOGAPPENDERCONSOLE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogEvent.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////
+/// \file   LogEvent.cpp
+/// \brief  User event to pass async messages to the console widget.
+/// \author JD Gascuel.
+/// \copyright (c) 2015 JD Gascuel. All rights reserved.
+/// $Id$
+///////////////////////////////////////////////////////////////////////////////
+
+#include "LogEvent.h"
+#include <QEvent>
+
+//////////////////////////////////////////////////////////////////////////////
+
+QEvent::Type LogEvent::_logEventType = QEvent::None;
+
+//////////////////////////////////////////////////////////////////////////////
+
+LogEvent::LogEvent(const QString& message)
+  : QEvent(_logEventType),
+    _message(message)
+{
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+LogEvent* LogEvent::make(const QString& message)
+{
+    if( _logEventType == QEvent::None )
+        _logEventType = (QEvent::Type) QEvent::registerEventType();
+    return new LogEvent(message);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogEvent.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,30 @@
+/////////////////////////////////////////////////////////////////////////////
+/// \file   LogEvent.h
+/// \brief  User event to pass async messages to the console widget.
+/// \author JD Gascuel.
+/// \copyright (c) 2015 JD Gascuel. All rights reserved.
+/// $Id$
+///////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2015-07-06 : Creation
+
+#ifndef LOGEVENT_H
+#define LOGEVENT_H
+
+#include <QEvent>
+#include <QString>
+
+class LogEvent : public QEvent
+{
+    static QEvent::Type _logEventType;
+    LogEvent(const QString& message);
+
+    QString _message;
+
+public:
+    static LogEvent* make(const QString& message);
+
+    inline QString what() const { return _message; }
+};
+
+#endif // LOGEVENT_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogFile.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,193 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   LogFile.cpp
+/// \brief  Put Log message into a log file.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "Utils/LogFile.h"
+
+#include <QCoreApplication>
+#include <QDir>
+#include <QMutex>
+#include <QSettings>
+#include <QRegularExpression>
+
+#ifndef WIN32
+#   include <sys/stat.h>            // umask
+#else
+#   include <io.h>                  // umask
+#endif
+
+static QMutex mutex;
+
+//////////////////////////////////////////////////////////////////////////////
+
+LogFile::LogFile(int argc, char *argv[])
+  : LogAppender(argc, argv),
+    _holdFiles(5),
+    _fileNameFormat("%a-%n.txt"),
+    _logFile(NULL)
+{
+    setMinLevel(defaultMinLevel());
+    for(int i=1; i<argc; ++i)
+    {
+        if( strcmp("-dd", argv[i]) == 0 )
+            setMinLevel(Log::LEVEL_DEBUG);
+        else if( strcmp("-d", argv[i]) == 0 )
+            setMinLevel(Log::LEVEL_TRACE);
+    }
+}
+
+LogFile::~LogFile()
+{
+    if( _logFile )
+        fclose(_logFile);
+    _logFile = NULL;
+}
+
+const char *LogFile::type() const
+{
+    return "File";
+}
+
+Log::Level LogFile::defaultMinLevel() const
+{
+    return Log::LEVEL_TRACE;
+}
+
+
+const char* LogFile::defaultFormat() const
+{
+    return "[%d %t] %o %F:%l: %m";
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QString LogFile::filePath(bool pattern) const
+{
+    QString path =
+#ifdef Q_OS_MAC
+        QString("/tmp")
+#else
+        QDir::tempPath()
+#endif
+        + "/"
+        + _fileNameFormat;
+    path.replace("%n", pattern ? "*" : "001")
+        .replace("%a", Log::applicationName());
+    return path;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void LogFile::cleanup()
+{
+    QString name = filePath(true);
+    QDir dir(name.section('/', 0, -2));
+    name = name.section('/', -1);
+
+    // ---- Make sure the directory exists, and is writable by everyone.
+    int oldUMask = umask(0);
+    if( ! dir.exists() )
+        QDir::current().mkdir( dir.absolutePath() );
+
+    // ---- Then perform the cleanup, if necessary
+    QFileInfoList list = dir.entryInfoList(QStringList(name), QDir::Files, QDir::Name | QDir::Reversed);
+   // QRegExp parse(name.replace("*", "([0-9]+)"));
+    QRegularExpression parse(name.replace("*", "([0-9]+)"));
+
+    for(int i=0; i<list.count(); ++i)
+    {
+        QString log = list[i].fileName();
+#if 0
+        if( parse.exactMatch(log) )
+        {
+            int num = parse.cap(1).toInt();
+            if( num >= _holdFiles )
+                dir.remove(log);
+            else
+            {
+                QString newName = _fileNameFormat;
+                newName.replace("%n", QString().sprintf("%03d", num+1))
+                       .replace("%a", Log::applicationName());
+                dir.rename(log, newName);
+            }
+        }
+#endif
+        QRegularExpressionMatch match = parse.match(log); // match erzeugen
+        if (match.hasMatch())                             // entspricht exactMatch
+        {
+            int num = match.captured(1).toInt();          // entspricht cap(1)
+
+            if (num >= _holdFiles)
+                dir.remove(log);
+            else
+            {
+                QString newName = _fileNameFormat;
+  //              newName.replace("%n", QString().sprintf("%03d", num+1))
+  //                  .replace("%a", Log::applicationName());
+                QString number = QString::number(num + 1).rightJustified(3, '0');
+                newName.replace("%n", number)
+                    .replace("%a", Log::applicationName());
+                dir.rename(log, newName);
+            }
+        }
+    }
+
+    umask(oldUMask);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void LogFile::operator()(const Log &log)
+{
+    // stdio FILE is not reentrant. So a lock here is mandatory.
+    QMutexLocker locker(&mutex);
+
+    //---- Open the current log file -----------------------------------------
+    if( ! _logFile )
+    {
+        //---- Clean old log files
+        cleanup();
+        //---- Then open the new one.
+        QString name = filePath();
+        _logFile = fopen(name.toLocal8Bit().constData(), "at+");
+    }
+
+    //---- Output log line ---------------------------------------------------
+    if( _logFile )  // File open succeded ?
+    {
+        QString line = format(log);
+        fprintf(_logFile, "%s\n", line.toUtf8().constData());
+        fflush(_logFile);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/LogFile.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,68 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   LogFile.h
+/// \brief  Put Log message into a log file.
+/// \author JD Gascuel.
+/// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-11-15  jDG: Creation.
+//  2016-05-24  jDG: BSD-2 version.
+
+#ifndef LOGAPPENDERROLLINGFILE_H
+#define LOGAPPENDERROLLINGFILE_H
+
+#include "Utils/LogAppender.h"
+#include <stdio.h>
+
+class QSettings;
+
+//////////////////////////////////////////////////////////////////////////////
+class EXPORT LogFile
+    : public LogAppender
+{
+    int     _holdFiles;
+    QString _fileNameFormat;
+    FILE*   _logFile;
+
+public:
+    LogFile(int argc, char *argv[]);
+    ~LogFile() override;
+
+    virtual QString filePath(bool pattern = false) const;
+    virtual void cleanup();
+
+    const char* type() const override;
+    void operator ()(const Log &log) override;
+    const char *defaultFormat() const override;
+    Log::Level defaultMinLevel() const override;
+};
+
+#endif // LOGAPPENDERROLLINGFILE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/ProgressEvent.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,86 @@
+/////////////////////////////////////////////////////////////////////////////
+/// \file   ProgressEvent.cpp
+/// \brief  User event to pass async progress-bar updates to the main window.
+/// \author JD Gascuel.
+/// \copyright (c) 2015 JD Gascuel. All rights reserved.
+/// $Id$
+///////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+
+#include "Utils/ProgressEvent.h"
+
+#include "Utils/Log.h"
+
+#include <QCoreApplication>
+
+///////////////////////////////////////////////////////////////////////////////
+
+QEvent::Type ProgressEvent::_logEventType = QEvent::None;
+
+///////////////////////////////////////////////////////////////////////////////
+
+ProgressEvent::ProgressEvent(int current, int range)
+  : QEvent(_logEventType),
+    current(current),
+    maximum(range)
+{}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ProgressEvent* ProgressEvent::make(int current, int range)
+{
+    if( _logEventType == QEvent::None )
+        _logEventType = (QEvent::Type) QEvent::registerEventType();
+    return new ProgressEvent(current, range);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ProgressManager::ProgressManager()
+  : _main(0)
+{}
+
+ProgressManager* ProgressManager::getInstance()
+{
+    static ProgressManager manager;
+    return & manager;
+}
+
+void ProgressManager::setMainWindow(QObject* main)
+{
+    _main = main;
+    assert(main);
+}
+
+void ProgressManager::post(int current, int range)
+{
+    assert(_main);
+    qApp->postEvent(_main, ProgressEvent::make(current, range));
+    qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 10);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utils/ProgressEvent.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,87 @@
+/////////////////////////////////////////////////////////////////////////////
+/// \file   ProgressEvent.h
+/// \brief  User event to pass async progress-bar updates to the main window.
+/// \author JD Gascuel.
+/// \copyright (c) 2015 JD Gascuel. All rights reserved.
+/// $Id$
+///////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2015-07-14  jDG: Creation.
+//  2016-05-24  jDG: BSD-2 version.
+
+#ifndef PROGRESS_EVENT_H
+#define PROGRESS_EVENT_H
+
+#include <QEvent>
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ProgressEvent : public QEvent
+{
+    static QEvent::Type _logEventType;
+
+    ProgressEvent(int current, int maximum);
+
+public:
+    const int current;
+    const int maximum;
+
+    static ProgressEvent* make(int current, int maximum);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class  ProgressManager
+{
+    QObject* _main;
+
+public:
+    static ProgressManager* getInstance();
+
+    ProgressManager();
+
+    /// \brief Define who manage ProgressEvent events.
+    void setMainWindow(QObject* main);
+
+    /// \brief Send (async) progress updates to the main progress bar.
+    /// Use ProgressEvent class to post updates and resets to the progress
+    /// bar, displayed by the main thread.
+    /// When \a current > \a range, this is a reset...
+    void post(int current = 101, int range = 100);
+};
+
+#define PROGRESS(current, range) \
+    ProgressManager::getInstance()->post(current, range)
+#define PROGRESS_THROTTLE() \
+    ProgressManager::getInstance()->post(0, 0)
+#define PROGRESS_RESET() \
+    ProgressManager::getInstance()->post()
+
+#endif // PROGRESS_EVENT_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VERSION.txt	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,4 @@
+MAJOR=3
+MINOR=3
+PATCH=1
+BETA=0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/editlogdialog.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,413 @@
+#include "editlogdialog.h"
+#include "ui_LogEditor.h"
+#include "MainWindow.h"     // Needed to propagare retranslate()
+#include "Utils/Log.h"
+
+#include "HardwareOperations.h"
+
+#include <QApplication>
+#include <QDialogButtonBox>
+#include <QDir>
+#include <QLibraryInfo>
+#include <QPushButton>
+#include <QTranslator>
+#include <QTableWidget>
+
+
+#ifdef Q_OS_WIN
+#   define NOMINMAX 1
+#   include <Windows.h>
+#   undef NOMINMAX
+#endif
+#define HEADER2OFFSET 0x400
+
+EditLogDialog::EditLogDialog(QWidget* parent, HardwareOperations *op)
+  : QDialog(parent),
+    _ui(new Ui::editLogWnd),
+    _op(op)
+{
+    uint32_t index, index2;
+    uint32_t sizeY = 0;
+    _ui->setupUi(this);
+    QTableWidget* headerView = _ui->SectorView;
+    QTableWidget* sampleView = _ui->SampleView;
+
+    _ui->textBrowser_2->setTabStopDistance(
+        QFontMetricsF(_ui->textBrowser_2->font()).horizontalAdvance(' ') * 4
+        );
+    headerView->horizontalHeader()->setMinimumSectionSize(8);
+    headerView->verticalHeader()->setMinimumSectionSize(8);
+    headerView->horizontalHeader()->setDefaultSectionSize(8);
+    headerView->verticalHeader()->setDefaultSectionSize(8);
+    headerView->setColumnWidth(0,8);
+    headerView->setRowHeight(0,8);
+    headerView->horizontalHeader()->hide();
+    headerView->verticalHeader()->hide();
+
+    sampleView->horizontalHeader()->setMinimumSectionSize(8);
+    sampleView->verticalHeader()->setMinimumSectionSize(8);
+    sampleView->horizontalHeader()->setDefaultSectionSize(8);
+    sampleView->verticalHeader()->setDefaultSectionSize(8);
+    sampleView->setColumnWidth(0,8);
+    sampleView->setRowHeight(0,8);
+    sampleView->horizontalHeader()->hide();
+    sampleView->verticalHeader()->hide();
+
+    headerView->setRowCount(16);
+    headerView->setColumnCount(16);
+
+    sampleView->setRowCount(12);
+    sampleView->setColumnCount(16);
+
+
+    HeaderBuffer = new unsigned char[0x8000*8 + 1];      // 64k Headerbuffer + lastDiveindex
+    SampleBuffer = new unsigned char[0xC00000 + 4];     // 12MB Samplebuffer + nextSampleAddr
+
+    headerView->resize(8*16+2,8*16+2);
+    sampleView->resize(8*16+2,8*12+2);
+    sizeY = (uint32_t)(headerView->geometry().width()) / 16;
+
+    if(sizeY < 8)
+    {
+        sizeY = 8;
+    }
+
+    for(index = 0; index <16; index++)
+    {
+        headerView->setColumnWidth(index,sizeY);
+        headerView->setRowHeight(index,sizeY);
+        for(index2 = 0; index2 < 16; index2++)
+        {
+
+            item[index * 16 + index2] = new QTableWidgetItem(" ");
+            headerView->setItem(index, index2, item[index * 16 + index2]);
+        }
+    }
+
+    for(index = 0; index <12; index++)
+    {
+        sampleView->setColumnWidth(index,sizeY);
+        sampleView->setRowHeight(index,sizeY);
+        for(index2 = 0; index2 < 16; index2++)
+        {
+
+            sampleitem[index * 16 + index2] = new QTableWidgetItem(" ");
+            sampleView->setItem(index, index2, sampleitem[index * 16 + index2]);
+        }
+    }
+
+    headerView->show();
+    headerView->setShowGrid(true);
+
+    sampleView->show();
+    sampleView->setShowGrid(true);
+
+}
+
+EditLogDialog::~EditLogDialog()
+{
+    uint32_t index;
+
+    for(index = 0; index <256; index++)
+    {
+        delete item[index];
+    }
+    for(index = 0; index <192; index++)
+    {
+        delete sampleitem[index];
+    }
+    delete _ui;
+    delete HeaderBuffer;
+    delete SampleBuffer;
+}
+
+
+void EditLogDialog::on_WriteAllHeader_clicked()
+{
+    _op->writeAllHeader(HeaderBuffer);
+}
+
+void EditLogDialog::on_pushButton_clicked()
+{
+    Q_ASSERT( _op );
+
+    HeaderBuffer[0] = 0;
+
+    try {
+            LOG_INFO(tr("Request All Headers..."));
+            _op->getAllHeader(HeaderBuffer);
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+    if(HeaderBuffer[0] != 0)
+    {
+        LOG_INFO(tr("Got something"));
+        updateHeaderStatus();
+    }
+}
+
+
+
+void EditLogDialog::on_HeaderUsage_valueChanged(int value)
+{
+
+}
+
+void EditLogDialog::on_ReadAllSamples_clicked()
+{
+    try {
+            LOG_INFO(tr("Request All Samples..."));
+            _op->getAllSamples(SampleBuffer);
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+void EditLogDialog::on_WriteAllSamples_clicked()
+{
+    try {
+            LOG_INFO(tr("Request All Samples..."));
+            _op->writeAllSamples(SampleBuffer);
+    }
+    catch(const std::exception& e) {
+        LOG_INFO( QString("<bg><font color='red'>%1</font></color>: %2")
+                    .arg(tr("Error"))
+                    .arg(e.what()) );
+    }
+}
+
+void EditLogDialog::on_pushButton_2_clicked()
+{
+    qint64 length;
+    QFile   dumpFile;
+     dumpFile.setFileName("Log_Dump.bin");
+    if( ! dumpFile.open(QIODevice::WriteOnly) )
+    {
+        LOG_THROW( "Cannot create dump file " );
+    }
+    else
+    {
+        length = 0x8000*8 + 1;
+        dumpFile.write((const char*)HeaderBuffer,length);
+        length = 0xC00000 + 4;
+        dumpFile.write((const char*)SampleBuffer,length);
+        dumpFile.close();
+    }
+}
+
+void EditLogDialog::on_LoadDump_clicked()
+{
+    qint64 length;
+    QFile   dumpFile;
+     dumpFile.setFileName("Log_Dump.bin");
+    if( ! dumpFile.open(QIODevice::ReadOnly) )
+    {
+        LOG_THROW( "Cannot read dump file " );
+    }
+    else
+    {
+        length = 0x8000*8 + 1;
+        dumpFile.read((char*)HeaderBuffer,length);
+        length = 0xC00000 + 4;
+        dumpFile.read((char*)SampleBuffer,length);
+        dumpFile.close();
+
+        updateHeaderStatus();
+        updateSampleStatus();
+    }
+}
+
+void EditLogDialog::updateHeaderStatus()
+{
+    QProgressBar* w = _ui->HeaderUsage;
+    QTableWidget* sv = _ui->SectorView;
+    SLogbookHeader* LogInfo;
+    SSmallHeader* smallHeader;
+    unsigned char HeaderInUse = 0;
+    uint32_t index, index2;
+    uint32_t sampleStartAddr, sampleEndAddr, sampleLength;
+    int row;
+    int colum;
+
+    for(index = 0; index < 16; index++)
+    {
+        for(index2 = 0; index2 < 16; index2++)
+        {
+            if((HeaderBuffer[(0x800 * (index * 16 + index2 )) + HEADER2OFFSET] == 0xFA)
+                && (HeaderBuffer[(0x800 * (index * 16 + index2 )) + HEADER2OFFSET + 1] == 0xFA))
+            {
+                HeaderInUse++;
+                LogInfo = (SLogbookHeader*)(HeaderBuffer+(0x800 * (index*16+index2))+0x400);
+                sampleEndAddr = (LogInfo->profileLength[2]<<16) + (LogInfo->profileLength[1]<<8) + LogInfo->profileLength[0];
+                if( sampleEndAddr == 0)
+                {
+                    sv->item(index,index2)->setBackground(Qt::black);
+                }
+                else
+                {
+                    LogInfo = (SLogbookHeader*)(HeaderBuffer+(0x800 * (index*16+index2)));
+                    sampleStartAddr = (LogInfo->pBeginProfileData[2]<<16) + (LogInfo->pBeginProfileData[1]<<8) + LogInfo->pBeginProfileData[0];
+                    smallHeader = (SSmallHeader*) &SampleBuffer[sampleStartAddr-0x100000];
+                    sampleLength = (smallHeader->profileLength[2] << 16)+ (smallHeader->profileLength[1] << 8) + smallHeader->profileLength[0];
+                    if(sampleLength == sampleEndAddr) // - sampleStartAddr))
+                    {
+                        sv->item(index,index2)->setBackground(Qt::green);
+                    }
+                    else {
+                        sv->item(index,index2)->setBackground(Qt::red);
+                    }
+                }
+
+            }
+            else
+            {
+                sv->item(index,index2)->setBackground(Qt::white);
+            }
+        }
+    }
+    row =(HeaderBuffer[(0x8000 * 8)])/16;
+    colum =(HeaderBuffer[(0x8000 * 8)] % 16);
+     sv->item(row,colum)->setBackground(Qt::blue);
+     w->setMaximum(256);
+     w->setValue(HeaderInUse);
+}
+
+
+
+
+
+void EditLogDialog::on_SectorView_cellClicked(int row, int column)
+{
+
+}
+
+void EditLogDialog::updateSampleStatus()
+{
+    uint8_t row,colum;
+    uint32_t index;
+    QTableWidget* sv = _ui->SampleView;
+    QProgressBar* w = _ui->SampleUsage;
+    uint8_t SamplesInUse = 0;
+
+    for(index = 0; index < 192; index++)
+    {
+        row = index / 16;
+        colum =index % 16;
+
+        if(SampleBuffer[index * 0x10000] != 0xFF)               /* used */
+        {
+            SamplesInUse++;
+            if(SampleBuffer[index * 0x10000 + 0xFFFF] == 0xFF)  /* open */
+            {
+                sv->item(row,colum)->setBackground(Qt::blue);
+            }
+            else
+            {
+                sv->item(row,colum)->setBackground(Qt::green);  /* closed */
+            }
+        }
+        else
+        {
+            sv->item(row,colum)->setBackground(Qt::white);      /* empty */
+        }
+    }
+    w->setMaximum(192);
+    w->setValue(SamplesInUse);
+}
+
+
+
+void EditLogDialog::on_SectorView_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn)
+{
+    SLogbookHeader* LogInfo;
+    SLogbookHeader* LogInfo2nd;
+    QTextEdit* tf = _ui->textBrowser_2;
+    QString InfoText;
+    QTableWidget* sv = _ui->SampleView;
+    uint32_t sampleAddrStart = 0;
+    uint32_t sampleAddrEnd = 0;
+    uint8_t rowidx, columidx;
+
+    tf->setReadOnly(true);
+
+    LogInfo = (SLogbookHeader*)(HeaderBuffer+(0x800 * (currentRow*16+currentColumn)));
+    LogInfo2nd = (SLogbookHeader*)(HeaderBuffer+(0x800 * (currentRow*16+currentColumn)) +0x400);
+
+    updateSampleStatus();
+
+    if(LogInfo->diveHeaderStart == 0xFAFA)
+    {
+        sampleAddrStart = (LogInfo->pBeginProfileData[2]<<16) + (LogInfo->pBeginProfileData[1]<<8) + LogInfo->pBeginProfileData[0];
+        sampleAddrEnd = (LogInfo2nd->pEndProfileData[2]<<16) + (LogInfo2nd->pEndProfileData[1]<<8) + LogInfo2nd->pEndProfileData[0];
+
+ //       InfoText.sprintf("Header: %d \nNummer: %d\nSamplestart 0x%x\nSampleend 0x%x", currentRow*16+currentColumn, LogInfo->diveNumber,sampleAddrStart,sampleAddrEnd);
+        InfoText = QString::asprintf(
+            "Header: %d \nNummer: %d\nSamplestart 0x%x\nSampleend 0x%x",
+            currentRow*16 + currentColumn,
+            LogInfo->diveNumber,
+            sampleAddrStart,
+            sampleAddrEnd
+            );
+        sampleAddrStart = (LogInfo->pBeginProfileData[2]<<16) + (LogInfo->pBeginProfileData[1]<<8) + LogInfo->pBeginProfileData[0];
+        if(sampleAddrStart != 0)
+        {
+            sampleAddrStart -= 0x100000; /* substract memory offset */
+            sampleAddrStart /= 0x10000;     /* calc sector */
+            sv->item(sampleAddrStart / 16, sampleAddrStart % 16)->setBackground(Qt::magenta);
+        }
+        else
+        {
+            sv->item(0, 0)->setBackground(Qt::black);
+        }
+    }
+    else
+    {
+        InfoText = QString::asprintf("Empty");
+    }
+    tf->setPlainText((InfoText));
+}
+
+
+
+
+
+void EditLogDialog::on_SampleView_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn)
+{
+    uint16_t sampleSector;
+    uint8_t headerRow, headerColumn;
+    SLogbookHeader* LogInfo;
+    SLogbookHeader* LogInfo2nd;
+    uint32_t sectorStart, sectorEnd;
+    uint32_t sampleAddrStart, sampleAddrEnd;
+    QTableWidget* headerView = _ui->SectorView;
+
+    sampleSector = currentRow * 16 + currentColumn;
+    sectorStart = sampleSector * 0x10000 + 0x100000;
+    sectorEnd = sectorStart +0xFFFF;
+    if(SampleBuffer[sectorStart - 0x100000] != 0xFF)      // is buffer used?
+    {
+        updateHeaderStatus();
+        for(headerRow = 0; headerRow < 16; headerRow++)
+        {
+            for(headerColumn = 0; headerColumn < 16; headerColumn++)
+            {
+                LogInfo = (SLogbookHeader*)(HeaderBuffer+(0x800 * (headerRow*16+headerColumn)));
+                LogInfo2nd = (SLogbookHeader*)(HeaderBuffer+(0x800 * (headerRow*16+headerColumn)) +0x400);
+                sampleAddrStart = (LogInfo->pBeginProfileData[2]<<16) + (LogInfo->pBeginProfileData[1]<<8) + LogInfo->pBeginProfileData[0];
+                sampleAddrEnd = (LogInfo2nd->pEndProfileData[2]<<16) + (LogInfo2nd->pEndProfileData[1]<<8) + LogInfo2nd->pEndProfileData[0];
+
+                if(((sampleAddrStart >= sectorStart)&&(sampleAddrStart < sectorEnd)
+                    || (sampleAddrEnd >= sectorStart)&&(sampleAddrEnd < sectorEnd)))
+                {
+                    headerView->item(headerRow, headerColumn)->setBackground(Qt::magenta);
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/editlogdialog.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,182 @@
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef EDITLOGDIALOG_H
+#define EDITLOGDIALOG_H
+
+#define NUM_GAS		(5)	/* number of selectable gases */
+
+typedef struct
+{
+    unsigned char profileLength[3];
+    unsigned char samplingRate_seconds;
+    unsigned char numDivisors;
+    unsigned char tempType;
+    unsigned char tempLength;
+    unsigned char tempDivisor;
+    unsigned char deco_ndlType;
+    unsigned char deco_ndlLength;
+    unsigned char deco_ndlDivisor;
+    unsigned char gfType;
+    unsigned char gfLength;
+    unsigned char gfDivisor;
+    unsigned char ppo2Type;
+    unsigned char ppo2Length;
+    unsigned char ppo2Divisor;
+    unsigned char decoplanType;
+    unsigned char decoplanLength;
+    unsigned char decoplanDivisor;
+    unsigned char cnsType;
+    unsigned char cnsLength;
+    unsigned char cnsDivisor;
+    unsigned char tankType;
+    unsigned char tankLength;
+    unsigned char tankDivisor;
+} SSmallHeader;
+
+typedef struct{
+unsigned char active:1;
+unsigned char first:1;
+unsigned char deco:1;
+unsigned char travel:1;
+unsigned char senderCode:4;
+} gasubit8_t;
+
+typedef union{
+gasubit8_t ub;
+unsigned char uw;
+} gasbit8_Type;
+typedef struct
+{
+    unsigned char setpoint_cbar;
+    unsigned char depth_meter;
+} SSetpointLog;
+
+typedef struct
+{
+    unsigned char oxygen_percentage;
+    unsigned char helium_percentage;
+    unsigned char depth_meter;
+    gasbit8_Type note;
+} SGasListLog;
+//Logbook
+typedef struct
+{
+    unsigned short diveHeaderStart;
+    unsigned char  pBeginProfileData[3];
+    unsigned char  pEndProfileData[3];
+    unsigned char  profileLength[3];
+    unsigned char  logbookProfileVersion;
+    unsigned char  dateYear;
+    unsigned char  dateMonth;
+    unsigned char  dateDay;
+    unsigned char  timeHour;
+    unsigned char  timeMinute;
+    unsigned char  extraPagesWithData; /* from here on: changes in order with respect to OSTC3 */
+    unsigned short maxDepth;
+    unsigned short diveTimeMinutes;
+    unsigned char  diveTimeSeconds;
+    unsigned char  samplingRate;
+    short  minTemp;
+    unsigned short surfacePressure_mbar;
+    unsigned short desaturationTime;
+    SGasListLog gasordil[NUM_GAS];
+    unsigned char  firmwareVersionLow;
+    unsigned char  firmwareVersionHigh;
+    unsigned short batteryVoltage;
+    unsigned short cnsAtBeginning;
+    unsigned char  gfAtBeginning;
+    unsigned char  gfAtEnd;
+    unsigned short personalDiveCount;
+    SSetpointLog setpoint[NUM_GAS];
+    unsigned short maxCNS;
+    unsigned short averageDepth_mbar;
+    unsigned short total_diveTime_seconds;
+    unsigned char  salinity;
+    unsigned char  gfLow_or_Vpm_conservatism;
+    unsigned char  gfHigh;
+    unsigned char  decoModel;
+    float    n2Compartments[16];
+    float    heCompartments[16];
+    unsigned char  n2CompartDesatTime_min[16];
+    unsigned char  heCompartDesatTime_min[16];
+    unsigned short diveNumber;
+    unsigned char  lastDecostop_m;
+    unsigned char  CCRmode;
+    unsigned char  diveMode;
+    unsigned char  hwHudLastStatus; /* from here on identical to OSTC3 again */
+    unsigned short hwHudBattery_mV;
+    unsigned char batteryGaugeRegisters[6];
+    unsigned short diveHeaderEnd;
+} SLogbookHeader;
+
+
+namespace Ui { class editLogWnd; }
+
+#include <QDialog>
+#include <QTableWidgetItem>
+
+class HardwareOperations;
+
+
+
+class EditLogDialog
+        : public QDialog
+{
+    Q_OBJECT
+    Ui::editLogWnd* _ui;
+    HardwareOperations* _op;
+
+    unsigned char* HeaderBuffer;
+    unsigned char* SampleBuffer;
+    QTableWidgetItem* item[256];
+    QTableWidgetItem* sampleitem[192];
+
+public:
+    EditLogDialog(QWidget* parent, HardwareOperations *op);
+    ~EditLogDialog();
+private slots:
+
+    void on_WriteAllHeader_clicked();
+    void on_pushButton_clicked();
+    void on_HeaderUsage_valueChanged(int value);
+    void on_ReadAllSamples_clicked();
+    void on_pushButton_2_clicked();
+
+    void updateHeaderStatus();
+    void updateSampleStatus();
+    void on_LoadDump_clicked();
+    void on_WriteAllSamples_clicked();
+    void on_SectorView_cellClicked(int row, int column);
+
+
+    void on_SectorView_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
+
+    void on_SampleView_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
+};
+
+#endif // EDITLOGDIALOG_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icon.rc	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,1 @@
+IDI_ICON1 ICON "Images/app_OSTC_Companion.ico"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,144 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   main.cpp
+/// \brief  Wrap everything up.
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-2014 JD Gascuel. All rights reserved.
+/// $Id$
+//////////////////////////////////////////////////////////////////////////////
+//
+// BSD 2-Clause License:
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+//////////////////////////////////////////////////////////////////////////////
+// HISTORY
+//  2013-03-17 : [jDG] Initial version.
+//  2014-07-07 : [jDG] Cleanups for Subsurface google-summer-of-code.
+//  2014-07-25 : [jDG] BSD 2-clause license.
+//  2014-10-21 : [jDG] Update for Qt5.3
+//  2015-03-16 : [jDG] Update for Qt5.4.1, Renamed OSTC2c, Added OSTC2, OSTC3+
+
+#include "MainWindow.h"
+
+#include "SettingsDialog.h"
+#include "Utils/Log.h"
+
+#include <QApplication>
+#include <QSettings>
+#include <QSysInfo>
+#include <QOperatingSystemVersion>
+
+QSettings* settings = NULL;
+
+//////////////////////////////////////////////////////////////////////////////
+
+int main(int argc, char *argv[])
+{
+    //---- LOG system and initialization -------------------------------------
+    Log::init(argc, argv);
+
+#if 0
+    LOG_TRACE("OSTC Companion "
+        << "v" << MAJOR_VERSION << "." << MINOR_VERSION
+        << (BETA_VERSION ? QString(" beta %1").arg(PATCH_VERSION) : " release")
+        << " " << QSysInfo::WordSize << "bits"
+        << " (build " BUILD_VERSION ").");
+#endif
+#if 0
+#define LOG_TRACE(msg) \
+    do { \
+            if(Log::minLevel <= Log::LEVEL_TRACE) { \
+                std::ostringstream oss; \
+                oss << msg; \
+                LogAction(Log::LEVEL_TRACE, __FILE__, __LINE__, LOG_FUNCTION_) << oss.str(); \
+        } \
+    } while(0)
+#endif
+#define LOG_TRACE(msg) \
+    do { \
+            if(Log::minLevel <= Log::LEVEL_TRACE) { \
+                LogAction(Log::LEVEL_TRACE, __FILE__, __LINE__, LOG_FUNCTION_) << QString("%1").arg(msg); \
+        } \
+    } while(0)
+
+#ifdef Q_OS_WIN32
+
+    auto os = QOperatingSystemVersion::current();
+
+    if (os >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10)) {
+        LOG_TRACE("    Windows 10 or newer");
+    } else if (os >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 8)) {
+        LOG_TRACE("    Windows 8");
+    }
+
+#if 0
+    switch(QSysInfo::windowsVersion()) {
+    case QSysInfo::WV_XP:           LOG_TRACE("    Windows XP");    break;
+    case QSysInfo::WV_2003:         LOG_TRACE("    Windows 2003");  break;
+    case QSysInfo::WV_VISTA:        LOG_TRACE("    Windows Vista"); break;
+    case QSysInfo::WV_WINDOWS7:     LOG_TRACE("    Windows 7");     break;
+    case QSysInfo::WV_WINDOWS8:     LOG_TRACE("    Windows 8");     break;
+    case QSysInfo::WV_WINDOWS8_1:   LOG_TRACE("    Windows 8.1");   break;
+#if QT_VERSION < 0x0505
+    case 0x00c0:
+#else
+    case QSysInfo::WV_WINDOWS10:
+#endif
+                                    LOG_TRACE("    Windows 10");    break;
+    default:                        LOG_TRACE("    Windows " << QString().sprintf("%04X", QSysInfo::windowsVersion()) ); break;
+    }
+#endif
+#elif defined(Q_OS_MACX)
+    if( QSysInfo::macVersion() < 2 )
+        LOG_TRACE("    MacOS 9" );
+    else
+        LOG_TRACE("    MacOS 10." << int(QSysInfo::macVersion()-2));
+#elif defined(Q_OS_LINUX)
+    LOG_TRACE("    Linux ");
+#else
+#       error Unknown OS not yet implemented
+#endif
+ //   LOG_TRACE("    Qt build " << QT_VERSION_STR << " (DLL " << qVersion() << ")");
+        LOG_TRACE(QString("    Qt build %1 (DLL %2)").arg(QT_VERSION_STR).arg(qVersion()));
+    //---- Initialize interface ----------------------------------------------
+    // Allow nice display on 4K screens:
+    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
+    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps,    true);
+
+    QApplication a(argc, argv);
+
+    settings = new QSettings("OSTC", "Companion", &a);
+    Settings::reload(0);
+    MainWindow w;
+    w.show();
+
+    int rc = a.exec();
+
+    settings->sync();
+    delete settings;
+
+    Log::close();
+    return rc;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/o3pack.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,113 @@
+#include <QApplication>
+#include <QDir>
+#include <QProgressBar>
+#include <QProgressDialog>
+#include <QtDebug>
+
+#include "HexFile.h"
+
+//////////////////////////////////////////////////////////////////////////////
+static unsigned char frogSecretKey[16] = {
+    111, 85, 190, 69,
+    108,254, 242, 19,
+    231, 49, 248,255,
+    233, 48, 176,241
+};
+static unsigned char ostc3SecretKey[16] = {
+    241,233, 176, 48,
+     69,111, 190, 85,
+    255,231, 248, 49,
+     19,108, 242,254
+};
+
+int main(int argc, char *argv[])
+{
+    QApplication app(argc, argv);
+    app.processEvents();
+
+    //---- Parse parameters --------------------------------------------------
+    QString in, out;
+    QDir current;
+    bool force = false;
+    bool ostc3 = true;
+
+    if( argc < 2 ) goto Usage;
+
+    if( QString(argv[1]).toLower() == "-frog" ) {
+        ostc3 = false;
+        argc--, argv++;
+    } else if(  QString(argv[1]).toLower() == "-ostc3" ) {
+        ostc3 = true;
+        argc--, argv++;
+    }
+
+    in  = QDir::cleanPath(current.absoluteFilePath(argv[1]));
+    out = QDir::cleanPath(current.absoluteFilePath(argv[2]));
+    if( argv[2] == QString("-f") && argc >= 3 ) {
+        out = QDir::cleanPath(current.absoluteFilePath(argv[3]));
+        force = true;
+    }
+
+    //---- Check parameters consistency --------------------------------------
+    {
+        QFileInfo fi(in);
+        if( ! fi.exists() || ! fi.isReadable() ) {
+            qWarning().nospace() << "Cannot read input file " << in;
+            goto Usage;
+        }
+    }
+
+    {
+        QFileInfo fo(out);
+        if( fo.exists() ) {
+            if( !force ) {
+                qWarning().nospace() << "File " << out << " exists. Use -f to force overwrite.";
+                goto Usage;
+            }
+
+            if( !fo.isWritable() ) {
+                qWarning().nospace() << "Cannot write to " << out << ". Protected file ?";
+                goto Usage;
+            }
+            current.remove(out);
+        }
+    }
+
+    //---- Load the HEX file -------------------------------------------------
+    {
+        QProgressBar* progress = new QProgressBar(0);
+        progress->setFormat("Loading %p%");
+        progress->show();
+
+        try {
+            HexFile hex;
+            hex.allocate(0x20000);
+
+            hex.load(in, progress);
+            hex.sqwiz(0x1E000);
+
+            char sum[10];
+            sprintf(sum, "%08X", hex.checksum());
+            qDebug() << "Checksum " << sum;
+
+    //---- Save encrypted HEX file -------------------------------------------
+            progress->setFormat("Saving %p%");
+            hex.saveEncrypted(out, (ostc3 ? ostc3SecretKey : frogSecretKey), progress);
+        }
+        catch(const char* msg) {
+            qWarning() << "Failed: " << msg;
+        }
+
+        delete progress;
+    }
+
+    //---- End --------------------------------------------------------------
+    return 0;
+
+Usage:
+    qWarning() << "Usage:" << endl
+               << "    " << QString(app.arguments()[0]).section('/', -1).section('\\',-1)
+               << "[-frog|-ostc3]"
+               << " input.hex [-f] output.hex";
+    exit(-1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui_LogEditor - Kopie.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,152 @@
+/********************************************************************************
+** Form generated from reading UI file 'LogEditor.ui'
+**
+** Created by: Qt User Interface Compiler version 5.13.0
+**
+** WARNING! All changes made in this file will be lost when recompiling UI file!
+********************************************************************************/
+
+#ifndef UI_LOGEDITOR_H
+#define UI_LOGEDITOR_H
+
+#include <QtCore/QVariant>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QFrame>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QProgressBar>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QTableWidget>
+#include <QtWidgets/QTextBrowser>
+
+QT_BEGIN_NAMESPACE
+
+class Ui_editLogWnd
+{
+public:
+    QLabel *noPortLabel;
+    QFrame *line_4;
+    QFrame *line;
+    QPushButton *WriteAllHeader;
+    QPushButton *okB;
+    QPushButton *ReadAllSamples;
+    QPushButton *WriteAllSamples;
+    QProgressBar *HeaderUsage;
+    QProgressBar *SampleUsage;
+    QPushButton *pushButton;
+    QTableWidget *SectorView;
+    QPushButton *pushButton_2;
+    QPushButton *LoadDump;
+    QLabel *label;
+    QTextBrowser *textBrowser_2;
+    QTableWidget *SampleView;
+    QPushButton *ResetSampleAddr;
+
+    void setupUi(QDialog *editLogWnd)
+    {
+        if (editLogWnd->objectName().isEmpty())
+            editLogWnd->setObjectName(QString::fromUtf8("editLogWnd"));
+        editLogWnd->resize(853, 385);
+        noPortLabel = new QLabel(editLogWnd);
+        noPortLabel->setObjectName(QString::fromUtf8("noPortLabel"));
+        noPortLabel->setGeometry(QRect(9, 34, 16, 16));
+        QSizePolicy sizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+        sizePolicy.setHorizontalStretch(0);
+        sizePolicy.setVerticalStretch(0);
+        sizePolicy.setHeightForWidth(noPortLabel->sizePolicy().hasHeightForWidth());
+        noPortLabel->setSizePolicy(sizePolicy);
+        noPortLabel->setTextFormat(Qt::RichText);
+        noPortLabel->setAlignment(Qt::AlignCenter);
+        noPortLabel->setOpenExternalLinks(true);
+        line_4 = new QFrame(editLogWnd);
+        line_4->setObjectName(QString::fromUtf8("line_4"));
+        line_4->setGeometry(QRect(20, 150, 341, 16));
+        line_4->setFrameShape(QFrame::HLine);
+        line_4->setFrameShadow(QFrame::Sunken);
+        line = new QFrame(editLogWnd);
+        line->setObjectName(QString::fromUtf8("line"));
+        line->setGeometry(QRect(20, 300, 341, 20));
+        line->setFrameShape(QFrame::HLine);
+        line->setFrameShadow(QFrame::Sunken);
+        WriteAllHeader = new QPushButton(editLogWnd);
+        WriteAllHeader->setObjectName(QString::fromUtf8("WriteAllHeader"));
+        WriteAllHeader->setGeometry(QRect(30, 80, 101, 23));
+        okB = new QPushButton(editLogWnd);
+        okB->setObjectName(QString::fromUtf8("okB"));
+        okB->setGeometry(QRect(240, 330, 75, 23));
+        okB->setAutoDefault(false);
+        ReadAllSamples = new QPushButton(editLogWnd);
+        ReadAllSamples->setObjectName(QString::fromUtf8("ReadAllSamples"));
+        ReadAllSamples->setGeometry(QRect(30, 230, 101, 23));
+        WriteAllSamples = new QPushButton(editLogWnd);
+        WriteAllSamples->setObjectName(QString::fromUtf8("WriteAllSamples"));
+        WriteAllSamples->setGeometry(QRect(30, 260, 101, 23));
+        HeaderUsage = new QProgressBar(editLogWnd);
+        HeaderUsage->setObjectName(QString::fromUtf8("HeaderUsage"));
+        HeaderUsage->setGeometry(QRect(30, 20, 131, 23));
+        HeaderUsage->setValue(24);
+        SampleUsage = new QProgressBar(editLogWnd);
+        SampleUsage->setObjectName(QString::fromUtf8("SampleUsage"));
+        SampleUsage->setGeometry(QRect(30, 190, 131, 23));
+        SampleUsage->setValue(24);
+        pushButton = new QPushButton(editLogWnd);
+        pushButton->setObjectName(QString::fromUtf8("pushButton"));
+        pushButton->setGeometry(QRect(30, 50, 101, 23));
+        SectorView = new QTableWidget(editLogWnd);
+        SectorView->setObjectName(QString::fromUtf8("SectorView"));
+        SectorView->setGeometry(QRect(180, 10, 151, 131));
+        pushButton_2 = new QPushButton(editLogWnd);
+        pushButton_2->setObjectName(QString::fromUtf8("pushButton_2"));
+        pushButton_2->setGeometry(QRect(110, 330, 75, 23));
+        LoadDump = new QPushButton(editLogWnd);
+        LoadDump->setObjectName(QString::fromUtf8("LoadDump"));
+        LoadDump->setGeometry(QRect(20, 330, 75, 23));
+        label = new QLabel(editLogWnd);
+        label->setObjectName(QString::fromUtf8("label"));
+        label->setGeometry(QRect(520, 10, 47, 13));
+        textBrowser_2 = new QTextBrowser(editLogWnd);
+        textBrowser_2->setObjectName(QString::fromUtf8("textBrowser_2"));
+        textBrowser_2->setGeometry(QRect(570, 10, 256, 361));
+        SampleView = new QTableWidget(editLogWnd);
+        SampleView->setObjectName(QString::fromUtf8("SampleView"));
+        SampleView->setGeometry(QRect(180, 180, 171, 41));
+        ResetSampleAddr = new QPushButton(editLogWnd);
+        ResetSampleAddr->setObjectName(QString::fromUtf8("ResetSampleAddr"));
+        ResetSampleAddr->setGeometry(QRect(340, 10, 101, 23));
+
+        retranslateUi(editLogWnd);
+        QObject::connect(okB, SIGNAL(clicked()), editLogWnd, SLOT(accept()));
+
+        okB->setDefault(true);
+
+
+        QMetaObject::connectSlotsByName(editLogWnd);
+    } // setupUi
+
+    void retranslateUi(QDialog *editLogWnd)
+    {
+        editLogWnd->setWindowTitle(QCoreApplication::translate("editLogWnd", "OSTC Companion settings", nullptr));
+        WriteAllHeader->setText(QCoreApplication::translate("editLogWnd", "WriteAllHeader", nullptr));
+        okB->setText(QCoreApplication::translate("editLogWnd", "OK", nullptr));
+#if QT_CONFIG(shortcut)
+        okB->setShortcut(QCoreApplication::translate("editLogWnd", "Return", nullptr));
+#endif // QT_CONFIG(shortcut)
+        ReadAllSamples->setText(QCoreApplication::translate("editLogWnd", "ReadAllSamples", nullptr));
+        WriteAllSamples->setText(QCoreApplication::translate("editLogWnd", "WriteAllSamples", nullptr));
+        pushButton->setText(QCoreApplication::translate("editLogWnd", "ReadAllHeader", nullptr));
+        pushButton_2->setText(QCoreApplication::translate("editLogWnd", "Save Dump", nullptr));
+        LoadDump->setText(QCoreApplication::translate("editLogWnd", "Load Dump", nullptr));
+        label->setText(QCoreApplication::translate("editLogWnd", "Info", nullptr));
+        ResetSampleAddr->setText(QCoreApplication::translate("editLogWnd", "ResetSampleAddr", nullptr));
+    } // retranslateUi
+
+};
+
+namespace Ui {
+    class editLogWnd: public Ui_editLogWnd {};
+} // namespace Ui
+
+QT_END_NAMESPACE
+
+#endif // UI_LOGEDITOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui_LogEditor.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,140 @@
+/********************************************************************************
+** Form generated from reading UI file 'LogEditor.ui'
+**
+** Created by: Qt User Interface Compiler version 6.10.1
+**
+** WARNING! All changes made in this file will be lost when recompiling UI file!
+********************************************************************************/
+
+#ifndef UI_LOGEDITOR_H
+#define UI_LOGEDITOR_H
+
+#include <QtCore/QVariant>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QHeaderView>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QProgressBar>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QTableWidget>
+#include <QtWidgets/QTextBrowser>
+
+QT_BEGIN_NAMESPACE
+
+class Ui_editLogWnd
+{
+public:
+    QLabel *noPortLabel;
+    QPushButton *WriteAllHeader;
+    QPushButton *okB;
+    QPushButton *ReadAllSamples;
+    QPushButton *WriteAllSamples;
+    QProgressBar *HeaderUsage;
+    QProgressBar *SampleUsage;
+    QPushButton *pushButton;
+    QTableWidget *SectorView;
+    QPushButton *pushButton_2;
+    QPushButton *LoadDump;
+    QLabel *label;
+    QTextBrowser *textBrowser_2;
+    QTableWidget *SampleView;
+    QPushButton *ResetSampleAddr;
+
+    void setupUi(QDialog *editLogWnd)
+    {
+        if (editLogWnd->objectName().isEmpty())
+            editLogWnd->setObjectName("editLogWnd");
+        editLogWnd->resize(853, 385);
+        noPortLabel = new QLabel(editLogWnd);
+        noPortLabel->setObjectName("noPortLabel");
+        noPortLabel->setGeometry(QRect(9, 34, 16, 16));
+        QSizePolicy sizePolicy(QSizePolicy::Policy::Ignored, QSizePolicy::Policy::Preferred);
+        sizePolicy.setHorizontalStretch(0);
+        sizePolicy.setVerticalStretch(0);
+        sizePolicy.setHeightForWidth(noPortLabel->sizePolicy().hasHeightForWidth());
+        noPortLabel->setSizePolicy(sizePolicy);
+        noPortLabel->setTextFormat(Qt::TextFormat::RichText);
+        noPortLabel->setAlignment(Qt::AlignmentFlag::AlignCenter);
+        noPortLabel->setOpenExternalLinks(true);
+        WriteAllHeader = new QPushButton(editLogWnd);
+        WriteAllHeader->setObjectName("WriteAllHeader");
+        WriteAllHeader->setGeometry(QRect(30, 80, 101, 23));
+        okB = new QPushButton(editLogWnd);
+        okB->setObjectName("okB");
+        okB->setGeometry(QRect(220, 330, 75, 23));
+        okB->setAutoDefault(false);
+        ReadAllSamples = new QPushButton(editLogWnd);
+        ReadAllSamples->setObjectName("ReadAllSamples");
+        ReadAllSamples->setGeometry(QRect(30, 230, 101, 23));
+        WriteAllSamples = new QPushButton(editLogWnd);
+        WriteAllSamples->setObjectName("WriteAllSamples");
+        WriteAllSamples->setGeometry(QRect(30, 260, 101, 23));
+        HeaderUsage = new QProgressBar(editLogWnd);
+        HeaderUsage->setObjectName("HeaderUsage");
+        HeaderUsage->setGeometry(QRect(30, 20, 131, 23));
+        HeaderUsage->setValue(24);
+        SampleUsage = new QProgressBar(editLogWnd);
+        SampleUsage->setObjectName("SampleUsage");
+        SampleUsage->setGeometry(QRect(30, 190, 131, 23));
+        SampleUsage->setValue(24);
+        pushButton = new QPushButton(editLogWnd);
+        pushButton->setObjectName("pushButton");
+        pushButton->setGeometry(QRect(30, 50, 101, 23));
+        SectorView = new QTableWidget(editLogWnd);
+        SectorView->setObjectName("SectorView");
+        SectorView->setGeometry(QRect(180, 10, 151, 131));
+        pushButton_2 = new QPushButton(editLogWnd);
+        pushButton_2->setObjectName("pushButton_2");
+        pushButton_2->setGeometry(QRect(110, 330, 75, 23));
+        LoadDump = new QPushButton(editLogWnd);
+        LoadDump->setObjectName("LoadDump");
+        LoadDump->setGeometry(QRect(20, 330, 75, 23));
+        label = new QLabel(editLogWnd);
+        label->setObjectName("label");
+        label->setGeometry(QRect(520, 10, 47, 13));
+        textBrowser_2 = new QTextBrowser(editLogWnd);
+        textBrowser_2->setObjectName("textBrowser_2");
+        textBrowser_2->setGeometry(QRect(570, 10, 256, 361));
+        textBrowser_2->setAcceptRichText(true);
+        SampleView = new QTableWidget(editLogWnd);
+        SampleView->setObjectName("SampleView");
+        SampleView->setGeometry(QRect(180, 180, 171, 41));
+        ResetSampleAddr = new QPushButton(editLogWnd);
+        ResetSampleAddr->setObjectName("ResetSampleAddr");
+        ResetSampleAddr->setGeometry(QRect(340, 10, 101, 23));
+
+        retranslateUi(editLogWnd);
+        QObject::connect(okB, &QPushButton::clicked, editLogWnd, qOverload<>(&QDialog::accept));
+
+        okB->setDefault(true);
+
+
+        QMetaObject::connectSlotsByName(editLogWnd);
+    } // setupUi
+
+    void retranslateUi(QDialog *editLogWnd)
+    {
+        editLogWnd->setWindowTitle(QCoreApplication::translate("editLogWnd", "OSTC Companion settings", nullptr));
+        WriteAllHeader->setText(QCoreApplication::translate("editLogWnd", "WriteAllHeader", nullptr));
+        okB->setText(QCoreApplication::translate("editLogWnd", "OK", nullptr));
+#if QT_CONFIG(shortcut)
+        okB->setShortcut(QCoreApplication::translate("editLogWnd", "Return", nullptr));
+#endif // QT_CONFIG(shortcut)
+        ReadAllSamples->setText(QCoreApplication::translate("editLogWnd", "ReadAllSamples", nullptr));
+        WriteAllSamples->setText(QCoreApplication::translate("editLogWnd", "WriteAllSamples", nullptr));
+        pushButton->setText(QCoreApplication::translate("editLogWnd", "ReadAllHeader", nullptr));
+        pushButton_2->setText(QCoreApplication::translate("editLogWnd", "Save Dump", nullptr));
+        LoadDump->setText(QCoreApplication::translate("editLogWnd", "Load Dump", nullptr));
+        label->setText(QCoreApplication::translate("editLogWnd", "Info", nullptr));
+        ResetSampleAddr->setText(QCoreApplication::translate("editLogWnd", "ResetSampleAddr", nullptr));
+    } // retranslateUi
+
+};
+
+namespace Ui {
+    class editLogWnd: public Ui_editLogWnd {};
+} // namespace Ui
+
+QT_END_NAMESPACE
+
+#endif // UI_LOGEDITOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui_MainWindow.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,300 @@
+/********************************************************************************
+** Form generated from reading UI file 'MainWindow.ui'
+**
+** Created by: Qt User Interface Compiler version 6.10.1
+**
+** WARNING! All changes made in this file will be lost when recompiling UI file!
+********************************************************************************/
+
+#ifndef UI_MAINWINDOW_H
+#define UI_MAINWINDOW_H
+
+#include <QtCore/QVariant>
+#include <QtGui/QIcon>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QComboBox>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QMainWindow>
+#include <QtWidgets/QPlainTextEdit>
+#include <QtWidgets/QProgressBar>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QSpacerItem>
+#include <QtWidgets/QToolButton>
+#include <QtWidgets/QVBoxLayout>
+#include <QtWidgets/QWidget>
+
+QT_BEGIN_NAMESPACE
+
+class Ui_MainWindow
+{
+public:
+    QWidget *centralWidget;
+    QGridLayout *gridLayout;
+    QProgressBar *progressBar;
+    QComboBox *computerType;
+    QVBoxLayout *verticalLayout;
+    QHBoxLayout *horizontalLayout;
+    QPushButton *connectButton;
+    QPushButton *dateButton;
+    QPushButton *nameButton;
+    QPushButton *iconButton;
+    QPushButton *signalButton;
+    QPushButton *upgradeButton;
+    QPushButton *closeButton;
+    QSpacerItem *verticalSpacer;
+    QPushButton *quitButton;
+    QLabel *companionUrlL;
+    QSpacerItem *horizontalSpacer_2;
+    QToolButton *settingsButton;
+    QPlainTextEdit *console;
+    QLabel *computerImage;
+    QSpacerItem *horizontalSpacer_3;
+    QSpacerItem *horizontalSpacer;
+    QSpacerItem *verticalSpacer_3;
+    QSpacerItem *verticalSpacer_2;
+    QPushButton *editLog;
+
+    void setupUi(QMainWindow *MainWindow)
+    {
+        if (MainWindow->objectName().isEmpty())
+            MainWindow->setObjectName("MainWindow");
+        MainWindow->resize(451, 418);
+        MainWindow->setWindowTitle(QString::fromUtf8("OSTC Companion"));
+        QIcon icon;
+        icon.addFile(QString::fromUtf8(":/Images/app_OSTC_Companion.svg"), QSize(), QIcon::Mode::Normal, QIcon::State::Off);
+        MainWindow->setWindowIcon(icon);
+        centralWidget = new QWidget(MainWindow);
+        centralWidget->setObjectName("centralWidget");
+        gridLayout = new QGridLayout(centralWidget);
+        gridLayout->setSpacing(6);
+        gridLayout->setContentsMargins(11, 11, 11, 11);
+        gridLayout->setObjectName("gridLayout");
+        progressBar = new QProgressBar(centralWidget);
+        progressBar->setObjectName("progressBar");
+        progressBar->setAlignment(Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter);
+        progressBar->setTextVisible(true);
+
+        gridLayout->addWidget(progressBar, 7, 0, 1, 4);
+
+        computerType = new QComboBox(centralWidget);
+        computerType->addItem(QString::fromUtf8("OSTC2 mk.2/2n/2c"));
+        computerType->addItem(QString::fromUtf8("OSTC hwOS (USB)"));
+        computerType->addItem(QString::fromUtf8("OSTC hwOS (Bluetooth)"));
+        computerType->addItem(QString::fromUtf8("OSTC4"));
+        computerType->setObjectName("computerType");
+
+        gridLayout->addWidget(computerType, 0, 1, 1, 1);
+
+        verticalLayout = new QVBoxLayout();
+        verticalLayout->setSpacing(0);
+        verticalLayout->setObjectName("verticalLayout");
+        verticalLayout->setContentsMargins(-1, -1, 0, 0);
+        horizontalLayout = new QHBoxLayout();
+        horizontalLayout->setSpacing(6);
+        horizontalLayout->setObjectName("horizontalLayout");
+        connectButton = new QPushButton(centralWidget);
+        connectButton->setObjectName("connectButton");
+        connectButton->setMinimumSize(QSize(0, 23));
+        connectButton->setStyleSheet(QString::fromUtf8("QPushButton {\n"
+"	border: 1px solid blue;\n"
+"    border-radius: 6px;\n"
+"	background: qlineargradient(x1:0, y1:0, x2:0, y2:1, \n"
+"		stop:0.00 white,\n"
+"		stop:0.55 #55aaff,\n"
+"		stop:0.56 #4964ff,\n"
+"		stop:1.00 #55aaff)\n"
+"}"));
+        connectButton->setAutoDefault(true);
+
+        horizontalLayout->addWidget(connectButton);
+
+
+        verticalLayout->addLayout(horizontalLayout);
+
+        dateButton = new QPushButton(centralWidget);
+        dateButton->setObjectName("dateButton");
+
+        verticalLayout->addWidget(dateButton);
+
+        nameButton = new QPushButton(centralWidget);
+        nameButton->setObjectName("nameButton");
+
+        verticalLayout->addWidget(nameButton);
+
+        iconButton = new QPushButton(centralWidget);
+        iconButton->setObjectName("iconButton");
+
+        verticalLayout->addWidget(iconButton);
+
+        signalButton = new QPushButton(centralWidget);
+        signalButton->setObjectName("signalButton");
+
+        verticalLayout->addWidget(signalButton);
+
+        upgradeButton = new QPushButton(centralWidget);
+        upgradeButton->setObjectName("upgradeButton");
+
+        verticalLayout->addWidget(upgradeButton);
+
+        closeButton = new QPushButton(centralWidget);
+        closeButton->setObjectName("closeButton");
+
+        verticalLayout->addWidget(closeButton);
+
+        verticalSpacer = new QSpacerItem(105, 8, QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Minimum);
+
+        verticalLayout->addItem(verticalSpacer);
+
+        quitButton = new QPushButton(centralWidget);
+        quitButton->setObjectName("quitButton");
+
+        verticalLayout->addWidget(quitButton);
+
+
+        gridLayout->addLayout(verticalLayout, 0, 3, 6, 1);
+
+        companionUrlL = new QLabel(centralWidget);
+        companionUrlL->setObjectName("companionUrlL");
+        companionUrlL->setText(QString::fromUtf8("<html><head/><body><p>See us on <a href=\"https://ostc-planner.net/wp/companion/\"><span style=\" text-decoration: underline; color:#0000ff;\">ostc-planner.net/wp/companion</span></a></p></body></html>"));
+        companionUrlL->setOpenExternalLinks(true);
+
+        gridLayout->addWidget(companionUrlL, 6, 0, 1, 4);
+
+        horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Minimum);
+
+        gridLayout->addItem(horizontalSpacer_2, 2, 0, 1, 1);
+
+        settingsButton = new QToolButton(centralWidget);
+        settingsButton->setObjectName("settingsButton");
+        QIcon icon1;
+        icon1.addFile(QString::fromUtf8(":/Images/Settings.svg"), QSize(), QIcon::Mode::Normal, QIcon::State::Off);
+        settingsButton->setIcon(icon1);
+
+        gridLayout->addWidget(settingsButton, 0, 0, 1, 1);
+
+        console = new QPlainTextEdit(centralWidget);
+        console->setObjectName("console");
+        console->setMinimumSize(QSize(0, 46));
+        console->setFrameShadow(QFrame::Shadow::Sunken);
+        console->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
+        console->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
+        console->setUndoRedoEnabled(false);
+        console->setReadOnly(true);
+        console->setCenterOnScroll(false);
+        console->setProperty("tabStopWidth", QVariant(4));
+
+        gridLayout->addWidget(console, 8, 0, 1, 4);
+
+        computerImage = new QLabel(centralWidget);
+        computerImage->setObjectName("computerImage");
+        QSizePolicy sizePolicy(QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Fixed);
+        sizePolicy.setHorizontalStretch(0);
+        sizePolicy.setVerticalStretch(0);
+        sizePolicy.setHeightForWidth(computerImage->sizePolicy().hasHeightForWidth());
+        computerImage->setSizePolicy(sizePolicy);
+        computerImage->setMinimumSize(QSize(160, 120));
+        computerImage->setMaximumSize(QSize(160, 120));
+        computerImage->setPixmap(QPixmap(QString::fromUtf8(":/Images/frog_160x120.png")));
+
+        gridLayout->addWidget(computerImage, 2, 1, 1, 1);
+
+        horizontalSpacer_3 = new QSpacerItem(40, 20, QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Minimum);
+
+        gridLayout->addItem(horizontalSpacer_3, 2, 2, 1, 1);
+
+        horizontalSpacer = new QSpacerItem(8, 20, QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Minimum);
+
+        gridLayout->addItem(horizontalSpacer, 0, 2, 1, 1);
+
+        verticalSpacer_3 = new QSpacerItem(20, 0, QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Minimum);
+
+        gridLayout->addItem(verticalSpacer_3, 5, 1, 1, 1);
+
+        verticalSpacer_2 = new QSpacerItem(20, 0, QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Minimum);
+
+        gridLayout->addItem(verticalSpacer_2, 1, 1, 1, 1);
+
+        editLog = new QPushButton(centralWidget);
+        editLog->setObjectName("editLog");
+        editLog->setEnabled(false);
+
+        gridLayout->addWidget(editLog, 5, 2, 1, 1);
+
+        MainWindow->setCentralWidget(centralWidget);
+
+        retranslateUi(MainWindow);
+        QObject::connect(computerType, SIGNAL(activated(int)), MainWindow, SLOT(changeTypeSlot()));
+        QObject::connect(quitButton, &QPushButton::clicked, MainWindow, qOverload<>(&QMainWindow::close));
+        QObject::connect(dateButton, SIGNAL(clicked()), MainWindow, SLOT(dateSlot()));
+        QObject::connect(nameButton, SIGNAL(clicked()), MainWindow, SLOT(nameSlot()));
+        QObject::connect(iconButton, SIGNAL(clicked()), MainWindow, SLOT(iconSlot()));
+        QObject::connect(upgradeButton, SIGNAL(clicked()), MainWindow, SLOT(upgradeSlot()));
+        QObject::connect(connectButton, SIGNAL(clicked()), MainWindow, SLOT(connectSlot()));
+        QObject::connect(settingsButton, SIGNAL(clicked()), MainWindow, SLOT(settingsSlot()));
+        QObject::connect(closeButton, SIGNAL(clicked()), MainWindow, SLOT(closeSlot()));
+
+        computerType->setCurrentIndex(0);
+
+
+        QMetaObject::connectSlotsByName(MainWindow);
+    } // setupUi
+
+    void retranslateUi(QMainWindow *MainWindow)
+    {
+
+#if QT_CONFIG(tooltip)
+        computerType->setToolTip(QCoreApplication::translate("MainWindow", "H&W dive computer model.", nullptr));
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(tooltip)
+        connectButton->setToolTip(QCoreApplication::translate("MainWindow", "Open or re-open the last selected  USB or Bluetooth port (See Preferences).\n"
+"NOTE: make sure to connect the serial cable, or to set the bluetooth mode first...", nullptr));
+#endif // QT_CONFIG(tooltip)
+        connectButton->setText(QCoreApplication::translate("MainWindow", "Connect", nullptr));
+#if QT_CONFIG(tooltip)
+        dateButton->setToolTip(QCoreApplication::translate("MainWindow", "Set the H&W computer date and time.\n"
+"Can be automatic at each connect, if asked to in the Preferences.\n"
+"(Valid once device is connected).", nullptr));
+#endif // QT_CONFIG(tooltip)
+        dateButton->setText(QCoreApplication::translate("MainWindow", "Set Date && Time", nullptr));
+#if QT_CONFIG(tooltip)
+        nameButton->setToolTip(QCoreApplication::translate("MainWindow", "Change the name displayed on the H&W device.\n"
+"(Valid once device is connected).", nullptr));
+#endif // QT_CONFIG(tooltip)
+        nameButton->setText(QCoreApplication::translate("MainWindow", "Set Name...", nullptr));
+#if QT_CONFIG(tooltip)
+        iconButton->setToolTip(QCoreApplication::translate("MainWindow", "Upload a customization icon.\n"
+"Only supported on Frog yet.\n"
+"(Valid once device is connected).", nullptr));
+#endif // QT_CONFIG(tooltip)
+        iconButton->setText(QCoreApplication::translate("MainWindow", "Set Icon...", nullptr));
+        signalButton->setText(QCoreApplication::translate("MainWindow", "Check Signal", nullptr));
+#if QT_CONFIG(tooltip)
+        upgradeButton->setToolTip(QCoreApplication::translate("MainWindow", "Ask for a firmware file, and upload it to the H&W device.\n"
+"Can be done before or after device (re-)connection.", nullptr));
+#endif // QT_CONFIG(tooltip)
+        upgradeButton->setText(QCoreApplication::translate("MainWindow", "Upgrade Firmware...", nullptr));
+#if QT_CONFIG(tooltip)
+        closeButton->setToolTip(QCoreApplication::translate("MainWindow", "Close USB or Bluetooth connection to the device.", nullptr));
+#endif // QT_CONFIG(tooltip)
+        closeButton->setText(QCoreApplication::translate("MainWindow", "Close", nullptr));
+        quitButton->setText(QCoreApplication::translate("MainWindow", "Quit", nullptr));
+#if QT_CONFIG(tooltip)
+        settingsButton->setToolTip(QCoreApplication::translate("MainWindow", "Open the Preferences menu.", nullptr));
+#endif // QT_CONFIG(tooltip)
+        settingsButton->setText(QCoreApplication::translate("MainWindow", "...", nullptr));
+        computerImage->setText(QString());
+        editLog->setText(QCoreApplication::translate("MainWindow", "Edit Log", nullptr));
+        (void)MainWindow;
+    } // retranslateUi
+
+};
+
+namespace Ui {
+    class MainWindow: public Ui_MainWindow {};
+} // namespace Ui
+
+QT_END_NAMESPACE
+
+#endif // UI_MAINWINDOW_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui_Settings.h	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,252 @@
+/********************************************************************************
+** Form generated from reading UI file 'Settings.ui'
+**
+** Created by: Qt User Interface Compiler version 5.13.0
+**
+** WARNING! All changes made in this file will be lost when recompiling UI file!
+********************************************************************************/
+
+#ifndef UI_SETTINGS_H
+#define UI_SETTINGS_H
+
+#include <QtCore/QVariant>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QCheckBox>
+#include <QtWidgets/QComboBox>
+#include <QtWidgets/QDialog>
+#include <QtWidgets/QFrame>
+#include <QtWidgets/QGridLayout>
+#include <QtWidgets/QHBoxLayout>
+#include <QtWidgets/QLabel>
+#include <QtWidgets/QPushButton>
+#include <QtWidgets/QSpacerItem>
+#include <QtWidgets/QToolButton>
+
+QT_BEGIN_NAMESPACE
+
+class Ui_Settings
+{
+public:
+    QGridLayout *gridLayout;
+    QFrame *line_3;
+    QCheckBox *forceFirmwareUpdate;
+    QComboBox *portMenu;
+    QCheckBox *autoSetDateTimeCB;
+    QCheckBox *forceFontlibUpdate;
+    QLabel *noPortLabel;
+    QComboBox *languageMenu;
+    QToolButton *updatePorts;
+    QHBoxLayout *horizontalLayout;
+    QPushButton *resetB;
+    QSpacerItem *horizontalSpacer_2;
+    QPushButton *okB;
+    QPushButton *cancelB;
+    QLabel *label;
+    QCheckBox *forceRTEUpdate;
+    QFrame *line_4;
+    QLabel *label_2;
+    QSpacerItem *horizontalSpacer;
+    QFrame *line;
+    QCheckBox *useFastMode;
+
+    void setupUi(QDialog *Settings)
+    {
+        if (Settings->objectName().isEmpty())
+            Settings->setObjectName(QString::fromUtf8("Settings"));
+        Settings->resize(341, 293);
+        gridLayout = new QGridLayout(Settings);
+        gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
+        line_3 = new QFrame(Settings);
+        line_3->setObjectName(QString::fromUtf8("line_3"));
+        line_3->setFrameShape(QFrame::HLine);
+        line_3->setFrameShadow(QFrame::Sunken);
+
+        gridLayout->addWidget(line_3, 10, 0, 1, 3);
+
+        forceFirmwareUpdate = new QCheckBox(Settings);
+        forceFirmwareUpdate->setObjectName(QString::fromUtf8("forceFirmwareUpdate"));
+
+        gridLayout->addWidget(forceFirmwareUpdate, 6, 0, 1, 2);
+
+        portMenu = new QComboBox(Settings);
+        portMenu->setObjectName(QString::fromUtf8("portMenu"));
+        portMenu->setEditable(true);
+
+        gridLayout->addWidget(portMenu, 2, 1, 1, 1);
+
+        autoSetDateTimeCB = new QCheckBox(Settings);
+        autoSetDateTimeCB->setObjectName(QString::fromUtf8("autoSetDateTimeCB"));
+
+        gridLayout->addWidget(autoSetDateTimeCB, 11, 0, 1, 3);
+
+        forceFontlibUpdate = new QCheckBox(Settings);
+        forceFontlibUpdate->setObjectName(QString::fromUtf8("forceFontlibUpdate"));
+
+        gridLayout->addWidget(forceFontlibUpdate, 8, 0, 1, 2);
+
+        noPortLabel = new QLabel(Settings);
+        noPortLabel->setObjectName(QString::fromUtf8("noPortLabel"));
+        QSizePolicy sizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+        sizePolicy.setHorizontalStretch(0);
+        sizePolicy.setVerticalStretch(0);
+        sizePolicy.setHeightForWidth(noPortLabel->sizePolicy().hasHeightForWidth());
+        noPortLabel->setSizePolicy(sizePolicy);
+        noPortLabel->setTextFormat(Qt::RichText);
+        noPortLabel->setAlignment(Qt::AlignCenter);
+        noPortLabel->setOpenExternalLinks(true);
+
+        gridLayout->addWidget(noPortLabel, 4, 0, 1, 3);
+
+        languageMenu = new QComboBox(Settings);
+        QIcon icon;
+        icon.addFile(QString::fromUtf8(":/icons/Icons/german.png"), QSize(), QIcon::Normal, QIcon::Off);
+        languageMenu->addItem(icon, QString::fromUtf8("Deutsch"));
+        QIcon icon1;
+        icon1.addFile(QString::fromUtf8(":/icons/Icons/english.png"), QSize(), QIcon::Normal, QIcon::Off);
+        languageMenu->addItem(icon1, QString::fromUtf8("English"));
+        QIcon icon2;
+        icon2.addFile(QString::fromUtf8(":/icons/Icons/spanish.png"), QSize(), QIcon::Normal, QIcon::Off);
+        languageMenu->addItem(icon2, QString::fromUtf8("Espa\303\261ol"));
+        QIcon icon3;
+        icon3.addFile(QString::fromUtf8(":/icons/Icons/french.png"), QSize(), QIcon::Normal, QIcon::Off);
+        languageMenu->addItem(icon3, QString::fromUtf8("Fran\303\247ais"));
+        QIcon icon4;
+        icon4.addFile(QString::fromUtf8(":/icons/Icons/italian.png"), QSize(), QIcon::Normal, QIcon::Off);
+        languageMenu->addItem(icon4, QString::fromUtf8("Italiano"));
+        QIcon icon5;
+        icon5.addFile(QString::fromUtf8(":/icons/Icons/russian.png"), QSize(), QIcon::Normal, QIcon::Off);
+        languageMenu->addItem(icon5, QString::fromUtf8("\320\240\321\203\321\201\321\201\320\272\320\270\320\271"));
+        languageMenu->setObjectName(QString::fromUtf8("languageMenu"));
+        languageMenu->setInsertPolicy(QComboBox::NoInsert);
+
+        gridLayout->addWidget(languageMenu, 0, 1, 1, 1);
+
+        updatePorts = new QToolButton(Settings);
+        updatePorts->setObjectName(QString::fromUtf8("updatePorts"));
+        updatePorts->setText(QString::fromUtf8("..."));
+
+        gridLayout->addWidget(updatePorts, 2, 2, 1, 1);
+
+        horizontalLayout = new QHBoxLayout();
+        horizontalLayout->setSpacing(0);
+        horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
+        resetB = new QPushButton(Settings);
+        resetB->setObjectName(QString::fromUtf8("resetB"));
+
+        horizontalLayout->addWidget(resetB);
+
+        horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
+
+        horizontalLayout->addItem(horizontalSpacer_2);
+
+        okB = new QPushButton(Settings);
+        okB->setObjectName(QString::fromUtf8("okB"));
+        okB->setAutoDefault(false);
+
+        horizontalLayout->addWidget(okB);
+
+        cancelB = new QPushButton(Settings);
+        cancelB->setObjectName(QString::fromUtf8("cancelB"));
+
+        horizontalLayout->addWidget(cancelB);
+
+
+        gridLayout->addLayout(horizontalLayout, 15, 0, 1, 3);
+
+        label = new QLabel(Settings);
+        label->setObjectName(QString::fromUtf8("label"));
+        label->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+
+        gridLayout->addWidget(label, 0, 0, 1, 1);
+
+        forceRTEUpdate = new QCheckBox(Settings);
+        forceRTEUpdate->setObjectName(QString::fromUtf8("forceRTEUpdate"));
+
+        gridLayout->addWidget(forceRTEUpdate, 7, 0, 1, 2);
+
+        line_4 = new QFrame(Settings);
+        line_4->setObjectName(QString::fromUtf8("line_4"));
+        line_4->setFrameShape(QFrame::HLine);
+        line_4->setFrameShadow(QFrame::Sunken);
+
+        gridLayout->addWidget(line_4, 5, 0, 1, 3);
+
+        label_2 = new QLabel(Settings);
+        label_2->setObjectName(QString::fromUtf8("label_2"));
+        QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred);
+        sizePolicy1.setHorizontalStretch(0);
+        sizePolicy1.setVerticalStretch(0);
+        sizePolicy1.setHeightForWidth(label_2->sizePolicy().hasHeightForWidth());
+        label_2->setSizePolicy(sizePolicy1);
+        label_2->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
+
+        gridLayout->addWidget(label_2, 2, 0, 1, 1);
+
+        horizontalSpacer = new QSpacerItem(0, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
+
+        gridLayout->addItem(horizontalSpacer, 0, 2, 1, 1);
+
+        line = new QFrame(Settings);
+        line->setObjectName(QString::fromUtf8("line"));
+        line->setFrameShape(QFrame::HLine);
+        line->setFrameShadow(QFrame::Sunken);
+
+        gridLayout->addWidget(line, 14, 0, 1, 3);
+
+        useFastMode = new QCheckBox(Settings);
+        useFastMode->setObjectName(QString::fromUtf8("useFastMode"));
+
+        gridLayout->addWidget(useFastMode, 13, 0, 1, 3);
+
+
+        retranslateUi(Settings);
+        QObject::connect(okB, SIGNAL(clicked()), Settings, SLOT(accept()));
+        QObject::connect(cancelB, SIGNAL(clicked()), Settings, SLOT(reject()));
+        QObject::connect(languageMenu, SIGNAL(activated(int)), Settings, SLOT(languageSlot(int)));
+        QObject::connect(updatePorts, SIGNAL(clicked()), Settings, SLOT(updatePortsSlot()));
+        QObject::connect(resetB, SIGNAL(clicked()), Settings, SLOT(resetSettingsSlot()));
+
+        languageMenu->setCurrentIndex(0);
+        okB->setDefault(true);
+
+
+        QMetaObject::connectSlotsByName(Settings);
+    } // setupUi
+
+    void retranslateUi(QDialog *Settings)
+    {
+        Settings->setWindowTitle(QCoreApplication::translate("Settings", "OSTC Companion settings", nullptr));
+        forceFirmwareUpdate->setText(QCoreApplication::translate("Settings", "Force Firmware update", nullptr));
+#if QT_CONFIG(tooltip)
+        autoSetDateTimeCB->setToolTip(QCoreApplication::translate("Settings", "If checked, any configuration upload will also setup date and time.", nullptr));
+#endif // QT_CONFIG(tooltip)
+        autoSetDateTimeCB->setText(QCoreApplication::translate("Settings", "Auto setup of date && time", nullptr));
+        forceFontlibUpdate->setText(QCoreApplication::translate("Settings", "Force FontLib update", nullptr));
+
+#if QT_CONFIG(tooltip)
+        updatePorts->setToolTip(QCoreApplication::translate("Settings", "Update port list with currently connected USB or Bluetooth devices.", nullptr));
+#endif // QT_CONFIG(tooltip)
+        resetB->setText(QCoreApplication::translate("Settings", "Restore Defaults", nullptr));
+        okB->setText(QCoreApplication::translate("Settings", "OK", nullptr));
+#if QT_CONFIG(shortcut)
+        okB->setShortcut(QCoreApplication::translate("Settings", "Return", nullptr));
+#endif // QT_CONFIG(shortcut)
+        cancelB->setText(QCoreApplication::translate("Settings", "Cancel", nullptr));
+#if QT_CONFIG(shortcut)
+        cancelB->setShortcut(QCoreApplication::translate("Settings", "Ctrl+W", nullptr));
+#endif // QT_CONFIG(shortcut)
+        label->setText(QCoreApplication::translate("Settings", "Language:", nullptr));
+        forceRTEUpdate->setText(QCoreApplication::translate("Settings", "Force RTE update", nullptr));
+        label_2->setText(QCoreApplication::translate("Settings", "Communication port:", nullptr));
+        useFastMode->setText(QCoreApplication::translate("Settings", "Enable fast mode if supported", nullptr));
+    } // retranslateUi
+
+};
+
+namespace Ui {
+    class Settings: public Ui_Settings {};
+} // namespace Ui
+
+QT_END_NAMESPACE
+
+#endif // UI_SETTINGS_H