diff --git a/include/data.h b/include/data.h
index f745e2bb..63a62a9c 100644
--- a/include/data.h
+++ b/include/data.h
@@ -150,6 +150,8 @@ R_API data_t *data_dat(data_t *first, char const *key, char const *pretty_key, c
 
     Type-safe alternative to `data_make()` and `data_append()`.
 
+    If `format` is NULL or empty then a default of "%02x" is used.
+
     Caller needs to provide a sufficiently sized buffer.
 */
 R_API data_t *data_hex(data_t *first, char const *key, char const *pretty_key, char const *format, uint8_t const *val, unsigned len, char *buf);
diff --git a/include/fatal.h b/include/fatal.h
index f451d04e..c802874c 100644
--- a/include/fatal.h
+++ b/include/fatal.h
@@ -12,6 +12,8 @@
 #ifndef INCLUDE_FATAL_H_
 #define INCLUDE_FATAL_H_
 
+#include <stdio.h> // fprintf
+
 #define STRINGIFYX(x) #x
 #define STRINGIFY(x) STRINGIFYX(x)
 #define FILE_LINE __FILE__ ":" STRINGIFY(__LINE__)
@@ -30,8 +32,9 @@
     Use like this:
 
     char *buf = malloc(size);
-    if (!buf)
+    if (!buf) {
         FATAL_MALLOC("my_func()");
+    }
 
 */
 
diff --git a/src/devices/acurite.c b/src/devices/acurite.c
index 672ae17d..79d2b87f 100644
--- a/src/devices/acurite.c
+++ b/src/devices/acurite.c
@@ -375,7 +375,6 @@ static int acurite_6045_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsign
 {
     float tempf;
     uint8_t humidity;
-    char raw_str[31], *rawp;
     uint16_t sensor_id;
     uint8_t strike_count, strike_distance;
     int battery_low, active, rfi_detect;
@@ -424,21 +423,6 @@ static int acurite_6045_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsign
     strike_distance = bb[7] & 0x1f;
     rfi_detect = (bb[7] & 0x20) == 0x20;
 
-
-    /*
-     * 2018-04-21 rct - There are still a number of unknown bits in the
-     * message that need to be figured out. Add the raw message hex to
-     * to the structured data output to allow future analysis without
-     * having to enable debug for long running rtl_433 processes.
-     */
-    rawp = (char *)raw_str;
-    for (int i=0; i < MIN(browlen, 15); i++) {
-        sprintf(rawp,"%02x",bb[i]);
-        rawp += 2;
-    }
-    *rawp = '\0';
-
-
     // Flag whether this message might need further analysis
     if ((bb[4] & 0x20) != 0) // unknown status bits, always off
         exception++;
@@ -456,10 +440,18 @@ static int acurite_6045_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsign
             "active",           "Active Mode",      DATA_INT,    active,
             "rfi",              "RFI Detect",       DATA_INT,    rfi_detect,
             "exception",        "Data Exception",   DATA_INT,    exception,
-            "raw_msg",          "Raw Message",      DATA_STRING, raw_str,
             NULL);
     /* clang-format on */
 
+    /*
+     * 2018-04-21 rct - There are still a number of unknown bits in the
+     * message that need to be figured out. Add the raw message hex to
+     * to the structured data output to allow future analysis without
+     * having to enable debug for long running rtl_433 processes.
+     */
+    char raw_str[31];
+    data = data_hex(data, "raw_msg", "Raw Message", NULL, bb, MIN(browlen, 15), raw_str);
+
     decoder_output_data(decoder, data);
 
     return 1; // If we got here 1 valid message was output
@@ -769,18 +761,6 @@ static int acurite_atlas_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsig
     uint16_t sensor_id = ((bb[0] & 0x03) << 8) | bb[1];
     char const *channel_str = acurite_getChannel(bb[0]);
 
-    // There are still a few unknown/unused bits in the message that
-    // message that could possibly hold some data. Add the raw message hex to
-    // to the structured data output to allow future analysis without
-    // having to enable debug for long running rtl_433 processes.
-    char raw_str[31], *rawp;
-    rawp = (char *)raw_str;
-    for (int i=0; i < MIN(browlen, 15); i++) {
-        sprintf(rawp,"%02x",bb[i]);
-        rawp += 2;
-    }
-    *rawp = '\0';
-
     // The sensor sends the same data three times, each of these have
     // an indicator of which one of the three it is. This means the
     // checksum and first byte will be different for each one.
@@ -922,7 +902,12 @@ static int acurite_atlas_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsig
 
     // @todo only do this if exception != 0, but would be somewhat incompatible
     data = data_int(data, "exception",  "Data Exception",   NULL,   exception);
-    data = data_str(data, "raw_msg",    "Raw Message",      NULL,   raw_str);
+    // There are still a few unknown/unused bits in the message that
+    // message that could possibly hold some data. Add the raw message hex to
+    // to the structured data output to allow future analysis without
+    // having to enable debug for long running rtl_433 processes.
+    char raw_str[31];
+    data = data_hex(data, "raw_msg", "Raw Message", NULL, bb, MIN(browlen, 15), raw_str);
 
     decoder_output_data(decoder, data);
 
diff --git a/src/devices/ert_idm.c b/src/devices/ert_idm.c
index 5d3c7e0a..ff582acb 100644
--- a/src/devices/ert_idm.c
+++ b/src/devices/ert_idm.c
@@ -199,7 +199,8 @@ static int ert_idm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
     strncpy(p, "0x", sizeof(TamperCounters_str));
     p += 2;
     for (int j = 0; j < 6; j++) {
-        p += sprintf(p, "%02X", b[13 + j]);
+        // GCC-14 is confused by sprintf()
+        p += snprintf(p, 3, "%02X", b[13 + j]);
     }
     decoder_logf_bitrow(decoder, 2, __func__, &b[13], 6 * 8, "TamperCounters_str   %s", TamperCounters_str);
 
@@ -210,7 +211,8 @@ static int ert_idm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
     strncpy(p, "0x", sizeof(PowerOutageFlags_str));
     p += 2;
     for (int j = 0; j < 6; j++) {
-        p += sprintf(p, "%02X", b[21 + j]);
+        // GCC-14 is confused by sprintf()
+        p += snprintf(p, 3, "%02X", b[21 + j]);
     }
     decoder_logf_bitrow(decoder, 2, __func__, &b[21], 6 * 8, "PowerOutageFlags_str %s", PowerOutageFlags_str);
 
@@ -449,7 +451,8 @@ static int ert_netidm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
     strncpy(p, "0x", sizeof(TamperCounters_str));
     p += 2;
     for (int j = 0; j < 6; j++) {
-        p += sprintf(p, "%02X", b[13 + j]);
+        // GCC-14 is confused by sprintf()
+        p += snprintf(p, 3, "%02X", b[13 + j]);
     }
     decoder_logf_bitrow(decoder, 2, __func__, &b[13], 6 * 8, "TamperCounters_str   %s", TamperCounters_str);
 
@@ -458,7 +461,8 @@ static int ert_netidm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
     strncpy(p, "0x", sizeof(Unknown_field_1_str));
     p += 2;
     for (int j = 0; j < 7; j++) {
-        p += sprintf(p, "%02X", b[19 + j]);
+        // GCC-14 is confused by sprintf()
+        p += snprintf(p, 3, "%02X", b[19 + j]);
     }
     decoder_logf_bitrow(decoder, 1, __func__, &b[19], 7 * 8, "Unknown_field_1 %s", Unknown_field_1_str);
 
@@ -470,7 +474,8 @@ static int ert_netidm_decode(r_device *decoder, bitbuffer_t *bitbuffer)
     strncpy(p, "0x", sizeof(Unknown_field_2_str));
     p += 2;
     for (int j = 0; j < 3; j++) {
-        p += sprintf(p, "%02X", b[29 + j]);
+        // GCC-14 is confused by sprintf()
+        p += snprintf(p, 3, "%02X", b[29 + j]);
     }
     decoder_logf_bitrow(decoder, 1, __func__, &b[29], 3 * 8, "Unknown_field_1 %s", Unknown_field_2_str);